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:
Vincent Petry 2015-08-12 17:30:20 +02:00
parent 997577cf7a
commit c964eff17b
12 changed files with 424 additions and 267 deletions

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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
*/

View File

@ -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 {

View File

@ -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();
}
});

View File

@ -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) {

View File

@ -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() {

View File

@ -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);
});

View File

@ -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() {

View File

@ -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);
});
});
});

View File

@ -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 {