DEV: Add single file progress and cancel for uppy in composer (#15053)
This commit adds handlers for the composer uppy mixin to allow for cancelling individual file uploads, not just all of them at once. This is also combined with better tracking of in progress uploads along with their progress percentage, for UI that needs to be able to display the progress for individual files and also cancel individual files. To use this, a cancel button in the UI should call a function like this: ```javascript cancelSingleUpload(fileId) { this.appEvents.trigger(`${this.eventPrefix}:cancel-upload`, { fileId, }); }, ``` Additionally, the `inProgressUploads` can be shown in the UI. It is an array of objects with the file name, ID, and the progress percentage. We can add more data to this if needed down the line.
This commit is contained in:
parent
52532758f7
commit
db4c52ca26
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue