import React, { useCallback, useState, useEffect, useRef } from 'react'; import { Text, Checkbox, Tooltip, ActionIcon } 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 { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; import { PDFPage, PDFDocument } from '../../types/pageEditor'; import { useThumbnailGeneration } from '../../hooks/useThumbnailGeneration'; import styles from './PageEditor.module.css'; // DOM Command types (match what PageEditor expects) abstract class DOMCommand { abstract execute(): void; abstract undo(): void; abstract description: string; } interface PageThumbnailProps { page: PDFPage; index: number; totalPages: number; originalFile?: File; selectedPages: number[]; selectionMode: boolean; movingPage: number | null; isAnimating: boolean; pageRefs: React.MutableRefObject>; onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => void; onTogglePage: (pageNumber: number) => void; onAnimateReorder: () => void; onExecuteCommand: (command: DOMCommand) => void; onSetStatus: (status: string) => void; onSetMovingPage: (page: number | null) => void; RotatePagesCommand: any; DeletePagesCommand: any; ToggleSplitCommand: any; pdfDocument: PDFDocument; setPdfDocument: (doc: PDFDocument) => void; } const PageThumbnail: React.FC = ({ page, index, totalPages, originalFile, selectedPages, selectionMode, movingPage, isAnimating, pageRefs, onReorderPages, onTogglePage, onAnimateReorder, onExecuteCommand, onSetStatus, onSetMovingPage, RotatePagesCommand, DeletePagesCommand, ToggleSplitCommand, pdfDocument, setPdfDocument, }: PageThumbnailProps) => { const [thumbnailUrl, setThumbnailUrl] = useState(page.thumbnail); const [isDragging, setIsDragging] = useState(false); const dragElementRef = useRef(null); const { getThumbnailFromCache } = useThumbnailGeneration(); // Update thumbnail URL when page prop changes useEffect(() => { if (page.thumbnail && page.thumbnail !== thumbnailUrl) { setThumbnailUrl(page.thumbnail); } }, [page.thumbnail, page.id]); // Poll for cached thumbnails as they're generated useEffect(() => { const checkThumbnail = () => { const cachedThumbnail = getThumbnailFromCache(page.id); if (cachedThumbnail && cachedThumbnail !== thumbnailUrl) { setThumbnailUrl(cachedThumbnail); } }; // Check immediately checkThumbnail(); // Poll every 500ms for new thumbnails const pollInterval = setInterval(checkThumbnail, 500); return () => clearInterval(pollInterval); }, [page.id, getThumbnailFromCache, thumbnailUrl]); const pageElementRef = useCallback((element: HTMLDivElement | null) => { if (element) { pageRefs.current.set(page.id, element); dragElementRef.current = element; const dragCleanup = draggable({ element, getInitialData: () => ({ pageNumber: page.pageNumber, pageId: page.id, selectedPages: selectionMode && selectedPages.includes(page.pageNumber) ? selectedPages : [page.pageNumber] }), onDragStart: () => { setIsDragging(true); }, onDrop: ({ location }) => { setIsDragging(false); if (location.current.dropTargets.length === 0) { return; } const dropTarget = location.current.dropTargets[0]; const targetData = dropTarget.data; if (targetData.type === 'page') { const targetPageNumber = targetData.pageNumber as number; const targetIndex = pdfDocument.pages.findIndex(p => p.pageNumber === targetPageNumber); if (targetIndex !== -1) { const pagesToMove = selectionMode && selectedPages.includes(page.pageNumber) ? selectedPages : undefined; onReorderPages(page.pageNumber, targetIndex, pagesToMove); } } } }); element.style.cursor = 'grab'; const dropCleanup = dropTargetForElements({ element, getData: () => ({ type: 'page', pageNumber: page.pageNumber }), onDrop: ({ source }) => {} }); (element as any).__dragCleanup = () => { dragCleanup(); dropCleanup(); }; } else { pageRefs.current.delete(page.id); if (dragElementRef.current && (dragElementRef.current as any).__dragCleanup) { (dragElementRef.current as any).__dragCleanup(); } } }, [page.id, page.pageNumber, pageRefs, selectionMode, selectedPages, pdfDocument.pages, onReorderPages]); // DOM command handlers const handleRotateLeft = useCallback((e: React.MouseEvent) => { e.stopPropagation(); // Use DOM command to rotate the image directly const pageElement = document.querySelector(`[data-page-number="${page.pageNumber}"]`); if (pageElement) { const img = pageElement.querySelector('img'); if (img) { const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0'); const newRotation = currentRotation - 90; img.style.rotate = `${newRotation}deg`; } } onSetStatus(`Rotated page ${page.pageNumber} left`); }, [page.pageNumber, onSetStatus]); const handleRotateRight = useCallback((e: React.MouseEvent) => { e.stopPropagation(); // Use DOM command to rotate the image directly const pageElement = document.querySelector(`[data-page-number="${page.pageNumber}"]`); if (pageElement) { const img = pageElement.querySelector('img'); if (img) { const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0'); const newRotation = currentRotation + 90; img.style.rotate = `${newRotation}deg`; } } onSetStatus(`Rotated page ${page.pageNumber} right`); }, [page.pageNumber, onSetStatus]); const handleDelete = useCallback((e: React.MouseEvent) => { e.stopPropagation(); console.log('Delete page:', page.pageNumber); onSetStatus(`Deleted page ${page.pageNumber}`); }, [page.pageNumber, onSetStatus]); const handleSplit = useCallback((e: React.MouseEvent) => { e.stopPropagation(); // Create a command to toggle split marker const command = new ToggleSplitCommand([page.id]); onExecuteCommand(command); const action = page.splitAfter ? 'removed' : 'added'; onSetStatus(`Split marker ${action} after page ${page.pageNumber}`); }, [page.pageNumber, page.id, page.splitAfter, onExecuteCommand, onSetStatus, ToggleSplitCommand]); return (
{selectionMode && (
e.stopPropagation()} onDragStart={(e) => { e.preventDefault(); e.stopPropagation(); }} onClick={(e) => { e.stopPropagation(); onTogglePage(page.pageNumber); }} > { // onChange is handled by the parent div click }} size="sm" />
)}
{thumbnailUrl ? ( {`Page ) : (
📄 Page {page.pageNumber}
)}
{page.pageNumber}
{ e.stopPropagation(); if (index > 0 && !movingPage && !isAnimating) { onSetMovingPage(page.pageNumber); onAnimateReorder(); setTimeout(() => onSetMovingPage(null), 500); onSetStatus(`Moved page ${page.pageNumber} left`); } }} > { e.stopPropagation(); if (index < totalPages - 1 && !movingPage && !isAnimating) { onSetMovingPage(page.pageNumber); onAnimateReorder(); setTimeout(() => onSetMovingPage(null), 500); onSetStatus(`Moved page ${page.pageNumber} right`); } }} > {index < totalPages - 1 && ( )}
{/* Split indicator - shows where document will be split */} {page.splitAfter && (
)}
); }; export default PageThumbnail;