Make file actions work from sidebar
The favorite icon in the sidebar now triggers the file action and also updates itself according to the model's state when triggered from the file row. The thumbnail triggers the default action. Currently only one FileInfoModel is used for the selection and state synchronization between views. FileList reload now auto-closes the sidebar.
This commit is contained in:
parent
997577cf7a
commit
c964eff17b
|
@ -16,34 +16,12 @@
|
|||
* Displays a block of details about the file info.
|
||||
*
|
||||
*/
|
||||
var DetailFileInfoView = function() {
|
||||
this.initialize();
|
||||
};
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
DetailFileInfoView.prototype = {
|
||||
/**
|
||||
* jQuery element
|
||||
*/
|
||||
$el: null,
|
||||
var DetailFileInfoView = OC.Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
className: 'detailFileInfoView',
|
||||
|
||||
_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
|
||||
*
|
||||
|
@ -53,31 +31,13 @@
|
|||
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.model = fileInfo;
|
||||
this.render();
|
||||
},
|
||||
|
||||
|
@ -87,9 +47,9 @@
|
|||
* @return {OCA.Files.FileInfo} file info
|
||||
*/
|
||||
getFileInfo: function() {
|
||||
return this._fileInfo;
|
||||
return this.model;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
OCA.Files.DetailFileInfoView = DetailFileInfoView;
|
||||
})();
|
||||
|
|
|
@ -33,30 +33,14 @@
|
|||
* The details view show details about a selected file.
|
||||
*
|
||||
*/
|
||||
var DetailsView = function() {
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
DetailsView.prototype = {
|
||||
|
||||
/**
|
||||
* jQuery element
|
||||
*/
|
||||
$el: null,
|
||||
var DetailsView = OC.Backbone.View.extend({
|
||||
id: 'app-sidebar',
|
||||
tabName: 'div',
|
||||
className: 'detailsView',
|
||||
|
||||
_template: null,
|
||||
_templateTabHeader: null,
|
||||
|
||||
/**
|
||||
* Currently displayed file info
|
||||
*
|
||||
* @type OCA.Files.FileInfo
|
||||
*/
|
||||
_fileInfo: null,
|
||||
|
||||
/**
|
||||
* List of detail tab views
|
||||
*
|
||||
|
@ -78,33 +62,25 @@
|
|||
*/
|
||||
_currentTabId: null,
|
||||
|
||||
events: {
|
||||
'click a.close': '_onClose',
|
||||
'click .tabHeaders .tabHeader': '_onClickTab'
|
||||
},
|
||||
|
||||
/**
|
||||
* 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();
|
||||
// this._addTestTabs();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy / uninitialize this instance.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (this.$el) {
|
||||
this.$el.remove();
|
||||
}
|
||||
_onClose: function(event) {
|
||||
OC.Apps.hideAppSidebar();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_onClickTab: function(e) {
|
||||
|
@ -148,7 +124,6 @@
|
|||
*/
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.$el.empty();
|
||||
|
||||
if (!this._template) {
|
||||
this._template = Handlebars.compile(TEMPLATE);
|
||||
|
@ -158,12 +133,13 @@
|
|||
this._templateTabHeader = Handlebars.compile(TEMPLATE_TAB_HEADER);
|
||||
}
|
||||
|
||||
var $el = $(this._template({
|
||||
this.$el.html(this._template({
|
||||
closeLabel: t('files', 'Close')
|
||||
}));
|
||||
var $tabsContainer = $el.find('.tabsContainer');
|
||||
var $tabHeadsContainer = $el.find('.tabHeaders');
|
||||
var $detailsContainer = $el.find('.detailFileInfoContainer');
|
||||
|
||||
var $tabsContainer = this.$el.find('.tabsContainer');
|
||||
var $tabHeadsContainer = this.$el.find('.tabHeaders');
|
||||
var $detailsContainer = this.$el.find('.detailFileInfoContainer');
|
||||
|
||||
// render details
|
||||
_.each(this._detailFileInfoViews, function(detailView) {
|
||||
|
@ -172,40 +148,36 @@
|
|||
|
||||
if (this._tabViews.length > 0) {
|
||||
if (!this._currentTab) {
|
||||
this._currentTab = this._tabViews[0].getId();
|
||||
this._currentTab = this._tabViews[0].id;
|
||||
}
|
||||
|
||||
// render tabs
|
||||
_.each(this._tabViews, function(tabView, i) {
|
||||
// hidden by default
|
||||
var $el = tabView.get$();
|
||||
var isCurrent = (tabView.getId() === self._currentTab);
|
||||
var isCurrent = (tabView.id === self._currentTab);
|
||||
if (!isCurrent) {
|
||||
$el.addClass('hidden');
|
||||
}
|
||||
$tabsContainer.append($el);
|
||||
|
||||
$tabHeadsContainer.append(self._templateTabHeader({
|
||||
tabId: tabView.getId(),
|
||||
tabId: tabView.id,
|
||||
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
|
||||
* @param {OCA.Files.FileInfoModel} fileInfo file info to set
|
||||
*/
|
||||
setFileInfo: function(fileInfo) {
|
||||
this._fileInfo = fileInfo;
|
||||
this.model = fileInfo;
|
||||
|
||||
this.render();
|
||||
|
||||
|
@ -221,10 +193,10 @@
|
|||
/**
|
||||
* Returns the file info.
|
||||
*
|
||||
* @return {OCA.Files.FileInfo} file info
|
||||
* @return {OCA.Files.FileInfoModel} file info
|
||||
*/
|
||||
getFileInfo: function() {
|
||||
return this._fileInfo;
|
||||
return this.model;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -244,7 +216,7 @@
|
|||
addDetailView: function(detailView) {
|
||||
this._detailFileInfoViews.push(detailView);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
OCA.Files.DetailsView = DetailsView;
|
||||
})();
|
||||
|
|
|
@ -17,23 +17,10 @@
|
|||
* Base class for tab views to display file information.
|
||||
*
|
||||
*/
|
||||
var DetailTabView = function(id) {
|
||||
this.initialize(id);
|
||||
};
|
||||
var DetailTabView = OC.Backbone.View.extend({
|
||||
tag: 'div',
|
||||
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
DetailTabView.prototype = {
|
||||
/**
|
||||
* jQuery element
|
||||
*/
|
||||
$el: null,
|
||||
|
||||
/**
|
||||
* Tab id
|
||||
*/
|
||||
_id: null,
|
||||
className: 'tab',
|
||||
|
||||
/**
|
||||
* Tab label
|
||||
|
@ -42,43 +29,11 @@
|
|||
|
||||
_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';
|
||||
initialize: function() {
|
||||
if (!this.id) {
|
||||
this.id = 'detailTabView' + DetailTabView._TAB_COUNT;
|
||||
DetailTabView._TAB_COUNT++;
|
||||
}
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -87,7 +42,7 @@
|
|||
* @return {String} label
|
||||
*/
|
||||
getLabel: function() {
|
||||
return 'Tab ' + this._id;
|
||||
return 'Tab ' + this.id;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -107,29 +62,29 @@
|
|||
render: function() {
|
||||
// to be implemented in subclass
|
||||
// FIXME: code is only for testing
|
||||
this.$el.empty();
|
||||
this.$el.append('<div>Hello ' + this._id + '</div>');
|
||||
this.$el.html('<div>Hello ' + this.id + '</div>');
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the file info to be displayed in the view
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo file info to set
|
||||
* @param {OCA.Files.FileInfoModel} fileInfo file info to set
|
||||
*/
|
||||
setFileInfo: function(fileInfo) {
|
||||
this._fileInfo = fileInfo;
|
||||
this.model = fileInfo;
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the file info.
|
||||
*
|
||||
* @return {OCA.Files.FileInfo} file info
|
||||
* @return {OCA.Files.FileInfoModel} file info
|
||||
*/
|
||||
getFileInfo: function() {
|
||||
return this._fileInfo;
|
||||
return this.model;
|
||||
}
|
||||
};
|
||||
});
|
||||
DetailTabView._TAB_COUNT = 0;
|
||||
|
||||
OCA.Files.DetailTabView = DetailTabView;
|
||||
})();
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
actions: {},
|
||||
defaults: {},
|
||||
icons: {},
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
currentFile: null,
|
||||
|
||||
/**
|
||||
|
@ -331,6 +335,9 @@
|
|||
$trigger.addClass('open');
|
||||
|
||||
menu = new OCA.Files.FileActionsMenu();
|
||||
|
||||
context.$file.find('td.filename').append(menu.$el);
|
||||
|
||||
menu.$el.on('afterHide', function() {
|
||||
context.$file.removeClass('mouseOver');
|
||||
$trigger.removeClass('open');
|
||||
|
@ -338,7 +345,6 @@
|
|||
});
|
||||
|
||||
context.$file.addClass('mouseOver');
|
||||
context.$file.find('td.filename').append(menu.$el);
|
||||
menu.show(context);
|
||||
},
|
||||
|
||||
|
@ -401,17 +407,85 @@
|
|||
// also set on global object for legacy apps
|
||||
window.FileActions.currentFile = currentFile;
|
||||
|
||||
var callContext = _.extend({}, context);
|
||||
|
||||
if (!context.dir && context.fileList) {
|
||||
callContext.dir = $file.attr('data-path') || context.fileList.getCurrentDirectory();
|
||||
}
|
||||
|
||||
if (!context.fileInfoModel && context.fileList) {
|
||||
callContext.fileInfoModel = context.fileList.getModelForFile(fileName);
|
||||
if (!callContext.fileInfoModel) {
|
||||
console.warn('No file info model found for file "' + fileName + '"');
|
||||
}
|
||||
}
|
||||
|
||||
actionSpec.action(
|
||||
fileName,
|
||||
_.extend(context, {
|
||||
dir: $file.attr('data-path') || context.fileList.getCurrentDirectory()
|
||||
})
|
||||
callContext
|
||||
);
|
||||
}
|
||||
);
|
||||
return $actionEl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger the given action on the given file.
|
||||
*
|
||||
* @param {string} actionName action name
|
||||
* @param {OCA.Files.FileInfoModel} fileInfoModel file info model
|
||||
* @param {OCA.Files.FileList} [fileList] file list, for compatibility with older action handlers [DEPRECATED]
|
||||
*
|
||||
* @return {boolean} true if the action handler was called, false otherwise
|
||||
*
|
||||
* @since 8.2
|
||||
*/
|
||||
triggerAction: function(actionName, fileInfoModel, fileList) {
|
||||
var actionFunc;
|
||||
var actions = this.get(
|
||||
fileInfoModel.get('mimetype'),
|
||||
fileInfoModel.isDirectory() ? 'dir' : 'file',
|
||||
fileInfoModel.get('permissions')
|
||||
);
|
||||
|
||||
if (actionName) {
|
||||
actionFunc = actions[actionName];
|
||||
} else {
|
||||
actionFunc = this.getDefault(
|
||||
fileInfoModel.get('mimetype'),
|
||||
fileInfoModel.isDirectory() ? 'dir' : 'file',
|
||||
fileInfoModel.get('permissions')
|
||||
);
|
||||
}
|
||||
|
||||
if (!actionFunc) {
|
||||
actionFunc = actions['Download'];
|
||||
}
|
||||
|
||||
if (!actionFunc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var context = {
|
||||
fileActions: this,
|
||||
fileInfoModel: fileInfoModel,
|
||||
dir: fileInfoModel.get('path')
|
||||
};
|
||||
|
||||
var fileName = fileInfoModel.get('name');
|
||||
this.currentFile = fileName;
|
||||
// also set on global object for legacy apps
|
||||
window.FileActions.currentFile = fileName;
|
||||
|
||||
if (fileList) {
|
||||
// compatibility with action handlers that expect these
|
||||
context.fileList = fileList;
|
||||
context.$file = fileList.findFileEl(fileName);
|
||||
}
|
||||
|
||||
actionFunc(fileName, context);
|
||||
},
|
||||
|
||||
/**
|
||||
* Display file actions for the given element
|
||||
* @param parent "td" element of the file for which to display actions
|
||||
|
@ -626,11 +700,12 @@
|
|||
* Action handler function for file actions
|
||||
*
|
||||
* @callback OCA.Files.FileActions~actionHandler
|
||||
* @param {String} fileName name of the clicked file
|
||||
* @param {String} fileName name of the file on which the action must be performed
|
||||
* @param context context
|
||||
* @param {String} context.dir directory of the file
|
||||
* @param context.$file jQuery element of the file
|
||||
* @param {OCA.Files.FileList} context.fileList the FileList instance on which the action occurred
|
||||
* @param {OCA.Files.FileInfoModel} fileInfoModel file info model
|
||||
* @param {Object} [context.$file] jQuery element of the file [DEPRECATED]
|
||||
* @param {OCA.Files.FileList} [context.fileList] the FileList instance on which the action occurred [DEPRECATED]
|
||||
* @param {OCA.Files.FileActions} context.fileActions the FileActions instance on which the action occurred
|
||||
*/
|
||||
|
||||
|
|
|
@ -213,7 +213,7 @@
|
|||
|
||||
if (_.isUndefined(options.detailsViewEnabled) || options.detailsViewEnabled) {
|
||||
this._detailsView = new OCA.Files.DetailsView();
|
||||
this._detailsView.addDetailView(new OCA.Files.MainFileInfoDetailView());
|
||||
this._detailsView.addDetailView(new OCA.Files.MainFileInfoDetailView({fileList: this, fileActions: this.fileActions}));
|
||||
this._detailsView.$el.insertBefore(this.$el);
|
||||
this._detailsView.$el.addClass('disappear');
|
||||
}
|
||||
|
@ -282,33 +282,75 @@
|
|||
this.fileActions.on('setDefault', this._onFileActionsUpdated);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a unique model for the given file name.
|
||||
*
|
||||
* @param {string|object} fileName file name or jquery row
|
||||
* @return {OCA.Files.FileInfoModel} file info model
|
||||
*/
|
||||
getModelForFile: function(fileName) {
|
||||
var $tr;
|
||||
// jQuery object ?
|
||||
if (fileName.is) {
|
||||
$tr = fileName;
|
||||
fileName = $tr.attr('data-file');
|
||||
} else {
|
||||
$tr = this.findFileEl(fileName);
|
||||
}
|
||||
|
||||
if (!$tr || !$tr.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if requesting the selected model, return it
|
||||
if (this._currentFileModel && this._currentFileModel.get('name') === fileName) {
|
||||
return this._currentFileModel;
|
||||
}
|
||||
|
||||
// TODO: note, this is a temporary model required for synchronising
|
||||
// state between different views.
|
||||
// In the future the FileList should work with Backbone.Collection
|
||||
// and contain existing models that can be used.
|
||||
// This method would in the future simply retrieve the matching model from the collection.
|
||||
var model = new OCA.Files.FileInfoModel(this.elementToFile($tr));
|
||||
if (!model.has('path')) {
|
||||
model.set('path', this.getCurrentDirectory(), {silent: true});
|
||||
}
|
||||
return model;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the details view to display the given file
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo file info to display
|
||||
* @param {string} fileName file name from the current list
|
||||
*/
|
||||
_updateDetailsView: function(fileInfo) {
|
||||
_updateDetailsView: function(fileName) {
|
||||
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');
|
||||
this.$fileList.children().filterAttr('data-id', '' + oldFileInfo.get('id')).removeClass('highlighted');
|
||||
oldFileInfo.off('change', this._onSelectedModelChanged, this);
|
||||
}
|
||||
|
||||
if (!fileInfo) {
|
||||
if (!fileName) {
|
||||
OC.Apps.hideAppSidebar();
|
||||
this._detailsView.setFileInfo(null);
|
||||
this._currentFileModel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.$fileList.children().filterAttr('data-id', '' + fileInfo.id).addClass('highlighted');
|
||||
this._detailsView.setFileInfo(_.extend({
|
||||
path: this.getCurrentDirectory()
|
||||
}, fileInfo));
|
||||
var $tr = this.findFileEl(fileName);
|
||||
var model = this.getModelForFile($tr);
|
||||
|
||||
this._currentFileModel = model;
|
||||
|
||||
$tr.addClass('highlighted');
|
||||
|
||||
this._detailsView.setFileInfo(model);
|
||||
this._detailsView.$el.scrollTop(0);
|
||||
_.defer(OC.Apps.showAppSidebar);
|
||||
},
|
||||
|
@ -367,7 +409,7 @@
|
|||
this._selectionSummary.remove(data);
|
||||
}
|
||||
if (this._selectionSummary.getTotal() === 1) {
|
||||
this._updateDetailsView(_.values(this._selectedFiles)[0]);
|
||||
this._updateDetailsView(_.values(this._selectedFiles)[0].name);
|
||||
} else {
|
||||
// show nothing when multiple files are selected
|
||||
this._updateDetailsView(null);
|
||||
|
@ -432,8 +474,7 @@
|
|||
$(event.target).closest('a').blur();
|
||||
}
|
||||
} else {
|
||||
var fileInfo = this.files[$tr.index()];
|
||||
this._updateDetailsView(fileInfo);
|
||||
this._updateDetailsView($tr.attr('data-file'));
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
@ -1188,6 +1229,8 @@
|
|||
sortdirection: this._sortDirection
|
||||
}
|
||||
});
|
||||
// close sidebar
|
||||
this._updateDetailsView(null);
|
||||
var callBack = this.reloadCallback.bind(this);
|
||||
return this._reloadCall.then(callBack, callBack);
|
||||
},
|
||||
|
@ -1587,7 +1630,7 @@
|
|||
tr.remove();
|
||||
tr = self.add(fileInfo, {updateSummary: false, silent: true});
|
||||
self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)}));
|
||||
self._updateDetailsView(fileInfo);
|
||||
self._updateDetailsView(fileInfo.name);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
(function() {
|
||||
var TEMPLATE =
|
||||
'<div class="thumbnail"></div><div title="{{name}}" class="fileName ellipsis">{{name}}</div>' +
|
||||
'<a href="#" class="thumbnail action-default"></a><div title="{{name}}" class="fileName ellipsis">{{name}}</div>' +
|
||||
'<div class="file-details ellipsis">' +
|
||||
' <a href="#" ' +
|
||||
' alt="{{starAltText}}"' +
|
||||
|
@ -27,58 +27,104 @@
|
|||
* Displays main details about a file
|
||||
*
|
||||
*/
|
||||
var MainFileInfoDetailView = function() {
|
||||
this.initialize();
|
||||
};
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
MainFileInfoDetailView.prototype = _.extend({}, OCA.Files.DetailFileInfoView.prototype,
|
||||
var MainFileInfoDetailView = OCA.Files.DetailFileInfoView.extend(
|
||||
/** @lends OCA.Files.MainFileInfoDetailView.prototype */ {
|
||||
_template: null,
|
||||
|
||||
className: 'mainFileInfoView',
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
* Associated file list instance, for file actions
|
||||
*
|
||||
* @type {OCA.Files.FileList}
|
||||
*/
|
||||
initialize: function() {
|
||||
this.$el = $('<div class="mainFileInfoView"></div>');
|
||||
_fileList: null,
|
||||
|
||||
/**
|
||||
* File actions
|
||||
*
|
||||
* @type {OCA.Files.FileActions}
|
||||
*/
|
||||
_fileActions: null,
|
||||
|
||||
events: {
|
||||
'click a.action-favorite': '_onClickFavorite',
|
||||
'click a.action-default': '_onClickDefaultAction'
|
||||
},
|
||||
|
||||
template: function(data) {
|
||||
if (!this._template) {
|
||||
this._template = Handlebars.compile(TEMPLATE);
|
||||
}
|
||||
return this._template(data);
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
this._fileList = options.fileList;
|
||||
this._fileActions = options.fileActions;
|
||||
if (!this._fileList) {
|
||||
throw 'Missing requird parameter "fileList"';
|
||||
}
|
||||
if (!this._fileActions) {
|
||||
throw 'Missing requird parameter "fileActions"';
|
||||
}
|
||||
},
|
||||
|
||||
_onClickFavorite: function(event) {
|
||||
event.preventDefault();
|
||||
this._fileActions.triggerAction('Favorite', this.model, this._fileList);
|
||||
},
|
||||
|
||||
_onClickDefaultAction: function(event) {
|
||||
event.preventDefault();
|
||||
this._fileActions.triggerAction(null, this.model, this._fileList);
|
||||
},
|
||||
|
||||
_onModelChanged: function() {
|
||||
// simply re-render
|
||||
this.render();
|
||||
},
|
||||
|
||||
setFileInfo: function(fileInfo) {
|
||||
if (this.model) {
|
||||
this.model.off('change', this._onModelChanged, this);
|
||||
}
|
||||
this.model = fileInfo;
|
||||
if (this.model) {
|
||||
this.model.on('change', this._onModelChanged, this);
|
||||
}
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* 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({
|
||||
if (this.model) {
|
||||
var isFavorite = (this.model.get('tags') || []).indexOf(OC.TAG_FAVORITE) >= 0;
|
||||
this.$el.html(this.template({
|
||||
nameLabel: t('files', 'Name'),
|
||||
name: this._fileInfo.name,
|
||||
name: this.model.get('name'),
|
||||
pathLabel: t('files', 'Path'),
|
||||
path: this._fileInfo.path,
|
||||
path: this.model.get('path'),
|
||||
sizeLabel: t('files', 'Size'),
|
||||
size: OC.Util.humanFileSize(this._fileInfo.size, true),
|
||||
altSize: n('files', '%n byte', '%n bytes', this._fileInfo.size),
|
||||
size: OC.Util.humanFileSize(this.model.get('size'), true),
|
||||
altSize: n('files', '%n byte', '%n bytes', this.model.get('size')),
|
||||
dateLabel: t('files', 'Modified'),
|
||||
altDate: OC.Util.formatDate(this._fileInfo.mtime),
|
||||
date: OC.Util.relativeModifiedDate(this._fileInfo.mtime),
|
||||
altDate: OC.Util.formatDate(this.model.get('mtime')),
|
||||
date: OC.Util.relativeModifiedDate(this.model.get('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') {
|
||||
if (!this.model.isDirectory()) {
|
||||
// TODO: inject utility class?
|
||||
FileList.lazyLoadPreview({
|
||||
path: this._fileInfo.path + '/' + this._fileInfo.name,
|
||||
mime: this._fileInfo.mimetype,
|
||||
etag: this._fileInfo.etag,
|
||||
path: this.model.getFullPath(),
|
||||
mime: this.model.get('mimetype'),
|
||||
etag: this.model.get('etag'),
|
||||
x: 50,
|
||||
y: 50,
|
||||
callback: function(previewUrl) {
|
||||
|
@ -90,7 +136,10 @@
|
|||
$iconDiv.css('background-image', 'url("' + OC.MimeType.getIconUrl('dir') + '")');
|
||||
}
|
||||
this.$el.find('[title]').tooltip({placement: 'bottom'});
|
||||
} else {
|
||||
this.$el.empty();
|
||||
}
|
||||
this.delegateEvents();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
var self = this;
|
||||
// register "star" action
|
||||
fileActions.registerAction({
|
||||
name: 'favorite',
|
||||
name: 'Favorite',
|
||||
displayName: 'Favorite',
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_READ,
|
||||
|
@ -124,6 +124,7 @@
|
|||
toggleStar($actionEl, (newTags.indexOf(OC.TAG_FAVORITE) >= 0));
|
||||
$file.attr('data-tags', newTags.join('|'));
|
||||
$file.attr('data-favorite', !isFavorite);
|
||||
context.fileInfoModel.set('tags', newTags);
|
||||
fileInfo.tags = newTags;
|
||||
});
|
||||
}
|
||||
|
@ -145,6 +146,12 @@
|
|||
$tr.find('td:first').prepend('<div class="favorite"></div>');
|
||||
return $tr;
|
||||
};
|
||||
var oldElementToFile = fileList.elementToFile;
|
||||
fileList.elementToFile = function($el) {
|
||||
var fileInfo = oldElementToFile.apply(this, arguments);
|
||||
fileInfo.tags = $el.attr('data-tags') || [];
|
||||
return fileInfo;
|
||||
};
|
||||
},
|
||||
|
||||
attach: function(fileList) {
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('OCA.Files.DetailsView tests', function() {
|
|||
detailsView = new OCA.Files.DetailsView();
|
||||
});
|
||||
afterEach(function() {
|
||||
detailsView.destroy();
|
||||
detailsView.remove();
|
||||
detailsView = undefined;
|
||||
});
|
||||
it('renders itself empty when nothing registered', function() {
|
||||
|
|
|
@ -27,6 +27,7 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
var $body = $('#testArea');
|
||||
$body.append('<input type="hidden" id="dir" value="/subdir"></input>');
|
||||
$body.append('<input type="hidden" id="permissions" value="31"></input>');
|
||||
$body.append('<table id="filestable"><tbody id="fileList"></tbody></table>');
|
||||
// dummy files table
|
||||
fileActions = new OCA.Files.FileActions();
|
||||
fileActions.registerAction({
|
||||
|
@ -152,9 +153,10 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
});
|
||||
});
|
||||
describe('action handler', function() {
|
||||
var actionStub, $tr;
|
||||
var actionStub, $tr, clock;
|
||||
|
||||
beforeEach(function() {
|
||||
clock = sinon.useFakeTimers();
|
||||
var fileData = {
|
||||
id: 18,
|
||||
type: 'file',
|
||||
|
@ -175,6 +177,12 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
});
|
||||
$tr = fileList.add(fileData);
|
||||
});
|
||||
afterEach(function() {
|
||||
OC.hideMenus();
|
||||
// jump past animations
|
||||
clock.tick(1000);
|
||||
clock.restore();
|
||||
});
|
||||
it('passes context to action handler', function() {
|
||||
$tr.find('.action-test').click();
|
||||
expect(actionStub.calledOnce).toEqual(true);
|
||||
|
@ -184,6 +192,7 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
expect(context.fileList).toBeDefined();
|
||||
expect(context.fileActions).toBeDefined();
|
||||
expect(context.dir).toEqual('/subdir');
|
||||
expect(context.fileInfoModel.get('name')).toEqual('testName.txt');
|
||||
|
||||
// when data-path is defined
|
||||
actionStub.reset();
|
||||
|
@ -192,6 +201,22 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
context = actionStub.getCall(0).args[1];
|
||||
expect(context.dir).toEqual('/somepath');
|
||||
});
|
||||
it('also triggers action handler when calling triggerAction()', function() {
|
||||
var model = new OCA.Files.FileInfoModel({
|
||||
id: 1,
|
||||
name: 'Test.txt',
|
||||
path: '/subdir',
|
||||
mime: 'text/plain',
|
||||
permissions: 31
|
||||
});
|
||||
fileActions.triggerAction('Test', model, fileList);
|
||||
|
||||
expect(actionStub.calledOnce).toEqual(true);
|
||||
expect(actionStub.getCall(0).args[0]).toEqual('Test.txt');
|
||||
expect(actionStub.getCall(0).args[1].fileList).toEqual(fileList);
|
||||
expect(actionStub.getCall(0).args[1].fileActions).toEqual(fileActions);
|
||||
expect(actionStub.getCall(0).args[1].fileInfoModel).toEqual(model);
|
||||
});
|
||||
describe('actions menu', function() {
|
||||
it('shows actions menu inside row when clicking the menu trigger', function() {
|
||||
expect($tr.find('td.filename .fileActionsMenu').length).toEqual(0);
|
||||
|
@ -203,12 +228,13 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
expect($tr.hasClass('mouseOver')).toEqual(true);
|
||||
});
|
||||
it('cleans up after hiding', function() {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var slideUpStub = sinon.stub($.fn, 'slideUp');
|
||||
$tr.find('.action-menu').click();
|
||||
expect($tr.find('.fileActionsMenu').length).toEqual(1);
|
||||
OC.hideMenus();
|
||||
// sliding animation
|
||||
clock.tick(500);
|
||||
expect(slideUpStub.calledOnce).toEqual(true);
|
||||
slideUpStub.getCall(0).args[1]();
|
||||
expect($tr.hasClass('mouseOver')).toEqual(false);
|
||||
expect($tr.find('.fileActionsMenu').length).toEqual(0);
|
||||
});
|
||||
|
|
|
@ -1912,6 +1912,20 @@ describe('OCA.Files.FileList tests', function() {
|
|||
expect($tr.hasClass('highlighted')).toEqual(false);
|
||||
expect(fileList._detailsView.getFileInfo()).toEqual(null);
|
||||
});
|
||||
it('returns the currently selected model instance when calling getModelForFile', function() {
|
||||
var $tr = fileList.findFileEl('One.txt');
|
||||
$tr.find('td.filename>a.name').click();
|
||||
|
||||
var model1 = fileList.getModelForFile('One.txt');
|
||||
var model2 = fileList.getModelForFile('One.txt');
|
||||
model1.set('test', true);
|
||||
|
||||
// it's the same model
|
||||
expect(model2).toEqual(model1);
|
||||
|
||||
var model3 = fileList.getModelForFile($tr);
|
||||
expect(model3).toEqual(model1);
|
||||
});
|
||||
});
|
||||
describe('File actions', function() {
|
||||
it('Clicking on a file name will trigger default action', function() {
|
||||
|
|
|
@ -20,32 +20,37 @@
|
|||
*/
|
||||
|
||||
describe('OCA.Files.MainFileInfoDetailView tests', function() {
|
||||
var view, tooltipStub, previewStub, fncLazyLoadPreview, fileListMock;
|
||||
var view, tooltipStub, fileListMock, fileActions, fileList, testFileInfo;
|
||||
|
||||
beforeEach(function() {
|
||||
tooltipStub = sinon.stub($.fn, 'tooltip');
|
||||
fileListMock = sinon.mock(OCA.Files.FileList.prototype);
|
||||
view = new OCA.Files.MainFileInfoDetailView();
|
||||
fileActions = new OCA.Files.FileActions();
|
||||
fileList = new OCA.Files.FileList($('<table></table>'), {
|
||||
fileActions: fileActions
|
||||
});
|
||||
view = new OCA.Files.MainFileInfoDetailView({
|
||||
fileList: fileList,
|
||||
fileActions: fileActions
|
||||
});
|
||||
testFileInfo = new OCA.Files.FileInfoModel({
|
||||
id: 5,
|
||||
name: 'One.txt',
|
||||
mimetype: 'text/plain',
|
||||
permissions: 31,
|
||||
path: '/subdir',
|
||||
size: 123456789,
|
||||
mtime: Date.UTC(2015, 6, 17, 1, 2, 0, 0)
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
view.destroy();
|
||||
view.remove();
|
||||
view = undefined;
|
||||
tooltipStub.restore();
|
||||
fileListMock.restore();
|
||||
|
||||
});
|
||||
describe('rendering', function() {
|
||||
var testFileInfo;
|
||||
beforeEach(function() {
|
||||
view = new OCA.Files.MainFileInfoDetailView();
|
||||
testFileInfo = {
|
||||
id: 5,
|
||||
name: 'One.txt',
|
||||
path: '/subdir',
|
||||
size: 123456789,
|
||||
mtime: Date.UTC(2015, 6, 17, 1, 2, 0, 0)
|
||||
};
|
||||
});
|
||||
it('displays basic info', function() {
|
||||
var clock = sinon.useFakeTimers(Date.UTC(2015, 6, 17, 1, 2, 0, 3));
|
||||
var dateExpected = OC.Util.formatDate(Date(Date.UTC(2015, 6, 17, 1, 2, 0, 0)));
|
||||
|
@ -59,39 +64,34 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() {
|
|||
clock.restore();
|
||||
});
|
||||
it('displays favorite icon', function() {
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
tags: [OC.TAG_FAVORITE]
|
||||
}));
|
||||
testFileInfo.set('tags', [OC.TAG_FAVORITE]);
|
||||
view.setFileInfo(testFileInfo);
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/starred'));
|
||||
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
tags: []
|
||||
}));
|
||||
testFileInfo.set('tags', []);
|
||||
view.setFileInfo(testFileInfo);
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/star'));
|
||||
});
|
||||
it('displays mime icon', function() {
|
||||
// File
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'text/calendar'
|
||||
}));
|
||||
testFileInfo.set('mimetype', 'text/calendar');
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
expect(view.$el.find('.thumbnail').css('background-image'))
|
||||
.toContain('filetypes/text-calendar.svg');
|
||||
|
||||
// Folder
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'httpd/unix-directory'
|
||||
}));
|
||||
testFileInfo.set('mimetype', 'httpd/unix-directory');
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
expect(view.$el.find('.thumbnail').css('background-image'))
|
||||
.toContain('filetypes/folder.svg');
|
||||
});
|
||||
it('displays thumbnail', function() {
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'text/plain'
|
||||
}));
|
||||
testFileInfo.set('mimetype', 'test/plain');
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
var expectation = fileListMock.expects('lazyLoadPreview');
|
||||
expectation.once();
|
||||
|
@ -100,5 +100,76 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() {
|
|||
|
||||
fileListMock.verify();
|
||||
});
|
||||
it('rerenders when changes are made on the model', function() {
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
testFileInfo.set('tags', [OC.TAG_FAVORITE]);
|
||||
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/starred'));
|
||||
|
||||
testFileInfo.set('tags', []);
|
||||
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/star'));
|
||||
});
|
||||
it('unbinds change listener from model', function() {
|
||||
view.setFileInfo(testFileInfo);
|
||||
view.setFileInfo(new OCA.Files.FileInfoModel({
|
||||
id: 999,
|
||||
name: 'test.txt',
|
||||
path: '/'
|
||||
}));
|
||||
|
||||
// set value on old model
|
||||
testFileInfo.set('tags', [OC.TAG_FAVORITE]);
|
||||
|
||||
// no change
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/star'));
|
||||
});
|
||||
});
|
||||
describe('events', function() {
|
||||
it('triggers default action when clicking on the thumbnail', function() {
|
||||
var actionHandler = sinon.stub();
|
||||
|
||||
fileActions.registerAction({
|
||||
name: 'Something',
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_READ,
|
||||
actionHandler: actionHandler
|
||||
});
|
||||
fileActions.setDefault('text/plain', 'Something');
|
||||
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
view.$el.find('.thumbnail').click();
|
||||
|
||||
expect(actionHandler.calledOnce).toEqual(true);
|
||||
expect(actionHandler.getCall(0).args[0]).toEqual('One.txt');
|
||||
expect(actionHandler.getCall(0).args[1].fileList).toEqual(fileList);
|
||||
expect(actionHandler.getCall(0).args[1].fileActions).toEqual(fileActions);
|
||||
expect(actionHandler.getCall(0).args[1].fileInfoModel).toEqual(testFileInfo);
|
||||
});
|
||||
it('triggers "Favorite" action when clicking on the star', function() {
|
||||
var actionHandler = sinon.stub();
|
||||
|
||||
fileActions.registerAction({
|
||||
name: 'Favorite',
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_READ,
|
||||
actionHandler: actionHandler
|
||||
});
|
||||
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
view.$el.find('.action-favorite').click();
|
||||
|
||||
expect(actionHandler.calledOnce).toEqual(true);
|
||||
expect(actionHandler.getCall(0).args[0]).toEqual('One.txt');
|
||||
expect(actionHandler.getCall(0).args[1].fileList).toEqual(fileList);
|
||||
expect(actionHandler.getCall(0).args[1].fileActions).toEqual(fileActions);
|
||||
expect(actionHandler.getCall(0).args[1].fileInfoModel).toEqual(testFileInfo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,30 +12,15 @@
|
|||
var TEMPLATE =
|
||||
'<div>Owner: {{owner}}';
|
||||
|
||||
/**
|
||||
* @class OCA.Sharing.ShareTabView
|
||||
* @classdesc
|
||||
*
|
||||
* Displays sharing information
|
||||
*
|
||||
*/
|
||||
var ShareTabView = function(id) {
|
||||
this.initialize(id);
|
||||
};
|
||||
/**
|
||||
* @memberof OCA.Sharing
|
||||
*/
|
||||
ShareTabView.prototype = _.extend({}, OCA.Files.DetailTabView.prototype,
|
||||
var ShareTabView = OCA.Files.DetailTabView.extend(
|
||||
/** @lends OCA.Sharing.ShareTabView.prototype */ {
|
||||
_template: null,
|
||||
id: 'shareTabView',
|
||||
className: 'tab shareTabView',
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
*/
|
||||
initialize: function() {
|
||||
OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments);
|
||||
this.$el.addClass('shareTabView');
|
||||
},
|
||||
_template: null,
|
||||
|
||||
getLabel: function() {
|
||||
return t('files_sharing', 'Sharing');
|
||||
|
@ -51,9 +36,9 @@
|
|||
this._template = Handlebars.compile(TEMPLATE);
|
||||
}
|
||||
|
||||
if (this._fileInfo) {
|
||||
if (this.model) {
|
||||
this.$el.append(this._template({
|
||||
owner: this._fileInfo.shareOwner || OC.currentUser
|
||||
owner: this.model.get('shareOwner') || OC.currentUser
|
||||
}));
|
||||
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue