diff --git a/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js b/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js index b31587a7025..316518f9dee 100644 --- a/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js +++ b/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js @@ -1,5 +1,6 @@ import Mixin from "@ember/object/mixin"; import ExtendableUploader from "discourse/mixins/extendable-uploader"; +import EmberObject from "@ember/object"; import UppyS3Multipart from "discourse/mixins/uppy-s3-multipart"; import { deepMerge } from "discourse-common/lib/object"; import UppyChecksum from "discourse/lib/uppy-checksum-plugin"; @@ -36,6 +37,11 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { uploadRootPath: "/uploads", uploadTargetBound: false, + @bind + _cancelSingleUpload(data) { + this._uppyInstance.removeFile(data.fileId); + }, + @observes("composerModel.uploadCancelled") _cancelUpload() { if (!this.get("composerModel.uploadCancelled")) { @@ -61,6 +67,10 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { this.element.removeEventListener("paste", this.pasteEventListener); this.appEvents.off(`${this.eventPrefix}:add-files`, this._addFiles); + this.appEvents.off( + `${this.eventPrefix}:cancel-upload`, + this._cancelSingleUpload + ); this._reset(); @@ -79,13 +89,17 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { }, _bindUploadTarget() { + this.set("inProgressUploads", []); this.placeholders = {}; - this._inProgressUploads = 0; this._preProcessorStatus = {}; this.fileInputEl = document.getElementById(this.fileUploadElementId); const isPrivateMessage = this.get("composerModel.privateMessage"); this.appEvents.on(`${this.eventPrefix}:add-files`, this._addFiles); + this.appEvents.on( + `${this.eventPrefix}:cancel-upload`, + this._cancelSingleUpload + ); this._unbindUploadTarget(); this.fileInputEventListener = bindFileInputChangeListener( @@ -181,6 +195,37 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { this.set("uploadProgress", progress); }); + this._uppyInstance.on("file-removed", (file, reason) => { + file.meta.cancelled = true; + + // we handle the cancel-all event specifically, so no need + // to do anything here + if (reason === "cancel-all") { + return; + } + + this._removeInProgressUpload(file.id); + this._resetUpload(file, { removePlaceholder: true }); + if (this.inProgressUploads.length === 0) { + this.set("userCancelled", true); + this._uppyInstance.cancelAll(); + } + }); + + this._uppyInstance.on("upload-progress", (file, progress) => { + if (this.isDestroying || this.isDestroyed) { + return; + } + + const upload = this.inProgressUploads.find((upl) => upl.id === file.id); + if (upload) { + const percentage = Math.round( + (progress.bytesUploaded / progress.bytesTotal) * 100 + ); + upload.set("progress", percentage); + } + }); + this._uppyInstance.on("upload", (data) => { this._addNeedProcessing(data.fileIDs.length); @@ -194,7 +239,13 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { }); files.forEach((file) => { - this._inProgressUploads++; + this.inProgressUploads.push( + EmberObject.create({ + fileName: file.name, + id: file.id, + progress: 0, + }) + ); const placeholder = this._uploadPlaceholder(file); this.placeholders[file.id] = { uploadPlaceholder: placeholder, @@ -205,7 +256,7 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { }); this._uppyInstance.on("upload-success", (file, response) => { - this._inProgressUploads--; + this._removeInProgressUpload(file.id); let upload = response.body; const markdown = this.uploadMarkdownResolvers.reduce( (md, resolver) => resolver(upload) || md, @@ -262,7 +313,7 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { @bind _handleUploadError(file, error, response) { - this._inProgressUploads--; + this._removeInProgressUpload(file.id); this._resetUpload(file, { removePlaceholder: true }); file.meta.error = error; @@ -272,11 +323,18 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, { this.appEvents.trigger(`${this.eventPrefix}:upload-error`, file); } - if (this._inProgressUploads === 0) { + if (this.inProgressUploads.length === 0) { this._reset(); } }, + _removeInProgressUpload(fileId) { + this.set( + "inProgressUploads", + this.inProgressUploads.filter((upl) => upl.id !== fileId) + ); + }, + _setupPreProcessors() { const checksumPreProcessor = { pluginClass: UppyChecksum, diff --git a/app/assets/javascripts/discourse/app/mixins/uppy-s3-multipart.js b/app/assets/javascripts/discourse/app/mixins/uppy-s3-multipart.js index 2c4d72bedd5..e2d75eb06c8 100644 --- a/app/assets/javascripts/discourse/app/mixins/uppy-s3-multipart.js +++ b/app/assets/javascripts/discourse/app/mixins/uppy-s3-multipart.js @@ -122,6 +122,10 @@ export default Mixin.create({ @bind _completeMultipartUpload(file, data) { + if (file.meta.cancelled) { + return; + } + this._uppyInstance.emit("complete-multipart", file.id); const parts = data.parts.map((part) => { return { part_number: part.PartNumber, etag: part.ETag }; @@ -159,6 +163,8 @@ export default Mixin.create({ return; } + file.meta.cancelled = true; + return ajax(getUrl(`${this.uploadRootPath}/abort-multipart.json`), { type: "POST", data: { diff --git a/app/assets/javascripts/discourse/app/mixins/uppy-upload.js b/app/assets/javascripts/discourse/app/mixins/uppy-upload.js index d2de36205cf..05c145d6450 100644 --- a/app/assets/javascripts/discourse/app/mixins/uppy-upload.js +++ b/app/assets/javascripts/discourse/app/mixins/uppy-upload.js @@ -1,4 +1,5 @@ import Mixin from "@ember/object/mixin"; +import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; import { bindFileInputChangeListener, @@ -26,7 +27,7 @@ export default Mixin.create(UppyS3Multipart, { uploadProgress: 0, _uppyInstance: null, autoStartUploads: true, - _inProgressUploads: 0, + inProgressUploads: null, id: null, uploadRootPath: "/uploads", @@ -59,6 +60,7 @@ export default Mixin.create(UppyS3Multipart, { fileInputEl: this.element.querySelector(".hidden-upload-field"), }); this.set("allowMultipleFiles", this.fileInputEl.multiple); + this.set("inProgressUploads", []); this._bindFileInputChange(); @@ -143,11 +145,22 @@ export default Mixin.create(UppyS3Multipart, { }); this._uppyInstance.on("upload", (data) => { - this._inProgressUploads += data.fileIDs.length; + const files = data.fileIDs.map((fileId) => + this._uppyInstance.getFile(fileId) + ); + files.forEach((file) => { + this.inProgressUploads.push( + EmberObject.create({ + fileName: file.name, + id: file.id, + progress: 0, + }) + ); + }); }); this._uppyInstance.on("upload-success", (file, response) => { - this._inProgressUploads--; + this._removeInProgressUpload(file.id); if (this.usingS3Uploads) { this.setProperties({ uploading: false, processing: true }); @@ -157,13 +170,13 @@ export default Mixin.create(UppyS3Multipart, { deepMerge(completeResponse, { file_name: file.name }) ); - if (this._inProgressUploads === 0) { + if (this.inProgressUploads.length === 0) { this._reset(); } }) .catch((errResponse) => { displayErrorForUpload(errResponse, this.siteSettings, file.name); - if (this._inProgressUploads === 0) { + if (this.inProgressUploads.length === 0) { this._reset(); } }); @@ -171,13 +184,14 @@ export default Mixin.create(UppyS3Multipart, { this.uploadDone( deepMerge(response?.body || {}, { file_name: file.name }) ); - if (this._inProgressUploads === 0) { + if (this.inProgressUploads.length === 0) { this._reset(); } } }); this._uppyInstance.on("upload-error", (file, error, response) => { + this._removeInProgressUpload(file.id); displayErrorForUpload(response || error, this.siteSettings, file.name); this._reset(); }); @@ -316,4 +330,11 @@ export default Mixin.create(UppyS3Multipart, { }); this.fileInputEl.value = ""; }, + + _removeInProgressUpload(fileId) { + this.set( + "inProgressUploads", + this.inProgressUploads.filter((upl) => upl.id !== fileId) + ); + }, });