From f7ae8682e062aca3f6cedf4b7f3c1eabba493bd6 Mon Sep 17 00:00:00 2001 From: hexastack Date: Thu, 31 Oct 2024 19:56:23 +0100 Subject: [PATCH] feat: add multiple attachment input --- .../attachment/AttachmentUploader.tsx | 3 + .../attachment/MultipleAttachmentInput.tsx | 122 ++++++++++++++++++ .../src/components/settings/SettingInput.tsx | 17 ++- 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app-components/attachment/MultipleAttachmentInput.tsx diff --git a/frontend/src/app-components/attachment/AttachmentUploader.tsx b/frontend/src/app-components/attachment/AttachmentUploader.tsx index 2a7f75a4..73cdf176 100644 --- a/frontend/src/app-components/attachment/AttachmentUploader.tsx +++ b/frontend/src/app-components/attachment/AttachmentUploader.tsx @@ -66,12 +66,14 @@ export type FileUploadProps = { accept: string; enableMediaLibrary?: boolean; onChange?: (data?: IAttachment | null) => void; + onUploadComplete?: () => void; }; const AttachmentUploader: FC = ({ accept, enableMediaLibrary, onChange, + onUploadComplete, }) => { const [attachment, setAttachment] = useState( undefined, @@ -87,6 +89,7 @@ const AttachmentUploader: FC = ({ toast.success(t("message.success_save")); setAttachment(data); onChange && onChange(data); + onUploadComplete && onUploadComplete(); }, }); const libraryDialogCtl = useDialog(false); diff --git a/frontend/src/app-components/attachment/MultipleAttachmentInput.tsx b/frontend/src/app-components/attachment/MultipleAttachmentInput.tsx new file mode 100644 index 00000000..68922716 --- /dev/null +++ b/frontend/src/app-components/attachment/MultipleAttachmentInput.tsx @@ -0,0 +1,122 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { Box, Button, FormHelperText, FormLabel } from "@mui/material"; +import { forwardRef, useState } from "react"; + +import { useHasPermission } from "@/hooks/useHasPermission"; +import { EntityType } from "@/services/types"; +import { IAttachment } from "@/types/attachment.types"; +import { PermissionAction } from "@/types/permission.types"; + +import AttachmentThumbnail from "./AttachmentThumbnail"; +import AttachmentUploader from "./AttachmentUploader"; + +type MultipleAttachmentInputProps = { + label: string; + value: string[]; + format: "small" | "basic" | "full"; + accept: string; + enableMediaLibrary?: boolean; + size?: number; + onChange?: (ids: string[]) => void; + error?: boolean; + helperText?: string; +}; + +const MultipleAttachmentInput = forwardRef< + HTMLDivElement, + MultipleAttachmentInputProps +>( + ( + { + label, + value, + format, + accept, + enableMediaLibrary = true, + size, + onChange, + error, + helperText, + }, + ref, + ) => { + const [attachments, setAttachments] = useState(value); + const [uploadKey, setUploadKey] = useState(Date.now()); + const hasPermission = useHasPermission(); + const handleChange = (attachment?: IAttachment | null, index?: number) => { + if (attachment) { + const updatedAttachments = [...attachments]; + + if (index !== undefined) { + updatedAttachments[index] = attachment.id; + } else { + updatedAttachments.push(attachment.id); + } + setAttachments(updatedAttachments); + onChange && onChange(updatedAttachments); + setUploadKey(Date.now()); + } + }; + const handleRemove = (index: number) => { + const updatedAttachments = attachments.filter((_, i) => i !== index); + + setAttachments(updatedAttachments); + onChange && onChange(updatedAttachments); + setUploadKey(Date.now()); + }; + + return ( + + + {label} + + {attachments.map((attachmentId, index) => ( + + handleChange(newAttachment, index)} + /> + + + ))} + {hasPermission(EntityType.ATTACHMENT, PermissionAction.CREATE) && ( + handleChange(attachment)} + /> + )} + {helperText && ( + {helperText} + )} + + ); + }, +); + +MultipleAttachmentInput.displayName = "MultipleAttachmentInput"; + +export default MultipleAttachmentInput; diff --git a/frontend/src/components/settings/SettingInput.tsx b/frontend/src/components/settings/SettingInput.tsx index edb95d25..d20136a3 100644 --- a/frontend/src/components/settings/SettingInput.tsx +++ b/frontend/src/components/settings/SettingInput.tsx @@ -11,6 +11,7 @@ import { FormControlLabel, MenuItem, Switch } from "@mui/material"; import { ControllerRenderProps } from "react-hook-form"; import AttachmentInput from "@/app-components/attachment/AttachmentInput"; +import MultipleAttachmentInput from "@/app-components/attachment/MultipleAttachmentInput"; import { Adornment } from "@/app-components/inputs/Adornment"; import AutoCompleteEntitySelect from "@/app-components/inputs/AutoCompleteEntitySelect"; import { Input } from "@/app-components/inputs/Input"; @@ -20,7 +21,7 @@ import { useTranslate } from "@/hooks/useTranslate"; import { EntityType, Format } from "@/services/types"; import { IBlock } from "@/types/block.types"; import { IHelper } from "@/types/helper.types"; -import { ISetting } from "@/types/setting.types"; +import { ISetting, SettingType } from "@/types/setting.types"; import { MIME_TYPES } from "@/utils/attachment"; interface RenderSettingInputProps { @@ -45,7 +46,7 @@ const SettingInput: React.FC = ({ }); switch (setting.type) { - case "text": + case SettingType.text: case "textarea": return ( = ({ size={128} /> ); + + case SettingType.multiple_attachment: + return ( + + ); default: return ; }