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 (
);
};
// 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 (
);
}
return (
{/* Visual Header */}
);
};
// 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 (
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 ? (
) : (
Rendering...
)}
{/* Overlay Actions (Only show if not dragging) */}
{!isDragging && activeTool === 'organize' && (
)}
{/* OCR Overlay */}
{!isDragging && activeTool === 'ocr' && (
)}
Create Signature
Initializing AllSilva Engine...
Loading PDF libraries
{/* --- Sidebar --- */}
{/* --- Main Content --- */}
{/* Header / Topbar */}
Signature Ready
)}
{/* Workspace */}
)}
setIsSignModalOpen(false)}
onSave={handleAddSignature}
/>
);
}
{activeTool} Mode {pages.length} Pages
{activeTool === 'sign' && signatures.length === 0 && ( )} {activeTool === 'sign' && signatures.length > 0 && (
{/* Empty State */}
{pages.length === 0 && (
)}
{/* Grid */}
{pages.length > 0 && (
p.id)} strategy={rectSortingStrategy}>
{/* Drag Overlay would go here for smoother animations, omitted for brevity in single file */}
)}
{/* OCR Result Overlay */}
{ocrResult && (
)}
{/* Status Bar / Loading Overlay */}
{isLoading && (
Drop PDF here
Drag and drop your document to begin organizing, merging, or editing with AllSilva tools.
{pages.map((page, idx) => (
))}
Scanned Text
{loadingText}
Processing locally in browser...

