From d6cab4aeb5c81379ae7e82214c4f383f9d4b70e4 Mon Sep 17 00:00:00 2001 From: Kenearos <86194771+Kenearos@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:29:07 +0100 Subject: [PATCH] feat: Improve file preview with Blob URLs Utilize `URL.createObjectURL` for richer file previews, especially for PDFs. This approach provides better compatibility and performance compared to Base64 previews. Introduces memory leak prevention by revoking Blob URLs when the component unmounts or the app resets. --- App.tsx | 10 ++++- components/FileUpload.tsx | 11 ++++-- components/ReviewPanel.tsx | 80 +++++++++++++++++++++++++------------- 3 files changed, 70 insertions(+), 31 deletions(-) diff --git a/App.tsx b/App.tsx index 8290833..a1cef50 100644 --- a/App.tsx +++ b/App.tsx @@ -44,6 +44,14 @@ const App: React.FC = () => { }; const reset = () => { + // Cleanup blob URLs to prevent memory leaks + if (formFile?.previewUrl && formFile.previewUrl.startsWith('blob:')) { + URL.revokeObjectURL(formFile.previewUrl); + } + if (sourceFile?.previewUrl && sourceFile.previewUrl.startsWith('blob:')) { + URL.revokeObjectURL(sourceFile.previewUrl); + } + setStatus(AppStatus.IDLE); setFormFile(null); setSourceFile(null); @@ -223,4 +231,4 @@ const App: React.FC = () => { ); }; -export default App; +export default App; \ No newline at end of file diff --git a/components/FileUpload.tsx b/components/FileUpload.tsx index b77d2de..1ba5c06 100644 --- a/components/FileUpload.tsx +++ b/components/FileUpload.tsx @@ -21,6 +21,9 @@ export const FileUpload: React.FC = ({ const [isDragging, setIsDragging] = useState(false); const processFile = (file: File) => { + // Create a robust Blob URL for previewing (works better than Base64 for PDFs) + const objectUrl = URL.createObjectURL(file); + const reader = new FileReader(); reader.onload = () => { const base64String = reader.result as string; @@ -29,7 +32,7 @@ export const FileUpload: React.FC = ({ onFileSelect({ file, - previewUrl: file.type.startsWith('image/') ? base64String : null, + previewUrl: objectUrl, base64: base64Content, type: file.type as any }); @@ -92,10 +95,10 @@ export const FileUpload: React.FC = ({ ) : (
- {selectedFile.previewUrl ? ( - Preview - ) : ( + {selectedFile.type === 'application/pdf' ? ( + ) : ( + Preview )}
diff --git a/components/ReviewPanel.tsx b/components/ReviewPanel.tsx index 4636b1a..2e6ca57 100644 --- a/components/ReviewPanel.tsx +++ b/components/ReviewPanel.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useMemo, useRef } from 'react'; import { ExtractedField, FileData } from '../types'; -import { Check, Edit2, Download, RefreshCw, FileText, AlertTriangle, XCircle, ArrowRight, PenTool, CheckCircle2, Circle, LayoutTemplate, List, Move } from 'lucide-react'; +import { Check, Edit2, Download, RefreshCw, FileText, AlertTriangle, XCircle, ArrowRight, PenTool, CheckCircle2, Circle, LayoutTemplate, List, Move, ExternalLink } from 'lucide-react'; import { createFilledPdf } from '../services/pdfService'; import { jsPDF } from "jspdf"; @@ -43,9 +43,10 @@ export const ReviewPanel: React.FC = ({ // Generate preview for PDF download useEffect(() => { let active = true; - let timeoutId: ReturnType; + let timeoutId: any; const generatePreview = async () => { + // If it's a PDF, we try to show the filled version if (formFile.type === 'application/pdf') { try { const filledPdfBytes = await createFilledPdf(formFile.base64, fields, isFillablePdf); @@ -60,14 +61,17 @@ export const ReviewPanel: React.FC = ({ }); } catch (e) { console.error("Failed to generate PDF preview", e); + // Fallback to original if generation fails + setPreviewUrl(formFile.previewUrl); } } else { + // For images, just show the original setPreviewUrl(formFile.previewUrl); } }; - // Debounce to avoid excessive PDF generation - timeoutId = setTimeout(generatePreview, 600); + // Debounce to avoid excessive PDF generation while typing + timeoutId = setTimeout(generatePreview, 1000); return () => { active = false; @@ -79,7 +83,9 @@ export const ReviewPanel: React.FC = ({ useEffect(() => { return () => { setPreviewUrl(prev => { - if (prev && prev.startsWith('blob:')) URL.revokeObjectURL(prev); + if (prev && prev.startsWith('blob:') && prev !== formFile.previewUrl) { + URL.revokeObjectURL(prev); + } return null; }); }; @@ -261,24 +267,32 @@ export const ReviewPanel: React.FC = ({
- PDF Preview + PDF Preview (Live)
{formFile.file.name}
{previewUrl ? ( formFile.type === 'application/pdf' ? ( - -
-

Unable to display PDF directly.

- Download to view + <> +