diff --git a/app/code/core/Mage/Catalog/Helper/Image.php b/app/code/core/Mage/Catalog/Helper/Image.php
index 674b2201d33..62ebbef4ea1 100644
--- a/app/code/core/Mage/Catalog/Helper/Image.php
+++ b/app/code/core/Mage/Catalog/Helper/Image.php
@@ -637,7 +637,7 @@ public function validateUploadFile($filePath)
}
if ($imageInfo[0] > $maxDimension || $imageInfo[1] > $maxDimension) {
- Mage::throwException($this->__('Disalollowed file format.'));
+ Mage::throwException($this->__('Disallowed file format.'));
}
$_processor = new Varien_Image($filePath);
diff --git a/app/code/core/Mage/Uploader/Model/Config/Uploader.php b/app/code/core/Mage/Uploader/Model/Config/Uploader.php
index 43c93231ed9..186fffcff8b 100644
--- a/app/code/core/Mage/Uploader/Model/Config/Uploader.php
+++ b/app/code/core/Mage/Uploader/Model/Config/Uploader.php
@@ -103,14 +103,23 @@ class Mage_Uploader_Model_Config_Uploader extends Mage_Uploader_Model_Config_Abs
*/
protected function _construct()
{
+ // Fix error where setting post_max_size or upload_max_filesize to 0
+ // causes the flow.js to make infinite chunks and crash the browser
+ $maxSize = $this->_getHelper()->getDataMaxSizeInBytes();
+
+ if ($maxSize === 0) {
+ $maxSize = PHP_INT_MAX;
+ }
+
$this
- ->setChunkSize($this->_getHelper()->getDataMaxSizeInBytes())
+ ->setChunkSize($maxSize)
->setWithCredentials(false)
->setForceChunkSize(false)
->setQuery(array(
'form_key' => Mage::getSingleton('core/session')->getFormKey()
))
->setMethod(self::UPLOAD_TYPE)
+ ->setSimultaneousUploads(1)
->setAllowDuplicateUploads(true)
->setPrioritizeFirstAndLastChunk(false)
->setTestChunks(self::TEST_CHUNKS)
diff --git a/app/design/adminhtml/default/default/layout/main.xml b/app/design/adminhtml/default/default/layout/main.xml
index 6d43399f6fb..080a3173f59 100644
--- a/app/design/adminhtml/default/default/layout/main.xml
+++ b/app/design/adminhtml/default/default/layout/main.xml
@@ -158,8 +158,6 @@ Layout for editor element
lib/uploader/flow.min.js
- lib/uploader/fusty-flow.js
- lib/uploader/fusty-flow-factory.js
mage/adminhtml/uploader/instance.js
diff --git a/app/locale/en_US/Mage_Catalog.csv b/app/locale/en_US/Mage_Catalog.csv
index 2b7adbd41bd..6f461c89f46 100644
--- a/app/locale/en_US/Mage_Catalog.csv
+++ b/app/locale/en_US/Mage_Catalog.csv
@@ -258,7 +258,7 @@
"Details","Details"
"Disabled","Disabled"
"Disallowed file type.","Disallowed file type."
-"Disalollowed file format.","Disalollowed file format."
+"Disallowed file format.","Disallowed file format."
"Display Actual Price","Display Actual Price"
"Display Page Control","Display Page Control"
"Display Price Interval as One Price","Display Price Interval as One Price"
diff --git a/js/lib/uploader/flow.js b/js/lib/uploader/flow.js
new file mode 100644
index 00000000000..e61f0a75bcd
--- /dev/null
+++ b/js/lib/uploader/flow.js
@@ -0,0 +1,1664 @@
+/**
+ * @license MIT
+ */
+(function(window, document, undefined) {'use strict';
+ if (!window || !document) {
+ console.warn('Flowjs needs window and document objects to work');
+ return;
+ }
+ // 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|Function} [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,
+ changeRawDataBeforeSend: 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) {
+ decrement();
+ 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);
+
+ /**
+ * Size of Each Chunk
+ * @type {number}
+ */
+ this.chunkSize = 0;
+
+ /**
+ * 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;
+ this.chunkSize = evalOpts(this.flowObj.opts.chunkSize, this);
+ var chunks = Math.max(
+ round(this.size / this.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.fileObj.chunkSize;
+
+ /**
+ * Chunk start byte in a file
+ * @type {number}
+ */
+ this.startByte = this.offset * this.chunkSize;
+
+ /**
+ * A specific filename for this chunk which otherwise default to the main name
+ * @type {string}
+ */
+ this.filename = null;
+
+ /**
+ * 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 if (!$.fileObj.paused) {
+ $.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.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 (params.length == 0) {
+ return target;
+ }
+
+ 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);
+ var changeRawDataBeforeSend = this.flowObj.opts.changeRawDataBeforeSend;
+ if (typeof changeRawDataBeforeSend === 'function') {
+ data = changeRawDataBeforeSend(this, data);
+ }
+ 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.filename || 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.14.1';
+
+ 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; } );
+ }
+ }
+})(typeof window !== 'undefined' && window, typeof document !== 'undefined' && document);
diff --git a/js/lib/uploader/flow.min.js b/js/lib/uploader/flow.min.js
index 34b888e7efd..ef301778ad8 100644
--- a/js/lib/uploader/flow.min.js
+++ b/js/lib/uploader/flow.min.js
@@ -1,2 +1,2 @@
-/*! flow.js 2.9.0 */
-!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);
\ No newline at end of file
+/*! @flowjs/flow.js 2.14.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=/Chrome/.test(a.navigator.userAgent)||/Firefox/.test(a.navigator.userAgent)||/Edge/.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,changeRawDataBeforeSend:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,allowDuplicateUploads:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,413,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1,initFileFn:null,readFileFn:f},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,d){this.flowObj=a,this.bytes=null,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=d===c?a.generateUniqueIdentifier(b):d,this.chunkSize=0,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,d,e){var f="slice";a.file.slice?f="slice":a.file.mozSlice?f="mozSlice":a.file.webkitSlice&&(f="webkitSlice"),e.readFinished(a.file[f](b,c,d))}function g(a,b,c){this.flowObj=a,this.fileObj=b,this.offset=c,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.readState=0,this.loaded=0,this.total=0,this.chunkSize=this.fileObj.chunkSize,this.startByte=this.offset*this.chunkSize,this.filename=null,this.computeEndByte=function(){var a=Math.min(this.fileObj.size,(this.offset+1)*this.chunkSize);return this.fileObj.size-a-1&&a.splice(c,1)}function i(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function j(a,b){setTimeout(a.bind(b),0)}function k(a,b){return l(arguments,function(b){b!==a&&l(b,function(b,c){a[c]=b})}),a}function l(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(l(this.files,function(a){if(a.paused||l(a.chunks,function(a){if("pending"===a.status())return a.send(),b=!0,!1}),b)return!1}),b)return!0;var c=!1;return l(this.files,function(a){if(!a.isComplete())return c=!0,!1}),c||a||j(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){a instanceof Element&&(a=[a]),l(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),k(f.style,{visibility:"hidden",position:"absolute",width:"1px",height:"1px"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),l(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){a.target.value&&(g.addFiles(a.target.files,a),a.target.value="")},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(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]),l(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 l(this.files,function(b){if(b.isUploading())return a=!0,!1}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return l(this.files,function(d){l(d.chunks,function(d){if("uploading"===d.status()&&(a++,a>=c))return b=!1,!1})}),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||j(function(){this.fire("complete")},this)}},resume:function(){l(this.files,function(a){a.isComplete()||a.resume()})},pause:function(){l(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 l(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=[];l(a,function(a){if((!m||m&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)){var d=this.generateUniqueIdentifier(a);if(this.opts.allowDuplicateUploads||!this.getFromUniqueIdentifier(d)){var f=new e(this,a,d);this.fire("fileAdded",f,b)&&c.push(f)}}},this),this.fire("filesAdded",c,b)&&(l(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(),this.fire("fileRemoved",a))},getFromUniqueIdentifier:function(a){var b=!1;return l(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return l(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return l(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return l(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._lastProgressCallback.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return l(this.chunks,function(b){if("uploading"===b.status())return a=!0,!1}),a},isComplete:function(){var a=!1;return l(this.chunks,function(b){var c=b.status();if("pending"===c||"uploading"===c||"reading"===c||1===b.preprocessState||1===b.readState)return a=!0,!1}),!a},sizeUploaded:function(){var a=0;return l(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()}},g.prototype={getParams:function(){return{flowChunkNumber:this.offset+1,flowChunkSize:this.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}},getTarget:function(a,b){return 0==b.length?a:(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=i(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.endByte=this.computeEndByte(),this.preprocessState=2,this.send()},readFinished:function(a){this.readState=2,this.bytes=a,this.send()},send:function(){var a=this.flowObj.opts.preprocess,b=this.flowObj.opts.readFileFn;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}switch(this.readState){case 0:return this.readState=1,void b(this.fileObj,this.startByte,this.endByte,this.fileObj.file.type,this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1,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 c=i(this.flowObj.opts.uploadMethod,this.fileObj,this),d=this.prepareXhrRequest(c,!1,this.flowObj.opts.method,this.bytes),e=this.flowObj.opts.changeRawDataBeforeSend;"function"==typeof e&&(d=e(this,d)),this.xhr.send(d)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return 1===this.readState?"reading":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=i(this.flowObj.opts.query,this.fileObj,this,b);e=k(e||{},this.getParams());var f=i(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var h=[];l(e,function(a,b){h.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,h),g=d||null}else g=new FormData,l(e,function(a,b){g.append(b,a)}),"undefined"!=typeof d&&g.append(this.flowObj.opts.fileParameterName,d,this.filename||this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,l(i(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=i,d.extend=k,d.each=l,d.FlowFile=e,d.FlowChunk=g,d.version="2.14.1","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}("undefined"!=typeof window&&window,"undefined"!=typeof document&&document);
\ No newline at end of file
diff --git a/js/lib/uploader/fusty-flow-factory.js b/js/lib/uploader/fusty-flow-factory.js
deleted file mode 100644
index 3d09bb08b5b..00000000000
--- a/js/lib/uploader/fusty-flow-factory.js
+++ /dev/null
@@ -1,14 +0,0 @@
-(function (Flow, FustyFlow, window) {
- 'use strict';
-
- var fustyFlowFactory = function (opts) {
- var flow = new Flow(opts);
- if (flow.support) {
- return flow;
- }
- return new FustyFlow(opts);
- }
-
- window.fustyFlowFactory = fustyFlowFactory;
-
-})(window.Flow, window.FustyFlow, window);
diff --git a/js/lib/uploader/fusty-flow.js b/js/lib/uploader/fusty-flow.js
deleted file mode 100644
index 78b35425e70..00000000000
--- a/js/lib/uploader/fusty-flow.js
+++ /dev/null
@@ -1,429 +0,0 @@
-(function (Flow, window, document, undefined) {
- 'use strict';
-
- var extend = Flow.extend;
- var each = Flow.each;
-
- function addEvent(element, type, handler) {
- if (element.addEventListener) {
- element.addEventListener(type, handler, false);
- } else if (element.attachEvent) {
- element.attachEvent("on" + type, handler);
- } else {
- element["on" + type] = handler;
- }
- }
-
- function removeEvent(element, type, handler) {
- if (element.removeEventListener) {
- element.removeEventListener(type, handler, false);
- } else if (element.detachEvent) {
- element.detachEvent("on" + type, handler);
- } else {
- element["on" + type] = null;
- }
- }
-
- function removeElement(element) {
- element.parentNode.removeChild(element);
- }
-
- function isFunction(functionToCheck) {
- var getType = {};
- return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
- }
-
- /**
- * Not resumable file upload library, for IE7-IE9 browsers
- * @name FustyFlow
- * @param [opts]
- * @param {bool} [opts.singleFile]
- * @param {string} [opts.fileParameterName]
- * @param {Object|Function} [opts.query]
- * @param {Object} [opts.headers]
- * @param {string} [opts.target]
- * @param {Function} [opts.generateUniqueIdentifier]
- * @param {bool} [opts.matchJSON]
- * @constructor
- */
- function FustyFlow(opts) {
- // Shortcut of "r instanceof Flow"
- this.support = false;
-
- this.files = [];
- this.events = [];
- this.defaults = {
- simultaneousUploads: 3,
- fileParameterName: 'file',
- query: {},
- target: '/',
- generateUniqueIdentifier: null,
- matchJSON: false
- };
-
- var $ = this;
-
- this.inputChangeEvent = function (event) {
- var input = event.target || event.srcElement;
- removeEvent(input, 'change', $.inputChangeEvent);
- var newClone = input.cloneNode(false);
- // change current input with new one
- input.parentNode.replaceChild(newClone, input);
- // old input will be attached to hidden form
- $.addFile(input, event);
- // reset new input
- newClone.value = '';
- addEvent(newClone, 'change', $.inputChangeEvent);
- };
-
- this.opts = Flow.extend({}, this.defaults, opts || {});
- }
-
- FustyFlow.prototype = {
- on: Flow.prototype.on,
- off: Flow.prototype.off,
- fire: Flow.prototype.fire,
- cancel: Flow.prototype.cancel,
- assignBrowse: function (domNodes) {
- if (typeof domNodes.length == 'undefined') {
- 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');
-
- extend(domNode.style, {
- display: 'inline-block',
- position: 'relative',
- overflow: 'hidden',
- verticalAlign: 'top'
- });
-
- extend(input.style, {
- position: 'absolute',
- top: 0,
- right: 0,
- fontFamily: 'Arial',
- // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
- fontSize: '118px',
- margin: 0,
- padding: 0,
- opacity: 0,
- filter: 'alpha(opacity=0)',
- cursor: 'pointer'
- });
-
- domNode.appendChild(input);
- }
- // When new files are added, simply append them to the overall list
- addEvent(input, 'change', this.inputChangeEvent);
- }, this);
- },
- assignDrop: function () {
- // not supported
- },
- unAssignDrop: function () {
- // not supported
- },
- isUploading: function () {
- var uploading = false;
- each(this.files, function (file) {
- if (file.isUploading()) {
- uploading = true;
- return false;
- }
- });
- return uploading;
- },
- upload: function () {
- // Kick off the queue
- var files = 0;
- each(this.files, function (file) {
- if (file.progress() == 1 || file.isPaused()) {
- return;
- }
- if (file.isUploading()) {
- files++;
- return;
- }
- if (files++ >= this.opts.simultaneousUploads) {
- return false;
- }
- if (files == 1) {
- this.fire('uploadStart');
- }
- file.send();
- }, this);
- if (!files) {
- this.fire('complete');
- }
- },
- pause: function () {
- each(this.files, function (file) {
- file.pause();
- });
- },
- resume: function () {
- each(this.files, function (file) {
- file.resume();
- });
- },
- progress: function () {
- var totalDone = 0;
- var totalFiles = 0;
- each(this.files, function (file) {
- totalDone += file.progress();
- totalFiles++;
- });
- return totalFiles > 0 ? totalDone / totalFiles : 0;
- },
- addFiles: function (elementsList, event) {
- var files = [];
- each(elementsList, function (element) {
- // is domElement ?
- if (element.nodeType === 1 && element.value) {
- var f = new FustyFlowFile(this, element);
- 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);
- },
- addFile: function (file, event) {
- this.addFiles([file], event);
- },
- generateUniqueIdentifier: function (element) {
- var custom = this.opts.generateUniqueIdentifier;
- if (typeof custom === 'function') {
- return custom(element);
- }
- return 'xxxxxxxx-xxxx-yxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
- var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
- return v.toString(16);
- });
- },
- getFromUniqueIdentifier: function (uniqueIdentifier) {
- var ret = false;
- each(this.files, function (f) {
- if (f.uniqueIdentifier == uniqueIdentifier) ret = f;
- });
- return ret;
- },
- removeFile: function (file) {
- for (var i = this.files.length - 1; i >= 0; i--) {
- if (this.files[i] === file) {
- this.files.splice(i, 1);
- }
- }
- },
- getSize: function () {
- // undefined
- },
- timeRemaining: function () {
- // undefined
- },
- sizeUploaded: function () {
- // undefined
- }
- };
-
- function FustyFlowFile(flowObj, element) {
- this.flowObj = flowObj;
- this.element = element;
- this.name = element.value && element.value.replace(/.*(\/|\\)/, "");
- this.relativePath = this.name;
- this.uniqueIdentifier = flowObj.generateUniqueIdentifier(element);
- this.iFrame = null;
-
- this.finished = false;
- this.error = false;
- this.paused = false;
-
- var $ = this;
- this.iFrameLoaded = function (event) {
- // when we remove iframe from dom
- // the request stops, but in IE load
- // event fires
- if (!$.iFrame || !$.iFrame.parentNode) {
- return;
- }
- $.finished = true;
- try {
- // fixing Opera 10.53
- if ($.iFrame.contentDocument &&
- $.iFrame.contentDocument.body &&
- $.iFrame.contentDocument.body.innerHTML == "false") {
- // In Opera event is fired second time
- // when body.innerHTML changed from false
- // to server response approx. after 1 sec
- // when we upload file with iframe
- return;
- }
- } catch (error) {
- //IE may throw an "access is denied" error when attempting to access contentDocument
- $.error = true;
- $.abort();
- $.flowObj.fire('fileError', $, error);
- return;
- }
- // iframe.contentWindow.document - for IE<7
- var doc = $.iFrame.contentDocument || $.iFrame.contentWindow.document;
- var innerHtml = doc.body.innerHTML;
- if ($.flowObj.opts.matchJSON) {
- innerHtml = /(\{.*\})/.exec(innerHtml)[0];
- }
-
- $.abort();
- $.flowObj.fire('fileSuccess', $, innerHtml);
- $.flowObj.upload();
- };
- this.bootstrap();
- }
-
- FustyFlowFile.prototype = {
- getExtension: Flow.FlowFile.prototype.getExtension,
- getType: function () {
- // undefined
- },
- send: function () {
- if (this.finished) {
- return;
- }
- var o = this.flowObj.opts;
- var form = this.createForm();
- var params = o.query;
- if (isFunction(params)) {
- params = params(this);
- }
- params[o.fileParameterName] = this.element;
- params['flowFilename'] = this.name;
- params['flowRelativePath'] = this.relativePath;
- params['flowIdentifier'] = this.uniqueIdentifier;
-
- this.addFormParams(form, params);
- addEvent(this.iFrame, 'load', this.iFrameLoaded);
- form.submit();
- removeElement(form);
- },
- abort: function (noupload) {
- if (this.iFrame) {
- this.iFrame.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;');
- removeElement(this.iFrame);
- this.iFrame = null;
- !noupload && this.flowObj.upload();
- }
- },
- cancel: function () {
- this.flowObj.removeFile(this);
- this.abort();
- },
- retry: function () {
- this.bootstrap();
- this.flowObj.upload();
- },
- bootstrap: function () {
- this.abort(true);
- this.finished = false;
- this.error = false;
- },
- timeRemaining: function () {
- // undefined
- },
- sizeUploaded: function () {
- // undefined
- },
- resume: function () {
- this.paused = false;
- this.flowObj.upload();
- },
- pause: function () {
- this.paused = true;
- this.abort();
- },
- isUploading: function () {
- return this.iFrame !== null;
- },
- isPaused: function () {
- return this.paused;
- },
- isComplete: function () {
- return this.progress() === 1;
- },
- progress: function () {
- if (this.error) {
- return 1;
- }
- return this.finished ? 1 : 0;
- },
-
- createIframe: function () {
- var iFrame = (/MSIE (6|7|8)/).test(navigator.userAgent) ?
- document.createElement('