Merge pull request #4766 from owncloud/fix_3728_with_file_exists_dialog

file upload conflicts dialog
This commit is contained in:
Thomas Müller 2013-09-19 08:23:07 -07:00
commit 09cfebe936
17 changed files with 1713 additions and 519 deletions

View File

@ -78,7 +78,7 @@ foreach ($_FILES['files']['error'] as $error) {
}
$files = $_FILES['files'];
$error = '';
$error = false;
$maxUploadFileSize = $storageStats['uploadMaxFilesize'];
$maxHumanFileSize = OCP\Util::humanFileSize($maxUploadFileSize);
@ -98,23 +98,57 @@ $result = array();
if (strpos($dir, '..') === false) {
$fileCount = count($files['name']);
for ($i = 0; $i < $fileCount; $i++) {
$target = OCP\Files::buildNotExistingFileName(stripslashes($dir), $files['name'][$i]);
// $path needs to be normalized - this failed within drag'n'drop upload to a sub-folder
$target = \OC\Files\Filesystem::normalizePath($target);
if (is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) {
$meta = \OC\Files\Filesystem::getFileInfo($target);
// updated max file size after upload
$storageStats = \OCA\files\lib\Helper::buildFileStorageStatistics($dir);
if ($meta === false) {
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Upload failed')), $storageStats)));
exit();
if (isset($_POST['resolution']) && $_POST['resolution']==='autorename') {
// append a number in brackets like 'filename (2).ext'
$target = OCP\Files::buildNotExistingFileName(stripslashes($dir), $files['name'][$i]);
} else {
$target = \OC\Files\Filesystem::normalizePath(stripslashes($dir).'/'.$files['name'][$i]);
}
if ( ! \OC\Files\Filesystem::file_exists($target)
|| (isset($_POST['resolution']) && $_POST['resolution']==='replace')
) {
// upload and overwrite file
if (is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) {
// updated max file size after upload
$storageStats = \OCA\files\lib\Helper::buildFileStorageStatistics($dir);
$meta = \OC\Files\Filesystem::getFileInfo($target);
if ($meta === false) {
$error = $l->t('Upload failed. Could not get file info.');
} else {
$result[] = array('status' => 'success',
'mime' => $meta['mimetype'],
'mtime' => $meta['mtime'],
'size' => $meta['size'],
'id' => $meta['fileid'],
'name' => basename($target),
'originalname' => $files['tmp_name'][$i],
'uploadMaxFilesize' => $maxUploadFileSize,
'maxHumanFilesize' => $maxHumanFileSize,
'permissions' => $meta['permissions'],
);
}
} else {
$result[] = array('status' => 'success',
$error = $l->t('Upload failed. Could not find uploaded file');
}
} else {
// file already exists
$meta = \OC\Files\Filesystem::getFileInfo($target);
if ($meta === false) {
$error = $l->t('Upload failed. Could not get file info.');
} else {
$result[] = array('status' => 'existserror',
'mime' => $meta['mimetype'],
'mtime' => $meta['mtime'],
'size' => $meta['size'],
'id' => $meta['fileid'],
'name' => basename($target),
'originalname' => $files['name'][$i],
'originalname' => $files['tmp_name'][$i],
'uploadMaxFilesize' => $maxUploadFileSize,
'maxHumanFilesize' => $maxHumanFileSize,
'permissions' => $meta['permissions'],
@ -122,10 +156,13 @@ if (strpos($dir, '..') === false) {
}
}
}
OCP\JSON::encodedPrint($result);
exit();
} else {
$error = $l->t('Invalid directory.');
}
OCP\JSON::error(array('data' => array_merge(array('message' => $error), $storageStats)));
if ($error === false) {
OCP\JSON::encodedPrint($result);
exit();
} else {
OCP\JSON::error(array('data' => array_merge(array('message' => $error), $storageStats)));
}

View File

@ -360,4 +360,3 @@ table.dragshadow td.size {
.mask.transparent{
opacity: 0;
}

119
apps/files/css/upload.css Normal file
View File

@ -0,0 +1,119 @@
#upload {
height:27px; padding:0; margin-left:0.2em; overflow:hidden;
vertical-align: top;
}
#upload a {
position:relative; display:block; width:100%; height:27px;
cursor:pointer; z-index:10;
background-image:url('%webroot%/core/img/actions/upload.svg');
background-repeat:no-repeat;
background-position:7px 6px;
opacity:0.65;
}
.file_upload_target { display:none; }
.file_upload_form { display:inline; float:left; margin:0; padding:0; cursor:pointer; overflow:visible; }
#file_upload_start {
float: left;
left:0; top:0; width:28px; height:27px; padding:0;
font-size:1em;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0;
z-index:20; position:relative; cursor:pointer; overflow:hidden;
}
#uploadprogresswrapper {
display: inline-block;
vertical-align: top;
margin:0.3em;
height: 29px;
}
#uploadprogressbar {
position:relative;
float: left;
margin-left: 12px;
width: 130px;
height: 26px;
display:inline-block;
}
#uploadprogressbar + stop {
font-size: 13px;
}
.oc-dialog .fileexists table {
width: 100%;
}
.oc-dialog .fileexists th {
padding-left: 0;
padding-right: 0;
}
.oc-dialog .fileexists th input[type='checkbox'] {
margin-right: 3px;
}
.oc-dialog .fileexists th:first-child {
width: 230px;
}
.oc-dialog .fileexists th label {
font-weight: normal;
color:black;
}
.oc-dialog .fileexists th .count {
margin-left: 3px;
}
.oc-dialog .fileexists .conflicts .template {
display: none;
}
.oc-dialog .fileexists .conflict {
width: 100%;
height: 85px;
}
.oc-dialog .fileexists .conflict .filename {
color:#777;
word-break: break-all;
clear: left;
}
.oc-dialog .fileexists .icon {
width: 64px;
height: 64px;
margin: 0px 5px 5px 5px;
background-repeat: no-repeat;
background-size: 64px 64px;
float: left;
}
.oc-dialog .fileexists .replacement {
float: left;
width: 230px;
}
.oc-dialog .fileexists .original {
float: left;
width: 230px;
}
.oc-dialog .fileexists .conflicts {
overflow-y:scroll;
max-height: 225px;
}
.oc-dialog .fileexists .conflict input[type='checkbox'] {
float: left;
}
.oc-dialog .fileexists .toggle {
background-image: url('%webroot%/core/img/actions/triangle-e.png');
width: 16px;
height: 16px;
}
.oc-dialog .fileexists #allfileslabel {
float:right;
}
.oc-dialog .fileexists #allfiles {
vertical-align: bottom;
position: relative;
top: -3px;
}
.oc-dialog .fileexists #allfiles + span{
vertical-align: bottom;
}
.oc-dialog .oc-dialog-buttonrow {
width:100%;
text-align:right;
}
.oc-dialog .oc-dialog-buttonrow .cancel {
float:left;
}

View File

@ -26,6 +26,7 @@ OCP\User::checkLoggedIn();
// Load the files we need
OCP\Util::addStyle('files', 'files');
OCP\Util::addStyle('files', 'upload');
OCP\Util::addscript('files', 'file-upload');
OCP\Util::addscript('files', 'jquery.iframe-transport');
OCP\Util::addscript('files', 'jquery.fileupload');

View File

@ -1,3 +1,12 @@
/**
* The file upload code uses several hooks to interact with blueimps jQuery file upload library:
* 1. the core upload handling hooks are added when initializing the plugin,
* 2. if the browser supports progress events they are added in a separate set after the initialization
* 3. every app can add it's own triggers for fileupload
* - files adds d'n'd handlers and also reacts to done events to add new rows to the filelist
* - TODO pictures upload button
* - TODO music upload button
*/
/**
* Function that will allow us to know if Ajax uploads are supported
@ -26,51 +35,278 @@ function supportAjaxUploadWithProgress() {
}
}
/**
* keeps track of uploads in progress and implements callbacks for the conflicts dialog
* @type {OC.Upload}
*/
OC.Upload = {
_uploads: [],
/**
* cancels a single upload,
* @deprecated because it was only used when a file currently beeing uploaded was deleted. Now they are added after
* they have been uploaded.
* @param {string} dir
* @param {string} filename
* @returns {unresolved}
*/
cancelUpload:function(dir, filename) {
var self = this;
var deleted = false;
//FIXME _selections
jQuery.each(this._uploads, function(i, jqXHR) {
if (selection.dir === dir && selection.uploads[filename]) {
deleted = self.deleteSelectionUpload(selection, filename);
return false; // end searching through selections
}
});
return deleted;
},
/**
* deletes the jqHXR object from a data selection
* @param {object} data
*/
deleteUpload:function(data) {
delete data.jqXHR;
},
/**
* cancels all uploads
*/
cancelUploads:function() {
this.log('canceling uploads');
jQuery.each(this._uploads,function(i, jqXHR){
jqXHR.abort();
});
this._uploads = [];
},
rememberUpload:function(jqXHR){
if (jqXHR) {
this._uploads.push(jqXHR);
}
},
/**
* Checks the currently known uploads.
* returns true if any hxr has the state 'pending'
* @returns {boolean}
*/
isProcessing:function(){
var count = 0;
jQuery.each(this._uploads,function(i, data){
if (data.state() === 'pending') {
count++;
}
});
return count > 0;
},
/**
* callback for the conflicts dialog
* @param {object} data
*/
onCancel:function(data) {
this.cancelUploads();
},
/**
* callback for the conflicts dialog
* calls onSkip, onReplace or onAutorename for each conflict
* @param {object} conflicts - list of conflict elements
*/
onContinue:function(conflicts) {
var self = this;
//iterate over all conflicts
jQuery.each(conflicts, function (i, conflict) {
conflict = $(conflict);
var keepOriginal = conflict.find('.original input[type="checkbox"]:checked').length === 1;
var keepReplacement = conflict.find('.replacement input[type="checkbox"]:checked').length === 1;
if (keepOriginal && keepReplacement) {
// when both selected -> autorename
self.onAutorename(conflict.data('data'));
} else if (keepReplacement) {
// when only replacement selected -> overwrite
self.onReplace(conflict.data('data'));
} else {
// when only original seleted -> skip
// when none selected -> skip
self.onSkip(conflict.data('data'));
}
});
},
/**
* handle skipping an upload
* @param {object} data
*/
onSkip:function(data){
this.log('skip', null, data);
this.deleteUpload(data);
},
/**
* handle replacing a file on the server with an uploaded file
* @param {object} data
*/
onReplace:function(data){
this.log('replace', null, data);
data.data.append('resolution', 'replace');
data.submit();
},
/**
* handle uploading a file and letting the server decide a new name
* @param {object} data
*/
onAutorename:function(data){
this.log('autorename', null, data);
if (data.data) {
data.data.append('resolution', 'autorename');
} else {
data.formData.push({name:'resolution',value:'autorename'}); //hack for ie8
}
data.submit();
},
_trace:false, //TODO implement log handler for JS per class?
log:function(caption, e, data) {
if (this._trace) {
console.log(caption);
console.log(data);
}
},
/**
* TODO 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
* @param {function} callbacks.onSkipConflicts
* @param {function} callbacks.onReplaceConflicts
* @param {function} callbacks.onChooseConflicts
* @param {function} callbacks.onCancel
*/
checkExistingFiles: function (selection, callbacks){
// TODO check filelist before uploading and show dialog on conflicts, use callbacks
callbacks.onNoConflicts(selection);
}
};
$(document).ready(function() {
if ( $('#file_upload_start').length ) {
if ( $('#file_upload_start').exists() ) {
var file_upload_param = {
dropZone: $('#content'), // restrict dropZone to content div
autoUpload: false,
sequentialUploads: true,
//singleFileUploads is on by default, so the data.files array will always have length 1
/**
* on first add of every selection
* - check all files of originalFiles array with files in dir
* - on conflict show dialog
* - skip all -> remember as single skip action for all conflicting files
* - replace all -> remember as single replace action for all conflicting files
* - choose -> show choose dialog
* - mark files to keep
* - when only existing -> remember as single skip action
* - when only new -> remember as single replace action
* - when both -> remember as single autorename action
* - start uploading selection
* @param {object} e
* @param {object} data
* @returns {boolean}
*/
add: function(e, data) {
if(data.files[0].type === '' && data.files[0].size == 4096)
{
data.textStatus = 'dirorzero';
data.errorThrown = t('files','Unable to upload your file as it is a directory or has 0 bytes');
var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
fu._trigger('fail', e, data);
return true; //don't upload this file but go on with next in queue
OC.Upload.log('add', e, data);
var that = $(this);
// we need to collect all data upload objects before starting the upload so we can check their existence
// and set individual conflict actions. unfortunately there is only one variable that we can use to identify
// the selection a data upload is part of, so we have to collect them in data.originalFiles
// turning singleFileUploads off is not an option because we want to gracefully handle server errors like
// already exists
// create a container where we can store the data objects
if ( ! data.originalFiles.selection ) {
// initialize selection and remember number of files to upload
data.originalFiles.selection = {
uploads: [],
filesToUpload: data.originalFiles.length,
totalBytes: 0
};
}
var totalSize=0;
$.each(data.originalFiles, function(i,file){
totalSize+=file.size;
});
if(totalSize>$('#max_upload').val()){
var selection = data.originalFiles.selection;
// add uploads
if ( selection.uploads.length < selection.filesToUpload ){
// remember upload
selection.uploads.push(data);
}
//examine file
var file = data.files[0];
if (file.type === '' && file.size === 4096) {
data.textStatus = 'dirorzero';
data.errorThrown = t('files', 'Unable to upload {filename} as it is a directory or has 0 bytes',
{filename: file.name}
);
}
// add size
selection.totalBytes += file.size;
//check max upload size
if (selection.totalBytes > $('#max_upload').val()) {
data.textStatus = 'notenoughspace';
data.errorThrown = t('files','Not enough space available');
var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
data.errorThrown = t('files', 'Not enough space available');
}
// end upload for whole selection on error
if (data.errorThrown) {
// trigger fileupload fail
var fu = that.data('blueimp-fileupload') || that.data('fileupload');
fu._trigger('fail', e, data);
return false; //don't upload anything
}
// start the actual file upload
var jqXHR = data.submit();
// check existing files when all is collected
if ( selection.uploads.length >= selection.filesToUpload ) {
//remove our selection hack:
delete data.originalFiles.selection;
// remember jqXHR to show warning to user when he navigates away but an upload is still in progress
if (typeof data.context !== 'undefined' && data.context.data('type') === 'dir') {
var dirName = data.context.data('file');
if(typeof uploadingFiles[dirName] === 'undefined') {
uploadingFiles[dirName] = {};
}
uploadingFiles[dirName][data.files[0].name] = jqXHR;
} else {
uploadingFiles[data.files[0].name] = jqXHR;
var callbacks = {
onNoConflicts: function (selection) {
$.each(selection.uploads, function(i, upload) {
upload.submit();
});
},
onSkipConflicts: function (selection) {
//TODO mark conflicting files as toskip
},
onReplaceConflicts: function (selection) {
//TODO mark conflicting files as toreplace
},
onChooseConflicts: function (selection) {
//TODO mark conflicting files as chosen
},
onCancel: function (selection) {
$.each(selection.uploads, function(i, upload) {
upload.abort();
});
}
};
OC.Upload.checkExistingFiles(selection, callbacks);
}
return true; // continue adding files
},
/**
* called after the first add, does NOT have the data param
* @param {object} e
*/
start: function(e) {
OC.Upload.log('start', e, null);
},
submit: function(e, data) {
OC.Upload.rememberUpload(data);
if ( ! data.formData ) {
// noone set update parameters, we set the minimum
data.formData = {
@ -79,13 +315,8 @@ $(document).ready(function() {
};
}
},
/**
* called after the first add, does NOT have the data param
* @param e
*/
start: function(e) {
},
fail: function(e, data) {
OC.Upload.log('fail', e, data);
if (typeof data.textStatus !== 'undefined' && data.textStatus !== 'success' ) {
if (data.textStatus === 'abort') {
$('#notification').text(t('files', 'Upload cancelled.'));
@ -99,14 +330,15 @@ $(document).ready(function() {
$('#notification').fadeOut();
}, 5000);
}
delete uploadingFiles[data.files[0].name];
OC.Upload.deleteUpload(data);
},
/**
* called for every successful upload
* @param e
* @param data
* @param {object} e
* @param {object} data
*/
done:function(e, data) {
OC.Upload.log('done', e, data);
// handle different responses (json or body from iframe for ie)
var response;
if (typeof data.result === 'string') {
@ -117,33 +349,34 @@ $(document).ready(function() {
}
var result=$.parseJSON(response);
if(typeof result[0] !== 'undefined' && result[0].status === 'success') {
var filename = result[0].originalname;
// delete jqXHR reference
if (typeof data.context !== 'undefined' && data.context.data('type') === 'dir') {
var dirName = data.context.data('file');
delete uploadingFiles[dirName][filename];
if ($.assocArraySize(uploadingFiles[dirName]) == 0) {
delete uploadingFiles[dirName];
}
} else {
delete uploadingFiles[filename];
}
var file = result[0];
} else {
delete data.jqXHR;
if(typeof result[0] === 'undefined') {
data.textStatus = 'servererror';
data.errorThrown = t('files', result.data.message);
data.errorThrown = t('files', 'Could not get result from server.');
var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
fu._trigger('fail', e, data);
} else if (result[0].status === 'existserror') {
//show "file already exists" dialog
var original = result[0];
var replacement = data.files[0];
var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
OC.dialogs.fileexists(data, original, replacement, OC.Upload, fu);
} else if (result[0].status !== 'success') {
//delete data.jqXHR;
data.textStatus = 'servererror';
data.errorThrown = result.data.message; // error message has been translated on server
var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
fu._trigger('fail', e, data);
}
},
/**
* called after last upload
* @param e
* @param data
* @param {object} e
* @param {object} data
*/
stop: function(e, data) {
OC.Upload.log('stop', e, data);
}
};
@ -155,6 +388,7 @@ $(document).ready(function() {
// add progress handlers
fileupload.on('fileuploadadd', function(e, data) {
OC.Upload.log('progress handle fileuploadadd', e, data);
//show cancel button
//if(data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie?
// $('#uploadprogresswrapper input.stop').show();
@ -162,23 +396,29 @@ $(document).ready(function() {
});
// add progress handlers
fileupload.on('fileuploadstart', function(e, data) {
OC.Upload.log('progress handle fileuploadstart', e, data);
$('#uploadprogresswrapper input.stop').show();
$('#uploadprogressbar').progressbar({value:0});
$('#uploadprogressbar').fadeIn();
});
fileupload.on('fileuploadprogress', function(e, data) {
OC.Upload.log('progress handle fileuploadprogress', e, data);
//TODO progressbar in row
});
fileupload.on('fileuploadprogressall', function(e, data) {
OC.Upload.log('progress handle fileuploadprogressall', e, data);
var progress = (data.loaded / data.total) * 100;
$('#uploadprogressbar').progressbar('value', progress);
});
fileupload.on('fileuploadstop', function(e, data) {
OC.Upload.log('progress handle fileuploadstop', e, data);
$('#uploadprogresswrapper input.stop').fadeOut();
$('#uploadprogressbar').fadeOut();
});
fileupload.on('fileuploadfail', function(e, data) {
OC.Upload.log('progress handle fileuploadfail', e, data);
//if user pressed cancel hide upload progress bar and cancel button
if (data.errorThrown === 'abort') {
$('#uploadprogresswrapper input.stop').fadeOut();
@ -201,9 +441,9 @@ $(document).ready(function() {
};
// warn user not to leave the page while upload is in progress
$(window).bind('beforeunload', function(e) {
if ($.assocArraySize(uploadingFiles) > 0) {
return t('files','File upload is in progress. Leaving the page now will cancel the upload.');
$(window).on('beforeunload', function(e) {
if (OC.Upload.isProcessing()) {
return t('files', 'File upload is in progress. Leaving the page now will cancel the upload.');
}
});
@ -392,4 +632,5 @@ $(document).ready(function() {
$('#new>a').click();
});
});
window.file_upload_param = file_upload_param;
});

View File

@ -177,7 +177,7 @@ $(document).ready(function () {
FileActions.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
return OC.imagePath('core', 'actions/delete');
}, function (filename) {
if (Files.cancelUpload(filename)) {
if (OC.Upload.cancelUpload($('#dir').val(), filename)) {
if (filename.substr) {
filename = [filename];
}

View File

@ -680,153 +680,167 @@ $(document).ready(function(){
// handle upload events
var file_upload_start = $('#file_upload_start');
file_upload_start.on('fileuploaddrop', function(e, data) {
// only handle drop to dir if fileList exists
if ($('#fileList').length > 0) {
var dropTarget = $(e.originalEvent.target).closest('tr');
if(dropTarget && dropTarget.data('type') === 'dir') { // drag&drop upload to folder
var dirName = dropTarget.data('file');
// update folder in form
data.formData = function(form) {
var formArray = form.serializeArray();
// array index 0 contains the max files size
// array index 1 contains the request token
// array index 2 contains the directory
var parentDir = formArray[2]['value'];
if (parentDir === '/') {
formArray[2]['value'] += dirName;
} else {
formArray[2]['value'] += '/'+dirName;
}
return formArray;
OC.Upload.log('filelist handle fileuploaddrop', e, data);
var dropTarget = $(e.originalEvent.target).closest('tr');
if(dropTarget && dropTarget.data('type') === 'dir') { // drag&drop upload to folder
// remember as context
data.context = dropTarget;
var dir = dropTarget.data('file');
// update folder in form
data.formData = function(form) {
var formArray = form.serializeArray();
// array index 0 contains the max files size
// array index 1 contains the request token
// array index 2 contains the directory
var parentDir = formArray[2]['value'];
if (parentDir === '/') {
formArray[2]['value'] += dir;
} else {
formArray[2]['value'] += '/' + dir;
}
}
}
return formArray;
};
}
});
file_upload_start.on('fileuploadadd', function(e, data) {
// only add to fileList if it exists
if ($('#fileList').length > 0) {
OC.Upload.log('filelist handle fileuploadadd', e, data);
if(FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!=-1){//finish delete if we are uploading a deleted file
FileList.finishDelete(null, true); //delete file before continuing
//finish delete if we are uploading a deleted file
if(FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1){
FileList.finishDelete(null, true); //delete file before continuing
}
// add ui visualization to existing folder
if(data.context && data.context.data('type') === 'dir') {
// add to existing folder
// update upload counter ui
var uploadtext = data.context.find('.uploadtext');
var currentUploads = parseInt(uploadtext.attr('currentUploads'));
currentUploads += 1;
uploadtext.attr('currentUploads', currentUploads);
var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
if(currentUploads === 1) {
var img = OC.imagePath('core', 'loading.gif');
data.context.find('td.filename').attr('style','background-image:url('+img+')');
uploadtext.text(translatedText);
uploadtext.show();
} else {
uploadtext.text(translatedText);
}
}
// add ui visualization to existing folder or as new stand-alone file?
var dropTarget = $(e.originalEvent.target).closest('tr');
if(dropTarget && dropTarget.data('type') === 'dir') {
// add to existing folder
var dirName = dropTarget.data('file');
});
/*
* when file upload done successfully add row to filelist
* update counter when uploading to sub folder
*/
file_upload_start.on('fileuploaddone', function(e, data) {
OC.Upload.log('filelist handle fileuploaddone', e, data);
var response;
if (typeof data.result === 'string') {
response = data.result;
} else {
// fetch response from iframe
response = data.result[0].body.innerText;
}
var result=$.parseJSON(response);
// set dir context
data.context = $('tr').filterAttr('data-type', 'dir').filterAttr('data-file', dirName);
if(typeof result[0] !== 'undefined' && result[0].status === 'success') {
var file = result[0];
if (data.context && data.context.data('type') === 'dir') {
// update upload counter ui
var uploadtext = data.context.find('.uploadtext');
var currentUploads = parseInt(uploadtext.attr('currentUploads'));
currentUploads += 1;
currentUploads -= 1;
uploadtext.attr('currentUploads', currentUploads);
var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
if(currentUploads === 1) {
var img = OC.imagePath('core', 'loading.gif');
if(currentUploads === 0) {
var img = OC.imagePath('core', 'filetypes/folder.png');
data.context.find('td.filename').attr('style','background-image:url('+img+')');
uploadtext.text(translatedText);
uploadtext.show();
uploadtext.hide();
} else {
uploadtext.text(translatedText);
}
// update folder size
var size = parseInt(data.context.data('size'));
size += parseInt(file.size);
data.context.attr('data-size', size);
data.context.find('td.filesize').text(humanFileSize(size));
} else {
// add as stand-alone row to filelist
var uniqueName = getUniqueName(data.files[0].name);
var size=t('files','Pending');
if(data.files[0].size>=0){
var size=t('files', 'Pending');
if (data.files[0].size>=0){
size=data.files[0].size;
}
var date=new Date();
var param = {};
if ($('#publicUploadRequestToken').length) {
param.download_url = document.location.href + '&download&path=/' + $('#dir').val() + '/' + uniqueName;
param.download_url = document.location.href + '&download&path=/' + $('#dir').val() + '/' + file.name;
}
// create new file context
data.context = FileList.addFile(uniqueName,size,date,true,false,param);
//should the file exist in the list remove it
FileList.remove(file.name);
// create new file context
data.context = FileList.addFile(file.name, file.size, date, false, false, param);
// update file data
data.context.attr('data-mime',file.mime).attr('data-id',file.id);
var permissions = data.context.data('permissions');
if(permissions != file.permissions) {
data.context.attr('data-permissions', file.permissions);
data.context.data('permissions', file.permissions);
}
FileActions.display(data.context.find('td.filename'));
var path = getPathForPreview(file.name);
lazyLoadPreview(path, file.mime, function(previewpath){
data.context.find('td.filename').attr('style','background-image:url('+previewpath+')');
});
}
}
});
file_upload_start.on('fileuploaddone', function(e, data) {
// only update the fileList if it exists
if ($('#fileList').length > 0) {
var response;
if (typeof data.result === 'string') {
response = data.result;
} else {
// fetch response from iframe
response = data.result[0].body.innerText;
}
var result=$.parseJSON(response);
file_upload_start.on('fileuploadstop', function(e, data) {
OC.Upload.log('filelist handle fileuploadstop', e, data);
if(typeof result[0] !== 'undefined' && result[0].status === 'success') {
var file = result[0];
if (data.context.data('type') === 'file') {
// update file data
data.context.attr('data-mime',file.mime).attr('data-id',file.id);
var size = data.context.data('size');
if(size!=file.size){
data.context.attr('data-size', file.size);
data.context.find('td.filesize').text(humanFileSize(file.size));
}
var permissions = data.context.data('permissions');
if(permissions != file.permissions) {
data.context.attr('data-permissions', file.permissions);
data.context.data('permissions', file.permissions);
}
FileActions.display(data.context.find('td.filename'));
if (FileList.loadingDone) {
FileList.loadingDone(file.name, file.id);
}
} else {
// update upload counter ui
var uploadtext = data.context.find('.uploadtext');
var currentUploads = parseInt(uploadtext.attr('currentUploads'));
currentUploads -= 1;
uploadtext.attr('currentUploads', currentUploads);
if(currentUploads === 0) {
var img = OC.imagePath('core', 'filetypes/folder.png');
data.context.find('td.filename').attr('style','background-image:url('+img+')');
uploadtext.text('');
uploadtext.hide();
} else {
uploadtext.text(currentUploads + ' ' + t('files', 'files uploading'));
}
// update folder size
var size = parseInt(data.context.data('size'));
size += parseInt(file.size);
data.context.attr('data-size', size);
data.context.find('td.filesize').text(humanFileSize(size));
}
}
//if user pressed cancel hide upload chrome
if (data.errorThrown === 'abort') {
//cleanup uploading to a dir
var uploadtext = $('tr .uploadtext');
var img = OC.imagePath('core', 'filetypes/folder.png');
uploadtext.parents('td.filename').attr('style','background-image:url('+img+')');
uploadtext.fadeOut();
uploadtext.attr('currentUploads', 0);
}
});
file_upload_start.on('fileuploadfail', function(e, data) {
// only update the fileList if it exists
// cleanup files, error notification has been shown by fileupload code
var tr = data.context;
if (typeof tr === 'undefined') {
tr = $('tr').filterAttr('data-file', data.files[0].name);
}
if (tr.attr('data-type') === 'dir') {
OC.Upload.log('filelist handle fileuploadfail', e, data);
//if user pressed cancel hide upload chrome
if (data.errorThrown === 'abort') {
//cleanup uploading to a dir
var uploadtext = tr.find('.uploadtext');
var uploadtext = $('tr .uploadtext');
var img = OC.imagePath('core', 'filetypes/folder.png');
tr.find('td.filename').attr('style','background-image:url('+img+')');
uploadtext.text('');
uploadtext.hide(); //TODO really hide already
} else {
//remove file
tr.fadeOut();
tr.remove();
uploadtext.parents('td.filename').attr('style','background-image:url('+img+')');
uploadtext.fadeOut();
uploadtext.attr('currentUploads', 0);
}
});

View File

@ -1,31 +1,4 @@
var uploadingFiles = {};
Files={
cancelUpload:function(filename) {
if(uploadingFiles[filename]) {
uploadingFiles[filename].abort();
delete uploadingFiles[filename];
return true;
}
return false;
},
cancelUploads:function() {
$.each(uploadingFiles,function(index,file) {
if(typeof file['abort'] === 'function') {
file.abort();
var filename = $('tr').filterAttr('data-file',index);
filename.hide();
filename.find('input[type="checkbox"]').removeAttr('checked');
filename.removeClass('selected');
} else {
$.each(file,function(i,f) {
f.abort();
delete file[i];
});
}
delete uploadingFiles[index];
});
procesSelection();
},
updateMaxUploadFilesize:function(response) {
if(response == undefined) {
return;
@ -208,7 +181,8 @@ $(document).ready(function() {
// Trigger cancelling of file upload
$('#uploadprogresswrapper .stop').on('click', function() {
Files.cancelUploads();
OC.Upload.cancelUploads();
procesSelection();
});
// Show trash bin
@ -530,7 +504,7 @@ var folderDropOptions={
$('#notification').fadeIn();
}
} else {
OC.dialogs.alert(t('Error moving file'), t('core', 'Error'));
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
}
});
});
@ -568,7 +542,7 @@ var crumbDropOptions={
$('#notification').fadeIn();
}
} else {
OC.dialogs.alert(t('Error moving file'), t('core', 'Error'));
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
}
});
});
@ -658,15 +632,25 @@ function getPathForPreview(name) {
return path;
}
function lazyLoadPreview(path, mime, ready) {
getMimeIcon(mime,ready);
var x = $('#filestable').data('preview-x');
var y = $('#filestable').data('preview-y');
var previewURL = OC.Router.generate('core_ajax_preview', {file: encodeURIComponent(path), x:x, y:y});
$.get(previewURL, function() {
previewURL = previewURL.replace('(','%28');
previewURL = previewURL.replace(')','%29');
ready(previewURL + '&reload=true');
function lazyLoadPreview(path, mime, ready, width, height) {
// get mime icon url
getMimeIcon(mime, function(iconURL) {
ready(iconURL); // set mimeicon URL
// now try getting a preview thumbnail URL
if ( ! width ) {
width = $('#filestable').data('preview-x');
}
if ( ! height ) {
height = $('#filestable').data('preview-y');
}
var previewURL = OC.Router.generate('core_ajax_preview', {file: encodeURIComponent(path), x:width, y:height});
$.get(previewURL, function() {
previewURL = previewURL.replace('(', '%28');
previewURL = previewURL.replace(')', '%29');
//set preview thumbnail URL
ready(previewURL + '&reload=true');
});
});
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
* jQuery Iframe Transport Plugin 1.3
* jQuery Iframe Transport Plugin 1.7
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
@ -30,27 +30,45 @@
// The iframe transport accepts three additional options:
// options.fileInput: a jQuery collection of file input fields
// options.paramName: the parameter name for the file form data,
// overrides the name property of the file input field(s)
// overrides the name property of the file input field(s),
// can be a string or an array of strings.
// options.formData: an array of objects with name and value properties,
// equivalent to the return data of .serializeArray(), e.g.:
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
$.ajaxTransport('iframe', function (options) {
if (options.async && (options.type === 'POST' || options.type === 'GET')) {
if (options.async) {
var form,
iframe;
iframe,
addParamChar;
return {
send: function (_, completeCallback) {
form = $('<form style="display:none;"></form>');
form.attr('accept-charset', options.formAcceptCharset);
addParamChar = /\?/.test(options.url) ? '&' : '?';
// XDomainRequest only supports GET and POST:
if (options.type === 'DELETE') {
options.url = options.url + addParamChar + '_method=DELETE';
options.type = 'POST';
} else if (options.type === 'PUT') {
options.url = options.url + addParamChar + '_method=PUT';
options.type = 'POST';
} else if (options.type === 'PATCH') {
options.url = options.url + addParamChar + '_method=PATCH';
options.type = 'POST';
}
// javascript:false as initial iframe src
// prevents warning popups on HTTPS in IE6.
// IE versions below IE8 cannot set the name property of
// elements that have already been added to the DOM,
// so we set the name along with the iframe HTML markup:
counter += 1;
iframe = $(
'<iframe src="javascript:false;" name="iframe-transport-' +
(counter += 1) + '"></iframe>'
counter + '"></iframe>'
).bind('load', function () {
var fileInputClones;
var fileInputClones,
paramNames = $.isArray(options.paramName) ?
options.paramName : [options.paramName];
iframe
.unbind('load')
.bind('load', function () {
@ -79,7 +97,12 @@
// (happens on form submits to iframe targets):
$('<iframe src="javascript:false;"></iframe>')
.appendTo(form);
form.remove();
window.setTimeout(function () {
// Removing the form in a setTimeout call
// allows Chrome's developer tools to display
// the response result
form.remove();
}, 0);
});
form
.prop('target', iframe.prop('name'))
@ -101,8 +124,11 @@
return fileInputClones[index];
});
if (options.paramName) {
options.fileInput.each(function () {
$(this).prop('name', options.paramName);
options.fileInput.each(function (index) {
$(this).prop(
'name',
paramNames[index] || options.paramName
);
});
}
// Appending the file input fields to the hidden form
@ -144,22 +170,36 @@
});
// The iframe transport returns the iframe content document as response.
// The following adds converters from iframe to text, json, html, and script:
// The following adds converters from iframe to text, json, html, xml
// and script.
// Please note that the Content-Type for JSON responses has to be text/plain
// or text/html, if the browser doesn't include application/json in the
// Accept header, else IE will show a download dialog.
// The Content-Type for XML responses on the other hand has to be always
// application/xml or text/xml, so IE properly parses the XML response.
// See also
// https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
$.ajaxSetup({
converters: {
'iframe text': function (iframe) {
return $(iframe[0].body).text();
return iframe && $(iframe[0].body).text();
},
'iframe json': function (iframe) {
return $.parseJSON($(iframe[0].body).text());
return iframe && $.parseJSON($(iframe[0].body).text());
},
'iframe html': function (iframe) {
return $(iframe[0].body).html();
return iframe && $(iframe[0].body).html();
},
'iframe xml': function (iframe) {
var xmlDoc = iframe && iframe[0];
return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
$.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
$(xmlDoc.body).html());
},
'iframe script': function (iframe) {
return $.globalEval($(iframe[0].body).text());
return iframe && $.globalEval($(iframe[0].body).text());
}
}
});
}));
}));

View File

@ -0,0 +1,26 @@
<div id="{dialog_name}" title="{title}" class="fileexists">
<span class="why">{why}<!-- Which files do you want to keep --></span><br/>
<span class="what">{what}<!-- If you select both versions, the copied file will have a number added to its name. --></span><br/>
<br/>
<table>
<th><label><input class="allnewfiles" type="checkbox" />New Files<span class="count"></span></label></th>
<th><label><input class="allexistingfiles" type="checkbox" />Already existing files<span class="count"></span></label></th>
</table>
<div class="conflicts">
<div class="template">
<div class="filename"></div>
<div class="replacement">
<input type="checkbox" />
<span class="svg icon"></span>
<div class="mtime"></div>
<div class="size"></div>
</div>
<div class="original">
<input type="checkbox" />
<span class="svg icon"></span>
<div class="mtime"></div>
<div class="size"></div>
</div>
</div>
</div>
</div>

View File

@ -62,6 +62,9 @@ $(document).ready(function() {
$('#controls').append($('#additional_controls div#uploadprogresswrapper'));
// Cancel upload trigger
$('#cancel_upload_button').click(Files.cancelUploads);
$('#cancel_upload_button').click(function() {
OC.Upload.cancelUploads();
procesSelection();
});
});

View File

@ -170,6 +170,7 @@ if (isset($path)) {
$tmpl->assign('dir', $getPath);
OCP\Util::addStyle('files', 'files');
OCP\Util::addStyle('files', 'upload');
OCP\Util::addScript('files', 'files');
OCP\Util::addScript('files', 'filelist');
OCP\Util::addscript('files', 'keyboardshortcuts');

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16px" width="16px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<path style="block-progression:tb;color:#000000;text-transform:none;text-indent:0" d="m4 12 8-4-7.989-4z"/>
</svg>

After

Width:  |  Height:  |  Size: 395 B

View File

@ -103,6 +103,9 @@
}
$.each(value, function(idx, val) {
var $button = $('<button>').text(val.text);
if (val.classes) {
$button.addClass(val.classes);
}
if(val.defaultButton) {
$button.addClass('primary');
self.$defaultButton = $button;

View File

@ -220,6 +220,245 @@ var OCdialogs = {
}
});
},
_fileexistsshown: false,
/**
* Displays file exists dialog
* @param {object} data upload object
* @param {object} original file with name, size and mtime
* @param {object} replacement file with name, size and mtime
* @param {object} controller with onCancel, onSkip, onReplace and onRename methods
*/
fileexists:function(data, original, replacement, controller) {
var self = this;
var getCroppedPreview = function(file) {
var deferred = new $.Deferred();
// Only process image files.
var type = file.type.split('/').shift();
if (window.FileReader && type === 'image') {
var reader = new FileReader();
reader.onload = function (e) {
var blob = new Blob([e.target.result]);
window.URL = window.URL || window.webkitURL;
var originalUrl = window.URL.createObjectURL(blob);
var image = new Image();
image.src = originalUrl;
image.onload = function () {
var url = crop(image);
deferred.resolve(url);
}
};
reader.readAsArrayBuffer(file);
} else {
deferred.reject();
}
return deferred;
};
var crop = function(img) {
var canvas = document.createElement('canvas'),
width = img.width,
height = img.height,
x, y, size;
// calculate the width and height, constraining the proportions
if (width > height) {
y = 0;
x = (width - height) / 2;
} else {
y = (height - width) / 2;
x = 0;
}
size = Math.min(width, height);
// resize the canvas and draw the image data into it
canvas.width = 64;
canvas.height = 64;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, x, y, size, size, 0, 0, 64, 64);
return canvas.toDataURL("image/png", 0.7);
};
var addConflict = function(conflicts, original, replacement) {
var conflict = conflicts.find('.template').clone().removeClass('template').addClass('conflict');
conflict.data('data',data);
conflict.find('.filename').text(original.name);
conflict.find('.original .size').text(humanFileSize(original.size));
conflict.find('.original .mtime').text(formatDate(original.mtime*1000));
// ie sucks
if (replacement.size && replacement.lastModifiedDate) {
conflict.find('.replacement .size').text(humanFileSize(replacement.size));
conflict.find('.replacement .mtime').text(formatDate(replacement.lastModifiedDate));
}
var path = getPathForPreview(original.name);
lazyLoadPreview(path, original.type, function(previewpath){
conflict.find('.original .icon').css('background-image','url('+previewpath+')');
}, 96, 96);
getCroppedPreview(replacement).then(
function(path){
conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
}, function(){
getMimeIcon(replacement.type,function(path){
conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
});
}
);
conflicts.append(conflict);
//set more recent mtime bold
// ie sucks
if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() > original.mtime*1000) {
conflict.find('.replacement .mtime').css('font-weight', 'bold');
} else if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() < original.mtime*1000) {
conflict.find('.original .mtime').css('font-weight', 'bold');
} else {
//TODO add to same mtime collection?
}
// set bigger size bold
if (replacement.size && replacement.size > original.size) {
conflict.find('.replacement .size').css('font-weight', 'bold');
} else if (replacement.size && replacement.size < original.size) {
conflict.find('.original .size').css('font-weight', 'bold');
} else {
//TODO add to same size collection?
}
//TODO show skip action for files with same size and mtime in bottom row
};
//var selection = controller.getSelection(data.originalFiles);
//if (selection.defaultAction) {
// controller[selection.defaultAction](data);
//} else {
var dialog_name = 'oc-dialog-fileexists-content';
var dialog_id = '#' + dialog_name;
if (this._fileexistsshown) {
// add conflict
var conflicts = $(dialog_id+ ' .conflicts');
addConflict(conflicts, original, replacement);
var count = $(dialog_id+ ' .conflict').length;
var title = n('files',
'{count} file conflict',
'{count} file conflicts',
count,
{count:count}
);
$(dialog_id).parent().children('.oc-dialog-title').text(title);
//recalculate dimensions
$(window).trigger('resize');
} else {
//create dialog
this._fileexistsshown = true;
$.when(this._getFileExistsTemplate()).then(function($tmpl) {
var title = t('files','One file conflict');
var $dlg = $tmpl.octemplate({
dialog_name: dialog_name,
title: title,
type: 'fileexists',
why: t('files','Which files do you want to keep?'),
what: t('files','If you select both versions, the copied file will have a number added to its name.')
});
$('body').append($dlg);
var conflicts = $($dlg).find('.conflicts');
addConflict(conflicts, original, replacement);
buttonlist = [{
text: t('core', 'Cancel'),
classes: 'cancel',
click: function(){
if ( typeof controller.onCancel !== 'undefined') {
controller.onCancel(data);
}
$(dialog_id).ocdialog('close');
}
},
{
text: t('core', 'Continue'),
classes: 'continue',
click: function(){
if ( typeof controller.onContinue !== 'undefined') {
controller.onContinue($(dialog_id + ' .conflict'));
}
$(dialog_id).ocdialog('close');
}
}];
$(dialog_id).ocdialog({
width: 500,
closeOnEscape: true,
modal: true,
buttons: buttonlist,
closeButton: null,
close: function(event, ui) {
self._fileexistsshown = false;
$(this).ocdialog('destroy').remove();
}
});
$(dialog_id).css('height','auto');
//add checkbox toggling actions
$(dialog_id).find('.allnewfiles').on('click', function() {
var checkboxes = $(dialog_id).find('.conflict .replacement input[type="checkbox"]');
checkboxes.prop('checked', $(this).prop('checked'));
});
$(dialog_id).find('.allexistingfiles').on('click', function() {
var checkboxes = $(dialog_id).find('.conflict .original input[type="checkbox"]');
checkboxes.prop('checked', $(this).prop('checked'));
});
$(dialog_id).find('.conflicts').on('click', '.replacement,.original', function() {
var checkbox = $(this).find('input[type="checkbox"]');
checkbox.prop('checked', !checkbox.prop('checked'));
});
$(dialog_id).find('.conflicts').on('click', 'input[type="checkbox"]', function() {
var checkbox = $(this);
checkbox.prop('checked', !checkbox.prop('checked'));
});
//update counters
$(dialog_id).on('click', '.replacement,.allnewfiles', function() {
var count = $(dialog_id).find('.conflict .replacement input[type="checkbox"]:checked').length;
if (count === $(dialog_id+ ' .conflict').length) {
$(dialog_id).find('.allnewfiles').prop('checked', true);
$(dialog_id).find('.allnewfiles + .count').text(t('files','(all selected)'));
} else if (count > 0) {
$(dialog_id).find('.allnewfiles').prop('checked', false);
$(dialog_id).find('.allnewfiles + .count').text(t('files','({count} selected)',{count:count}));
} else {
$(dialog_id).find('.allnewfiles').prop('checked', false);
$(dialog_id).find('.allnewfiles + .count').text('');
}
});
$(dialog_id).on('click', '.original,.allexistingfiles', function(){
var count = $(dialog_id).find('.conflict .original input[type="checkbox"]:checked').length;
if (count === $(dialog_id+ ' .conflict').length) {
$(dialog_id).find('.allexistingfiles').prop('checked', true);
$(dialog_id).find('.allexistingfiles + .count').text(t('files','(all selected)'));
} else if (count > 0) {
$(dialog_id).find('.allexistingfiles').prop('checked', false);
$(dialog_id).find('.allexistingfiles + .count').text(t('files','({count} selected)',{count:count}));
} else {
$(dialog_id).find('.allexistingfiles').prop('checked', false);
$(dialog_id).find('.allexistingfiles + .count').text('');
}
});
})
.fail(function() {
alert(t('core', 'Error loading file exists template'));
});
}
//}
},
_getFilePickerTemplate: function() {
var defer = $.Deferred();
if(!this.$filePickerTemplate) {
@ -253,6 +492,22 @@ var OCdialogs = {
}
return defer.promise();
},
_getFileExistsTemplate: function () {
var defer = $.Deferred();
if (!this.$fileexistsTemplate) {
var self = this;
$.get(OC.filePath('files', 'templates', 'fileexists.html'), function (tmpl) {
self.$fileexistsTemplate = $(tmpl);
defer.resolve(self.$fileexistsTemplate);
})
.fail(function () {
defer.reject();
});
} else {
defer.resolve(this.$fileexistsTemplate);
}
return defer.promise();
},
_getFileList: function(dir, mimeType) {
if (typeof(mimeType) === "string") {
mimeType = [mimeType];