quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
264 lines (217 loc) • 6.25 kB
JavaScript
import { ref, computed } from 'vue'
function getFn(prop) {
return typeof prop === 'function' ? prop : () => prop
}
const name = 'QUploader'
const componentProps = {
url: [Function, String],
method: {
type: [Function, String],
default: 'POST'
},
fieldName: {
type: [Function, String],
default: () => 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 = (propName, arg) =>
factory[propName] !== void 0
? getFn(factory[propName])(arg)
: xhrProps.value[propName](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)
if (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)
if (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,
props: componentProps,
emits,
injectPlugin
}