UNPKG

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
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 } }