1
0
mirror of synced 2025-01-27 12:51:40 +03:00
vue-formulario/src/FileUpload.ts

232 lines
7.3 KiB
TypeScript
Raw Normal View History

2020-02-28 11:19:44 -05:00
import nanoid from 'nanoid/non-secure'
import { AxiosResponse, AxiosError } from '@/axios.types'
interface FileItem {
uuid: string;
name: string;
path: string | false;
progress: number | false;
error: any | false;
complete: boolean;
file: File;
justFinished: boolean;
removeFile(): void;
previewData: string | false;
}
interface ProgressSetter {
(progress: number): void;
}
interface ErrorHandler {
(error: AxiosError): any;
}
2020-10-09 22:58:28 +03:00
// noinspection JSUnusedGlobalSymbols
/**
* The file upload class holds and represents a files upload state durring
* the upload flow.
*/
class FileUpload {
public input: DataTransfer
public fileList: FileList
public files: FileItem[]
public options: Record<string, any>
public context: Record<string, any>
public results: any[] | boolean
constructor (input: DataTransfer, context: Record<string, any> = {}, options: Record<string, any> = {}) {
2020-05-22 14:22:56 +03:00
this.input = input
this.fileList = input.files
this.files = []
this.options = { mimes: {}, ...options }
2020-05-22 14:22:56 +03:00
this.results = false
this.context = context
if (Array.isArray(this.fileList)) {
this.rehydrateFileList(this.fileList)
} else {
this.addFileList(this.fileList)
}
}
2020-05-22 14:22:56 +03:00
/**
* Given a pre-existing array of files, create a faux FileList.
* @param {array} items expects an array of objects [{ url: '/uploads/file.pdf' }]
*/
rehydrateFileList (items: any[]): void {
2020-05-22 14:22:56 +03:00
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 = this.options.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)
}
2020-05-22 14:22:56 +03:00
/**
* Produce an array of files and alert the callback.
2020-10-09 22:58:28 +03:00
* @param {FileList} fileList
2020-05-22 14:22:56 +03:00
*/
addFileList (fileList: FileList): void {
2020-05-22 14:22:56 +03:00
for (let i = 0; i < fileList.length; i++) {
const file: File = fileList[i]
2020-05-22 14:22:56 +03:00
const uuid = nanoid()
this.files.push({
progress: false,
error: false,
complete: false,
justFinished: false,
name: file.name || 'file-upload',
file,
uuid,
path: false,
2020-10-09 22:58:28 +03:00
removeFile: () => this.removeFile(uuid),
// @ts-ignore
2020-05-22 14:22:56 +03:00
previewData: file.previewData || false
})
}
}
2020-05-22 14:22:56 +03:00
/**
* Check if the file has an.
*/
hasUploader (): boolean {
2020-05-22 14:22:56 +03:00
return !!this.context.uploader
}
2020-05-22 14:22:56 +03:00
/**
* 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 (): boolean {
2020-10-09 22:58:28 +03:00
return this.hasUploader &&
2020-05-22 14:22:56 +03:00
typeof this.context.uploader.request === 'function' &&
typeof this.context.uploader.get === 'function' &&
typeof this.context.uploader.delete === 'function' &&
typeof this.context.uploader.post === 'function'
}
2020-05-22 14:22:56 +03:00
/**
* Get a new uploader function.
*/
getUploader (...args: [File, ProgressSetter, ErrorHandler, Record<string, any>]) {
2020-05-22 14:22:56 +03:00
if (this.uploaderIsAxios()) {
const data = new FormData()
data.append(this.context.name || 'file', args[0])
2020-05-22 14:22:56 +03:00
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, data, {
2020-05-22 14:22:56 +03:00
headers: {
'Content-Type': 'multipart/form-data',
2020-05-22 14:22:56 +03:00
},
onUploadProgress: (event: ProgressEvent) => {
2020-05-22 14:22:56 +03:00
// args[1] here is the upload progress handler function
args[1](Math.round((event.loaded * 100) / event.total))
2020-05-22 14:22:56 +03:00
}
})
.then((response: AxiosResponse) => response.data)
.catch(args[2])
2020-05-22 14:22:56 +03:00
}
return this.context.uploader(...args)
}
2020-05-22 14:22:56 +03:00
/**
* 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'))
}
2020-05-22 14:22:56 +03:00
Promise.all(this.files.map(file => {
return file.path ? Promise.resolve(file.path) : this.getUploader(
file.file,
progress => {
2020-05-22 14:22:56 +03:00
file.progress = progress
if (progress >= 100) {
if (!file.complete) {
file.justFinished = true
setTimeout(() => { file.justFinished = false }, this.options.uploadJustCompleteDuration)
}
file.complete = true
}
},
error => {
2020-05-22 14:22:56 +03:00
file.progress = 0
file.error = error
file.complete = true
},
this.options
)
}))
.then(results => {
this.results = results
resolve(results)
})
.catch(err => { throw new Error(err) })
})
2020-05-22 14:22:56 +03:00
}
2020-05-22 14:22:56 +03:00
/**
* Remove a file from the uploader (and the file list)
*/
removeFile (uuid: string): void {
2020-05-22 14:22:56 +03:00
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))
2020-05-22 14:22:56 +03:00
this.fileList = transfer.files
this.input = transfer
2020-05-22 14:22:56 +03:00
}
}
2020-05-22 14:22:56 +03:00
/**
* 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()
// @ts-ignore
2020-05-22 14:22:56 +03:00
reader.onload = e => Object.assign(file, { previewData: e.target.result })
reader.readAsDataURL(file.file)
}
})
}
2020-05-22 14:22:56 +03:00
/**
* Get the files.
*/
getFileList () {
return this.fileList
}
2020-05-22 14:22:56 +03:00
/**
* Get the files.
*/
getFiles () {
return this.files
}
toString (): string {
2020-05-22 14:22:56 +03:00
const descriptor = this.files.length ? this.files.length + ' files' : 'empty'
return this.results ? JSON.stringify(this.results, null, ' ') : `FileUpload(${descriptor})`
}
}
export default FileUpload