diff options
Diffstat (limited to 'apps/files/js')
-rw-r--r-- | apps/files/js/detailsview.js | 61 | ||||
-rw-r--r-- | apps/files/js/detailtabview.js | 17 | ||||
-rw-r--r-- | apps/files/js/file-upload.js | 65 | ||||
-rw-r--r-- | apps/files/js/fileactions.js | 4 | ||||
-rw-r--r-- | apps/files/js/fileactionsmenu.js | 8 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 81 | ||||
-rw-r--r-- | apps/files/js/files.js | 21 | ||||
-rw-r--r-- | apps/files/js/mainfileinfodetailview.js | 33 |
8 files changed, 215 insertions, 75 deletions
diff --git a/apps/files/js/detailsview.js b/apps/files/js/detailsview.js index b01f9cea610..f04adcf1292 100644 --- a/apps/files/js/detailsview.js +++ b/apps/files/js/detailsview.js @@ -132,16 +132,22 @@ closeLabel: t('files', 'Close') }; - if (this._tabViews.length > 1) { - // only render headers if there is more than one available - templateVars.tabHeaders = _.map(this._tabViews, function(tabView, i) { - return { - tabId: tabView.id, - tabIndex: i, - label: tabView.getLabel() - }; - }); - } + this._tabViews = this._tabViews.sort(function(tabA, tabB) { + var orderA = tabA.order || 0; + var orderB = tabB.order || 0; + if (orderA === orderB) { + return OC.Util.naturalSortCompare(tabA.getLabel(), tabB.getLabel()); + } + return orderA - orderB; + }); + + templateVars.tabHeaders = _.map(this._tabViews, function(tabView, i) { + return { + tabId: tabView.id, + tabIndex: i, + label: tabView.getLabel() + }; + }); this.$el.html(this.template(templateVars)); @@ -158,6 +164,8 @@ this.selectTab(this._currentTabId); + this._updateTabVisibilities(); + this._dirty = false; }, @@ -216,6 +224,8 @@ if (this._dirty) { this.render(); + } else { + this._updateTabVisibilities(); } if (this._currentTabId) { @@ -233,6 +243,37 @@ }, /** + * Update tab headers based on the current model + */ + _updateTabVisibilities: function() { + // update tab header visibilities + var self = this; + var deselect = false; + var countVisible = 0; + var $tabHeaders = this.$el.find('.tabHeaders li'); + _.each(this._tabViews, function(tabView) { + var isVisible = tabView.canDisplay(self.model); + if (isVisible) { + countVisible += 1; + } + if (!isVisible && self._currentTabId === tabView.id) { + deselect = true; + } + $tabHeaders.filterAttr('data-tabid', tabView.id).toggleClass('hidden', !isVisible); + }); + + // hide the whole container if there is only one tab + this.$el.find('.tabHeaders').toggleClass('hidden', countVisible <= 1); + + if (deselect) { + // select the first visible tab instead + var visibleTabId = this.$el.find('.tabHeader:not(.hidden):first').attr('data-tabid'); + this.selectTab(visibleTabId); + } + + }, + + /** * Returns the file info. * * @return {OCA.Files.FileInfoModel} file info diff --git a/apps/files/js/detailtabview.js b/apps/files/js/detailtabview.js index 449047cf252..0bd34a88188 100644 --- a/apps/files/js/detailtabview.js +++ b/apps/files/js/detailtabview.js @@ -29,11 +29,15 @@ _template: null, - initialize: function() { + initialize: function(options) { + options = options || {}; if (!this.id) { this.id = 'detailTabView' + DetailTabView._TAB_COUNT; DetailTabView._TAB_COUNT++; } + if (options.order) { + this.order = options.order || 0; + } }, /** @@ -91,6 +95,17 @@ */ nextPage: function() { // load the next page, if applicable + }, + + /** + * Returns whether the current tab is able to display + * the given file info, for example based on mime type. + * + * @param {OCA.Files.FileInfoModel} fileInfo file info model + * @return {bool} whether to display this tab + */ + canDisplay: function(fileInfo) { + return true; } }); DetailTabView._TAB_COUNT = 0; diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 17f0f777169..03330ad7c5d 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -18,7 +18,7 @@ * - TODO music upload button */ -/* global Files, FileList, jQuery, oc_requesttoken, humanFileSize, getUniqueName */ +/* global jQuery, oc_requesttoken, humanFileSize */ /** * Function that will allow us to know if Ajax uploads are supported @@ -139,6 +139,9 @@ OC.Upload = { if (data.data) { data.data.append('resolution', 'replace'); } else { + if (!data.formData) { + data.formData = []; + } data.formData.push({name:'resolution', value:'replace'}); //hack for ie8 } data.submit(); @@ -152,6 +155,9 @@ OC.Upload = { if (data.data) { data.data.append('resolution', 'autorename'); } else { + if (!data.formData) { + data.formData = []; + } data.formData.push({name:'resolution', value:'autorename'}); //hack for ie8 } data.submit(); @@ -164,8 +170,9 @@ OC.Upload = { } }, /** - * TODO checks the list of existing files prior to uploading and shows a simple dialog to choose + * checks the list of existing files prior to uploading and shows a simple dialog to choose * skip all, replace all or choose which files to keep + * * @param {array} selection of files to upload * @param {object} callbacks - object with several callback methods * @param {function} callbacks.onNoConflicts @@ -175,14 +182,34 @@ OC.Upload = { * @param {function} callbacks.onCancel */ checkExistingFiles: function (selection, callbacks) { - /* - $.each(selection.uploads, function(i, upload) { - var $row = OCA.Files.App.fileList.findFileEl(upload.files[0].name); - if ($row) { - // TODO check filelist before uploading and show dialog on conflicts, use callbacks + var fileList = OCA.Files.App.fileList; + var conflicts = []; + // only keep non-conflicting uploads + selection.uploads = _.filter(selection.uploads, function(upload) { + var fileInfo = fileList.findFile(upload.files[0].name); + if (fileInfo) { + conflicts.push([ + // original + _.extend(fileInfo, { + directory: fileInfo.directory || fileInfo.path || fileList.getCurrentDirectory() + }), + // replacement (File object) + upload + ]); + return false; } + return true; }); - */ + if (conflicts.length) { + _.each(conflicts, function(conflictData) { + OC.dialogs.fileexists(conflictData[1], conflictData[0], conflictData[1].files[0], OC.Upload); + }); + } + + // upload non-conflicting files + // note: when reaching the server they might still meet conflicts + // if the folder was concurrently modified, these will get added + // to the already visible dialog, if applicable callbacks.onNoConflicts(selection); }, @@ -368,18 +395,18 @@ OC.Upload = { }, submit: function(e, data) { OC.Upload.rememberUpload(data); - if ( ! data.formData ) { - var fileDirectory = ''; - if(typeof data.files[0].relativePath !== 'undefined') { - fileDirectory = data.files[0].relativePath; - } - // noone set update parameters, we set the minimum - data.formData = { - requesttoken: oc_requesttoken, - dir: data.targetDir || FileList.getCurrentDirectory(), - file_directory: fileDirectory - }; + if (!data.formData) { + data.formData = []; + } + + var fileDirectory = ''; + if(typeof data.files[0].relativePath !== 'undefined') { + fileDirectory = data.files[0].relativePath; } + // FIXME: prevent re-adding the same + data.formData.push({name: 'requesttoken', value: oc_requesttoken}); + data.formData.push({name: 'dir', value: data.targetDir || FileList.getCurrentDirectory()}); + data.formData.push({name: 'file_directory', value: fileDirectory}); }, fail: function(e, data) { OC.Upload.log('fail', e, data); diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index d3904f2f6d3..bde0b094b87 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -141,6 +141,7 @@ name: name, displayName: action.displayName, mime: mime, + order: action.order || 0, icon: action.icon, permissions: action.permissions, type: action.type || FileActions.TYPE_DROPDOWN @@ -565,6 +566,7 @@ this.registerAction({ name: 'Download', displayName: t('files', 'Download'), + order: -20, mime: 'all', permissions: OC.PERMISSION_READ, icon: function () { @@ -596,6 +598,7 @@ name: 'Rename', displayName: t('files', 'Rename'), mime: 'all', + order: -30, permissions: OC.PERMISSION_UPDATE, icon: function() { return OC.imagePath('core', 'actions/rename'); @@ -617,6 +620,7 @@ name: 'Delete', displayName: t('files', 'Delete'), mime: 'all', + order: 1000, // permission is READ because we show a hint instead if there is no permission permissions: OC.PERMISSION_DELETE, icon: function() { diff --git a/apps/files/js/fileactionsmenu.js b/apps/files/js/fileactionsmenu.js index 5ab0e42f93e..67cbb48c965 100644 --- a/apps/files/js/fileactionsmenu.js +++ b/apps/files/js/fileactionsmenu.js @@ -100,6 +100,14 @@ (!defaultAction || actionSpec.name !== defaultAction.name) ); }); + items = items.sort(function(actionA, actionB) { + var orderA = actionA.order || 0; + var orderB = actionB.order || 0; + if (orderB === orderA) { + return OC.Util.naturalSortCompare(actionA.displayName, actionB.displayName); + } + return orderA - orderB; + }); items = _.map(items, function(item) { item.nameLowerCase = item.name.toLowerCase(); return item; diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 060269c220f..7a025e772c5 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -239,13 +239,6 @@ this.updateSearch(); - this.$el.on('click', function(event) { - var $target = $(event.target); - // click outside file row ? - if (!$target.closest('tbody').length && !$target.closest('#app-sidebar').length) { - self._updateDetailsView(null); - } - }); this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this)); this.$fileList.on('change', 'td.filename>.selectCheckBox', _.bind(this._onClickFileCheckbox, this)); this.$el.on('urlChanged', _.bind(this._onUrlChanged, this)); @@ -278,6 +271,9 @@ if (this._newButton) { this._newButton.remove(); } + if (this._detailsView) { + this._detailsView.remove(); + } // TODO: also unregister other event handlers this.fileActions.off('registerAction', this._onFileActionsUpdated); this.fileActions.off('setDefault', this._onFileActionsUpdated); @@ -302,11 +298,11 @@ name: 'Details', displayName: t('files', 'Details'), mime: 'all', + order: -50, icon: OC.imagePath('core', 'actions/details'), permissions: OC.PERMISSION_READ, actionHandler: function(fileName, context) { self._updateDetailsView(fileName); - OC.Apps.showAppSidebar(self._detailsView.$el); } }); } @@ -407,9 +403,14 @@ this._currentFileModel.off(); } this._currentFileModel = null; + OC.Apps.hideAppSidebar(this._detailsView.$el); return; } + if (this._detailsView.$el.hasClass('disappear')) { + OC.Apps.showAppSidebar(this._detailsView.$el); + } + var $tr = this.findFileEl(fileName); var model = this.getModelForFile($tr); @@ -452,10 +453,10 @@ * Selected/deselects the given file element and updated * the internal selection cache. * - * @param $tr single file row element - * @param state true to select, false to deselect + * @param {Object} $tr single file row element + * @param {bool} state true to select, false to deselect */ - _selectFileEl: function($tr, state) { + _selectFileEl: function($tr, state, showDetailsView) { var $checkbox = $tr.find('td.filename>.selectCheckBox'); var oldData = !!this._selectedFiles[$tr.data('id')]; var data; @@ -474,11 +475,8 @@ delete this._selectedFiles[$tr.data('id')]; this._selectionSummary.remove(data); } - if (this._selectionSummary.getTotal() === 1) { + if (this._detailsView && this._selectionSummary.getTotal() === 1 && !this._detailsView.$el.hasClass('disappear')) { this._updateDetailsView(_.values(this._selectedFiles)[0].name); - } else { - // show nothing when multiple files are selected - this._updateDetailsView(null); } this.$el.find('.select-all').prop('checked', this._selectionSummary.getTotal() === this.files.length); }, @@ -488,6 +486,9 @@ */ _onClickFile: function(event) { var $tr = $(event.target).closest('tr'); + if ($tr.hasClass('dragging')) { + return; + } if (this._allowSelection && (event.ctrlKey || event.shiftKey)) { event.preventDefault(); if (event.shiftKey) { @@ -551,9 +552,13 @@ */ _onClickFileCheckbox: function(e) { var $tr = $(e.target).closest('tr'); - this._selectFileEl($tr, !$tr.hasClass('selected')); + var state = !$tr.hasClass('selected'); + this._selectFileEl($tr, state); this._lastChecked = $tr; this.updateSelectionSummary(); + if (state) { + this._updateDetailsView($tr.attr('data-file')); + } }, /** @@ -721,8 +726,23 @@ return true; }, /** - * Returns the tr element for a given file name - * @param fileName file name + * Returns the file info for the given file name from the internal collection. + * + * @param {string} fileName file name + * @return {OCA.Files.FileInfo} file info or null if it was not found + * + * @since 8.2 + */ + findFile: function(fileName) { + return _.find(this.files, function(aFile) { + return (aFile.name === fileName); + }) || null; + }, + /** + * Returns the tr element for a given file name, but only if it was already rendered. + * + * @param {string} fileName file name + * @return {Object} jQuery object of the matching row */ findFileEl: function(fileName){ // use filterAttr to avoid escaping issues @@ -928,7 +948,7 @@ if (this._allowSelection) { td.append( '<input id="select-' + this.id + '-' + fileData.id + - '" type="checkbox" class="selectCheckBox"/><label for="select-' + this.id + '-' + fileData.id + '">' + + '" type="checkbox" class="selectCheckBox checkbox"/><label for="select-' + this.id + '-' + fileData.id + '">' + '<div class="thumbnail" style="background-image:url(' + icon + '); background-size: 32px;"></div>' + '<span class="hidden-visually">' + t('files', 'Select') + '</span>' + '</label>' @@ -1307,8 +1327,10 @@ sortdirection: this._sortDirection } }); - // close sidebar - this._updateDetailsView(null); + if (this._detailsView) { + // close sidebar + this._updateDetailsView(null); + } var callBack = this.reloadCallback.bind(this); return this._reloadCall.then(callBack, callBack); }, @@ -1396,8 +1418,8 @@ } urlSpec.x *= window.devicePixelRatio; urlSpec.y *= window.devicePixelRatio; - urlSpec.x = Math.floor(urlSpec.x); - urlSpec.y = Math.floor(urlSpec.y); + urlSpec.x = Math.ceil(urlSpec.x); + urlSpec.y = Math.ceil(urlSpec.y); urlSpec.forceIcon = 0; return OC.generateUrl('/core/preview.png?') + $.param(urlSpec); }, @@ -1515,11 +1537,12 @@ remove: function(name, options){ options = options || {}; var fileEl = this.findFileEl(name); + var fileId = fileEl.data('id'); var index = fileEl.index(); if (!fileEl.length) { return null; } - if (this._selectedFiles[fileEl.data('id')]) { + if (this._selectedFiles[fileId]) { // remove from selection first this._selectFileEl(fileEl, false); this.updateSelectionSummary(); @@ -1529,6 +1552,14 @@ fileEl.find('td.filename').draggable('destroy'); } this.files.splice(index, 1); + if (this._currentFileModel && this._currentFileModel.get('id') === fileId) { + // Note: in the future we should call destroy() directly on the model + // and the model will take care of the deletion. + // Here we only trigger the event to notify listeners that + // the file was removed. + this._currentFileModel.trigger('destroy'); + this._updateDetailsView(null); + } fileEl.remove(); // TODO: improve performance on batch update this.isEmpty = !this.files.length; @@ -1879,7 +1910,7 @@ * @return {bool} true if the file exists in the list, false otherwise */ inList:function(file) { - return this.findFileEl(file).length; + return this.findFile(file); }, /** diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 4fdc9eb2110..9ab7609cc40 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -356,7 +356,7 @@ var createDragShadow = function(event) { var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked'); if (!isDragSelected) { //select dragged file - FileList._selectFileEl($(event.target).parents('tr:first'), true); + FileList._selectFileEl($(event.target).parents('tr:first'), true, false); } // do not show drag shadow for too many files @@ -365,7 +365,7 @@ var createDragShadow = function(event) { if (!isDragSelected && selectedFiles.length === 1) { //revert the selection - FileList._selectFileEl($(event.target).parents('tr:first'), false); + FileList._selectFileEl($(event.target).parents('tr:first'), false, false); } // build dragshadow @@ -413,22 +413,17 @@ var dragOptions={ cursor: 'move', start: function(event, ui){ var $selectedFiles = $('td.filename input:checkbox:checked'); - if($selectedFiles.length > 1){ - $selectedFiles.parents('tr').fadeTo(250, 0.2); - } - else{ - $(this).fadeTo(250, 0.2); + if (!$selectedFiles.length) { + $selectedFiles = $(this); } + $selectedFiles.closest('tr').fadeTo(250, 0.2).addClass('dragging'); }, stop: function(event, ui) { var $selectedFiles = $('td.filename input:checkbox:checked'); - if($selectedFiles.length > 1){ - $selectedFiles.parents('tr').fadeTo(250, 1); - } - else{ - $(this).fadeTo(250, 1); + if (!$selectedFiles.length) { + $selectedFiles = $(this); } - $('#fileList tr td.filename').addClass('ui-draggable'); + $selectedFiles.closest('tr').fadeTo(250, 1).removeClass('dragging'); } }; // sane browsers support using the distance option diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js index 830f074f3f1..39a00d0a5a5 100644 --- a/apps/files/js/mainfileinfodetailview.js +++ b/apps/files/js/mainfileinfodetailview.js @@ -10,7 +10,7 @@ (function() { var TEMPLATE = - '<div class="thumbnailContainer"><a href="#" class="thumbnail action-default"></a></div>' + + '<div class="thumbnailContainer"><a href="#" class="thumbnail action-default"><div class="stretcher"/></a></div>' + '<div class="file-details-container">' + '<div class="fileName"><h3 title="{{name}}" class="ellipsis">{{name}}</h3></div>' + ' <div class="file-details ellipsis">' + @@ -140,13 +140,18 @@ }, loadPreview: function(path, mime, etag, $iconDiv, $container, isImage) { - var maxImageHeight = ($container.parent().width() + 50) / (16/9); // 30px for negative margin + var maxImageWidth = $container.parent().width() + 50; // 50px for negative margins + var maxImageHeight = maxImageWidth / (16/9); var smallPreviewSize = 75; var isLandscape = function(img) { return img.width > (img.height * 1.2); }; + var isSmall = function(img) { + return (img.width * 1.1) < (maxImageWidth * window.devicePixelRatio); + }; + var getTargetHeight = function(img) { if(isImage) { var targetHeight = img.height / window.devicePixelRatio; @@ -159,13 +164,23 @@ } }; + var getTargetRatio = function(img){ + var ratio = img.width / img.height; + if (ratio > 16/9) { + return ratio; + } else { + return 16/9; + } + }; + this._fileList.lazyLoadPreview({ path: path, mime: mime, etag: etag, y: isImage ? maxImageHeight : smallPreviewSize, - x: isImage ? 99999 /* only limit on y */ : smallPreviewSize, + x: isImage ? maxImageWidth : smallPreviewSize, a: isImage ? 1 : null, + mode: isImage ? 'cover' : null, callback: function (previewUrl, img) { $iconDiv.previewImg = previewUrl; @@ -176,9 +191,7 @@ $iconDiv.removeClass('icon-loading icon-32'); var targetHeight = getTargetHeight(img); if (this.model.isImage() && targetHeight > smallPreviewSize) { - if (!isLandscape(img)) { - $container.addClass('portrait'); - } + $container.addClass((isLandscape(img) && !isSmall(img))? 'landscape': 'portrait'); $container.addClass('image'); } @@ -186,7 +199,13 @@ // when we dont have a preview we show the mime icon in the error handler $iconDiv.css({ 'background-image': 'url("' + previewUrl + '")', - 'height': targetHeight + height: (targetHeight > smallPreviewSize)? 'auto': targetHeight, + 'max-height': isSmall(img)? targetHeight: null + }); + + var targetRatio = getTargetRatio(img); + $iconDiv.find('.stretcher').css({ + 'padding-bottom': (100 / targetRatio) + '%' }); }.bind(this), error: function () { |