import React, { useEffect, useRef } from 'react'; import type { PdfFile } from '../pdf/pdfTypes'; import * as pdfjsLib from 'pdfjs-dist'; import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?worker&url'; // pdf.js worker setup // eslint-disable-next-line @typescript-eslint/no-explicit-any (pdfjsLib as any).GlobalWorkerOptions.workerSrc = pdfjsWorker; interface PagePreviewModalProps { isOpen: boolean; pdf: PdfFile | null; pageIndex: number | null; // original page index, 0-based rotation: number; // degrees visualIndex: number | null; // current position in order, 0-based totalPages: number; canGoPrevious: boolean; canGoNext: boolean; onPrevious: () => void; onNext: () => void; onClose: () => void; } const PagePreviewModal: React.FC = ({ isOpen, pdf, pageIndex, rotation, visualIndex, totalPages, canGoPrevious, canGoNext, onPrevious, onNext, onClose, }) => { const canvasRef = useRef(null); useEffect(() => { if (!isOpen) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); return; } if (e.key === 'ArrowLeft' && canGoPrevious) { e.preventDefault(); onPrevious(); return; } if (e.key === 'ArrowRight' && canGoNext) { e.preventDefault(); onNext(); } }; window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('keydown', handleKeyDown); }; }, [isOpen, canGoPrevious, canGoNext, onPrevious, onNext, onClose]); useEffect(() => { if (!isOpen || !pdf || pageIndex == null) return; let cancelled = false; (async () => { try { const canvas = canvasRef.current; if (canvas) { const ctx = canvas.getContext('2d'); if (ctx) { ctx.clearRect(0, 0, canvas.width, canvas.height); } } // copy data for pdf.js (avoid detaching original ArrayBuffer) const src = new Uint8Array(pdf.arrayBuffer); const copy = new Uint8Array(src.byteLength); copy.set(src); const loadingTask = pdfjsLib.getDocument({ data: copy }); const doc = await loadingTask.promise; if (cancelled) return; const page = await doc.getPage(pageIndex + 1); if (cancelled) return; const viewport = page.getViewport({ scale: 1 }); const maxWidth = Math.min(window.innerWidth * 0.9, 800); const maxHeight = window.innerHeight * 0.75; const scale = Math.min( maxWidth / viewport.width, maxHeight / viewport.height ); const scaledViewport = page.getViewport({ scale }); const visibleCanvas = canvasRef.current; if (!visibleCanvas) return; const visibleCtx = visibleCanvas.getContext('2d'); if (!visibleCtx) return; let canvasWidth = scaledViewport.width; let canvasHeight = scaledViewport.height; const angle = ((rotation % 360) + 360) % 360; if (angle === 90 || angle === 270) { canvasWidth = scaledViewport.height; canvasHeight = scaledViewport.width; } visibleCanvas.width = canvasWidth; visibleCanvas.height = canvasHeight; const baseCanvas = document.createElement('canvas'); const baseCtx = baseCanvas.getContext('2d'); if (!baseCtx) return; baseCanvas.width = scaledViewport.width; baseCanvas.height = scaledViewport.height; const renderTask = page.render({ canvasContext: baseCtx, viewport: scaledViewport, }); await renderTask.promise; if (cancelled) return; visibleCtx.save(); switch (angle) { case 90: visibleCtx.translate(canvasWidth, 0); visibleCtx.rotate((angle * Math.PI) / 180); break; case 180: visibleCtx.translate(canvasWidth, canvasHeight); visibleCtx.rotate((angle * Math.PI) / 180); break; case 270: visibleCtx.translate(0, canvasHeight); visibleCtx.rotate((angle * Math.PI) / 180); break; } visibleCtx.drawImage(baseCanvas, 0, 0); visibleCtx.restore(); } catch (e) { console.error('Error rendering preview', e); } })(); return () => { cancelled = true; }; }, [isOpen, pdf, pageIndex, rotation]); if (!isOpen || !pdf || pageIndex == null) return null; const positionLabel = visualIndex != null && visualIndex >= 0 ? `${visualIndex + 1} / ${totalPages}` : `Page ${pageIndex + 1}`; return (
e.stopPropagation()} style={{ position: 'relative', background: '#111827', borderRadius: '0.75rem', padding: '0.75rem', maxWidth: '90vw', maxHeight: '90vh', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem', overflow: 'visible', }} > {/* Previous page */} {/* Next page */} {/* Close */}
{positionLabel} · Original page {pageIndex + 1} · Rot {rotation}°
); }; export default PagePreviewModal;