diff --git a/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md b/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md index bb04444d0f..1e61e00bd9 100644 --- a/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md +++ b/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We fixed an issue where the dropzone turned grey without explanation when the file limit was reached. A message now appears below the dropzone stating "Maximum file count of X reached." + +### Added + +- We added a new "File limit reached" text property to customize the message shown when the upload limit is reached. +- We added a new "Maximum files per upload batch" property to limit how many files are committed to the server per drop or selection. Files exceeding the batch limit appear in the list with an error message explaining why they were not uploaded. + +### Changed + +- The "Maximum number of files" property is now optional. Leaving it empty or setting it to 0 means unlimited files are allowed. The default behavior is now unlimited (no cap). + ## [2.4.2] - 2026-04-23 ### Fixed diff --git a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorConfig.ts b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorConfig.ts index db775b3864..35e3b7afd4 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorConfig.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorConfig.ts @@ -1,6 +1,6 @@ +import { hideNestedPropertiesIn, hidePropertiesIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; import { FileUploaderPreviewProps } from "../typings/FileUploaderProps"; import { parseAllowedFormats } from "./utils/parseAllowedFormats"; -import { hideNestedPropertiesIn, hidePropertiesIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; import { predefinedFormats } from "./utils/predefinedFormats"; export function getProperties( diff --git a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorPreview.tsx b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorPreview.tsx index 860277b43f..60d7aa7221 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorPreview.tsx +++ b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.editorPreview.tsx @@ -1,6 +1,6 @@ +import classNames from "classnames"; import { ReactElement } from "react"; import { FileUploaderPreviewProps } from "../typings/FileUploaderProps"; -import classNames from "classnames"; export function preview(props: FileUploaderPreviewProps): ReactElement { return ( diff --git a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml index be3069e972..4b5373cab5 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml +++ b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml @@ -80,9 +80,14 @@ - + Maximum number of files - Limit the number of files per upload. + Maximum total number of files that can be associated at once. Leave empty or set to 0 for unlimited. Use this to cap the total number of attachments. + + + + Maximum files per upload batch + Limits how many files are committed to the server in a single drop or selection. Leave empty or set to 0 for unlimited. Smaller batch sizes reduce peak server load. @@ -163,6 +168,22 @@ Te veel bestanden toegevoegd. Slechts ### bestanden per upload zijn toegestaan. + + File limit reached + Shown below the dropzone when the maximum number of files is already reached. + + Maximum file count of ### reached. + Maximum aantal bestanden van ### bereikt. + + + + Batch limit exceeded + Shown on files that were dropped but not uploaded because the batch limit was already reached. + + File not uploaded. Batch limit of ### files per drop was reached. + Bestand niet geüpload. Batchlimiet van ### bestanden per upload is bereikt. + + Action to create new files is not available or failed diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/ActionButton.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/ActionButton.tsx index b2b716dae1..f5a7f3b441 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/components/ActionButton.tsx +++ b/packages/pluggableWidgets/file-uploader-web/src/components/ActionButton.tsx @@ -1,6 +1,6 @@ -import { MouseEvent, ReactElement, useCallback } from "react"; import classNames from "classnames"; import { ListActionValue } from "mendix"; +import { MouseEvent, ReactElement, useCallback } from "react"; import { FileStore } from "../stores/FileStore"; interface ActionButtonProps { diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx index 0d74d7ad0d..0bc1990d7c 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx +++ b/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx @@ -1,7 +1,7 @@ import { ReactElement, useCallback } from "react"; -import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; -import { ActionButton, FileActionButton } from "./ActionButton"; import { IconInternal } from "@mendix/widget-plugin-component-kit/IconInternal"; +import { ActionButton, FileActionButton } from "./ActionButton"; +import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; import { FileStore } from "../stores/FileStore"; import { useTranslationsStore } from "../utils/useTranslationsStore"; diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx index eb46f9b5df..e7d35fa606 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx +++ b/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx @@ -1,9 +1,9 @@ -import { observer } from "mobx-react-lite"; import classNames from "classnames"; +import { observer } from "mobx-react-lite"; import { Fragment, ReactElement } from "react"; import { FileRejection, useDropzone } from "react-dropzone"; -import { MimeCheckFormat } from "../utils/parseAllowedFormats"; import { TranslationsStore } from "../stores/TranslationsStore"; +import { MimeCheckFormat } from "../utils/parseAllowedFormats"; import { useTranslationsStore } from "../utils/useTranslationsStore"; interface DropzoneProps { diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/FileEntry.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/FileEntry.tsx index 3ac5575000..a8b74b9c6d 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/components/FileEntry.tsx +++ b/packages/pluggableWidgets/file-uploader-web/src/components/FileEntry.tsx @@ -1,13 +1,13 @@ import classNames from "classnames"; +import { observer } from "mobx-react-lite"; +import { KeyboardEvent, MouseEvent, ReactElement, ReactNode, useCallback } from "react"; +import { ActionsBar } from "./ActionsBar"; +import { FileIcon } from "./FileIcon"; import { ProgressBar } from "./ProgressBar"; import { UploadInfo } from "./UploadInfo"; -import { KeyboardEvent, MouseEvent, ReactElement, ReactNode, useCallback } from "react"; +import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; import { FileStatus, FileStore } from "../stores/FileStore"; -import { observer } from "mobx-react-lite"; -import { FileIcon } from "./FileIcon"; import { fileSize } from "../utils/fileSize"; -import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; -import { ActionsBar } from "./ActionsBar"; interface FileEntryContainerProps { store: FileStore; diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx index 520ffbfc23..a7c6b9d701 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx +++ b/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx @@ -3,11 +3,11 @@ import { observer } from "mobx-react-lite"; import { ReactElement, useCallback } from "react"; import { FileRejection } from "react-dropzone"; +import { Dropzone } from "./Dropzone"; +import { FileEntryContainer } from "./FileEntry"; import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; import { prepareAcceptForDropzone } from "../utils/prepareAcceptForDropzone"; import { useRootStore } from "../utils/useRootStore"; -import { FileEntryContainer } from "./FileEntry"; -import { Dropzone } from "./Dropzone"; import "../ui/FileUploader.scss"; @@ -26,7 +26,7 @@ export const FileUploaderRoot = observer((props: FileUploaderContainerProps): Re {!rootStore.isReadOnly && ( ; + _maxFilesPerUpload: DynamicValue | undefined; + _maxFilesPerBatch: DynamicValue | undefined; errorMessage?: string = undefined; @@ -37,6 +38,7 @@ export class FileUploaderStore { this._maxFileSizeMiB = props.maxFileSize; this._maxFileSize = this._maxFileSizeMiB * 1024 * 1024; this._maxFilesPerUpload = props.maxFilesPerUpload; + this._maxFilesPerBatch = props.maxFilesPerBatch; this._uploadMode = props.uploadMode; this.objectCreationHelper = new ObjectCreationHelper(this._widgetName, props.objectCreationTimeout); @@ -81,8 +83,11 @@ export class FileUploaderStore { errorMessage: observable, allowedFormatsDescription: computed, maxFilesPerUpload: computed, + maxFilesPerBatch: computed, _maxFilesPerUpload: observable, - isFileUploadLimitReached: computed + _maxFilesPerBatch: observable, + isFileUploadLimitReached: computed, + warningMessage: computed }); this.updateProps(props); @@ -91,8 +96,8 @@ export class FileUploaderStore { updateProps(props: FileUploaderContainerProps): void { this.objectCreationHelper.updateProps(props); - // Update max files properties this._maxFilesPerUpload = props.maxFilesPerUpload; + this._maxFilesPerBatch = props.maxFilesPerBatch; this.translations.updateProps(props); this.updateProcessor.processUpdate( @@ -116,11 +121,18 @@ export class FileUploaderStore { } get maxFilesPerUpload(): number { - const expressionValue = this._maxFilesPerUpload.value; + const expressionValue = this._maxFilesPerUpload?.value; + if (expressionValue) { + return expressionValue.toNumber(); + } + return 0; + } + + get maxFilesPerBatch(): number { + const expressionValue = this._maxFilesPerBatch?.value; if (expressionValue) { return expressionValue.toNumber(); } - // Fallback to unlimited return 0; } @@ -138,6 +150,13 @@ export class FileUploaderStore { return activeFiles.length >= this.maxFilesPerUpload; } + get warningMessage(): string | undefined { + if (this.isFileUploadLimitReached) { + return this.translations.get("uploadLimitReachedMessage", this.maxFilesPerUpload.toString()); + } + return this.errorMessage; + } + setMessage(msg?: string): void { this.errorMessage = msg; } @@ -160,6 +179,11 @@ export class FileUploaderStore { this.setMessage(); + const batchLimit = this.maxFilesPerBatch; + const filesToProcess = + batchLimit > 0 && acceptedFiles.length > batchLimit ? acceptedFiles.slice(0, batchLimit) : acceptedFiles; + const batchExcess = batchLimit > 0 && acceptedFiles.length > batchLimit ? acceptedFiles.slice(batchLimit) : []; + for (const file of fileRejections) { const newFileStore = FileStore.newFileWithError( file.file, @@ -186,7 +210,16 @@ export class FileUploaderStore { this.files.unshift(newFileStore); } - for (const file of acceptedFiles) { + for (const file of batchExcess) { + const newFileStore = FileStore.newFileWithError( + file, + this.translations.get("uploadBatchLimitExceededMessage", batchLimit.toString()), + this + ); + this.files.unshift(newFileStore); + } + + for (const file of filesToProcess) { const newFileStore = FileStore.newFile(file, this); if (this.isFileUploadLimitReached) { diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/TranslationsStore.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/TranslationsStore.ts index 2abc3f3e47..a9de503f5a 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/stores/TranslationsStore.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/stores/TranslationsStore.ts @@ -1,6 +1,6 @@ -import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; import { DynamicValue } from "mendix"; import { action, makeObservable, observable } from "mobx"; +import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; export class TranslationsStore { translationsMap: Map = new Map(); diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts new file mode 100644 index 0000000000..31e5c4c7a3 --- /dev/null +++ b/packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts @@ -0,0 +1,142 @@ +import { Big } from "big.js"; +import { DynamicValue } from "mendix"; +import { actionValue, dynamic, ListValueBuilder, obj } from "@mendix/widget-plugin-test-utils"; + +function unavailableDynamic(): DynamicValue { + return { status: "unavailable", value: undefined } as unknown as DynamicValue; +} +import { FileUploaderContainerProps } from "../../../typings/FileUploaderProps"; +import { FileUploaderStore } from "../FileUploaderStore"; +import { TranslationsStore } from "../TranslationsStore"; + +function buildProps(overrides: Partial = {}): FileUploaderContainerProps { + return { + name: "fileUploader1", + class: "", + style: undefined, + tabIndex: 0, + uploadMode: "files", + associatedFiles: new ListValueBuilder().withItems([]).build(), + associatedImages: new ListValueBuilder().withItems([]).build(), + readOnlyMode: false, + createFileAction: actionValue(true, false), + createImageAction: actionValue(true, false), + allowedFileFormats: [], + maxFilesPerUpload: dynamic(new Big(2)), + maxFilesPerBatch: unavailableDynamic(), + maxFileSize: 25, + objectCreationTimeout: 10, + dropzoneIdleMessage: dynamic("Drag and drop files here"), + dropzoneAcceptedMessage: dynamic("All files can be uploaded."), + dropzoneRejectedMessage: dynamic("Some files may not be uploadable."), + uploadInProgressMessage: dynamic("Uploading..."), + uploadSuccessMessage: dynamic("Uploaded successfully."), + uploadFailureGenericMessage: dynamic("An error occurred during uploading."), + uploadFailureInvalidFileFormatMessage: dynamic("File format is not supported, supported formats are ###."), + uploadFailureFileIsTooBigMessage: dynamic("File size exceeds the maximum limit of ### megabytes."), + uploadFailureTooManyFilesMessage: dynamic("Too many files added. Only ### files per upload are allowed."), + uploadLimitReachedMessage: dynamic("Maximum file count of ### reached."), + uploadBatchLimitExceededMessage: dynamic("File not uploaded. Batch limit of ### files per drop was reached."), + unavailableCreateActionMessage: dynamic( + "Can't upload files at this time. Please contact your system administrator." + ), + downloadButtonTextMessage: dynamic("Download this file"), + removeButtonTextMessage: dynamic("Remove this file"), + removeSuccessMessage: dynamic("Removed successfully."), + removeErrorMessage: dynamic("An error occurred while removing this file."), + enableCustomButtons: false, + customButtons: [], + onUploadSuccessFile: undefined, + onUploadSuccessImage: undefined, + onUploadFailureFile: undefined, + onUploadFailureImage: undefined, + ...overrides + }; +} + +function buildStore(overrides: Partial = {}): FileUploaderStore { + const props = buildProps(overrides); + const translations = new TranslationsStore(props); + return new FileUploaderStore(props, translations); +} + +describe("FileUploaderStore.warningMessage", () => { + test("returns undefined when no limit set and no error", () => { + const store = buildStore({ maxFilesPerUpload: unavailableDynamic() }); + expect(store.warningMessage).toBeUndefined(); + }); + + test("returns undefined when under limit and no error", () => { + const store = buildStore({ maxFilesPerUpload: dynamic(new Big(5)) }); + expect(store.warningMessage).toBeUndefined(); + }); + + test("returns limit-reached message when file limit is reached", () => { + const store = buildStore({ maxFilesPerUpload: dynamic(new Big(2)) }); + + store.files.push( + { fileStatus: "existingFile", _objectItem: obj("a") } as any, + { fileStatus: "existingFile", _objectItem: obj("b") } as any + ); + + expect(store.isFileUploadLimitReached).toBe(true); + expect(store.warningMessage).toBe("Maximum file count of 2 reached."); + }); + + test("returns errorMessage when limit not reached but error set", () => { + const store = buildStore({ maxFilesPerUpload: dynamic(new Big(5)) }); + store.setMessage("Some other error"); + + expect(store.warningMessage).toBe("Some other error"); + }); + + test("clears limit-reached message when file removed below limit", () => { + const store = buildStore({ maxFilesPerUpload: dynamic(new Big(2)) }); + + const fileA = { fileStatus: "existingFile", _objectItem: obj("a") } as any; + const fileB = { fileStatus: "existingFile", _objectItem: obj("b") } as any; + store.files.push(fileA, fileB); + + expect(store.warningMessage).toBe("Maximum file count of 2 reached."); + + store.files.splice(store.files.indexOf(fileA), 1); + + expect(store.isFileUploadLimitReached).toBe(false); + expect(store.warningMessage).toBeUndefined(); + }); +}); + +describe("FileUploaderStore.isFileUploadLimitReached", () => { + test("returns false when maxFilesPerUpload is 0 (unlimited)", () => { + const store = buildStore({ maxFilesPerUpload: dynamic(new Big(0)) }); + + store.files.push( + { fileStatus: "existingFile" } as any, + { fileStatus: "existingFile" } as any, + { fileStatus: "existingFile" } as any + ); + + expect(store.isFileUploadLimitReached).toBe(false); + }); + + test("returns false when maxFilesPerUpload expression is unavailable (unlimited fallback)", () => { + const store = buildStore({ maxFilesPerUpload: unavailableDynamic() }); + + store.files.push({ fileStatus: "existingFile" } as any); + + expect(store.isFileUploadLimitReached).toBe(false); + }); + + test("excludes missing, removedFile, and validationError from active count", () => { + const store = buildStore({ maxFilesPerUpload: dynamic(new Big(2)) }); + + store.files.push( + { fileStatus: "existingFile" } as any, + { fileStatus: "missing" } as any, + { fileStatus: "removedFile" } as any, + { fileStatus: "validationError" } as any + ); + + expect(store.isFileUploadLimitReached).toBe(false); + }); +}); diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/DatasourceUpdateProcessor.spec.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/DatasourceUpdateProcessor.spec.ts index 46383743e0..c1961b6ee0 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/DatasourceUpdateProcessor.spec.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/DatasourceUpdateProcessor.spec.ts @@ -1,6 +1,6 @@ -import { DatasourceUpdateProcessor, DatasourceUpdateProcessorCallbacks } from "../DatasourceUpdateProcessor"; -import { ListValueBuilder, obj } from "@mendix/widget-plugin-test-utils"; import { ObjectItem } from "mendix"; +import { ListValueBuilder, obj } from "@mendix/widget-plugin-test-utils"; +import { DatasourceUpdateProcessor, DatasourceUpdateProcessorCallbacks } from "../DatasourceUpdateProcessor"; const fileHasContentsMock = jest.fn(); jest.mock("../mx-data", () => ({ diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/parseAllowedFormats.spec.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/parseAllowedFormats.spec.ts index cae4020b47..d23035117c 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/parseAllowedFormats.spec.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/parseAllowedFormats.spec.ts @@ -1,6 +1,6 @@ +import { dynamicValue } from "@mendix/widget-plugin-test-utils"; import { AllowedFileFormatsType } from "../../../typings/FileUploaderProps"; import { parseAllowedFormats } from "../parseAllowedFormats"; -import { dynamicValue } from "@mendix/widget-plugin-test-utils"; describe("parseAllowedFormats", () => { test("returns parsed results for correct advanced formats", () => { diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/mx-data.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/mx-data.ts index e6477fc96f..d07f95ab76 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/mx-data.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/mx-data.ts @@ -1,5 +1,5 @@ -import { ObjectItem } from "mendix"; import { Big } from "big.js"; +import { ObjectItem } from "mendix"; export type MxObject = { getGuid(): string; diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/parseAllowedFormats.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/parseAllowedFormats.ts index a78200785a..e43fe3d0d1 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/parseAllowedFormats.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/parseAllowedFormats.ts @@ -1,5 +1,5 @@ -import { AllowedFileFormatsPreviewType, AllowedFileFormatsType } from "../../typings/FileUploaderProps"; import { FileCheckFormat, predefinedFormats } from "./predefinedFormats"; +import { AllowedFileFormatsPreviewType, AllowedFileFormatsType } from "../../typings/FileUploaderProps"; export type MimeCheckFormat = { [key: string]: string[]; diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/prepareAcceptForDropzone.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/prepareAcceptForDropzone.ts index e5d1d1fd11..6eed13a39e 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/prepareAcceptForDropzone.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/prepareAcceptForDropzone.ts @@ -1,5 +1,5 @@ -import { FileCheckFormat } from "./predefinedFormats"; import { MimeCheckFormat } from "./parseAllowedFormats"; +import { FileCheckFormat } from "./predefinedFormats"; export function prepareAcceptForDropzone(formats: FileCheckFormat[]): MimeCheckFormat { const acc = {} as MimeCheckFormat; diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/useRootStore.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/useRootStore.ts index 340b9e6fc0..a67bda945b 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/useRootStore.ts +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/useRootStore.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { FileUploaderStore } from "../stores/FileUploaderStore"; -import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; import { useTranslationsStore } from "./useTranslationsStore"; +import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; +import { FileUploaderStore } from "../stores/FileUploaderStore"; export function useRootStore(props: FileUploaderContainerProps): FileUploaderStore { const translations = useTranslationsStore(); diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/useTranslationsStore.tsx b/packages/pluggableWidgets/file-uploader-web/src/utils/useTranslationsStore.tsx index 209bb5a5c9..e5a94af416 100644 --- a/packages/pluggableWidgets/file-uploader-web/src/utils/useTranslationsStore.tsx +++ b/packages/pluggableWidgets/file-uploader-web/src/utils/useTranslationsStore.tsx @@ -1,6 +1,6 @@ import { createContext, ReactElement, ReactNode, useContext, useEffect, useState } from "react"; -import { TranslationsStore } from "../stores/TranslationsStore"; import { FileUploaderContainerProps } from "../../typings/FileUploaderProps"; +import { TranslationsStore } from "../stores/TranslationsStore"; function useInitTranslationsStore(props: FileUploaderContainerProps): TranslationsStore { const [store] = useState(() => { diff --git a/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts b/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts index 751fbf1fee..ea5521b265 100644 --- a/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts +++ b/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts @@ -59,7 +59,8 @@ export interface FileUploaderContainerProps { createFileAction?: ActionValue; createImageAction?: ActionValue; allowedFileFormats: AllowedFileFormatsType[]; - maxFilesPerUpload: DynamicValue; + maxFilesPerUpload?: DynamicValue; + maxFilesPerBatch?: DynamicValue; maxFileSize: number; dropzoneIdleMessage: DynamicValue; dropzoneAcceptedMessage: DynamicValue; @@ -70,6 +71,8 @@ export interface FileUploaderContainerProps { uploadFailureInvalidFileFormatMessage: DynamicValue; uploadFailureFileIsTooBigMessage: DynamicValue; uploadFailureTooManyFilesMessage: DynamicValue; + uploadLimitReachedMessage: DynamicValue; + uploadBatchLimitExceededMessage: DynamicValue; unavailableCreateActionMessage: DynamicValue; downloadButtonTextMessage: DynamicValue; removeButtonTextMessage: DynamicValue; @@ -103,6 +106,7 @@ export interface FileUploaderPreviewProps { createImageAction: {} | null; allowedFileFormats: AllowedFileFormatsPreviewType[]; maxFilesPerUpload: string; + maxFilesPerBatch: string; maxFileSize: number | null; dropzoneIdleMessage: string; dropzoneAcceptedMessage: string; @@ -113,6 +117,8 @@ export interface FileUploaderPreviewProps { uploadFailureInvalidFileFormatMessage: string; uploadFailureFileIsTooBigMessage: string; uploadFailureTooManyFilesMessage: string; + uploadLimitReachedMessage: string; + uploadBatchLimitExceededMessage: string; unavailableCreateActionMessage: string; downloadButtonTextMessage: string; removeButtonTextMessage: string;