vue-upload-component
Version:
Vue.js file upload component, Multi-file upload, Upload directory, Drag upload, Drag the directory, Upload multiple files at the same time, html4 (IE 9), `PUT` method, Customize the filter
396 lines (350 loc) • 8.42 kB
JavaScript
import {
default as request,
createRequest,
sendFormRequest
} from '../utils/request'
export default class ChunkUploadHandler {
/**
* Constructor
*
* @param {File} file
* @param {Object} options
*/
constructor(file, options) {
this.file = file
this.options = options
this.chunks = []
this.sessionId = null
this.chunkSize = null
this.speedInterval = null
}
/**
* Gets the max retries from options
*/
get maxRetries() {
return parseInt(this.options.maxRetries, 10)
}
/**
* Gets the max number of active chunks being uploaded at once from options
*/
get maxActiveChunks() {
return parseInt(this.options.maxActive, 10)
}
/**
* Gets the file type
*/
get fileType() {
return this.file.type
}
/**
* Gets the file size
*/
get fileSize() {
return this.file.size
}
/**
* Gets the file name
*/
get fileName() {
return this.file.name
}
/**
* Gets action (url) to upload the file
*/
get action() {
return this.options.action || null
}
/**
* Gets the body to be merged when sending the request in start phase
*/
get startBody() {
return this.options.startBody || {}
}
/**
* Gets the body to be merged when sending the request in upload phase
*/
get uploadBody() {
return this.options.uploadBody || {}
}
/**
* Gets the body to be merged when sending the request in finish phase
*/
get finishBody() {
return this.options.finishBody || {}
}
/**
* Gets the headers of the requests from options
*/
get headers() {
return this.options.headers || {}
}
/**
* Whether it's ready to upload files or not
*/
get readyToUpload() {
return !!this.chunks
}
/**
* Gets the progress of the chunk upload
* - Gets all the completed chunks
* - Gets the progress of all the chunks that are being uploaded
*/
get progress() {
const completedProgress = (this.chunksUploaded.length / this.chunks.length) * 100
const uploadingProgress = this.chunksUploading.reduce((progress, chunk) => {
return progress + ((chunk.progress | 0) / this.chunks.length)
}, 0)
return Math.min(completedProgress + uploadingProgress, 100)
}
/**
* Gets all the chunks that are pending to be uploaded
*/
get chunksToUpload() {
return this.chunks.filter(chunk => {
return !chunk.active && !chunk.uploaded
})
}
/**
* Whether there are chunks to upload or not
*/
get hasChunksToUpload() {
return this.chunksToUpload.length > 0
}
/**
* Gets all the chunks that are uploading
*/
get chunksUploading() {
return this.chunks.filter(chunk => {
return !!chunk.xhr && !!chunk.active
})
}
/**
* Gets all the chunks that have finished uploading
*/
get chunksUploaded() {
return this.chunks.filter(chunk => {
return !!chunk.uploaded
})
}
/**
* Creates all the chunks in the initial state
*/
createChunks() {
this.chunks = []
let start = 0
let end = this.chunkSize
while (start < this.fileSize) {
this.chunks.push({
blob: this.file.file.slice(start, end),
startOffset: start,
active: false,
retries: this.maxRetries
})
start = end
end = start + this.chunkSize
}
}
/**
* Updates the progress of the file with the handler's progress
*/
updateFileProgress() {
this.file.progress = this.progress
}
/**
* Paues the upload process
* - Stops all active requests
* - Sets the file not active
*/
pause() {
this.file.active = false
this.stopChunks()
}
/**
* Stops all the current chunks
*/
stopChunks() {
this.chunksUploading.forEach(chunk => {
chunk.xhr.abort()
chunk.active = false
})
this.stopSpeedCalc()
}
/**
* Resumes the file upload
* - Sets the file active
* - Starts the following chunks
*/
resume() {
this.file.active = true
this.startChunking()
}
/**
* Starts the file upload
*
* @returns Promise
* - resolve The file was uploaded
* - reject The file upload failed
*/
upload() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
this.start()
return this.promise
}
/**
* Start phase
* Sends a request to the backend to initialise the chunks
*/
start() {
request({
method: 'POST',
headers: Object.assign({}, this.headers, {
'Content-Type': 'application/json'
}),
url: this.action,
body: Object.assign(this.startBody, {
phase: 'start',
mime_type: this.fileType,
size: this.fileSize,
name: this.fileName
})
}).then(res => {
if (res.status !== 'success') {
this.file.response = res
return this.reject('server')
}
this.sessionId = res.data.session_id
this.chunkSize = res.data.end_offset
this.createChunks()
this.startChunking()
}).catch(res => {
this.file.response = res
this.reject('server')
})
}
/**
* Starts to upload chunks
*/
startChunking() {
for (let i = 0; i < this.maxActiveChunks; i++) {
this.uploadNextChunk()
}
this.startSpeedCalc()
}
/**
* Uploads the next chunk
* - Won't do anything if the process is paused
* - Will start finish phase if there are no more chunks to upload
*/
uploadNextChunk() {
if (this.file.active) {
if (this.hasChunksToUpload) {
return this.uploadChunk(this.chunksToUpload[0])
}
if (this.chunksUploading.length === 0) {
return this.finish()
}
}
}
/**
* Uploads a chunk
* - Sends the chunk to the backend
* - Sets the chunk as uploaded if everything went well
* - Decreases the number of retries if anything went wrong
* - Fails if there are no more retries
*
* @param {Object} chunk
*/
uploadChunk(chunk) {
chunk.progress = 0
chunk.active = true
this.updateFileProgress()
chunk.xhr = createRequest({
method: 'POST',
headers: this.headers,
url: this.action
})
chunk.xhr.upload.addEventListener('progress', function (evt) {
if (evt.lengthComputable) {
chunk.progress = Math.round(evt.loaded / evt.total * 100)
}
}, false)
sendFormRequest(chunk.xhr, Object.assign(this.uploadBody, {
phase: 'upload',
session_id: this.sessionId,
start_offset: chunk.startOffset,
chunk: chunk.blob
})).then(res => {
chunk.active = false
if (res.status === 'success') {
chunk.uploaded = true
} else {
if (chunk.retries-- <= 0) {
this.stopChunks()
return this.reject('upload')
}
}
this.uploadNextChunk()
}).catch(() => {
chunk.active = false
if (chunk.retries-- <= 0) {
this.stopChunks()
return this.reject('upload')
}
this.uploadNextChunk()
})
}
/**
* Finish phase
* Sends a request to the backend to finish the process
*/
finish() {
this.updateFileProgress()
this.stopSpeedCalc()
request({
method: 'POST',
headers: Object.assign({}, this.headers, {
'Content-Type': 'application/json'
}),
url: this.action,
body: Object.assign(this.finishBody, {
phase: 'finish',
session_id: this.sessionId
})
}).then(res => {
this.file.response = res
if (res.status !== 'success') {
return this.reject('server')
}
this.resolve(res)
}).catch(res => {
this.file.response = res
this.reject('server')
})
}
/**
* Sets an interval to calculate and
* set upload speed every 3 seconds
*/
startSpeedCalc() {
this.file.speed = 0
let lastUploadedBytes = 0
if (!this.speedInterval) {
this.speedInterval = window.setInterval(() => {
let uploadedBytes = (this.progress / 100) * this.fileSize
this.file.speed = (uploadedBytes - lastUploadedBytes)
lastUploadedBytes = uploadedBytes
}, 1000)
}
}
/**
* Removes the upload speed interval
*/
stopSpeedCalc() {
this.speedInterval && window.clearInterval(this.speedInterval)
this.speedInterval = null
this.file.speed = 0
}
}