216 lines
5.8 KiB
JavaScript
216 lines
5.8 KiB
JavaScript
import nanoid from 'nanoid/non-secure'
|
||
import mimes from './libs/mimes'
|
||
|
||
/**
|
||
* The file upload class holds and represents a file’s upload state durring
|
||
* the upload flow.
|
||
*/
|
||
class FileUpload {
|
||
/**
|
||
* Create a file upload object.
|
||
* @param {FileList} fileList
|
||
* @param {object} context
|
||
*/
|
||
constructor (input, context, options) {
|
||
this.input = input
|
||
this.fileList = input.files
|
||
this.files = []
|
||
this.options = options
|
||
this.results = false
|
||
this.context = context
|
||
if (Array.isArray(this.fileList)) {
|
||
this.rehydrateFileList(this.fileList)
|
||
} else {
|
||
this.addFileList(this.fileList)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Given a pre-existing array of files, create a faux FileList.
|
||
* @param {array} items expects an array of objects [{ url: '/uploads/file.pdf' }]
|
||
* @param {string} pathKey the object-key to access the url (defaults to "url")
|
||
*/
|
||
rehydrateFileList (items) {
|
||
const fauxFileList = items.reduce((fileList, item) => {
|
||
const key = this.options ? this.options.fileUrlKey : 'url'
|
||
const url = item[key]
|
||
const ext = (url && url.lastIndexOf('.') !== -1) ? url.substr(url.lastIndexOf('.') + 1) : false
|
||
const mime = mimes[ext] || false
|
||
fileList.push(Object.assign({}, item, url ? {
|
||
name: url.substr((url.lastIndexOf('/') + 1) || 0),
|
||
type: item.type ? item.type : mime,
|
||
previewData: url
|
||
} : {}))
|
||
return fileList
|
||
}, [])
|
||
this.results = items
|
||
this.addFileList(fauxFileList)
|
||
}
|
||
|
||
/**
|
||
* Produce an array of files and alert the callback.
|
||
* @param {FileList}
|
||
*/
|
||
addFileList (fileList) {
|
||
for (let i = 0; i < fileList.length; i++) {
|
||
const file = fileList[i]
|
||
const uuid = nanoid()
|
||
const removeFile = function () {
|
||
this.removeFile(uuid)
|
||
}
|
||
this.files.push({
|
||
progress: false,
|
||
error: false,
|
||
complete: false,
|
||
justFinished: false,
|
||
name: file.name || 'file-upload',
|
||
file,
|
||
uuid,
|
||
path: false,
|
||
removeFile: removeFile.bind(this),
|
||
previewData: file.previewData || false
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Check if the file has an.
|
||
*/
|
||
hasUploader () {
|
||
return !!this.context.uploader
|
||
}
|
||
|
||
/**
|
||
* Check if the given uploader is axios instance. This isn't a great way of
|
||
* testing if it is or not, but AFIK there isn't a better way right now:
|
||
*
|
||
* https://github.com/axios/axios/issues/737
|
||
*/
|
||
uploaderIsAxios () {
|
||
if (
|
||
this.hasUploader &&
|
||
typeof this.context.uploader.request === 'function' &&
|
||
typeof this.context.uploader.get === 'function' &&
|
||
typeof this.context.uploader.delete === 'function' &&
|
||
typeof this.context.uploader.post === 'function'
|
||
) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* Get a new uploader function.
|
||
*/
|
||
getUploader (...args) {
|
||
if (this.uploaderIsAxios()) {
|
||
const formData = new FormData()
|
||
formData.append(this.context.name || 'file', args[0])
|
||
if (this.context.uploadUrl === false) {
|
||
throw new Error('No uploadURL specified: https://vueformulate.com/guide/inputs/file/#props')
|
||
}
|
||
return this.context.uploader.post(this.context.uploadUrl, formData, {
|
||
headers: {
|
||
'Content-Type': 'multipart/form-data'
|
||
},
|
||
onUploadProgress: progressEvent => {
|
||
// args[1] here is the upload progress handler function
|
||
args[1](Math.round((progressEvent.loaded * 100) / progressEvent.total))
|
||
}
|
||
})
|
||
.then(res => res.data)
|
||
.catch(err => args[2](err))
|
||
}
|
||
return this.context.uploader(...args)
|
||
}
|
||
|
||
/**
|
||
* Perform the file upload.
|
||
*/
|
||
upload () {
|
||
if (this.results) {
|
||
return Promise.resolve(this.results)
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
if (!this.hasUploader) {
|
||
return reject(new Error('No uploader has been defined'))
|
||
}
|
||
Promise.all(this.files.map(file => {
|
||
return file.path ? Promise.resolve(file.path) : this.getUploader(
|
||
file.file,
|
||
(progress) => {
|
||
file.progress = progress
|
||
if (progress >= 100) {
|
||
if (!file.complete) {
|
||
file.justFinished = true
|
||
setTimeout(() => { file.justFinished = false }, this.options.uploadJustCompleteDuration)
|
||
}
|
||
file.complete = true
|
||
}
|
||
},
|
||
(error) => {
|
||
file.progress = 0
|
||
file.error = error
|
||
file.complete = true
|
||
},
|
||
this.options
|
||
)
|
||
}))
|
||
.then(results => {
|
||
this.results = results
|
||
resolve(results)
|
||
})
|
||
.catch(err => { throw new Error(err) })
|
||
})
|
||
}
|
||
|
||
/**
|
||
* Remove a file from the uploader (and the file list)
|
||
* @param {string} uuid
|
||
*/
|
||
removeFile (uuid) {
|
||
this.files = this.files.filter(file => file.uuid !== uuid)
|
||
this.context.performValidation()
|
||
if (window && this.fileList instanceof FileList) {
|
||
const transfer = new DataTransfer()
|
||
this.files.map(file => transfer.items.add(file.file))
|
||
this.fileList = transfer.files
|
||
this.input.files = this.fileList
|
||
}
|
||
}
|
||
|
||
/**
|
||
* load image previews for all uploads.
|
||
*/
|
||
loadPreviews () {
|
||
this.files.map(file => {
|
||
if (!file.previewData && window && window.FileReader && /^image\//.test(file.file.type)) {
|
||
const reader = new FileReader()
|
||
reader.onload = e => Object.assign(file, { previewData: e.target.result })
|
||
reader.readAsDataURL(file.file)
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* Get the files.
|
||
*/
|
||
getFileList () {
|
||
return this.fileList
|
||
}
|
||
|
||
/**
|
||
* Get the files.
|
||
*/
|
||
getFiles () {
|
||
return this.files
|
||
}
|
||
|
||
toString () {
|
||
const descriptor = this.files.length ? this.files.length + ' files' : 'empty'
|
||
return this.results ? JSON.stringify(this.results, null, ' ') : `FileUpload(${descriptor})`
|
||
}
|
||
}
|
||
|
||
export default FileUpload
|