import React, { useCallback, useState, useEffect, useRef } from 'react'; import { Text, Checkbox, Tooltip, ActionIcon, Loader } from '@mantine/core'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import RotateLeftIcon from '@mui/icons-material/RotateLeft'; import RotateRightIcon from '@mui/icons-material/RotateRight'; import DeleteIcon from '@mui/icons-material/Delete'; import ContentCutIcon from '@mui/icons-material/ContentCut'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import { PDFPage, PDFDocument } from '../../types/pageEditor'; import { RotatePagesCommand, DeletePagesCommand, ToggleSplitCommand } from '../../commands/pageCommands'; import { Command } from '../../hooks/useUndoRedo'; import styles from './PageEditor.module.css'; import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist'; // Ensure PDF.js worker is available if (!GlobalWorkerOptions.workerSrc) { GlobalWorkerOptions.workerSrc = '/pdf.worker.js'; console.log('📸 PageThumbnail: Set PDF.js worker source to /pdf.worker.js'); } else { console.log('📸 PageThumbnail: PDF.js worker source already set to', GlobalWorkerOptions.workerSrc); } interface PageThumbnailProps { page: PDFPage; index: number; totalPages: number; originalFile?: File; // For lazy thumbnail generation selectedPages: number[]; selectionMode: boolean; draggedPage: number | null; dropTarget: number | 'end' | null; movingPage: number | null; isAnimating: boolean; pageRefs: React.MutableRefObject>; onDragStart: (pageNumber: number) => void; onDragEnd: () => void; onDragOver: (e: React.DragEvent) => void; onDragEnter: (pageNumber: number) => void; onDragLeave: () => void; onDrop: (e: React.DragEvent, pageNumber: number) => void; onTogglePage: (pageNumber: number) => void; onAnimateReorder: (pageNumber: number, targetIndex: number) => void; onExecuteCommand: (command: Command) => void; onSetStatus: (status: string) => void; onSetMovingPage: (pageNumber: number | null) => void; RotatePagesCommand: typeof RotatePagesCommand; DeletePagesCommand: typeof DeletePagesCommand; ToggleSplitCommand: typeof ToggleSplitCommand; pdfDocument: PDFDocument; setPdfDocument: (doc: PDFDocument) => void; } const PageThumbnail = React.memo(({ page, index, totalPages, originalFile, selectedPages, selectionMode, draggedPage, dropTarget, movingPage, isAnimating, pageRefs, onDragStart, onDragEnd, onDragOver, onDragEnter, onDragLeave, onDrop, onTogglePage, onAnimateReorder, onExecuteCommand, onSetStatus, onSetMovingPage, RotatePagesCommand, DeletePagesCommand, ToggleSplitCommand, pdfDocument, setPdfDocument, }: PageThumbnailProps) => { const [thumbnailUrl, setThumbnailUrl] = useState(page.thumbnail); const [isLoadingThumbnail, setIsLoadingThumbnail] = useState(false); // Update thumbnail URL when page prop changes useEffect(() => { if (page.thumbnail && page.thumbnail !== thumbnailUrl) { console.log(`📸 PageThumbnail: Updating thumbnail URL for page ${page.pageNumber}`, page.thumbnail.substring(0, 50) + '...'); setThumbnailUrl(page.thumbnail); } }, [page.thumbnail, page.pageNumber, page.id, thumbnailUrl]); // Listen for ready thumbnails from Web Workers (only if no existing thumbnail) useEffect(() => { if (thumbnailUrl) { console.log(`📸 PageThumbnail: Page ${page.pageNumber} already has thumbnail, skipping worker listener`); return; // Skip if we already have a thumbnail } console.log(`📸 PageThumbnail: Setting up worker listener for page ${page.pageNumber} (${page.id})`); const handleThumbnailReady = (event: CustomEvent) => { const { pageNumber, thumbnail, pageId } = event.detail; console.log(`📸 PageThumbnail: Received worker thumbnail for page ${pageNumber}, looking for page ${page.pageNumber} (${page.id})`); if (pageNumber === page.pageNumber && pageId === page.id) { console.log(`✓ PageThumbnail: Thumbnail matched for page ${page.pageNumber}, setting URL`); setThumbnailUrl(thumbnail); } }; window.addEventListener('thumbnailReady', handleThumbnailReady as EventListener); return () => { console.log(`📸 PageThumbnail: Cleaning up worker listener for page ${page.pageNumber}`); window.removeEventListener('thumbnailReady', handleThumbnailReady as EventListener); }; }, [page.pageNumber, page.id, thumbnailUrl]); // Register this component with pageRefs for animations const pageElementRef = useCallback((element: HTMLDivElement | null) => { if (element) { pageRefs.current.set(page.id, element); } else { pageRefs.current.delete(page.id); } }, [page.id, pageRefs]); return (
{ if (!isAnimating && draggedPage && page.pageNumber !== draggedPage && dropTarget === page.pageNumber) { return 'translateX(20px)'; } return 'translateX(0)'; })(), transition: isAnimating ? 'none' : 'transform 0.2s ease-in-out' }} draggable onDragStart={() => onDragStart(page.pageNumber)} onDragEnd={onDragEnd} onDragOver={onDragOver} onDragEnter={() => onDragEnter(page.pageNumber)} onDragLeave={onDragLeave} onDrop={(e) => onDrop(e, page.pageNumber)} > {selectionMode && (
e.stopPropagation()} onDragStart={(e) => { e.preventDefault(); e.stopPropagation(); }} onClick={(e) => { console.log('📸 Checkbox clicked for page', page.pageNumber); e.stopPropagation(); onTogglePage(page.pageNumber); }} > { // onChange is handled by the parent div click }} size="sm" />
)}
{thumbnailUrl ? ( {`Page ) : isLoadingThumbnail ? (
Loading...
) : (
📄 Page {page.pageNumber}
)}
{page.pageNumber}
{ e.stopPropagation(); if (index > 0 && !movingPage && !isAnimating) { onSetMovingPage(page.pageNumber); onAnimateReorder(page.pageNumber, index - 1); setTimeout(() => onSetMovingPage(null), 500); onSetStatus(`Moved page ${page.pageNumber} left`); } }} > { e.stopPropagation(); if (index < totalPages - 1 && !movingPage && !isAnimating) { onSetMovingPage(page.pageNumber); onAnimateReorder(page.pageNumber, index + 1); setTimeout(() => onSetMovingPage(null), 500); onSetStatus(`Moved page ${page.pageNumber} right`); } }} > { e.stopPropagation(); const command = new RotatePagesCommand( pdfDocument, setPdfDocument, [page.id], -90 ); onExecuteCommand(command); onSetStatus(`Rotated page ${page.pageNumber} left`); }} > { e.stopPropagation(); const command = new RotatePagesCommand( pdfDocument, setPdfDocument, [page.id], 90 ); onExecuteCommand(command); onSetStatus(`Rotated page ${page.pageNumber} right`); }} > { e.stopPropagation(); const command = new DeletePagesCommand( pdfDocument, setPdfDocument, [page.id] ); onExecuteCommand(command); onSetStatus(`Deleted page ${page.pageNumber}`); }} > {index > 0 && ( { e.stopPropagation(); const command = new ToggleSplitCommand( pdfDocument, setPdfDocument, [page.id] ); onExecuteCommand(command); onSetStatus(`Split marker toggled for page ${page.pageNumber}`); }} > )}
); }, (prevProps, nextProps) => { // Only re-render if essential props change return ( prevProps.page.id === nextProps.page.id && prevProps.page.pageNumber === nextProps.page.pageNumber && prevProps.page.rotation === nextProps.page.rotation && prevProps.page.thumbnail === nextProps.page.thumbnail && prevProps.selectedPages === nextProps.selectedPages && // Compare array reference - will re-render when selection changes prevProps.selectionMode === nextProps.selectionMode && prevProps.draggedPage === nextProps.draggedPage && prevProps.dropTarget === nextProps.dropTarget && prevProps.movingPage === nextProps.movingPage && prevProps.isAnimating === nextProps.isAnimating ); }); export default PageThumbnail;