diff options
author | Christoph Wurst <christoph@winzerhof-wurst.at> | 2019-04-29 18:16:15 +0200 |
---|---|---|
committer | Christoph Wurst <christoph@winzerhof-wurst.at> | 2019-04-30 11:11:24 +0200 |
commit | 35d8e7a4e036dc7eda504a4fd7e5f587cfb69cc2 (patch) | |
tree | d7a27d66637b04dee96caee564cdc28fc5498646 /core/src | |
parent | 2e0199a04a077db17a74a6018b8f3957f0b37eb9 (diff) | |
download | nextcloud-server-35d8e7a4e036dc7eda504a4fd7e5f587cfb69cc2.tar.gz nextcloud-server-35d8e7a4e036dc7eda504a4fd7e5f587cfb69cc2.zip |
Move OC.dialogs to the main bundle
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/OC/dialogs.js | 1216 | ||||
-rw-r--r-- | core/src/OC/index.js | 2 | ||||
-rw-r--r-- | core/src/globals.js | 15 |
3 files changed, 1232 insertions, 1 deletions
diff --git a/core/src/OC/dialogs.js b/core/src/OC/dialogs.js new file mode 100644 index 00000000000..6308927093b --- /dev/null +++ b/core/src/OC/dialogs.js @@ -0,0 +1,1216 @@ +/* global alert */ + +/* + * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import _ from 'underscore' +import $ from 'jquery' + +import OC from './index' +import OCA from '../OCA/index' + +/** + * this class to ease the usage of jquery dialogs + */ +const Dialogs = { + // dialog button types + YES_NO_BUTTONS: 70, + OK_BUTTONS: 71, + + FILEPICKER_TYPE_CHOOSE: 1, + FILEPICKER_TYPE_MOVE: 2, + FILEPICKER_TYPE_COPY: 3, + FILEPICKER_TYPE_COPY_MOVE: 4, + + // used to name each dialog + dialogsCounter: 0, + /** + * displays alert dialog + * @param text content of dialog + * @param title dialog title + * @param callback which will be triggered when user presses OK + * @param modal make the dialog modal + */ + alert: function (text, title, callback, modal) { + this.message( + text, + title, + 'alert', + Dialogs.OK_BUTTON, + callback, + modal + ); + }, + /** + * displays info dialog + * @param text content of dialog + * @param title dialog title + * @param callback which will be triggered when user presses OK + * @param modal make the dialog modal + */ + info: function (text, title, callback, modal) { + this.message(text, title, 'info', Dialogs.OK_BUTTON, callback, modal); + }, + /** + * displays confirmation dialog + * @param text content of dialog + * @param title dialog title + * @param callback which will be triggered when user presses YES or NO + * (true or false would be passed to callback respectively) + * @param modal make the dialog modal + */ + confirm: function (text, title, callback, modal) { + return this.message( + text, + title, + 'notice', + Dialogs.YES_NO_BUTTONS, + callback, + modal + ); + }, + /** + * displays confirmation dialog + * @param text content of dialog + * @param title dialog title + * @param callback which will be triggered when user presses YES or NO + * (true or false would be passed to callback respectively) + * @param modal make the dialog modal + */ + confirmHtml: function (text, title, callback, modal) { + return this.message( + text, + title, + 'notice', + Dialogs.YES_NO_BUTTONS, + callback, + modal, + true + ); + }, + /** + * displays prompt dialog + * @param text content of dialog + * @param title dialog title + * @param callback which will be triggered when user presses YES or NO + * (true or false would be passed to callback respectively) + * @param modal make the dialog modal + * @param name name of the input field + * @param password whether the input should be a password input + */ + prompt: function (text, title, callback, modal, name, password) { + return $.when(this._getMessageTemplate()).then(function ($tmpl) { + var dialogName = 'oc-dialog-' + Dialogs.dialogsCounter + '-content'; + var dialogId = '#' + dialogName; + var $dlg = $tmpl.octemplate({ + dialog_name: dialogName, + title: title, + message: text, + type: 'notice' + }); + var input = $('<input/>'); + input.attr('type', password ? 'password' : 'text').attr('id', dialogName + '-input').attr('placeholder', name); + var label = $('<label/>').attr('for', dialogName + '-input').text(name + ': '); + $dlg.append(label); + $dlg.append(input); + if (modal === undefined) { + modal = false; + } + $('body').append($dlg); + + // wrap callback in _.once(): + // only call callback once and not twice (button handler and close + // event) but call it for the close event, if ESC or the x is hit + if (callback !== undefined) { + callback = _.once(callback); + } + + var buttonlist = [{ + text: t('core', 'No'), + click: function () { + if (callback !== undefined) { + callback(false, input.val()); + } + $(dialogId).ocdialog('close'); + } + }, { + text: t('core', 'Yes'), + click: function () { + if (callback !== undefined) { + callback(true, input.val()); + } + $(dialogId).ocdialog('close'); + }, + defaultButton: true + } + ]; + + $(dialogId).ocdialog({ + closeOnEscape: true, + modal: modal, + buttons: buttonlist, + close: function () { + // callback is already fired if Yes/No is clicked directly + if (callback !== undefined) { + callback(false, input.val()); + } + } + }); + input.focus(); + Dialogs.dialogsCounter++; + }); + }, + /** + * show a file picker to pick a file from + * + * 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 title dialog title + * @param callback which will be triggered when user presses Choose + * @param multiselect whether it should be possible to select multiple files + * @param mimetypeFilter mimetype to filter by - directories will always be included + * @param modal make the dialog modal + * @param type Type of file picker : Choose, copy, move, copy and move + * @param path path to the folder that the the file can be picket from + */ + filepicker: function (title, callback, multiselect, mimetypeFilter, modal, type, path) { + var self = this; + + this.filepicker.sortField = 'name'; + this.filepicker.sortOrder = 'asc'; + // avoid opening the picker twice + if (this.filepicker.loading) { + return; + } + + if (type === undefined) { + type = this.FILEPICKER_TYPE_CHOOSE; + } + + var emptyText = t('core', 'No files in here'); + var newText = t('files', 'New folder'); + if (type === this.FILEPICKER_TYPE_COPY || type === this.FILEPICKER_TYPE_MOVE || type === this.FILEPICKER_TYPE_COPY_MOVE) { + emptyText = t('core', 'No more subfolders in here'); + } + + this.filepicker.loading = true; + this.filepicker.filesClient = (OCA.Sharing && OCA.Sharing.PublicApp && OCA.Sharing.PublicApp.fileList) ? OCA.Sharing.PublicApp.fileList.filesClient : OC.Files.getClient(); + + this.filelist = null; + path = path || ''; + + $.when(this._getFilePickerTemplate()).then(function ($tmpl) { + self.filepicker.loading = false; + var dialogName = 'oc-dialog-filepicker-content'; + if (self.$filePicker) { + self.$filePicker.ocdialog('close'); + } + + if (mimetypeFilter === undefined || mimetypeFilter === null) { + mimetypeFilter = []; + } + if (typeof (mimetypeFilter) === "string") { + mimetypeFilter = [mimetypeFilter]; + } + + self.$filePicker = $tmpl.octemplate({ + dialog_name: dialogName, + title: title, + emptytext: emptyText, + newtext: newText, + nameCol: t('core', 'Name'), + sizeCol: t('core', 'Size'), + modifiedCol: t('core', 'Modified') + }).data('path', path).data('multiselect', multiselect).data('mimetype', mimetypeFilter); + + if (modal === undefined) { + modal = false; + } + if (multiselect === undefined) { + multiselect = false; + } + + // No grid for IE! + if (OC.Util.isIE()) { + self.$filePicker.find('#picker-view-toggle').remove(); + self.$filePicker.find('#picker-filestable').removeClass('view-grid'); + } + + $('body').append(self.$filePicker); + + self.$showGridView = $('input#picker-showgridview'); + self.$showGridView.on('change', _.bind(self._onGridviewChange, self)); + + if (!OC.Util.isIE()) { + self._getGridSettings(); + } + + var newButton = self.$filePicker.find('.actions.creatable .button-add'); + if (type === self.FILEPICKER_TYPE_CHOOSE) { + newButton.hide(); + } + newButton.on('focus', function () { + self.$filePicker.ocdialog('setEnterCallback', function () { + event.stopImmediatePropagation(); + event.preventDefault(); + newButton.click(); + }); + }); + newButton.on('blur', function () { + self.$filePicker.ocdialog('unsetEnterCallback'); + }); + + OC.registerMenu(newButton, self.$filePicker.find('.menu'), function () { + $input.focus(); + self.$filePicker.ocdialog('setEnterCallback', function () { + event.stopImmediatePropagation(); + event.preventDefault(); + self.$form.submit(); + }); + var newName = $input.val(); + var lastPos = newName.lastIndexOf('.'); + if (lastPos === -1) { + lastPos = newName.length; + } + $input.selectRange(0, lastPos); + }); + var $form = self.$filePicker.find('.filenameform'); + var $input = $form.find('input[type=\'text\']'); + var $submit = $form.find('input[type=\'submit\']'); + $submit.on('click', function (event) { + event.stopImmediatePropagation(); + event.preventDefault(); + $form.submit(); + }); + + var checkInput = function () { + var filename = $input.val(); + try { + if (!Files.isFileNameValid(filename)) { + // Files.isFileNameValid(filename) throws an exception itself + } else if (self.filelist.find(function (file) { + return file.name === this; + }, filename)) { + throw t('files', '{newName} already exists', {newName: filename}, undefined, { + escape: false + }); + } else { + return true; + } + } catch (error) { + $input.attr('title', error); + $input.tooltip({ + placement: 'right', + trigger: 'manual', + 'container': '.newFolderMenu' + }); + $input.tooltip('fixTitle'); + $input.tooltip('show'); + $input.addClass('error'); + } + return false; + }; + + $form.on('submit', function (event) { + event.stopPropagation(); + event.preventDefault(); + + if (checkInput()) { + var newname = $input.val(); + self.filepicker.filesClient.createDirectory(self.$filePicker.data('path') + "/" + newname).always(function (status) { + self._fillFilePicker(self.$filePicker.data('path') + newname); + }); + OC.hideMenus(); + self.$filePicker.ocdialog('unsetEnterCallback'); + self.$filePicker.click(); + $input.val(newText); + } + }); + $input.keypress(function (event) { + if (event.keyCode === 13 || event.which === 13) { + event.stopImmediatePropagation(); + event.preventDefault(); + $form.submit(); + } + }); + + self.$filePicker.ready(function () { + self.$fileListHeader = self.$filePicker.find('.filelist thead tr'); + self.$filelist = self.$filePicker.find('.filelist tbody'); + self.$filelistContainer = self.$filePicker.find('.filelist-container'); + self.$dirTree = self.$filePicker.find('.dirtree'); + self.$dirTree.on('click', 'div:not(:last-child)', self, function (event) { + self._handleTreeListSelect(event, type); + }); + self.$filelist.on('click', 'tr', function (event) { + self._handlePickerClick(event, $(this), type); + }); + self.$fileListHeader.on('click', 'a', function (event) { + var dir = self.$filePicker.data('path'); + self.filepicker.sortField = $(event.currentTarget).data('sort'); + self.filepicker.sortOrder = self.filepicker.sortOrder === 'asc' ? 'desc' : 'asc'; + self._fillFilePicker(dir); + }); + self._fillFilePicker(path); + }); + + // build buttons + var functionToCall = function (returnType) { + if (callback !== undefined) { + var datapath; + if (multiselect === true) { + datapath = []; + self.$filelist.find('tr.filepicker_element_selected').each(function (index, element) { + datapath.push(self.$filePicker.data('path') + '/' + $(element).data('entryname')); + }); + } else { + datapath = self.$filePicker.data('path'); + var selectedName = self.$filelist.find('tr.filepicker_element_selected').data('entryname'); + if (selectedName) { + datapath += '/' + selectedName; + } + } + callback(datapath, returnType); + self.$filePicker.ocdialog('close'); + } + }; + + var chooseCallback = function () { + functionToCall(Dialogs.FILEPICKER_TYPE_CHOOSE); + }; + + var copyCallback = function () { + functionToCall(Dialogs.FILEPICKER_TYPE_COPY); + }; + + var moveCallback = function () { + functionToCall(Dialogs.FILEPICKER_TYPE_MOVE); + }; + + var buttonlist = []; + if (type === Dialogs.FILEPICKER_TYPE_CHOOSE) { + buttonlist.push({ + text: t('core', 'Choose'), + click: chooseCallback, + defaultButton: true + }); + } else { + if (type === Dialogs.FILEPICKER_TYPE_COPY || type === Dialogs.FILEPICKER_TYPE_COPY_MOVE) { + buttonlist.push({ + text: t('core', 'Copy'), + click: copyCallback, + defaultButton: false + }); + } + if (type === Dialogs.FILEPICKER_TYPE_MOVE || type === Dialogs.FILEPICKER_TYPE_COPY_MOVE) { + buttonlist.push({ + text: t('core', 'Move'), + click: moveCallback, + defaultButton: true + }); + } + } + + self.$filePicker.ocdialog({ + closeOnEscape: true, + // max-width of 600 + width: 600, + height: 500, + modal: modal, + buttons: buttonlist, + style: { + buttons: 'aside', + }, + close: function () { + try { + $(this).ocdialog('destroy').remove(); + } catch (e) { + } + self.$filePicker = null; + } + }); + + // We can access primary class only from oc-dialog. + // Hence this is one of the approach to get the choose button. + var getOcDialog = self.$filePicker.closest('.oc-dialog'); + var buttonEnableDisable = getOcDialog.find('.primary'); + if (self.$filePicker.data('mimetype').indexOf("httpd/unix-directory") !== -1) { + buttonEnableDisable.prop("disabled", false); + } else { + buttonEnableDisable.prop("disabled", true); + } + }) + .fail(function (status, error) { + // If the method is called while navigating away + // from the page, it is probably not needed ;) + self.filepicker.loading = false; + if (status !== 0) { + alert(t('core', 'Error loading file picker template: {error}', {error: error})); + } + }); + }, + /** + * Displays raw dialog + * You better use a wrapper instead ... + */ + message: function (content, title, dialogType, buttons, callback, modal, allowHtml) { + return $.when(this._getMessageTemplate()).then(function ($tmpl) { + var dialogName = 'oc-dialog-' + Dialogs.dialogsCounter + '-content'; + var dialogId = '#' + dialogName; + var $dlg = $tmpl.octemplate({ + dialog_name: dialogName, + title: title, + message: content, + type: dialogType + }, allowHtml ? {escapeFunction: ''} : {}); + if (modal === undefined) { + modal = false; + } + $('body').append($dlg); + var buttonlist = []; + switch (buttons) { + case Dialogs.YES_NO_BUTTONS: + buttonlist = [{ + text: t('core', 'No'), + click: function () { + if (callback !== undefined) { + callback(false); + } + $(dialogId).ocdialog('close'); + } + }, + { + text: t('core', 'Yes'), + click: function () { + if (callback !== undefined) { + callback(true); + } + $(dialogId).ocdialog('close'); + }, + defaultButton: true + }]; + break; + case Dialogs.OK_BUTTON: + var functionToCall = function () { + $(dialogId).ocdialog('close'); + if (callback !== undefined) { + callback(); + } + }; + buttonlist[0] = { + text: t('core', 'OK'), + click: functionToCall, + defaultButton: true + }; + break; + } + + $(dialogId).ocdialog({ + closeOnEscape: true, + modal: modal, + buttons: buttonlist + }); + Dialogs.dialogsCounter++; + }) + .fail(function (status, error) { + // If the method is called while navigating away from + // the page, we still want to deliver the message. + if (status === 0) { + alert(title + ': ' + content); + } else { + alert(t('core', 'Error loading message template: {error}', {error: error})); + } + }); + }, + _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 + * @return {Promise} jquery promise that resolves after the dialog template was loaded + */ + 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'), + targetSize = 96, + width = img.width, + height = img.height, + x, y, 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(humanFileSize(original.size)); + $originalDiv.find('.mtime').text(formatDate(original.mtime)); + // ie sucks + if (replacement.size && replacement.lastModifiedDate) { + $replacementDiv.find('.size').text(humanFileSize(replacement.size)); + $replacementDiv.find('.mtime').text(formatDate(replacement.lastModifiedDate)); + } + 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.lastModifiedDate && replacement.lastModifiedDate.getTime() > original.mtime) { + $replacementDiv.find('.mtime').css('font-weight', 'bold'); + } else if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() < 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; + $(this).ocdialog('destroy').remove(); + } + }); + + $(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(); + }, + // get the gridview setting and set the input accordingly + _getGridSettings: function () { + var self = this; + $.get(OC.generateUrl('/apps/files/api/v1/showgridview'), function (response) { + self.$showGridView.get(0).checked = response.gridview; + self.$showGridView.next('#picker-view-toggle') + .removeClass('icon-toggle-filelist icon-toggle-pictures') + .addClass(response.gridview ? 'icon-toggle-filelist' : 'icon-toggle-pictures') + $('.list-container').toggleClass('view-grid', response.gridview); + }); + }, + _onGridviewChange: function () { + var show = this.$showGridView.is(':checked'); + // only save state if user is logged in + if (OC.currentUser) { + $.post(OC.generateUrl('/apps/files/api/v1/showgridview'), { + show: show + }); + } + this.$showGridView.next('#picker-view-toggle') + .removeClass('icon-toggle-filelist icon-toggle-pictures') + .addClass(show ? 'icon-toggle-filelist' : 'icon-toggle-pictures') + $('.list-container').toggleClass('view-grid', show); + }, + _getFilePickerTemplate: function () { + var defer = $.Deferred(); + if (!this.$filePickerTemplate) { + var self = this; + $.get(OC.filePath('core', 'templates', 'filepicker.html'), function (tmpl) { + self.$filePickerTemplate = $(tmpl); + self.$listTmpl = self.$filePickerTemplate.find('.filelist tbody tr:first-child').detach(); + defer.resolve(self.$filePickerTemplate); + }) + .fail(function (jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + } else { + defer.resolve(this.$filePickerTemplate); + } + return defer.promise(); + }, + _getMessageTemplate: function () { + var defer = $.Deferred(); + if (!this.$messageTemplate) { + var self = this; + $.get(OC.filePath('core', 'templates', 'message.html'), function (tmpl) { + self.$messageTemplate = $(tmpl); + defer.resolve(self.$messageTemplate); + }) + .fail(function (jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + } else { + defer.resolve(this.$messageTemplate); + } + return defer.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(); + }, + _getFileList: function (dir, mimeType) { //this is only used by the spreedme app atm + if (typeof (mimeType) === "string") { + mimeType = [mimeType]; + } + + return $.getJSON( + OC.filePath('files', 'ajax', 'list.php'), + { + dir: dir, + mimetypes: JSON.stringify(mimeType) + } + ); + }, + + /** + * fills the filepicker with files + */ + _fillFilePicker: function (dir) { + var self = this; + this.$filelist.empty(); + this.$filePicker.find('.emptycontent').hide(); + this.$filelistContainer.addClass('icon-loading'); + this.$filePicker.data('path', dir); + var filter = this.$filePicker.data('mimetype'); + if (typeof (filter) === "string") { + filter = [filter]; + } + self.$fileListHeader.find('.sort-indicator').addClass('hidden').removeClass('icon-triangle-n').removeClass('icon-triangle-s'); + self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').removeClass('hidden'); + if (self.filepicker.sortOrder === 'asc') { + self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').addClass('icon-triangle-n'); + } else { + self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').addClass('icon-triangle-s'); + } + self.filepicker.filesClient.getFolderContents(dir).then(function (status, files) { + self.filelist = files; + if (filter && filter.length > 0 && filter.indexOf('*') === -1) { + files = files.filter(function (file) { + return file.type === 'dir' || filter.indexOf(file.mimetype) !== -1; + }); + } + + var Comparators = { + name: function (fileInfo1, fileInfo2) { + if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { + return -1; + } + if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { + return 1; + } + return OC.Util.naturalSortCompare(fileInfo1.name, fileInfo2.name); + }, + size: function (fileInfo1, fileInfo2) { + return fileInfo1.size - fileInfo2.size; + }, + mtime: function (fileInfo1, fileInfo2) { + return fileInfo1.mtime - fileInfo2.mtime; + } + }; + var comparator = Comparators[self.filepicker.sortField] || Comparators.name; + files = files.sort(function (file1, file2) { + var isFavorite = function (fileInfo) { + return fileInfo.tags && fileInfo.tags.indexOf(OC.TAG_FAVORITE) >= 0; + }; + + if (isFavorite(file1) && !isFavorite(file2)) { + return -1; + } else if (!isFavorite(file1) && isFavorite(file2)) { + return 1; + } + + return self.filepicker.sortOrder === 'asc' ? comparator(file1, file2) : -comparator(file1, file2); + }); + + self._fillSlug(); + + if (files.length === 0) { + self.$filePicker.find('.emptycontent').show(); + self.$fileListHeader.hide(); + } else { + self.$filePicker.find('.emptycontent').hide(); + self.$fileListHeader.show(); + } + + $.each(files, function (idx, entry) { + entry.icon = OC.MimeType.getIconUrl(entry.mimetype); + var simpleSize, sizeColor; + if (typeof (entry.size) !== 'undefined' && entry.size >= 0) { + simpleSize = humanFileSize(parseInt(entry.size, 10), true); + sizeColor = Math.round(160 - Math.pow((entry.size / (1024 * 1024)), 2)); + } else { + simpleSize = t('files', 'Pending'); + sizeColor = 80; + } + + // split the filename in half if the size is bigger than 20 char + // for ellipsis + if (entry.name.length >= 10) { + // leave maximum 10 letters + var split = Math.min(Math.floor(entry.name.length / 2), 10) + var filename1 = entry.name.substr(0, entry.name.length - split) + var filename2 = entry.name.substr(entry.name.length - split) + } else { + var filename1 = entry.name + var filename2 = '' + } + + var $row = self.$listTmpl.octemplate({ + type: entry.type, + dir: dir, + filename: entry.name, + filename1: filename1, + filename2: filename2, + date: OC.Util.relativeModifiedDate(entry.mtime), + size: simpleSize, + sizeColor: sizeColor, + icon: entry.icon + }); + if (entry.type === 'file') { + var urlSpec = { + file: dir + '/' + entry.name, + x: 100, + y: 100 + }; + var img = new Image(); + var previewUrl = OC.generateUrl('/core/preview.png?') + $.param(urlSpec); + img.onload = function () { + if (img.width > 5) { + $row.find('td.filename').attr('style', 'background-image:url(' + previewUrl + ')'); + } + }; + img.src = previewUrl; + } + self.$filelist.append($row); + }); + + self.$filelistContainer.removeClass('icon-loading'); + }); + }, + /** + * fills the tree list with directories + */ + _fillSlug: function () { + this.$dirTree.empty(); + var self = this; + var dir; + var path = this.$filePicker.data('path'); + var $template = $('<div data-dir="{dir}"><a>{name}</a></div>').addClass('crumb'); + if (path) { + var paths = path.split('/'); + $.each(paths, function (index, dir) { + dir = paths.pop(); + if (dir === '') { + return false; + } + self.$dirTree.prepend($template.octemplate({ + dir: paths.join('/') + '/' + dir, + name: dir + })); + }); + } + $template.octemplate({ + dir: '', + name: '' // Ugly but works ;) + }, {escapeFunction: null}).prependTo(this.$dirTree); + }, + /** + * handle selection made in the tree list + */ + _handleTreeListSelect: function (event, type) { + var self = event.data; + var dir = $(event.target).closest('.crumb').data('dir'); + self._fillFilePicker(dir); + var getOcDialog = (event.target).closest('.oc-dialog'); + var buttonEnableDisable = $('.primary', getOcDialog); + this._changeButtonsText(type, dir.split(/[/]+/).pop()); + if (this.$filePicker.data('mimetype').indexOf("httpd/unix-directory") !== -1) { + buttonEnableDisable.prop("disabled", false); + } else { + buttonEnableDisable.prop("disabled", true); + } + }, + /** + * handle clicks made in the filepicker + */ + _handlePickerClick: function (event, $element, type) { + var getOcDialog = this.$filePicker.closest('.oc-dialog'); + var buttonEnableDisable = getOcDialog.find('.primary'); + if ($element.data('type') === 'file') { + if (this.$filePicker.data('multiselect') !== true || !event.ctrlKey) { + this.$filelist.find('.filepicker_element_selected').removeClass('filepicker_element_selected'); + } + $element.toggleClass('filepicker_element_selected'); + buttonEnableDisable.prop("disabled", false); + } else if ($element.data('type') === 'dir') { + this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname')); + this._changeButtonsText(type, $element.data('entryname')); + if (this.$filePicker.data('mimetype').indexOf("httpd/unix-directory") !== -1) { + buttonEnableDisable.prop("disabled", false); + } else { + buttonEnableDisable.prop("disabled", true); + } + } + }, + + /** + * Handle + * @param type of action + * @param dir on which to change buttons text + * @private + */ + _changeButtonsText: function (type, dir) { + var copyText = dir === '' ? t('core', 'Copy') : t('core', 'Copy to {folder}', {folder: dir}); + var moveText = dir === '' ? t('core', 'Move') : t('core', 'Move to {folder}', {folder: dir}); + var buttons = $('.oc-dialog-buttonrow button'); + switch (type) { + case this.FILEPICKER_TYPE_CHOOSE: + break; + case this.FILEPICKER_TYPE_COPY: + buttons.text(copyText); + break; + case this.FILEPICKER_TYPE_MOVE: + buttons.text(moveText); + break; + case this.FILEPICKER_TYPE_COPY_MOVE: + buttons.eq(0).text(copyText); + buttons.eq(1).text(moveText); + break; + } + } +} + +export default Dialogs diff --git a/core/src/OC/index.js b/core/src/OC/index.js index 6a918cc5226..76b244adf3d 100644 --- a/core/src/OC/index.js +++ b/core/src/OC/index.js @@ -23,6 +23,7 @@ import Apps from './apps' import AppConfig from './appconfig' import Backbone from './backbone' import ContactsMenu from './contactsmenu' +import Dialogs from './dialogs' import EventSource from './eventsource' import L10N from './l10n' import msg from './msg' @@ -39,6 +40,7 @@ export default { AppConfig, Backbone, ContactsMenu, + dialogs: Dialogs, EventSource, L10N, msg, diff --git a/core/src/globals.js b/core/src/globals.js index 7d201ec5b0b..cbb26cfc2a8 100644 --- a/core/src/globals.js +++ b/core/src/globals.js @@ -20,7 +20,7 @@ */ /** - * + * * @param {Function} func the library to deprecate * @param {String} funcName the name of the library */ @@ -34,6 +34,18 @@ const deprecate = (func, funcName) => { return newFunc } +const setDeprecatedProp = (global, val, msg) => + Object.defineProperty(window, global, { + get: () => { + if (msg) { + console.warn(`${global} is deprecated: ${msg}`) + } else { + console.warn(`${global} is deprecated`) + } + return val + } + }) + import _ from 'underscore' import $ from 'jquery' import 'jquery-migrate/dist/jquery-migrate.min' @@ -91,6 +103,7 @@ window['md5'] = md5 window['moment'] = moment window['OC'] = OC +setDeprecatedProp('OCDialogs', OC.dialogs, 'use OC.dialogs') window['OCP'] = OCP window['OCA'] = OCA window['escapeHTML'] = deprecate(escapeHTML, 'escapeHTML') |