From aada2cf8ea0601dbbe47cd3fd376ca3a78d63b0a Mon Sep 17 00:00:00 2001 From: Widle Studio LLP Date: Tue, 7 Apr 2026 12:24:29 +0530 Subject: [PATCH] added component --- .../multi-file-viewer-uploader/README.md | 194 +++++ .../multi-file-viewer-uploader/cover.png | Bin 0 -> 63012 bytes .../multi-file-viewer-uploader/metadata.json | 14 + .../multi-file-viewer-uploader/package.json | 48 ++ .../src/components/index.css | 497 ++++++++++++ .../src/components/index.tsx | 743 ++++++++++++++++++ .../multi-file-viewer-uploader/src/index.tsx | 1 + 7 files changed, 1497 insertions(+) create mode 100644 components/multi-file-viewer-uploader/README.md create mode 100644 components/multi-file-viewer-uploader/cover.png create mode 100644 components/multi-file-viewer-uploader/metadata.json create mode 100644 components/multi-file-viewer-uploader/package.json create mode 100644 components/multi-file-viewer-uploader/src/components/index.css create mode 100644 components/multi-file-viewer-uploader/src/components/index.tsx create mode 100644 components/multi-file-viewer-uploader/src/index.tsx diff --git a/components/multi-file-viewer-uploader/README.md b/components/multi-file-viewer-uploader/README.md new file mode 100644 index 0000000..333381b --- /dev/null +++ b/components/multi-file-viewer-uploader/README.md @@ -0,0 +1,194 @@ +# Multi File Viewer + +A Retool custom component for uploading and previewing multiple files, including images, PDFs, video, audio, text, JSON, CSV, and Excel files. + +## What `{{ multiFileViewer1 }}` returns + +When you reference the component directly in Retool: + +```js +{{ multiFileViewer1 }} +``` + +it returns the component state object, which looks like this shape: + +```js +{ + pluginType: "DynamicWidget_MultiFileUploaderAndViewer_MultiFileViewer", + heightType: "auto", + files: [], + selectedFile: { + id: "", + name: "", + url: "", + previewUrl: "", + mimeType: "", + label: "", + kind: "unknown" + }, + acceptedKind: "", + collectionUuid: "...", + allowMixedFileTypes: true, + id: "multiFileViewer1" +} +``` + +Depending on uploads and selection, `files` and `selectedFile` will contain real values. + +## Recommended Retool usage + +### Get the full component object + +```js +{{ multiFileViewer1 }} +``` + +### Get all uploaded files + +```js +{{ multiFileViewer1.files }} +``` + +### Get the currently selected file + +```js +{{ multiFileViewer1.selectedFile }} +``` + +### Get the selected file name + +```js +{{ multiFileViewer1.selectedFile.name }} +``` + +### Get the selected file MIME type + +```js +{{ multiFileViewer1.selectedFile.mimeType }} +``` + +### Get the selected file preview URL + +```js +{{ multiFileViewer1.selectedFile.previewUrl }} +``` + +### Get the selected file kind + +```js +{{ multiFileViewer1.selectedFile.kind }} +``` + +### Check whether mixed file types are enabled + +```js +{{ multiFileViewer1.allowMixedFileTypes }} +``` + +### Get the accepted upload kind for single-type mode + +```js +{{ multiFileViewer1.acceptedKind }} +``` + +## File object structure + +Each item inside `multiFileViewer1.files` follows this shape: + +```js +{ + id: "file-id", + name: "example.pdf", + url: "blob:...", + previewUrl: "blob:...", + mimeType: "application/pdf", + label: "PDF", + kind: "pdf", + objectUrl: "blob:...", + base64: "..." +} +``` + +## Selected file object structure + +`multiFileViewer1.selectedFile` follows this shape: + +```js +{ + id: "file-id", + name: "example.pdf", + url: "blob:...", + previewUrl: "blob:...", + mimeType: "application/pdf", + label: "PDF", + kind: "pdf" +} +``` + +## Common examples + +### Send the selected file to a query + +```js +{{ + { + name: multiFileViewer1.selectedFile.name, + type: multiFileViewer1.selectedFile.mimeType, + kind: multiFileViewer1.selectedFile.kind, + previewUrl: multiFileViewer1.selectedFile.previewUrl + } +}} +``` + +### Send all files to a query + +```js +{{ multiFileViewer1.files }} +``` + +### Get only file names + +```js +{{ multiFileViewer1.files.map(file => file.name) }} +``` + +### Get only base64 values + +```js +{{ multiFileViewer1.files.map(file => file.base64) }} +``` + +### Get the first uploaded file + +```js +{{ multiFileViewer1.files[0] }} +``` + +## Notes + +- `files` is an array of uploaded files. +- `selectedFile` is the file currently chosen in the sidebar. +- `acceptedKind` is used when mixed file uploads are turned off. +- `allowMixedFileTypes` is the checkbox-controlled setting for mixed uploads. +- `heightType: "auto"` means the component is configured to work with auto height in Retool. +- `previewUrl` and `url` are usually blob URLs for local uploaded files. + +## Best practice + +In most Retool queries and transformers, use nested access instead of the whole object when possible. + +Good: + +```js +{{ multiFileViewer1.selectedFile }} +{{ multiFileViewer1.files }} +{{ multiFileViewer1.allowMixedFileTypes }} +``` + +Less useful on its own: + +```js +{{ multiFileViewer1 }} +``` + +because that returns the entire component state wrapper, not just the selected file. diff --git a/components/multi-file-viewer-uploader/cover.png b/components/multi-file-viewer-uploader/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc47b7f966c0753c7ea63c2f3255c40a919cfb8 GIT binary patch literal 63012 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

f|e-vOVeiY#~P=cS%kGX zkX5ma)rH>%b}0vLHN<@ATPa-$NcV;CgLdQ9lcP;BZe{U4=T6^8q&0tF$#g44I|eoDrhRo~D%Rm{fmso^3D<$)fS>FshTQiq+nWzS4J1XD!_6iB`m?QVLnIu|w7 zbEe6phchYGxHPR2c`ruu^qw?HNJlQPr?R9ZLQHq=-L~`lWW&nyn{=%~=8(du=$LGZ z{6JWv=(ye)*M?vr?m=`%s;<}2TAxwIRdF>}@LPiPYr_a^#rf?I!337%`u zokX5|$@itQrp+E|C(;JdFKvnO3k{bfgINyXf+JJI!uc;b80BT#?F7*3w`5UZe%1HV z=i<}COv?~+r@vwidio)&J<+xww&UHd%5polF^PGJWZRP>6iQY`o`vCCO-JaRu`Rll zRYZ+kFLSmvlPovsHf`jZ3K!Qt4(#~=7%{sF%yGjbf6~}+yok$DV8-YOjdgi(d#R=K zz5*ySPi^B*;vIJ1@0$Hdb`EN1%gDr{!w_NHnu5u#&`O6OXWGQZT1`@PP-;7*% zlxzVNq}0eSQ=4z_&$T7#@5`C&n>vvEyr38m*vDBHnJ0NJMjk&Ej&`&z5B~0oF7)Zk(>8CD}e$fS;Iz z<=zGoq{ijyKD&MC{G{w6?fnxu#ndA&jJ8?56_pJfgBze9+a>r0!F^xb`a^qV@|ewG zv7U-o*4sDfX^_@P({~xXb~Ymj75{L}728-%KXSlccmaFKR%mhC=i`{F*?H4TarPH7 zq*46lkob^-c3Ia2k1`YSu@h&C{xrYlE1Bx=A_Kbg2p6c>@W*#k(H~-h=>@zBvIi-bed{;Fa-Qv8cZrFK1HYW1~h6Hm!xPFH1aranO71#;&ejnbl394q<=8z&! zjH6d`v3Q)<1H&Tp?$wuBpvbnq%5|Uf9GDJ?DA=>TG0Y|@0jqAw%jMeVRz=YaZVs8b zQTkSTDe+d(N(D?N?-{o1^R6+wK|d&pRW;o%(v9r&;=sg|Xzkk#ECZ5N@di_%FlkU{J}D+T~kyim2Vda&2-zhYMr=k zN?0_#ZtYQ$D@z*KUepfd$fo|9slS8#a8gpFrAM#bZYj2%n<)b3#EC{o?9ou|bc(iD zpf2%#^!CVR=451r)FbKI`^M%-OWPGxqX7%vmjU&8b#hqyBg{#de4hYiq5RH$0aTP$ z5xpdN68o1mY60-PO%t<|+3(DzXFz%bmFy~Ix6}PTW+?lIq@4zam=@7?;g0Qtk(M!E z*7*UGDncHm6U*pos08dm1S*7No+}zdsmS%rt)9QCvK^q^JVV=c;;feQ{_JE(asxpZ zae>FWD+W*dZR{(Kx<-rcW~bid_v5`jCHE|KdZS>hdvXAv_m9Gn?8SvP{0)zB>gk(P zI>N-U_W}gxsYqt?p|X9b5fw>9|Kn;sAVSqG=Aw3d(Z61@4SV@ZK-^4*!}s;OH_5?i z8C3u*>#@2?5_=|l%0Dls6VoK<7$uSNh7G=CE>G;e!C zw0Uvof${%&?VCG57*qD~+4uhcyaec{gaZ@iNzJ$s3%h^e5vl>`S|KgRVb6U2RRiC) zCk*!=0RMVMWkAWYdTS#H)$_J9!oWJx^ly7dZJGFmT@YldzOEoU{6v7~jtQb+Bj{o|u z%lgpoa-3`|@_o}fpL=e9ZbrEsw=gWc{q#Oiag#e-QY$2oe5Fb~GWx zUtZu^rchz{i9rWf99kOH8DzYA+!j^%%Vmf|(2%3Iw$4gC82wmt`6jiEMdFD*Ki9HJ zn5s2mR=TEN9zsl_{!L_N?d0cI-)ssywV6kJJW^zY;KGVSfhd_UB2R1Ri{_aZaw^w&bC|I{-2QPHH2oN+*1takneg_ zRgd966k<@YgzFF&nD9`s#}Z5$*g8KjnR~M*1>cjUWDB6MKHw(}jXm%<+%Jl zLvbI6TuY}~_PV`~ zn~?)o4;PjHQ$Gj4nc!wm#cyr!J@w{aLogv8IHMu{E&3p>|AZ=IK9Kx=20.0.0" + }, + "scripts": { + "dev": "npx retool-ccl dev", + "deploy": "npx retool-ccl deploy", + "test": "vitest" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/react": "^18.2.55", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "postcss-modules": "^6.0.0", + "prettier": "^3.0.3", + "vitest": "^4.0.17" + }, + "retoolCustomComponentLibraryConfig": { + "name": "MultileFileUploaderAndViewer", + "label": "Multile file uploader and viewer", + "description": "We can view multiple files that we uploaded", + "entryPoint": "src/index.tsx", + "outputPath": "dist" + } +} \ No newline at end of file diff --git a/components/multi-file-viewer-uploader/src/components/index.css b/components/multi-file-viewer-uploader/src/components/index.css new file mode 100644 index 0000000..f506e84 --- /dev/null +++ b/components/multi-file-viewer-uploader/src/components/index.css @@ -0,0 +1,497 @@ +.mfv-root { + width: 100%; + display: flex; + flex-direction: column; + background: #ffffff; + border: 1px solid #dbe3f0; + border-radius: 16px; + overflow: hidden; + box-sizing: border-box; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.mfv-topbar { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12px; + padding: 18px 20px 14px; + border-bottom: 1px solid #e5e7eb; + background: linear-gradient(180deg, #f8fbff 0%, #f8fafc 100%); + flex-wrap: wrap; +} + +.mfv-topbar-text { + min-width: 0; + flex: 1 1 280px; +} + +.mfv-title { + font-size: 18px; + font-weight: 800; + color: #0f172a; + line-height: 1.25; +} + +.mfv-subtitle { + margin-top: 4px; + font-size: 12.5px; + color: #64748b; + line-height: 1.5; + overflow-wrap: anywhere; +} + +.mfv-topbar-right { + display: flex; + gap: 8px; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + min-width: 0; + flex: 1 1 260px; +} + +.mfv-pill { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 30px; + max-width: 180px; + padding: 0 12px; + border-radius: 999px; + border: 1px solid #dbe3f0; + background: #ffffff; + font-size: 13px; + font-weight: 700; + color: #475569; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + box-sizing: border-box; +} + +.mfv-clear-btn { + min-height: 32px; + max-width: 180px; + padding: 0 12px; + border-radius: 999px; + border: 1px solid #dbe3f0; + background: #ffffff; + color: #334155; + font-weight: 700; + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; +} + +.mfv-clear-btn:hover { + background: #f8fafc; + border-color: #cbd5e1; +} + +/* Upload area */ + +.mfv-upload-zone { + margin: 12px 20px; + border: 2px dashed #cbd5e1; + border-radius: 14px; + background: #f8fafc; + padding: 20px; + display: flex; + align-items: flex-start; + gap: 14px; + cursor: pointer; + transition: all 0.15s ease; + box-sizing: border-box; +} + +.mfv-upload-zone:hover { + border-color: #94a3b8; + background: #f8fbff; +} + +.mfv-upload-zone.is-dragover { + border-color: #2563eb; + background: #eff6ff; +} + +.mfv-hidden-input { + display: none; +} + +.mfv-upload-icon { + font-size: 24px; + line-height: 1; + flex: 0 0 auto; +} + +.mfv-upload-copy { + min-width: 0; + flex: 1 1 auto; +} + +.mfv-upload-title { + font-size: 13px; + font-weight: 700; + color: #334155; + line-height: 1.4; + overflow-wrap: anywhere; +} + +.mfv-upload-subtitle { + font-size: 11.5px; + color: #94a3b8; + margin-top: 3px; + line-height: 1.5; + overflow-wrap: anywhere; +} + +/* Main body */ + +.mfv-body { + display: grid; + min-height: 0; + width: 100%; + box-sizing: border-box; +} + +.mfv-body.with-sidebar { + grid-template-columns: minmax(240px, 280px) minmax(0, 1fr); +} + +.mfv-body.no-sidebar { + grid-template-columns: minmax(0, 1fr); +} + +.mfv-sidebar { + border-right: 1px solid #e5e7eb; + padding: 16px 14px; + background: #fafafa; + box-sizing: border-box; + min-width: 0; +} + +.mfv-sidebar-label { + font-size: 12px; + font-weight: 800; + letter-spacing: 0.08em; + color: #94a3b8; + margin-bottom: 12px; +} + +.mfv-file-row-wrap { + position: relative; + margin-bottom: 8px; +} + +.mfv-file-row { + width: 100%; + display: flex; + align-items: center; + gap: 10px; + padding: 10px 42px 10px 12px; + border: 1px solid #e2e8f0; + border-radius: 14px; + cursor: pointer; + text-align: left; + background: #ffffff; + transition: all 0.15s ease; + box-sizing: border-box; + min-width: 0; +} + +.mfv-file-row:hover { + border-color: #cbd5e1; + background: #f8fafc; +} + +.mfv-file-row.active { + border-color: #2563eb; + background: #eff6ff; + box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.08); +} + +.mfv-icon-wrap { + width: 38px; + height: 38px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + background: #f1f5f9; + font-size: 18px; + flex: 0 0 38px; +} + +.mfv-file-meta { + min-width: 0; + flex: 1 1 auto; + overflow: hidden; +} + +.mfv-file-name { + font-size: 13px; + font-weight: 700; + color: #0f172a; + line-height: 1.35; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.mfv-file-label { + font-size: 11.5px; + color: #64748b; + margin-top: 3px; + line-height: 1.35; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.mfv-remove-btn { + position: absolute; + top: 6px; + right: 6px; + width: 22px; + height: 22px; + border-radius: 999px; + border: 1px solid #e2e8f0; + background: #ffffff; + color: #64748b; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + z-index: 2; +} + +.mfv-remove-btn:hover { + background: #f8fafc; + color: #0f172a; +} + +.mfv-viewer { + min-width: 0; + padding: 16px; + background: #ffffff; + box-sizing: border-box; +} + +.mfv-center { + min-height: 240px; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.mfv-fallback-card { + width: min(100%, 460px); + text-align: center; + border: 1px solid #e5e7eb; + border-radius: 16px; + padding: 22px 24px; + background: #ffffff; + box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04); + box-sizing: border-box; +} + +.mfv-fallback-title { + font-size: 18px; + font-weight: 800; + color: #0f172a; + line-height: 1.35; + overflow-wrap: anywhere; +} + +.mfv-fallback-subtitle { + font-size: 13px; + color: #475569; + margin-top: 10px; + line-height: 1.6; + overflow-wrap: anywhere; +} + +.mfv-hero-image-wrap { + width: 100%; + border-radius: 16px; + overflow: hidden; + border: 1px solid #e5e7eb; + background: #f8fafc; + box-sizing: border-box; +} + +.mfv-hero-image { + display: block; + width: 100%; + height: auto; + max-height: min(70vh, 720px); + object-fit: contain; + background: #f8fafc; +} + +.mfv-embed-wrap { + width: 100%; + border: 1px solid #e5e7eb; + border-radius: 14px; + overflow: hidden; + background: #ffffff; + box-sizing: border-box; +} + +.mfv-iframe { + width: 100%; + height: clamp(420px, 65vh, 900px); + border: 0; + display: block; + background: #ffffff; +} + +.mfv-media { + width: 100%; + height: auto; + max-height: min(70vh, 720px); + display: block; + background: #000000; +} + +.mfv-audio-card { + width: min(100%, 560px); + border: 1px solid #e5e7eb; + border-radius: 16px; + padding: 20px; + background: #ffffff; + box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04); + box-sizing: border-box; +} + +.mfv-audio-title { + font-size: 16px; + font-weight: 800; + color: #0f172a; + margin-bottom: 10px; + line-height: 1.4; + overflow-wrap: anywhere; +} + +.mfv-audio { + width: 100%; +} + +.mfv-code { + margin: 0; + width: 100%; + box-sizing: border-box; + white-space: pre-wrap; + word-break: break-word; + overflow-wrap: anywhere; + font-size: 12.5px; + line-height: 1.6; + color: #0f172a; + background: #f8fafc; + border: 1px solid #e5e7eb; + border-radius: 14px; + padding: 16px; + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; +} + +.mfv-table-wrap { + width: 100%; + overflow: auto; + border: 1px solid #e5e7eb; + border-radius: 14px; + background: #ffffff; + box-sizing: border-box; +} + +.mfv-table { + width: 100%; + border-collapse: collapse; + font-size: 12.5px; +} + +.mfv-table th { + position: sticky; + top: 0; + background: #f8fafc; + color: #334155; + font-weight: 800; + text-align: left; + padding: 10px 12px; + border-bottom: 1px solid #e5e7eb; + white-space: nowrap; +} + +.mfv-table td { + padding: 10px 12px; + border-bottom: 1px solid #f1f5f9; + color: #0f172a; + vertical-align: top; + overflow-wrap: anywhere; +} + +/* Responsive behavior */ + +@media (max-width: 1100px) { + .mfv-body.with-sidebar { + grid-template-columns: minmax(220px, 260px) minmax(0, 1fr); + } + + .mfv-pill, + .mfv-clear-btn { + max-width: 150px; + } +} + +@media (max-width: 860px) { + .mfv-topbar { + padding: 16px; + } + + .mfv-upload-zone { + margin: 12px 16px; + padding: 16px; + } + + .mfv-viewer { + padding: 14px; + } + + .mfv-body.with-sidebar { + grid-template-columns: 1fr; + } + + .mfv-sidebar { + border-right: 0; + border-bottom: 1px solid #e5e7eb; + } + + .mfv-pill, + .mfv-clear-btn { + max-width: none; + } +} + +@media (max-width: 640px) { + .mfv-topbar-right { + justify-content: flex-start; + } + + .mfv-upload-zone { + flex-direction: row; + align-items: flex-start; + } + + .mfv-iframe { + height: clamp(320px, 55vh, 720px); + } + + .mfv-fallback-card, + .mfv-audio-card { + padding: 18px; + } +} \ No newline at end of file diff --git a/components/multi-file-viewer-uploader/src/components/index.tsx b/components/multi-file-viewer-uploader/src/components/index.tsx new file mode 100644 index 0000000..cc6d8d0 --- /dev/null +++ b/components/multi-file-viewer-uploader/src/components/index.tsx @@ -0,0 +1,743 @@ +import React, { FC, useMemo, useRef, useState } from "react"; +import { Retool } from "@tryretool/custom-component-support"; +import * as XLSX from "xlsx"; +import "./index.css"; + +type FileKind = + | "image" + | "pdf" + | "video" + | "audio" + | "text" + | "json" + | "csv" + | "excel" + | "office" + | "unknown"; + +type ViewerFile = { + id: string; + name: string; + url: string; + previewUrl?: string; + mimeType?: string; + label: string; + kind: FileKind; + objectUrl?: string; + base64?: string; +}; + +const KIND_ICONS: Record = { + image: "🖼️", + pdf: "📄", + video: "🎬", + audio: "🎵", + text: "📝", + json: "🧩", + csv: "📊", + excel: "📗", + office: "📎", + unknown: "📁", +}; + +const MultiFileViewer: FC = () => { + Retool.useComponentSettings({ + defaultWidth: 9, + defaultHeight: 28, + }); + + const [files, setFiles] = Retool.useStateArray({ + name: "files", + initialValue: [], + inspector: "hidden", + }); + + const [_selectedFile, setSelectedFile] = Retool.useStateObject({ + name: "selectedFile", + initialValue: { + id: "", + name: "", + url: "", + previewUrl: "", + mimeType: "", + label: "", + kind: "unknown", + }, + inspector: "hidden", + }); + + const [_selectedFileData, setSelectedFileData] = Retool.useStateObject({ + name: "selectedFileData", + initialValue: { + name: "", + type: "", + base64: "", + }, + inspector: "hidden", + }); + + const [_allFileData, setAllFileData] = Retool.useStateArray({ + name: "allFileData", + initialValue: [], + inspector: "hidden", + }); + + const [acceptedKind, setAcceptedKind] = Retool.useStateString({ + name: "acceptedKind", + initialValue: "", + inspector: "hidden", + }); + + const [allowMixedFileTypes] = Retool.useStateBoolean({ + name: "allowMixedFileTypes", + initialValue: false, + label: "Allow different file types", + inspector: "checkbox", + }); + + const [selectedFileId, setSelectedFileId] = useState(""); + const [dragOver, setDragOver] = useState(false); + const [textPreview, setTextPreview] = useState(""); + const [jsonPreview, setJsonPreview] = useState(""); + const [sheetPreview, setSheetPreview] = useState(null); + + const onFileSelect = Retool.useEventCallback({ name: "fileSelect" }); + const onFilesChange = Retool.useEventCallback({ name: "filesChange" }); + + const fileInputRef = useRef(null); + + const visibleFiles = useMemo( + () => (Array.isArray(files) ? (files as ViewerFile[]) : []), + [files] + ); + + const activeFile = useMemo( + () => visibleFiles.find((f) => f.id === selectedFileId) ?? null, + [visibleFiles, selectedFileId] + ); + + const showSidebar = visibleFiles.length > 0; + + const truncate = (value: string, max = 40) => + value.length > max ? `${value.slice(0, max)}…` : value; + + const detectKind = (file: File): FileKind => { + const type = (file.type || "").toLowerCase(); + const name = file.name.toLowerCase(); + + if (type.startsWith("image/")) return "image"; + if (type === "application/pdf" || name.endsWith(".pdf")) return "pdf"; + if (type.startsWith("video/")) return "video"; + if (type.startsWith("audio/")) return "audio"; + if (type.includes("json") || name.endsWith(".json")) return "json"; + if (type.includes("csv") || name.endsWith(".csv")) return "csv"; + + if ( + type.includes("spreadsheet") || + type.includes("excel") || + name.endsWith(".xlsx") || + name.endsWith(".xls") + ) { + return "excel"; + } + + if ( + type.includes("word") || + type.includes("presentation") || + name.endsWith(".doc") || + name.endsWith(".docx") || + name.endsWith(".ppt") || + name.endsWith(".pptx") + ) { + return "office"; + } + + if ( + type.startsWith("text/") || + name.endsWith(".txt") || + name.endsWith(".md") || + name.endsWith(".log") + ) { + return "text"; + } + + return "unknown"; + }; + + const normalizeLabel = (kind: FileKind) => { + switch (kind) { + case "image": + return "Image"; + case "pdf": + return "PDF"; + case "video": + return "Video"; + case "audio": + return "Audio"; + case "text": + return "Text"; + case "json": + return "JSON"; + case "csv": + return "CSV"; + case "excel": + return "Excel"; + case "office": + return "Office"; + default: + return "File"; + } + }; + + const fileToBase64 = (file: File): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = () => { + const result = typeof reader.result === "string" ? reader.result : ""; + const base64 = result.includes(",") ? result.split(",")[1] : result; + resolve(base64); + }; + + reader.onerror = () => reject(new Error(`Failed to read ${file.name}`)); + reader.readAsDataURL(file); + }); + + const buildAllFileData = (items: ViewerFile[]) => + items.map((file) => ({ + id: file.id, + name: file.name, + type: file.mimeType || "", + kind: file.kind, + base64: file.base64 || "", + label: file.label, + })); + + const decodeBase64Utf8 = (base64: string) => { + try { + const binary = atob(base64); + const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0)); + return new TextDecoder().decode(bytes); + } catch { + return ""; + } + }; + + const readPreviewFromSourceFile = async (file: File, kind: FileKind) => { + setTextPreview(""); + setJsonPreview(""); + setSheetPreview(null); + + if (kind === "text") { + const text = await file.text(); + setTextPreview(text); + return; + } + + if (kind === "json") { + const text = await file.text(); + try { + const parsed = JSON.parse(text); + setJsonPreview(JSON.stringify(parsed, null, 2)); + } catch { + setJsonPreview(text); + } + return; + } + + if (kind === "csv") { + const text = await file.text(); + const wb = XLSX.read(text, { type: "string" }); + const ws = wb.Sheets[wb.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }) as any[][]; + setSheetPreview(rows); + return; + } + + if (kind === "excel") { + const buffer = await file.arrayBuffer(); + const wb = XLSX.read(buffer, { type: "array" }); + const ws = wb.Sheets[wb.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }) as any[][]; + setSheetPreview(rows); + } + }; + + const hydratePreviewFromStoredFile = (file: ViewerFile) => { + setTextPreview(""); + setJsonPreview(""); + setSheetPreview(null); + + if (!file.base64) return; + + if (file.kind === "text") { + setTextPreview(decodeBase64Utf8(file.base64)); + return; + } + + if (file.kind === "json") { + const decoded = decodeBase64Utf8(file.base64); + try { + const parsed = JSON.parse(decoded); + setJsonPreview(JSON.stringify(parsed, null, 2)); + } catch { + setJsonPreview(decoded); + } + return; + } + + if (file.kind === "csv") { + try { + const decoded = decodeBase64Utf8(file.base64); + const wb = XLSX.read(decoded, { type: "string" }); + const ws = wb.Sheets[wb.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }) as any[][]; + setSheetPreview(rows); + } catch { + setSheetPreview(null); + } + return; + } + + if (file.kind === "excel") { + try { + const binary = atob(file.base64); + const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0)); + const wb = XLSX.read(bytes, { type: "array" }); + const ws = wb.Sheets[wb.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }) as any[][]; + setSheetPreview(rows); + } catch { + setSheetPreview(null); + } + } + }; + + const selectFile = async (file: ViewerFile) => { + setSelectedFileId(file.id); + + setSelectedFile({ + id: file.id, + name: file.name, + url: file.url, + previewUrl: file.previewUrl || "", + mimeType: file.mimeType || "", + label: file.label, + kind: file.kind, + }); + + setSelectedFileData({ + name: file.name, + type: file.mimeType || "", + base64: file.base64 || "", + }); + + hydratePreviewFromStoredFile(file); + onFileSelect(); + }; + + const handleFiles = async (fileList: FileList | null) => { + if (!fileList || fileList.length === 0) return; + + const incoming = Array.from(fileList); + const firstIncomingKind = detectKind(incoming[0]); + const sessionKind = acceptedKind || firstIncomingKind; + + const acceptedFiles = allowMixedFileTypes + ? incoming + : incoming.filter((file) => detectKind(file) === sessionKind); + + if (acceptedFiles.length === 0) return; + + if (!acceptedKind && !allowMixedFileTypes) { + setAcceptedKind(sessionKind); + } + + const mapped: ViewerFile[] = await Promise.all( + acceptedFiles.map(async (file) => { + const kind = detectKind(file); + const objectUrl = URL.createObjectURL(file); + const base64 = await fileToBase64(file); + + return { + id: `${file.name}-${file.size}-${file.lastModified}-${Math.random() + .toString(36) + .slice(2)}`, + name: file.name, + url: objectUrl, + previewUrl: objectUrl, + mimeType: file.type, + label: normalizeLabel(kind), + kind, + objectUrl, + base64, + }; + }) + ); + + const nextFiles = [...visibleFiles, ...mapped]; + setFiles(nextFiles); + setAllFileData(buildAllFileData(nextFiles)); + + const first = mapped[0]; + const sourceFile = acceptedFiles[0]; + + if (first) { + setSelectedFileId(first.id); + + setSelectedFile({ + id: first.id, + name: first.name, + url: first.url, + previewUrl: first.previewUrl || "", + mimeType: first.mimeType || "", + label: first.label, + kind: first.kind, + }); + + setSelectedFileData({ + name: first.name, + type: first.mimeType || "", + base64: first.base64 || "", + }); + + if (sourceFile) { + await readPreviewFromSourceFile(sourceFile, first.kind); + } + + onFileSelect(); + } + + onFilesChange(); + }; + + const removeLocalFile = (id: string) => { + const target = visibleFiles.find((f) => f.id === id); + if (target?.objectUrl) { + URL.revokeObjectURL(target.objectUrl); + } + + const nextFiles = visibleFiles.filter((f) => f.id !== id); + setFiles(nextFiles); + setAllFileData(buildAllFileData(nextFiles)); + + if (selectedFileId === id) { + const next = nextFiles[0]; + + if (next) { + setSelectedFileId(next.id); + setSelectedFile({ + id: next.id, + name: next.name, + url: next.url, + previewUrl: next.previewUrl || "", + mimeType: next.mimeType || "", + label: next.label, + kind: next.kind, + }); + setSelectedFileData({ + name: next.name, + type: next.mimeType || "", + base64: next.base64 || "", + }); + hydratePreviewFromStoredFile(next); + } else { + setSelectedFileId(""); + setSelectedFile({ + id: "", + name: "", + url: "", + previewUrl: "", + mimeType: "", + label: "", + kind: "unknown", + }); + setSelectedFileData({ + name: "", + type: "", + base64: "", + }); + setAllFileData([]); + setAcceptedKind(""); + setTextPreview(""); + setJsonPreview(""); + setSheetPreview(null); + } + } + + onFilesChange(); + }; + + const clearAll = () => { + visibleFiles.forEach((file) => { + if (file.objectUrl) URL.revokeObjectURL(file.objectUrl); + }); + + setFiles([]); + setSelectedFileId(""); + setAcceptedKind(""); + setSelectedFile({ + id: "", + name: "", + url: "", + previewUrl: "", + mimeType: "", + label: "", + kind: "unknown", + }); + setSelectedFileData({ + name: "", + type: "", + base64: "", + }); + setAllFileData([]); + setTextPreview(""); + setJsonPreview(""); + setSheetPreview(null); + onFilesChange(); + }; + + const renderSheet = (rows: any[][]) => { + if (!rows.length) { + return ; + } + + return ( +

+ + + {rows.map((row, rIdx) => ( + + {row.map((cell, cIdx) => + rIdx === 0 ? ( + + ) : ( + + ) + )} + + ))} + +
{String(cell ?? "")}{String(cell ?? "")}
+
+ ); + }; + + const renderPreview = () => { + if (!activeFile) { + return ( + + ); + } + + const previewUrl = activeFile.previewUrl || activeFile.url; + const kind = activeFile.kind; + + if (kind === "image") { + return ( +
+ {activeFile.name} +
+ ); + } + + if (kind === "pdf") { + return ( +
+