feat: add multiple attachment input

This commit is contained in:
hexastack 2024-10-31 19:56:23 +01:00
parent 1792b125d3
commit f7ae8682e0
3 changed files with 140 additions and 2 deletions

View File

@ -66,12 +66,14 @@ export type FileUploadProps = {
accept: string; accept: string;
enableMediaLibrary?: boolean; enableMediaLibrary?: boolean;
onChange?: (data?: IAttachment | null) => void; onChange?: (data?: IAttachment | null) => void;
onUploadComplete?: () => void;
}; };
const AttachmentUploader: FC<FileUploadProps> = ({ const AttachmentUploader: FC<FileUploadProps> = ({
accept, accept,
enableMediaLibrary, enableMediaLibrary,
onChange, onChange,
onUploadComplete,
}) => { }) => {
const [attachment, setAttachment] = useState<IAttachment | undefined>( const [attachment, setAttachment] = useState<IAttachment | undefined>(
undefined, undefined,
@ -87,6 +89,7 @@ const AttachmentUploader: FC<FileUploadProps> = ({
toast.success(t("message.success_save")); toast.success(t("message.success_save"));
setAttachment(data); setAttachment(data);
onChange && onChange(data); onChange && onChange(data);
onUploadComplete && onUploadComplete();
}, },
}); });
const libraryDialogCtl = useDialog<never>(false); const libraryDialogCtl = useDialog<never>(false);

View File

@ -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<string[]>(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 (
<Box ref={ref}>
<FormLabel
component="label"
style={{ display: "inline-block", marginBottom: 8 }}
>
{label}
</FormLabel>
{attachments.map((attachmentId, index) => (
<Box
key={attachmentId}
sx={{ display: "flex", alignItems: "center", mb: 2 }}
>
<AttachmentThumbnail
id={attachmentId}
format={format}
size={size}
onChange={(newAttachment) => handleChange(newAttachment, index)}
/>
<Button
onClick={() => handleRemove(index)}
sx={{ ml: 2 }}
variant="outlined"
color="secondary"
>
Remove
</Button>
</Box>
))}
{hasPermission(EntityType.ATTACHMENT, PermissionAction.CREATE) && (
<AttachmentUploader
key={uploadKey}
accept={accept}
enableMediaLibrary={enableMediaLibrary}
onChange={(attachment) => handleChange(attachment)}
/>
)}
{helperText && (
<FormHelperText error={error}>{helperText}</FormHelperText>
)}
</Box>
);
},
);
MultipleAttachmentInput.displayName = "MultipleAttachmentInput";
export default MultipleAttachmentInput;

View File

@ -11,6 +11,7 @@ import { FormControlLabel, MenuItem, Switch } from "@mui/material";
import { ControllerRenderProps } from "react-hook-form"; import { ControllerRenderProps } from "react-hook-form";
import AttachmentInput from "@/app-components/attachment/AttachmentInput"; import AttachmentInput from "@/app-components/attachment/AttachmentInput";
import MultipleAttachmentInput from "@/app-components/attachment/MultipleAttachmentInput";
import { Adornment } from "@/app-components/inputs/Adornment"; import { Adornment } from "@/app-components/inputs/Adornment";
import AutoCompleteEntitySelect from "@/app-components/inputs/AutoCompleteEntitySelect"; import AutoCompleteEntitySelect from "@/app-components/inputs/AutoCompleteEntitySelect";
import { Input } from "@/app-components/inputs/Input"; import { Input } from "@/app-components/inputs/Input";
@ -20,7 +21,7 @@ import { useTranslate } from "@/hooks/useTranslate";
import { EntityType, Format } from "@/services/types"; import { EntityType, Format } from "@/services/types";
import { IBlock } from "@/types/block.types"; import { IBlock } from "@/types/block.types";
import { IHelper } from "@/types/helper.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"; import { MIME_TYPES } from "@/utils/attachment";
interface RenderSettingInputProps { interface RenderSettingInputProps {
@ -45,7 +46,7 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
}); });
switch (setting.type) { switch (setting.type) {
case "text": case SettingType.text:
case "textarea": case "textarea":
return ( return (
<Input <Input
@ -185,6 +186,18 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
size={128} size={128}
/> />
); );
case SettingType.multiple_attachment:
return (
<MultipleAttachmentInput
label={label}
{...field}
value={field.value}
accept={MIME_TYPES["images"].join(",")}
format="full"
size={128}
/>
);
default: default:
return <Input {...field} />; return <Input {...field} />;
} }