import { type MultipartPart, parseMultipart } from "@mjackson/multipart-parser"; import { lazy, Suspense, useMemo } from "react"; import { languageFromContentType } from "../../lib/contentType"; import { Banner } from "../core/Banner"; import { Icon } from "../core/Icon"; import { LoadingIcon } from "../core/LoadingIcon"; import { TabContent, Tabs } from "../core/Tabs/Tabs"; import { AudioViewer } from "./AudioViewer"; import { CsvViewer } from "./CsvViewer"; import { ImageViewer } from "./ImageViewer"; import { SvgViewer } from "./SvgViewer"; import { TextViewer } from "./TextViewer"; import { VideoViewer } from "./VideoViewer"; import { WebPageViewer } from "./WebPageViewer"; const PdfViewer = lazy(() => import("./PdfViewer").then((m) => ({ default: m.PdfViewer }))); interface Props { data: Uint8Array; boundary: string; idPrefix?: string; } export function MultipartViewer({ data, boundary, idPrefix = "multipart" }: Props) { const parseResult = useMemo(() => { try { const maxFileSize = 1024 * 1024 * 10; // 10MB const parsed = parseMultipart(data, { boundary, maxFileSize }); const parts = Array.from(parsed); return { parts, error: null }; } catch (err) { return { parts: [], error: err instanceof Error ? err.message : String(err) }; } }, [data, boundary]); const { parts, error } = parseResult; if (error) { return ( Failed to parse multipart data: {error} ); } if (parts.length === 0) { return ( No multipart parts found ); } return ( ({ label: part.name ?? "", value: tabValue(part, i), rightSlot: part.filename && part.headers.contentType.mediaType?.startsWith("image/") ? (
) : part.filename ? ( ) : null, }))} > {parts.map((part, i) => ( ))}
); } function Part({ part }: { part: MultipartPart }) { const mimeType = part.headers.contentType.mediaType ?? null; const contentTypeHeader = part.headers.get("content-type"); const { uint8Array, content, detectedLanguage } = useMemo(() => { const uint8Array = new Uint8Array(part.arrayBuffer); const content = new TextDecoder().decode(part.arrayBuffer); const detectedLanguage = languageFromContentType(contentTypeHeader, content); return { uint8Array, content, detectedLanguage }; }, [part, contentTypeHeader]); if (mimeType?.match(/^image\/svg/i)) { return ; } if (mimeType?.match(/^image/i)) { return ; } if (mimeType?.match(/^audio/i)) { return ; } if (mimeType?.match(/^video/i)) { return ; } if (mimeType?.match(/csv|tab-separated/i)) { return ; } if (mimeType?.match(/^text\/html/i) || detectedLanguage === "html") { return ; } if (mimeType?.match(/pdf/i)) { return ( }> ); } return ; } function tabValue(part: MultipartPart, i: number) { return `${part.name ?? ""}::${i}`; }