|
@@ -1,514 +0,0 @@
|
|
|
-var utils = require('./utils')
|
|
|
-var event = require('./event')
|
|
|
-var File = require('./file')
|
|
|
-var Chunk = require('./chunk')
|
|
|
-
|
|
|
-var version = '__VERSION__'
|
|
|
-
|
|
|
-var isServer = typeof window === 'undefined'
|
|
|
-
|
|
|
-// ie10+
|
|
|
-var ie10plus = isServer ? false : window.navigator.msPointerEnabled
|
|
|
-var support = (function () {
|
|
|
- if (isServer) {
|
|
|
- return false
|
|
|
- }
|
|
|
- var sliceName = 'slice'
|
|
|
- var _support = utils.isDefined(window.File) && utils.isDefined(window.Blob) &&
|
|
|
- utils.isDefined(window.FileList)
|
|
|
- var bproto = null
|
|
|
- if (_support) {
|
|
|
- bproto = window.Blob.prototype
|
|
|
- utils.each(['slice', 'webkitSlice', 'mozSlice'], function (n) {
|
|
|
- if (bproto[n]) {
|
|
|
- sliceName = n
|
|
|
- return false
|
|
|
- }
|
|
|
- })
|
|
|
- _support = !!bproto[sliceName]
|
|
|
- }
|
|
|
- if (_support) Uploader.sliceName = sliceName
|
|
|
- bproto = null
|
|
|
- return _support
|
|
|
-})()
|
|
|
-
|
|
|
-var supportDirectory = (function () {
|
|
|
- if (isServer) {
|
|
|
- return false
|
|
|
- }
|
|
|
- var input = window.document.createElement('input')
|
|
|
- input.type = 'file'
|
|
|
- var sd = 'webkitdirectory' in input || 'directory' in input
|
|
|
- input = null
|
|
|
- return sd
|
|
|
-})()
|
|
|
-
|
|
|
-function Uploader (opts) {
|
|
|
- this.support = support
|
|
|
- /* istanbul ignore if */
|
|
|
- if (!this.support) {
|
|
|
- return
|
|
|
- }
|
|
|
- this.supportDirectory = supportDirectory
|
|
|
- utils.defineNonEnumerable(this, 'filePaths', {})
|
|
|
- this.opts = utils.extend({}, Uploader.defaults, opts || {})
|
|
|
-
|
|
|
- this.preventEvent = utils.bind(this._preventEvent, this)
|
|
|
-
|
|
|
- File.call(this, this)
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * Default read function using the webAPI
|
|
|
- *
|
|
|
- * @function webAPIFileRead(fileObj, fileType, startByte, endByte, chunk)
|
|
|
- *
|
|
|
- */
|
|
|
-var webAPIFileRead = function (fileObj, fileType, startByte, endByte, chunk) {
|
|
|
- chunk.readFinished(fileObj.file[Uploader.sliceName](startByte, endByte, fileType))
|
|
|
-}
|
|
|
-
|
|
|
-Uploader.version = version
|
|
|
-
|
|
|
-Uploader.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, 415, 500, 501],
|
|
|
- successStatuses: [200, 201, 202],
|
|
|
- onDropStopPropagation: false,
|
|
|
- initFileFn: null,
|
|
|
- readFileFn: webAPIFileRead,
|
|
|
- checkChunkUploadedByResponse: null,
|
|
|
- initialPaused: false,
|
|
|
- processResponse: function (response, cb) {
|
|
|
- cb(null, response)
|
|
|
- },
|
|
|
- processParams: function (params) {
|
|
|
- return params
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-Uploader.utils = utils
|
|
|
-Uploader.event = event
|
|
|
-Uploader.File = File
|
|
|
-Uploader.Chunk = Chunk
|
|
|
-
|
|
|
-// inherit file
|
|
|
-Uploader.prototype = utils.extend({}, File.prototype)
|
|
|
-// inherit event
|
|
|
-utils.extend(Uploader.prototype, event)
|
|
|
-utils.extend(Uploader.prototype, {
|
|
|
-
|
|
|
- constructor: Uploader,
|
|
|
-
|
|
|
- _trigger: function (name) {
|
|
|
- var args = utils.toArray(arguments)
|
|
|
- var preventDefault = !this.trigger.apply(this, arguments)
|
|
|
- if (name !== 'catchAll') {
|
|
|
- args.unshift('catchAll')
|
|
|
- preventDefault = !this.trigger.apply(this, args) || preventDefault
|
|
|
- }
|
|
|
- return !preventDefault
|
|
|
- },
|
|
|
-
|
|
|
- _triggerAsync: function () {
|
|
|
- var args = arguments
|
|
|
- utils.nextTick(function () {
|
|
|
- this._trigger.apply(this, args)
|
|
|
- }, this)
|
|
|
- },
|
|
|
-
|
|
|
- addFiles: function (files, evt) {
|
|
|
- var _files = []
|
|
|
- var oldFileListLen = this.fileList.length
|
|
|
- utils.each(files, function (file) {
|
|
|
- // Uploading empty file IE10/IE11 hangs indefinitely
|
|
|
- // Directories have size `0` and name `.`
|
|
|
- // Ignore already added files if opts.allowDuplicateUploads is set to false
|
|
|
- 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 _file = new File(this, file, this)
|
|
|
- _file.uniqueIdentifier = uniqueIdentifier
|
|
|
- if (this._trigger('fileAdded', _file, evt)) {
|
|
|
- _files.push(_file)
|
|
|
- } else {
|
|
|
- File.prototype.removeFile.call(this, _file)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }, this)
|
|
|
- // get new fileList
|
|
|
- var newFileList = this.fileList.slice(oldFileListLen)
|
|
|
- if (this._trigger('filesAdded', _files, newFileList, evt)) {
|
|
|
- utils.each(_files, function (file) {
|
|
|
- if (this.opts.singleFile && this.files.length > 0) {
|
|
|
- this.removeFile(this.files[0])
|
|
|
- }
|
|
|
- this.files.push(file)
|
|
|
- }, this)
|
|
|
- this._trigger('filesSubmitted', _files, newFileList, evt)
|
|
|
- } else {
|
|
|
- utils.each(newFileList, function (file) {
|
|
|
- File.prototype.removeFile.call(this, file)
|
|
|
- }, this)
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- addFile: function (file, evt) {
|
|
|
- this.addFiles([file], evt)
|
|
|
- },
|
|
|
-
|
|
|
- cancel: function () {
|
|
|
- for (var i = this.fileList.length - 1; i >= 0; i--) {
|
|
|
- this.fileList[i].cancel()
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- removeFile: function (file) {
|
|
|
- File.prototype.removeFile.call(this, file)
|
|
|
- this._trigger('fileRemoved', file)
|
|
|
- },
|
|
|
-
|
|
|
- generateUniqueIdentifier: function (file) {
|
|
|
- var custom = this.opts.generateUniqueIdentifier
|
|
|
- if (utils.isFunction(custom)) {
|
|
|
- return custom(file)
|
|
|
- }
|
|
|
- /* istanbul ignore next */
|
|
|
- // Some confusion in different versions of Firefox
|
|
|
- var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name
|
|
|
- /* istanbul ignore next */
|
|
|
- return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')
|
|
|
- },
|
|
|
-
|
|
|
- getFromUniqueIdentifier: function (uniqueIdentifier) {
|
|
|
- var ret = false
|
|
|
- utils.each(this.files, function (file) {
|
|
|
- if (file.uniqueIdentifier === uniqueIdentifier) {
|
|
|
- ret = file
|
|
|
- return false
|
|
|
- }
|
|
|
- })
|
|
|
- return ret
|
|
|
- },
|
|
|
-
|
|
|
- uploadNextChunk: function (preventEvents) {
|
|
|
- var found = false
|
|
|
- var pendingStatus = Chunk.STATUS.PENDING
|
|
|
- var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
|
|
|
- if (this.opts.prioritizeFirstAndLastChunk) {
|
|
|
- utils.each(this.files, function (file) {
|
|
|
- if (file.paused) {
|
|
|
- return
|
|
|
- }
|
|
|
- if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
|
|
|
- // waiting for current file's first chunk response
|
|
|
- return
|
|
|
- }
|
|
|
- if (file.chunks.length && file.chunks[0].status() === pendingStatus) {
|
|
|
- file.chunks[0].send()
|
|
|
- found = true
|
|
|
- return false
|
|
|
- }
|
|
|
- if (file.chunks.length > 1 && file.chunks[file.chunks.length - 1].status() === pendingStatus) {
|
|
|
- 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
|
|
|
- utils.each(this.files, function (file) {
|
|
|
- if (!file.paused) {
|
|
|
- if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
|
|
|
- // waiting for current file's first chunk response
|
|
|
- return
|
|
|
- }
|
|
|
- utils.each(file.chunks, function (chunk) {
|
|
|
- if (chunk.status() === pendingStatus) {
|
|
|
- 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
|
|
|
- utils.each(this.files, function (file) {
|
|
|
- if (!file.isComplete()) {
|
|
|
- outstanding = true
|
|
|
- return false
|
|
|
- }
|
|
|
- })
|
|
|
- // should check files now
|
|
|
- // if now files in list
|
|
|
- // should not trigger complete event
|
|
|
- if (!outstanding && !preventEvents && this.files.length) {
|
|
|
- // All chunks have been uploaded, complete
|
|
|
- this._triggerAsync('complete')
|
|
|
- }
|
|
|
- return outstanding
|
|
|
- },
|
|
|
-
|
|
|
- upload: function (preventEvents) {
|
|
|
- // Make sure we don't start too many uploads at once
|
|
|
- var ret = this._shouldUploadNext()
|
|
|
- if (ret === false) {
|
|
|
- return
|
|
|
- }
|
|
|
- !preventEvents && this._trigger('uploadStart')
|
|
|
- var started = false
|
|
|
- for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
|
|
|
- started = this.uploadNextChunk(!preventEvents) || started
|
|
|
- if (!started && preventEvents) {
|
|
|
- // completed
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- if (!started && !preventEvents) {
|
|
|
- this._triggerAsync('complete')
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * should upload next chunk
|
|
|
- * @function
|
|
|
- * @returns {Boolean|Number}
|
|
|
- */
|
|
|
- _shouldUploadNext: function () {
|
|
|
- var num = 0
|
|
|
- var should = true
|
|
|
- var simultaneousUploads = this.opts.simultaneousUploads
|
|
|
- var uploadingStatus = Chunk.STATUS.UPLOADING
|
|
|
- utils.each(this.files, function (file) {
|
|
|
- utils.each(file.chunks, function (chunk) {
|
|
|
- if (chunk.status() === uploadingStatus) {
|
|
|
- num++
|
|
|
- if (num >= simultaneousUploads) {
|
|
|
- should = false
|
|
|
- return false
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- return should
|
|
|
- })
|
|
|
- // if should is true then return uploading chunks's length
|
|
|
- return should && num
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Assign a browse action to one or more DOM nodes.
|
|
|
- * @function
|
|
|
- * @param {Element|Array.<Element>} 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 (typeof domNodes.length === 'undefined') {
|
|
|
- domNodes = [domNodes]
|
|
|
- }
|
|
|
-
|
|
|
- utils.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
|
|
|
- utils.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 (e) {
|
|
|
- if (domNode.tagName.toLowerCase() === 'label') {
|
|
|
- return
|
|
|
- }
|
|
|
- input.click()
|
|
|
- }, false)
|
|
|
- }
|
|
|
- if (!this.opts.singleFile && !singleFile) {
|
|
|
- input.setAttribute('multiple', 'multiple')
|
|
|
- }
|
|
|
- if (isDirectory) {
|
|
|
- input.setAttribute('webkitdirectory', 'webkitdirectory')
|
|
|
- }
|
|
|
- attributes && utils.each(attributes, function (value, key) {
|
|
|
- input.setAttribute(key, value)
|
|
|
- })
|
|
|
- // When new files are added, simply append them to the overall list
|
|
|
- var that = this
|
|
|
- input.addEventListener('change', function (e) {
|
|
|
- that._trigger(e.type, e)
|
|
|
- if (e.target.value) {
|
|
|
- that.addFiles(e.target.files, e)
|
|
|
- e.target.value = ''
|
|
|
- }
|
|
|
- }, false)
|
|
|
- }, this)
|
|
|
- },
|
|
|
-
|
|
|
- onDrop: function (evt) {
|
|
|
- this._trigger(evt.type, evt)
|
|
|
- if (this.opts.onDropStopPropagation) {
|
|
|
- evt.stopPropagation()
|
|
|
- }
|
|
|
- evt.preventDefault()
|
|
|
- this._parseDataTransfer(evt.dataTransfer, evt)
|
|
|
- },
|
|
|
-
|
|
|
- _parseDataTransfer: function (dataTransfer, evt) {
|
|
|
- if (dataTransfer.items && dataTransfer.items[0] &&
|
|
|
- dataTransfer.items[0].webkitGetAsEntry) {
|
|
|
- this.webkitReadDataTransfer(dataTransfer, evt)
|
|
|
- } else {
|
|
|
- this.addFiles(dataTransfer.files, evt)
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- webkitReadDataTransfer: function (dataTransfer, evt) {
|
|
|
- var self = this
|
|
|
- var queue = dataTransfer.items.length
|
|
|
- var files = []
|
|
|
- utils.each(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
|
|
|
- utils.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) {
|
|
|
- self.addFiles(files, evt)
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- _assignHelper: function (domNodes, handles, remove) {
|
|
|
- if (typeof domNodes.length === 'undefined') {
|
|
|
- domNodes = [domNodes]
|
|
|
- }
|
|
|
- var evtMethod = remove ? 'removeEventListener' : 'addEventListener'
|
|
|
- utils.each(domNodes, function (domNode) {
|
|
|
- utils.each(handles, function (handler, name) {
|
|
|
- domNode[evtMethod](name, handler, false)
|
|
|
- }, this)
|
|
|
- }, this)
|
|
|
- },
|
|
|
-
|
|
|
- _preventEvent: function (e) {
|
|
|
- utils.preventEvent(e)
|
|
|
- this._trigger(e.type, e)
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Assign one or more DOM nodes as a drop target.
|
|
|
- * @function
|
|
|
- * @param {Element|Array.<Element>} domNodes
|
|
|
- */
|
|
|
- assignDrop: function (domNodes) {
|
|
|
- this._onDrop = utils.bind(this.onDrop, this)
|
|
|
- this._assignHelper(domNodes, {
|
|
|
- dragover: this.preventEvent,
|
|
|
- dragenter: this.preventEvent,
|
|
|
- dragleave: this.preventEvent,
|
|
|
- drop: this._onDrop
|
|
|
- })
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Un-assign drop event from DOM nodes
|
|
|
- * @function
|
|
|
- * @param domNodes
|
|
|
- */
|
|
|
- unAssignDrop: function (domNodes) {
|
|
|
- this._assignHelper(domNodes, {
|
|
|
- dragover: this.preventEvent,
|
|
|
- dragenter: this.preventEvent,
|
|
|
- dragleave: this.preventEvent,
|
|
|
- drop: this._onDrop
|
|
|
- }, true)
|
|
|
- this._onDrop = null
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
-module.exports = Uploader
|