diff --git a/frontend/src/core/components/pageEditor/DragDropGrid.tsx b/frontend/src/core/components/pageEditor/DragDropGrid.tsx index 47225c19fe..4d55d8e072 100644 --- a/frontend/src/core/components/pageEditor/DragDropGrid.tsx +++ b/frontend/src/core/components/pageEditor/DragDropGrid.tsx @@ -479,6 +479,21 @@ const DragDropGrid = ({ onVisibleItemsChange(visibleItemsForCallback); }, [virtualRows, visibleItems, itemsPerRow, onVisibleItemsChange]); + const virtualRows = rowVirtualizer.getVirtualItems(); + + useEffect(() => { + if (!onVisibleItemsChange) return; + + const visibleItemsForCallback: T[] = []; + virtualRows.forEach((row) => { + const startIndex = row.index * itemsPerRow; + const endIndex = Math.min(startIndex + itemsPerRow, visibleItems.length); + visibleItemsForCallback.push(...visibleItems.slice(startIndex, endIndex)); + }); + + onVisibleItemsChange(visibleItemsForCallback); + }, [virtualRows, visibleItems, itemsPerRow, onVisibleItemsChange]); + // Re-measure virtualizer when zoom or items per row changes // Also remeasure when items change (not just length) to handle item additions/removals const visibleItemsSignature = useMemo(() => visibleItems.map(item => item.id).join(','), [visibleItems]); @@ -774,6 +789,7 @@ const DragDropGrid = ({ margin: '0 auto', }} > + {virtualRows.map((virtualRow) => { {virtualRows.map((virtualRow) => { const startIndex = virtualRow.index * itemsPerRow; const endIndex = Math.min(startIndex + itemsPerRow, visibleItems.length); diff --git a/frontend/src/core/components/pageEditor/PageEditor.tsx b/frontend/src/core/components/pageEditor/PageEditor.tsx index b9f7f8ab9c..27331f69f5 100644 --- a/frontend/src/core/components/pageEditor/PageEditor.tsx +++ b/frontend/src/core/components/pageEditor/PageEditor.tsx @@ -4,6 +4,7 @@ import { useFileState, useFileActions } from "@app/contexts/FileContext"; import { useNavigationGuard, useNavigationState } from "@app/contexts/NavigationContext"; import { usePageEditor } from "@app/contexts/PageEditorContext"; import { PageEditorFunctions, PDFPage } from "@app/types/pageEditor"; +import { PageEditorFunctions, PDFPage } from "@app/types/pageEditor"; // Thumbnail generation is now handled by individual PageThumbnail components import '@app/components/pageEditor/PageEditor.module.css'; import PageThumbnail from '@app/components/pageEditor/PageThumbnail'; @@ -24,6 +25,7 @@ import { usePageSelectionManager } from "@app/components/pageEditor/hooks/usePag import { usePageEditorCommands } from "@app/components/pageEditor/hooks/useEditorCommands"; import { usePageEditorExport } from "@app/components/pageEditor/hooks/usePageEditorExport"; import { useThumbnailGeneration } from "@app/hooks/useThumbnailGeneration"; +import { useThumbnailGeneration } from "@app/hooks/useThumbnailGeneration"; export interface PageEditorProps { onFunctionsReady?: (functions: PageEditorFunctions) => void; @@ -51,6 +53,27 @@ const PageEditor = ({ savePersistedDocument, } = usePageEditor(); + const [visiblePageIds, setVisiblePageIds] = useState([]); + const thumbnailRequestsRef = useRef>(new Set()); + const { requestThumbnail, getThumbnailFromCache } = useThumbnailGeneration(); + const handleVisibleItemsChange = useCallback((items: PDFPage[]) => { + setVisiblePageIds(prev => { + const ids = items.map(item => item.id); + if (prev.length === ids.length && prev.every((id, index) => id === ids[index])) { + return prev; + } + return ids; + }); + }, []); + const { + updateFileOrderFromPages, + fileOrder, + reorderedPages, + clearReorderedPages, + updateCurrentPages, + savePersistedDocument, + } = usePageEditor(); + const [visiblePageIds, setVisiblePageIds] = useState([]); const thumbnailRequestsRef = useRef>(new Set()); const { requestThumbnail, getThumbnailFromCache } = useThumbnailGeneration(); @@ -557,6 +580,7 @@ const PageEditor = ({ ]); return ( +
@@ -589,6 +619,7 @@ const PageEditor = ({ )} {displayDocument && ( + {/* Split Lines Overlay */} @@ -693,6 +724,7 @@ const PageEditor = ({ />
+ ); }; diff --git a/frontend/src/core/components/pageEditor/hooks/usePageDocument.ts b/frontend/src/core/components/pageEditor/hooks/usePageDocument.ts index b579aec07b..a5ebc3671c 100644 --- a/frontend/src/core/components/pageEditor/hooks/usePageDocument.ts +++ b/frontend/src/core/components/pageEditor/hooks/usePageDocument.ts @@ -4,6 +4,7 @@ import { usePageEditor } from '@app/contexts/PageEditorContext'; import { PDFDocument, PDFPage } from '@app/types/pageEditor'; import { FileId } from '@app/types/file'; import { FileAnalyzer } from '@app/services/fileAnalyzer'; +import { FileAnalyzer } from '@app/services/fileAnalyzer'; export interface PageDocumentHook { document: PDFDocument | null; @@ -18,6 +19,7 @@ export interface PageDocumentHook { export function usePageDocument(): PageDocumentHook { const { state, selectors } = useFileState(); const { fileOrder, currentPages, persistedDocument, persistedDocumentSignature } = usePageEditor(); + const { fileOrder, currentPages, persistedDocument, persistedDocumentSignature } = usePageEditor(); // Use PageEditorContext's fileOrder instead of FileContext's global order // This ensures the page editor respects its own workspace ordering diff --git a/frontend/src/core/contexts/PageEditorContext.tsx b/frontend/src/core/contexts/PageEditorContext.tsx index c73b72e311..f56dc7ca17 100644 --- a/frontend/src/core/contexts/PageEditorContext.tsx +++ b/frontend/src/core/contexts/PageEditorContext.tsx @@ -2,6 +2,7 @@ import React, { createContext, useContext, useState, useCallback, ReactNode, use import { FileId } from '@app/types/file'; import { useFileActions, useFileState } from '@app/contexts/FileContext'; import { PDFDocument, PDFPage } from '@app/types/pageEditor'; +import { PDFDocument, PDFPage } from '@app/types/pageEditor'; import { MAX_PAGE_EDITOR_FILES } from '@app/components/pageEditor/fileColors'; import { useNavigationState } from '@app/contexts/NavigationContext'; @@ -134,6 +135,10 @@ interface PageEditorContextValue { persistedDocumentSignature: string | null; savePersistedDocument: (document: PDFDocument, signature: string) => void; clearPersistedDocument: () => void; + persistedDocument: PDFDocument | null; + persistedDocumentSignature: string | null; + savePersistedDocument: (document: PDFDocument, signature: string) => void; + clearPersistedDocument: () => void; } const PageEditorContext = createContext(undefined); @@ -315,6 +320,7 @@ export function PageEditorProvider({ children }: PageEditorProviderProps) { }); }, 100); }, [state.files.ids, state.files.byId, fileActions]); + }, [state.files.ids, state.files.byId, fileActions]); const updateCurrentPages = useCallback((pages: PDFPage[] | null) => { setCurrentPages(pages); @@ -444,6 +450,10 @@ export function PageEditorProvider({ children }: PageEditorProviderProps) { persistedDocumentSignature, savePersistedDocument, clearPersistedDocument, + persistedDocument, + persistedDocumentSignature, + savePersistedDocument, + clearPersistedDocument, }), [ currentPages, updateCurrentPages, @@ -460,6 +470,10 @@ export function PageEditorProvider({ children }: PageEditorProviderProps) { persistedDocumentSignature, savePersistedDocument, clearPersistedDocument, + persistedDocument, + persistedDocumentSignature, + savePersistedDocument, + clearPersistedDocument, ]); return ( diff --git a/frontend/src/core/hooks/useThumbnailGeneration.ts b/frontend/src/core/hooks/useThumbnailGeneration.ts index ecf8b4ef6e..edd98b3194 100644 --- a/frontend/src/core/hooks/useThumbnailGeneration.ts +++ b/frontend/src/core/hooks/useThumbnailGeneration.ts @@ -23,10 +23,16 @@ const activeRequests = new Map>(); // Cache ArrayBuffers to avoid reading the same file multiple times const fileArrayBufferCache = new Map(); +// Cache ArrayBuffers to avoid reading the same file multiple times +const fileArrayBufferCache = new Map(); + // Batch processing configuration const BATCH_SIZE = 10; // Process thumbnails in batches of 10 for faster initial load const BATCH_DELAY = 50; // Wait 50ms to collect requests before processing const PRIORITY_BATCH_DELAY = 10; // Very fast processing for the first batch (visible pages) +const BATCH_SIZE = 10; // Process thumbnails in batches of 10 for faster initial load +const BATCH_DELAY = 50; // Wait 50ms to collect requests before processing +const PRIORITY_BATCH_DELAY = 10; // Very fast processing for the first batch (visible pages) // Process the queue in batches for better performance async function processRequestQueue() { @@ -128,6 +134,10 @@ async function processRequestQueue() { if (requestQueue.length === 0) { fileArrayBufferCache.clear(); } + // Clean up ArrayBuffer cache when queue is empty + if (requestQueue.length === 0) { + fileArrayBufferCache.clear(); + } } } @@ -188,6 +198,9 @@ export function useThumbnailGeneration() { // Clear ArrayBuffer cache fileArrayBufferCache.clear(); + // Clear ArrayBuffer cache + fileArrayBufferCache.clear(); + thumbnailGenerationService.destroy(); }, []);