mirror of https://github.com/nextcloud/server.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
790 lines
24 KiB
JavaScript
790 lines
24 KiB
JavaScript
/**
|
|
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-FileCopyrightText: 2015 ownCloud, Inc.
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
/* eslint-disable */
|
|
import _ from 'underscore'
|
|
import $ from 'jquery'
|
|
|
|
import IconMove from '@mdi/svg/svg/folder-move.svg?raw'
|
|
import IconCopy from '@mdi/svg/svg/folder-multiple.svg?raw'
|
|
|
|
import OC from './index.js'
|
|
import { DialogBuilder, FilePickerType, getFilePickerBuilder, spawnDialog } from '@nextcloud/dialogs'
|
|
import { translate as t } from '@nextcloud/l10n'
|
|
import { basename } from 'path'
|
|
import { defineAsyncComponent } from 'vue'
|
|
|
|
/**
|
|
* this class to ease the usage of jquery dialogs
|
|
*/
|
|
const Dialogs = {
|
|
// dialog button types
|
|
/** @deprecated use `@nextcloud/dialogs` */
|
|
YES_NO_BUTTONS: 70,
|
|
/** @deprecated use `@nextcloud/dialogs` */
|
|
OK_BUTTONS: 71,
|
|
|
|
/** @deprecated use FilePickerType from `@nextcloud/dialogs` */
|
|
FILEPICKER_TYPE_CHOOSE: 1,
|
|
/** @deprecated use FilePickerType from `@nextcloud/dialogs` */
|
|
FILEPICKER_TYPE_MOVE: 2,
|
|
/** @deprecated use FilePickerType from `@nextcloud/dialogs` */
|
|
FILEPICKER_TYPE_COPY: 3,
|
|
/** @deprecated use FilePickerType from `@nextcloud/dialogs` */
|
|
FILEPICKER_TYPE_COPY_MOVE: 4,
|
|
/** @deprecated use FilePickerType from `@nextcloud/dialogs` */
|
|
FILEPICKER_TYPE_CUSTOM: 5,
|
|
|
|
/**
|
|
* displays alert dialog
|
|
* @param {string} text content of dialog
|
|
* @param {string} title dialog title
|
|
* @param {function} callback which will be triggered when user presses OK
|
|
* @param {boolean} [modal] make the dialog modal
|
|
*
|
|
* @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
|
|
*/
|
|
alert: function(text, title, callback, modal) {
|
|
this.message(
|
|
text,
|
|
title,
|
|
'alert',
|
|
Dialogs.OK_BUTTON,
|
|
callback,
|
|
modal
|
|
)
|
|
},
|
|
|
|
/**
|
|
* displays info dialog
|
|
* @param {string} text content of dialog
|
|
* @param {string} title dialog title
|
|
* @param {function} callback which will be triggered when user presses OK
|
|
* @param {boolean} [modal] make the dialog modal
|
|
*
|
|
* @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
|
|
*/
|
|
info: function(text, title, callback, modal) {
|
|
this.message(text, title, 'info', Dialogs.OK_BUTTON, callback, modal)
|
|
},
|
|
|
|
/**
|
|
* displays confirmation dialog
|
|
* @param {string} text content of dialog
|
|
* @param {string} title dialog title
|
|
* @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
|
|
* @param {boolean} [modal] make the dialog modal
|
|
* @returns {Promise}
|
|
*
|
|
* @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
|
|
*/
|
|
confirm: function(text, title, callback, modal) {
|
|
return this.message(
|
|
text,
|
|
title,
|
|
'notice',
|
|
Dialogs.YES_NO_BUTTONS,
|
|
callback,
|
|
modal
|
|
)
|
|
},
|
|
/**
|
|
* displays confirmation dialog
|
|
* @param {string} text content of dialog
|
|
* @param {string} title dialog title
|
|
* @param {(number|{type: number, confirm: string, cancel: string, confirmClasses: string})} buttons text content of buttons
|
|
* @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
|
|
* @param {boolean} [modal] make the dialog modal
|
|
* @returns {Promise}
|
|
*
|
|
* @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
|
|
*/
|
|
confirmDestructive: function(text, title, buttons = Dialogs.OK_BUTTONS, callback = () => {}, modal) {
|
|
return (new DialogBuilder())
|
|
.setName(title)
|
|
.setText(text)
|
|
.setButtons(
|
|
buttons === Dialogs.OK_BUTTONS
|
|
? [
|
|
{
|
|
label: t('core', 'Yes'),
|
|
type: 'error',
|
|
callback: () => {
|
|
callback.clicked = true
|
|
callback(true)
|
|
},
|
|
}
|
|
]
|
|
: Dialogs._getLegacyButtons(buttons, callback)
|
|
)
|
|
.build()
|
|
.show()
|
|
.then(() => {
|
|
if (!callback.clicked) {
|
|
callback(false)
|
|
}
|
|
})
|
|
},
|
|
/**
|
|
* displays confirmation dialog
|
|
* @param {string} text content of dialog
|
|
* @param {string} title dialog title
|
|
* @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
|
|
* @param {boolean} [modal] make the dialog modal
|
|
* @returns {Promise}
|
|
*
|
|
* @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
|
|
*/
|
|
confirmHtml: function(text, title, callback, modal) {
|
|
return (new DialogBuilder())
|
|
.setName(title)
|
|
.setText('')
|
|
.setButtons([
|
|
{
|
|
label: t('core', 'No'),
|
|
callback: () => {},
|
|
},
|
|
{
|
|
label: t('core', 'Yes'),
|
|
type: 'primary',
|
|
callback: () => {
|
|
callback.clicked = true
|
|
callback(true)
|
|
},
|
|
},
|
|
])
|
|
.build()
|
|
.setHTML(text)
|
|
.show()
|
|
.then(() => {
|
|
if (!callback.clicked) {
|
|
callback(false)
|
|
}
|
|
})
|
|
},
|
|
/**
|
|
* displays prompt dialog
|
|
* @param {string} text content of dialog
|
|
* @param {string} title dialog title
|
|
* @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
|
|
* @param {boolean} [modal] make the dialog modal
|
|
* @param {string} name name of the input field
|
|
* @param {boolean} password whether the input should be a password input
|
|
* @returns {Promise}
|
|
*
|
|
* @deprecated Use NcDialog from `@nextcloud/vue` instead
|
|
*/
|
|
prompt: function(text, title, callback, modal, name, password) {
|
|
return new Promise((resolve) => {
|
|
spawnDialog(
|
|
defineAsyncComponent(() => import('../components/LegacyDialogPrompt.vue')),
|
|
{
|
|
text,
|
|
name: title,
|
|
callback,
|
|
inputName: name,
|
|
isPassword: !!password
|
|
},
|
|
(...args) => {
|
|
callback(...args)
|
|
resolve()
|
|
},
|
|
)
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Legacy wrapper to the new Vue based filepicker from `@nextcloud/dialogs`
|
|
*
|
|
* Prefer to use the Vue filepicker directly instead.
|
|
*
|
|
* In order to pick several types of mime types they need to be passed as an
|
|
* array of strings.
|
|
*
|
|
* When no mime type filter is given only files can be selected. In order to
|
|
* be able to select both files and folders "['*', 'httpd/unix-directory']"
|
|
* should be used instead.
|
|
*
|
|
* @param {string} title dialog title
|
|
* @param {Function} callback which will be triggered when user presses Choose
|
|
* @param {boolean} [multiselect] whether it should be possible to select multiple files
|
|
* @param {string[]} [mimetype] mimetype to filter by - directories will always be included
|
|
* @param {boolean} [_modal] do not use
|
|
* @param {string} [type] Type of file picker : Choose, copy, move, copy and move
|
|
* @param {string} [path] path to the folder that the the file can be picket from
|
|
* @param {object} [options] additonal options that need to be set
|
|
* @param {Function} [options.filter] filter function for advanced filtering
|
|
* @param {boolean} [options.allowDirectoryChooser] Allow to select directories
|
|
* @deprecated since 27.1.0 use the filepicker from `@nextcloud/dialogs` instead
|
|
*/
|
|
filepicker(title, callback, multiselect = false, mimetype = undefined, _modal = undefined, type = FilePickerType.Choose, path = undefined, options = undefined) {
|
|
|
|
/**
|
|
* Create legacy callback wrapper to support old filepicker syntax
|
|
* @param fn The original callback
|
|
* @param type The file picker type which was used to pick the file(s)
|
|
*/
|
|
const legacyCallback = (fn, type) => {
|
|
const getPath = (node) => {
|
|
const root = node?.root || ''
|
|
let path = node?.path || ''
|
|
// TODO: Fix this in @nextcloud/files
|
|
if (path.startsWith(root)) {
|
|
path = path.slice(root.length) || '/'
|
|
}
|
|
return path
|
|
}
|
|
|
|
if (multiselect) {
|
|
return (nodes) => fn(nodes.map(getPath), type)
|
|
} else {
|
|
return (nodes) => fn(getPath(nodes[0]), type)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Coverting a Node into a legacy file info to support the OC.dialogs.filepicker filter function
|
|
* @param node The node to convert
|
|
*/
|
|
const nodeToLegacyFile = (node) => ({
|
|
id: node.fileid || null,
|
|
path: node.path,
|
|
mimetype: node.mime || null,
|
|
mtime: node.mtime?.getTime() || null,
|
|
permissions: node.permissions,
|
|
name: node.attributes?.displayName || node.basename,
|
|
etag: node.attributes?.etag || null,
|
|
hasPreview: node.attributes?.hasPreview || null,
|
|
mountType: node.attributes?.mountType || null,
|
|
quotaAvailableBytes: node.attributes?.quotaAvailableBytes || null,
|
|
icon: null,
|
|
sharePermissions: null,
|
|
})
|
|
|
|
const builder = getFilePickerBuilder(title)
|
|
|
|
// Setup buttons
|
|
if (type === this.FILEPICKER_TYPE_CUSTOM) {
|
|
(options.buttons || []).forEach((button) => {
|
|
builder.addButton({
|
|
callback: legacyCallback(callback, button.type),
|
|
label: button.text,
|
|
type: button.defaultButton ? 'primary' : 'secondary',
|
|
})
|
|
})
|
|
} else {
|
|
builder.setButtonFactory((nodes, path) => {
|
|
const buttons = []
|
|
const node = nodes?.[0]?.attributes?.displayName || nodes?.[0]?.basename
|
|
const target = node || basename(path)
|
|
|
|
if (type === FilePickerType.Choose) {
|
|
buttons.push({
|
|
callback: legacyCallback(callback, FilePickerType.Choose),
|
|
label: node && !this.multiSelect ? t('core', 'Choose {file}', { file: node }) : t('core', 'Choose'),
|
|
type: 'primary',
|
|
})
|
|
}
|
|
if (type === FilePickerType.CopyMove || type === FilePickerType.Copy) {
|
|
buttons.push({
|
|
callback: legacyCallback(callback, FilePickerType.Copy),
|
|
label: target ? t('core', 'Copy to {target}', { target }) : t('core', 'Copy'),
|
|
type: 'primary',
|
|
icon: IconCopy,
|
|
})
|
|
}
|
|
if (type === FilePickerType.Move || type === FilePickerType.CopyMove) {
|
|
buttons.push({
|
|
callback: legacyCallback(callback, FilePickerType.Move),
|
|
label: target ? t('core', 'Move to {target}', { target }) : t('core', 'Move'),
|
|
type: type === FilePickerType.Move ? 'primary' : 'secondary',
|
|
icon: IconMove,
|
|
})
|
|
}
|
|
return buttons
|
|
})
|
|
}
|
|
|
|
if (mimetype) {
|
|
builder.setMimeTypeFilter(typeof mimetype === 'string' ? [mimetype] : (mimetype || []))
|
|
}
|
|
if (typeof options?.filter === 'function') {
|
|
builder.setFilter((node) => options.filter(nodeToLegacyFile(node)))
|
|
}
|
|
builder.allowDirectories(options?.allowDirectoryChooser === true || mimetype?.includes('httpd/unix-directory') || false)
|
|
.setMultiSelect(multiselect)
|
|
.startAt(path)
|
|
.build()
|
|
.pick()
|
|
},
|
|
|
|
/**
|
|
* Displays raw dialog
|
|
* You better use a wrapper instead ...
|
|
*
|
|
* @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
|
|
*/
|
|
message: function(content, title, dialogType, buttons, callback = () => {}, modal, allowHtml) {
|
|
const builder = (new DialogBuilder())
|
|
.setName(title)
|
|
.setText(allowHtml ? '' : content)
|
|
.setButtons(Dialogs._getLegacyButtons(buttons, callback))
|
|
|
|
switch (dialogType) {
|
|
case 'alert':
|
|
builder.setSeverity('warning')
|
|
break
|
|
case 'notice':
|
|
builder.setSeverity('info')
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
|
|
const dialog = builder.build()
|
|
|
|
if (allowHtml) {
|
|
dialog.setHTML(content)
|
|
}
|
|
|
|
return dialog.show().then(() => {
|
|
if(!callback._clicked) {
|
|
callback(false)
|
|
}
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Helper for legacy API
|
|
* @deprecated
|
|
*/
|
|
_getLegacyButtons(buttons, callback) {
|
|
const buttonList = []
|
|
|
|
switch (typeof buttons === 'object' ? buttons.type : buttons) {
|
|
case Dialogs.YES_NO_BUTTONS:
|
|
buttonList.push({
|
|
label: buttons?.cancel ?? t('core', 'No'),
|
|
callback: () => {
|
|
callback._clicked = true
|
|
callback(false)
|
|
},
|
|
})
|
|
buttonList.push({
|
|
label: buttons?.confirm ?? t('core', 'Yes'),
|
|
type: 'primary',
|
|
callback: () => {
|
|
callback._clicked = true
|
|
callback(true)
|
|
},
|
|
})
|
|
break
|
|
case Dialogs.OK_BUTTONS:
|
|
buttonList.push({
|
|
label: buttons?.confirm ?? t('core', 'OK'),
|
|
type: 'primary',
|
|
callback: () => {
|
|
callback._clicked = true
|
|
callback(true)
|
|
},
|
|
})
|
|
break
|
|
default:
|
|
console.error('Invalid call to OC.dialogs')
|
|
break
|
|
}
|
|
return buttonList
|
|
},
|
|
|
|
_fileexistsshown: false,
|
|
/**
|
|
* Displays file exists dialog
|
|
* @param {object} data upload object
|
|
* @param {object} original file with name, size and mtime
|
|
* @param {object} replacement file with name, size and mtime
|
|
* @param {object} controller with onCancel, onSkip, onReplace and onRename methods
|
|
* @returns {Promise} jquery promise that resolves after the dialog template was loaded
|
|
*
|
|
* @deprecated 29.0.0 Use openConflictPicker from the @nextcloud/upload package instead
|
|
*/
|
|
fileexists: function(data, original, replacement, controller) {
|
|
var self = this
|
|
var dialogDeferred = new $.Deferred()
|
|
|
|
var getCroppedPreview = function(file) {
|
|
var deferred = new $.Deferred()
|
|
// Only process image files.
|
|
var type = file.type && file.type.split('/').shift()
|
|
if (window.FileReader && type === 'image') {
|
|
var reader = new FileReader()
|
|
reader.onload = function(e) {
|
|
var blob = new Blob([e.target.result])
|
|
window.URL = window.URL || window.webkitURL
|
|
var originalUrl = window.URL.createObjectURL(blob)
|
|
var image = new Image()
|
|
image.src = originalUrl
|
|
image.onload = function() {
|
|
var url = crop(image)
|
|
deferred.resolve(url)
|
|
}
|
|
}
|
|
reader.readAsArrayBuffer(file)
|
|
} else {
|
|
deferred.reject()
|
|
}
|
|
return deferred
|
|
}
|
|
|
|
var crop = function(img) {
|
|
var canvas = document.createElement('canvas')
|
|
var targetSize = 96
|
|
var width = img.width
|
|
var height = img.height
|
|
var x; var y; var size
|
|
|
|
// Calculate the width and height, constraining the proportions
|
|
if (width > height) {
|
|
y = 0
|
|
x = (width - height) / 2
|
|
} else {
|
|
y = (height - width) / 2
|
|
x = 0
|
|
}
|
|
size = Math.min(width, height)
|
|
|
|
// Set canvas size to the cropped area
|
|
canvas.width = size
|
|
canvas.height = size
|
|
var ctx = canvas.getContext('2d')
|
|
ctx.drawImage(img, x, y, size, size, 0, 0, size, size)
|
|
|
|
// Resize the canvas to match the destination (right size uses 96px)
|
|
resampleHermite(canvas, size, size, targetSize, targetSize)
|
|
|
|
return canvas.toDataURL('image/png', 0.7)
|
|
}
|
|
|
|
/**
|
|
* Fast image resize/resample using Hermite filter with JavaScript.
|
|
*
|
|
* @author: ViliusL
|
|
*
|
|
* @param {*} canvas
|
|
* @param {number} W
|
|
* @param {number} H
|
|
* @param {number} W2
|
|
* @param {number} H2
|
|
*/
|
|
var resampleHermite = function(canvas, W, H, W2, H2) {
|
|
W2 = Math.round(W2)
|
|
H2 = Math.round(H2)
|
|
var img = canvas.getContext('2d').getImageData(0, 0, W, H)
|
|
var img2 = canvas.getContext('2d').getImageData(0, 0, W2, H2)
|
|
var data = img.data
|
|
var data2 = img2.data
|
|
var ratio_w = W / W2
|
|
var ratio_h = H / H2
|
|
var ratio_w_half = Math.ceil(ratio_w / 2)
|
|
var ratio_h_half = Math.ceil(ratio_h / 2)
|
|
|
|
for (var j = 0; j < H2; j++) {
|
|
for (var i = 0; i < W2; i++) {
|
|
var x2 = (i + j * W2) * 4
|
|
var weight = 0
|
|
var weights = 0
|
|
var weights_alpha = 0
|
|
var gx_r = 0
|
|
var gx_g = 0
|
|
var gx_b = 0
|
|
var gx_a = 0
|
|
var center_y = (j + 0.5) * ratio_h
|
|
for (var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) {
|
|
var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half
|
|
var center_x = (i + 0.5) * ratio_w
|
|
var w0 = dy * dy // pre-calc part of w
|
|
for (var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) {
|
|
var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half
|
|
var w = Math.sqrt(w0 + dx * dx)
|
|
if (w >= -1 && w <= 1) {
|
|
// hermite filter
|
|
weight = 2 * w * w * w - 3 * w * w + 1
|
|
if (weight > 0) {
|
|
dx = 4 * (xx + yy * W)
|
|
// alpha
|
|
gx_a += weight * data[dx + 3]
|
|
weights_alpha += weight
|
|
// colors
|
|
if (data[dx + 3] < 255) { weight = weight * data[dx + 3] / 250 }
|
|
gx_r += weight * data[dx]
|
|
gx_g += weight * data[dx + 1]
|
|
gx_b += weight * data[dx + 2]
|
|
weights += weight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
data2[x2] = gx_r / weights
|
|
data2[x2 + 1] = gx_g / weights
|
|
data2[x2 + 2] = gx_b / weights
|
|
data2[x2 + 3] = gx_a / weights_alpha
|
|
}
|
|
}
|
|
canvas.getContext('2d').clearRect(0, 0, Math.max(W, W2), Math.max(H, H2))
|
|
canvas.width = W2
|
|
canvas.height = H2
|
|
canvas.getContext('2d').putImageData(img2, 0, 0)
|
|
}
|
|
|
|
var addConflict = function($conflicts, original, replacement) {
|
|
|
|
var $conflict = $conflicts.find('.template').clone().removeClass('template').addClass('conflict')
|
|
var $originalDiv = $conflict.find('.original')
|
|
var $replacementDiv = $conflict.find('.replacement')
|
|
|
|
$conflict.data('data', data)
|
|
|
|
$conflict.find('.filename').text(original.name)
|
|
$originalDiv.find('.size').text(OC.Util.humanFileSize(original.size))
|
|
$originalDiv.find('.mtime').text(OC.Util.formatDate(original.mtime))
|
|
// ie sucks
|
|
if (replacement.size && replacement.lastModified) {
|
|
$replacementDiv.find('.size').text(OC.Util.humanFileSize(replacement.size))
|
|
$replacementDiv.find('.mtime').text(OC.Util.formatDate(replacement.lastModified))
|
|
}
|
|
var path = original.directory + '/' + original.name
|
|
var urlSpec = {
|
|
file: path,
|
|
x: 96,
|
|
y: 96,
|
|
c: original.etag,
|
|
forceIcon: 0
|
|
}
|
|
var previewpath = Files.generatePreviewUrl(urlSpec)
|
|
// Escaping single quotes
|
|
previewpath = previewpath.replace(/'/g, '%27')
|
|
$originalDiv.find('.icon').css({ 'background-image': "url('" + previewpath + "')" })
|
|
getCroppedPreview(replacement).then(
|
|
function(path) {
|
|
$replacementDiv.find('.icon').css('background-image', 'url(' + path + ')')
|
|
}, function() {
|
|
path = OC.MimeType.getIconUrl(replacement.type)
|
|
$replacementDiv.find('.icon').css('background-image', 'url(' + path + ')')
|
|
}
|
|
)
|
|
// connect checkboxes with labels
|
|
var checkboxId = $conflicts.find('.conflict').length
|
|
$originalDiv.find('input:checkbox').attr('id', 'checkbox_original_' + checkboxId)
|
|
$replacementDiv.find('input:checkbox').attr('id', 'checkbox_replacement_' + checkboxId)
|
|
|
|
$conflicts.append($conflict)
|
|
|
|
// set more recent mtime bold
|
|
// ie sucks
|
|
if (replacement.lastModified > original.mtime) {
|
|
$replacementDiv.find('.mtime').css('font-weight', 'bold')
|
|
} else if (replacement.lastModified < original.mtime) {
|
|
$originalDiv.find('.mtime').css('font-weight', 'bold')
|
|
} else {
|
|
// TODO add to same mtime collection?
|
|
}
|
|
|
|
// set bigger size bold
|
|
if (replacement.size && replacement.size > original.size) {
|
|
$replacementDiv.find('.size').css('font-weight', 'bold')
|
|
} else if (replacement.size && replacement.size < original.size) {
|
|
$originalDiv.find('.size').css('font-weight', 'bold')
|
|
} else {
|
|
// TODO add to same size collection?
|
|
}
|
|
|
|
// TODO show skip action for files with same size and mtime in bottom row
|
|
|
|
// always keep readonly files
|
|
|
|
if (original.status === 'readonly') {
|
|
$originalDiv
|
|
.addClass('readonly')
|
|
.find('input[type="checkbox"]')
|
|
.prop('checked', true)
|
|
.prop('disabled', true)
|
|
$originalDiv.find('.message')
|
|
.text(t('core', 'read-only'))
|
|
}
|
|
}
|
|
// var selection = controller.getSelection(data.originalFiles);
|
|
// if (selection.defaultAction) {
|
|
// controller[selection.defaultAction](data);
|
|
// } else {
|
|
var dialogName = 'oc-dialog-fileexists-content'
|
|
var dialogId = '#' + dialogName
|
|
if (this._fileexistsshown) {
|
|
// add conflict
|
|
|
|
var $conflicts = $(dialogId + ' .conflicts')
|
|
addConflict($conflicts, original, replacement)
|
|
|
|
var count = $(dialogId + ' .conflict').length
|
|
var title = n('core',
|
|
'{count} file conflict',
|
|
'{count} file conflicts',
|
|
count,
|
|
{ count: count }
|
|
)
|
|
$(dialogId).parent().children('.oc-dialog-title').text(title)
|
|
|
|
// recalculate dimensions
|
|
$(window).trigger('resize')
|
|
dialogDeferred.resolve()
|
|
} else {
|
|
// create dialog
|
|
this._fileexistsshown = true
|
|
$.when(this._getFileExistsTemplate()).then(function($tmpl) {
|
|
var title = t('core', 'One file conflict')
|
|
var $dlg = $tmpl.octemplate({
|
|
dialog_name: dialogName,
|
|
title: title,
|
|
type: 'fileexists',
|
|
|
|
allnewfiles: t('core', 'New Files'),
|
|
allexistingfiles: t('core', 'Already existing files'),
|
|
|
|
why: t('core', 'Which files do you want to keep?'),
|
|
what: t('core', 'If you select both versions, the copied file will have a number added to its name.')
|
|
})
|
|
$('body').append($dlg)
|
|
|
|
if (original && replacement) {
|
|
var $conflicts = $dlg.find('.conflicts')
|
|
addConflict($conflicts, original, replacement)
|
|
}
|
|
|
|
var buttonlist = [{
|
|
text: t('core', 'Cancel'),
|
|
classes: 'cancel',
|
|
click: function() {
|
|
if (typeof controller.onCancel !== 'undefined') {
|
|
controller.onCancel(data)
|
|
}
|
|
$(dialogId).ocdialog('close')
|
|
}
|
|
},
|
|
{
|
|
text: t('core', 'Continue'),
|
|
classes: 'continue',
|
|
click: function() {
|
|
if (typeof controller.onContinue !== 'undefined') {
|
|
controller.onContinue($(dialogId + ' .conflict'))
|
|
}
|
|
$(dialogId).ocdialog('close')
|
|
}
|
|
}]
|
|
|
|
$(dialogId).ocdialog({
|
|
width: 500,
|
|
closeOnEscape: true,
|
|
modal: true,
|
|
buttons: buttonlist,
|
|
closeButton: null,
|
|
close: function() {
|
|
self._fileexistsshown = false
|
|
try {
|
|
$(this).ocdialog('destroy').remove()
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
}
|
|
})
|
|
|
|
$(dialogId).css('height', 'auto')
|
|
|
|
var $primaryButton = $dlg.closest('.oc-dialog').find('button.continue')
|
|
$primaryButton.prop('disabled', true)
|
|
|
|
function updatePrimaryButton() {
|
|
var checkedCount = $dlg.find('.conflicts .checkbox:checked').length
|
|
$primaryButton.prop('disabled', checkedCount === 0)
|
|
}
|
|
|
|
// add checkbox toggling actions
|
|
$(dialogId).find('.allnewfiles').on('click', function() {
|
|
var $checkboxes = $(dialogId).find('.conflict .replacement input[type="checkbox"]')
|
|
$checkboxes.prop('checked', $(this).prop('checked'))
|
|
})
|
|
$(dialogId).find('.allexistingfiles').on('click', function() {
|
|
var $checkboxes = $(dialogId).find('.conflict .original:not(.readonly) input[type="checkbox"]')
|
|
$checkboxes.prop('checked', $(this).prop('checked'))
|
|
})
|
|
$(dialogId).find('.conflicts').on('click', '.replacement,.original:not(.readonly)', function() {
|
|
var $checkbox = $(this).find('input[type="checkbox"]')
|
|
$checkbox.prop('checked', !$checkbox.prop('checked'))
|
|
})
|
|
$(dialogId).find('.conflicts').on('click', '.replacement input[type="checkbox"],.original:not(.readonly) input[type="checkbox"]', function() {
|
|
var $checkbox = $(this)
|
|
$checkbox.prop('checked', !$checkbox.prop('checked'))
|
|
})
|
|
|
|
// update counters
|
|
$(dialogId).on('click', '.replacement,.allnewfiles', function() {
|
|
var count = $(dialogId).find('.conflict .replacement input[type="checkbox"]:checked').length
|
|
if (count === $(dialogId + ' .conflict').length) {
|
|
$(dialogId).find('.allnewfiles').prop('checked', true)
|
|
$(dialogId).find('.allnewfiles + .count').text(t('core', '(all selected)'))
|
|
} else if (count > 0) {
|
|
$(dialogId).find('.allnewfiles').prop('checked', false)
|
|
$(dialogId).find('.allnewfiles + .count').text(t('core', '({count} selected)', { count: count }))
|
|
} else {
|
|
$(dialogId).find('.allnewfiles').prop('checked', false)
|
|
$(dialogId).find('.allnewfiles + .count').text('')
|
|
}
|
|
updatePrimaryButton()
|
|
})
|
|
$(dialogId).on('click', '.original,.allexistingfiles', function() {
|
|
var count = $(dialogId).find('.conflict .original input[type="checkbox"]:checked').length
|
|
if (count === $(dialogId + ' .conflict').length) {
|
|
$(dialogId).find('.allexistingfiles').prop('checked', true)
|
|
$(dialogId).find('.allexistingfiles + .count').text(t('core', '(all selected)'))
|
|
} else if (count > 0) {
|
|
$(dialogId).find('.allexistingfiles').prop('checked', false)
|
|
$(dialogId).find('.allexistingfiles + .count')
|
|
.text(t('core', '({count} selected)', { count: count }))
|
|
} else {
|
|
$(dialogId).find('.allexistingfiles').prop('checked', false)
|
|
$(dialogId).find('.allexistingfiles + .count').text('')
|
|
}
|
|
updatePrimaryButton()
|
|
})
|
|
|
|
dialogDeferred.resolve()
|
|
})
|
|
.fail(function() {
|
|
dialogDeferred.reject()
|
|
alert(t('core', 'Error loading file exists template'))
|
|
})
|
|
}
|
|
// }
|
|
return dialogDeferred.promise()
|
|
},
|
|
|
|
_getFileExistsTemplate: function() {
|
|
var defer = $.Deferred()
|
|
if (!this.$fileexistsTemplate) {
|
|
var self = this
|
|
$.get(OC.filePath('files', 'templates', 'fileexists.html'), function(tmpl) {
|
|
self.$fileexistsTemplate = $(tmpl)
|
|
defer.resolve(self.$fileexistsTemplate)
|
|
})
|
|
.fail(function() {
|
|
defer.reject()
|
|
})
|
|
} else {
|
|
defer.resolve(this.$fileexistsTemplate)
|
|
}
|
|
return defer.promise()
|
|
},
|
|
}
|
|
|
|
export default Dialogs
|