Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WC-2784] Fix uploading error in file uploader on uncommited objects #1400

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/pluggableWidgets/file-uploader-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Fixed

- We fixed an issue where an error occurred when uploading multiple files on a newly created context object.

## [1.0.1] - 2024-12-19

### Added

- The file uploader widget enables you to upload files by dragging and dropping them onto the widget or by using the file selection dialog.
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/file-uploader-web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mendix/file-uploader-web",
"widgetName": "FileUploader",
"version": "1.0.0",
"version": "1.0.2",
"description": "",
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
"private": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@

<property key="createFileAction" type="action">
<caption>Action to create new files</caption>
<description>Action that creates an associated file object and commits it.</description>
<description>Nanoflow that creates a file document object, associates it to the current object and commits it.</description>
</property>

<property key="createImageAction" type="action">
<caption>Action to create new images</caption>
<description>Action that creates an associated image object and commits it.</description>
<description>Nanoflow that creates an image document object, associates it to the current object and commits it.</description>
</property>

<property key="allowedFileFormats" type="object" isList="true" required="false">
Expand Down Expand Up @@ -167,7 +167,7 @@
</property>

<property key="unavailableCreateActionMessage" type="textTemplate">
<caption>Action to create new files is not available</caption>
<caption>Action to create new files is not available or failed</caption>
<description />
<translations>
<translation lang="en_US">Can't upload files at this time. Please contact your system administrator.</translation>
Expand Down Expand Up @@ -206,5 +206,11 @@
</translations>
</property>
</propertyGroup>
<propertyGroup caption="Advanced">
<property key="objectCreationTimeout" type="integer" defaultValue="10">
<caption>Object creation timeout</caption>
<description>Consider uploads unsuccessful if the Action to create new files/images does not create new objects within the configured amount of seconds.</description>
</property>
</propertyGroup>
</properties>
</widget>
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const FileEntryContainer = observer(({ store }: FileEntryContainerProps):
errorMessage={store.errorDescription}
canRemove={store.canRemove}
onRemove={onRemove}
canDownload={store.canDownload}
downloadUrl={store.downloadUrl}
/>
);
Expand All @@ -44,13 +45,14 @@ interface FileEntryProps {
canRemove: boolean;
onRemove: () => void;

canDownload: boolean;
downloadUrl?: string;
}

function FileEntry(props: FileEntryProps): ReactElement {
const translations = useTranslationsStore();

const { downloadUrl, onRemove } = props;
const { canDownload, downloadUrl, onRemove } = props;

const onViewClick = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
Expand Down Expand Up @@ -86,9 +88,9 @@ function FileEntry(props: FileEntryProps): ReactElement {
removed: props.fileStatus === "removedFile",
invalid: props.fileStatus === "validationError"
})}
tabIndex={0}
onClick={onViewClick}
onKeyDown={onKeyDown}
tabIndex={canDownload ? 0 : undefined}
onClick={canDownload ? onViewClick : undefined}
onKeyDown={canDownload ? onKeyDown : undefined}
>
<div className={"entry-details"}>
<div
Expand All @@ -109,7 +111,7 @@ function FileEntry(props: FileEntryProps): ReactElement {
</div>

<div className={"entry-details-actions"}>
<div className={"download-icon"} />
{downloadUrl && <div className={"download-icon"} />}
<button
className={classNames("action-button", {
disabled: !props.canRemove
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="FileUploader" version="1.0.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="FileUploader" version="1.0.2" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="FileUploader.xml" />
</widgetFiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ export class FileStore {
return this.fileStatus === "existingFile" || this.fileStatus === "done";
}

get canDownload(): boolean {
return this.fileStatus === "done" || this.fileStatus === "existingFile";
}

async remove(): Promise<void> {
if (!this.canRemove || !this._objectItem) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { TranslationsStore } from "./TranslationsStore";
export class FileUploaderStore {
files: FileStore[] = [];
lastSeenItems: Set<ObjectItem["id"]> = new Set<ObjectItem["id"]>();
currentWaiting: Array<(v: ObjectItem) => void> = [];
currentWaiting: Array<(v: ObjectItem | undefined) => void> = [];
itemCreationTimeout?: number = undefined;

existingItemsLoaded = false;

Expand All @@ -24,6 +25,7 @@ export class FileUploaderStore {
_maxFileSize = 0;
_ds?: ListValue;
_maxFilesPerUpload: number;
_objectCreationTimeout: number;

errorMessage?: string = undefined;

Expand All @@ -35,6 +37,7 @@ export class FileUploaderStore {
this._maxFileSize = this._maxFileSizeMb * 1024 * 1024;
this._maxFilesPerUpload = props.maxFilesPerUpload;
this._uploadMode = props.uploadMode;
this._objectCreationTimeout = props.objectCreationTimeout;

this.acceptedFileTypes =
this._uploadMode === "files" ? parseAllowedFormats(props.allowedFileFormats) : getImageUploaderFormats();
Expand Down Expand Up @@ -100,6 +103,8 @@ export class FileUploaderStore {
}

this.lastSeenItems.add(item.id);

this.executeFileObjectCreation();
}

get allowedFormatsDescription(): string {
Expand All @@ -115,26 +120,54 @@ export class FileUploaderStore {
return this.existingItemsLoaded && !!this._createObjectAction;
}

requestFileObject(): Promise<ObjectItem> {
requestFileObject(): Promise<ObjectItem | undefined> {
if (!this.canRequestFile) {
throw new Error("Can't request file");
}

return new Promise<ObjectItem>(resolve => {
this._createObjectAction!.execute();

return new Promise<ObjectItem | undefined>(resolve => {
this.currentWaiting.push(resolve);

this.executeFileObjectCreation();
});
}

executeFileObjectCreation(): void {
if (!this.canRequestFile) {
throw new Error("Can't request file");
}

clearTimeout(this.itemCreationTimeout);
this.itemCreationTimeout = undefined;

if (!this.currentWaiting.length) {
return;
}
// we need to check if creation is taking too much time
// start the timer to measure how long it takes,
// if a threshold is reached, declare it a failure
// this means the action is probably misconfigured.
this.itemCreationTimeout = setTimeout(() => {
gjulivan marked this conversation as resolved.
Show resolved Hide resolved
console.error(
`Looks like the 'Action to create new files/images' action did not create any objects within ${this._objectCreationTimeout} seconds. Please check if '${this._widgetName}' widget is configured correctly.`
);
// fail all waiting
while (this.currentWaiting.length) {
this.currentWaiting.shift()?.(undefined);
}
}, this._objectCreationTimeout * 1000) as any as number;

this._createObjectAction!.execute();
}

setMessage(msg?: string): void {
this.errorMessage = msg;
}

processDrop(acceptedFiles: File[], fileRejections: FileRejection[]): void {
if (!this._createObjectAction || !this._createObjectAction.canExecute) {
console.error(
`'Action to create new files' is not available or can't be executed. Please check if '${this._widgetName}' widget is configured correctly.`
`'Action to create new files/images' is not available or can't be executed. Please check if '${this._widgetName}' widget is configured correctly.`
);
this.setMessage(this.translations.get("unavailableCreateActionMessage"));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ export const predefinedFormats: Record<PredefinedTypeEnum, FileCheckFormat> = {
description: "Text file"
},
anyImageFile: {
entries: [["image/*", []]],
entries: [
["image/png", [".png"]],
["image/jpeg", [".jpeg", ".jpg"]],
["image/gif", [".gif"]],
["image/bmp", [".bmp"]],
["image/webp", [".webp"]],
["image/svg+xml", [".svg"]]
],
description: "Image"
},
anyAudioFile: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface FileUploaderContainerProps {
removeButtonTextMessage: DynamicValue<string>;
removeSuccessMessage: DynamicValue<string>;
removeErrorMessage: DynamicValue<string>;
objectCreationTimeout: number;
}

export interface FileUploaderPreviewProps {
Expand Down Expand Up @@ -89,4 +90,5 @@ export interface FileUploaderPreviewProps {
removeButtonTextMessage: string;
removeSuccessMessage: string;
removeErrorMessage: string;
objectCreationTimeout: number | null;
}
Loading