aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/js')
-rw-r--r--apps/files/js/detailsview.js61
-rw-r--r--apps/files/js/detailtabview.js17
-rw-r--r--apps/files/js/file-upload.js65
-rw-r--r--apps/files/js/fileactions.js4
-rw-r--r--apps/files/js/fileactionsmenu.js8
-rw-r--r--apps/files/js/filelist.js81
-rw-r--r--apps/files/js/files.js21
-rw-r--r--apps/files/js/mainfileinfodetailview.js33
8 files changed, 215 insertions, 75 deletions
diff --git a/apps/files/js/detailsview.js b/apps/files/js/detailsview.js
index b01f9cea610..f04adcf1292 100644
--- a/apps/files/js/detailsview.js
+++ b/apps/files/js/detailsview.js
@@ -132,16 +132,22 @@
closeLabel: t('files', 'Close')
};
- if (this._tabViews.length > 1) {
- // only render headers if there is more than one available
- templateVars.tabHeaders = _.map(this._tabViews, function(tabView, i) {
- return {
- tabId: tabView.id,
- tabIndex: i,
- label: tabView.getLabel()
- };
- });
- }
+ this._tabViews = this._tabViews.sort(function(tabA, tabB) {
+ var orderA = tabA.order || 0;
+ var orderB = tabB.order || 0;
+ if (orderA === orderB) {
+ return OC.Util.naturalSortCompare(tabA.getLabel(), tabB.getLabel());
+ }
+ return orderA - orderB;
+ });
+
+ templateVars.tabHeaders = _.map(this._tabViews, function(tabView, i) {
+ return {
+ tabId: tabView.id,
+ tabIndex: i,
+ label: tabView.getLabel()
+ };
+ });
this.$el.html(this.template(templateVars));
@@ -158,6 +164,8 @@
this.selectTab(this._currentTabId);
+ this._updateTabVisibilities();
+
this._dirty = false;
},
@@ -216,6 +224,8 @@
if (this._dirty) {
this.render();
+ } else {
+ this._updateTabVisibilities();
}
if (this._currentTabId) {
@@ -233,6 +243,37 @@
},
/**
+ * Update tab headers based on the current model
+ */
+ _updateTabVisibilities: function() {
+ // update tab header visibilities
+ var self = this;
+ var deselect = false;
+ var countVisible = 0;
+ var $tabHeaders = this.$el.find('.tabHeaders li');
+ _.each(this._tabViews, function(tabView) {
+ var isVisible = tabView.canDisplay(self.model);
+ if (isVisible) {
+ countVisible += 1;
+ }
+ if (!isVisible && self._currentTabId === tabView.id) {
+ deselect = true;
+ }
+ $tabHeaders.filterAttr('data-tabid', tabView.id).toggleClass('hidden', !isVisible);
+ });
+
+ // hide the whole container if there is only one tab
+ this.$el.find('.tabHeaders').toggleClass('hidden', countVisible <= 1);
+
+ if (deselect) {
+ // select the first visible tab instead
+ var visibleTabId = this.$el.find('.tabHeader:not(.hidden):first').attr('data-tabid');
+ this.selectTab(visibleTabId);
+ }
+
+ },
+
+ /**
* Returns the file info.
*
* @return {OCA.Files.FileInfoModel} file info
diff --git a/apps/files/js/detailtabview.js b/apps/files/js/detailtabview.js
index 449047cf252..0bd34a88188 100644
--- a/apps/files/js/detailtabview.js
+++ b/apps/files/js/detailtabview.js
@@ -29,11 +29,15 @@
_template: null,
- initialize: function() {
+ initialize: function(options) {
+ options = options || {};
if (!this.id) {
this.id = 'detailTabView' + DetailTabView._TAB_COUNT;
DetailTabView._TAB_COUNT++;
}
+ if (options.order) {
+ this.order = options.order || 0;
+ }
},
/**
@@ -91,6 +95,17 @@
*/
nextPage: function() {
// load the next page, if applicable
+ },
+
+ /**
+ * Returns whether the current tab is able to display
+ * the given file info, for example based on mime type.
+ *
+ * @param {OCA.Files.FileInfoModel} fileInfo file info model
+ * @return {bool} whether to display this tab
+ */
+ canDisplay: function(fileInfo) {
+ return true;
}
});
DetailTabView._TAB_COUNT = 0;
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 17f0f777169..03330ad7c5d 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -18,7 +18,7 @@
* - TODO music upload button
*/
-/* global Files, FileList, jQuery, oc_requesttoken, humanFileSize, getUniqueName */
+/* global jQuery, oc_requesttoken, humanFileSize */
/**
* Function that will allow us to know if Ajax uploads are supported
@@ -139,6 +139,9 @@ OC.Upload = {
if (data.data) {
data.data.append('resolution', 'replace');
} else {
+ if (!data.formData) {
+ data.formData = [];
+ }
data.formData.push({name:'resolution', value:'replace'}); //hack for ie8
}
data.submit();
@@ -152,6 +155,9 @@ OC.Upload = {
if (data.data) {
data.data.append('resolution', 'autorename');
} else {
+ if (!data.formData) {
+ data.formData = [];
+ }
data.formData.push({name:'resolution', value:'autorename'}); //hack for ie8
}
data.submit();
@@ -164,8 +170,9 @@ OC.Upload = {
}
},
/**
- * TODO checks the list of existing files prior to uploading and shows a simple dialog to choose
+ * checks the list of existing files prior to uploading and shows a simple dialog to choose
* skip all, replace all or choose which files to keep
+ *
* @param {array} selection of files to upload
* @param {object} callbacks - object with several callback methods
* @param {function} callbacks.onNoConflicts
@@ -175,14 +182,34 @@ OC.Upload = {
* @param {function} callbacks.onCancel
*/
checkExistingFiles: function (selection, callbacks) {
- /*
- $.each(selection.uploads, function(i, upload) {
- var $row = OCA.Files.App.fileList.findFileEl(upload.files[0].name);
- if ($row) {
- // TODO check filelist before uploading and show dialog on conflicts, use callbacks
+ var fileList = OCA.Files.App.fileList;
+ var conflicts = [];
+ // only keep non-conflicting uploads
+ selection.uploads = _.filter(selection.uploads, function(upload) {
+ var fileInfo = fileList.findFile(upload.files[0].name);
+ if (fileInfo) {
+ conflicts.push([
+ // original
+ _.extend(fileInfo, {
+ directory: fileInfo.directory || fileInfo.path || fileList.getCurrentDirectory()
+ }),
+ // replacement (File object)
+ upload
+ ]);
+ return false;
}
+ return true;
});
- */
+ if (conflicts.length) {
+ _.each(conflicts, function(conflictData) {
+ OC.dialogs.fileexists(conflictData[1], conflictData[0], conflictData[1].files[0], OC.Upload);
+ });
+ }
+
+ // upload non-conflicting files
+ // note: when reaching the server they might still meet conflicts
+ // if the folder was concurrently modified, these will get added
+ // to the already visible dialog, if applicable
callbacks.onNoConflicts(selection);
},
@@ -368,18 +395,18 @@ OC.Upload = {
},
submit: function(e, data) {
OC.Upload.rememberUpload(data);
- if ( ! data.formData ) {
- var fileDirectory = '';
- if(typeof data.files[0].relativePath !== 'undefined') {
- fileDirectory = data.files[0].relativePath;
- }
- // noone set update parameters, we set the minimum
- data.formData = {
- requesttoken: oc_requesttoken,
- dir: data.targetDir || FileList.getCurrentDirectory(),
- file_directory: fileDirectory
- };
+ if (!data.formData) {
+ data.formData = [];
+ }
+
+ var fileDirectory = '';
+ if(typeof data.files[0].relativePath !== 'undefined') {
+ fileDirectory = data.files[0].relativePath;
}
+ // FIXME: prevent re-adding the same
+ data.formData.push({name: 'requesttoken', value: oc_requesttoken});
+ data.formData.push({name: 'dir', value: data.targetDir || FileList.getCurrentDirectory()});
+ data.formData.push({name: 'file_directory', value: fileDirectory});
},
fail: function(e, data) {
OC.Upload.log('fail', e, data);
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index d3904f2f6d3..bde0b094b87 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -141,6 +141,7 @@
name: name,
displayName: action.displayName,
mime: mime,
+ order: action.order || 0,
icon: action.icon,
permissions: action.permissions,
type: action.type || FileActions.TYPE_DROPDOWN
@@ -565,6 +566,7 @@
this.registerAction({
name: 'Download',
displayName: t('files', 'Download'),
+ order: -20,
mime: 'all',
permissions: OC.PERMISSION_READ,
icon: function () {
@@ -596,6 +598,7 @@
name: 'Rename',
displayName: t('files', 'Rename'),
mime: 'all',
+ order: -30,
permissions: OC.PERMISSION_UPDATE,
icon: function() {
return OC.imagePath('core', 'actions/rename');
@@ -617,6 +620,7 @@
name: 'Delete',
displayName: t('files', 'Delete'),
mime: 'all',
+ order: 1000,
// permission is READ because we show a hint instead if there is no permission
permissions: OC.PERMISSION_DELETE,
icon: function() {
diff --git a/apps/files/js/fileactionsmenu.js b/apps/files/js/fileactionsmenu.js
index 5ab0e42f93e..67cbb48c965 100644
--- a/apps/files/js/fileactionsmenu.js
+++ b/apps/files/js/fileactionsmenu.js
@@ -100,6 +100,14 @@
(!defaultAction || actionSpec.name !== defaultAction.name)
);
});
+ items = items.sort(function(actionA, actionB) {
+ var orderA = actionA.order || 0;
+ var orderB = actionB.order || 0;
+ if (orderB === orderA) {
+ return OC.Util.naturalSortCompare(actionA.displayName, actionB.displayName);
+ }
+ return orderA - orderB;
+ });
items = _.map(items, function(item) {
item.nameLowerCase = item.name.toLowerCase();
return item;
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 060269c220f..7a025e772c5 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -239,13 +239,6 @@
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));
@@ -278,6 +271,9 @@
if (this._newButton) {
this._newButton.remove();
}
+ if (this._detailsView) {
+ this._detailsView.remove();
+ }
// TODO: also unregister other event handlers
this.fileActions.off('registerAction', this._onFileActionsUpdated);
this.fileActions.off('setDefault', this._onFileActionsUpdated);
@@ -302,11 +298,11 @@
name: 'Details',
displayName: t('files', 'Details'),
mime: 'all',
+ order: -50,
icon: OC.imagePath('core', 'actions/details'),
permissions: OC.PERMISSION_READ,
actionHandler: function(fileName, context) {
self._updateDetailsView(fileName);
- OC.Apps.showAppSidebar(self._detailsView.$el);
}
});
}
@@ -407,9 +403,14 @@
this._currentFileModel.off();
}
this._currentFileModel = null;
+ OC.Apps.hideAppSidebar(this._detailsView.$el);
return;
}
+ if (this._detailsView.$el.hasClass('disappear')) {
+ OC.Apps.showAppSidebar(this._detailsView.$el);
+ }
+
var $tr = this.findFileEl(fileName);
var model = this.getModelForFile($tr);
@@ -452,10 +453,10 @@
* Selected/deselects the given file element and updated
* the internal selection cache.
*
- * @param $tr single file row element
- * @param state true to select, false to deselect
+ * @param {Object} $tr single file row element
+ * @param {bool} state true to select, false to deselect
*/
- _selectFileEl: function($tr, state) {
+ _selectFileEl: function($tr, state, showDetailsView) {
var $checkbox = $tr.find('td.filename>.selectCheckBox');
var oldData = !!this._selectedFiles[$tr.data('id')];
var data;
@@ -474,11 +475,8 @@
delete this._selectedFiles[$tr.data('id')];
this._selectionSummary.remove(data);
}
- if (this._selectionSummary.getTotal() === 1) {
+ if (this._detailsView && this._selectionSummary.getTotal() === 1 && !this._detailsView.$el.hasClass('disappear')) {
this._updateDetailsView(_.values(this._selectedFiles)[0].name);
- } else {
- // show nothing when multiple files are selected
- this._updateDetailsView(null);
}
this.$el.find('.select-all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
},
@@ -488,6 +486,9 @@
*/
_onClickFile: function(event) {
var $tr = $(event.target).closest('tr');
+ if ($tr.hasClass('dragging')) {
+ return;
+ }
if (this._allowSelection && (event.ctrlKey || event.shiftKey)) {
event.preventDefault();
if (event.shiftKey) {
@@ -551,9 +552,13 @@
*/
_onClickFileCheckbox: function(e) {
var $tr = $(e.target).closest('tr');
- this._selectFileEl($tr, !$tr.hasClass('selected'));
+ var state = !$tr.hasClass('selected');
+ this._selectFileEl($tr, state);
this._lastChecked = $tr;
this.updateSelectionSummary();
+ if (state) {
+ this._updateDetailsView($tr.attr('data-file'));
+ }
},
/**
@@ -721,8 +726,23 @@
return true;
},
/**
- * Returns the tr element for a given file name
- * @param fileName file name
+ * Returns the file info for the given file name from the internal collection.
+ *
+ * @param {string} fileName file name
+ * @return {OCA.Files.FileInfo} file info or null if it was not found
+ *
+ * @since 8.2
+ */
+ findFile: function(fileName) {
+ return _.find(this.files, function(aFile) {
+ return (aFile.name === fileName);
+ }) || null;
+ },
+ /**
+ * Returns the tr element for a given file name, but only if it was already rendered.
+ *
+ * @param {string} fileName file name
+ * @return {Object} jQuery object of the matching row
*/
findFileEl: function(fileName){
// use filterAttr to avoid escaping issues
@@ -928,7 +948,7 @@
if (this._allowSelection) {
td.append(
'<input id="select-' + this.id + '-' + fileData.id +
- '" type="checkbox" class="selectCheckBox"/><label for="select-' + this.id + '-' + fileData.id + '">' +
+ '" type="checkbox" class="selectCheckBox checkbox"/><label for="select-' + this.id + '-' + fileData.id + '">' +
'<div class="thumbnail" style="background-image:url(' + icon + '); background-size: 32px;"></div>' +
'<span class="hidden-visually">' + t('files', 'Select') + '</span>' +
'</label>'
@@ -1307,8 +1327,10 @@
sortdirection: this._sortDirection
}
});
- // close sidebar
- this._updateDetailsView(null);
+ if (this._detailsView) {
+ // close sidebar
+ this._updateDetailsView(null);
+ }
var callBack = this.reloadCallback.bind(this);
return this._reloadCall.then(callBack, callBack);
},
@@ -1396,8 +1418,8 @@
}
urlSpec.x *= window.devicePixelRatio;
urlSpec.y *= window.devicePixelRatio;
- urlSpec.x = Math.floor(urlSpec.x);
- urlSpec.y = Math.floor(urlSpec.y);
+ urlSpec.x = Math.ceil(urlSpec.x);
+ urlSpec.y = Math.ceil(urlSpec.y);
urlSpec.forceIcon = 0;
return OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
},
@@ -1515,11 +1537,12 @@
remove: function(name, options){
options = options || {};
var fileEl = this.findFileEl(name);
+ var fileId = fileEl.data('id');
var index = fileEl.index();
if (!fileEl.length) {
return null;
}
- if (this._selectedFiles[fileEl.data('id')]) {
+ if (this._selectedFiles[fileId]) {
// remove from selection first
this._selectFileEl(fileEl, false);
this.updateSelectionSummary();
@@ -1529,6 +1552,14 @@
fileEl.find('td.filename').draggable('destroy');
}
this.files.splice(index, 1);
+ if (this._currentFileModel && this._currentFileModel.get('id') === fileId) {
+ // Note: in the future we should call destroy() directly on the model
+ // and the model will take care of the deletion.
+ // Here we only trigger the event to notify listeners that
+ // the file was removed.
+ this._currentFileModel.trigger('destroy');
+ this._updateDetailsView(null);
+ }
fileEl.remove();
// TODO: improve performance on batch update
this.isEmpty = !this.files.length;
@@ -1879,7 +1910,7 @@
* @return {bool} true if the file exists in the list, false otherwise
*/
inList:function(file) {
- return this.findFileEl(file).length;
+ return this.findFile(file);
},
/**
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 4fdc9eb2110..9ab7609cc40 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -356,7 +356,7 @@ var createDragShadow = function(event) {
var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
if (!isDragSelected) {
//select dragged file
- FileList._selectFileEl($(event.target).parents('tr:first'), true);
+ FileList._selectFileEl($(event.target).parents('tr:first'), true, false);
}
// do not show drag shadow for too many files
@@ -365,7 +365,7 @@ var createDragShadow = function(event) {
if (!isDragSelected && selectedFiles.length === 1) {
//revert the selection
- FileList._selectFileEl($(event.target).parents('tr:first'), false);
+ FileList._selectFileEl($(event.target).parents('tr:first'), false, false);
}
// build dragshadow
@@ -413,22 +413,17 @@ var dragOptions={
cursor: 'move',
start: function(event, ui){
var $selectedFiles = $('td.filename input:checkbox:checked');
- if($selectedFiles.length > 1){
- $selectedFiles.parents('tr').fadeTo(250, 0.2);
- }
- else{
- $(this).fadeTo(250, 0.2);
+ if (!$selectedFiles.length) {
+ $selectedFiles = $(this);
}
+ $selectedFiles.closest('tr').fadeTo(250, 0.2).addClass('dragging');
},
stop: function(event, ui) {
var $selectedFiles = $('td.filename input:checkbox:checked');
- if($selectedFiles.length > 1){
- $selectedFiles.parents('tr').fadeTo(250, 1);
- }
- else{
- $(this).fadeTo(250, 1);
+ if (!$selectedFiles.length) {
+ $selectedFiles = $(this);
}
- $('#fileList tr td.filename').addClass('ui-draggable');
+ $selectedFiles.closest('tr').fadeTo(250, 1).removeClass('dragging');
}
};
// sane browsers support using the distance option
diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js
index 830f074f3f1..39a00d0a5a5 100644
--- a/apps/files/js/mainfileinfodetailview.js
+++ b/apps/files/js/mainfileinfodetailview.js
@@ -10,7 +10,7 @@
(function() {
var TEMPLATE =
- '<div class="thumbnailContainer"><a href="#" class="thumbnail action-default"></a></div>' +
+ '<div class="thumbnailContainer"><a href="#" class="thumbnail action-default"><div class="stretcher"/></a></div>' +
'<div class="file-details-container">' +
'<div class="fileName"><h3 title="{{name}}" class="ellipsis">{{name}}</h3></div>' +
' <div class="file-details ellipsis">' +
@@ -140,13 +140,18 @@
},
loadPreview: function(path, mime, etag, $iconDiv, $container, isImage) {
- var maxImageHeight = ($container.parent().width() + 50) / (16/9); // 30px for negative margin
+ var maxImageWidth = $container.parent().width() + 50; // 50px for negative margins
+ var maxImageHeight = maxImageWidth / (16/9);
var smallPreviewSize = 75;
var isLandscape = function(img) {
return img.width > (img.height * 1.2);
};
+ var isSmall = function(img) {
+ return (img.width * 1.1) < (maxImageWidth * window.devicePixelRatio);
+ };
+
var getTargetHeight = function(img) {
if(isImage) {
var targetHeight = img.height / window.devicePixelRatio;
@@ -159,13 +164,23 @@
}
};
+ var getTargetRatio = function(img){
+ var ratio = img.width / img.height;
+ if (ratio > 16/9) {
+ return ratio;
+ } else {
+ return 16/9;
+ }
+ };
+
this._fileList.lazyLoadPreview({
path: path,
mime: mime,
etag: etag,
y: isImage ? maxImageHeight : smallPreviewSize,
- x: isImage ? 99999 /* only limit on y */ : smallPreviewSize,
+ x: isImage ? maxImageWidth : smallPreviewSize,
a: isImage ? 1 : null,
+ mode: isImage ? 'cover' : null,
callback: function (previewUrl, img) {
$iconDiv.previewImg = previewUrl;
@@ -176,9 +191,7 @@
$iconDiv.removeClass('icon-loading icon-32');
var targetHeight = getTargetHeight(img);
if (this.model.isImage() && targetHeight > smallPreviewSize) {
- if (!isLandscape(img)) {
- $container.addClass('portrait');
- }
+ $container.addClass((isLandscape(img) && !isSmall(img))? 'landscape': 'portrait');
$container.addClass('image');
}
@@ -186,7 +199,13 @@
// when we dont have a preview we show the mime icon in the error handler
$iconDiv.css({
'background-image': 'url("' + previewUrl + '")',
- 'height': targetHeight
+ height: (targetHeight > smallPreviewSize)? 'auto': targetHeight,
+ 'max-height': isSmall(img)? targetHeight: null
+ });
+
+ var targetRatio = getTargetRatio(img);
+ $iconDiv.find('.stretcher').css({
+ 'padding-bottom': (100 / targetRatio) + '%'
});
}.bind(this),
error: function () {