import type { RowMedia } from "@toolctrl/db/prisma-browser"; import clsx from "clsx"; import { lazy, type ReactNode, Suspense, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import type { MediaDto } from "~/application/dtos/entities/MediaDto"; import type { FileBase64 } from "~/application/dtos/shared/FileBase64"; import HintTooltip from "~/components/ui/tooltips/HintTooltip"; import MediaItem from "~/components/ui/uploaders/MediaItem"; import UploadDocuments from "~/components/ui/uploaders/UploadDocument"; import { useLocaleFormatters } from "~/locale/format"; import { updateItemByIdx } from "~/utils/shared/ObjectUtils"; const PreviewMediaModal = lazy(() => import("~/components/ui/media/PreviewMediaModal")); interface Props { name: string; title?: string; initialMedia?: RowMedia[] | MediaDto[] | undefined; disabled?: boolean; onSelected?: (item: MediaDto[]) => void; className?: string; readOnly?: boolean; required?: boolean; min?: number; max?: number; accept?: string; hint?: ReactNode; help?: string; icon?: string; maxSize?: number; uploadText?: string; autoFocus?: boolean; } export default function InputMedia({ name, title, initialMedia, disabled, onSelected, className, readOnly, required, max, accept, hint, help, maxSize, uploadText, autoFocus, }: Props) { const { t } = useTranslation(); const formatters = useLocaleFormatters(); const [error, setError] = useState(undefined); const [items, setItems] = useState(initialMedia ?? []); const [selectedItem, setSelectedItem] = useState(); useEffect(() => { if (onSelected) { onSelected(items); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [items]); function deleteMedia(idx: number) { setItems(items.filter((_f, index) => index !== idx)); } function onDroppedFiles(e: FileBase64[]) { setError(undefined); if (max) { if (e.length + items.length > max) { setError(t("components.media.errors.maxFilesExceeded", { max })); return; } } if (maxSize) { const bytesToMegaBytes = (bytes: number) => bytes / (1024 * 1024); const found = e.find((f) => bytesToMegaBytes(f.file.size) > maxSize); if (found) { setError( t("components.media.errors.maxSizeExceeded", { maxSize: formatters.number(maxSize, { maximumFractionDigits: 2 }), foundSize: formatters.number(bytesToMegaBytes(found.file.size), { maximumFractionDigits: 2 }), }) ); return; } } if (accept?.includes(".")) { const acceptedFileExtensions = accept.split(","); const invalidFiles = e.map((f) => { let foundExtension = ""; acceptedFileExtensions .filter((f) => f) .forEach((element) => { if (f.file.name.toLowerCase().endsWith(element.toLowerCase())) { foundExtension = element; } }); if (!foundExtension) { return f; } return null; }); if (invalidFiles.find((f) => f !== null)) { setError(t("components.media.errors.invalidFileType", { accept })); return; } } const newAttachments: MediaDto[] = [...items]; e.forEach(({ file, base64 }) => { newAttachments.push({ title: file.name.split(".").slice(0, -1).join("."), file: base64, name: file.name, type: file.type, // _file: file, }); }); setItems(newAttachments); } function preview(item: MediaDto) { setSelectedItem(item); } function download(item: MediaDto) { const downloadLink = document.createElement("a"); downloadLink.href = item.publicUrl ?? item.file; downloadLink.download = item.name; downloadLink.click(); } function isDisabled() { return disabled || readOnly; } return (
{/* */} {!isDisabled() && items.length === 0 && ( )} {isDisabled() && items.length === 0 && (
{/* Subframe FileUpload empty dropzone hull: rounded-lg, dashed border-strong, neutral-primary fill, px-6 py-8. */}

{t("shared.files.noFilesUploaded")}

)} {items.map((item, idx) => { return ; })} {items.length > 0 ? (
{items.map((item, idx) => { return ( updateItemByIdx(items, setItems, idx, { title, name: title + "." + item.name.split(".").slice(-1)[0], }) } onDelete={() => deleteMedia(idx)} readOnly={isDisabled()} onDownload={() => download(item)} onPreview={item.type.includes("pdf") || item.type.includes("image") ? () => preview(item) : undefined} /> ); })}
) : (
)} {isDisabled() && items.filter((f) => f.type.includes("image")).length > 0 && (
{items .filter((f) => f.type.includes("image")) .map((item) => { return (
{/* Subframe-style preview hull: solid border-default, neutral-primary fill, rounded-md. */} {item.title}
); })}
)}
{selectedItem && ( setSelectedItem(undefined)} onDownload={() => download(selectedItem)} /> )}
); }