diff --git a/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md b/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md index f06cc92d8f..c75286ae17 100644 --- a/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md +++ b/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added configuration options to execute actions after both successful and unsuccessful file uploads. + ## [2.3.0] - 2025-08-15 ### Fixed diff --git a/packages/pluggableWidgets/file-uploader-web/package.json b/packages/pluggableWidgets/file-uploader-web/package.json index 343a19b049..3ad72cefd7 100644 --- a/packages/pluggableWidgets/file-uploader-web/package.json +++ b/packages/pluggableWidgets/file-uploader-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/file-uploader-web", "widgetName": "FileUploader", - "version": "2.3.0", + "version": "2.4.0", "description": "Upload files via drag-and-drop or file dialog. Supports multiple file uploads and image preview thumbnails.", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", diff --git a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml index f72de5e2c0..97bd0f8408 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml +++ b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml @@ -195,6 +195,23 @@ Object creation timeout Consider uploads unsuccessful if the Action to create new files/images does not create new objects within the configured amount of seconds. + + On upload success + The action to be called if file content uploaded successfully. + + + On upload success + The action to be called if image content uploaded successfully. + + + On upload failure + The action to be called if file content upload was not successful. + + + + On upload failure + The action to be called if image content upload was not successful. + Enable custom buttons diff --git a/packages/pluggableWidgets/file-uploader-web/src/package.xml b/packages/pluggableWidgets/file-uploader-web/src/package.xml index 83f1f22572..65511b3abf 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/package.xml +++ b/packages/pluggableWidgets/file-uploader-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts index 112de33ae8..10c96aedb6 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts @@ -102,18 +102,30 @@ export class FileStore { this.fileStatus = "uploading"; }); + // create object try { - // request object item this._objectItem = await this._rootStore.objectCreationHelper.request(); + } catch (_e: unknown) { + runInAction(() => { + this.fileStatus = "uploadingError"; + this._rootStore.objectCreationHelper.reportCreationFailure(); + }); + return; + } + + // upload content to object + try { await saveFile(this._objectItem, this._file!); await this.fetchMxObject(); runInAction(() => { this.fileStatus = "done"; + this._rootStore.objectCreationHelper.reportUploadSuccess(this._objectItem!); }); } catch (_e: unknown) { runInAction(() => { this.fileStatus = "uploadingError"; + this._rootStore.objectCreationHelper.reportUploadFailure(this._objectItem!); }); } } diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts index 9a34132a93..eb44dfb68f 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts @@ -1,4 +1,4 @@ -import { DynamicValue, ListValue, ObjectItem } from "mendix"; +import { DynamicValue, ObjectItem } from "mendix"; import { FileUploaderContainerProps, UploadModeEnum } from "../../typings/FileUploaderProps"; import { action, computed, makeObservable, observable } from "mobx"; import { Big } from "big.js"; @@ -26,7 +26,6 @@ export class FileUploaderStore { _uploadMode: UploadModeEnum; _maxFileSizeMiB = 0; _maxFileSize = 0; - _ds?: ListValue; _maxFilesPerUpload: DynamicValue; errorMessage?: string = undefined; @@ -90,19 +89,15 @@ export class FileUploaderStore { } updateProps(props: FileUploaderContainerProps): void { - if (props.uploadMode === "files") { - this.objectCreationHelper.updateProps(props.createFileAction); - this._ds = props.associatedFiles; - } else { - this.objectCreationHelper.updateProps(props.createImageAction); - this._ds = props.associatedImages; - } + this.objectCreationHelper.updateProps(props); // Update max files properties this._maxFilesPerUpload = props.maxFilesPerUpload; this.translations.updateProps(props); - this.updateProcessor.processUpdate(this._ds); + this.updateProcessor.processUpdate( + props.uploadMode === "files" ? props.associatedFiles : props.associatedImages + ); } processExistingFileItem(item: ObjectItem): void { diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts index 95bdc8eb08..d814016447 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts @@ -1,7 +1,22 @@ -import { ActionValue, ObjectItem } from "mendix"; +import { ActionValue, ListActionValue, ObjectItem } from "mendix"; +import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; +import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; + +type UpdateProps = Pick< + FileUploaderContainerProps, + | "uploadMode" + | "createFileAction" + | "createImageAction" + | "onUploadFailureFile" + | "onUploadSuccessFile" + | "onUploadFailureImage" + | "onUploadSuccessImage" +>; export class ObjectCreationHelper { private objectCreationAction?: ActionValue; + private onUploadFailure?: ListActionValue; + private onUploadSuccess?: ListActionValue; private requestingEnabled = false; private currentWaiting: Array<[(v: ObjectItem) => void, (e: Error) => void]> = []; private itemCreationTimer?: number = undefined; @@ -25,8 +40,16 @@ export class ObjectCreationHelper { } } - updateProps(createAction?: ActionValue): void { - this.objectCreationAction = createAction; + updateProps(props: UpdateProps): void { + if (props.uploadMode === "files") { + this.objectCreationAction = props.createFileAction; + this.onUploadFailure = props.onUploadFailureFile; + this.onUploadSuccess = props.onUploadSuccessFile; + } else { + this.objectCreationAction = props.createImageAction; + this.onUploadFailure = props.onUploadFailureImage; + this.onUploadSuccess = props.onUploadSuccessImage; + } } request(): Promise { @@ -55,6 +78,20 @@ export class ObjectCreationHelper { this.executeCreation(); } + reportCreationFailure(): void { + console.warn(`File object creation has failed.`); + } + + reportUploadFailure(item: ObjectItem): void { + console.warn(`Uploading file content to ${item.id} has failed.`); + executeAction(this.onUploadFailure?.get(item)); + } + + reportUploadSuccess(item: ObjectItem): void { + console.warn(`Uploading file content to ${item.id} has succeeded.`); + executeAction(this.onUploadSuccess?.get(item)); + } + private executeCreation(): void { if (!this.requestingEnabled) { // we are not yet able to create objects, wait till next run diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts index 572115e616..5ba6b30d15 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts @@ -6,7 +6,7 @@ describe("ObjectCreationHelper", () => { jest.useFakeTimers(); const createAction = actionValue(true, false); const helper = new ObjectCreationHelper("grid1", 10); - helper.updateProps(createAction); + helper.updateProps({ uploadMode: "files", createFileAction: createAction }); helper.enable(); const req1 = helper.request(); const req2 = helper.request(); @@ -34,7 +34,7 @@ describe("ObjectCreationHelper", () => { jest.useFakeTimers(); const helper = new ObjectCreationHelper("grid1", 10); const createAction = actionValue(true, false); - helper.updateProps(createAction); + helper.updateProps({ uploadMode: "files", createFileAction: createAction }); helper.enable(); const req1 = helper.request(); const req2 = helper.request(); @@ -48,7 +48,7 @@ describe("ObjectCreationHelper", () => { jest.useFakeTimers(); const helper = new ObjectCreationHelper("grid1", 10); const createAction = actionValue(true, false); - helper.updateProps(createAction); + helper.updateProps({ uploadMode: "files", createFileAction: createAction }); const req1 = helper.request(); const req2 = helper.request(); jest.advanceTimersByTime(1000); diff --git a/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts b/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts index c76a4c15a1..751fbf1fee 100644 --- a/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts +++ b/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts @@ -76,6 +76,10 @@ export interface FileUploaderContainerProps { removeSuccessMessage: DynamicValue; removeErrorMessage: DynamicValue; objectCreationTimeout: number; + onUploadSuccessFile?: ListActionValue; + onUploadSuccessImage?: ListActionValue; + onUploadFailureFile?: ListActionValue; + onUploadFailureImage?: ListActionValue; enableCustomButtons: boolean; customButtons: CustomButtonsType[]; } @@ -115,6 +119,10 @@ export interface FileUploaderPreviewProps { removeSuccessMessage: string; removeErrorMessage: string; objectCreationTimeout: number | null; + onUploadSuccessFile: {} | null; + onUploadSuccessImage: {} | null; + onUploadFailureFile: {} | null; + onUploadFailureImage: {} | null; enableCustomButtons: boolean; customButtons: CustomButtonsPreviewType[]; }