mirror of
https://github.com/nextcloud/server.git
synced 2026-06-06 15:23:17 -04:00
Merge pull request #17656 from owncloud/files-rightsidebar
Basic work for right sidebar
This commit is contained in:
commit
15e16d335d
24 changed files with 1194 additions and 27 deletions
55
apps/files/css/detailsView.css
Normal file
55
apps/files/css/detailsView.css
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#app-sidebar .detailFileInfoContainer {
|
||||
min-height: 50px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#app-sidebar .detailFileInfoContainer > div {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#app-sidebar .mainFileInfoView {
|
||||
margin-right: 20px; /* accomodate for close icon */
|
||||
}
|
||||
|
||||
#app-sidebar .thumbnail {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
background-size: 50px;
|
||||
}
|
||||
|
||||
#app-sidebar .ellipsis {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-sidebar .fileName {
|
||||
font-size: 16px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
#app-sidebar .file-details {
|
||||
margin-top: 3px;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
opacity: .5;
|
||||
}
|
||||
#app-sidebar .action-favorite {
|
||||
vertical-align: text-bottom;
|
||||
padding: 10px;
|
||||
margin: -10px;
|
||||
}
|
||||
|
||||
#app-sidebar .detailList {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#app-sidebar .close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 15px;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
opacity: .5;
|
||||
}
|
||||
|
|
@ -103,6 +103,10 @@
|
|||
min-height: 100%;
|
||||
}
|
||||
|
||||
.app-files #app-content {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* icons for sidebar */
|
||||
.nav-icon-files {
|
||||
background-image: url('../img/folder.svg');
|
||||
|
|
@ -143,6 +147,7 @@
|
|||
#filestable tbody tr:active {
|
||||
background-color: rgb(240,240,240);
|
||||
}
|
||||
#filestable tbody tr.highlighted,
|
||||
#filestable tbody tr.selected {
|
||||
background-color: rgb(230,230,230);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ OCP\Util::addscript('files', 'search');
|
|||
\OCP\Util::addScript('files', 'tagsplugin');
|
||||
\OCP\Util::addScript('files', 'favoritesplugin');
|
||||
|
||||
\OCP\Util::addScript('files', 'detailfileinfoview');
|
||||
\OCP\Util::addScript('files', 'detailtabview');
|
||||
\OCP\Util::addScript('files', 'mainfileinfodetailview');
|
||||
\OCP\Util::addScript('files', 'detailsview');
|
||||
\OCP\Util::addStyle('files', 'detailsView');
|
||||
|
||||
\OC_Util::addVendorScript('core', 'handlebars/handlebars');
|
||||
|
||||
OCP\App::setActiveNavigationEntry('files_index');
|
||||
|
|
|
|||
96
apps/files/js/detailfileinfoview.js
Normal file
96
apps/files/js/detailfileinfoview.js
Normal file
|
|
@ -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;
|
||||
})();
|
||||
|
||||
251
apps/files/js/detailsview.js
Normal file
251
apps/files/js/detailsview.js
Normal file
|
|
@ -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;
|
||||
})();
|
||||
|
||||
136
apps/files/js/detailtabview.js
Normal file
136
apps/files/js/detailtabview.js
Normal file
|
|
@ -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;
|
||||
})();
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
@ -64,6 +65,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));
|
||||
|
|
@ -262,6 +282,37 @@
|
|||
this.fileActions.on('setDefault', this._onFileActionsUpdated);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
98
apps/files/js/mainfileinfodetailview.js
Normal file
98
apps/files/js/mainfileinfodetailview.js
Normal file
|
|
@ -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;
|
||||
})();
|
||||
105
apps/files/tests/js/detailsviewSpec.js
Normal file
105
apps/files/tests/js/detailsviewSpec.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Vincent Petry
|
||||
* @copyright 2015 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('OCA.Files.DetailsView tests', function() {
|
||||
var detailsView;
|
||||
|
||||
beforeEach(function() {
|
||||
detailsView = new OCA.Files.DetailsView();
|
||||
});
|
||||
afterEach(function() {
|
||||
detailsView.destroy();
|
||||
detailsView = undefined;
|
||||
});
|
||||
it('renders itself empty when nothing registered', function() {
|
||||
detailsView.render();
|
||||
expect(detailsView.$el.find('.detailFileInfoContainer').length).toEqual(1);
|
||||
expect(detailsView.$el.find('.tabsContainer').length).toEqual(1);
|
||||
});
|
||||
describe('file info detail view', function() {
|
||||
it('renders registered view', function() {
|
||||
var testView = new OCA.Files.DetailFileInfoView();
|
||||
var testView2 = new OCA.Files.DetailFileInfoView();
|
||||
detailsView.addDetailView(testView);
|
||||
detailsView.addDetailView(testView2);
|
||||
detailsView.render();
|
||||
|
||||
expect(detailsView.$el.find('.detailFileInfoContainer .detailFileInfoView').length).toEqual(2);
|
||||
});
|
||||
it('updates registered tabs when fileinfo is updated', function() {
|
||||
var viewRenderStub = sinon.stub(OCA.Files.DetailFileInfoView.prototype, 'render');
|
||||
var testView = new OCA.Files.DetailFileInfoView();
|
||||
var testView2 = new OCA.Files.DetailFileInfoView();
|
||||
detailsView.addDetailView(testView);
|
||||
detailsView.addDetailView(testView2);
|
||||
detailsView.render();
|
||||
|
||||
var fileInfo = {id: 5, name: 'test.txt'};
|
||||
viewRenderStub.reset();
|
||||
detailsView.setFileInfo(fileInfo);
|
||||
|
||||
expect(testView.getFileInfo()).toEqual(fileInfo);
|
||||
expect(testView2.getFileInfo()).toEqual(fileInfo);
|
||||
|
||||
expect(viewRenderStub.callCount).toEqual(2);
|
||||
viewRenderStub.restore();
|
||||
});
|
||||
});
|
||||
describe('tabs', function() {
|
||||
var testView, testView2;
|
||||
|
||||
beforeEach(function() {
|
||||
testView = new OCA.Files.DetailTabView('test1');
|
||||
testView2 = new OCA.Files.DetailTabView('test2');
|
||||
detailsView.addTabView(testView);
|
||||
detailsView.addTabView(testView2);
|
||||
detailsView.render();
|
||||
});
|
||||
it('renders registered tabs', function() {
|
||||
expect(detailsView.$el.find('.tab').length).toEqual(2);
|
||||
});
|
||||
it('updates registered tabs when fileinfo is updated', function() {
|
||||
var tabRenderStub = sinon.stub(OCA.Files.DetailTabView.prototype, 'render');
|
||||
var fileInfo = {id: 5, name: 'test.txt'};
|
||||
tabRenderStub.reset();
|
||||
detailsView.setFileInfo(fileInfo);
|
||||
|
||||
expect(testView.getFileInfo()).toEqual(fileInfo);
|
||||
expect(testView2.getFileInfo()).toEqual(fileInfo);
|
||||
|
||||
expect(tabRenderStub.callCount).toEqual(2);
|
||||
tabRenderStub.restore();
|
||||
});
|
||||
it('selects the first tab by default', function() {
|
||||
expect(detailsView.$el.find('.tabHeader').eq(0).hasClass('selected')).toEqual(true);
|
||||
expect(detailsView.$el.find('.tabHeader').eq(1).hasClass('selected')).toEqual(false);
|
||||
expect(detailsView.$el.find('.tab').eq(0).hasClass('hidden')).toEqual(false);
|
||||
expect(detailsView.$el.find('.tab').eq(1).hasClass('hidden')).toEqual(true);
|
||||
});
|
||||
it('switches the current tab when clicking on tab header', function() {
|
||||
detailsView.$el.find('.tabHeader').eq(1).click();
|
||||
expect(detailsView.$el.find('.tabHeader').eq(0).hasClass('selected')).toEqual(false);
|
||||
expect(detailsView.$el.find('.tabHeader').eq(1).hasClass('selected')).toEqual(true);
|
||||
expect(detailsView.$el.find('.tab').eq(0).hasClass('hidden')).toEqual(true);
|
||||
expect(detailsView.$el.find('.tab').eq(1).hasClass('hidden')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -113,7 +113,7 @@ describe('OCA.Files.FavoritesPlugin tests', function() {
|
|||
shareOwner: 'user2'
|
||||
}]);
|
||||
|
||||
fileList.findFileEl('testdir').find('td a.name').click();
|
||||
fileList.findFileEl('testdir').find('td .nametext').click();
|
||||
|
||||
expect(OCA.Files.App.fileList.getCurrentDirectory()).toEqual('/somewhere/inside/subdir/testdir');
|
||||
|
||||
|
|
|
|||
|
|
@ -1870,6 +1870,50 @@ describe('OCA.Files.FileList tests', function() {
|
|||
});
|
||||
})
|
||||
});
|
||||
describe('Details sidebar', function() {
|
||||
beforeEach(function() {
|
||||
fileList.setFiles(testFiles);
|
||||
});
|
||||
it('Clicking on a file row will trigger file action if no details view configured', function() {
|
||||
fileList._detailsView = null;
|
||||
var updateDetailsViewStub = sinon.stub(fileList, '_updateDetailsView');
|
||||
var actionStub = sinon.stub();
|
||||
fileList.setFiles(testFiles);
|
||||
fileList.fileActions.register(
|
||||
'text/plain',
|
||||
'Test',
|
||||
OC.PERMISSION_ALL,
|
||||
function() {
|
||||
// Specify icon for hitory button
|
||||
return OC.imagePath('core','actions/history');
|
||||
},
|
||||
actionStub
|
||||
);
|
||||
fileList.fileActions.setDefault('text/plain', 'Test');
|
||||
var $tr = fileList.findFileEl('One.txt');
|
||||
$tr.find('td.filename>a.name').click();
|
||||
expect(actionStub.calledOnce).toEqual(true);
|
||||
expect(updateDetailsViewStub.notCalled).toEqual(true);
|
||||
updateDetailsViewStub.restore();
|
||||
});
|
||||
it('Clicking on a file row will trigger details sidebar', function() {
|
||||
fileList.fileActions.setDefault('text/plain', 'Test');
|
||||
var $tr = fileList.findFileEl('One.txt');
|
||||
$tr.find('td.filename>a.name').click();
|
||||
expect($tr.hasClass('highlighted')).toEqual(true);
|
||||
|
||||
expect(fileList._detailsView.getFileInfo().id).toEqual(1);
|
||||
});
|
||||
it('Clicking outside to deselect a file row will trigger details sidebar', function() {
|
||||
var $tr = fileList.findFileEl('One.txt');
|
||||
$tr.find('td.filename>a.name').click();
|
||||
|
||||
fileList.$el.find('tfoot').click();
|
||||
|
||||
expect($tr.hasClass('highlighted')).toEqual(false);
|
||||
expect(fileList._detailsView.getFileInfo()).toEqual(null);
|
||||
});
|
||||
});
|
||||
describe('File actions', function() {
|
||||
it('Clicking on a file name will trigger default action', function() {
|
||||
var actionStub = sinon.stub();
|
||||
|
|
@ -1886,7 +1930,7 @@ describe('OCA.Files.FileList tests', function() {
|
|||
);
|
||||
fileList.fileActions.setDefault('text/plain', 'Test');
|
||||
var $tr = fileList.findFileEl('One.txt');
|
||||
$tr.find('td.filename>a.name').click();
|
||||
$tr.find('td.filename .nametext').click();
|
||||
expect(actionStub.calledOnce).toEqual(true);
|
||||
expect(actionStub.getCall(0).args[0]).toEqual('One.txt');
|
||||
var context = actionStub.getCall(0).args[1];
|
||||
|
|
|
|||
104
apps/files/tests/js/mainfileinfodetailviewSpec.js
Normal file
104
apps/files/tests/js/mainfileinfodetailviewSpec.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Vincent Petry
|
||||
* @copyright 2015 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('OCA.Files.MainFileInfoDetailView tests', function() {
|
||||
var view, tooltipStub, previewStub, fncLazyLoadPreview, fileListMock;
|
||||
|
||||
beforeEach(function() {
|
||||
tooltipStub = sinon.stub($.fn, 'tooltip');
|
||||
fileListMock = sinon.mock(OCA.Files.FileList.prototype);
|
||||
view = new OCA.Files.MainFileInfoDetailView();
|
||||
});
|
||||
afterEach(function() {
|
||||
view.destroy();
|
||||
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)));
|
||||
view.setFileInfo(testFileInfo);
|
||||
expect(view.$el.find('.fileName').text()).toEqual('One.txt');
|
||||
expect(view.$el.find('.fileName').attr('title')).toEqual('One.txt');
|
||||
expect(view.$el.find('.size').text()).toEqual('117.7 MB');
|
||||
expect(view.$el.find('.size').attr('title')).toEqual('123456789 bytes');
|
||||
expect(view.$el.find('.date').text()).toEqual('a few seconds ago');
|
||||
expect(view.$el.find('.date').attr('title')).toEqual(dateExpected);
|
||||
clock.restore();
|
||||
});
|
||||
it('displays favorite icon', function() {
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
tags: [OC.TAG_FAVORITE]
|
||||
}));
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/starred'));
|
||||
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
tags: []
|
||||
}));
|
||||
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'
|
||||
}));
|
||||
|
||||
expect(view.$el.find('.thumbnail').css('background-image'))
|
||||
.toContain('filetypes/text-calendar.svg');
|
||||
|
||||
// Folder
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'httpd/unix-directory'
|
||||
}));
|
||||
|
||||
expect(view.$el.find('.thumbnail').css('background-image'))
|
||||
.toContain('filetypes/folder.svg');
|
||||
});
|
||||
it('displays thumbnail', function() {
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'text/plain'
|
||||
}));
|
||||
|
||||
var expectation = fileListMock.expects('lazyLoadPreview');
|
||||
expectation.once();
|
||||
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
fileListMock.verify();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -56,6 +56,7 @@ $application->setupPropagation();
|
|||
|
||||
\OCP\Util::addScript('files_sharing', 'share');
|
||||
\OCP\Util::addScript('files_sharing', 'external');
|
||||
\OCP\Util::addStyle('files_sharing', 'sharetabview');
|
||||
|
||||
// FIXME: registering a job here will cause additional useless SQL queries
|
||||
// when the route is not cron.php, needs a better way
|
||||
|
|
|
|||
3
apps/files_sharing/css/sharetabview.css
Normal file
3
apps/files_sharing/css/sharetabview.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.app-files .shareTabView {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
|
@ -57,7 +57,8 @@ OCA.Sharing.PublicApp = {
|
|||
scrollContainer: $(window),
|
||||
dragOptions: dragOptions,
|
||||
folderDropOptions: folderDropOptions,
|
||||
fileActions: fileActions
|
||||
fileActions: fileActions,
|
||||
detailsViewEnabled: false
|
||||
}
|
||||
);
|
||||
this.files = OCA.Files.Files;
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@
|
|||
}
|
||||
});
|
||||
}, t('files_sharing', 'Share'));
|
||||
|
||||
OC.addScript('files_sharing', 'sharetabview').done(function() {
|
||||
fileList.registerTabView(new OCA.Sharing.ShareTabView('shareTabView'));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
67
apps/files_sharing/js/sharetabview.js
Normal file
67
apps/files_sharing/js/sharetabview.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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>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,
|
||||
/** @lends OCA.Sharing.ShareTabView.prototype */ {
|
||||
_template: null,
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
*/
|
||||
initialize: function() {
|
||||
OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments);
|
||||
this.$el.addClass('shareTabView');
|
||||
},
|
||||
|
||||
getLabel: function() {
|
||||
return t('files_sharing', 'Sharing');
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders this details view
|
||||
*/
|
||||
render: function() {
|
||||
this.$el.empty();
|
||||
|
||||
if (!this._template) {
|
||||
this._template = Handlebars.compile(TEMPLATE);
|
||||
}
|
||||
|
||||
if (this._fileInfo) {
|
||||
this.$el.append(this._template({
|
||||
owner: this._fileInfo.shareOwner || OC.currentUser
|
||||
}));
|
||||
|
||||
} else {
|
||||
// TODO: render placeholder text?
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
OCA.Sharing.ShareTabView = ShareTabView;
|
||||
})();
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ describe('OCA.Sharing.App tests', function() {
|
|||
shareOwner: 'user2'
|
||||
}]);
|
||||
|
||||
fileListIn.findFileEl('testdir').find('td a.name').click();
|
||||
fileListIn.findFileEl('testdir').find('td .nametext').click();
|
||||
|
||||
expect(OCA.Files.App.fileList.getCurrentDirectory()).toEqual('/somewhere/inside/subdir/testdir');
|
||||
|
||||
|
|
|
|||
|
|
@ -417,7 +417,39 @@
|
|||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* APP-SIDEBAR ----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
Sidebar: a sidebar to be used within #app-content
|
||||
have it as first element within app-content in order to shrink other
|
||||
sibling containers properly. Compare Files app for example.
|
||||
*/
|
||||
#app-sidebar {
|
||||
position: fixed;
|
||||
top: 45px;
|
||||
right: 0;
|
||||
left: auto;
|
||||
bottom: 0;
|
||||
width: 27%;
|
||||
display: block;
|
||||
background: #eee;
|
||||
-webkit-transition: margin-right 300ms;
|
||||
-moz-transition: margin-right 300ms;
|
||||
-o-transition: margin-right 300ms;
|
||||
transition: margin-right 300ms;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
visibility: visible;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
#app-content.with-app-sidebar {
|
||||
margin-right: 27%;
|
||||
}
|
||||
|
||||
#app-sidebar.disappear {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* APP-SETTINGS ---------------------------------------------------------------*/
|
||||
|
||||
|
|
@ -556,3 +588,50 @@ em {
|
|||
padding:16px;
|
||||
}
|
||||
|
||||
/* generic tab styles */
|
||||
.tabHeaders {
|
||||
margin: 15px;
|
||||
background-color: #1D2D44;
|
||||
}
|
||||
|
||||
.tabHeaders .tabHeader {
|
||||
float: left;
|
||||
border: 1px solid #ddd;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
background-color: #f8f8f8;
|
||||
font-weight: bold;
|
||||
}
|
||||
.tabHeaders .tabHeader, .tabHeaders .tabHeader a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.tabHeaders .tabHeader:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.tabHeaders .tabHeader:last-child {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.tabHeaders .tabHeader.selected,
|
||||
.tabHeaders .tabHeader:hover {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.tabHeaders .tabHeader.selected,
|
||||
.tabHeaders .tabHeader.selected a,
|
||||
.tabHeaders .tabHeader:hover,
|
||||
.tabHeaders .tabHeader:hover a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.tabsContainer {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.tabsContainer .tab {
|
||||
padding: 15px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@
|
|||
z-index: 1000;
|
||||
}
|
||||
|
||||
#app-sidebar{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* allow horizontal scrollbar in settings
|
||||
otherwise user management is not usable on mobile */
|
||||
#body-settings #app-content {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,26 @@
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the #app-sidebar and add .with-app-sidebar to subsequent siblings
|
||||
*/
|
||||
exports.Apps.showAppSidebar = function() {
|
||||
var $appSidebar = $('#app-sidebar');
|
||||
$appSidebar.removeClass('disappear')
|
||||
$('#app-content').addClass('with-app-sidebar');
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the #app-sidebar and removes .with-app-sidebar from subsequent
|
||||
* siblings
|
||||
*/
|
||||
exports.Apps.hideAppSidebar = function() {
|
||||
var $appSidebar = $('#app-sidebar');
|
||||
$appSidebar.addClass('disappear');
|
||||
$('#app-content').removeClass('with-app-sidebar');
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides a way to slide down a target area through a button and slide it
|
||||
* up if the user clicks somewhere else. Used for the news app settings and
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"oc-dialogs.js",
|
||||
"js.js",
|
||||
"l10n.js",
|
||||
"apps.js",
|
||||
"share.js",
|
||||
"octemplate.js",
|
||||
"eventsource.js",
|
||||
|
|
|
|||
|
|
@ -1366,13 +1366,13 @@ function initCore() {
|
|||
// if there is a scrollbar …
|
||||
if($('#app-content').get(0).scrollHeight > $('#app-content').height()) {
|
||||
if($(window).width() > 768) {
|
||||
controlsWidth = $('#content').width() - $('#app-navigation').width() - getScrollBarWidth();
|
||||
controlsWidth = $('#content').width() - $('#app-navigation').width() - $('#app-sidebar').width() - getScrollBarWidth();
|
||||
} else {
|
||||
controlsWidth = $('#content').width() - getScrollBarWidth();
|
||||
}
|
||||
} else { // if there is none
|
||||
if($(window).width() > 768) {
|
||||
controlsWidth = $('#content').width() - $('#app-navigation').width();
|
||||
controlsWidth = $('#content').width() - $('#app-navigation').width() - $('#app-sidebar').width();
|
||||
} else {
|
||||
controlsWidth = $('#content').width();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ window.isPhantom = /phantom/i.test(navigator.userAgent);
|
|||
OC.TestUtil = TestUtil;
|
||||
}
|
||||
|
||||
moment.locale('en');
|
||||
|
||||
// reset plugins
|
||||
OC.Plugins._plugins = [];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue