diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index bb14ed4de..5919a54a6 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -16,8 +16,7 @@ import { enhancedPDFProcessingService } from "../../services/enhancedPDFProcessi import { fileProcessingService } from "../../services/fileProcessingService"; import { pdfProcessingService } from "../../services/pdfProcessingService"; import { pdfWorkerManager } from "../../services/pdfWorkerManager"; -import { useThumbnailGeneration } from "../../hooks/useThumbnailGeneration"; -import { calculateScaleFromFileSize } from "../../utils/thumbnailUtils"; +// Thumbnail generation is now handled by individual PageThumbnail components import { fileStorage } from "../../services/fileStorage"; import { indexedDBManager, DATABASE_CONFIGS } from "../../services/indexedDBManager"; import './PageEditor.module.css'; @@ -47,9 +46,12 @@ class RotatePageCommand extends DOMCommand { if (pageElement) { const img = pageElement.querySelector('img'); if (img) { - const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0'); + // Extract current rotation from transform property to match the animated CSS + const currentTransform = img.style.transform || ''; + const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/); + const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0; const newRotation = currentRotation + this.degrees; - img.style.rotate = `${newRotation}deg`; + img.style.transform = `rotate(${newRotation}deg)`; } } } @@ -60,9 +62,12 @@ class RotatePageCommand extends DOMCommand { if (pageElement) { const img = pageElement.querySelector('img'); if (img) { - const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0'); + // Extract current rotation from transform property + const currentTransform = img.style.transform || ''; + const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/); + const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0; const previousRotation = currentRotation - this.degrees; - img.style.rotate = `${previousRotation}deg`; + img.style.transform = `rotate(${previousRotation}deg)`; } } } @@ -297,14 +302,18 @@ class BulkRotateCommand extends DOMCommand { if (img) { // Store original rotation for undo (only on first execution) if (!this.originalRotations.has(pageId)) { - const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0'); + const currentTransform = img.style.transform || ''; + const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/); + const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0; this.originalRotations.set(pageId, currentRotation); } - // Apply rotation - const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0'); + // Apply rotation using transform to trigger CSS animation + const currentTransform = img.style.transform || ''; + const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/); + const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0; const newRotation = currentRotation + this.degrees; - img.style.rotate = `${newRotation}deg`; + img.style.transform = `rotate(${newRotation}deg)`; } } }); @@ -316,7 +325,7 @@ class BulkRotateCommand extends DOMCommand { if (pageElement) { const img = pageElement.querySelector('img'); if (img && this.originalRotations.has(pageId)) { - img.style.rotate = `${this.originalRotations.get(pageId)}deg`; + img.style.transform = `rotate(${this.originalRotations.get(pageId)}deg)`; } } }); @@ -476,50 +485,7 @@ const PageEditor = ({ // DOM-first undo manager (replaces the old React state undo system) const undoManagerRef = useRef(new UndoManager()); - // Thumbnail generation (opt-in for visual tools) - MUST be before mergedPdfDocument - const { - generateThumbnails, - addThumbnailToCache, - getThumbnailFromCache, - stopGeneration, - destroyThumbnails - } = useThumbnailGeneration(); - - // Helper function to generate thumbnails in batches - const generateThumbnailBatch = useCallback(async (file: File, fileId: string, pageNumbers: number[]) => { - console.log(`📸 PageEditor: Starting thumbnail batch for ${file.name}, pages: [${pageNumbers.join(', ')}]`); - - try { - // Load PDF array buffer for Web Workers - const arrayBuffer = await file.arrayBuffer(); - - // Calculate quality scale based on file size - const scale = calculateScaleFromFileSize(selectors.getFileRecord(fileId)?.size || 0); - - // Start parallel thumbnail generation - const results = await generateThumbnails( - fileId, - arrayBuffer, - pageNumbers, - { - scale, - parallelBatches: Math.min(4, pageNumbers.length), - } - ); - - // Cache all generated thumbnails - results.forEach(({ pageNumber, thumbnail }) => { - if (thumbnail) { - const pageId = `${fileId}-${pageNumber}`; - addThumbnailToCache(pageId, thumbnail); - } - }); - - console.log(`📸 PageEditor: Thumbnail batch completed for ${file.name}. Generated ${results.length} thumbnails`); - } catch (error) { - console.error(`PageEditor: Thumbnail generation failed for ${file.name}:`, error); - } - }, [generateThumbnails, addThumbnailToCache, selectors]); + // Thumbnail generation is now handled on-demand by individual PageThumbnail components using modern services // Get primary file record outside useMemo to track processedFile changes @@ -617,52 +583,13 @@ const PageEditor = ({ return mergedDoc; }, [activeFileIds, primaryFileId, primaryFileRecord, processedFilePages, processedFileTotalPages, selectors, filesSignature]); - // Generate missing thumbnails for all loaded files - const generateMissingThumbnails = useCallback(async () => { - if (!mergedPdfDocument || activeFileIds.length === 0) { - return; - } + // Large document detection for smart loading + const isVeryLargeDocument = useMemo(() => { + return mergedPdfDocument ? mergedPdfDocument.totalPages > 2000 : false; + }, [mergedPdfDocument?.totalPages]); - console.log(`📸 PageEditor: Generating thumbnails for ${activeFileIds.length} files with ${mergedPdfDocument.totalPages} total pages`); - - // Process files sequentially to avoid PDF document contention - for (const fileId of activeFileIds) { - const file = selectors.getFile(fileId); - const fileRecord = selectors.getFileRecord(fileId); - - if (!file || !fileRecord?.processedFile) continue; - - const fileTotalPages = fileRecord.processedFile.totalPages; - if (!fileTotalPages) continue; - - // Find missing thumbnails for this file - const pageNumbersToGenerate: number[] = []; - for (let pageNum = 1; pageNum <= fileTotalPages; pageNum++) { - const pageId = `${fileId}-${pageNum}`; - if (!getThumbnailFromCache(pageId)) { - pageNumbersToGenerate.push(pageNum); - } - } - - if (pageNumbersToGenerate.length > 0) { - console.log(`📸 PageEditor: Generating thumbnails for ${fileRecord.name}: pages [${pageNumbersToGenerate.join(', ')}]`); - await generateThumbnailBatch(file, fileId, pageNumbersToGenerate); - } - - // Small delay between files to ensure proper sequential processing - if (activeFileIds.length > 1) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - } - }, [mergedPdfDocument, activeFileIds, selectors, getThumbnailFromCache, generateThumbnailBatch]); - - // Generate missing thumbnails when document is ready - useEffect(() => { - if (mergedPdfDocument && mergedPdfDocument.totalPages > 0) { - console.log(`📸 PageEditor: Document ready with ${mergedPdfDocument.totalPages} pages, checking for missing thumbnails`); - generateMissingThumbnails(); - } - }, [mergedPdfDocument, generateMissingThumbnails]); + // Thumbnails are now generated on-demand by PageThumbnail components + // No bulk generation needed - modern thumbnail service handles this efficiently // Selection and UI state management const [selectionMode, setSelectionMode] = useState(false); diff --git a/frontend/src/components/pageEditor/PageThumbnail.tsx b/frontend/src/components/pageEditor/PageThumbnail.tsx index ac2655da0..f59ae0969 100644 --- a/frontend/src/components/pageEditor/PageThumbnail.tsx +++ b/frontend/src/components/pageEditor/PageThumbnail.tsx @@ -72,7 +72,7 @@ const PageThumbnail: React.FC = ({ const [isMouseDown, setIsMouseDown] = useState(false); const [mouseStartPos, setMouseStartPos] = useState<{x: number, y: number} | null>(null); const dragElementRef = useRef(null); - const { getThumbnailFromCache } = useThumbnailGeneration(); + const { getThumbnailFromCache, requestThumbnail } = useThumbnailGeneration(); // Update thumbnail URL when page prop changes useEffect(() => { @@ -81,22 +81,43 @@ const PageThumbnail: React.FC = ({ } }, [page.thumbnail, page.id]); - // Poll for cached thumbnails as they're generated + // Request thumbnail on-demand using modern service useEffect(() => { - const checkThumbnail = () => { - const cachedThumbnail = getThumbnailFromCache(page.id); - if (cachedThumbnail && cachedThumbnail !== thumbnailUrl) { - setThumbnailUrl(cachedThumbnail); - } + let isCancelled = false; + + // If we already have a thumbnail, use it + if (page.thumbnail) { + setThumbnailUrl(page.thumbnail); + return; + } + + // Check cache first + const cachedThumbnail = getThumbnailFromCache(page.id); + if (cachedThumbnail) { + setThumbnailUrl(cachedThumbnail); + return; + } + + // Request thumbnail generation if we have the original file + if (originalFile) { + // Extract page number from page.id (format: fileId-pageNumber) + const pageNumber = parseInt(page.id.split('-').pop() || '1'); + + requestThumbnail(page.id, originalFile, pageNumber) + .then(thumbnail => { + if (!isCancelled && thumbnail) { + setThumbnailUrl(thumbnail); + } + }) + .catch(error => { + console.warn(`Failed to generate thumbnail for ${page.id}:`, error); + }); + } + + return () => { + isCancelled = true; }; - - // Check immediately - checkThumbnail(); - - // Poll every 500ms for new thumbnails - const pollInterval = setInterval(checkThumbnail, 500); - return () => clearInterval(pollInterval); - }, [page.id, getThumbnailFromCache, thumbnailUrl]); + }, [page.id, page.thumbnail, originalFile, getThumbnailFromCache, requestThumbnail]); const pageElementRef = useCallback((element: HTMLDivElement | null) => { if (element) { @@ -270,13 +291,11 @@ const PageThumbnail: React.FC = ({ top: 8, right: 8, zIndex: 10, - backgroundColor: 'rgba(255, 255, 255, 0.95)', - border: '1px solid #ccc', + backgroundColor: 'white', borderRadius: '4px', - padding: '4px', + padding: '2px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)', - pointerEvents: 'auto', - cursor: 'pointer' + pointerEvents: 'auto' }} onMouseDown={(e) => e.stopPropagation()} onMouseUp={(e) => e.stopPropagation()} @@ -369,6 +388,9 @@ const PageThumbnail: React.FC = ({ alignItems: 'center', whiteSpace: 'nowrap' }} + onMouseDown={(e) => e.stopPropagation()} + onMouseUp={(e) => e.stopPropagation()} + onClick={(e) => e.stopPropagation()} >