From 7ee9ba4423d34b8de1292b919962d99ceedfeffa Mon Sep 17 00:00:00 2001 From: NastuzziSamy Date: Mon, 5 Feb 2018 14:28:24 +0100 Subject: [PATCH 01/25] Handle translation --- l10n/fr.js | 18 ++++++++++++++++++ l10n/fr.json | 16 ++++++++++++++++ templates/main.php | 38 +++++++++++++++++++------------------- 3 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 l10n/fr.js create mode 100644 l10n/fr.json diff --git a/l10n/fr.js b/l10n/fr.js new file mode 100644 index 00000000..4661e982 --- /dev/null +++ b/l10n/fr.js @@ -0,0 +1,18 @@ +OC.L10N.register( + "flowupload", { + "FlowUpload" : "Envoyer des fichiers", + "Select File": "Sélectionnez un fichier", + "... or drag and drop your files here" : "... ou déposez ici vos fichiers", + "Transfers" : "Transfères", + "Upload" : "Envoyer", + "Pause" : "Pause", + "Cancel" : "Annuler", + "Uploading" : "Envoie en cours", + "Size" : "Taille", + "Progress" : "Progression", + "Retry" : "Réessayer", + "Completed" : "Complété", + "The files will be saved in your home directory" : "Les fichiers seront sauvegardés dans votre dossier personnel" + }, + "nplurals=2; plural=(n > 1);" +); diff --git a/l10n/fr.json b/l10n/fr.json new file mode 100644 index 00000000..ccc9d81a --- /dev/null +++ b/l10n/fr.json @@ -0,0 +1,16 @@ +{ "translations": { + "FlowUpload" : "Envoyer des fichiers", + "Select File": "Sélectionnez un fichier", + "... or drag and drop your files here" : "... ou déposez ici vos fichiers", + "Transfers" : "Transfères", + "Upload" : "Envoyer", + "Pause" : "Pause", + "Cancel" : "Annuler", + "Uploading" : "Envoie en cours", + "Size" : "Taille", + "Progress" : "Progression", + "Retry" : "Réessayer", + "Completed" : "Complété", + "The files will be saved in your home directory." : "Les fichiers seront sauvegardés dans votre dossier personnel." +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} diff --git a/templates/main.php b/templates/main.php index 198f296c..327c3b63 100644 --- a/templates/main.php +++ b/templates/main.php @@ -1,31 +1,31 @@
- Select File - Select Folder + t('Select File'); ?> + t('Select Folder'); ?>
... or drag and drop your files here + ng-class="class">t('... or drag and drop your files here'); ?>

-

Transfers

+

t('Transfers'); ?>

- Upload - Pause - Cancel - Size: {{$flow.getSize() | bytes}} - Uploading... + t('Upload'); ?> + t('Pause'); ?> + t('Cancel'); ?> + t('Size'); ?>: {{$flow.getSize() | bytes}} + t('Uploading'); ?>...

- - - + + + @@ -37,22 +37,22 @@ - Completed + t('Completed'); ?>
#NameSizeProgresst('Name'); ?>t('Size'); ?>t('Progress'); ?>
-

The files will be saved in your home directory.

-
\ No newline at end of file +

t('The files will be saved in your home directory.'); ?>

+ From 494f8982a7ceeacf4b9a7f02ac4a2099276378fd Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Wed, 7 Feb 2018 16:10:25 +0100 Subject: [PATCH 02/25] Updated ng-flow version (2.7.7), fixed regex for character sanitation --- README.md | 10 +- ajax/upload.php | 4 +- appinfo/info.xml | 2 +- appinfo/version | 2 +- js/ng-flow-standalone.js | 1927 +++++++++++++++++++++++++++++++++++++- 5 files changed, 1938 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index edd9852a..b47cec4b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Large File Uploader to upload files and folders* with unlimited size. It uses An Early alpha state, not approved for security. This app does not support server side encryption at the moment. -*) Folder upload only works in Chrome browser +*) Folder upload only works with Firefox, Edge and Chromium-based (Google Chrome, Opera, Vivaldi) browsers ![](https://raw.githubusercontent.com/e-alfred/flowupload/master/appinfo/flowupload.gif) @@ -12,6 +12,14 @@ Installation ------------ Clone the contents of the repository into your `apps` directory and rename it to `flowupload`, if necessary. Otherwise simply download it from the Nextcloud app store within your Nextcloud instance: https://apps.nextcloud.com/apps/flowupload +Version information +------------- +Version 0.0.7: +- Added localisation (thanks @NastuzziSamy) +- Added folder upload support for Firefox, Chrome and Edge +- Updated ng-flow-standalone.js version to 2.7.7 +- Added character sanitation to prevent upload of files with unsupported characters (especially foreign languages) + Licenses ------- Flowupload: [GNU Affero General Public License](http://www.gnu.org/licenses/agpl-3.0.html) diff --git a/ajax/upload.php b/ajax/upload.php index 72d4ff46..877972f7 100644 --- a/ajax/upload.php +++ b/ajax/upload.php @@ -20,7 +20,7 @@ // Filter paths $path = preg_replace('/(\.\.\/|~|\/\/)/i', '', $request->getRelativePath()); -$path = preg_replace('/[^a-z0-9äöüß \(\)\.\-_\/]/i', '', $path); +$path = preg_replace('/([^a-zA-Z0-9]+)/', '', $path); $path = trim($path, '/'); // Skip existing files // ToDo: Check if file size changed? @@ -70,4 +70,4 @@ // ToDo: error handling //OCP\JSON::error(array("data" => array("message" => $msg))); //OCP\Util::writeLog('flowupload', "Failed to create file: " . $path, OC_Log::ERROR); -?> \ No newline at end of file +?> diff --git a/appinfo/info.xml b/appinfo/info.xml index 4af70522..f680e3fb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -3,7 +3,7 @@ flowupload Flow Upload Provides functions to upload large files with your HTML5-Browser. - 0.0.6 + 0.0.7 AGPL Patrick Schwarz e-alfred diff --git a/appinfo/version b/appinfo/version index 1750564f..5a5831ab 100644 --- a/appinfo/version +++ b/appinfo/version @@ -1 +1 @@ -0.0.6 +0.0.7 diff --git a/js/ng-flow-standalone.js b/js/ng-flow-standalone.js index c30c6f0f..c80610c3 100644 --- a/js/ng-flow-standalone.js +++ b/js/ng-flow-standalone.js @@ -1,2 +1,1925 @@ -/*! ng-flow 2.6.1 */ -!function(a,b,c){"use strict";function d(b){if(this.support=!("undefined"==typeof File||"undefined"==typeof Blob||"undefined"==typeof FileList||!Blob.prototype.slice&&!Blob.prototype.webkitSlice&&!Blob.prototype.mozSlice),this.support){this.supportDirectory=/WebKit/.test(a.navigator.userAgent),this.files=[],this.defaults={chunkSize:1048576,forceChunkSize:!1,simultaneousUploads:3,singleFile:!1,fileParameterName:"file",progressCallbacksInterval:500,speedSmoothingFactor:.1,query:{},headers:{},withCredentials:!1,preprocess:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1},this.opts={},this.events={};var c=this;this.onDrop=function(a){c.opts.onDropStopPropagation&&a.stopPropagation(),a.preventDefault();var b=a.dataTransfer;b.items&&b.items[0]&&b.items[0].webkitGetAsEntry?c.webkitReadDataTransfer(a):c.addFiles(b.files,a)},this.preventEvent=function(a){a.preventDefault()},this.opts=d.extend({},this.defaults,b||{})}}function e(a,b){this.flowObj=a,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=a.generateUniqueIdentifier(b),this.chunks=[],this.paused=!1,this.error=!1,this.averageSpeed=0,this.currentSpeed=0,this._lastProgressCallback=Date.now(),this._prevUploadedSize=0,this._prevProgress=0,this.bootstrap()}function f(a,b,c){this.flowObj=a,this.fileObj=b,this.fileObjSize=b.size,this.offset=c,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.loaded=0,this.total=0;var d=this.flowObj.opts.chunkSize;this.startByte=this.offset*d,this.endByte=Math.min(this.fileObjSize,(this.offset+1)*d),this.xhr=null,this.fileObjSize-this.endByte-1&&a.splice(c,1)}function h(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function i(a,b){setTimeout(a.bind(b),0)}function j(a){return k(arguments,function(b){b!==a&&k(b,function(b,c){a[c]=b})}),a}function k(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()&&0===a.chunks[0].preprocessState?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(k(this.files,function(a){return a.paused||k(a.chunks,function(a){return"pending"===a.status()&&0===a.preprocessState?(a.send(),b=!0,!1):void 0}),b?!1:void 0}),b)return!0;var c=!1;return k(this.files,function(a){return a.isComplete()?void 0:(c=!0,!1)}),c||a||i(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),j(f.style,{visibility:"hidden",position:"absolute"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),k(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){g.addFiles(a.target.files,a),a.target.value=""},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){a.addEventListener("dragover",this.preventEvent,!1),a.addEventListener("dragenter",this.preventEvent,!1),a.addEventListener("drop",this.onDrop,!1)},this)},unAssignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){a.removeEventListener("dragover",this.preventEvent),a.removeEventListener("dragenter",this.preventEvent),a.removeEventListener("drop",this.onDrop)},this)},isUploading:function(){var a=!1;return k(this.files,function(b){return b.isUploading()?(a=!0,!1):void 0}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return k(this.files,function(d){k(d.chunks,function(d){return"uploading"===d.status()&&(a++,a>=c)?(b=!1,!1):void 0})}),b&&a},upload:function(){var a=this._shouldUploadNext();if(a!==!1){this.fire("uploadStart");for(var b=!1,c=1;c<=this.opts.simultaneousUploads-a;c++)b=this.uploadNextChunk(!0)||b;b||i(function(){this.fire("complete")},this)}},resume:function(){k(this.files,function(a){a.resume()})},pause:function(){k(this.files,function(a){a.pause()})},cancel:function(){for(var a=this.files.length-1;a>=0;a--)this.files[a].cancel()},progress:function(){var a=0,b=0;return k(this.files,function(c){a+=c.progress()*c.size,b+=c.size}),b>0?a/b:0},addFile:function(a,b){this.addFiles([a],b)},addFiles:function(a,b){var c=[];k(a,function(a){if((!l||l&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)&&!this.getFromUniqueIdentifier(this.generateUniqueIdentifier(a))){var d=new e(this,a);this.fire("fileAdded",d,b)&&c.push(d)}},this),this.fire("filesAdded",c,b)&&k(c,function(a){this.opts.singleFile&&this.files.length>0&&this.removeFile(this.files[0]),this.files.push(a)},this),this.fire("filesSubmitted",c,b)},removeFile:function(a){for(var b=this.files.length-1;b>=0;b--)this.files[b]===a&&(this.files.splice(b,1),a.abort())},getFromUniqueIdentifier:function(a){var b=!1;return k(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return k(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return k(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return k(this.files,function(c){c.paused||c.error||(a+=c.size-c.sizeUploaded(),b+=c.averageSpeed)}),a&&!b?Number.POSITIVE_INFINITY:a||b?Math.floor(a/b):0}},e.prototype={measureSpeed:function(){var a=Date.now()-this._lastProgressCallback;if(a){var b=this.flowObj.opts.speedSmoothingFactor,c=this.sizeUploaded();this.currentSpeed=Math.max((c-this._prevUploadedSize)/a*1e3,0),this.averageSpeed=b*this.currentSpeed+(1-b)*this.averageSpeed,this._prevUploadedSize=c}},chunkEvent:function(a,b,c){switch(b){case"progress":if(Date.now()-this._lastProgressCallbackc;c++)this.chunks.push(new f(this.flowObj,this,c))},progress:function(){if(this.error)return 1;if(1===this.chunks.length)return this._prevProgress=Math.max(this._prevProgress,this.chunks[0].progress()),this._prevProgress;var a=0;k(this.chunks,function(b){a+=b.progress()*(b.endByte-b.startByte)});var b=a/this.size;return this._prevProgress=Math.max(this._prevProgress,b>.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return k(this.chunks,function(b){return"uploading"===b.status()?(a=!0,!1):void 0}),a},isComplete:function(){var a=!1;return k(this.chunks,function(b){var c=b.status();return"pending"===c||"uploading"===c||1===b.preprocessState?(a=!0,!1):void 0}),!a},sizeUploaded:function(){var a=0;return k(this.chunks,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){if(this.paused||this.error)return 0;var a=this.size-this.sizeUploaded();return a&&!this.averageSpeed?Number.POSITIVE_INFINITY:a||this.averageSpeed?Math.floor(a/this.averageSpeed):0},getType:function(){return this.file.type&&this.file.type.split("/")[1]},getExtension:function(){return this.name.substr((~-this.name.lastIndexOf(".")>>>0)+2).toLowerCase()}},f.prototype={getParams:function(){return{flowChunkNumber:this.offset+1,flowChunkSize:this.flowObj.opts.chunkSize,flowCurrentChunkSize:this.endByte-this.startByte,flowTotalSize:this.fileObjSize,flowIdentifier:this.fileObj.uniqueIdentifier,flowFilename:this.fileObj.name,flowRelativePath:this.fileObj.relativePath,flowTotalChunks:this.fileObj.chunks.length}},getTarget:function(a,b){return a+=a.indexOf("?")<0?"?":"&",a+b.join("&")},test:function(){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.testHandler,!1),this.xhr.addEventListener("error",this.testHandler,!1);var a=h(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.preprocessState=2,this.send()},send:function(){var a=this.flowObj.opts.preprocess;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1;var b=this.fileObj.file.slice?"slice":this.fileObj.file.mozSlice?"mozSlice":this.fileObj.file.webkitSlice?"webkitSlice":"slice",c=this.fileObj.file[b](this.startByte,this.endByte,this.fileObj.file.type);this.xhr=new XMLHttpRequest,this.xhr.upload.addEventListener("progress",this.progressHandler,!1),this.xhr.addEventListener("load",this.doneHandler,!1),this.xhr.addEventListener("error",this.doneHandler,!1);var d=h(this.flowObj.opts.uploadMethod,this.fileObj,this),e=this.prepareXhrRequest(d,!1,this.flowObj.opts.method,c);this.xhr.send(e)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return this.pendingRetry||1===this.preprocessState?"uploading":this.xhr?this.xhr.readyState<4?"uploading":this.flowObj.opts.successStatuses.indexOf(this.xhr.status)>-1?"success":this.flowObj.opts.permanentErrors.indexOf(this.xhr.status)>-1||!a&&this.retries>=this.flowObj.opts.maxChunkRetries?"error":(this.abort(),"pending"):"pending"},message:function(){return this.xhr?this.xhr.responseText:""},progress:function(){if(this.pendingRetry)return 0;var a=this.status();return"success"===a||"error"===a?1:"pending"===a?0:this.total>0?this.loaded/this.total:0},sizeUploaded:function(){var a=this.endByte-this.startByte;return"success"!==this.status()&&(a=this.progress()*a),a},prepareXhrRequest:function(a,b,c,d){var e=h(this.flowObj.opts.query,this.fileObj,this,b);e=j(this.getParams(),e);var f=h(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var i=[];k(e,function(a,b){i.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,i),g=d||null}else g=new FormData,k(e,function(a,b){g.append(b,a)}),g.append(this.flowObj.opts.fileParameterName,d,this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,k(h(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=h,d.extend=j,d.each=k,d.FlowFile=e,d.FlowChunk=f,d.version="2.9.0","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}(window,document),angular.module("flow.provider",[]).provider("flowFactory",function(){"use strict";this.defaults={},this.factory=function(a){return new Flow(a)},this.events=[],this.on=function(a,b){this.events.push([a,b])},this.$get=function(){var a=this.factory,b=this.defaults,c=this.events;return{create:function(d){var e=a(angular.extend({},b,d));return angular.forEach(c,function(a){e.on(a[0],a[1])}),e}}}}),angular.module("flow.init",["flow.provider"]).controller("flowCtrl",["$scope","$attrs","$parse","flowFactory",function(a,b,c,d){var e=angular.extend({},a.$eval(b.flowInit)),f=a.$eval(b.flowObject)||d.create(e),g=function(b){var c=Array.prototype.slice.call(arguments);c.shift();var d=a.$broadcast.apply(a,["flow::"+b,f].concat(c));return{progress:1,filesSubmitted:1,fileSuccess:1,fileError:1,complete:1}[b]&&a.$apply(),d.defaultPrevented?!1:void 0};f.on("catchAll",g),a.$on("$destroy",function(){f.off("catchAll",g)}),a.$flow=f,b.hasOwnProperty("flowName")&&(c(b.flowName).assign(a,f),a.$on("$destroy",function(){c(b.flowName).assign(a)}))}]).directive("flowInit",[function(){return{scope:!0,controller:"flowCtrl"}}]),angular.module("flow.btn",["flow.init"]).directive("flowBtn",[function(){return{restrict:"EA",scope:!1,require:"^flowInit",link:function(a,b,c){var d=c.hasOwnProperty("flowDirectory"),e=c.hasOwnProperty("flowSingleFile"),f=c.hasOwnProperty("flowAttrs")&&a.$eval(c.flowAttrs);a.$flow.assignBrowse(b,d,e,f)}}}]),angular.module("flow.dragEvents",["flow.init"]).directive("flowPreventDrop",function(){return{scope:!1,link:function(a,b){b.bind("drop dragover",function(a){a.preventDefault()})}}}).directive("flowDragEnter",["$timeout",function(a){return{scope:!1,link:function(b,c,d){function e(a){var b=!1,c=a.dataTransfer||a.originalEvent.dataTransfer;return angular.forEach(c&&c.types,function(a){"Files"===a&&(b=!0)}),b}var f,g=!1;c.bind("dragover",function(c){e(c)&&(g||(b.$apply(d.flowDragEnter),g=!0),a.cancel(f),c.preventDefault())}),c.bind("dragleave drop",function(){a.cancel(f),f=a(function(){b.$eval(d.flowDragLeave),f=null,g=!1},100)})}}}]),angular.module("flow.drop",["flow.init"]).directive("flowDrop",function(){return{scope:!1,require:"^flowInit",link:function(a,b,c){function d(){a.$flow.assignDrop(b)}function e(){a.$flow.unAssignDrop(b)}c.flowDropEnabled?a.$watch(c.flowDropEnabled,function(a){a?d():e()}):d()}}}),!function(a){"use strict";function b(a){return a.charAt(0).toUpperCase()+a.slice(1)}var c=a.module("flow.events",["flow.init"]),d={fileSuccess:["$file","$message"],fileProgress:["$file"],fileAdded:["$file","$event"],filesAdded:["$files","$event"],filesSubmitted:["$files","$event"],fileRetry:["$file"],fileError:["$file","$message"],uploadStart:[],complete:[],progress:[],error:["$message","$file"]};a.forEach(d,function(d,e){var f="flow"+b(e);"flowUploadStart"==f&&(f="flowUploadStarted"),c.directive(f,[function(){return{require:"^flowInit",controller:["$scope","$attrs",function(b,c){b.$on("flow::"+e,function(){var e=Array.prototype.slice.call(arguments),g=e.shift();if(b.$flow===e.shift()){var h={};a.forEach(d,function(a,b){h[a]=e[b]}),b.$eval(c[f],h)===!1&&g.preventDefault()}})}]}}])})}(angular),angular.module("flow.img",["flow.init"]).directive("flowImg",[function(){return{scope:!1,require:"^flowInit",link:function(a,b,c){var d=c.flowImg;a.$watch(d,function(b){if(b){var d=new FileReader;d.readAsDataURL(b.file),d.onload=function(b){a.$apply(function(){c.$set("src",b.target.result)})}}})}}}]),angular.module("flow.transfers",["flow.init"]).directive("flowTransfers",[function(){return{scope:!0,require:"^flowInit",link:function(a){a.transfers=a.$flow.files}}}]),angular.module("flow",["flow.provider","flow.init","flow.events","flow.btn","flow.drop","flow.transfers","flow.img","flow.dragEvents"]); \ No newline at end of file +/** + * @license MIT + */ +(function(window, document, undefined) {'use strict'; + // ie10+ + var ie10plus = window.navigator.msPointerEnabled; + /** + * Flow.js is a library providing multiple simultaneous, stable and + * resumable uploads via the HTML5 File API. + * @param [opts] + * @param {number} [opts.chunkSize] + * @param {bool} [opts.forceChunkSize] + * @param {number} [opts.simultaneousUploads] + * @param {bool} [opts.singleFile] + * @param {string} [opts.fileParameterName] + * @param {number} [opts.progressCallbacksInterval] + * @param {number} [opts.speedSmoothingFactor] + * @param {Object|Function} [opts.query] + * @param {Object|Function} [opts.headers] + * @param {bool} [opts.withCredentials] + * @param {Function} [opts.preprocess] + * @param {string} [opts.method] + * @param {string|Function} [opts.testMethod] + * @param {string|Function} [opts.uploadMethod] + * @param {bool} [opts.prioritizeFirstAndLastChunk] + * @param {bool} [opts.allowDuplicateUploads] + * @param {string|Function} [opts.target] + * @param {number} [opts.maxChunkRetries] + * @param {number} [opts.chunkRetryInterval] + * @param {Array.} [opts.permanentErrors] + * @param {Array.} [opts.successStatuses] + * @param {Function} [opts.initFileFn] + * @param {Function} [opts.readFileFn] + * @param {Function} [opts.generateUniqueIdentifier] + * @constructor + */ + function Flow(opts) { + /** + * Supported by browser? + * @type {boolean} + */ + this.support = ( + typeof File !== 'undefined' && + typeof Blob !== 'undefined' && + typeof FileList !== 'undefined' && + ( + !!Blob.prototype.slice || !!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || + false + ) // slicing files support + ); + + if (!this.support) { + return ; + } + + /** + * Check if directory upload is supported + * @type {boolean} + */ + this.supportDirectory = ( + /Chrome/.test(window.navigator.userAgent) || + /Firefox/.test(window.navigator.userAgent) || + /Edge/.test(window.navigator.userAgent) + ); + + /** + * List of FlowFile objects + * @type {Array.} + */ + this.files = []; + + /** + * Default options for flow.js + * @type {Object} + */ + this.defaults = { + chunkSize: 1024 * 1024, + forceChunkSize: false, + simultaneousUploads: 3, + singleFile: false, + fileParameterName: 'file', + progressCallbacksInterval: 500, + speedSmoothingFactor: 0.1, + query: {}, + headers: {}, + withCredentials: false, + preprocess: null, + method: 'multipart', + testMethod: 'GET', + uploadMethod: 'POST', + prioritizeFirstAndLastChunk: false, + allowDuplicateUploads: false, + target: '/', + testChunks: true, + generateUniqueIdentifier: null, + maxChunkRetries: 0, + chunkRetryInterval: null, + permanentErrors: [404, 413, 415, 500, 501], + successStatuses: [200, 201, 202], + onDropStopPropagation: false, + initFileFn: null, + readFileFn: webAPIFileRead + }; + + /** + * Current options + * @type {Object} + */ + this.opts = {}; + + /** + * List of events: + * key stands for event name + * value array list of callbacks + * @type {} + */ + this.events = {}; + + var $ = this; + + /** + * On drop event + * @function + * @param {MouseEvent} event + */ + this.onDrop = function (event) { + if ($.opts.onDropStopPropagation) { + event.stopPropagation(); + } + event.preventDefault(); + var dataTransfer = event.dataTransfer; + if (dataTransfer.items && dataTransfer.items[0] && + dataTransfer.items[0].webkitGetAsEntry) { + $.webkitReadDataTransfer(event); + } else { + $.addFiles(dataTransfer.files, event); + } + }; + + /** + * Prevent default + * @function + * @param {MouseEvent} event + */ + this.preventEvent = function (event) { + event.preventDefault(); + }; + + + /** + * Current options + * @type {Object} + */ + this.opts = Flow.extend({}, this.defaults, opts || {}); + + } + + Flow.prototype = { + /** + * Set a callback for an event, possible events: + * fileSuccess(file), fileProgress(file), fileAdded(file, event), + * fileRemoved(file), fileRetry(file), fileError(file, message), + * complete(), progress(), error(message, file), pause() + * @function + * @param {string} event + * @param {Function} callback + */ + on: function (event, callback) { + event = event.toLowerCase(); + if (!this.events.hasOwnProperty(event)) { + this.events[event] = []; + } + this.events[event].push(callback); + }, + + /** + * Remove event callback + * @function + * @param {string} [event] removes all events if not specified + * @param {Function} [fn] removes all callbacks of event if not specified + */ + off: function (event, fn) { + if (event !== undefined) { + event = event.toLowerCase(); + if (fn !== undefined) { + if (this.events.hasOwnProperty(event)) { + arrayRemove(this.events[event], fn); + } + } else { + delete this.events[event]; + } + } else { + this.events = {}; + } + }, + + /** + * Fire an event + * @function + * @param {string} event event name + * @param {...} args arguments of a callback + * @return {bool} value is false if at least one of the event handlers which handled this event + * returned false. Otherwise it returns true. + */ + fire: function (event, args) { + // `arguments` is an object, not array, in FF, so: + args = Array.prototype.slice.call(arguments); + event = event.toLowerCase(); + var preventDefault = false; + if (this.events.hasOwnProperty(event)) { + each(this.events[event], function (callback) { + preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault; + }, this); + } + if (event != 'catchall') { + args.unshift('catchAll'); + preventDefault = this.fire.apply(this, args) === false || preventDefault; + } + return !preventDefault; + }, + + /** + * Read webkit dataTransfer object + * @param event + */ + webkitReadDataTransfer: function (event) { + var $ = this; + var queue = event.dataTransfer.items.length; + var files = []; + each(event.dataTransfer.items, function (item) { + var entry = item.webkitGetAsEntry(); + if (!entry) { + decrement(); + return ; + } + if (entry.isFile) { + // due to a bug in Chrome's File System API impl - #149735 + fileReadSuccess(item.getAsFile(), entry.fullPath); + } else { + readDirectory(entry.createReader()); + } + }); + function readDirectory(reader) { + reader.readEntries(function (entries) { + if (entries.length) { + queue += entries.length; + each(entries, function(entry) { + if (entry.isFile) { + var fullPath = entry.fullPath; + entry.file(function (file) { + fileReadSuccess(file, fullPath); + }, readError); + } else if (entry.isDirectory) { + readDirectory(entry.createReader()); + } + }); + readDirectory(reader); + } else { + decrement(); + } + }, readError); + } + function fileReadSuccess(file, fullPath) { + // relative path should not start with "/" + file.relativePath = fullPath.substring(1); + files.push(file); + decrement(); + } + function readError(fileError) { + throw fileError; + } + function decrement() { + if (--queue == 0) { + $.addFiles(files, event); + } + } + }, + + /** + * Generate unique identifier for a file + * @function + * @param {FlowFile} file + * @returns {string} + */ + generateUniqueIdentifier: function (file) { + var custom = this.opts.generateUniqueIdentifier; + if (typeof custom === 'function') { + return custom(file); + } + // Some confusion in different versions of Firefox + var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name; + return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, ''); + }, + + /** + * Upload next chunk from the queue + * @function + * @returns {boolean} + * @private + */ + uploadNextChunk: function (preventEvents) { + // In some cases (such as videos) it's really handy to upload the first + // and last chunk of a file quickly; this let's the server check the file's + // metadata and determine if there's even a point in continuing. + var found = false; + if (this.opts.prioritizeFirstAndLastChunk) { + each(this.files, function (file) { + if (!file.paused && file.chunks.length && + file.chunks[0].status() === 'pending') { + file.chunks[0].send(); + found = true; + return false; + } + if (!file.paused && file.chunks.length > 1 && + file.chunks[file.chunks.length - 1].status() === 'pending') { + file.chunks[file.chunks.length - 1].send(); + found = true; + return false; + } + }); + if (found) { + return found; + } + } + + // Now, simply look for the next, best thing to upload + each(this.files, function (file) { + if (!file.paused) { + each(file.chunks, function (chunk) { + if (chunk.status() === 'pending') { + chunk.send(); + found = true; + return false; + } + }); + } + if (found) { + return false; + } + }); + if (found) { + return true; + } + + // The are no more outstanding chunks to upload, check is everything is done + var outstanding = false; + each(this.files, function (file) { + if (!file.isComplete()) { + outstanding = true; + return false; + } + }); + if (!outstanding && !preventEvents) { + // All chunks have been uploaded, complete + async(function () { + this.fire('complete'); + }, this); + } + return false; + }, + + + /** + * Assign a browse action to one or more DOM nodes. + * @function + * @param {Element|Array.} domNodes + * @param {boolean} isDirectory Pass in true to allow directories to + * @param {boolean} singleFile prevent multi file upload + * @param {Object} attributes set custom attributes: + * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes + * eg: accept: 'image/*' + * be selected (Chrome only). + */ + assignBrowse: function (domNodes, isDirectory, singleFile, attributes) { + if (domNodes instanceof Element) { + domNodes = [domNodes]; + } + + each(domNodes, function (domNode) { + var input; + if (domNode.tagName === 'INPUT' && domNode.type === 'file') { + input = domNode; + } else { + input = document.createElement('input'); + input.setAttribute('type', 'file'); + // display:none - not working in opera 12 + extend(input.style, { + visibility: 'hidden', + position: 'absolute', + width: '1px', + height: '1px' + }); + // for opera 12 browser, input must be assigned to a document + domNode.appendChild(input); + // https://developer.mozilla.org/en/using_files_from_web_applications) + // event listener is executed two times + // first one - original mouse click event + // second - input.click(), input is inside domNode + domNode.addEventListener('click', function() { + input.click(); + }, false); + } + if (!this.opts.singleFile && !singleFile) { + input.setAttribute('multiple', 'multiple'); + } + if (isDirectory) { + input.setAttribute('webkitdirectory', 'webkitdirectory'); + } + each(attributes, function (value, key) { + input.setAttribute(key, value); + }); + // When new files are added, simply append them to the overall list + var $ = this; + input.addEventListener('change', function (e) { + if (e.target.value) { + $.addFiles(e.target.files, e); + e.target.value = ''; + } + }, false); + }, this); + }, + + /** + * Assign one or more DOM nodes as a drop target. + * @function + * @param {Element|Array.} domNodes + */ + assignDrop: function (domNodes) { + if (typeof domNodes.length === 'undefined') { + domNodes = [domNodes]; + } + each(domNodes, function (domNode) { + domNode.addEventListener('dragover', this.preventEvent, false); + domNode.addEventListener('dragenter', this.preventEvent, false); + domNode.addEventListener('drop', this.onDrop, false); + }, this); + }, + + /** + * Un-assign drop event from DOM nodes + * @function + * @param domNodes + */ + unAssignDrop: function (domNodes) { + if (typeof domNodes.length === 'undefined') { + domNodes = [domNodes]; + } + each(domNodes, function (domNode) { + domNode.removeEventListener('dragover', this.preventEvent); + domNode.removeEventListener('dragenter', this.preventEvent); + domNode.removeEventListener('drop', this.onDrop); + }, this); + }, + + /** + * Returns a boolean indicating whether or not the instance is currently + * uploading anything. + * @function + * @returns {boolean} + */ + isUploading: function () { + var uploading = false; + each(this.files, function (file) { + if (file.isUploading()) { + uploading = true; + return false; + } + }); + return uploading; + }, + + /** + * should upload next chunk + * @function + * @returns {boolean|number} + */ + _shouldUploadNext: function () { + var num = 0; + var should = true; + var simultaneousUploads = this.opts.simultaneousUploads; + each(this.files, function (file) { + each(file.chunks, function(chunk) { + if (chunk.status() === 'uploading') { + num++; + if (num >= simultaneousUploads) { + should = false; + return false; + } + } + }); + }); + // if should is true then return uploading chunks's length + return should && num; + }, + + /** + * Start or resume uploading. + * @function + */ + upload: function () { + // Make sure we don't start too many uploads at once + var ret = this._shouldUploadNext(); + if (ret === false) { + return; + } + // Kick off the queue + this.fire('uploadStart'); + var started = false; + for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) { + started = this.uploadNextChunk(true) || started; + } + if (!started) { + async(function () { + this.fire('complete'); + }, this); + } + }, + + /** + * Resume uploading. + * @function + */ + resume: function () { + each(this.files, function (file) { + if (!file.isComplete()) { + file.resume(); + } + }); + }, + + /** + * Pause uploading. + * @function + */ + pause: function () { + each(this.files, function (file) { + file.pause(); + }); + }, + + /** + * Cancel upload of all FlowFile objects and remove them from the list. + * @function + */ + cancel: function () { + for (var i = this.files.length - 1; i >= 0; i--) { + this.files[i].cancel(); + } + }, + + /** + * Returns a number between 0 and 1 indicating the current upload progress + * of all files. + * @function + * @returns {number} + */ + progress: function () { + var totalDone = 0; + var totalSize = 0; + // Resume all chunks currently being uploaded + each(this.files, function (file) { + totalDone += file.progress() * file.size; + totalSize += file.size; + }); + return totalSize > 0 ? totalDone / totalSize : 0; + }, + + /** + * Add a HTML5 File object to the list of files. + * @function + * @param {File} file + * @param {Event} [event] event is optional + */ + addFile: function (file, event) { + this.addFiles([file], event); + }, + + /** + * Add a HTML5 File object to the list of files. + * @function + * @param {FileList|Array} fileList + * @param {Event} [event] event is optional + */ + addFiles: function (fileList, event) { + var files = []; + each(fileList, function (file) { + // https://github.com/flowjs/flow.js/issues/55 + if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) { + var uniqueIdentifier = this.generateUniqueIdentifier(file); + if (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(uniqueIdentifier)) { + var f = new FlowFile(this, file, uniqueIdentifier); + if (this.fire('fileAdded', f, event)) { + files.push(f); + } + } + } + }, this); + if (this.fire('filesAdded', files, event)) { + each(files, function (file) { + if (this.opts.singleFile && this.files.length > 0) { + this.removeFile(this.files[0]); + } + this.files.push(file); + }, this); + this.fire('filesSubmitted', files, event); + } + }, + + + /** + * Cancel upload of a specific FlowFile object from the list. + * @function + * @param {FlowFile} file + */ + removeFile: function (file) { + for (var i = this.files.length - 1; i >= 0; i--) { + if (this.files[i] === file) { + this.files.splice(i, 1); + file.abort(); + this.fire('fileRemoved', file); + } + } + }, + + /** + * Look up a FlowFile object by its unique identifier. + * @function + * @param {string} uniqueIdentifier + * @returns {boolean|FlowFile} false if file was not found + */ + getFromUniqueIdentifier: function (uniqueIdentifier) { + var ret = false; + each(this.files, function (file) { + if (file.uniqueIdentifier === uniqueIdentifier) { + ret = file; + } + }); + return ret; + }, + + /** + * Returns the total size of all files in bytes. + * @function + * @returns {number} + */ + getSize: function () { + var totalSize = 0; + each(this.files, function (file) { + totalSize += file.size; + }); + return totalSize; + }, + + /** + * Returns the total size uploaded of all files in bytes. + * @function + * @returns {number} + */ + sizeUploaded: function () { + var size = 0; + each(this.files, function (file) { + size += file.sizeUploaded(); + }); + return size; + }, + + /** + * Returns remaining time to upload all files in seconds. Accuracy is based on average speed. + * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY` + * @function + * @returns {number} + */ + timeRemaining: function () { + var sizeDelta = 0; + var averageSpeed = 0; + each(this.files, function (file) { + if (!file.paused && !file.error) { + sizeDelta += file.size - file.sizeUploaded(); + averageSpeed += file.averageSpeed; + } + }); + if (sizeDelta && !averageSpeed) { + return Number.POSITIVE_INFINITY; + } + if (!sizeDelta && !averageSpeed) { + return 0; + } + return Math.floor(sizeDelta / averageSpeed); + } + }; + + + + + + + /** + * FlowFile class + * @name FlowFile + * @param {Flow} flowObj + * @param {File} file + * @param {string} uniqueIdentifier + * @constructor + */ + function FlowFile(flowObj, file, uniqueIdentifier) { + + /** + * Reference to parent Flow instance + * @type {Flow} + */ + this.flowObj = flowObj; + + /** + * Used to store the bytes read + * @type {Blob|string} + */ + this.bytes = null; + + /** + * Reference to file + * @type {File} + */ + this.file = file; + + /** + * File name. Some confusion in different versions of Firefox + * @type {string} + */ + this.name = file.fileName || file.name; + + /** + * File size + * @type {number} + */ + this.size = file.size; + + /** + * Relative file path + * @type {string} + */ + this.relativePath = file.relativePath || file.webkitRelativePath || this.name; + + /** + * File unique identifier + * @type {string} + */ + this.uniqueIdentifier = (uniqueIdentifier === undefined ? flowObj.generateUniqueIdentifier(file) : uniqueIdentifier); + + /** + * List of chunks + * @type {Array.} + */ + this.chunks = []; + + /** + * Indicated if file is paused + * @type {boolean} + */ + this.paused = false; + + /** + * Indicated if file has encountered an error + * @type {boolean} + */ + this.error = false; + + /** + * Average upload speed + * @type {number} + */ + this.averageSpeed = 0; + + /** + * Current upload speed + * @type {number} + */ + this.currentSpeed = 0; + + /** + * Date then progress was called last time + * @type {number} + * @private + */ + this._lastProgressCallback = Date.now(); + + /** + * Previously uploaded file size + * @type {number} + * @private + */ + this._prevUploadedSize = 0; + + /** + * Holds previous progress + * @type {number} + * @private + */ + this._prevProgress = 0; + + this.bootstrap(); + } + + FlowFile.prototype = { + /** + * Update speed parameters + * @link http://stackoverflow.com/questions/2779600/how-to-estimate-download-time-remaining-accurately + * @function + */ + measureSpeed: function () { + var timeSpan = Date.now() - this._lastProgressCallback; + if (!timeSpan) { + return ; + } + var smoothingFactor = this.flowObj.opts.speedSmoothingFactor; + var uploaded = this.sizeUploaded(); + // Prevent negative upload speed after file upload resume + this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0); + this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed; + this._prevUploadedSize = uploaded; + }, + + /** + * For internal usage only. + * Callback when something happens within the chunk. + * @function + * @param {FlowChunk} chunk + * @param {string} event can be 'progress', 'success', 'error' or 'retry' + * @param {string} [message] + */ + chunkEvent: function (chunk, event, message) { + switch (event) { + case 'progress': + if (Date.now() - this._lastProgressCallback < + this.flowObj.opts.progressCallbacksInterval) { + break; + } + this.measureSpeed(); + this.flowObj.fire('fileProgress', this, chunk); + this.flowObj.fire('progress'); + this._lastProgressCallback = Date.now(); + break; + case 'error': + this.error = true; + this.abort(true); + this.flowObj.fire('fileError', this, message, chunk); + this.flowObj.fire('error', message, this, chunk); + break; + case 'success': + if (this.error) { + return; + } + this.measureSpeed(); + this.flowObj.fire('fileProgress', this, chunk); + this.flowObj.fire('progress'); + this._lastProgressCallback = Date.now(); + if (this.isComplete()) { + this.currentSpeed = 0; + this.averageSpeed = 0; + this.flowObj.fire('fileSuccess', this, message, chunk); + } + break; + case 'retry': + this.flowObj.fire('fileRetry', this, chunk); + break; + } + }, + + /** + * Pause file upload + * @function + */ + pause: function() { + this.paused = true; + this.abort(); + }, + + /** + * Resume file upload + * @function + */ + resume: function() { + this.paused = false; + this.flowObj.upload(); + }, + + /** + * Abort current upload + * @function + */ + abort: function (reset) { + this.currentSpeed = 0; + this.averageSpeed = 0; + var chunks = this.chunks; + if (reset) { + this.chunks = []; + } + each(chunks, function (c) { + if (c.status() === 'uploading') { + c.abort(); + this.flowObj.uploadNextChunk(); + } + }, this); + }, + + /** + * Cancel current upload and remove from a list + * @function + */ + cancel: function () { + this.flowObj.removeFile(this); + }, + + /** + * Retry aborted file upload + * @function + */ + retry: function () { + this.bootstrap(); + this.flowObj.upload(); + }, + + /** + * Clear current chunks and slice file again + * @function + */ + bootstrap: function () { + if (typeof this.flowObj.opts.initFileFn === "function") { + this.flowObj.opts.initFileFn(this); + } + + this.abort(true); + this.error = false; + // Rebuild stack of chunks from file + this._prevProgress = 0; + var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor; + var chunks = Math.max( + round(this.size / this.flowObj.opts.chunkSize), 1 + ); + for (var offset = 0; offset < chunks; offset++) { + this.chunks.push( + new FlowChunk(this.flowObj, this, offset) + ); + } + }, + + /** + * Get current upload progress status + * @function + * @returns {number} from 0 to 1 + */ + progress: function () { + if (this.error) { + return 1; + } + if (this.chunks.length === 1) { + this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress()); + return this._prevProgress; + } + // Sum up progress across everything + var bytesLoaded = 0; + each(this.chunks, function (c) { + // get chunk progress relative to entire file + bytesLoaded += c.progress() * (c.endByte - c.startByte); + }); + var percent = bytesLoaded / this.size; + // We don't want to lose percentages when an upload is paused + this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent); + return this._prevProgress; + }, + + /** + * Indicates if file is being uploaded at the moment + * @function + * @returns {boolean} + */ + isUploading: function () { + var uploading = false; + each(this.chunks, function (chunk) { + if (chunk.status() === 'uploading') { + uploading = true; + return false; + } + }); + return uploading; + }, + + /** + * Indicates if file is has finished uploading and received a response + * @function + * @returns {boolean} + */ + isComplete: function () { + var outstanding = false; + each(this.chunks, function (chunk) { + var status = chunk.status(); + if (status === 'pending' || status === 'uploading' || status === 'reading' || chunk.preprocessState === 1 || chunk.readState === 1) { + outstanding = true; + return false; + } + }); + return !outstanding; + }, + + /** + * Count total size uploaded + * @function + * @returns {number} + */ + sizeUploaded: function () { + var size = 0; + each(this.chunks, function (chunk) { + size += chunk.sizeUploaded(); + }); + return size; + }, + + /** + * Returns remaining time to finish upload file in seconds. Accuracy is based on average speed. + * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY` + * @function + * @returns {number} + */ + timeRemaining: function () { + if (this.paused || this.error) { + return 0; + } + var delta = this.size - this.sizeUploaded(); + if (delta && !this.averageSpeed) { + return Number.POSITIVE_INFINITY; + } + if (!delta && !this.averageSpeed) { + return 0; + } + return Math.floor(delta / this.averageSpeed); + }, + + /** + * Get file type + * @function + * @returns {string} + */ + getType: function () { + return this.file.type && this.file.type.split('/')[1]; + }, + + /** + * Get file extension + * @function + * @returns {string} + */ + getExtension: function () { + return this.name.substr((~-this.name.lastIndexOf(".") >>> 0) + 2).toLowerCase(); + } + }; + + /** + * Default read function using the webAPI + * + * @function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk) + * + */ + function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk) { + var function_name = 'slice'; + + if (fileObj.file.slice) + function_name = 'slice'; + else if (fileObj.file.mozSlice) + function_name = 'mozSlice'; + else if (fileObj.file.webkitSlice) + function_name = 'webkitSlice'; + + chunk.readFinished(fileObj.file[function_name](startByte, endByte, fileType)); + } + + + /** + * Class for storing a single chunk + * @name FlowChunk + * @param {Flow} flowObj + * @param {FlowFile} fileObj + * @param {number} offset + * @constructor + */ + function FlowChunk(flowObj, fileObj, offset) { + + /** + * Reference to parent flow object + * @type {Flow} + */ + this.flowObj = flowObj; + + /** + * Reference to parent FlowFile object + * @type {FlowFile} + */ + this.fileObj = fileObj; + + /** + * File offset + * @type {number} + */ + this.offset = offset; + + /** + * Indicates if chunk existence was checked on the server + * @type {boolean} + */ + this.tested = false; + + /** + * Number of retries performed + * @type {number} + */ + this.retries = 0; + + /** + * Pending retry + * @type {boolean} + */ + this.pendingRetry = false; + + /** + * Preprocess state + * @type {number} 0 = unprocessed, 1 = processing, 2 = finished + */ + this.preprocessState = 0; + + /** + * Read state + * @type {number} 0 = not read, 1 = reading, 2 = finished + */ + this.readState = 0; + + + /** + * Bytes transferred from total request size + * @type {number} + */ + this.loaded = 0; + + /** + * Total request size + * @type {number} + */ + this.total = 0; + + /** + * Size of a chunk + * @type {number} + */ + this.chunkSize = this.flowObj.opts.chunkSize; + + /** + * Chunk start byte in a file + * @type {number} + */ + this.startByte = this.offset * this.chunkSize; + + /** + * Compute the endbyte in a file + * + */ + this.computeEndByte = function() { + var endByte = Math.min(this.fileObj.size, (this.offset + 1) * this.chunkSize); + if (this.fileObj.size - endByte < this.chunkSize && !this.flowObj.opts.forceChunkSize) { + // The last chunk will be bigger than the chunk size, + // but less than 2 * this.chunkSize + endByte = this.fileObj.size; + } + return endByte; + } + + /** + * Chunk end byte in a file + * @type {number} + */ + this.endByte = this.computeEndByte(); + + /** + * XMLHttpRequest + * @type {XMLHttpRequest} + */ + this.xhr = null; + + var $ = this; + + /** + * Send chunk event + * @param event + * @param {...} args arguments of a callback + */ + this.event = function (event, args) { + args = Array.prototype.slice.call(arguments); + args.unshift($); + $.fileObj.chunkEvent.apply($.fileObj, args); + }; + /** + * Catch progress event + * @param {ProgressEvent} event + */ + this.progressHandler = function(event) { + if (event.lengthComputable) { + $.loaded = event.loaded ; + $.total = event.total; + } + $.event('progress', event); + }; + + /** + * Catch test event + * @param {Event} event + */ + this.testHandler = function(event) { + var status = $.status(true); + if (status === 'error') { + $.event(status, $.message()); + $.flowObj.uploadNextChunk(); + } else if (status === 'success') { + $.tested = true; + $.event(status, $.message()); + $.flowObj.uploadNextChunk(); + } else if (!$.fileObj.paused) { + // Error might be caused by file pause method + // Chunks does not exist on the server side + $.tested = true; + $.send(); + } + }; + + /** + * Upload has stopped + * @param {Event} event + */ + this.doneHandler = function(event) { + var status = $.status(); + if (status === 'success' || status === 'error') { + delete this.data; + $.event(status, $.message()); + $.flowObj.uploadNextChunk(); + } else { + $.event('retry', $.message()); + $.pendingRetry = true; + $.abort(); + $.retries++; + var retryInterval = $.flowObj.opts.chunkRetryInterval; + if (retryInterval !== null) { + setTimeout(function () { + $.send(); + }, retryInterval); + } else { + $.send(); + } + } + }; + } + + FlowChunk.prototype = { + /** + * Get params for a request + * @function + */ + getParams: function () { + return { + flowChunkNumber: this.offset + 1, + flowChunkSize: this.flowObj.opts.chunkSize, + flowCurrentChunkSize: this.endByte - this.startByte, + flowTotalSize: this.fileObj.size, + flowIdentifier: this.fileObj.uniqueIdentifier, + flowFilename: this.fileObj.name, + flowRelativePath: this.fileObj.relativePath, + flowTotalChunks: this.fileObj.chunks.length + }; + }, + + /** + * Get target option with query params + * @function + * @param params + * @returns {string} + */ + getTarget: function(target, params){ + if(target.indexOf('?') < 0) { + target += '?'; + } else { + target += '&'; + } + return target + params.join('&'); + }, + + /** + * Makes a GET request without any data to see if the chunk has already + * been uploaded in a previous session + * @function + */ + test: function () { + // Set up request and listen for event + this.xhr = new XMLHttpRequest(); + this.xhr.addEventListener("load", this.testHandler, false); + this.xhr.addEventListener("error", this.testHandler, false); + var testMethod = evalOpts(this.flowObj.opts.testMethod, this.fileObj, this); + var data = this.prepareXhrRequest(testMethod, true); + this.xhr.send(data); + }, + + /** + * Finish preprocess state + * @function + */ + preprocessFinished: function () { + // Re-compute the endByte after the preprocess function to allow an + // implementer of preprocess to set the fileObj size + this.endByte = this.computeEndByte(); + + this.preprocessState = 2; + this.send(); + }, + + /** + * Finish read state + * @function + */ + readFinished: function (bytes) { + this.readState = 2; + this.bytes = bytes; + this.send(); + }, + + + /** + * Uploads the actual data in a POST call + * @function + */ + send: function () { + var preprocess = this.flowObj.opts.preprocess; + var read = this.flowObj.opts.readFileFn; + if (typeof preprocess === 'function') { + switch (this.preprocessState) { + case 0: + this.preprocessState = 1; + preprocess(this); + return; + case 1: + return; + } + } + switch (this.readState) { + case 0: + this.readState = 1; + read(this.fileObj, this.startByte, this.endByte, this.fileObj.file.type, this); + return; + case 1: + return; + } + if (this.flowObj.opts.testChunks && !this.tested) { + this.test(); + return; + } + + this.loaded = 0; + this.total = 0; + this.pendingRetry = false; + + // Set up request and listen for event + this.xhr = new XMLHttpRequest(); + this.xhr.upload.addEventListener('progress', this.progressHandler, false); + this.xhr.addEventListener("load", this.doneHandler, false); + this.xhr.addEventListener("error", this.doneHandler, false); + + var uploadMethod = evalOpts(this.flowObj.opts.uploadMethod, this.fileObj, this); + var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, this.bytes); + this.xhr.send(data); + }, + + /** + * Abort current xhr request + * @function + */ + abort: function () { + // Abort and reset + var xhr = this.xhr; + this.xhr = null; + if (xhr) { + xhr.abort(); + } + }, + + /** + * Retrieve current chunk upload status + * @function + * @returns {string} 'pending', 'uploading', 'success', 'error' + */ + status: function (isTest) { + if (this.readState === 1) { + return 'reading'; + } else if (this.pendingRetry || this.preprocessState === 1) { + // if pending retry then that's effectively the same as actively uploading, + // there might just be a slight delay before the retry starts + return 'uploading'; + } else if (!this.xhr) { + return 'pending'; + } else if (this.xhr.readyState < 4) { + // Status is really 'OPENED', 'HEADERS_RECEIVED' + // or 'LOADING' - meaning that stuff is happening + return 'uploading'; + } else { + if (this.flowObj.opts.successStatuses.indexOf(this.xhr.status) > -1) { + // HTTP 200, perfect + // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed. + return 'success'; + } else if (this.flowObj.opts.permanentErrors.indexOf(this.xhr.status) > -1 || + !isTest && this.retries >= this.flowObj.opts.maxChunkRetries) { + // HTTP 413/415/500/501, permanent error + return 'error'; + } else { + // this should never happen, but we'll reset and queue a retry + // a likely case for this would be 503 service unavailable + this.abort(); + return 'pending'; + } + } + }, + + /** + * Get response from xhr request + * @function + * @returns {String} + */ + message: function () { + return this.xhr ? this.xhr.responseText : ''; + }, + + /** + * Get upload progress + * @function + * @returns {number} + */ + progress: function () { + if (this.pendingRetry) { + return 0; + } + var s = this.status(); + if (s === 'success' || s === 'error') { + return 1; + } else if (s === 'pending') { + return 0; + } else { + return this.total > 0 ? this.loaded / this.total : 0; + } + }, + + /** + * Count total size uploaded + * @function + * @returns {number} + */ + sizeUploaded: function () { + var size = this.endByte - this.startByte; + // can't return only chunk.loaded value, because it is bigger than chunk size + if (this.status() !== 'success') { + size = this.progress() * size; + } + return size; + }, + + /** + * Prepare Xhr request. Set query, headers and data + * @param {string} method GET or POST + * @param {bool} isTest is this a test request + * @param {string} [paramsMethod] octet or form + * @param {Blob} [blob] to send + * @returns {FormData|Blob|Null} data to send + */ + prepareXhrRequest: function(method, isTest, paramsMethod, blob) { + // Add data from the query options + var query = evalOpts(this.flowObj.opts.query, this.fileObj, this, isTest); + query = extend(query, this.getParams()); + + var target = evalOpts(this.flowObj.opts.target, this.fileObj, this, isTest); + var data = null; + if (method === 'GET' || paramsMethod === 'octet') { + // Add data from the query options + var params = []; + each(query, function (v, k) { + params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); + }); + target = this.getTarget(target, params); + data = blob || null; + } else { + // Add data from the query options + data = new FormData(); + each(query, function (v, k) { + data.append(k, v); + }); + if (typeof blob !== "undefined") data.append(this.flowObj.opts.fileParameterName, blob, this.fileObj.file.name); + } + + this.xhr.open(method, target, true); + this.xhr.withCredentials = this.flowObj.opts.withCredentials; + + // Add data from header options + each(evalOpts(this.flowObj.opts.headers, this.fileObj, this, isTest), function (v, k) { + this.xhr.setRequestHeader(k, v); + }, this); + + return data; + } + }; + + /** + * Remove value from array + * @param array + * @param value + */ + function arrayRemove(array, value) { + var index = array.indexOf(value); + if (index > -1) { + array.splice(index, 1); + } + } + + /** + * If option is a function, evaluate it with given params + * @param {*} data + * @param {...} args arguments of a callback + * @returns {*} + */ + function evalOpts(data, args) { + if (typeof data === "function") { + // `arguments` is an object, not array, in FF, so: + args = Array.prototype.slice.call(arguments); + data = data.apply(null, args.slice(1)); + } + return data; + } + Flow.evalOpts = evalOpts; + + /** + * Execute function asynchronously + * @param fn + * @param context + */ + function async(fn, context) { + setTimeout(fn.bind(context), 0); + } + + /** + * Extends the destination object `dst` by copying all of the properties from + * the `src` object(s) to `dst`. You can specify multiple `src` objects. + * @function + * @param {Object} dst Destination object. + * @param {...Object} src Source object(s). + * @returns {Object} Reference to `dst`. + */ + function extend(dst, src) { + each(arguments, function(obj) { + if (obj !== dst) { + each(obj, function(value, key){ + dst[key] = value; + }); + } + }); + return dst; + } + Flow.extend = extend; + + /** + * Iterate each element of an object + * @function + * @param {Array|Object} obj object or an array to iterate + * @param {Function} callback first argument is a value and second is a key. + * @param {Object=} context Object to become context (`this`) for the iterator function. + */ + function each(obj, callback, context) { + if (!obj) { + return ; + } + var key; + // Is Array? + // Array.isArray won't work, not only arrays can be iterated by index https://github.com/flowjs/ng-flow/issues/236# + if (typeof(obj.length) !== 'undefined') { + for (key = 0; key < obj.length; key++) { + if (callback.call(context, obj[key], key) === false) { + return ; + } + } + } else { + for (key in obj) { + if (obj.hasOwnProperty(key) && callback.call(context, obj[key], key) === false) { + return ; + } + } + } + } + Flow.each = each; + + /** + * FlowFile constructor + * @type {FlowFile} + */ + Flow.FlowFile = FlowFile; + + /** + * FlowFile constructor + * @type {FlowChunk} + */ + Flow.FlowChunk = FlowChunk; + + /** + * Library version + * @type {string} + */ + Flow.version = '2.13.0'; + + if ( typeof module === "object" && module && typeof module.exports === "object" ) { + // Expose Flow as module.exports in loaders that implement the Node + // module pattern (including browserify). Do not create the global, since + // the user will be storing it themselves locally, and globals are frowned + // upon in the Node module world. + module.exports = Flow; + } else { + // Otherwise expose Flow to the global object as usual + window.Flow = Flow; + + // Register as a named AMD module, since Flow can be concatenated with other + // files that may use define, but not via a proper concatenation script that + // understands anonymous AMD modules. A named AMD is safest and most robust + // way to register. Lowercase flow is used because AMD module names are + // derived from file names, and Flow is normally delivered in a lowercase + // file name. Do this after creating the global so that if an AMD module wants + // to call noConflict to hide this version of Flow, it will work. + if ( typeof define === "function" && define.amd ) { + define( "flow", [], function () { return Flow; } ); + } + } +})(window, document); + +/** + * @description + * var app = angular.module('App', ['flow.provider'], function(flowFactoryProvider){ + * flowFactoryProvider.defaults = {target: '/'}; + * }); + * @name flowFactoryProvider + */ +angular.module('flow.provider', []) +.provider('flowFactory', function() { + 'use strict'; + /** + * Define the default properties for flow.js + * @name flowFactoryProvider.defaults + * @type {Object} + */ + this.defaults = {}; + + /** + * Flow, MaybeFlow or NotFlow + * @name flowFactoryProvider.factory + * @type {function} + * @return {Flow} + */ + this.factory = function (options) { + return new Flow(options); + }; + + /** + * Define the default events + * @name flowFactoryProvider.events + * @type {Array} + * @private + */ + this.events = []; + + /** + * Add default events + * @name flowFactoryProvider.on + * @function + * @param {string} event + * @param {Function} callback + */ + this.on = function (event, callback) { + this.events.push([event, callback]); + }; + + this.$get = function() { + var fn = this.factory; + var defaults = this.defaults; + var events = this.events; + return { + 'create': function(opts) { + // combine default options with global options and options + var flow = fn(angular.extend({}, defaults, opts)); + angular.forEach(events, function (event) { + flow.on(event[0], event[1]); + }); + return flow; + } + }; + }; +}); +angular.module('flow.init', ['flow.provider']) + .controller('flowCtrl', ['$scope', '$attrs', '$parse', 'flowFactory', + function ($scope, $attrs, $parse, flowFactory) { + + var options = angular.extend({}, $scope.$eval($attrs.flowInit)); + + // use existing flow object or create a new one + var flow = $scope.$eval($attrs.flowObject) || flowFactory.create(options); + + var catchAllHandler = function(eventName){ + var args = Array.prototype.slice.call(arguments); + args.shift(); + var event = $scope.$broadcast.apply($scope, ['flow::' + eventName, flow].concat(args)); + if ({ + 'progress':1, 'filesSubmitted':1, 'fileSuccess': 1, 'fileError': 1, 'complete': 1 + }[eventName]) { + $scope.$apply(); + } + if (event.defaultPrevented) { + return false; + } + }; + + flow.on('catchAll', catchAllHandler); + $scope.$on('$destroy', function(){ + flow.off('catchAll', catchAllHandler); + }); + + $scope.$flow = flow; + + if ($attrs.hasOwnProperty('flowName')) { + $parse($attrs.flowName).assign($scope, flow); + $scope.$on('$destroy', function () { + $parse($attrs.flowName).assign($scope); + }); + } + }]) + .directive('flowInit', [function() { + return { + scope: true, + controller: 'flowCtrl' + }; + }]); +angular.module('flow.btn', ['flow.init']) +.directive('flowBtn', [function() { + return { + 'restrict': 'EA', + 'scope': false, + 'require': '^flowInit', + 'link': function(scope, element, attrs) { + var isDirectory = attrs.hasOwnProperty('flowDirectory'); + var isSingleFile = attrs.hasOwnProperty('flowSingleFile'); + var inputAttrs = attrs.hasOwnProperty('flowAttrs') && scope.$eval(attrs.flowAttrs); + scope.$flow.assignBrowse(element, isDirectory, isSingleFile, inputAttrs); + } + }; +}]); +angular.module('flow.dragEvents', ['flow.init']) +/** + * @name flowPreventDrop + * Prevent loading files then dropped on element + */ + .directive('flowPreventDrop', function() { + return { + 'scope': false, + 'link': function(scope, element, attrs) { + element.bind('drop dragover', function (event) { + event.preventDefault(); + }); + } + }; + }) +/** + * @name flowDragEnter + * executes `flowDragEnter` and `flowDragLeave` events + */ + .directive('flowDragEnter', ['$timeout', function($timeout) { + return { + 'scope': false, + 'link': function(scope, element, attrs) { + var promise; + var enter = false; + element.bind('dragover', function (event) { + if (!isFileDrag(event)) { + return ; + } + if (!enter) { + scope.$apply(attrs.flowDragEnter); + enter = true; + } + $timeout.cancel(promise); + event.preventDefault(); + }); + element.bind('dragleave drop', function (event) { + $timeout.cancel(promise); + promise = $timeout(function () { + scope.$eval(attrs.flowDragLeave); + promise = null; + enter = false; + }, 100); + }); + function isFileDrag(dragEvent) { + var fileDrag = false; + var dataTransfer = dragEvent.dataTransfer || dragEvent.originalEvent.dataTransfer; + angular.forEach(dataTransfer && dataTransfer.types, function(val) { + if (val === 'Files') { + fileDrag = true; + } + }); + return fileDrag; + } + } + }; + }]); + +angular.module('flow.drop', ['flow.init']) +.directive('flowDrop', function() { + return { + 'scope': false, + 'require': '^flowInit', + 'link': function(scope, element, attrs) { + if (attrs.flowDropEnabled) { + scope.$watch(attrs.flowDropEnabled, function (value) { + if (value) { + assignDrop(); + } else { + unAssignDrop(); + } + }); + } else { + assignDrop(); + } + function assignDrop() { + scope.$flow.assignDrop(element); + } + function unAssignDrop() { + scope.$flow.unAssignDrop(element); + } + } + }; +}); + +!function (angular) {'use strict'; + var module = angular.module('flow.events', ['flow.init']); + var events = { + fileSuccess: ['$file', '$message'], + fileProgress: ['$file'], + fileAdded: ['$file', '$event'], + filesAdded: ['$files', '$event'], + filesSubmitted: ['$files', '$event'], + fileRetry: ['$file'], + fileRemoved: ['$file'], + fileError: ['$file', '$message'], + uploadStart: [], + complete: [], + progress: [], + error: ['$message', '$file'] + }; + + angular.forEach(events, function (eventArgs, eventName) { + var name = 'flow' + capitaliseFirstLetter(eventName); + if (name == 'flowUploadStart') { + name = 'flowUploadStarted';// event alias + } + module.directive(name, [function() { + return { + require: '^flowInit', + controller: ['$scope', '$attrs', function ($scope, $attrs) { + $scope.$on('flow::' + eventName, function () { + var funcArgs = Array.prototype.slice.call(arguments); + var event = funcArgs.shift();// remove angular event + // remove flow object and ignore event if it is from parent directive + if ($scope.$flow !== funcArgs.shift()) { + return ; + } + var args = {}; + angular.forEach(eventArgs, function(value, key) { + args[value] = funcArgs[key]; + }); + if ($scope.$eval($attrs[name], args) === false) { + event.preventDefault(); + } + }); + }] + }; + }]); + }); + + function capitaliseFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } +}(angular); + +angular.module('flow.img', ['flow.init']) +.directive('flowImg', [function() { + return { + 'scope': false, + 'require': '^flowInit', + 'link': function(scope, element, attrs) { + var file = attrs.flowImg; + scope.$watch(file, function (file) { + if (!file) { + return ; + } + var fileReader = new FileReader(); + fileReader.readAsDataURL(file.file); + fileReader.onload = function (event) { + scope.$apply(function () { + attrs.$set('src', event.target.result); + }); + }; + }); + } + }; +}]); +angular.module('flow.transfers', ['flow.init']) +.directive('flowTransfers', [function() { + return { + 'scope': true, + 'require': '^flowInit', + 'link': function(scope) { + scope.transfers = scope.$flow.files; + } + }; +}]); +angular.module('flow', ['flow.provider', 'flow.init', 'flow.events', 'flow.btn', + 'flow.drop', 'flow.transfers', 'flow.img', 'flow.dragEvents']); \ No newline at end of file From 96f42df2d2229611fe0d63496650544694e0b762 Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Wed, 7 Feb 2018 18:00:40 +0100 Subject: [PATCH 03/25] Improved santitation of directory paths --- ajax/upload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ajax/upload.php b/ajax/upload.php index 877972f7..962781fb 100644 --- a/ajax/upload.php +++ b/ajax/upload.php @@ -20,7 +20,7 @@ // Filter paths $path = preg_replace('/(\.\.\/|~|\/\/)/i', '', $request->getRelativePath()); -$path = preg_replace('/([^a-zA-Z0-9]+)/', '', $path); +$path = preg_replace('/[^a-z0-9äöüßáàâãéèêíìîóòõôúùûºªç&$%*#@ \(\)\.\-_\/]/i', '', $path); $path = trim($path, '/'); // Skip existing files // ToDo: Check if file size changed? From 434dfedc4dd25d7e5778d04dff6e65a1ea8c124b Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Mon, 12 Feb 2018 15:50:54 +0100 Subject: [PATCH 04/25] Fix deprecated API call \OCP\addNavigationEntry() --- appinfo/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/app.php b/appinfo/app.php index f39059bf..a895fee5 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -1,6 +1,6 @@ getNavigationManager()->add(array( 'id' => 'flowupload', 'order' => 74, 'href' => \OCP\Util::linkToRoute('flowupload_index'), From 5003da09fd73da669238e3b8362d07fcf5f3ff81 Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Mon, 12 Feb 2018 16:02:21 +0100 Subject: [PATCH 05/25] Remove deprecated PHP requirements --- appinfo/info.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index f680e3fb..9b78d4b4 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -16,6 +16,5 @@ - From b253fb0cec720ed5520a80b8d87e87396c6f8a81 Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Mon, 12 Feb 2018 16:06:42 +0100 Subject: [PATCH 06/25] Remove deprecated file appinfo/version --- appinfo/version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 appinfo/version diff --git a/appinfo/version b/appinfo/version deleted file mode 100644 index 5a5831ab..00000000 --- a/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -0.0.7 From 31bb5aa576be5c65839ea9110f0e011390d7ce1d Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Tue, 13 Feb 2018 11:27:21 +0100 Subject: [PATCH 07/25] Added German translation --- l10n/de.js | 18 ++++++++++++++++++ l10n/de.json | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 l10n/de.js create mode 100644 l10n/de.json diff --git a/l10n/de.js b/l10n/de.js new file mode 100644 index 00000000..d0393156 --- /dev/null +++ b/l10n/de.js @@ -0,0 +1,18 @@ +OC.L10N.register( + "flowupload", { + "FlowUpload" : "Flowupload", + "Select File": "Datei auswählen", + "... or drag and drop your files here" : "... oder ziehen Sie Ihre Dateien hierher", + "Transfers" : "Übertragungen", + "Upload" : "Hochladen", + "Pause" : "Pause", + "Cancel" : "Abbrechen", + "Uploading" : "Wird hochgeladen", + "Size" : "Dateigröße", + "Progress" : "Fortschritt", + "Retry" : "Wiederholen", + "Completed" : "Fertig", + "The files will be saved in your home directory." : "Die Dateien werden in Ihrem Account unter /flowupload gespeichert." + }, + "nplurals=2; plural=(n > 1);" +); diff --git a/l10n/de.json b/l10n/de.json new file mode 100644 index 00000000..40563701 --- /dev/null +++ b/l10n/de.json @@ -0,0 +1,16 @@ +{ "translations": { + "FlowUpload" : "Flowupload", + "Select File": "Datei auswählen", + "... or drag and drop your files here" : "... oder ziehen Sie Ihre Dateien hierher", + "Transfers" : "Übertragungen", + "Upload" : "Hochladen", + "Pause" : "Pause", + "Cancel" : "Abbrechen", + "Uploading" : "Wird hochgeladen", + "Size" : "Dateigröße", + "Progress" : "Fortschritt", + "Retry" : "Wiederholen", + "Completed" : "Fertig", + "The files will be saved in your home directory." : "Die Dateien werden in Ihrem Account unter /flowupload gespeichert." +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} From 6636bbd68caea52084360a2f4e117b044d642032 Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Wed, 14 Feb 2018 15:59:21 +0100 Subject: [PATCH 08/25] Bump supported version and remove Owncloud --- appinfo/info.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 9b78d4b4..11f233c2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -14,7 +14,6 @@ https://github.com/e-alfred/flowupload https://github.com/e-alfred/flowupload/raw/master/appinfo/flowupload.gif - - + From bdb26571a8910de054c342e00e5caaaebd4a2ce1 Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Wed, 14 Feb 2018 16:39:31 +0100 Subject: [PATCH 09/25] Improve translations --- l10n/de.js | 3 ++- l10n/de.json | 2 +- l10n/fr.js | 1 + l10n/fr.json | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/l10n/de.js b/l10n/de.js index d0393156..9ae17132 100644 --- a/l10n/de.js +++ b/l10n/de.js @@ -2,6 +2,7 @@ OC.L10N.register( "flowupload", { "FlowUpload" : "Flowupload", "Select File": "Datei auswählen", + "Select Folder": "Ordner auswählen", "... or drag and drop your files here" : "... oder ziehen Sie Ihre Dateien hierher", "Transfers" : "Übertragungen", "Upload" : "Hochladen", @@ -12,7 +13,7 @@ OC.L10N.register( "Progress" : "Fortschritt", "Retry" : "Wiederholen", "Completed" : "Fertig", - "The files will be saved in your home directory." : "Die Dateien werden in Ihrem Account unter /flowupload gespeichert." + "The files will be saved in your home directory." : "Die Dateien werden in Ihrem Account im Ordner flowupload gespeichert." }, "nplurals=2; plural=(n > 1);" ); diff --git a/l10n/de.json b/l10n/de.json index 40563701..686e7763 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -11,6 +11,6 @@ "Progress" : "Fortschritt", "Retry" : "Wiederholen", "Completed" : "Fertig", - "The files will be saved in your home directory." : "Die Dateien werden in Ihrem Account unter /flowupload gespeichert." + "The files will be saved in your home directory." : "Die Dateien werden in Ihrem Account im Ordner flowupload gespeichert." },"pluralForm" :"nplurals=2; plural=(n > 1);" } diff --git a/l10n/fr.js b/l10n/fr.js index 4661e982..eeceb86b 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -2,6 +2,7 @@ OC.L10N.register( "flowupload", { "FlowUpload" : "Envoyer des fichiers", "Select File": "Sélectionnez un fichier", + "Select Folder": "Sélectionner le dossier", "... or drag and drop your files here" : "... ou déposez ici vos fichiers", "Transfers" : "Transfères", "Upload" : "Envoyer", diff --git a/l10n/fr.json b/l10n/fr.json index ccc9d81a..0ae7a44e 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -1,6 +1,7 @@ { "translations": { "FlowUpload" : "Envoyer des fichiers", "Select File": "Sélectionnez un fichier", + "Select Folder": "Sélectionner le dossier", "... or drag and drop your files here" : "... ou déposez ici vos fichiers", "Transfers" : "Transfères", "Upload" : "Envoyer", From 0a467c116033bc62f4967ec11e176c880e16723a Mon Sep 17 00:00:00 2001 From: mzary Date: Sat, 3 Mar 2018 17:06:48 +0100 Subject: [PATCH 10/25] Create pl.js Proposed Polish translation file --- l10n/pl.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 l10n/pl.js diff --git a/l10n/pl.js b/l10n/pl.js new file mode 100644 index 00000000..c0362feb --- /dev/null +++ b/l10n/pl.js @@ -0,0 +1,20 @@ +OC.L10N.register( + "flowupload", { + "FlowUpload" : "Wyślij pliki", + "Select File": "Wybierz plik", + "Select Folder" : "Wybierz folder", + "... or drag and drop your files here" : "... albo przeciągnij i upuść swoje pliki tutaj", + "Transfers" : "Transfery", + "Upload" : "Wyślij", + "Pause" : "Wstrzymaj", + "Cancel" : "Anuluj", + "Uploading" : "Przesyłanie", + "Size" : "Rozmiar", + "Progress" : "Postęp", + "Retry" : "Ponów", + "Resume" : "Wznów", + "Completed" : "Ukończone", + "The files will be saved in your home directory." : "Pliki zostaną zapisane w twoim katalogu domowym." + }, + "nplurals=2; plural=(n > 1);" +); From 1e0b52561b3bf0fb8f2645e59786f2998569dc78 Mon Sep 17 00:00:00 2001 From: mzary Date: Sat, 3 Mar 2018 17:09:25 +0100 Subject: [PATCH 11/25] Create pl.json Thank you for this app. It works great. Let me propose one more Polish translation file. --- l10n/pl.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 l10n/pl.json diff --git a/l10n/pl.json b/l10n/pl.json new file mode 100644 index 00000000..6a7300ac --- /dev/null +++ b/l10n/pl.json @@ -0,0 +1,18 @@ +{ "translations": { + "FlowUpload" : "Wyślij pliki", + "Select File": "Wybierz plik", + "Select Folder" : "Wybierz folder", + "... or drag and drop your files here" : "... albo przeciągnij i upuść swoje pliki tutaj", + "Transfers" : "Transfery", + "Upload" : "Wyślij", + "Pause" : "Wstrzymaj", + "Cancel" : "Anuluj", + "Uploading" : "Przesyłanie", + "Size" : "Rozmiar", + "Progress" : "Postęp", + "Retry" : "Ponów", + "Resume" : "Wznów", + "Completed" : "Ukończone", + "The files will be saved in your home directory." : "Pliki zostaną zapisane w twoim katalogu domowym." +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} From dee4e37a853bf2aba367c5cf544fbca68a83a095 Mon Sep 17 00:00:00 2001 From: CHazz Date: Mon, 5 Mar 2018 15:26:42 +0100 Subject: [PATCH 12/25] Add files via upload translation to Czech --- l10n/cs.js | 20 ++++++++++++++++++++ l10n/cs.json | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 l10n/cs.js create mode 100644 l10n/cs.json diff --git a/l10n/cs.js b/l10n/cs.js new file mode 100644 index 00000000..cbb0fb4e --- /dev/null +++ b/l10n/cs.js @@ -0,0 +1,20 @@ +OC.L10N.register( + "flowupload", { + "FlowUpload" : "Odeslat soubory", + "Select File": "Vyber Soubor", + "Select Folder" : "Vyber Adresář", + "... or drag and drop your files here" : "... a nebo přetáhni soubory sem", + "Transfers" : "Přenosy", + "Upload" : "Nahrát", + "Pause" : "Pauza", + "Cancel" : "Zrušit", + "Uploading" : "Nahrávání", + "Size" : "Velikost", + "Progress" : "Průběh", + "Retry" : "Znovu", + "Resume" : "Pokračuj", + "Completed" : "Hotovo", + "The files will be saved in your home directory." : "Soubory budou uloženy do vašeho domovského adresáře." + }, + "nplurals=2; plural=(n > 1);" +); diff --git a/l10n/cs.json b/l10n/cs.json new file mode 100644 index 00000000..a7a07a74 --- /dev/null +++ b/l10n/cs.json @@ -0,0 +1,18 @@ +{ "translations": { + "FlowUpload" : "Odeslat soubory", + "Select File": "Vyber Soubor", + "Select Folder" : "Vyber Adresář", + "... or drag and drop your files here" : "... a nebo přetáhni soubory sem", + "Transfers" : "Přenosy", + "Upload" : "Nahrát", + "Pause" : "Pauza", + "Cancel" : "Zrušit", + "Uploading" : "Nahrávání", + "Size" : "Velikost", + "Progress" : "Průběh", + "Retry" : "Znovu", + "Resume" : "Pokračuj", + "Completed" : "Hotovo", + "The files will be saved in your home directory." : "Soubory budou uloženy do vašeho domovského adresáře." +},"pluralForm" :"nplurals=2; plural=(n > 1);" +} From f0c6beeaa7557f74275aa94bf3e7e4c719dc3f6e Mon Sep 17 00:00:00 2001 From: CHazz Date: Mon, 5 Mar 2018 21:12:20 +0100 Subject: [PATCH 13/25] add one string --- l10n/cs.js | 1 + l10n/cs.json | 1 + 2 files changed, 2 insertions(+) diff --git a/l10n/cs.js b/l10n/cs.js index cbb0fb4e..eeab0727 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -10,6 +10,7 @@ OC.L10N.register( "Cancel" : "Zrušit", "Uploading" : "Nahrávání", "Size" : "Velikost", + "Name" : "Jméno", "Progress" : "Průběh", "Retry" : "Znovu", "Resume" : "Pokračuj", diff --git a/l10n/cs.json b/l10n/cs.json index a7a07a74..6753414f 100644 --- a/l10n/cs.json +++ b/l10n/cs.json @@ -9,6 +9,7 @@ "Cancel" : "Zrušit", "Uploading" : "Nahrávání", "Size" : "Velikost", + "Name" : "Jméno", "Progress" : "Průběh", "Retry" : "Znovu", "Resume" : "Pokračuj", From b8c83d85025e5af2d6c184920e0112462cc557a0 Mon Sep 17 00:00:00 2001 From: CHazz Date: Mon, 5 Mar 2018 21:31:38 +0100 Subject: [PATCH 14/25] better .. --- l10n/cs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n/cs.js b/l10n/cs.js index eeab0727..b04d8666 100644 --- a/l10n/cs.js +++ b/l10n/cs.js @@ -10,7 +10,7 @@ OC.L10N.register( "Cancel" : "Zrušit", "Uploading" : "Nahrávání", "Size" : "Velikost", - "Name" : "Jméno", + "Name" : "Jméno", "Progress" : "Průběh", "Retry" : "Znovu", "Resume" : "Pokračuj", From 259120f65a54593de40933832e23a5c6031b1e89 Mon Sep 17 00:00:00 2001 From: e-alfred Date: Wed, 7 Mar 2018 11:35:51 +0000 Subject: [PATCH 15/25] Fixed missing "Select folder" translation --- l10n/de.json | 1 + 1 file changed, 1 insertion(+) diff --git a/l10n/de.json b/l10n/de.json index 686e7763..d3c2964e 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -1,6 +1,7 @@ { "translations": { "FlowUpload" : "Flowupload", "Select File": "Datei auswählen", + "Select Folder": "Ordner auswählen", "... or drag and drop your files here" : "... oder ziehen Sie Ihre Dateien hierher", "Transfers" : "Übertragungen", "Upload" : "Hochladen", From d8bc52e1cbee227869117a789096fe819a098bda Mon Sep 17 00:00:00 2001 From: e-alfred Date: Mon, 12 Mar 2018 16:25:02 +0000 Subject: [PATCH 16/25] Add issue template --- issue_template.md | 132 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 issue_template.md diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 00000000..1755ff06 --- /dev/null +++ b/issue_template.md @@ -0,0 +1,132 @@ + +### Steps to reproduce +1. +2. +3. + +### Expected behaviour +Tell us what should happen + +### Actual behaviour +Tell us what happens instead + +### Server configuration + +**Operating system**: + +**Web server:** + +**Database:** + +**PHP version:** + +**Nextcloud version:** (see Nextcloud admin page) + +**Updated from an older Nextcloud/ownCloud or fresh install:** + +**Where did you install Nextcloud from:** + +**Signing status:** +
+Signing status + +``` +Login as admin user into your Nextcloud and access +http://example.com/index.php/settings/integrity/failed +paste the results here. +``` +
+ +**List of activated apps:** +
+App list + +``` +If you have access to your command line run e.g.: +sudo -u www-data php occ app:list +from within your Nextcloud installation folder +``` +
+ +**Nextcloud configuration:** +
+Config report + +``` +If you have access to your command line run e.g.: +sudo -u www-data php occ config:list system +from within your Nextcloud installation folder + +or + +Insert your config.php content here. +Make sure to remove all sensitive content such as passwords. (e.g. database password, passwordsalt, secret, smtp password, …) +``` +
+ +**Are you using external storage, if yes which one:** local/smb/sftp/... + +**Are you using encryption:** yes/no + +**Are you using an external user-backend, if yes which one:** LDAP/ActiveDirectory/Webdav/... + +#### LDAP configuration (delete this part if not used) +
+LDAP config + +``` +With access to your command line run e.g.: +sudo -u www-data php occ ldap:show-config +from within your Nextcloud installation folder + +Without access to your command line download the data/owncloud.db to your local +computer or access your SQL server remotely and run the select query: +SELECT * FROM `oc_appconfig` WHERE `appid` = 'user_ldap'; + + +Eventually replace sensitive data as the name/IP-address of your LDAP server or groups. +``` +
+ +### Client configuration +**Browser:** + +**Operating system:** + +### Logs +#### Web server error log +
+Web server error log + +``` +Insert your webserver log here +``` +
+ +#### Nextcloud log (data/nextcloud.log) +
+Nextcloud log + +``` +Insert your Nextcloud log here +``` +
+ +#### Browser log +
+Browser log + +``` +Insert your browser log here, this could for example include: + +a) The javascript console log +b) The network log +c) ... +``` +
+ From 1cf3a5944c1621ab9adb7bdb53c7aa5824873803 Mon Sep 17 00:00:00 2001 From: Larsene Date: Thu, 22 Mar 2018 21:39:15 +0100 Subject: [PATCH 17/25] Update fr.js Corrected translation (transfers, uploading) --- l10n/fr.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/l10n/fr.js b/l10n/fr.js index eeceb86b..93b8bf9e 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -4,11 +4,11 @@ OC.L10N.register( "Select File": "Sélectionnez un fichier", "Select Folder": "Sélectionner le dossier", "... or drag and drop your files here" : "... ou déposez ici vos fichiers", - "Transfers" : "Transfères", + "Transfers" : "Transferts", "Upload" : "Envoyer", "Pause" : "Pause", "Cancel" : "Annuler", - "Uploading" : "Envoie en cours", + "Uploading" : "Envoi en cours", "Size" : "Taille", "Progress" : "Progression", "Retry" : "Réessayer", From 7a4441b145305012d6955305a020824857552dfb Mon Sep 17 00:00:00 2001 From: Larsene Date: Thu, 22 Mar 2018 21:44:16 +0100 Subject: [PATCH 18/25] Update fr.json change two bad translations (transfers and uploading) --- l10n/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/l10n/fr.json b/l10n/fr.json index 0ae7a44e..f7cde8a6 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -3,11 +3,11 @@ "Select File": "Sélectionnez un fichier", "Select Folder": "Sélectionner le dossier", "... or drag and drop your files here" : "... ou déposez ici vos fichiers", - "Transfers" : "Transfères", + "Transfers" : "Transferts", "Upload" : "Envoyer", "Pause" : "Pause", "Cancel" : "Annuler", - "Uploading" : "Envoie en cours", + "Uploading" : "Envoi en cours", "Size" : "Taille", "Progress" : "Progression", "Retry" : "Réessayer", From 7df537435b34d2dcd160c21d53bc17631d720f13 Mon Sep 17 00:00:00 2001 From: e-alfred Date: Mon, 26 Mar 2018 13:24:13 +0000 Subject: [PATCH 19/25] Removed deprecated \OCP\Util methods --- appinfo/app.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/appinfo/app.php b/appinfo/app.php index a895fee5..8ca6ab2f 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -1,9 +1,12 @@ getL10N('flowupload'); +$g = \OC::$server->getURLGenerator(); + \OC::$server->getNavigationManager()->add(array( - 'id' => 'flowupload', - 'order' => 74, - 'href' => \OCP\Util::linkToRoute('flowupload_index'), - 'icon' => \OCP\Util::imagePath('flowupload', 'flowupload.svg'), - 'name' => \OC::$server->getL10N('flowupload')->t('FlowUpload') + 'id' => 'flowupload', + 'order' => 74, + 'href' => $g->linkToRoute('flowupload_index'), + 'icon' => $g->imagePath('flowupload', 'flowupload.svg'), + 'name' => 'Flowupload' )); From 0f843fe658a46c00846f3319cd579f70a506059f Mon Sep 17 00:00:00 2001 From: Alfred Egger Date: Mon, 26 Mar 2018 15:42:08 +0200 Subject: [PATCH 20/25] Remove deprecated methods --- ajax/upload.php | 2 +- index.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ajax/upload.php b/ajax/upload.php index 962781fb..5b2e2563 100644 --- a/ajax/upload.php +++ b/ajax/upload.php @@ -1,6 +1,6 @@ Date: Tue, 24 Apr 2018 10:37:34 +0200 Subject: [PATCH 21/25] Redesign Change templates Add location managments --- ajax/locations.php | 62 ++++++++++++++ ajax/upload.php | 4 +- appinfo/routes.php | 5 +- index.php | 12 +++ js/app.js | 59 ++++++++++++- js/script.js | 12 +++ l10n/fr.js | 7 +- l10n/fr.json | 5 ++ templates/main.php | 156 ++++++++++++++++++++++------------ templates/part.content.php | 10 +++ templates/part.navigation.php | 11 +++ 11 files changed, 286 insertions(+), 57 deletions(-) create mode 100644 ajax/locations.php create mode 100644 js/script.js create mode 100644 templates/part.content.php create mode 100644 templates/part.navigation.php diff --git a/ajax/locations.php b/ajax/locations.php new file mode 100644 index 00000000..35162cde --- /dev/null +++ b/ajax/locations.php @@ -0,0 +1,62 @@ + 0, + 'location' => '/flowupload/', + 'pause' => 0, + 'uploading' => 0, + 'completed' => 0, + 'aborted' => 0 + ) + ); + } + + function addNewLocation($location) { + // ToDo: Add to database + return 2; + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $_POST = json_decode(file_get_contents('php://input'), true); + + if (isset($_POST['location']) && \OC\Files\Filesystem::isValidPath($_POST['location'])) { + $locations = getAllLocations(); + + foreach ($locations as $location) { + if ($location['location'] === $_POST['location']) { + \OC_Response::setStatus(409); + die(); + } + } + + \OC_Response::setStatus(201); + + echo json_encode(array( + "new" => array( + 'id' => addNewLocation($_POST['location']), + 'location' => $_POST['location'], + 'pause' => 0, + 'uploading' => 0, + 'completed' => 0, + 'aborted' => 0 + ) + )); + } + else { + \OC_Response::setStatus(400); + } + + die(); + } + + echo json_encode(array( + "locations" => getAllLocations() + )); +?> diff --git a/ajax/upload.php b/ajax/upload.php index 72d4ff46..8190dc5b 100644 --- a/ajax/upload.php +++ b/ajax/upload.php @@ -11,7 +11,7 @@ // Directory definitions $userhome = OC_User::getHome(OC_User::getUser()); $temp = $userhome.'/.flowupload_tmp/'; -$result = '/flowupload/'; +$result = '/tmp/'; // Initialize uploader $config = new \Flow\Config(); @@ -70,4 +70,4 @@ // ToDo: error handling //OCP\JSON::error(array("data" => array("message" => $msg))); //OCP\Util::writeLog('flowupload', "Failed to create file: " . $path, OC_Log::ERROR); -?> \ No newline at end of file +?> diff --git a/appinfo/routes.php b/appinfo/routes.php index df1b7bed..c4682e19 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -4,4 +4,7 @@ ->actionInclude('flowupload/index.php'); $this->create('flowupload_ajax_upload', 'ajax/upload.php') - ->actionInclude('flowupload/ajax/upload.php'); \ No newline at end of file + ->actionInclude('flowupload/ajax/upload.php'); + +$this->create('flowupload_ajax_locations', 'ajax/locations.php') + ->actionInclude('flowupload/ajax/locations.php'); diff --git a/index.php b/index.php index 5318995d..4adc5b8b 100644 --- a/index.php +++ b/index.php @@ -6,7 +6,19 @@ \OCP\Util::addScript('flowupload', 'angular'); \OCP\Util::addScript('flowupload', 'ng-flow-standalone'); \OCP\Util::addScript('flowupload', 'app'); +\OCP\Util::addScript('flowupload', 'script'); \OCP\Util::addStyle('flowupload', 'bootstrap-combined'); +$locations = array( + array( + 'id' => 0, + 'location' => '/flowupload/', + 'nbrInPause' => 0, + 'nbrUploading' => 0, + 'nbrCompleted' => 0, + 'nbrAborted' => 0, + ) +); + $tpl = new OCP\Template("flowupload", "main", "user"); $tpl->printPage(); diff --git a/js/app.js b/js/app.js index 0c85b638..2a786b10 100644 --- a/js/app.js +++ b/js/app.js @@ -28,4 +28,61 @@ app.filter('bytes', function() { number = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; } -}); \ No newline at end of file +}); + +app.controller('flowInfo', function ($scope) { + $scope.beforeUploading = { + query: function (flowFile, flowChunk) { + // function will be called for every request + console.log(flowFile); + console.log(flowChunk); + return { + id: 'Coucou' + }; + } + }; +}); + +app.controller('locations', function ($scope, $http) { + $scope.isOpen = false; + + $scope.seeUploads = function ($event, id, type) { + $event.stopPropagation(); + $event.preventDefault(); + + $('#locations li.active').removeClass('active'); + + $($event.currentTarget).addClass('active'); + $('#location-' + id).addClass('active'); + $('#currentLocation').val(id); + }; + + $scope.getLocations = function () { + $http({ + method: "GET", + url: "ajax/locations.php" + }).then(function mySuccess(response) { + $scope.locations = response.data.locations; + }, function myError(response) { + $scope.locations = {}; + }); + }; + + $scope.getLocations(); + + $scope.addNewLocation = function () { + $http({ + method : "POST", + url : "ajax/locations.php", + data : { + location: $('#newLocationName').val() + } + }).then(function mySuccess(response) { + $('#newLocationName').val(''); + + $scope.locations.push(response.data.new); + }, function myError(response) { + $scope.locations = {}; + }); + }; +}); diff --git a/js/script.js b/js/script.js new file mode 100644 index 00000000..72087f95 --- /dev/null +++ b/js/script.js @@ -0,0 +1,12 @@ +$('li#app-navigation-entry-utils-create').on('click', function() { + $('li#app-navigation-entry-utils-add').addClass('editing'); +}); + +$('li#app-navigation-entry-utils-add .icon-close').on('click', function () { + $('li#app-navigation-entry-utils-add').removeClass('editing'); +}) + +$('li#app-navigation-entry-utils-add .icon-checkmark').on('click', function () { + // Ajouter la destination dans la bdd + $('li#app-navigation-entry-utils-add').removeClass('editing'); +}) diff --git a/l10n/fr.js b/l10n/fr.js index 4661e982..21e7d3cc 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -2,17 +2,22 @@ OC.L10N.register( "flowupload", { "FlowUpload" : "Envoyer des fichiers", "Select File": "Sélectionnez un fichier", + "New destination": "Nouvelle destination", + "Destination name": "Destination", "... or drag and drop your files here" : "... ou déposez ici vos fichiers", "Transfers" : "Transfères", "Upload" : "Envoyer", "Pause" : "Pause", "Cancel" : "Annuler", "Uploading" : "Envoie en cours", + "Waiting" : "En attente", + "Aborted" : "Annulé", + "Name" : "Nom du fichier", "Size" : "Taille", "Progress" : "Progression", "Retry" : "Réessayer", "Completed" : "Complété", - "The files will be saved in your home directory" : "Les fichiers seront sauvegardés dans votre dossier personnel" + "The files will be saved in your home directory." : "Les fichiers seront sauvegardés dans votre dossier personnel." }, "nplurals=2; plural=(n > 1);" ); diff --git a/l10n/fr.json b/l10n/fr.json index ccc9d81a..1425b3ae 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -1,12 +1,17 @@ { "translations": { "FlowUpload" : "Envoyer des fichiers", "Select File": "Sélectionnez un fichier", + "New destination": "Nouvelle destination", + "Destination name": "Destination", "... or drag and drop your files here" : "... ou déposez ici vos fichiers", "Transfers" : "Transfères", "Upload" : "Envoyer", "Pause" : "Pause", "Cancel" : "Annuler", "Uploading" : "Envoie en cours", + "Waiting" : "En attente", + "Aborted" : "Annulé", + "Name" : "Nom du fichier", "Size" : "Taille", "Progress" : "Progression", "Retry" : "Réessayer", diff --git a/templates/main.php b/templates/main.php index 327c3b63..c3aa0c05 100644 --- a/templates/main.php +++ b/templates/main.php @@ -1,58 +1,110 @@ -
+
+
+ + + -
t('... or drag and drop your files here'); ?> +
+
+ +
+
+
+ + +
+ + + Utilisez cette adresse pour accéder à vos fichiers par WebDAV +
+
-
- -

t('Transfers'); ?>

-

- t('Upload'); ?> - t('Pause'); ?> - t('Cancel'); ?> - t('Size'); ?>: {{$flow.getSize() | bytes}} - t('Uploading'); ?>... -

- - - - - - - - - - - - - - - - - -
#t('Name'); ?>t('Size'); ?>t('Progress'); ?>
{{$index+1}}{{file.relativePath}}{{file.size*file.progress() | bytes}}/{{file.size | bytes}} - - t('Completed'); ?> -
-

t('The files will be saved in your home directory.'); ?>

+
+ + t('Select File'); ?> + t('Select Folder'); ?> + +
+ +
t('... or drag and drop your files here'); ?> +
+ +
+ +

t('Transfers'); ?>

+

+ t('Upload'); ?> + t('Pause'); ?> + t('Cancel'); ?> + t('Size'); ?>: {{$flow.getSize() | bytes}} + t('Uploading'); ?>... +

+ + + + + + + + + + + + + + + + + +
#t('Name'); ?>t('Size'); ?>t('Progress'); ?>
{{$index+1}}{{file.relativePath}}{{file.size*file.progress() | bytes}}/{{file.size | bytes}} + + t('Completed'); ?> +
+

t('The files will be saved in your home directory.'); ?>

diff --git a/templates/part.content.php b/templates/part.content.php new file mode 100644 index 00000000..5239375f --- /dev/null +++ b/templates/part.content.php @@ -0,0 +1,10 @@ + +
diff --git a/templates/part.navigation.php b/templates/part.navigation.php new file mode 100644 index 00000000..99891a4b --- /dev/null +++ b/templates/part.navigation.php @@ -0,0 +1,11 @@ +
  • + + + + +
  • From 919129d65d14a0bb6a407124ac7389a561769d03 Mon Sep 17 00:00:00 2001 From: NastuzziSamy Date: Tue, 24 Apr 2018 18:25:34 +0200 Subject: [PATCH 22/25] Preparation of the location feature --- ajax/locations.php | 6 +- js/app.js | 180 +++++++++++++++++++++++++++------------------ l10n/fr.js | 4 +- l10n/fr.json | 2 + templates/main.php | 21 +++--- 5 files changed, 127 insertions(+), 86 deletions(-) diff --git a/ajax/locations.php b/ajax/locations.php index 35162cde..950d90b0 100644 --- a/ajax/locations.php +++ b/ajax/locations.php @@ -20,7 +20,7 @@ function getAllLocations() { function addNewLocation($location) { // ToDo: Add to database - return 2; + return $location; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -39,15 +39,13 @@ function addNewLocation($location) { \OC_Response::setStatus(201); echo json_encode(array( - "new" => array( 'id' => addNewLocation($_POST['location']), 'location' => $_POST['location'], 'pause' => 0, 'uploading' => 0, 'completed' => 0, 'aborted' => 0 - ) - )); + )); } else { \OC_Response::setStatus(400); diff --git a/js/app.js b/js/app.js index 2a786b10..c9ab4916 100644 --- a/js/app.js +++ b/js/app.js @@ -2,87 +2,125 @@ 'use strict'; /** - * The main app module - * @name app - * @type {angular.Module} - */ -var app = angular.module('app', ['flow']) -.config(['flowFactoryProvider', function (flowFactoryProvider) { - flowFactoryProvider.defaults = { - target: 'ajax/upload.php', - permanentErrors: [403, 404, 500, 501], - maxChunkRetries: 2, - chunkRetryInterval: 5000, - simultaneousUploads: 4 - }; - flowFactoryProvider.on('catchAll', function (event) { - console.log('catchAll', arguments); - }); +* The main app module +* @name app +* @type {angular.Module} +*/ +var app = angular.module('app', ['flow']).config(['flowFactoryProvider', function (flowFactoryProvider) { + flowFactoryProvider.defaults = { + target: 'ajax/upload.php', + permanentErrors: [403, 404, 500, 501], + maxChunkRetries: 2, + chunkRetryInterval: 5000, + simultaneousUploads: 4 + }; + flowFactoryProvider.on('catchAll', function ($event, $scope) { + if (arguments[0] === 'fileAdded') + console.log(arguments[1].uniqueIdentifier); + }); }]); -app.filter('bytes', function() { +app.filter('bytes', function () { return function(bytes, precision) { if (isNaN(parseFloat(bytes)) || bytes == 0 || !isFinite(bytes)) return '-'; if (typeof precision === 'undefined') precision = 1; + var units = ['bytes', 'kB', 'MB', 'GB'], - number = Math.floor(Math.log(bytes) / Math.log(1024)); + number = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; } }); -app.controller('flowInfo', function ($scope) { - $scope.beforeUploading = { - query: function (flowFile, flowChunk) { - // function will be called for every request - console.log(flowFile); - console.log(flowChunk); - return { - id: 'Coucou' - }; - } - }; +app.controller('location', function ($scope) { + $scope.init = function (id, $flow) { + $scope.$flow = $flow; + } + + $scope.beforeUploading = { + query: function (flowFile, flowChunk) { + // function will be called for every request + console.log('File', flowFile); + console.log('Chunk', flowChunk); + return { + id: 'Coucou' + }; + } + }; + + $scope.seeUploads = function ($event, type) { + $event.stopPropagation(); + $event.preventDefault(); + + $('#locations li.active').removeClass('active'); + + $($event.currentTarget).addClass('active'); + }; +}); + +app.controller('flow', function ($scope) { + $scope.locationId = 0; + + $scope.$on('changeLocation', function (event, id) { + $scope.locationId = id; + + console.log(id); + }); }); -app.controller('locations', function ($scope, $http) { - $scope.isOpen = false; - - $scope.seeUploads = function ($event, id, type) { - $event.stopPropagation(); - $event.preventDefault(); - - $('#locations li.active').removeClass('active'); - - $($event.currentTarget).addClass('active'); - $('#location-' + id).addClass('active'); - $('#currentLocation').val(id); - }; - - $scope.getLocations = function () { - $http({ - method: "GET", - url: "ajax/locations.php" - }).then(function mySuccess(response) { - $scope.locations = response.data.locations; - }, function myError(response) { - $scope.locations = {}; - }); - }; - - $scope.getLocations(); - - $scope.addNewLocation = function () { - $http({ - method : "POST", - url : "ajax/locations.php", - data : { - location: $('#newLocationName').val() - } - }).then(function mySuccess(response) { - $('#newLocationName').val(''); - - $scope.locations.push(response.data.new); - }, function myError(response) { - $scope.locations = {}; - }); - }; +app.controller('locations', function ($rootScope, $scope, $http) { + $scope.isOpen = false; + $scope.locationId = 0; + + $scope.reloadLocations = function () { + setTimeout(function () { + $scope.setLocation($scope.locationId); + }, 100); + } + + $scope.setLocation = function (id) { + $rootScope.$broadcast('changeLocation', id); + $scope.locationId = id; + + $('.locations').each(function () { + if ($(this).attr('id') === 'location-' + id) + $(this).addClass('open'); + else + $(this).removeClass('open'); + } + ); + } + + $scope.getLocations = function () { + $http({ + method: "GET", + url: "ajax/locations.php" + }).then(function mySuccess(response) { + $scope.locations = response.data.locations; + + $scope.locationId = $scope.locations[0].id; + }, function myError(response) { + $scope.locations = {}; + } + ); + }; + + $scope.addNewLocation = function () { + $http({ + method : "POST", + url : "ajax/locations.php", + data : { + location: $('#newLocationName').val() + } + }).then(function mySuccess(response) { + $('#newLocationName').val(''); + + $scope.locationId = response.data.id; + $scope.locations.push(response.data); + }, function myError(response) { + $scope.locations = {}; + }); + }; + + $scope.getLocations(); }); diff --git a/l10n/fr.js b/l10n/fr.js index 21e7d3cc..ba6c6b04 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -6,7 +6,9 @@ OC.L10N.register( "Destination name": "Destination", "... or drag and drop your files here" : "... ou déposez ici vos fichiers", "Transfers" : "Transfères", - "Upload" : "Envoyer", + "Upload" : "Envoyer", + "Upload a file" : "Envoyer un fichier", + "Upload a whole folder" : "Envoyer un dossier", "Pause" : "Pause", "Cancel" : "Annuler", "Uploading" : "Envoie en cours", diff --git a/l10n/fr.json b/l10n/fr.json index 1425b3ae..90a8c28a 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -6,6 +6,8 @@ "... or drag and drop your files here" : "... ou déposez ici vos fichiers", "Transfers" : "Transfères", "Upload" : "Envoyer", + "Upload a file" : "Envoyer un fichier", + "Upload a whole folder" : "Envoyer un dossier", "Pause" : "Pause", "Cancel" : "Annuler", "Uploading" : "Envoie en cours", diff --git a/templates/main.php b/templates/main.php index c3aa0c05..1810fd14 100644 --- a/templates/main.php +++ b/templates/main.php @@ -1,16 +1,18 @@ -
    +
    t('Select File'); ?> t('Select Folder'); ?> From 12ec459356376c08d80e44fb2ab4286ee3a19aa6 Mon Sep 17 00:00:00 2001 From: NastuzziSamy Date: Tue, 24 Apr 2018 21:42:11 +0200 Subject: [PATCH 23/25] Possibility to upload in diferent locations in the same time --- ajax/locations.php | 10 ++++++-- ajax/upload.php | 3 ++- js/app.js | 50 ++++++++++++++++++++++++---------------- js/ng-flow-standalone.js | 2 +- templates/main.php | 31 ++++++------------------- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/ajax/locations.php b/ajax/locations.php index 950d90b0..ba8f7412 100644 --- a/ajax/locations.php +++ b/ajax/locations.php @@ -20,6 +20,10 @@ function getAllLocations() { function addNewLocation($location) { // ToDo: Add to database + $location = preg_replace('/(\.\.\/|~|\/\/)/i', '', '/'.$location.'/'); + $location = preg_replace('/[^a-z0-9äöüß \(\)\.\-_\/]/i', '', $location); + $location = trim($location); + return $location; } @@ -38,9 +42,11 @@ function addNewLocation($location) { \OC_Response::setStatus(201); + $location = addNewLocation($_POST['location']); + echo json_encode(array( - 'id' => addNewLocation($_POST['location']), - 'location' => $_POST['location'], + 'id' => preg_replace('#/#', '', $location), + 'location' => $location, 'pause' => 0, 'uploading' => 0, 'completed' => 0, diff --git a/ajax/upload.php b/ajax/upload.php index 8190dc5b..145719c5 100644 --- a/ajax/upload.php +++ b/ajax/upload.php @@ -11,7 +11,8 @@ // Directory definitions $userhome = OC_User::getHome(OC_User::getUser()); $temp = $userhome.'/.flowupload_tmp/'; -$result = '/tmp/'; +$result = $_REQUEST['target'] ?? '/flowupload/'; +$result = '/'.$result.'/'; // Initialize uploader $config = new \Flow\Config(); diff --git a/js/app.js b/js/app.js index c9ab4916..7966b37a 100644 --- a/js/app.js +++ b/js/app.js @@ -14,9 +14,9 @@ var app = angular.module('app', ['flow']).config(['flowFactoryProvider', functio chunkRetryInterval: 5000, simultaneousUploads: 4 }; - flowFactoryProvider.on('catchAll', function ($event, $scope) { - if (arguments[0] === 'fileAdded') - console.log(arguments[1].uniqueIdentifier); + flowFactoryProvider.on('catchAll', function ($event, file) { + if ($event === 'fileAdded') + console.log(file); }); }]); @@ -33,53 +33,63 @@ app.filter('bytes', function () { }); app.controller('location', function ($scope) { - $scope.init = function (id, $flow) { - $scope.$flow = $flow; + $scope.init = function (id, name) { + $scope.locationId = id; + $scope.locationName = name; } + $scope.seeUploads = function ($event, type) { + $event.stopPropagation(); + $event.preventDefault(); + + console.log($scope); + + $('#locations li.active').removeClass('active'); + + $($event.currentTarget).addClass('active'); + }; + $scope.beforeUploading = { query: function (flowFile, flowChunk) { // function will be called for every request console.log('File', flowFile); console.log('Chunk', flowChunk); return { - id: 'Coucou' + //id: $scope.locationId + // Temporary: + target: $scope.locationName }; } }; - - $scope.seeUploads = function ($event, type) { - $event.stopPropagation(); - $event.preventDefault(); - - $('#locations li.active').removeClass('active'); - - $($event.currentTarget).addClass('active'); - }; }); app.controller('flow', function ($scope) { $scope.locationId = 0; - $scope.$on('changeLocation', function (event, id) { + $scope.$on('changeLocation', function (event, id, $flow) { $scope.locationId = id; + $scope.$flow = $flow; console.log(id); + console.log($flow); }); }); app.controller('locations', function ($rootScope, $scope, $http) { $scope.isOpen = false; - $scope.locationId = 0; $scope.reloadLocations = function () { setTimeout(function () { - $scope.setLocation($scope.locationId); + if ($scope.locationId === undefined) + $($($('.locations')[0]).find('a')).click(); + else + $($('#location-' + $scope.locationId).find('a')).click(); }, 100); } - $scope.setLocation = function (id) { - $rootScope.$broadcast('changeLocation', id); + $scope.setLocation = function (id, $flow) { + $rootScope.$broadcast('changeLocation', id, $flow); + $scope.$flow = $flow; $scope.locationId = id; $('.locations').each(function () { diff --git a/js/ng-flow-standalone.js b/js/ng-flow-standalone.js index c30c6f0f..99179080 100644 --- a/js/ng-flow-standalone.js +++ b/js/ng-flow-standalone.js @@ -1,2 +1,2 @@ /*! ng-flow 2.6.1 */ -!function(a,b,c){"use strict";function d(b){if(this.support=!("undefined"==typeof File||"undefined"==typeof Blob||"undefined"==typeof FileList||!Blob.prototype.slice&&!Blob.prototype.webkitSlice&&!Blob.prototype.mozSlice),this.support){this.supportDirectory=/WebKit/.test(a.navigator.userAgent),this.files=[],this.defaults={chunkSize:1048576,forceChunkSize:!1,simultaneousUploads:3,singleFile:!1,fileParameterName:"file",progressCallbacksInterval:500,speedSmoothingFactor:.1,query:{},headers:{},withCredentials:!1,preprocess:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1},this.opts={},this.events={};var c=this;this.onDrop=function(a){c.opts.onDropStopPropagation&&a.stopPropagation(),a.preventDefault();var b=a.dataTransfer;b.items&&b.items[0]&&b.items[0].webkitGetAsEntry?c.webkitReadDataTransfer(a):c.addFiles(b.files,a)},this.preventEvent=function(a){a.preventDefault()},this.opts=d.extend({},this.defaults,b||{})}}function e(a,b){this.flowObj=a,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=a.generateUniqueIdentifier(b),this.chunks=[],this.paused=!1,this.error=!1,this.averageSpeed=0,this.currentSpeed=0,this._lastProgressCallback=Date.now(),this._prevUploadedSize=0,this._prevProgress=0,this.bootstrap()}function f(a,b,c){this.flowObj=a,this.fileObj=b,this.fileObjSize=b.size,this.offset=c,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.loaded=0,this.total=0;var d=this.flowObj.opts.chunkSize;this.startByte=this.offset*d,this.endByte=Math.min(this.fileObjSize,(this.offset+1)*d),this.xhr=null,this.fileObjSize-this.endByte-1&&a.splice(c,1)}function h(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function i(a,b){setTimeout(a.bind(b),0)}function j(a){return k(arguments,function(b){b!==a&&k(b,function(b,c){a[c]=b})}),a}function k(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()&&0===a.chunks[0].preprocessState?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(k(this.files,function(a){return a.paused||k(a.chunks,function(a){return"pending"===a.status()&&0===a.preprocessState?(a.send(),b=!0,!1):void 0}),b?!1:void 0}),b)return!0;var c=!1;return k(this.files,function(a){return a.isComplete()?void 0:(c=!0,!1)}),c||a||i(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),j(f.style,{visibility:"hidden",position:"absolute"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),k(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){g.addFiles(a.target.files,a),a.target.value=""},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){a.addEventListener("dragover",this.preventEvent,!1),a.addEventListener("dragenter",this.preventEvent,!1),a.addEventListener("drop",this.onDrop,!1)},this)},unAssignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){a.removeEventListener("dragover",this.preventEvent),a.removeEventListener("dragenter",this.preventEvent),a.removeEventListener("drop",this.onDrop)},this)},isUploading:function(){var a=!1;return k(this.files,function(b){return b.isUploading()?(a=!0,!1):void 0}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return k(this.files,function(d){k(d.chunks,function(d){return"uploading"===d.status()&&(a++,a>=c)?(b=!1,!1):void 0})}),b&&a},upload:function(){var a=this._shouldUploadNext();if(a!==!1){this.fire("uploadStart");for(var b=!1,c=1;c<=this.opts.simultaneousUploads-a;c++)b=this.uploadNextChunk(!0)||b;b||i(function(){this.fire("complete")},this)}},resume:function(){k(this.files,function(a){a.resume()})},pause:function(){k(this.files,function(a){a.pause()})},cancel:function(){for(var a=this.files.length-1;a>=0;a--)this.files[a].cancel()},progress:function(){var a=0,b=0;return k(this.files,function(c){a+=c.progress()*c.size,b+=c.size}),b>0?a/b:0},addFile:function(a,b){this.addFiles([a],b)},addFiles:function(a,b){var c=[];k(a,function(a){if((!l||l&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)&&!this.getFromUniqueIdentifier(this.generateUniqueIdentifier(a))){var d=new e(this,a);this.fire("fileAdded",d,b)&&c.push(d)}},this),this.fire("filesAdded",c,b)&&k(c,function(a){this.opts.singleFile&&this.files.length>0&&this.removeFile(this.files[0]),this.files.push(a)},this),this.fire("filesSubmitted",c,b)},removeFile:function(a){for(var b=this.files.length-1;b>=0;b--)this.files[b]===a&&(this.files.splice(b,1),a.abort())},getFromUniqueIdentifier:function(a){var b=!1;return k(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return k(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return k(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return k(this.files,function(c){c.paused||c.error||(a+=c.size-c.sizeUploaded(),b+=c.averageSpeed)}),a&&!b?Number.POSITIVE_INFINITY:a||b?Math.floor(a/b):0}},e.prototype={measureSpeed:function(){var a=Date.now()-this._lastProgressCallback;if(a){var b=this.flowObj.opts.speedSmoothingFactor,c=this.sizeUploaded();this.currentSpeed=Math.max((c-this._prevUploadedSize)/a*1e3,0),this.averageSpeed=b*this.currentSpeed+(1-b)*this.averageSpeed,this._prevUploadedSize=c}},chunkEvent:function(a,b,c){switch(b){case"progress":if(Date.now()-this._lastProgressCallbackc;c++)this.chunks.push(new f(this.flowObj,this,c))},progress:function(){if(this.error)return 1;if(1===this.chunks.length)return this._prevProgress=Math.max(this._prevProgress,this.chunks[0].progress()),this._prevProgress;var a=0;k(this.chunks,function(b){a+=b.progress()*(b.endByte-b.startByte)});var b=a/this.size;return this._prevProgress=Math.max(this._prevProgress,b>.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return k(this.chunks,function(b){return"uploading"===b.status()?(a=!0,!1):void 0}),a},isComplete:function(){var a=!1;return k(this.chunks,function(b){var c=b.status();return"pending"===c||"uploading"===c||1===b.preprocessState?(a=!0,!1):void 0}),!a},sizeUploaded:function(){var a=0;return k(this.chunks,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){if(this.paused||this.error)return 0;var a=this.size-this.sizeUploaded();return a&&!this.averageSpeed?Number.POSITIVE_INFINITY:a||this.averageSpeed?Math.floor(a/this.averageSpeed):0},getType:function(){return this.file.type&&this.file.type.split("/")[1]},getExtension:function(){return this.name.substr((~-this.name.lastIndexOf(".")>>>0)+2).toLowerCase()}},f.prototype={getParams:function(){return{flowChunkNumber:this.offset+1,flowChunkSize:this.flowObj.opts.chunkSize,flowCurrentChunkSize:this.endByte-this.startByte,flowTotalSize:this.fileObjSize,flowIdentifier:this.fileObj.uniqueIdentifier,flowFilename:this.fileObj.name,flowRelativePath:this.fileObj.relativePath,flowTotalChunks:this.fileObj.chunks.length}},getTarget:function(a,b){return a+=a.indexOf("?")<0?"?":"&",a+b.join("&")},test:function(){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.testHandler,!1),this.xhr.addEventListener("error",this.testHandler,!1);var a=h(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.preprocessState=2,this.send()},send:function(){var a=this.flowObj.opts.preprocess;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1;var b=this.fileObj.file.slice?"slice":this.fileObj.file.mozSlice?"mozSlice":this.fileObj.file.webkitSlice?"webkitSlice":"slice",c=this.fileObj.file[b](this.startByte,this.endByte,this.fileObj.file.type);this.xhr=new XMLHttpRequest,this.xhr.upload.addEventListener("progress",this.progressHandler,!1),this.xhr.addEventListener("load",this.doneHandler,!1),this.xhr.addEventListener("error",this.doneHandler,!1);var d=h(this.flowObj.opts.uploadMethod,this.fileObj,this),e=this.prepareXhrRequest(d,!1,this.flowObj.opts.method,c);this.xhr.send(e)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return this.pendingRetry||1===this.preprocessState?"uploading":this.xhr?this.xhr.readyState<4?"uploading":this.flowObj.opts.successStatuses.indexOf(this.xhr.status)>-1?"success":this.flowObj.opts.permanentErrors.indexOf(this.xhr.status)>-1||!a&&this.retries>=this.flowObj.opts.maxChunkRetries?"error":(this.abort(),"pending"):"pending"},message:function(){return this.xhr?this.xhr.responseText:""},progress:function(){if(this.pendingRetry)return 0;var a=this.status();return"success"===a||"error"===a?1:"pending"===a?0:this.total>0?this.loaded/this.total:0},sizeUploaded:function(){var a=this.endByte-this.startByte;return"success"!==this.status()&&(a=this.progress()*a),a},prepareXhrRequest:function(a,b,c,d){var e=h(this.flowObj.opts.query,this.fileObj,this,b);e=j(this.getParams(),e);var f=h(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var i=[];k(e,function(a,b){i.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,i),g=d||null}else g=new FormData,k(e,function(a,b){g.append(b,a)}),g.append(this.flowObj.opts.fileParameterName,d,this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,k(h(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=h,d.extend=j,d.each=k,d.FlowFile=e,d.FlowChunk=f,d.version="2.9.0","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}(window,document),angular.module("flow.provider",[]).provider("flowFactory",function(){"use strict";this.defaults={},this.factory=function(a){return new Flow(a)},this.events=[],this.on=function(a,b){this.events.push([a,b])},this.$get=function(){var a=this.factory,b=this.defaults,c=this.events;return{create:function(d){var e=a(angular.extend({},b,d));return angular.forEach(c,function(a){e.on(a[0],a[1])}),e}}}}),angular.module("flow.init",["flow.provider"]).controller("flowCtrl",["$scope","$attrs","$parse","flowFactory",function(a,b,c,d){var e=angular.extend({},a.$eval(b.flowInit)),f=a.$eval(b.flowObject)||d.create(e),g=function(b){var c=Array.prototype.slice.call(arguments);c.shift();var d=a.$broadcast.apply(a,["flow::"+b,f].concat(c));return{progress:1,filesSubmitted:1,fileSuccess:1,fileError:1,complete:1}[b]&&a.$apply(),d.defaultPrevented?!1:void 0};f.on("catchAll",g),a.$on("$destroy",function(){f.off("catchAll",g)}),a.$flow=f,b.hasOwnProperty("flowName")&&(c(b.flowName).assign(a,f),a.$on("$destroy",function(){c(b.flowName).assign(a)}))}]).directive("flowInit",[function(){return{scope:!0,controller:"flowCtrl"}}]),angular.module("flow.btn",["flow.init"]).directive("flowBtn",[function(){return{restrict:"EA",scope:!1,require:"^flowInit",link:function(a,b,c){var d=c.hasOwnProperty("flowDirectory"),e=c.hasOwnProperty("flowSingleFile"),f=c.hasOwnProperty("flowAttrs")&&a.$eval(c.flowAttrs);a.$flow.assignBrowse(b,d,e,f)}}}]),angular.module("flow.dragEvents",["flow.init"]).directive("flowPreventDrop",function(){return{scope:!1,link:function(a,b){b.bind("drop dragover",function(a){a.preventDefault()})}}}).directive("flowDragEnter",["$timeout",function(a){return{scope:!1,link:function(b,c,d){function e(a){var b=!1,c=a.dataTransfer||a.originalEvent.dataTransfer;return angular.forEach(c&&c.types,function(a){"Files"===a&&(b=!0)}),b}var f,g=!1;c.bind("dragover",function(c){e(c)&&(g||(b.$apply(d.flowDragEnter),g=!0),a.cancel(f),c.preventDefault())}),c.bind("dragleave drop",function(){a.cancel(f),f=a(function(){b.$eval(d.flowDragLeave),f=null,g=!1},100)})}}}]),angular.module("flow.drop",["flow.init"]).directive("flowDrop",function(){return{scope:!1,require:"^flowInit",link:function(a,b,c){function d(){a.$flow.assignDrop(b)}function e(){a.$flow.unAssignDrop(b)}c.flowDropEnabled?a.$watch(c.flowDropEnabled,function(a){a?d():e()}):d()}}}),!function(a){"use strict";function b(a){return a.charAt(0).toUpperCase()+a.slice(1)}var c=a.module("flow.events",["flow.init"]),d={fileSuccess:["$file","$message"],fileProgress:["$file"],fileAdded:["$file","$event"],filesAdded:["$files","$event"],filesSubmitted:["$files","$event"],fileRetry:["$file"],fileError:["$file","$message"],uploadStart:[],complete:[],progress:[],error:["$message","$file"]};a.forEach(d,function(d,e){var f="flow"+b(e);"flowUploadStart"==f&&(f="flowUploadStarted"),c.directive(f,[function(){return{require:"^flowInit",controller:["$scope","$attrs",function(b,c){b.$on("flow::"+e,function(){var e=Array.prototype.slice.call(arguments),g=e.shift();if(b.$flow===e.shift()){var h={};a.forEach(d,function(a,b){h[a]=e[b]}),b.$eval(c[f],h)===!1&&g.preventDefault()}})}]}}])})}(angular),angular.module("flow.img",["flow.init"]).directive("flowImg",[function(){return{scope:!1,require:"^flowInit",link:function(a,b,c){var d=c.flowImg;a.$watch(d,function(b){if(b){var d=new FileReader;d.readAsDataURL(b.file),d.onload=function(b){a.$apply(function(){c.$set("src",b.target.result)})}}})}}}]),angular.module("flow.transfers",["flow.init"]).directive("flowTransfers",[function(){return{scope:!0,require:"^flowInit",link:function(a){a.transfers=a.$flow.files}}}]),angular.module("flow",["flow.provider","flow.init","flow.events","flow.btn","flow.drop","flow.transfers","flow.img","flow.dragEvents"]); \ No newline at end of file +!function(a,b,c){"use strict";function d(b){if(this.support=!("undefined"==typeof File||"undefined"==typeof Blob||"undefined"==typeof FileList||!Blob.prototype.slice&&!Blob.prototype.webkitSlice&&!Blob.prototype.mozSlice),this.support){this.supportDirectory=/WebKit/.test(a.navigator.userAgent),this.files=[],this.defaults={chunkSize:1048576,forceChunkSize:!1,simultaneousUploads:3,singleFile:!1,fileParameterName:"file",progressCallbacksInterval:500,speedSmoothingFactor:.1,query:{},headers:{},withCredentials:!1,preprocess:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1},this.opts={},this.events={};var c=this;this.onDrop=function(a){c.opts.onDropStopPropagation&&a.stopPropagation(),a.preventDefault();var b=a.dataTransfer;b.items&&b.items[0]&&b.items[0].webkitGetAsEntry?c.webkitReadDataTransfer(a):c.addFiles(b.files,a)},this.preventEvent=function(a){a.preventDefault()},this.opts=d.extend({},this.defaults,b||{})}}function e(a,b){this.flowObj=a,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=a.generateUniqueIdentifier(b),this.chunks=[],this.paused=!1,this.error=!1,this.averageSpeed=0,this.currentSpeed=0,this._lastProgressCallback=Date.now(),this._prevUploadedSize=0,this._prevProgress=0,this.bootstrap()}function f(a,b,c){this.flowObj=a,this.fileObj=b,this.fileObjSize=b.size,this.offset=c,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.loaded=0,this.total=0;var d=this.flowObj.opts.chunkSize;this.startByte=this.offset*d,this.endByte=Math.min(this.fileObjSize,(this.offset+1)*d),this.xhr=null,this.fileObjSize-this.endByte-1&&a.splice(c,1)}function h(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function i(a,b){setTimeout(a.bind(b),0)}function j(a){return k(arguments,function(b){b!==a&&k(b,function(b,c){a[c]=b})}),a}function k(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()&&0===a.chunks[0].preprocessState?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(k(this.files,function(a){return a.paused||k(a.chunks,function(a){return"pending"===a.status()&&0===a.preprocessState?(a.send(),b=!0,!1):void 0}),b?!1:void 0}),b)return!0;var c=!1;return k(this.files,function(a){return a.isComplete()?void 0:(c=!0,!1)}),c||a||i(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),j(f.style,{visibility:"hidden",position:"absolute"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),k(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){g.addFiles(a.target.files,a),a.target.value=""},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){a.addEventListener("dragover",this.preventEvent,!1),a.addEventListener("dragenter",this.preventEvent,!1),a.addEventListener("drop",this.onDrop,!1)},this)},unAssignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),k(a,function(a){a.removeEventListener("dragover",this.preventEvent),a.removeEventListener("dragenter",this.preventEvent),a.removeEventListener("drop",this.onDrop)},this)},isUploading:function(){var a=!1;return k(this.files,function(b){return b.isUploading()?(a=!0,!1):void 0}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return k(this.files,function(d){k(d.chunks,function(d){return"uploading"===d.status()&&(a++,a>=c)?(b=!1,!1):void 0})}),b&&a},upload:function(){var a=this._shouldUploadNext();if(a!==!1){this.fire("uploadStart");for(var b=!1,c=1;c<=this.opts.simultaneousUploads-a;c++)b=this.uploadNextChunk(!0)||b;b||i(function(){this.fire("complete")},this)}},resume:function(){k(this.files,function(a){a.resume()})},pause:function(){k(this.files,function(a){a.pause()})},cancel:function(){for(var a=this.files.length-1;a>=0;a--)this.files[a].cancel()},progress:function(){var a=0,b=0;return k(this.files,function(c){a+=c.progress()*c.size,b+=c.size}),b>0?a/b:0},addFile:function(a,b){this.addFiles([a],b)},addFiles:function(a,b){var c=[];k(a,function(a){if((!l||l&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)&&!this.getFromUniqueIdentifier(this.generateUniqueIdentifier(a))){var d=new e(this,a);this.fire("fileAdded",d,b)&&c.push(d)}},this),this.fire("filesAdded",c,b)&&k(c,function(a){this.opts.singleFile&&this.files.length>0&&this.removeFile(this.files[0]),this.files.push(a)},this),this.fire("filesSubmitted",c,b)},removeFile:function(a){for(var b=this.files.length-1;b>=0;b--)this.files[b]===a&&(this.files.splice(b,1),a.abort())},getFromUniqueIdentifier:function(a){var b=!1;return k(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return k(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return k(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return k(this.files,function(c){c.paused||c.error||(a+=c.size-c.sizeUploaded(),b+=c.averageSpeed)}),a&&!b?Number.POSITIVE_INFINITY:a||b?Math.floor(a/b):0}},e.prototype={measureSpeed:function(){var a=Date.now()-this._lastProgressCallback;if(a){var b=this.flowObj.opts.speedSmoothingFactor,c=this.sizeUploaded();this.currentSpeed=Math.max((c-this._prevUploadedSize)/a*1e3,0),this.averageSpeed=b*this.currentSpeed+(1-b)*this.averageSpeed,this._prevUploadedSize=c}},chunkEvent:function(a,b,c){switch(b){case"progress":if(Date.now()-this._lastProgressCallbackc;c++)this.chunks.push(new f(this.flowObj,this,c))},progress:function(){if(this.error)return 1;if(1===this.chunks.length)return this._prevProgress=Math.max(this._prevProgress,this.chunks[0].progress()),this._prevProgress;var a=0;k(this.chunks,function(b){a+=b.progress()*(b.endByte-b.startByte)});var b=a/this.size;return this._prevProgress=Math.max(this._prevProgress,b>.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return k(this.chunks,function(b){return"uploading"===b.status()?(a=!0,!1):void 0}),a},isComplete:function(){var a=!1;return k(this.chunks,function(b){var c=b.status();return"pending"===c||"uploading"===c||1===b.preprocessState?(a=!0,!1):void 0}),!a},sizeUploaded:function(){var a=0;return k(this.chunks,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){if(this.paused||this.error)return 0;var a=this.size-this.sizeUploaded();return a&&!this.averageSpeed?Number.POSITIVE_INFINITY:a||this.averageSpeed?Math.floor(a/this.averageSpeed):0},getType:function(){return this.file.type&&this.file.type.split("/")[1]},getExtension:function(){return this.name.substr((~-this.name.lastIndexOf(".")>>>0)+2).toLowerCase()}},f.prototype={getParams:function(){return{flowChunkNumber:this.offset+1,flowChunkSize:this.flowObj.opts.chunkSize,flowCurrentChunkSize:this.endByte-this.startByte,flowTotalSize:this.fileObjSize,flowIdentifier:this.fileObj.uniqueIdentifier,flowFilename:this.fileObj.name,flowRelativePath:this.fileObj.relativePath,flowTotalChunks:this.fileObj.chunks.length}},getTarget:function(a,b){return a+=a.indexOf("?")<0?"?":"&",a+b.join("&")},test:function(){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.testHandler,!1),this.xhr.addEventListener("error",this.testHandler,!1);var a=h(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.preprocessState=2,this.send()},send:function(){var a=this.flowObj.opts.preprocess;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1;var b=this.fileObj.file.slice?"slice":this.fileObj.file.mozSlice?"mozSlice":this.fileObj.file.webkitSlice?"webkitSlice":"slice",c=this.fileObj.file[b](this.startByte,this.endByte,this.fileObj.file.type);this.xhr=new XMLHttpRequest,this.xhr.upload.addEventListener("progress",this.progressHandler,!1),this.xhr.addEventListener("load",this.doneHandler,!1),this.xhr.addEventListener("error",this.doneHandler,!1);var d=h(this.flowObj.opts.uploadMethod,this.fileObj,this),e=this.prepareXhrRequest(d,!1,this.flowObj.opts.method,c);this.xhr.send(e)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return this.pendingRetry||1===this.preprocessState?"uploading":this.xhr?this.xhr.readyState<4?"uploading":this.flowObj.opts.successStatuses.indexOf(this.xhr.status)>-1?"success":this.flowObj.opts.permanentErrors.indexOf(this.xhr.status)>-1||!a&&this.retries>=this.flowObj.opts.maxChunkRetries?"error":(this.abort(),"pending"):"pending"},message:function(){return this.xhr?this.xhr.responseText:""},progress:function(){if(this.pendingRetry)return 0;var a=this.status();return"success"===a||"error"===a?1:"pending"===a?0:this.total>0?this.loaded/this.total:0},sizeUploaded:function(){var a=this.endByte-this.startByte;return"success"!==this.status()&&(a=this.progress()*a),a},prepareXhrRequest:function(a,b,c,d){var e=h(this.flowObj.opts.query,this.fileObj,this,b);e=j(this.getParams(),e);var f=h(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var i=[];k(e,function(a,b){i.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,i),g=d||null}else g=new FormData,k(e,function(a,b){g.append(b,a)}),g.append(this.flowObj.opts.fileParameterName,d,this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,k(h(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=h,d.extend=j,d.each=k,d.FlowFile=e,d.FlowChunk=f,d.version="2.9.0","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}(window,document),angular.module("flow.provider",[]).provider("flowFactory",function(){"use strict";this.defaults={},this.factory=function(a){return new Flow(a)},this.events=[],this.on=function(a,b){this.events.push([a,b])},this.$get=function(){var a=this.factory,b=this.defaults,c=this.events;return{create:function(d){var e=a(angular.extend({},b,d));return angular.forEach(c,function(a){e.on(a[0],a[1])}),e}}}}),angular.module("flow.init",["flow.provider"]).controller("flowCtrl",["$scope","$attrs","$parse","flowFactory",function(a,b,c,d){var e=angular.extend({},a.$eval(b.flowInit)),f=a.$eval(b.flowObject)||d.create(e),g=function(b){var c=Array.prototype.slice.call(arguments);c.shift();var d=a.$broadcast.apply(a,["flow::"+b,f].concat(c));return{progress:1,filesSubmitted:1,fileSuccess:1,fileError:1,complete:1}[b]&&a.$apply(),d.defaultPrevented?!1:void 0};f.on("catchAll",g),a.$on("$destroy",function(){f.off("catchAll",g)}),a.$flow=f,b.hasOwnProperty("flowName")&&(c(b.flowName).assign(a,f),a.$on("$destroy",function(){c(b.flowName).assign(a)}))}]).directive("flowInit",[function(){return{scope:!0,controller:"flowCtrl"}}]),angular.module("flow.btn",["flow.init"]).directive("flowBtn",[function(){return{restrict:"EA",scope:!1,require:"^flowInit",link:function(a,b,c){var d=c.hasOwnProperty("flowDirectory"),e=c.hasOwnProperty("flowSingleFile"),f=c.hasOwnProperty("flowAttrs")&&a.$eval(c.flowAttrs);a.$flow.assignBrowse(b,d,e,f)}}}]),angular.module("flow.dragEvents",["flow.init"]).directive("flowPreventDrop",function(){return{scope:!1,link:function(a,b){b.bind("drop dragover",function(a){a.preventDefault()})}}}).directive("flowDragEnter",["$timeout",function(a){return{scope:!1,link:function(b,c,d){function e(a){var b=!1,c=a.dataTransfer||a.originalEvent.dataTransfer;return angular.forEach(c&&c.types,function(a){"Files"===a&&(b=!0)}),b}var f,g=!1;c.bind("dragover",function(c){e(c)&&(g||(b.$apply(d.flowDragEnter),g=!0),a.cancel(f),c.preventDefault())}),c.bind("dragleave drop",function(){a.cancel(f),f=a(function(){b.$eval(d.flowDragLeave),f=null,g=!1},100)})}}}]),angular.module("flow.drop",["flow.init"]).directive("flowDrop",function(){return{scope:!1,require:"^flowInit",link:function(a,b,c){function d(){a.$flow.assignDrop(b)}function e(){a.$flow.unAssignDrop(b)}c.flowDropEnabled?a.$watch(c.flowDropEnabled,function(a){a?d():e()}):d()}}}),!function(a){"use strict";function b(a){return a.charAt(0).toUpperCase()+a.slice(1)}var c=a.module("flow.events",["flow.init"]),d={fileSuccess:["$file","$message"],fileProgress:["$file"],fileAdded:["$file","$event"],filesAdded:["$files","$event"],filesSubmitted:["$files","$event"],fileRetry:["$file"],fileError:["$file","$message"],uploadStart:[],complete:[],progress:[],error:["$message","$file"]};a.forEach(d,function(d,e){var f="flow"+b(e);"flowUploadStart"==f&&(f="flowUploadStarted"),c.directive(f,[function(){return{require:"^flowInit",controller:["$scope","$attrs",function(b,c){b.$on("flow::"+e,function(){var e=Array.prototype.slice.call(arguments),g=e.shift();if(b.$flow===e.shift()){var h={};a.forEach(d,function(a,b){h[a]=e[b]}),b.$eval(c[f],h)===!1&&g.preventDefault()}})}]}}])})}(angular),angular.module("flow.img",["flow.init"]).directive("flowImg",[function(){return{scope:!1,require:"^flowInit",link:function(a,b,c){var d=c.flowImg;a.$watch(d,function(b){if(b){var d=new FileReader;d.readAsDataURL(b.file),d.onload=function(b){a.$apply(function(){c.$set("src",b.target.result)})}}})}}}]),angular.module("flow.transfers",["flow.init"]).directive("flowTransfers",[function(){return{scope:!0,require:"^flowInit",link:function(a){a.transfers=a.$flow.files}}}]),angular.module("flow",["flow.provider","flow.init","flow.events","flow.btn","flow.drop","flow.transfers","flow.img","flow.dragEvents"]); diff --git a/templates/main.php b/templates/main.php index 1810fd14..5a70c7cb 100644 --- a/templates/main.php +++ b/templates/main.php @@ -1,16 +1,11 @@ -
    - - t('Select File'); ?> - t('Select Folder'); ?> - -
    - -
    t('... or drag and drop your files here'); ?> -
    - -
    - -

    t('Transfers'); ?>

    -

    +

    +

    t('Transfers'); ?>

    +

    t('Upload'); ?> t('Pause'); ?> t('Cancel'); ?> @@ -82,7 +65,7 @@ - + {{$index+1}} {{file.relativePath}} {{file.size*file.progress() | bytes}}/{{file.size | bytes}} @@ -107,5 +90,5 @@ -

    t('The files will be saved in your home directory.'); ?>

    +

    t('The files will be saved in your home directory.'); ?>

    From 0dfd86611d95d8d64eb922fab9ccb282f5a32170 Mon Sep 17 00:00:00 2001 From: NastuzziSamy Date: Tue, 24 Apr 2018 21:46:43 +0200 Subject: [PATCH 24/25] Add me as author and update the version --- appinfo/info.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 4af70522..6c1a2558 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -3,10 +3,11 @@ flowupload Flow Upload Provides functions to upload large files with your HTML5-Browser. - 0.0.6 + 0.1.0 AGPL Patrick Schwarz e-alfred + NastuzziSamy tools https://github.com/e-alfred/flowupload https://github.com/e-alfred/flowupload From 076669824ab9b2c5adff95d7e59429507085a01b Mon Sep 17 00:00:00 2001 From: e-alfred Date: Fri, 4 Jan 2019 19:27:32 +0100 Subject: [PATCH 25/25] Fix deprecated calls for NC14+ compatibliity --- ajax/locations.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ajax/locations.php b/ajax/locations.php index 1f6ca637..04d65b8a 100644 --- a/ajax/locations.php +++ b/ajax/locations.php @@ -2,7 +2,7 @@ \OCP\User::checkLoggedIn(); if (!\OCP\App::isEnabled('flowupload')) { - \OC_Response::setStatus(403); + http_response_code(403); } function getAllLocations() { @@ -36,12 +36,12 @@ function addNewLocation($location) { foreach ($locations as $location) { if ($location['location'] === $_POST['location']) { - \OC_Response::setStatus(409); + http_response_code(409); die(); } } - \OC_Response::setStatus(201); + http_response_code(201); $location = addNewLocation($_POST['location']); @@ -55,7 +55,7 @@ function addNewLocation($location) { )); } else { - \OC_Response::setStatus(400); + http_response_code(400); } die();