diff options
author | Vincent Petry <pvince81@owncloud.com> | 2015-08-10 13:14:15 +0200 |
---|---|---|
committer | Vincent Petry <pvince81@owncloud.com> | 2015-08-10 13:14:15 +0200 |
commit | 15e16d335db5771778477e944d4e63ac807382b9 (patch) | |
tree | 597e7ea2f7adf12f257ccc78c04f90c06958aba3 /apps/files/js | |
parent | 214729a5524e2c406415985717c174bedc810954 (diff) | |
parent | 038d29b8def77ad906a722f72a1501b369f9c1ee (diff) | |
download | nextcloud-server-15e16d335db5771778477e944d4e63ac807382b9.tar.gz nextcloud-server-15e16d335db5771778477e944d4e63ac807382b9.zip |
Merge pull request #17656 from owncloud/files-rightsidebar
Basic work for right sidebar
Diffstat (limited to 'apps/files/js')
-rw-r--r-- | apps/files/js/detailfileinfoview.js | 96 | ||||
-rw-r--r-- | apps/files/js/detailsview.js | 251 | ||||
-rw-r--r-- | apps/files/js/detailtabview.js | 136 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 127 | ||||
-rw-r--r-- | apps/files/js/mainfileinfodetailview.js | 98 |
5 files changed, 687 insertions, 21 deletions
diff --git a/apps/files/js/detailfileinfoview.js b/apps/files/js/detailfileinfoview.js new file mode 100644 index 00000000000..9a88b5e2d8a --- /dev/null +++ b/apps/files/js/detailfileinfoview.js @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + /** + * @class OCA.Files.DetailFileInfoView + * @classdesc + * + * Displays a block of details about the file info. + * + */ + var DetailFileInfoView = function() { + this.initialize(); + }; + /** + * @memberof OCA.Files + */ + DetailFileInfoView.prototype = { + /** + * jQuery element + */ + $el: null, + + _template: null, + + /** + * Currently displayed file info + * + * @type OCA.Files.FileInfo + */ + _fileInfo: null, + + /** + * Initialize the details view + */ + initialize: function() { + this.$el = $('<div class="detailFileInfoView"></div>'); + }, + + /** + * returns the jQuery object for HTML output + * + * @returns {jQuery} + */ + get$: function() { + return this.$el; + }, + + /** + * Destroy / uninitialize this instance. + */ + destroy: function() { + if (this.$el) { + this.$el.remove(); + } + }, + + /** + * Renders this details view + * + * @abstract + */ + render: function() { + // to be implemented in subclass + }, + + /** + * Sets the file info to be displayed in the view + * + * @param {OCA.Files.FileInfo} fileInfo file info to set + */ + setFileInfo: function(fileInfo) { + this._fileInfo = fileInfo; + this.render(); + }, + + /** + * Returns the file info. + * + * @return {OCA.Files.FileInfo} file info + */ + getFileInfo: function() { + return this._fileInfo; + } + }; + + OCA.Files.DetailFileInfoView = DetailFileInfoView; +})(); + diff --git a/apps/files/js/detailsview.js b/apps/files/js/detailsview.js new file mode 100644 index 00000000000..7b7bd013f9e --- /dev/null +++ b/apps/files/js/detailsview.js @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + + var TEMPLATE = + '<div>' + + ' <div class="detailFileInfoContainer">' + + ' </div>' + + ' <div>' + + ' <ul class="tabHeaders">' + + ' </ul>' + + ' <div class="tabsContainer">' + + ' </div>' + + ' </div>' + + ' <a class="close icon-close" href="#" alt="{{closeLabel}}"></a>' + + '</div>'; + + var TEMPLATE_TAB_HEADER = + '<li class="tabHeader {{#if selected}}selected{{/if}}" data-tabid="{{tabId}}" data-tabindex="{{tabIndex}}"><a href="#">{{label}}</a></li>'; + + /** + * @class OCA.Files.DetailsView + * @classdesc + * + * The details view show details about a selected file. + * + */ + var DetailsView = function() { + this.initialize(); + }; + + /** + * @memberof OCA.Files + */ + DetailsView.prototype = { + + /** + * jQuery element + */ + $el: null, + + _template: null, + _templateTabHeader: null, + + /** + * Currently displayed file info + * + * @type OCA.Files.FileInfo + */ + _fileInfo: null, + + /** + * List of detail tab views + * + * @type Array<OCA.Files.DetailTabView> + */ + _tabViews: [], + + /** + * List of detail file info views + * + * @type Array<OCA.Files.DetailFileInfoView> + */ + _detailFileInfoViews: [], + + /** + * Id of the currently selected tab + * + * @type string + */ + _currentTabId: null, + + /** + * Initialize the details view + */ + initialize: function() { + this.$el = $('<div id="app-sidebar"></div>'); + this.fileInfo = null; + this._tabViews = []; + this._detailFileInfoViews = []; + + this.$el.on('click', 'a.close', function(event) { + OC.Apps.hideAppSidebar(); + event.preventDefault(); + }); + + this.$el.on('click', '.tabHeaders .tabHeader', _.bind(this._onClickTab, this)); + + // uncomment to add some dummy tabs for testing + //this._addTestTabs(); + }, + + /** + * Destroy / uninitialize this instance. + */ + destroy: function() { + if (this.$el) { + this.$el.remove(); + } + }, + + _onClickTab: function(e) { + var $target = $(e.target); + if (!$target.hasClass('tabHeader')) { + $target = $target.closest('.tabHeader'); + } + var tabIndex = $target.attr('data-tabindex'); + var targetTab; + if (_.isUndefined(tabIndex)) { + return; + } + + this.$el.find('.tabsContainer .tab').addClass('hidden'); + targetTab = this._tabViews[tabIndex]; + targetTab.$el.removeClass('hidden'); + + this.$el.find('.tabHeaders li').removeClass('selected'); + $target.addClass('selected'); + + e.preventDefault(); + }, + + _addTestTabs: function() { + for (var j = 0; j < 2; j++) { + var testView = new OCA.Files.DetailTabView('testtab' + j); + testView.index = j; + testView.getLabel = function() { return 'Test tab ' + this.index; }; + testView.render = function() { + this.$el.empty(); + for (var i = 0; i < 100; i++) { + this.$el.append('<div>Test tab ' + this.index + ' row ' + i + '</div>'); + } + }; + this._tabViews.push(testView); + } + }, + + /** + * Renders this details view + */ + render: function() { + var self = this; + this.$el.empty(); + + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + + if (!this._templateTabHeader) { + this._templateTabHeader = Handlebars.compile(TEMPLATE_TAB_HEADER); + } + + var $el = $(this._template({ + closeLabel: t('files', 'Close') + })); + var $tabsContainer = $el.find('.tabsContainer'); + var $tabHeadsContainer = $el.find('.tabHeaders'); + var $detailsContainer = $el.find('.detailFileInfoContainer'); + + // render details + _.each(this._detailFileInfoViews, function(detailView) { + $detailsContainer.append(detailView.get$()); + }); + + if (this._tabViews.length > 0) { + if (!this._currentTab) { + this._currentTab = this._tabViews[0].getId(); + } + + // render tabs + _.each(this._tabViews, function(tabView, i) { + // hidden by default + var $el = tabView.get$(); + var isCurrent = (tabView.getId() === self._currentTab); + if (!isCurrent) { + $el.addClass('hidden'); + } + $tabsContainer.append($el); + + $tabHeadsContainer.append(self._templateTabHeader({ + tabId: tabView.getId(), + tabIndex: i, + label: tabView.getLabel(), + selected: isCurrent + })); + }); + } + + // TODO: select current tab + + this.$el.append($el); + }, + + /** + * Sets the file info to be displayed in the view + * + * @param {OCA.Files.FileInfo} fileInfo file info to set + */ + setFileInfo: function(fileInfo) { + this._fileInfo = fileInfo; + + this.render(); + + // notify all panels + _.each(this._tabViews, function(tabView) { + tabView.setFileInfo(fileInfo); + }); + _.each(this._detailFileInfoViews, function(detailView) { + detailView.setFileInfo(fileInfo); + }); + }, + + /** + * Returns the file info. + * + * @return {OCA.Files.FileInfo} file info + */ + getFileInfo: function() { + return this._fileInfo; + }, + + /** + * Adds a tab in the tab view + * + * @param {OCA.Files.DetailTabView} tab view + */ + addTabView: function(tabView) { + this._tabViews.push(tabView); + }, + + /** + * Adds a detail view for file info. + * + * @param {OCA.Files.DetailFileInfoView} detail view + */ + addDetailView: function(detailView) { + this._detailFileInfoViews.push(detailView); + } + }; + + OCA.Files.DetailsView = DetailsView; +})(); + diff --git a/apps/files/js/detailtabview.js b/apps/files/js/detailtabview.js new file mode 100644 index 00000000000..b9b1dda2ccc --- /dev/null +++ b/apps/files/js/detailtabview.js @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + + /** + * @class OCA.Files.DetailTabView + * @classdesc + * + * Base class for tab views to display file information. + * + */ + var DetailTabView = function(id) { + this.initialize(id); + }; + + /** + * @memberof OCA.Files + */ + DetailTabView.prototype = { + /** + * jQuery element + */ + $el: null, + + /** + * Tab id + */ + _id: null, + + /** + * Tab label + */ + _label: null, + + _template: null, + + /** + * Currently displayed file info + * + * @type OCA.Files.FileInfo + */ + _fileInfo: null, + + /** + * Initialize the details view + * + * @param {string} id tab id + */ + initialize: function(id) { + if (!id) { + throw 'Argument "id" is required'; + } + this._id = id; + this.$el = $('<div class="tab"></div>'); + this.$el.attr('data-tabid', id); + }, + + /** + * Destroy / uninitialize this instance. + */ + destroy: function() { + if (this.$el) { + this.$el.remove(); + } + }, + + /** + * Returns the tab element id + * + * @return {string} tab id + */ + getId: function() { + return this._id; + }, + + /** + * Returns the tab label + * + * @return {String} label + */ + getLabel: function() { + return 'Tab ' + this._id; + }, + + /** + * returns the jQuery object for HTML output + * + * @returns {jQuery} + */ + get$: function() { + return this.$el; + }, + + /** + * Renders this details view + * + * @abstract + */ + render: function() { + // to be implemented in subclass + // FIXME: code is only for testing + this.$el.empty(); + this.$el.append('<div>Hello ' + this._id + '</div>'); + }, + + /** + * Sets the file info to be displayed in the view + * + * @param {OCA.Files.FileInfo} fileInfo file info to set + */ + setFileInfo: function(fileInfo) { + this._fileInfo = fileInfo; + this.render(); + }, + + /** + * Returns the file info. + * + * @return {OCA.Files.FileInfo} file info + */ + getFileInfo: function() { + return this._fileInfo; + } + }; + + OCA.Files.DetailTabView = DetailTabView; +})(); + diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index c56c786929a..f5629ecd2c3 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -23,6 +23,7 @@ * @param [options.scrollContainer] scrollable container, defaults to $(window) * @param [options.dragOptions] drag options, disabled by default * @param [options.folderDropOptions] folder drop options, disabled by default + * @param [options.detailsViewEnabled=true] whether to enable details view */ var FileList = function($el, options) { this.initialize($el, options); @@ -65,6 +66,11 @@ fileSummary: null, /** + * @type OCA.Files.DetailsView + */ + _detailsView: null, + + /** * Whether the file list was initialized already. * @type boolean */ @@ -205,6 +211,13 @@ } this.breadcrumb = new OCA.Files.BreadCrumb(breadcrumbOptions); + if (_.isUndefined(options.detailsViewEnabled) || options.detailsViewEnabled) { + this._detailsView = new OCA.Files.DetailsView(); + this._detailsView.addDetailView(new OCA.Files.MainFileInfoDetailView()); + this._detailsView.$el.insertBefore(this.$el); + this._detailsView.$el.addClass('disappear'); + } + this.$el.find('#controls').prepend(this.breadcrumb.$el); this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); @@ -216,6 +229,13 @@ 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)); @@ -263,6 +283,37 @@ }, /** + * Update the details view to display the given file + * + * @param {OCA.Files.FileInfo} fileInfo file info to display + */ + _updateDetailsView: function(fileInfo) { + if (!this._detailsView) { + return; + } + + var self = this; + var oldFileInfo = this._detailsView.getFileInfo(); + if (oldFileInfo) { + // TODO: use more efficient way, maybe track the highlight + this.$fileList.children().filterAttr('data-id', '' + oldFileInfo.id).removeClass('highlighted'); + } + + if (!fileInfo) { + OC.Apps.hideAppSidebar(); + this._detailsView.setFileInfo(null); + return; + } + + this.$fileList.children().filterAttr('data-id', '' + fileInfo.id).addClass('highlighted'); + this._detailsView.setFileInfo(_.extend({ + path: this.getCurrentDirectory() + }, fileInfo)); + this._detailsView.$el.scrollTop(0); + _.defer(OC.Apps.showAppSidebar); + }, + + /** * Event handler for when the window size changed */ _onResize: function() { @@ -315,6 +366,12 @@ delete this._selectedFiles[$tr.data('id')]; this._selectionSummary.remove(data); } + if (this._selectionSummary.getTotal() === 1) { + this._updateDetailsView(_.values(this._selectedFiles)[0]); + } else { + // show nothing when multiple files are selected + this._updateDetailsView(null); + } this.$el.find('.select-all').prop('checked', this._selectionSummary.getTotal() === this.files.length); }, @@ -350,27 +407,34 @@ this._selectFileEl($tr, !$checkbox.prop('checked')); this.updateSelectionSummary(); } else { - var filename = $tr.attr('data-file'); - var renaming = $tr.data('renaming'); - if (!renaming) { - this.fileActions.currentFile = $tr.find('td'); - var mime = this.fileActions.getCurrentMimeType(); - var type = this.fileActions.getCurrentType(); - var permissions = this.fileActions.getCurrentPermissions(); - var action = this.fileActions.getDefault(mime,type, permissions); - if (action) { - event.preventDefault(); - // also set on global object for legacy apps - window.FileActions.currentFile = this.fileActions.currentFile; - action(filename, { - $file: $tr, - fileList: this, - fileActions: this.fileActions, - dir: $tr.attr('data-path') || this.getCurrentDirectory() - }); + // clicked directly on the name + if (!this._detailsView || $(event.target).is('.nametext') || $(event.target).closest('.nametext').length) { + var filename = $tr.attr('data-file'); + var renaming = $tr.data('renaming'); + if (!renaming) { + this.fileActions.currentFile = $tr.find('td'); + var mime = this.fileActions.getCurrentMimeType(); + var type = this.fileActions.getCurrentType(); + var permissions = this.fileActions.getCurrentPermissions(); + var action = this.fileActions.getDefault(mime,type, permissions); + if (action) { + event.preventDefault(); + // also set on global object for legacy apps + window.FileActions.currentFile = this.fileActions.currentFile; + action(filename, { + $file: $tr, + fileList: this, + fileActions: this.fileActions, + dir: $tr.attr('data-path') || this.getCurrentDirectory() + }); + } + // deselect row + $(event.target).closest('a').blur(); } - // deselect row - $(event.target).closest('a').blur(); + } else { + var fileInfo = this.files[$tr.index()]; + this._updateDetailsView(fileInfo); + event.preventDefault(); } } }, @@ -825,7 +889,7 @@ var formatted; var text; if (mtime > 0) { - formatted = formatDate(mtime); + formatted = OC.Util.formatDate(mtime); text = OC.Util.relativeModifiedDate(mtime); } else { formatted = t('files', 'Unable to determine date'); @@ -1239,6 +1303,12 @@ ready(iconURL); // set mimeicon URL urlSpec.file = OCA.Files.Files.fixPath(path); + if (options.x) { + urlSpec.x = options.x; + } + if (options.y) { + urlSpec.y = options.y; + } if (etag){ // use etag as cache buster @@ -1521,6 +1591,7 @@ tr.remove(); tr = self.add(fileInfo, {updateSummary: false, silent: true}); self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)})); + self._updateDetailsView(fileInfo); } }); } else { @@ -2177,6 +2248,20 @@ } }); + }, + + /** + * Register a tab view to be added to all views + */ + registerTabView: function(tabView) { + this._detailsView.addTabView(tabView); + }, + + /** + * Register a detail view to be added to all views + */ + registerDetailView: function(detailView) { + this._detailsView.addDetailView(detailView); } }; diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js new file mode 100644 index 00000000000..a00d907d0d6 --- /dev/null +++ b/apps/files/js/mainfileinfodetailview.js @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + var TEMPLATE = + '<div class="thumbnail"></div><div title="{{name}}" class="fileName ellipsis">{{name}}</div>' + + '<div class="file-details ellipsis">' + + ' <a href="#" ' + + ' alt="{{starAltText}}"' + + ' class="action action-favorite favorite">' + + ' <img class="svg" src="{{starIcon}}" />' + + ' </a>' + + ' <span class="size" title="{{altSize}}">{{size}}</span>, <span class="date" title="{{altDate}}">{{date}}</span>' + + '</div>'; + + /** + * @class OCA.Files.MainFileInfoDetailView + * @classdesc + * + * Displays main details about a file + * + */ + var MainFileInfoDetailView = function() { + this.initialize(); + }; + /** + * @memberof OCA.Files + */ + MainFileInfoDetailView.prototype = _.extend({}, OCA.Files.DetailFileInfoView.prototype, + /** @lends OCA.Files.MainFileInfoDetailView.prototype */ { + _template: null, + + /** + * Initialize the details view + */ + initialize: function() { + this.$el = $('<div class="mainFileInfoView"></div>'); + }, + + /** + * Renders this details view + */ + render: function() { + this.$el.empty(); + + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + + if (this._fileInfo) { + var isFavorite = (this._fileInfo.tags || []).indexOf(OC.TAG_FAVORITE) >= 0; + this.$el.append(this._template({ + nameLabel: t('files', 'Name'), + name: this._fileInfo.name, + pathLabel: t('files', 'Path'), + path: this._fileInfo.path, + sizeLabel: t('files', 'Size'), + size: OC.Util.humanFileSize(this._fileInfo.size, true), + altSize: n('files', '%n byte', '%n bytes', this._fileInfo.size), + dateLabel: t('files', 'Modified'), + altDate: OC.Util.formatDate(this._fileInfo.mtime), + date: OC.Util.relativeModifiedDate(this._fileInfo.mtime), + starAltText: isFavorite ? t('files', 'Favorited') : t('files', 'Favorite'), + starIcon: OC.imagePath('core', isFavorite ? 'actions/starred' : 'actions/star') + })); + + // TODO: we really need OC.Previews + var $iconDiv = this.$el.find('.thumbnail'); + if (this._fileInfo.mimetype !== 'httpd/unix-directory') { + // TODO: inject utility class? + FileList.lazyLoadPreview({ + path: this._fileInfo.path + '/' + this._fileInfo.name, + mime: this._fileInfo.mimetype, + etag: this._fileInfo.etag, + x: 50, + y: 50, + callback: function(previewUrl) { + $iconDiv.css('background-image', 'url("' + previewUrl + '")'); + } + }); + } else { + // TODO: special icons / shared / external + $iconDiv.css('background-image', 'url("' + OC.MimeType.getIconUrl('dir') + '")'); + } + this.$el.find('[title]').tooltip({placement: 'bottom'}); + } + } + }); + + OCA.Files.MainFileInfoDetailView = MainFileInfoDetailView; +})(); |