import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Upload, FileText, Trash2, RotateCw, Download, Scissors, Layers, PenTool, Lock, FileSearch, Minimize2, Move, Plus, X, ChevronRight, Save, CheckCircle, AlertCircle, Loader2 } from 'lucide-react'; import { useDropzone } from 'react-dropzone'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragOverlay } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, rectSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; // --- Dynamic Script Loader --- // Since we cannot use standard imports for these heavy libraries in this environment, // we load them via CDN and access them via window object. const useLibraryLoader = () => { const [loaded, setLoaded] = useState(false); useEffect(() => { const loadScript = (src) => { return new Promise((resolve, reject) => { if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; } const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); }; Promise.all([ loadScript('https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js'), loadScript('https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'), loadScript('https://unpkg.com/tesseract.js@4.1.1/dist/tesseract.min.js') ]).then(() => { // Configure PDF.js Worker if (window.pdfjsLib) { window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; } setLoaded(true); }).catch(err => { console.error("Failed to load libraries", err); }); }, []); return loaded; }; // --- Utility Functions --- const formatBytes = (bytes, decimals = 2) => { if (!+bytes) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; }; // --- Components --- // 1. Sortable Page Card const SortablePage = ({ id, page, index, onRotate, onDelete, onSelect, isSelected, activeTool }) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }); const style = { transform: CSS.Transform.toString(transform), transition, zIndex: isDragging ? 50 : 'auto', opacity: isDragging ? 0.5 : 1, }; return (
{/* Visual Header */}
Page {index + 1} {activeTool === 'split' && ( onSelect(id)} className="w-4 h-4 rounded border-slate-600 text-blue-500 focus:ring-blue-500 bg-slate-700" /> )}
{/* Thumbnail Container */}
{page.thumbnail ? ( {`Page ) : (
Rendering...
)} {/* Overlay Actions (Only show if not dragging) */} {!isDragging && activeTool === 'organize' && (
)} {/* OCR Overlay */} {!isDragging && activeTool === 'ocr' && (
)}
); }; // 2. Signing Modal const SigningModal = ({ isOpen, onClose, onSave }) => { const canvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); useEffect(() => { if(isOpen && canvasRef.current) { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.strokeStyle = '#000000'; ctx.clearRect(0, 0, canvas.width, canvas.height); } }, [isOpen]); const startDraw = (e) => { const ctx = canvasRef.current.getContext('2d'); const rect = canvasRef.current.getBoundingClientRect(); ctx.beginPath(); ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top); setIsDrawing(true); }; const draw = (e) => { if (!isDrawing) return; const ctx = canvasRef.current.getContext('2d'); const rect = canvasRef.current.getBoundingClientRect(); ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top); ctx.stroke(); }; const stopDraw = () => setIsDrawing(false); const handleSave = () => { const dataUrl = canvasRef.current.toDataURL('image/png'); onSave(dataUrl); onClose(); }; if (!isOpen) return null; return (

Create Signature

); }; // 3. Main Application Component export default function AllSilvaPDF() { const libsLoaded = useLibraryLoader(); // State const [pages, setPages] = useState([]); // { id, originalIndex, rotation, thumbnail, fileId, pdfDoc } const [pdfDocs, setPdfDocs] = useState({}); // Map fileId -> PDFDocument const [activeTool, setActiveTool] = useState('organize'); // organize, merge, split, compress, sign, ocr, protect const [isLoading, setIsLoading] = useState(false); const [loadingText, setLoadingText] = useState(''); const [selectedPageIds, setSelectedPageIds] = useState(new Set()); const [isSignModalOpen, setIsSignModalOpen] = useState(false); const [signatures, setSignatures] = useState([]); // Array of dataURLs const [ocrResult, setOcrResult] = useState(null); // DnD Sensors const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) ); // --- Handlers --- const processFile = async (file) => { if (!window.PDFLib || !window.pdfjsLib) { alert("Libraries are still initializing. Please wait a moment."); return; } setIsLoading(true); setLoadingText('Parsing PDF...'); try { const arrayBuffer = await file.arrayBuffer(); // Use window.PDFLib const pdfDoc = await window.PDFLib.PDFDocument.load(arrayBuffer); const fileId = Math.random().toString(36).substr(2, 9); // Store the parsed PDFLib document setPdfDocs(prev => ({ ...prev, [fileId]: pdfDoc })); // Render Thumbnails using PDF.js via window const pdfData = new Uint8Array(arrayBuffer); const loadingTask = window.pdfjsLib.getDocument({ data: pdfData }); const pdf = await loadingTask.promise; const newPages = []; const totalPages = pdf.numPages; setLoadingText('Generating Thumbnails...'); for (let i = 1; i <= totalPages; i++) { const page = await pdf.getPage(i); const viewport = page.getViewport({ scale: 0.5 }); // Low scale for thumbnail const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: context, viewport: viewport }).promise; newPages.push({ id: `${fileId}-${i}`, fileId, originalIndex: i - 1, // 0-based for pdf-lib rotation: 0, thumbnail: canvas.toDataURL(), width: viewport.width, height: viewport.height }); } setPages(prev => [...prev, ...newPages]); } catch (err) { console.error(err); alert("Error loading PDF. It might be password protected or corrupted."); } finally { setIsLoading(false); setLoadingText(''); } }; const onDrop = useCallback(acceptedFiles => { acceptedFiles.forEach(processFile); }, [libsLoaded]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: {'application/pdf': ['.pdf']}, disabled: !libsLoaded }); // DnD Handlers const handleDragEnd = (event) => { const { active, over } = event; if (active.id !== over.id) { setPages((items) => { const oldIndex = items.findIndex(item => item.id === active.id); const newIndex = items.findIndex(item => item.id === over.id); return arrayMove(items, oldIndex, newIndex); }); } }; // Tool Actions const handleRotate = (id) => { setPages(pages.map(p => p.id === id ? { ...p, rotation: (p.rotation + 90) % 360 } : p )); }; const handleDelete = (id) => { setPages(pages.filter(p => p.id !== id)); setSelectedPageIds(prev => { const newSet = new Set(prev); newSet.delete(id); return newSet; }); }; const handleSelect = (id) => { if (activeTool === 'ocr') { performOCR(id); return; } setSelectedPageIds(prev => { const newSet = new Set(prev); if (newSet.has(id)) newSet.delete(id); else newSet.add(id); return newSet; }); }; const handleAddSignature = (signatureDataUrl) => { setSignatures(prev => [...prev, signatureDataUrl]); alert("Signature added to clipboard. In a full version, you would drag this onto the page."); // For this demo, we will automatically apply the signature to the bottom right of ALL selected pages (or first page if none) on export. }; const performOCR = async (id) => { if (!window.Tesseract) return; const page = pages.find(p => p.id === id); if(!page) return; setIsLoading(true); setLoadingText('Scanning text with Tesseract...'); try { const { data: { text } } = await window.Tesseract.recognize(page.thumbnail, 'eng'); setOcrResult(text); } catch (e) { console.error(e); alert("OCR Failed"); } finally { setIsLoading(false); } }; // --- The Big One: EXPORT --- const handleExport = async () => { if (pages.length === 0 || !window.PDFLib) return; setIsLoading(true); setLoadingText('Processing PDF...'); try { // 1. Create new Doc const newPdf = await window.PDFLib.PDFDocument.create(); // 2. Iterate current pages state for (const p of pages) { const sourceDoc = pdfDocs[p.fileId]; const [copiedPage] = await newPdf.copyPages(sourceDoc, [p.originalIndex]); // Apply rotation const currentRotation = copiedPage.getRotation(); copiedPage.setRotation(window.PDFLib.degrees(currentRotation.angle + p.rotation)); // Apply Signature (If any exist, apply to all for MVP simplicity) if (signatures.length > 0) { const sigImage = await newPdf.embedPng(signatures[0]); const { width } = copiedPage.getSize(); copiedPage.drawImage(sigImage, { x: width - 150, y: 50, width: 100, height: 50, }); } newPdf.addPage(copiedPage); } // 3. Tool Specific Logic if (activeTool === 'compress') { setLoadingText('Compressing...'); // Simulating compression by saving without formatting // Real compression requires canvas re-rendering which is too heavy for this snippet } if (activeTool === 'protect') { newPdf.encrypt({ userPassword: '', ownerPassword: 'admin', permissions: { printing: 'highResolution', modifying: false, copying: false } }); } // 4. Save const pdfBytes = await newPdf.save(); // 5. Download const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `allsilva_export_${Date.now()}.pdf`; link.click(); } catch (err) { console.error(err); alert("Export failed. See console."); } finally { setIsLoading(false); } }; // --- Render Helpers --- const renderToolButton = (id, icon, label, description) => ( ); // Initial Loader Check if (!libsLoaded) { return (

Initializing AllSilva Engine...

Loading PDF libraries

); } return (
{/* --- Sidebar --- */} {/* --- Main Content --- */}
{/* Header / Topbar */}

{activeTool} Mode {pages.length} Pages

{activeTool === 'sign' && signatures.length === 0 && ( )} {activeTool === 'sign' && signatures.length > 0 && ( Signature Ready )}
{/* Workspace */}
{/* Empty State */} {pages.length === 0 && (

Drop PDF here

Drag and drop your document to begin organizing, merging, or editing with AllSilva tools.

)} {/* Grid */} {pages.length > 0 && ( p.id)} strategy={rectSortingStrategy}>
{pages.map((page, idx) => ( ))}
{/* Drag Overlay would go here for smoother animations, omitted for brevity in single file */}
)} {/* OCR Result Overlay */} {ocrResult && (

Scanned Text