Merge branch 'bug/page-editor-fixes' of https://github.com/Stirling-Tools/Stirling-PDF into bug/page-editor-fixes

This commit is contained in:
Reece
2026-02-03 11:52:54 +00:00
5 changed files with 77 additions and 0 deletions
@@ -479,6 +479,21 @@ const DragDropGrid = <T extends DragDropItem>({
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 = <T extends DragDropItem>({
margin: '0 auto',
}}
>
{virtualRows.map((virtualRow) => {
{virtualRows.map((virtualRow) => {
const startIndex = virtualRow.index * itemsPerRow;
const endIndex = Math.min(startIndex + itemsPerRow, visibleItems.length);
@@ -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<string[]>([]);
const thumbnailRequestsRef = useRef<Set<string>>(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<string[]>([]);
const thumbnailRequestsRef = useRef<Set<string>>(new Set());
const { requestThumbnail, getThumbnailFromCache } = useThumbnailGeneration();
@@ -557,6 +580,7 @@ const PageEditor = ({
]);
return (
<div
<div
ref={containerRef}
data-scrolling-container="true"
@@ -568,6 +592,12 @@ const PageEditor = ({
position: 'relative',
width: '100%',
}}
style={{
height: '100%',
overflow: 'auto',
position: 'relative',
width: '100%',
}}
>
<LoadingOverlay visible={globalProcessing && !initialDocument} />
@@ -589,6 +619,7 @@ const PageEditor = ({
)}
{displayDocument && (
<Box ref={gridContainerRef} p={0} pt="2rem" pb="4rem" style={{ position: 'relative' }}>
<Box ref={gridContainerRef} p={0} pt="2rem" pb="4rem" style={{ position: 'relative' }}>
{/* Split Lines Overlay */}
@@ -693,6 +724,7 @@ const PageEditor = ({
/>
</div>
</div>
);
};
@@ -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
@@ -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<PageEditorContextValue | undefined>(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 (
@@ -23,10 +23,16 @@ const activeRequests = new Map<string, Promise<string | null>>();
// Cache ArrayBuffers to avoid reading the same file multiple times
const fileArrayBufferCache = new Map<File, ArrayBuffer>();
// Cache ArrayBuffers to avoid reading the same file multiple times
const fileArrayBufferCache = new Map<File, ArrayBuffer>();
// 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();
}, []);