From 6fd084243b65a556d4775209ba3916145ef5912a Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 12 May 2014 19:54:20 +0200 Subject: [PATCH] Fixed many issues, clean up - fixed upload and storage statistics - fixed infinite scroll to use the correct contain for scroll detection - fixed unit test that sometimes fail for rename case - controls are now sticky again - fixed selection overlay to be aligned with the table - fixed "select all" checkbox that had id conflicts - fixed public page - fixed global actions permissions detection - fix when URL contains an invalid view id - viewer mode now hides the sidebar (ex: text editor) - added unit tests for trashbin - clean up storage info in template (most is retrieved via ajax call now) --- apps/files/css/files.css | 45 ++++-- apps/files/index.php | 5 + apps/files/js/app.js | 26 +++- apps/files/js/breadcrumb.js | 10 +- apps/files/js/file-upload.js | 3 - apps/files/js/fileactions.js | 2 + apps/files/js/filelist.js | 114 ++++++++++----- apps/files/js/files.js | 72 ++++------ apps/files/js/keyboardshortcuts.js | 3 +- apps/files/js/navigation.js | 21 ++- apps/files/list.php | 18 +-- apps/files/templates/index.php | 1 - apps/files/templates/list.php | 21 +-- apps/files/tests/js/appSpec.js | 81 +++++++++-- apps/files/tests/js/fileactionsSpec.js | 15 +- apps/files/tests/js/filelistSpec.js | 104 ++++++++------ apps/files_sharing/css/public.css | 8 -- apps/files_sharing/js/public.js | 26 +++- apps/files_sharing/js/share.js | 13 +- apps/files_sharing/public.php | 2 +- apps/files_trashbin/appinfo/app.php | 2 +- apps/files_trashbin/css/trash.css | 13 +- apps/files_trashbin/js/app.js | 8 +- apps/files_trashbin/js/filelist.js | 30 ++-- apps/files_trashbin/js/files.js | 23 --- apps/files_trashbin/{index.php => list.php} | 1 - apps/files_trashbin/templates/index.php | 4 +- apps/files_trashbin/tests/js/filelistSpec.js | 140 +++++++++++++++---- core/css/styles.css | 5 + 29 files changed, 540 insertions(+), 276 deletions(-) delete mode 100644 apps/files_trashbin/js/files.js rename apps/files_trashbin/{index.php => list.php} (85%) diff --git a/apps/files/css/files.css b/apps/files/css/files.css index ba299ea291..009cb355ba 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -3,10 +3,11 @@ See the COPYING-README file. */ /* FILE MENU */ -.actions { padding:5px; height:32px; width: 100%; } +.actions { padding:5px; height:32px; display: inline-block; float: left; } .actions input, .actions button, .actions .button { margin:0; float:left; } .actions .button a { color: #555; } .actions .button a:hover, .actions .button a:active { color: #333; } +.actions.hidden { display: none; } #new { z-index: 1010; @@ -70,10 +71,12 @@ /* FILE TABLE */ -.app-files #filestable { +#filestable { position: relative; + top: 44px; width: 100%; } + /* make sure there's enough room for the file actions */ #body-user #filestable { min-width: 688px; /* 768 (mobile break) - 80 (nav width) */ @@ -83,22 +86,33 @@ } #filestable tbody tr { background-color:#fff; height:51px; } + +.app-files #app-content { + position: relative; +} + /** * Override global #controls styles * to be more flexible / relative */ -.app-files #controls { - position: static; - left: auto; - top: auto; +#body-user .app-files #controls { + left: 310px; /* main nav bar + sidebar */ + position: fixed; + padding-left: 0px; +} + +/* this is mostly for file viewer apps, text editor, etc */ +#body-user .app-files.no-sidebar #controls { + left: 0px; + padding-left: 80px; /* main nav bar */ } .app-files #app-navigation { - width: 150px; + width: 230px; } .app-files #app-settings { - width: 149px; /* DUH */ + width: 229px; /* DUH */ } .app-files #app-settings input { @@ -205,9 +219,7 @@ table.multiselect thead { z-index: 10; -moz-box-sizing: border-box; box-sizing: border-box; - left: 0; - padding-left: 80px; - width: 100%; + left: 310px; /* main nav bar + sidebar */ } table.multiselect thead th { @@ -294,7 +306,7 @@ table td.filename form { font-size:14px; margin-left:48px; margin-right:48px; } /* Use label to have bigger clickable size for checkbox */ #fileList tr td.filename>input[type="checkbox"] + label, -#select_all + label { +.select-all + label { height: 50px; position: absolute; width: 50px; @@ -308,10 +320,10 @@ table td.filename form { font-size:14px; margin-left:48px; margin-right:48px; } #fileList tr td.filename>input[type="checkbox"] + label { left: 0; } -#select_all + label { +.select-all + label { top: 0; } -#select_all { +.select-all { position: absolute; top: 18px; left: 18px; @@ -359,6 +371,9 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; } display: inline; padding: 17px 5px; } +.selectedActions a.hidden { + display: none; +} .selectedActions a img { position:relative; top:5px; @@ -434,7 +449,7 @@ table.dragshadow td.size { } .mask { z-index: 50; - position: fixed; + position: absolute; top: 0; left: 0; right: 0; diff --git a/apps/files/index.php b/apps/files/index.php index 69fa1c1913..9d4007d7f9 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -62,6 +62,9 @@ $user = OC_User::getUser(); $config = \OC::$server->getConfig(); +// mostly for the home storage's free space +$dirInfo = \OC\Files\Filesystem::getFileInfo('/', false); +$storageInfo=OC_Helper::getStorageInfo('/', $dirInfo); // if the encryption app is disabled, than everything is fine (INIT_SUCCESSFUL status code) $encryptionInitStatus = 2; if (OC_App::isEnabled('files_encryption')) { @@ -107,6 +110,7 @@ OCP\Util::addscript('files', 'files'); OCP\Util::addscript('files', 'navigation'); OCP\Util::addscript('files', 'keyboardshortcuts'); $tmpl = new OCP\Template('files', 'index', 'user'); +$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']); $tmpl->assign('isPublic', false); $tmpl->assign("encryptedFiles", \OCP\Util::encryptedFiles()); $tmpl->assign("mailNotificationEnabled", $config->getAppValue('core', 'shareapi_allow_mail_notification', 'yes')); @@ -114,5 +118,6 @@ $tmpl->assign("allowShareWithLink", $config->getAppValue('core', 'shareapi_allow $tmpl->assign("encryptionInitStatus", $encryptionInitStatus); $tmpl->assign('appNavigation', $nav); $tmpl->assign('appContents', $contentItems); +$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); $tmpl->printPage(); diff --git a/apps/files/js/app.js b/apps/files/js/app.js index 6cdb339b22..9155fb38cd 100644 --- a/apps/files/js/app.js +++ b/apps/files/js/app.js @@ -11,6 +11,7 @@ * */ +/* global dragOptions, folderDropOptions */ (function() { if (!OCA.Files) { @@ -24,11 +25,16 @@ this.navigation = new OCA.Files.Navigation($('#app-navigation')); // TODO: ideally these should be in a separate class / app (the embedded "all files" app) - this.fileList = OCA.Files.FileList; this.fileActions = OCA.Files.FileActions; this.files = OCA.Files.Files; - this.fileList = new OCA.Files.FileList($('#app-content-files')); + this.fileList = new OCA.Files.FileList( + $('#app-content-files'), { + scrollContainer: $('#app-content'), + dragOptions: dragOptions, + folderDropOptions: folderDropOptions + } + ); this.files.initialize(); this.fileActions.registerDefaultActions(this.fileList); this.fileList.setFileActions(this.fileActions); @@ -58,7 +64,8 @@ OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this)); // detect when app changed their current directory - $('#app-content>div').on('changeDirectory', _.bind(this._onDirectoryChanged, this)); + $('#app-content').delegate('>div', 'changeDirectory', _.bind(this._onDirectoryChanged, this)); + $('#app-content').delegate('>div', 'changeViewerMode', _.bind(this._onChangeViewerMode, this)); $('#app-navigation').on('itemChanged', _.bind(this._onNavigationChanged, this)); }, @@ -87,6 +94,16 @@ } }, + /** + * Event handler for when an app notifies that it needs space + * for viewer mode. + */ + _onChangeViewerMode: function(e) { + var state = !!e.viewerModeEnabled; + $('#app-navigation').toggleClass('hidden', state); + $('.app-files').toggleClass('viewer-mode no-sidebar', state); + }, + /** * Event handler for when the URL changed */ @@ -96,6 +113,9 @@ view: 'files' }, params); var lastId = this.navigation.getActiveItem(); + if (!this.navigation.itemExists(params.view)) { + params.view = 'files'; + } this.navigation.setActiveItem(params.view, {silent: true}); if (lastId !== this.navigation.getActiveItem()) { this.navigation.getActiveContainer().trigger(new $.Event('show')); diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js index 1a77481b67..c017d710d6 100644 --- a/apps/files/js/breadcrumb.js +++ b/apps/files/js/breadcrumb.js @@ -159,7 +159,11 @@ this.totalWidth = 64; // FIXME: this class should not know about global elements if ( $('#navigation').length ) { - this.totalWidth += $('#navigation').get(0).offsetWidth; + this.totalWidth += $('#navigation').outerWidth(); + } + + if ( $('#app-navigation').length && !$('#app-navigation').hasClass('hidden')) { + this.totalWidth += $('#app-navigation').outerWidth(); } this.hiddenBreadcrumbs = 0; @@ -167,8 +171,8 @@ this.totalWidth += $(this.breadcrumbs[i]).get(0).offsetWidth; } - $.each($('#controls .actions>div'), function(index, action) { - self.totalWidth += $(action).get(0).offsetWidth; + $.each($('#controls .actions'), function(index, action) { + self.totalWidth += $(action).outerWidth(); }); }, diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 0ca2852f9f..6b0ca79368 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -460,7 +460,6 @@ OC.Upload = { $('#uploadprogresswrapper input.stop').fadeOut(); $('#uploadprogressbar').fadeOut(); - Files.updateStorageStatistics(); }); fileupload.on('fileuploadfail', function(e, data) { OC.Upload.log('progress handle fileuploadfail', e, data); @@ -471,8 +470,6 @@ OC.Upload = { } }); - } else { - console.log('skipping file progress because your browser is broken'); } } diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 7be599871e..b9cd9816d4 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -209,6 +209,8 @@ * Register the actions that are used by default for the files app. */ registerDefaultActions: function(fileList) { + // TODO: try to find a way to not make it depend on fileList, + // maybe get a handler or listener to trigger events on this.register('all', 'Delete', OC.PERMISSION_DELETE, function () { return OC.imagePath('core', 'actions/delete'); }, function (filename) { diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index de34d932b1..38766e2b80 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -8,21 +8,20 @@ * */ -/* global Files */ -/* global dragOptions, folderDropOptions */ (function() { /** * The FileList class manages a file list view. * A file list view consists of a controls bar and * a file list table. */ - var FileList = function($el) { - this.initialize($el); + var FileList = function($el, options) { + this.initialize($el, options); }; FileList.prototype = { SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', + id: 'files', appName: t('files', 'Files'), isEmpty: true, useUndo:true, @@ -95,16 +94,35 @@ */ _currentDirectory: null, + _dragOptions: null, + _folderDropOptions: null, + /** * Initialize the file list and its components + * + * @param $el container element with existing markup for the #controls + * and a table + * @param options map of options, see other parameters + * @param scrollContainer scrollable container, defaults to $(window) + * @param dragOptions drag options, disabled by default + * @param folderDropOptions folder drop options, disabled by default */ - initialize: function($el) { + initialize: function($el, options) { var self = this; + options = options || {}; if (this.initialized) { return; } + if (options.dragOptions) { + this._dragOptions = options.dragOptions; + } + if (options.folderDropOptions) { + this._folderDropOptions = options.folderDropOptions; + } + this.$el = $el; + this.$container = options.scrollContainer || $(window); this.$table = $el.find('table:first'); this.$fileList = $el.find('#fileList'); this.fileActions = OCA.Files.FileActions; @@ -116,13 +134,17 @@ this.setSort('name', 'asc'); - this.breadcrumb = new OCA.Files.BreadCrumb({ + var breadcrumbOptions = { onClick: _.bind(this._onClickBreadCrumb, this), - onDrop: _.bind(this._onDropOnBreadCrumb, this), - getCrumbUrl: function(part, index) { + getCrumbUrl: function(part) { return self.linkTo(part.dir); } - }); + }; + // if dropping on folders is allowed, then also allow on breadcrumbs + if (this._folderDropOptions) { + breadcrumbOptions.onDrop = _.bind(this._onDropOnBreadCrumb, this); + } + this.breadcrumb = new OCA.Files.BreadCrumb(breadcrumbOptions); this.$el.find('#controls').prepend(this.breadcrumb.$el); @@ -137,14 +159,13 @@ this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this)); this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this)); this.$el.on('urlChanged', _.bind(this._onUrlChanged, this)); - this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this)); + this.$el.find('.select-all').click(_.bind(this._onClickSelectAll, this)); this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this)); this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this)); this.setupUploadEvents(); - // FIXME: only do this when visible - $(window).scroll(function(e) {self._onScroll(e);}); + this.$container.on('scroll', _.bind(this._onScroll, this)); }, /** @@ -182,7 +203,7 @@ delete this._selectedFiles[$tr.data('id')]; this._selectionSummary.remove(data); } - this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length); + this.$el.find('.select-all').prop('checked', this._selectionSummary.getTotal() === this.files.length); }, /** @@ -327,8 +348,12 @@ } }, + /** + * Event handler for when scrolling the list container. + * This appends/renders the next page of entries when reaching the bottom. + */ _onScroll: function(e) { - if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) { + if (this.$container.scrollTop() + this.$container.height() > this.$el.height() - 100) { this._nextPage(true); } }, @@ -460,7 +485,7 @@ this.$fileList.empty(); // clear "Select all" checkbox - this.$el.find('#select_all').prop('checked', false); + this.$el.find('.select-all').prop('checked', false); this.isEmpty = this.files.length === 0; this._nextPage(); @@ -469,10 +494,6 @@ this.updateEmptyContent(); this.$fileList.trigger(jQuery.Event("fileActionsReady")); - // "Files" might not be loaded in extending apps - if (window.Files) { - Files.setupDragAndDrop(); - } this.fileSummary.calculate(filesArray); @@ -540,7 +561,8 @@ else { linkUrl = this.getDownloadUrl(name, this.getCurrentDirectory()); } - td.append(''); + td.append(''); var linkElem = $('').attr({ "class": "name", "href": linkUrl @@ -694,12 +716,12 @@ // TODO: move dragging to FileActions ? // enable drag only for deletable files - if (permissions & OC.PERMISSION_DELETE) { - filenameTd.draggable(dragOptions); + if (this._dragOptions && permissions & OC.PERMISSION_DELETE) { + filenameTd.draggable(this._dragOptions); } // allow dropping on folders - if (fileData.type === 'dir') { - filenameTd.droppable(folderDropOptions); + if (this._folderDropOptions && fileData.type === 'dir') { + filenameTd.droppable(this._folderDropOptions); } if (options.hidden) { @@ -780,8 +802,7 @@ * @param changeUrl true to also update the URL, false otherwise (default) */ _setCurrentDir: function(targetDir, changeUrl) { - var url, - previousDir = this.getCurrentDirectory(), + var previousDir = this.getCurrentDirectory(), baseDir = OC.basename(targetDir); if (baseDir !== '') { @@ -832,7 +853,7 @@ var self = this; this._selectedFiles = {}; this._selectionSummary.clear(); - this.$el.find('#select_all').prop('checked', false); + this.$el.find('.select-all').prop('checked', false); this.showMask(); if (this._reloadCall) { this._reloadCall.abort(); @@ -873,7 +894,7 @@ // TODO: should rather return upload file size through // the files list ajax call - Files.updateStorageStatistics(true); + this.updateStorageStatistics(true); if (result.data.permissions) { this.setDirectoryPermissions(result.data.permissions); @@ -882,12 +903,16 @@ this.setFiles(result.data.files); }, + updateStorageStatistics: function(force) { + OCA.Files.Files.updateStorageStatistics(this.getCurrentDirectory(), force); + }, + getAjaxUrl: function(action, params) { - return Files.getAjaxUrl(action, params); + return OCA.Files.Files.getAjaxUrl(action, params); }, getDownloadUrl: function(files, dir) { - return Files.getDownloadUrl(files, dir || this.getCurrentDirectory()); + return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory()); }, /** @@ -994,6 +1019,7 @@ setViewerMode: function(show){ this.showActions(!show); this.$el.find('#filestable').toggleClass('hidden', show); + this.$el.trigger(new $.Event('changeViewerMode', {viewerModeEnabled: show})); }, /** * Removes a file entry from the list @@ -1014,7 +1040,7 @@ this._selectFileEl(fileEl, false); this.updateSelectionSummary(); } - if (fileEl.data('permissions') & OC.PERMISSION_DELETE) { + if (this._dragOptions && (fileEl.data('permissions') & OC.PERMISSION_DELETE)) { // file is only draggable when delete permissions are set fileEl.find('td.filename').draggable('destroy'); } @@ -1109,7 +1135,8 @@ OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); } $td.css('background-image', oldBackgroundImage); - }); + } + ); }); }, @@ -1144,7 +1171,7 @@ var filename = input.val(); if (filename !== oldname) { // Files.isFileNameValid(filename) throws an exception itself - Files.isFileNameValid(filename); + OCA.Files.Files.isFileNameValid(filename); if (self.inList(filename)) { throw t('files', '{new_name} already exists', {new_name: filename}); } @@ -1299,7 +1326,7 @@ self.updateEmptyContent(); self.fileSummary.update(); self.updateSelectionSummary(); - Files.updateStorageStatistics(); + self.updateStorageStatistics(); } else { if (result.status === 'error' && result.data.message) { OC.Notification.show(result.data.message); @@ -1406,6 +1433,7 @@ */ updateSelectionSummary: function() { var summary = this._selectionSummary.summary; + var canDelete; if (summary.totalFiles === 0 && summary.totalDirs === 0) { this.$el.find('#headerName a.name>span:first').text(t('files','Name')); this.$el.find('#headerSize a>span:first').text(t('files','Size')); @@ -1414,6 +1442,7 @@ this.$el.find('.selectedActions').addClass('hidden'); } else { + canDelete = (this.getDirectoryPermissions() & OC.PERMISSION_DELETE); this.$el.find('.selectedActions').removeClass('hidden'); this.$el.find('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); var selection = ''; @@ -1429,6 +1458,7 @@ this.$el.find('#headerName a.name>span:first').text(selection); this.$el.find('#modified a>span:first').text(''); this.$el.find('table').addClass('multiselect'); + this.$el.find('.delete-selected').toggleClass('hidden', !canDelete); } }, @@ -1437,7 +1467,7 @@ * @return true if all files are selected, false otherwise */ isAllSelected: function() { - return this.$el.find('#select_all').prop('checked'); + return this.$el.find('.select-all').prop('checked'); }, /** @@ -1475,7 +1505,10 @@ } return name; }, - + + /** + * Setup file upload events related to the file-upload plugin + */ setupUploadEvents: function() { var self = this; @@ -1486,6 +1519,11 @@ OC.Upload.log('filelist handle fileuploaddrop', e, data); var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); + // check if dropped inside this list at all + if (dropTarget && !self.$el.has(dropTarget).length) { + return false; + } + if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder // remember as context @@ -1515,7 +1553,7 @@ }; } else { // cancel uploads to current dir if no permission - var isCreatable = (this.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; + var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; if (!isCreatable) { return false; } @@ -1655,6 +1693,7 @@ uploadText.fadeOut(); uploadText.attr('currentUploads', 0); } + self.updateStorageStatistics(); }); fileUploadStart.on('fileuploadfail', function(e, data) { OC.Upload.log('filelist handle fileuploadfail', e, data); @@ -1668,6 +1707,7 @@ uploadText.fadeOut(); uploadText.attr('currentUploads', 0); } + self.updateStorageStatistics(); }); } diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 9ab8d0fcb5..81588c2f96 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -15,13 +15,8 @@ (function() { var Files = { // file space size sync - _updateStorageStatistics: function() { - // FIXME - console.warn('FIXME: storage statistics!'); - return; - Files._updateStorageStatisticsTimeout = null; - var currentDir = OCA.Files.FileList.getCurrentDirectory(), - state = Files.updateStorageStatistics; + _updateStorageStatistics: function(currentDir) { + var state = Files.updateStorageStatistics; if (state.dir){ if (state.dir === currentDir) { return; @@ -36,20 +31,26 @@ Files.updateMaxUploadFilesize(response); }); }, - updateStorageStatistics: function(force) { + /** + * Update storage statistics such as free space, max upload, + * etc based on the given directory. + * + * Note this function is debounced to avoid making too + * many ajax calls in a row. + * + * @param dir directory + * @param force whether to force retrieving + */ + updateStorageStatistics: function(dir, force) { if (!OC.currentUser) { return; } - // debounce to prevent calling too often - if (Files._updateStorageStatisticsTimeout) { - clearTimeout(Files._updateStorageStatisticsTimeout); - } if (force) { - Files._updateStorageStatistics(); + Files._updateStorageStatistics(dir); } else { - Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250); + Files._updateStorageStatisticsDebounced(dir); } }, @@ -149,24 +150,6 @@ } }, - // TODO: move to FileList class - setupDragAndDrop: function() { - var $fileList = $('#fileList'); - - //drag/drop of files - $fileList.find('tr td.filename').each(function(i,e) { - if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) { - $(e).draggable(dragOptions); - } - }); - - $fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) { - if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) { - $(e).droppable(folderDropOptions); - } - }); - }, - /** * Returns the download URL of the given file(s) * @param filename string or array of file names to download @@ -243,9 +226,7 @@ initialize: function() { Files.getMimeIcon.cache = {}; Files.displayEncryptionWarning(); - Files.bindKeyboardShortcuts(document, jQuery); - - Files.setupDragAndDrop(); + Files.bindKeyboardShortcuts(document, $); // TODO: move file list related code (upload) to OCA.Files.FileList $('#file_action_panel').attr('activeAction', false); @@ -259,7 +240,6 @@ // Trigger cancelling of file upload $('#uploadprogresswrapper .stop').on('click', function() { OC.Upload.cancelUploads(); - OCA.Files.FileList.updateSelectionSummary(); }); // drag&drop support using jquery.fileupload @@ -275,18 +255,19 @@ setTimeout(Files.displayStorageWarnings, 100); OC.Notification.setDefault(Files.displayStorageWarnings); - // only possible at the moment if user is logged in - if (OC.currentUser) { + // only possible at the moment if user is logged in or the files app is loaded + if (OC.currentUser && OCA.Files.App) { // start on load - we ask the server every 5 minutes var updateStorageStatisticsInterval = 5*60*1000; - var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); + var updateStorageStatisticsIntervalId = setInterval(OCA.Files.App.fileList.updateStorageStatistics, updateStorageStatisticsInterval); + // TODO: this should also stop when switching to another view // Use jquery-visibility to de-/re-activate file stats sync if ($.support.pageVisibility) { $(document).on({ 'show.visibility': function() { if (!updateStorageStatisticsIntervalId) { - updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); + updateStorageStatisticsIntervalId = setInterval(OCA.Files.App.fileList.updateStorageStatistics, updateStorageStatisticsInterval); } }, 'hide.visibility': function() { @@ -314,6 +295,7 @@ } } + Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250); OCA.Files.Files = Files; })(); @@ -349,7 +331,9 @@ function scanFiles(force, dir, users) { scannerEventSource.listen('done',function(count) { scanFiles.scanning=false; console.log('done after ' + count + ' files'); - OCA.Files.Files.updateStorageStatistics(); + if (OCA.Files.App) { + OCA.Files.App.fileList.updateStorageStatistics(true); + } }); scannerEventSource.listen('user',function(user) { console.log('scanning files for ' + user); @@ -360,7 +344,7 @@ scanFiles.scanning=false; // TODO: move to FileList var createDragShadow = function(event) { //select dragged file - var FileList = OCA.Files.FileList; + var FileList = OCA.Files.App.fileList; var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked'); if (!isDragSelected) { //select dragged file @@ -395,7 +379,7 @@ var createDragShadow = function(event) { newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')'); } else { var path = dir + '/' + elem.name; - OCA.Files.App.fileList.lazyLoadPreview(path, elem.mime, function(previewpath) { + OCA.Files.App.files.lazyLoadPreview(path, elem.mime, function(previewpath) { newtr.find('td.filename').attr('style','background-image:url('+previewpath+')'); }, null, null, elem.etag); } @@ -446,7 +430,7 @@ var folderDropOptions = { hoverClass: "canDrop", drop: function( event, ui ) { // don't allow moving a file into a selected folder - var FileList = OCA.Files.FileList; + var FileList = OCA.Files.App.fileList; if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) { return false; } diff --git a/apps/files/js/keyboardshortcuts.js b/apps/files/js/keyboardshortcuts.js index 9d6c3ae8c3..b2f2cd0e58 100644 --- a/apps/files/js/keyboardshortcuts.js +++ b/apps/files/js/keyboardshortcuts.js @@ -12,7 +12,6 @@ * enter: open file/folder * delete/backspace: delete file/folder *****************************/ -var Files = Files || {}; (function(Files) { var keys = []; var keyCodes = { @@ -167,4 +166,4 @@ var Files = Files || {}; removeA(keys, event.keyCode); }); }; -})(Files); +})((OCA.Files && OCA.Files.Files) || {}); diff --git a/apps/files/js/navigation.js b/apps/files/js/navigation.js index c4a02ee754..c58a284e83 100644 --- a/apps/files/js/navigation.js +++ b/apps/files/js/navigation.js @@ -73,24 +73,39 @@ * @param array options "silent" to not trigger event */ setActiveItem: function(itemId, options) { + var oldItemId = this._activeItem; if (itemId === this._activeItem) { + if (!options || !options.silent) { + this.$el.trigger( + new $.Event('itemChanged', {itemId: itemId, previousItemId: oldItemId}) + ); + } return; } - this._activeItem = itemId; this.$el.find('li').removeClass('selected'); if (this.$currentContent) { this.$currentContent.addClass('hidden'); this.$currentContent.trigger(jQuery.Event('hide')); } + this._activeItem = itemId; + this.$el.find('li[data-id=' + itemId + ']').addClass('selected'); this.$currentContent = $('#app-content-' + itemId); this.$currentContent.removeClass('hidden'); - this.$el.find('li[data-id=' + itemId + ']').addClass('selected'); if (!options || !options.silent) { this.$currentContent.trigger(jQuery.Event('show')); - this.$el.trigger(new $.Event('itemChanged', {itemId: itemId})); + this.$el.trigger( + new $.Event('itemChanged', {itemId: itemId, previousItemId: oldItemId}) + ); } }, + /** + * Returns whether a given item exists + */ + itemExists: function(itemId) { + return this.$el.find('li[data-id=' + itemId + ']').length; + }, + /** * Event handler for when clicking on an item. */ diff --git a/apps/files/list.php b/apps/files/list.php index 78b1b072b8..e583839b25 100644 --- a/apps/files/list.php +++ b/apps/files/list.php @@ -21,29 +21,17 @@ * */ - // Check if we are a user OCP\User::checkLoggedIn(); -// dummy, will be refreshed with an ajax call -$dir = '/'; - -// information about storage capacities -// FIXME: storage info -/* -$storageInfo=OC_Helper::getStorageInfo($dir, $dirInfo); +$config = \OC::$server->getConfig(); +// TODO: move this to the generated config.js $publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes'); $uploadLimit=OCP\Util::uploadLimit(); -$maxUploadFilesize=OCP\Util::maxUploadFilesize($dir, $freeSpace); -$freeSpace=$storageInfo['free']; -*/ +// renders the controls and table headers template $tmpl = new OCP\Template('files', 'list', ''); -$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']); -$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); // minimium of freeSpace and uploadLimit -$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); $tmpl->assign('uploadLimit', $uploadLimit); // PHP upload limit -$tmpl->assign('freeSpace', $freeSpace); $tmpl->assign('publicUploadEnabled', $publicUploadEnabled); $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); $tmpl->printPage(); diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index ce065a987f..8cab4ce220 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -10,7 +10,6 @@ - diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php index a11defae05..8f11f965b2 100644 --- a/apps/files/templates/list.php +++ b/apps/files/templates/list.php @@ -18,19 +18,20 @@ +
- = 0):?> - - - - + title="t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) : '' ?>"> + + + + value="(max )"> @@ -56,8 +57,8 @@
- - + + t( 'Name' )); ?> @@ -88,6 +89,8 @@ + +

diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js index 315f11f4b5..0e9abad698 100644 --- a/apps/files/tests/js/appSpec.js +++ b/apps/files/tests/js/appSpec.js @@ -26,6 +26,7 @@ describe('OCA.Files.App tests', function() { beforeEach(function() { $('#testArea').append( + '

' + '
' + '
  • Files
  • ' + '
  • Other
  • ' + @@ -36,6 +37,7 @@ describe('OCA.Files.App tests', function() { '' + '
' + + '
' + '
' ); @@ -89,30 +91,43 @@ describe('OCA.Files.App tests', function() { expect(handler.getCall(0).args[0].dir).toEqual('/somedir'); }); it('sends "show" event to current app and sets navigation', function() { - var handlerFiles = sinon.stub(); - var handlerOther = sinon.stub(); - $('#app-content-files').on('show', handlerFiles); - $('#app-content-other').on('show', handlerOther); + var showHandlerFiles = sinon.stub(); + var showHandlerOther = sinon.stub(); + var hideHandlerFiles = sinon.stub(); + var hideHandlerOther = sinon.stub(); + $('#app-content-files').on('show', showHandlerFiles); + $('#app-content-files').on('hide', hideHandlerFiles); + $('#app-content-other').on('show', showHandlerOther); + $('#app-content-other').on('hide', hideHandlerOther); App._onPopState({view: 'other', dir: '/somedir'}); - expect(handlerFiles.notCalled).toEqual(true); - expect(handlerOther.calledOnce).toEqual(true); + expect(showHandlerFiles.notCalled).toEqual(true); + expect(hideHandlerFiles.calledOnce).toEqual(true); + expect(showHandlerOther.calledOnce).toEqual(true); + expect(hideHandlerOther.notCalled).toEqual(true); - handlerFiles.reset(); - handlerOther.reset(); + showHandlerFiles.reset(); + showHandlerOther.reset(); + hideHandlerFiles.reset(); + hideHandlerOther.reset(); App._onPopState({view: 'files', dir: '/somedir'}); - expect(handlerFiles.calledOnce).toEqual(true); - expect(handlerOther.notCalled).toEqual(true); + expect(showHandlerFiles.calledOnce).toEqual(true); + expect(hideHandlerFiles.notCalled).toEqual(true); + expect(showHandlerOther.notCalled).toEqual(true); + expect(hideHandlerOther.calledOnce).toEqual(true); expect(App.navigation.getActiveItem()).toEqual('files'); expect($('#app-content-files').hasClass('hidden')).toEqual(false); expect($('#app-content-other').hasClass('hidden')).toEqual(true); }); - it('does not send "show" event to current app when already visible', function() { - var handler = sinon.stub(); - $('#app-content-files').on('show', handler); + it('does not send "show" or "hide" event to current app when already visible', function() { + var showHandler = sinon.stub(); + var hideHandler = sinon.stub(); + $('#app-content-files').on('show', showHandler); + $('#app-content-files').on('hide', hideHandler); App._onPopState({view: 'files', dir: '/somedir'}); - expect(handler.notCalled).toEqual(true); + expect(showHandler.notCalled).toEqual(true); + expect(hideHandler.notCalled).toEqual(true); }); it('state defaults to files app with root dir', function() { var handler = sinon.stub(); @@ -123,6 +138,12 @@ describe('OCA.Files.App tests', function() { expect(handler.getCall(0).args[0].view).toEqual('files'); expect(handler.getCall(0).args[0].dir).toEqual('/'); }); + it('activates files app if invalid view is passed', function() { + App._onPopState({view: 'invalid', dir: '/somedir'}); + + expect(App.navigation.getActiveItem()).toEqual('files'); + expect($('#app-content-files').hasClass('hidden')).toEqual(false); + }); }); describe('navigation', function() { it('switches the navigation item and panel visibility when onpopstate', function() { @@ -156,13 +177,43 @@ describe('OCA.Files.App tests', function() { expect($('li[data-id=files]').hasClass('selected')).toEqual(true); expect($('li[data-id=other]').hasClass('selected')).toEqual(false); }); - it('clicking on navigation sends "urlChanged" event', function() { + it('clicking on navigation sends "show" and "urlChanged" event', function() { var handler = sinon.stub(); + var showHandler = sinon.stub(); $('#app-content-other').on('urlChanged', handler); + $('#app-content-other').on('show', showHandler); $('li[data-id=other]>a').click(); expect(handler.calledOnce).toEqual(true); expect(handler.getCall(0).args[0].view).toEqual('other'); expect(handler.getCall(0).args[0].dir).toEqual('/'); + expect(showHandler.calledOnce).toEqual(true); + }); + it('clicking on activate navigation only sends "urlChanged" event', function() { + var handler = sinon.stub(); + var showHandler = sinon.stub(); + $('#app-content-files').on('urlChanged', handler); + $('#app-content-files').on('show', showHandler); + $('li[data-id=files]>a').click(); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].view).toEqual('files'); + expect(handler.getCall(0).args[0].dir).toEqual('/'); + expect(showHandler.notCalled).toEqual(true); + }); + }); + describe('viewer mode', function() { + it('toggles the sidebar when viewer mode is enabled', function() { + $('#app-content-files').trigger( + new $.Event('changeViewerMode', {viewerModeEnabled: true} + )); + expect($('#app-navigation').hasClass('hidden')).toEqual(true); + expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(true); + + $('#app-content-files').trigger( + new $.Event('changeViewerMode', {viewerModeEnabled: false} + )); + + expect($('#app-navigation').hasClass('hidden')).toEqual(false); + expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(false); }); }); }); diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js index 23a13be45d..9152dbb58c 100644 --- a/apps/files/tests/js/fileactionsSpec.js +++ b/apps/files/tests/js/fileactionsSpec.js @@ -38,12 +38,19 @@ describe('OCA.Files.FileActions tests', function() { fileList = undefined; $('#dir, #permissions, #filestable').remove(); }); + it('calling clear() clears file actions', function() { + FileActions.clear(); + expect(FileActions.actions).toEqual({}); + expect(FileActions.defaults).toEqual({}); + expect(FileActions.icons).toEqual({}); + expect(FileActions.currentFile).toBe(null); + }); it('calling display() sets file actions', function() { var fileData = { id: 18, type: 'file', name: 'testName.txt', - mimetype: 'plain/text', + mimetype: 'text/plain', size: '1234', etag: 'a01234c', mtime: '123456' @@ -64,7 +71,7 @@ describe('OCA.Files.FileActions tests', function() { id: 18, type: 'file', name: 'testName.txt', - mimetype: 'plain/text', + mimetype: 'text/plain', size: '1234', etag: 'a01234c', mtime: '123456' @@ -85,7 +92,7 @@ describe('OCA.Files.FileActions tests', function() { id: 18, type: 'file', name: 'testName.txt', - mimetype: 'plain/text', + mimetype: 'text/plain', size: '1234', etag: 'a01234c', mtime: '123456' @@ -105,7 +112,7 @@ describe('OCA.Files.FileActions tests', function() { id: 18, type: 'file', name: 'testName.txt', - mimetype: 'plain/text', + mimetype: 'text/plain', size: '1234', etag: 'a01234c', mtime: '123456' diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index a0d8870f33..bfc983c748 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -51,19 +51,13 @@ describe('OCA.Files.FileList tests', function() { } beforeEach(function() { - // init horrible parameters - var $body = $('body'); - $body.append(''); - $body.append(''); - // dummy files table - $body.append('
'); - alertStub = sinon.stub(OC.dialogs, 'alert'); notificationStub = sinon.stub(OC.Notification, 'show'); // init parameters and test table elements $('#testArea').append( '
' + + // init horrible parameters '' + '' + // dummy controls @@ -76,7 +70,7 @@ describe('OCA.Files.FileList tests', function() { '' + '' + '' + '' + '' + - '' + + '' + '' + '
' + '
Empty content message
' + @@ -123,6 +117,7 @@ describe('OCA.Files.FileList tests', function() { }]; fileList = new OCA.Files.FileList($('#app-content-files')); + FileActions.clear(); FileActions.registerDefaultActions(fileList); fileList.setFileActions(FileActions); }); @@ -131,7 +126,6 @@ describe('OCA.Files.FileList tests', function() { fileList = undefined; FileActions.clear(); - $('#dir, #permissions, #filestable').remove(); notificationStub.restore(); alertStub.restore(); }); @@ -160,7 +154,7 @@ describe('OCA.Files.FileList tests', function() { id: 18, type: 'file', name: 'testName.txt', - mimetype: 'plain/text', + mimetype: 'text/plain', size: '1234', etag: 'a01234c', mtime: '123456' @@ -175,9 +169,11 @@ describe('OCA.Files.FileList tests', function() { expect($tr.attr('data-size')).toEqual('1234'); expect($tr.attr('data-etag')).toEqual('a01234c'); expect($tr.attr('data-permissions')).toEqual('31'); - expect($tr.attr('data-mime')).toEqual('plain/text'); + expect($tr.attr('data-mime')).toEqual('text/plain'); expect($tr.attr('data-mtime')).toEqual('123456'); - expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt'); + expect($tr.find('a.name').attr('href')) + .toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt'); + expect($tr.find('.nametext').text().trim()).toEqual('testName.txt'); expect($tr.find('.filesize').text()).toEqual('1 kB'); expect(fileList.findFileEl('testName.txt')[0]).toEqual($tr[0]); @@ -482,7 +478,9 @@ describe('OCA.Files.FileList tests', function() { // trigger rename prompt fileList.rename('One.txt'); $input = fileList.$fileList.find('input.filename'); - $input.val('Tu_after_three.txt').blur(); + $input.val('Tu_after_three.txt'); + // trigger submit because triggering blur doesn't work in all browsers + $input.closest('form').trigger('submit'); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; @@ -926,6 +924,18 @@ describe('OCA.Files.FileList tests', function() { expect($('.actions').hasClass('hidden')).toEqual(true); expect($('.notCreatable').hasClass('hidden')).toEqual(false); }); + it('toggling viewer mode triggers event', function() { + var handler = sinon.stub(); + fileList.$el.on('changeViewerMode', handler); + fileList.setViewerMode(true); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].viewerModeEnabled).toEqual(true); + + handler.reset(); + fileList.setViewerMode(false); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].viewerModeEnabled).toEqual(false); + }); }); describe('loading file list', function() { beforeEach(function() { @@ -1198,27 +1208,27 @@ describe('OCA.Files.FileList tests', function() { expect(selection).toContain('Three.pdf'); }); it('Selecting all files will automatically check "select all" checkbox', function() { - expect($('#select_all').prop('checked')).toEqual(false); + expect($('.select-all').prop('checked')).toEqual(false); $('#fileList tr td.filename input:checkbox').click(); - expect($('#select_all').prop('checked')).toEqual(true); + expect($('.select-all').prop('checked')).toEqual(true); }); it('Selecting all files on the first visible page will not automatically check "select all" checkbox', function() { fileList.setFiles(generateFiles(0, 41)); - expect($('#select_all').prop('checked')).toEqual(false); + expect($('.select-all').prop('checked')).toEqual(false); $('#fileList tr td.filename input:checkbox').click(); - expect($('#select_all').prop('checked')).toEqual(false); + expect($('.select-all').prop('checked')).toEqual(false); }); it('Clicking "select all" will select/deselect all files', function() { fileList.setFiles(generateFiles(0, 41)); - $('#select_all').click(); - expect($('#select_all').prop('checked')).toEqual(true); + $('.select-all').click(); + expect($('.select-all').prop('checked')).toEqual(true); $('#fileList tr input:checkbox').each(function() { expect($(this).prop('checked')).toEqual(true); }); expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(42); - $('#select_all').click(); - expect($('#select_all').prop('checked')).toEqual(false); + $('.select-all').click(); + expect($('.select-all').prop('checked')).toEqual(false); $('#fileList tr input:checkbox').each(function() { expect($(this).prop('checked')).toEqual(false); @@ -1226,44 +1236,44 @@ describe('OCA.Files.FileList tests', function() { expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(0); }); it('Clicking "select all" then deselecting a file will uncheck "select all"', function() { - $('#select_all').click(); - expect($('#select_all').prop('checked')).toEqual(true); + $('.select-all').click(); + expect($('.select-all').prop('checked')).toEqual(true); var $tr = fileList.findFileEl('One.txt'); $tr.find('input:checkbox').click(); - expect($('#select_all').prop('checked')).toEqual(false); + expect($('.select-all').prop('checked')).toEqual(false); expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3); }); it('Updates the selection summary when doing a few manipulations with "Select all"', function() { - $('#select_all').click(); - expect($('#select_all').prop('checked')).toEqual(true); + $('.select-all').click(); + expect($('.select-all').prop('checked')).toEqual(true); var $tr = fileList.findFileEl('One.txt'); // unselect one $tr.find('input:checkbox').click(); - expect($('#select_all').prop('checked')).toEqual(false); + expect($('.select-all').prop('checked')).toEqual(false); expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3); // select all - $('#select_all').click(); - expect($('#select_all').prop('checked')).toEqual(true); + $('.select-all').click(); + expect($('.select-all').prop('checked')).toEqual(true); expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(4); // unselect one $tr.find('input:checkbox').click(); - expect($('#select_all').prop('checked')).toEqual(false); + expect($('.select-all').prop('checked')).toEqual(false); expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3); // re-select it $tr.find('input:checkbox').click(); - expect($('#select_all').prop('checked')).toEqual(true); + expect($('.select-all').prop('checked')).toEqual(true); expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(4); }); it('Auto-selects files on next page when "select all" is checked', function() { fileList.setFiles(generateFiles(0, 41)); - $('#select_all').click(); + $('.select-all').click(); expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(20); fileList._nextPage(true); @@ -1295,7 +1305,7 @@ describe('OCA.Files.FileList tests', function() { expect($actions.hasClass('hidden')).toEqual(true); }); it('Selection is cleared when switching dirs', function() { - $('#select_all').click(); + $('.select-all').click(); var data = { status: 'success', data: { @@ -1311,13 +1321,13 @@ describe('OCA.Files.FileList tests', function() { ]); fileList.changeDirectory('/'); fakeServer.respond(); - expect($('#select_all').prop('checked')).toEqual(false); + expect($('.select-all').prop('checked')).toEqual(false); expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual([]); }); it('getSelectedFiles returns the selected files even when they are on the next page', function() { var selectedFiles; fileList.setFiles(generateFiles(0, 41)); - $('#select_all').click(); + $('.select-all').click(); // unselect one to not have the "allFiles" case fileList.$fileList.find('tr input:checkbox:first').click(); @@ -1326,6 +1336,18 @@ describe('OCA.Files.FileList tests', function() { expect(selectedFiles.length).toEqual(41); }); + describe('Selection overlay', function() { + it('show delete action according to directory permissions', function() { + fileList.setFiles(testFiles); + $('#permissions').val(OC.PERMISSION_READ | OC.PERMISSION_DELETE); + $('.select-all').click(); + expect(fileList.$el.find('.delete-selected').hasClass('hidden')).toEqual(false); + $('.select-all').click(); + $('#permissions').val(OC.PERMISSION_READ); + $('.select-all').click(); + expect(fileList.$el.find('.delete-selected').hasClass('hidden')).toEqual(true); + }); + }); describe('Actions', function() { beforeEach(function() { fileList.findFileEl('One.txt').find('input:checkbox').click(); @@ -1391,7 +1413,7 @@ describe('OCA.Files.FileList tests', function() { }); it('Downloads root folder when all selected in root folder', function() { $('#dir').val('/'); - $('#select_all').click(); + $('.select-all').click(); var redirectStub = sinon.stub(OC, 'redirect'); $('.selectedActions .download').click(); expect(redirectStub.calledOnce).toEqual(true); @@ -1399,7 +1421,7 @@ describe('OCA.Files.FileList tests', function() { redirectStub.restore(); }); it('Downloads parent folder when all selected in subfolder', function() { - $('#select_all').click(); + $('.select-all').click(); var redirectStub = sinon.stub(OC, 'redirect'); $('.selectedActions .download').click(); expect(redirectStub.calledOnce).toEqual(true); @@ -1428,7 +1450,7 @@ describe('OCA.Files.FileList tests', function() { }); it('Deletes all files when all selected when "Delete" clicked', function() { var request; - $('#select_all').click(); + $('.select-all').click(); $('.selectedActions .delete-selected').click(); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; @@ -1445,9 +1467,9 @@ describe('OCA.Files.FileList tests', function() { }); }); it('resets the file selection on reload', function() { - fileList.$el.find('#select_all').click(); + fileList.$el.find('.select-all').click(); fileList.reload(); - expect(fileList.$el.find('#select_all').prop('checked')).toEqual(false); + expect(fileList.$el.find('.select-all').prop('checked')).toEqual(false); expect(fileList.getSelectedFiles()).toEqual([]); }); }); diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css index 8abeb8b034..70897af9eb 100644 --- a/apps/files_sharing/css/public.css +++ b/apps/files_sharing/css/public.css @@ -1,11 +1,3 @@ -#controls { - left: 0; -} - -#filestable { - margin-top: 90px; -} - #preview { background: #fff; text-align: center; diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 6ee54968e1..d3d4479215 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -8,7 +8,8 @@ * */ -/* global OC, FileActions, FileList, Files */ +/* global FileActions, Files */ +/* global dragOptions, folderDropOptions */ OCA.Sharing = {}; if (!OCA.Files) { OCA.Files = {}; @@ -23,7 +24,16 @@ OCA.Sharing.PublicApp = { this._initialized = true; // file list mode ? if ($el.find('#filestable')) { - this.fileList = new OCA.Files.FileList($el); + this.fileList = new OCA.Files.FileList( + $el, + { + scrollContainer: $(window), + dragOptions: dragOptions, + folderDropOptions: folderDropOptions + } + ); + this.files = OCA.Files.Files; + this.files.initialize(); } var mimetype = $('#mimetype').val(); @@ -145,5 +155,17 @@ OCA.Sharing.PublicApp = { $(document).ready(function() { var App = OCA.Sharing.PublicApp; App.initialize($('#preview')); + + // HACK: for oc-dialogs previews that depends on Files: + Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) { + return App.fileList.lazyLoadPreview({ + path: path, + mime: mime, + callback: ready, + width: width, + height: height, + etag: etag + }); + }; }); diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index ac46ab7b9e..973c63c5d7 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -8,14 +8,15 @@ * */ -/* global OC, t, FileList, FileActions */ +/* global FileList, FileActions */ $(document).ready(function() { var sharesLoaded = false; if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined') { - var oldCreateRow = FileList._createRow; - FileList._createRow = function(fileData) { + // TODO: make a separate class for this or a hook or jQuery event ? + var oldCreateRow = OCA.Files.FileList.prototype._createRow; + OCA.Files.FileList.prototype._createRow = function(fileData) { var tr = oldCreateRow.apply(this, arguments); if (fileData.shareOwner) { tr.attr('data-share-owner', fileData.shareOwner); @@ -24,14 +25,16 @@ $(document).ready(function() { }; $('#fileList').on('fileActionsReady',function(){ - - var allShared = $('#fileList').find('[data-share-owner] [data-Action="Share"]'); + var $fileList = $(this); + var allShared = $fileList.find('[data-share-owner] [data-Action="Share"]'); allShared.addClass('permanent'); allShared.find('span').text(function(){ var $owner = $(this).closest('tr').attr('data-share-owner'); return ' ' + t('files_sharing', 'Shared by {owner}', {owner: $owner}); }); + // FIXME: these calls are also working on hard-coded + // list selectors... if (!sharesLoaded){ OC.Share.loadIcons('file'); // assume that we got all shares, so switching directories diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index a73d97f4fe..8a86cb3806 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -153,7 +153,7 @@ if (isset($path)) { $folder->assign('dir', $getPath); $folder->assign('dirToken', $linkItem['token']); $folder->assign('permissions', OCP\PERMISSION_READ); - $folder->assign('isPublic',true); + $folder->assign('isPublic', true); $folder->assign('publicUploadEnabled', 'no'); $folder->assign('files', $files); $folder->assign('uploadMaxFilesize', $maxUploadFilesize); diff --git a/apps/files_trashbin/appinfo/app.php b/apps/files_trashbin/appinfo/app.php index 06c2e3447f..b8900ee0de 100644 --- a/apps/files_trashbin/appinfo/app.php +++ b/apps/files_trashbin/appinfo/app.php @@ -8,7 +8,7 @@ $l = OC_L10N::get('files_trashbin'); array( "id" => 'trashbin', "appname" => 'files_trashbin', - "script" => 'index.php', + "script" => 'list.php', "order" => 1, "name" => $l->t('Deleted files') ) diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css index 7ca3e355fc..04b4a175c8 100644 --- a/apps/files_trashbin/css/trash.css +++ b/apps/files_trashbin/css/trash.css @@ -1,4 +1,13 @@ -#fileList tr[data-type="file"] td a.name, -#fileList tr[data-type="file"] td a.name span { +/* + * Copyright (c) 2014 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ +#app-content-trashbin tbody tr[data-type="file"] td a.name, +#app-content-trashbin tbody tr[data-type="file"] td a.name span { cursor: default; } diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js index 9ab78e7cbb..aa499ae179 100644 --- a/apps/files_trashbin/js/app.js +++ b/apps/files_trashbin/js/app.js @@ -17,7 +17,11 @@ OCA.Trashbin.App = { return; } this._initialized = true; - this.fileList = new OCA.Trashbin.FileList($el); + this.fileList = new OCA.Trashbin.FileList( + $('#app-content-trashbin'), { + scrollContainer: $('#app-content') + } + ); this.registerFileActions(this.fileList); }, @@ -68,7 +72,7 @@ OCA.Trashbin.App = { }; $(document).ready(function() { - $('#app-content-trashbin').on('show', function() { + $('#app-content-trashbin').one('show', function() { var App = OCA.Trashbin.App; App.initialize($('#app-content-trashbin')); // force breadcrumb init diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index d3206958e8..205f879f33 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -30,6 +30,7 @@ this.initialize($el); }; FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, { + id: 'trashbin', appName: t('files_trashbin', 'Deleted files'), initialize: function() { @@ -37,11 +38,6 @@ this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this)); this.setSort('mtime', 'desc'); - - // override crumb URL maker - this.breadcrumb.getCrumbUrl = function(part, index) { - return OC.linkTo('files_trashbin', 'index.php')+"?view=trashbin&dir=" + encodeURIComponent(part.dir); - }; /** * Override crumb making to add "Deleted Files" entry * and convert files with ".d" extensions to a more @@ -58,6 +54,13 @@ return result; }, + /** + * Override to only return read permissions + */ + getDirectoryPermissions: function() { + return OC.PERMISSION_READ | OC.PERMISSION_DELETE; + }, + _setCurrentDir: function(targetDir) { OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments); @@ -97,8 +100,12 @@ return OC.filePath('files_trashbin', 'ajax', action + '.php') + q; }, + setupUploadEvents: function() { + // override and do nothing + }, + linkTo: function(dir){ - return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); + return OC.linkTo('files', 'index.php')+"?view=trashbin&dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); }, updateEmptyContent: function(){ @@ -126,7 +133,7 @@ _onClickRestoreSelected: function(event) { event.preventDefault(); var self = this; - var allFiles = this.$el.find('#select_all').is(':checked'); + var allFiles = this.$el.find('.select-all').is(':checked'); var files = []; var params = {}; this.disableActions(); @@ -171,7 +178,7 @@ _onClickDeleteSelected: function(event) { event.preventDefault(); var self = this; - var allFiles = this.$el.find('#select_all').is(':checked'); + var allFiles = this.$el.find('.select-all').is(':checked'); var files = []; var params = {}; if (allFiles) { @@ -230,7 +237,7 @@ return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec); }, - getDownloadUrl: function(action, params) { + getDownloadUrl: function() { // no downloads return '#'; }, @@ -243,6 +250,11 @@ disableActions: function() { this.$el.find('.action').css('display', 'none'); this.$el.find(':input:checkbox').css('display', 'none'); + }, + + updateStorageStatistics: function() { + // no op because the trashbin doesn't have + // storage info like free space / used space } }); diff --git a/apps/files_trashbin/js/files.js b/apps/files_trashbin/js/files.js deleted file mode 100644 index f46b96a40b..0000000000 --- a/apps/files_trashbin/js/files.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2014 - * - * This file is licensed under the Affero General Public License version 3 - * or later. - * - * See the COPYING-README file. - * - */ - -(function() { - - var Files = _.extend({}, OCA.Files.Files, { - updateStorageStatistics: function() { - // no op because the trashbin doesn't have - // storage info like free space / used space - } - - }); - - OCA.Trashbin.Files = Files; -})(); - diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/list.php similarity index 85% rename from apps/files_trashbin/index.php rename to apps/files_trashbin/list.php index 08227b7f32..b4047b82ef 100644 --- a/apps/files_trashbin/index.php +++ b/apps/files_trashbin/list.php @@ -7,6 +7,5 @@ OCP\User::checkLoggedIn(); $tmpl = new OCP\Template('files_trashbin', 'index', ''); OCP\Util::addStyle('files_trashbin', 'trash'); OCP\Util::addScript('files_trashbin', 'app'); -OCP\Util::addScript('files_trashbin', 'files'); OCP\Util::addScript('files_trashbin', 'filelist'); $tmpl->printPage(); diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index 6622c1d8f5..fc18e88c41 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -13,8 +13,8 @@
- - + + t( 'Name' )); ?> diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js index 291b2ffe14..d41c24c3cc 100644 --- a/apps/files_trashbin/tests/js/filelistSpec.js +++ b/apps/files_trashbin/tests/js/filelistSpec.js @@ -24,19 +24,16 @@ describe('OCA.Trashbin.FileList tests', function() { var FileActions = OCA.Files.FileActions; beforeEach(function() { - // init horrible parameters - var $body = $('body'); - $body.append(''); - // dummy files table - $body.append('
'); - alertStub = sinon.stub(OC.dialogs, 'alert'); notificationStub = sinon.stub(OC.Notification, 'show'); // init parameters and test table elements $('#testArea').append( '
' + + // init horrible parameters '' + + // set this but it shouldn't be used (could be the one from the + // files app) '' + // dummy controls '
' + @@ -47,13 +44,13 @@ describe('OCA.Trashbin.FileList tests', function() { // TODO: at some point this will be rendered by the fileList class itself! '' + '' + - '' + + '' + '' + '
' + '
Empty content message
' + @@ -66,7 +63,6 @@ describe('OCA.Trashbin.FileList tests', function() { name: 'One.txt', mtime: 11111000, mimetype: 'text/plain', - size: 12, etag: 'abc' }, { id: 2, @@ -74,7 +70,6 @@ describe('OCA.Trashbin.FileList tests', function() { name: 'Two.jpg', mtime: 22222000, mimetype: 'image/jpeg', - size: 12049, etag: 'def', }, { id: 3, @@ -82,7 +77,6 @@ describe('OCA.Trashbin.FileList tests', function() { name: 'Three.pdf', mtime: 33333000, mimetype: 'application/pdf', - size: 58009, etag: '123', }, { id: 4, @@ -90,7 +84,6 @@ describe('OCA.Trashbin.FileList tests', function() { mtime: 99999000, name: 'somedir', mimetype: 'httpd/unix-directory', - size: 250, etag: '456' }]; @@ -106,10 +99,91 @@ describe('OCA.Trashbin.FileList tests', function() { notificationStub.restore(); alertStub.restore(); }); + describe('Initialization', function() { + it('Sorts by mtime by default', function() { + expect(fileList._sort).toEqual('mtime'); + expect(fileList._sortDirection).toEqual('desc'); + }); + it('Always returns read and delete permission', function() { + expect(fileList.getDirectoryPermissions()).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE); + }); + }); + describe('Breadcrumbs', function() { + beforeEach(function() { + var data = { + status: 'success', + data: { + files: testFiles, + permissions: 1 + } + }; + fakeServer.respondWith(/\/index\.php\/apps\/files_trashbin\/ajax\/list.php\?dir=%2Fsubdir/, [ + 200, { + "Content-Type": "application/json" + }, + JSON.stringify(data) + ]); + }); + it('links the breadcrumb to the trashbin view', function() { + fileList.changeDirectory('/subdir', false, true); + fakeServer.respond(); + var $crumbs = fileList.$el.find('#controls .crumb'); + expect($crumbs.length).toEqual(2); + expect($crumbs.eq(0).find('a').text()).toEqual(''); + expect($crumbs.eq(0).find('a').attr('href')) + .toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/'); + expect($crumbs.eq(1).find('a').text()).toEqual('subdir'); + expect($crumbs.eq(1).find('a').attr('href')) + .toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/subdir'); + }); + }); describe('Rendering rows', function() { - // TODO. test that rows show the correct name but - // have the real file name with the ".d" suffix - // TODO: with and without dir listing + it('renders rows with the correct data when in root', function() { + // dir listing is false when in root + $('#dir').val('/'); + fileList.setFiles(testFiles); + var $rows = fileList.$el.find('tbody tr'); + var $tr = $rows.eq(0); + expect($rows.length).toEqual(4); + expect($tr.attr('data-id')).toEqual('1'); + expect($tr.attr('data-type')).toEqual('file'); + expect($tr.attr('data-file')).toEqual('One.txt.d11111'); + expect($tr.attr('data-size')).not.toBeDefined(); + expect($tr.attr('data-etag')).toEqual('abc'); + expect($tr.attr('data-permissions')).toEqual('9'); // read and delete + expect($tr.attr('data-mime')).toEqual('text/plain'); + expect($tr.attr('data-mtime')).toEqual('11111000'); + expect($tr.find('a.name').attr('href')).toEqual('#'); + + expect($tr.find('.nametext').text().trim()).toEqual('One.txt'); + + expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]); + }); + it('renders rows with the correct data when in subdirectory', function() { + // dir listing is true when in a subdir + $('#dir').val('/subdir'); + + fileList.setFiles(testFiles); + var $rows = fileList.$el.find('tbody tr'); + var $tr = $rows.eq(0); + expect($rows.length).toEqual(4); + expect($tr.attr('data-id')).toEqual('1'); + expect($tr.attr('data-type')).toEqual('file'); + expect($tr.attr('data-file')).toEqual('One.txt'); + expect($tr.attr('data-size')).not.toBeDefined(); + expect($tr.attr('data-etag')).toEqual('abc'); + expect($tr.attr('data-permissions')).toEqual('9'); // read and delete + expect($tr.attr('data-mime')).toEqual('text/plain'); + expect($tr.attr('data-mtime')).toEqual('11111000'); + expect($tr.find('a.name').attr('href')).toEqual('#'); + + expect($tr.find('.nametext').text().trim()).toEqual('One.txt'); + + expect(fileList.findFileEl('One.txt')[0]).toEqual($tr[0]); + }); + it('does not render a size column', function() { + expect(fileList.$el.find('tbody tr .filesize').length).toEqual(0); + }); }); describe('File actions', function() { describe('Deleting single files', function() { @@ -142,7 +216,6 @@ describe('OCA.Trashbin.FileList tests', function() { fileList.findFileEl('somedir.d99999').find('input:checkbox').click(); }); describe('Delete', function() { - // TODO: also test with "allFiles" it('Deletes selected files when "Delete" clicked', function() { var request; $('.selectedActions .delete-selected').click(); @@ -154,7 +227,16 @@ describe('OCA.Trashbin.FileList tests', function() { fakeServer.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) + JSON.stringify({ + status: 'success', + data: { + success: [ + {filename: 'One.txt.d11111'}, + {filename: 'Three.pdf.d33333'}, + {filename: 'somedir.d99999'} + ] + } + }) ); expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0); expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0); @@ -163,7 +245,7 @@ describe('OCA.Trashbin.FileList tests', function() { }); it('Deletes all files when all selected when "Delete" clicked', function() { var request; - $('#select_all').click(); + $('.select-all').click(); $('.selectedActions .delete-selected').click(); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; @@ -179,7 +261,6 @@ describe('OCA.Trashbin.FileList tests', function() { }); }); describe('Restore', function() { - // TODO: also test with "allFiles" it('Restores selected files when "Restore" clicked', function() { var request; $('.selectedActions .undelete').click(); @@ -191,16 +272,25 @@ describe('OCA.Trashbin.FileList tests', function() { fakeServer.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - JSON.stringify({status: 'success'}) + JSON.stringify({ + status: 'success', + data: { + success: [ + {filename: 'One.txt.d11111'}, + {filename: 'Three.pdf.d33333'}, + {filename: 'somedir.d99999'} + ] + } + }) ); - expect(fileList.findFileEl('One.txt').length).toEqual(0); - expect(fileList.findFileEl('Three.pdf').length).toEqual(0); - expect(fileList.findFileEl('somedir').length).toEqual(0); - expect(fileList.findFileEl('Two.jpg').length).toEqual(1); + expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0); + expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0); + expect(fileList.findFileEl('somedir.d99999').length).toEqual(0); + expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1); }); it('Restores all files when all selected when "Restore" clicked', function() { var request; - $('#select_all').click(); + $('.select-all').click(); $('.selectedActions .undelete').click(); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; diff --git a/core/css/styles.css b/core/css/styles.css index b99af47ff3..d21e6bc690 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -302,6 +302,11 @@ input[type="submit"].enabled { border-bottom: 1px solid #e7e7e7; z-index: 50; } +/* account for shift of controls bar due to app navigation */ +#body-user #controls, +#body-settings #controls { + padding-left: 80px; +} #controls .button, #controls button, #controls input[type='submit'],