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

232 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
// 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> = {}) {
this.input = input
this.fileList = input.files
this.files = []
this.options = { mimes: {}, ...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' }]
*/
rehydrateFileList (items: any[]): void {
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)
}
/**
* Produce an array of files and alert the callback.
* @param {FileList} fileList
*/
addFileList (fileList: FileList): void {
for (let i = 0; i < fileList.length; i++) {
const file: File = fileList[i]
const uuid = nanoid()
this.files.push({
progress: false,
error: false,
complete: false,
justFinished: false,
name: file.name || 'file-upload',
file,
uuid,
path: false,
removeFile: () => this.removeFile(uuid),
// @ts-ignore
previewData: file.previewData || false
})
}
}
/**
* Check if the file has an.
*/
hasUploader (): boolean {
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 (): boolean {
return 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'
}
/**
* Get a new uploader function.
*/
getUploader (...args: [File, ProgressSetter, ErrorHandler, Record<string, any>]) {
if (this.uploaderIsAxios()) {
const data = new FormData()
data.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, data, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (event: ProgressEvent) => {
// args[1] here is the upload progress handler function
args[1](Math.round((event.loaded * 100) / event.total))
}
})
.then((response: AxiosResponse) => response.data)
.catch(args[2])
}
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)
*/
removeFile (uuid: string): void {
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))
this.fileList = transfer.files
this.input = transfer
}
}
/**
* 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
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 (): string {
const descriptor = this.files.length ? this.files.length + ' files' : 'empty'
return this.results ? JSON.stringify(this.results, null, ' ') : `FileUpload(${descriptor})`
}
}
export default FileUpload