From ca4f3ea1d3113b6694e985ce606575ef32b698f1 Mon Sep 17 00:00:00 2001 From: Widle Date: Mon, 6 Apr 2026 17:37:45 +0530 Subject: [PATCH] File viewer component added successfully. --- components/.DS_Store | Bin 8196 -> 8196 bytes components/global-file-viewer/README.md | 86 +++ components/global-file-viewer/cover.png | Bin 0 -> 80526 bytes components/global-file-viewer/metadata.json | 7 + components/global-file-viewer/package.json | 53 ++ .../src/components/fileViewerComponent.tsx | 592 ++++++++++++++++++ components/global-file-viewer/src/index.tsx | 1 + 7 files changed, 739 insertions(+) create mode 100644 components/global-file-viewer/README.md create mode 100644 components/global-file-viewer/cover.png create mode 100644 components/global-file-viewer/metadata.json create mode 100644 components/global-file-viewer/package.json create mode 100644 components/global-file-viewer/src/components/fileViewerComponent.tsx create mode 100644 components/global-file-viewer/src/index.tsx diff --git a/components/.DS_Store b/components/.DS_Store index 81e44163f7fdbef5f62f0a3bffcbe8a4e051eac1..a9e49eba82f2757158f144db357995a4e26f5ee2 100644 GIT binary patch delta 352 zcmZp1XmOa}&nUbxU^hRb@MInVS3^Dq7Y1VnT?RLXe1;;1WQJ5A+a1VCWGGtQTTPXUG9+NdhX&0cuHO$OO_L&1FDVDnmIC7olnI z25J8f20#`A15suql@}L4ov>-edtGjm>`qMOikp LOMC+o!tBfdhL2Iy delta 51 zcmZp1XmOa}&&aniU^hP_-((&E*Uh~GeTUGrPn$7S{hDu(6Pd F834%z6XgH^ diff --git a/components/global-file-viewer/README.md b/components/global-file-viewer/README.md new file mode 100644 index 0000000..a3372c6 --- /dev/null +++ b/components/global-file-viewer/README.md @@ -0,0 +1,86 @@ +## Username + +widlestudiollp + +## Project Name + +Smart File Viewer + +## About + +Smart File Viewer is an intelligent, auto-detecting file viewer component for Retool that supports multiple file formats and input sources. It automatically detects the file type and renders it using the appropriate viewer without requiring manual configuration. + +The component supports files from uploads, URLs (including Google Drive/Docs), and Base64 strings. It provides a seamless experience for previewing documents, media, and structured data directly inside Retool apps. + +## Preview + +![Smart File Viewer Preview](cover.png) + +## How it works + +The component receives input via a text field (URL/Base64) or file upload. It then processes the input using a detection engine that determines the file type based on MIME type, file extension, or Base64 signature. + +### File detection logic + +* Base64 prefix → Determines MIME type (PDF, Image, etc.) +* File extension → Fallback detection (`.docx`, `.xlsx`, `.csv`, etc.) +* Google URLs → Converted into embeddable preview links +* Blob conversion → Ensures consistent rendering across formats + +### Rendering logic + +* PDF → Rendered using `react-pdf` with pagination +* Word → Converted to HTML using `mammoth` +* Excel → Parsed into table using `xlsx` +* CSV → Custom parser → table view +* JSON → Structured table +* Text → Code-style preview +* Image → Responsive image preview +* Video → Native HTML5 player +* Google Docs/Drive → Embedded iframe preview + +### Example input + +```json +[ + { "name": "Smith", "role": "Developer" }, + { "name": "John", "role": "Designer" } +] +``` + +Or Base64: + +``` +data:application/pdf;base64,JVBERi0xLjQKJ... +``` + +Or URL: + +``` +https://drive.google.com/file/d/FILE_ID/view +``` + +## Build process + +The component is built using React and integrates with Retool through `@tryretool/custom-component-support`. It uses multiple libraries for handling different file types and rendering them efficiently. + +### Key implementation details + +* Uses `useMemo` for optimized file type detection +* Uses `useEffect` for async file loading and parsing +* Converts files to Blob URLs for consistent rendering +* Handles Google Drive/Docs links via URL transformation +* Implements custom parsers for CSV and JSON +* Supports dynamic container resizing using `ResizeObserver` +* Includes proper cleanup for Blob URLs to prevent memory leaks + +### Extensibility + +The component is designed to be flexible and extensible. Developers can: + +* Add support for additional file types (e.g., audio, markdown) +* Replace Word rendering engine for better fidelity +* Add drag-and-drop upload support +* Extend parsing logic for large datasets +* Customize UI themes and controls +* Add caching or file size optimizations diff --git a/components/global-file-viewer/cover.png b/components/global-file-viewer/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..71b16a401aa8913b6da6559fd30d66ae284a67e8 GIT binary patch literal 80526 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 literal 0 HcmV?d00001 diff --git a/components/global-file-viewer/metadata.json b/components/global-file-viewer/metadata.json new file mode 100644 index 0000000..adba99a --- /dev/null +++ b/components/global-file-viewer/metadata.json @@ -0,0 +1,7 @@ +{ + "id": "smart-file-viewer", + "title": "Smart File Viewer", + "author": "@widlestudiollp", + "shortDescription": "An intelligent auto-detect file viewer component that supports multiple file formats (PDF, Word, Excel, CSV, JSON, media) from uploads, URLs, and Base64, rendering them with the appropriate viewer automatically.", + "tags": ["File Viewer", "Document Viewer", "Auto Detection", "PDF", "Media", "Data Preview"] +} \ No newline at end of file diff --git a/components/global-file-viewer/package.json b/components/global-file-viewer/package.json new file mode 100644 index 0000000..c9d0c06 --- /dev/null +++ b/components/global-file-viewer/package.json @@ -0,0 +1,53 @@ +{ + "name": "my-react-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@tryretool/custom-component-support": "latest", + "dompurify": "^3.3.3", + "mammoth": "^1.12.0", + "papaparse": "^5.5.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-pdf": "^10.4.1", + "xlsx": "^0.18.5" + }, + "engines": { + "node": ">=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/papaparse": "^5.5.2", + "@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": "FileViewerComponent", + "label": "File viewer component", + "description": "A file viewer component.", + "entryPoint": "src/index.tsx", + "outputPath": "dist" + } +} diff --git a/components/global-file-viewer/src/components/fileViewerComponent.tsx b/components/global-file-viewer/src/components/fileViewerComponent.tsx new file mode 100644 index 0000000..f3c4082 --- /dev/null +++ b/components/global-file-viewer/src/components/fileViewerComponent.tsx @@ -0,0 +1,592 @@ +import React, { FC, useState, useMemo, useEffect, useCallback, useRef } from "react"; +import { Retool } from "@tryretool/custom-component-support"; +import { Document, Page, pdfjs } from "react-pdf"; +import * as XLSX from "xlsx"; + +import "react-pdf/dist/Page/AnnotationLayer.css"; +import "react-pdf/dist/Page/TextLayer.css"; + +pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`; + +type FileType = "image" | "video" | "pdf" | "word" | "excel" | "csv" | "json" | "text" | "other"; + +function transformGoogleUrl(url: string): string | null { + try { + const u = new URL(url); + const host = u.hostname; + + const driveFile = url.match(/drive\.google\.com\/file\/d\/([^/?&#]+)/); + if (driveFile) return `https://drive.google.com/file/d/${driveFile[1]}/preview`; + + const driveOpen = url.match(/drive\.google\.com\/open\?id=([^&#]+)/); + if (driveOpen) return `https://drive.google.com/file/d/${driveOpen[1]}/preview`; + + const docsDoc = url.match(/docs\.google\.com\/document\/d\/([^/?&#]+)/); + if (docsDoc) return `https://docs.google.com/document/d/${docsDoc[1]}/preview`; + + const docsSheet = url.match(/docs\.google\.com\/spreadsheets\/d\/([^/?&#]+)/); + if (docsSheet) return `https://docs.google.com/spreadsheets/d/${docsSheet[1]}/preview`; + + const docsSlides = url.match(/docs\.google\.com\/presentation\/d\/([^/?&#]+)/); + if (docsSlides) return `https://docs.google.com/presentation/d/${docsSlides[1]}/embed`; + + const docsForms = url.match(/docs\.google\.com\/forms\/d\/([^/?&#]+)/); + if (docsForms) return `https://docs.google.com/forms/d/${docsForms[1]}/viewform?embedded=true`; + + return null; + } catch { + return null; + } +} + +function isGoogleUrl(url: string): boolean { + return url.includes("drive.google.com") || url.includes("docs.google.com"); +} + +function detectFileType(input: string): FileType { + if (!input) return "other"; + const str = input.toLowerCase(); + if (str.startsWith("data:")) { + if (str.includes("image/")) return "image"; + if (str.includes("video/")) return "video"; + if (str.includes("pdf")) return "pdf"; + if ( + str.includes("word") || + str.includes("officedocument.wordprocessingml") || + str.includes("application/vnd.openxmlformats-officedocument.wordprocessingml.document") + ) return "word"; + if (str.includes("excel") || str.includes("spreadsheet")) return "excel"; + if (str.includes("text/csv")) return "csv"; + if (str.includes("application/json")) return "json"; + if (str.includes("text/plain")) return "text"; + return "other"; + } + if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)(\?|$)/i.test(str)) return "image"; + if (/\.(mp4|webm|mov|avi|mkv|m4v|ogg)(\?|$)/i.test(str)) return "video"; + if (/\.pdf(\?|$)/i.test(str)) return "pdf"; + if (/\.(doc|docx)(\?|$)/i.test(str)) return "word"; + if (/\.(ppt|pptx)(\?|$)/i.test(str)) return "word"; + if (/\.(xls|xlsx)(\?|$)/i.test(str)) return "excel"; + if (/\.csv(\?|$)/i.test(str)) return "csv"; + if (/\.json(\?|$)/i.test(str)) return "json"; + if (/\.(txt|log|md)(\?|$)/i.test(str)) return "text"; + if (/\.(mp3|wav|ogg|aac|flac|m4a)(\?|$)/i.test(str)) return "other"; + return "other"; +} + +function ensureBase64Prefix(b64: string, filenameHint?: string): string { + if (b64.startsWith("data:")) return b64; + + const c = b64.replace(/\s/g, ""); + + if (c.startsWith("JVBER")) return `data:application/pdf;base64,${c}`; + if (c.startsWith("iVBOR")) return `data:image/png;base64,${c}`; + if (c.startsWith("/9j/")) return `data:image/jpeg;base64,${c}`; + if (c.startsWith("R0lGOD")) return `data:image/gif;base64,${c}`; + + if (c.startsWith("UEsDB")) { + if (filenameHint?.toLowerCase().endsWith(".docx")) { + return `data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${c}`; + } + if (filenameHint?.toLowerCase().endsWith(".xlsx")) { + return `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${c}`; + } + if (filenameHint?.toLowerCase().endsWith(".pptx")) { + return `data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,${c}`; + } + + return `data:application/zip;base64,${c}`; + } + + return `data:application/octet-stream;base64,${c}`; +} + +async function toBlobUrl(file: string, type: FileType): Promise { + if (file.startsWith("data:")) { + if (type === "video" || type === "image") return file; + try { + const res = await fetch(file); + const blob = await res.blob(); + return URL.createObjectURL(blob); + } catch { return file; } + } + if (file.includes("google.com") && (file.includes("/preview") || file.includes("/embed") || file.includes("/viewform"))) { + return file; + } + try { + const res = await fetch(file); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const blob = await res.blob(); + return URL.createObjectURL(blob); + } catch (e) { + throw new Error(`Failed to fetch file: ${e}`); + } +} + +function parseCSV(text: string): Record[] { + const lines = text.split(/\r?\n/).filter((l) => l.trim()); + if (!lines.length) return []; + const parseRow = (row: string): string[] => { + const result: string[] = []; let cur = ""; let inQ = false; + for (let i = 0; i < row.length; i++) { + const ch = row[i]; + if (ch === '"') { if (inQ && row[i + 1] === '"') { cur += '"'; i++; } else inQ = !inQ; } + else if (ch === "," && !inQ) { result.push(cur.trim()); cur = ""; } + else cur += ch; + } + result.push(cur.trim()); return result; + }; + const headers = parseRow(lines[0]); + return lines.slice(1).map((line) => { + const vals = parseRow(line); + return headers.reduce((acc: Record, h, i) => { acc[h] = vals[i] ?? ""; return acc; }, {}); + }); +} + +async function parseFile(file: string, type: FileType): Promise[] | string | null> { + try { + const res = await fetch(file); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const buffer = await res.arrayBuffer(); + if (type === "csv") return parseCSV(new TextDecoder().decode(buffer)); + if (type === "excel") { + const wb = XLSX.read(buffer, { type: "array" }); + return XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); + } + if (type === "json") { + const parsed = JSON.parse(new TextDecoder().decode(buffer)); + return Array.isArray(parsed) ? parsed : [parsed]; + } + if (type === "text") return new TextDecoder().decode(buffer); + if (type === "word") { + const mammoth = await import("mammoth"); + return (await mammoth.convertToHtml({ arrayBuffer: buffer })).value; + } + } catch (e) { console.error("Parse error:", e); throw e; } + return null; +} + +export const FileViewer: FC = () => { + Retool.useComponentSettings({ defaultWidth: 6, defaultHeight: 40 }); + const [inputValue, setInputValue] = useState(""); + const [uploadedDataUrl, setUploadedDataUrl] = useState(""); + const [loadedInputValue, setLoadedInputValue] = useState(""); + const [filenameHint, setFilenameHint] = useState(""); + const [file, setFile] = useState(""); + const [embedUrl, setEmbedUrl] = useState(""); + const [blobUrl, setBlobUrl] = useState(""); + const [data, setData] = useState[] | string | null>(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [page, setPage] = useState(1); + const [pages, setPages] = useState(0); + const [containerWidth, setContainerWidth] = useState(800); + const containerRef = useRef(null); + + const type = useMemo(() => { + const fromDataUrl = detectFileType(file); + if (fromDataUrl !== "other") return fromDataUrl; + if (filenameHint) return detectFileType(filenameHint); + return "other"; + }, [file, filenameHint]); + + const isUploaded = uploadedDataUrl !== "" && file === uploadedDataUrl; + const isViewLoaded = loadedInputValue !== "" && loadedInputValue === inputValue.trim() && !isUploaded; + const isLoaded = isUploaded || isViewLoaded; + const canClickView = inputValue.trim() !== "" && !isLoaded; + + useEffect(() => { + if (!containerRef.current) return; + const ro = new ResizeObserver((entries) => { + const w = entries[0]?.contentRect.width; + if (w) setContainerWidth(w - 48); + }); + ro.observe(containerRef.current); + return () => ro.disconnect(); + }, []); + + useEffect(() => { + return () => { if (blobUrl?.startsWith("blob:")) URL.revokeObjectURL(blobUrl); }; + }, [blobUrl]); + + useEffect(() => { + if (!file) { setBlobUrl(""); setData(null); setEmbedUrl(""); return; } + + let cancelled = false; + let currentBlobUrl = ""; + + const load = async () => { + setLoading(true); setError(""); setPage(1); setPages(0); setData(null); setEmbedUrl(""); + + try { + if (file.startsWith("http") && isGoogleUrl(file)) { + const preview = transformGoogleUrl(file); + if (preview) { + if (!cancelled) { setEmbedUrl(preview); setBlobUrl(preview); } + return; + } + if (!cancelled) { setEmbedUrl(file); setBlobUrl(file); } + return; + } + + const url = await toBlobUrl(file, type); + if (cancelled) { if (url.startsWith("blob:")) URL.revokeObjectURL(url); return; } + currentBlobUrl = url; + setBlobUrl(url); + + if (["csv", "excel", "json", "text", "word"].includes(type)) { + const parsed = await parseFile(url, type); + if (!cancelled) setData(parsed); + } + } catch (e) { + if (!cancelled) setError(e instanceof Error ? e.message : "Failed to load file"); + } finally { + if (!cancelled) setLoading(false); + } + }; + + load(); + return () => { + cancelled = true; + if (currentBlobUrl?.startsWith("blob:")) URL.revokeObjectURL(currentBlobUrl); + }; + }, [file, type]); + + const handleView = useCallback(() => { + let value = inputValue.trim(); + if (!value) return; + if (!value.startsWith("http") && !value.startsWith("data:")) { + value = ensureBase64Prefix(value, filenameHint); + } + setUploadedDataUrl(""); + setLoadedInputValue(inputValue.trim()); + setFile(value); + }, [inputValue]); + + const handleUpload = useCallback((f: File) => { + const reader = new FileReader(); + reader.onload = () => { + if (typeof reader.result === "string") { + const dataUrl = reader.result; + setInputValue(f.name); + setFilenameHint(f.name); + setUploadedDataUrl(dataUrl); + setFile(dataUrl); + } + }; + reader.onerror = () => setError("Failed to read file"); + reader.readAsDataURL(f); + }, []); + + const handleDownload = useCallback(() => { + if (embedUrl) { window.open(file, "_blank"); return; } + const url = blobUrl || file; + if (!url) return; + const a = document.createElement("a"); + a.href = url; a.download = inputValue || "download"; + document.body.appendChild(a); a.click(); document.body.removeChild(a); + }, [blobUrl, file, inputValue, embedUrl]); + + const handlePrint = useCallback(() => { + if (type === "word" && typeof data === "string") { + const win = window.open("", "_blank"); + if (win) { win.document.write(`${data}`); win.document.close(); win.print(); } + return; + } + const url = blobUrl || file; + if (!url) return; + const win = window.open(url, "_blank"); + if (win) win.onload = () => win.print(); + }, [blobUrl, file, type, data]); + + const handleClear = useCallback(() => { + if (blobUrl?.startsWith("blob:")) URL.revokeObjectURL(blobUrl); + setFile(""); setInputValue(""); setUploadedDataUrl(""); setLoadedInputValue(""); setFilenameHint(""); + setBlobUrl(""); setData(null); setError(""); + setPage(1); setPages(0); setEmbedUrl(""); + }, [blobUrl]); + + const C = { + primary: "#4F46E5", surface: "#FFFFFF", bg: "#F8FAFC", + border: "#E2E8F0", muted: "#64748B", danger: "#EF4444", + dangerBg: "#FEF2F2", text: "#1E293B", tableHead: "#F1F5F9", + }; + + const typeTag: Record = { + pdf: { bg: "#FEE2E2", color: "#EF4444" }, + image: { bg: "#EDE9FE", color: "#7C3AED" }, + video: { bg: "#DBEAFE", color: "#2563EB" }, + word: { bg: "#DBEAFE", color: "#1D4ED8" }, + excel: { bg: "#DCFCE7", color: "#16A34A" }, + csv: { bg: "#E0F2FE", color: "#0284C7" }, + json: { bg: "#FEF3C7", color: "#D97706" }, + text: { bg: "#F1F5F9", color: "#475569" }, + other: { bg: "#F1F5F9", color: "#475569" }, + }; + const tag = typeTag[type] ?? typeTag.other; + + const btnBase: React.CSSProperties = { + padding: "6px 12px", borderRadius: 7, fontSize: 12, fontWeight: 500, + cursor: "pointer", border: `1px solid ${C.border}`, background: C.surface, color: C.text, + }; + const thSt: React.CSSProperties = { + padding: "10px 14px", borderBottom: `1px solid ${C.border}`, background: C.tableHead, + textAlign: "left", position: "sticky", top: 0, fontSize: 11, fontWeight: 700, + color: C.muted, textTransform: "uppercase", letterSpacing: "0.05em", + }; + const tdSt: React.CSSProperties = { padding: "10px 14px", borderBottom: `1px solid #F1F5F9`, fontSize: 13 }; + + const renderContent = () => { + if (!file) return ( +
+
📂
+
No file loaded
+
+ Upload a file or paste a URL / Base64 string above +
+
+ ); + + if (loading) return ( +
+
+ +
Loading file…
+
+ ); + + if (error) return ( +
+
⚠️
+
Failed to load file
+
{error}
+
+ ); + + if (embedUrl) return ( +