quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
259 lines (213 loc) • 6.14 kB
JavaScript
import { ref, computed } from 'vue'
function getFn (prop) {
return typeof prop === 'function'
? prop
: () => prop
}
const props = {
url: [ Function, String ],
method: {
type: [ Function, String ],
default: 'POST'
},
fieldName: {
type: [ Function, String ],
default: () => {
return file => file.name
}
},
headers: [ Function, Array ],
formFields: [ Function, Array ],
withCredentials: [ Function, Boolean ],
sendRaw: [ Function, Boolean ],
batch: [ Function, Boolean ],
factory: Function
}
const emits = [ 'factoryFailed', 'uploaded', 'failed', 'uploading' ]
function injectPlugin ({ props, emit, helpers }) {
const xhrs = ref([])
const promises = ref([])
const workingThreads = ref(0)
const xhrProps = computed(() => ({
url: getFn(props.url),
method: getFn(props.method),
headers: getFn(props.headers),
formFields: getFn(props.formFields),
fieldName: getFn(props.fieldName),
withCredentials: getFn(props.withCredentials),
sendRaw: getFn(props.sendRaw),
batch: getFn(props.batch)
}))
const isUploading = computed(() => workingThreads.value > 0)
const isBusy = computed(() => promises.value.length > 0)
let abortPromises
function abort () {
xhrs.value.forEach(x => { x.abort() })
if (promises.value.length > 0) {
abortPromises = true
}
}
function upload () {
const queue = helpers.queuedFiles.value.slice(0)
helpers.queuedFiles.value = []
if (xhrProps.value.batch(queue)) {
runFactory(queue)
}
else {
queue.forEach(file => {
runFactory([ file ])
})
}
}
function runFactory (files) {
workingThreads.value++
if (typeof props.factory !== 'function') {
performUpload(files, {})
return
}
const res = props.factory(files)
if (!res) {
emit(
'factoryFailed',
new Error('QUploader: factory() does not return properly'),
files
)
workingThreads.value--
}
else if (typeof res.catch === 'function' && typeof res.then === 'function') {
promises.value.push(res)
const failed = err => {
if (helpers.isAlive() === true) {
promises.value = promises.value.filter(p => p !== res)
if (promises.value.length === 0) {
abortPromises = false
}
helpers.queuedFiles.value = helpers.queuedFiles.value.concat(files)
files.forEach(f => { helpers.updateFileStatus(f, 'failed') })
emit('factoryFailed', err, files)
workingThreads.value--
}
}
res.then(factory => {
if (abortPromises === true) {
failed(new Error('Aborted'))
}
else if (helpers.isAlive() === true) {
promises.value = promises.value.filter(p => p !== res)
performUpload(files, factory)
}
}).catch(failed)
}
else {
performUpload(files, res || {})
}
}
function performUpload (files, factory) {
const
form = new FormData(),
xhr = new XMLHttpRequest()
const getProp = (name, arg) => {
return factory[ name ] !== void 0
? getFn(factory[ name ])(arg)
: xhrProps.value[ name ](arg)
}
const url = getProp('url', files)
if (!url) {
console.error('q-uploader: invalid or no URL specified')
workingThreads.value--
return
}
const fields = getProp('formFields', files)
fields !== void 0 && fields.forEach(field => {
form.append(field.name, field.value)
})
let
uploadIndex = 0,
uploadIndexSize = 0,
localUploadedSize = 0,
maxUploadSize = 0,
aborted
xhr.upload.addEventListener('progress', e => {
if (aborted === true) { return }
const loaded = Math.min(maxUploadSize, e.loaded)
helpers.uploadedSize.value += loaded - localUploadedSize
localUploadedSize = loaded
let size = localUploadedSize - uploadIndexSize
for (let i = uploadIndex; size > 0 && i < files.length; i++) {
const
file = files[ i ],
uploaded = size > file.size
if (uploaded) {
size -= file.size
uploadIndex++
uploadIndexSize += file.size
helpers.updateFileStatus(file, 'uploading', file.size)
}
else {
helpers.updateFileStatus(file, 'uploading', size)
return
}
}
}, false)
xhr.onreadystatechange = () => {
if (xhr.readyState < 4) {
return
}
if (xhr.status && xhr.status < 400) {
helpers.uploadedFiles.value = helpers.uploadedFiles.value.concat(files)
files.forEach(f => { helpers.updateFileStatus(f, 'uploaded') })
emit('uploaded', { files, xhr })
}
else {
aborted = true
helpers.uploadedSize.value -= localUploadedSize
helpers.queuedFiles.value = helpers.queuedFiles.value.concat(files)
files.forEach(f => { helpers.updateFileStatus(f, 'failed') })
emit('failed', { files, xhr })
}
workingThreads.value--
xhrs.value = xhrs.value.filter(x => x !== xhr)
}
xhr.open(
getProp('method', files),
url
)
if (getProp('withCredentials', files) === true) {
xhr.withCredentials = true
}
const headers = getProp('headers', files)
headers !== void 0 && headers.forEach(head => {
xhr.setRequestHeader(head.name, head.value)
})
const sendRaw = getProp('sendRaw', files)
files.forEach(file => {
helpers.updateFileStatus(file, 'uploading', 0)
if (sendRaw !== true) {
form.append(getProp('fieldName', file), file, file.name)
}
file.xhr = xhr
file.__abort = () => { xhr.abort() }
maxUploadSize += file.size
})
emit('uploading', { files, xhr })
xhrs.value.push(xhr)
if (sendRaw === true) {
xhr.send(new Blob(files))
}
else {
xhr.send(form)
}
}
return {
isUploading,
isBusy,
abort,
upload
}
}
export default {
name: 'QUploader',
props,
emits,
injectPlugin
}