From d39a7ddda73c49c384cd2dfcb31def775e3b6562 Mon Sep 17 00:00:00 2001
From: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
Date: Sat, 31 Jan 2026 20:07:45 +0000
Subject: [PATCH] Bug/pageeditor virtualisation (#5614)
---
.../src/core/components/layout/Workbench.tsx | 13 +-
.../components/pageEditor/DragDropGrid.tsx | 46 +++++-
.../core/components/pageEditor/PageEditor.tsx | 141 +++++++++++++++++-
.../components/pageEditor/PageThumbnail.tsx | 41 -----
.../pageEditor/commands/pageCommands.ts | 64 ++++----
.../core/components/pageEditor/constants.ts | 4 +-
.../pageEditor/hooks/usePageDocument.ts | 96 +++++++++++-
.../src/core/contexts/PageEditorContext.tsx | 43 +++++-
.../src/core/hooks/useThumbnailGeneration.ts | 23 ++-
.../services/enhancedPDFProcessingService.ts | 22 +--
frontend/src/core/services/fileAnalyzer.ts | 2 +-
11 files changed, 381 insertions(+), 114 deletions(-)
diff --git a/frontend/src/core/components/layout/Workbench.tsx b/frontend/src/core/components/layout/Workbench.tsx
index a0be189f9..99d65e83f 100644
--- a/frontend/src/core/components/layout/Workbench.tsx
+++ b/frontend/src/core/components/layout/Workbench.tsx
@@ -141,14 +141,15 @@ export default function Workbench() {
);
case "pageEditor":
-
+
return (
- <>
+
{pageEditorFunctions && (
-
+
+
)}
- >
+
);
default:
@@ -207,9 +209,10 @@ export default function Workbench() {
{/* Main content area */}
{renderMainContent()}
diff --git a/frontend/src/core/components/pageEditor/DragDropGrid.tsx b/frontend/src/core/components/pageEditor/DragDropGrid.tsx
index 32e76ec7e..8f8598c29 100644
--- a/frontend/src/core/components/pageEditor/DragDropGrid.tsx
+++ b/frontend/src/core/components/pageEditor/DragDropGrid.tsx
@@ -37,6 +37,7 @@ interface DragDropGridProps {
getThumbnailData?: (itemId: string) => { src: string; rotation: number } | null;
zoomLevel?: number;
selectedFileIds?: string[];
+ onVisibleItemsChange?: (items: T[]) => void;
}
type DropSide = 'left' | 'right' | null;
@@ -198,7 +199,7 @@ interface DraggableItemProps {
zoomLevel: number;
}
-const DraggableItem = ({ item, index, itemRefs, boxSelectedPageIds, clearBoxSelection, getBoxSelection, activeId, activeDragIds, justMoved, getThumbnailData, renderItem, onUpdateDropTarget, zoomLevel }: DraggableItemProps) => {
+const DraggableItemInner = ({ item, index, itemRefs, boxSelectedPageIds, clearBoxSelection, getBoxSelection, activeId, activeDragIds, justMoved, getThumbnailData, renderItem, onUpdateDropTarget, zoomLevel }: DraggableItemProps) => {
const isPlaceholder = Boolean(item.isPlaceholder);
const pageNumber = (item as any).pageNumber ?? index + 1;
const { attributes, listeners, setNodeRef: setDraggableRef } = useDraggable({
@@ -252,6 +253,31 @@ const DraggableItem = ({ item, index, itemRefs, boxSelec
);
};
+// Memoize to prevent unnecessary re-renders and hook thrashing
+const DraggableItem = React.memo(DraggableItemInner, (prevProps, nextProps) => {
+ // Return true to SKIP re-render (props are equal)
+ // Return false to RE-RENDER (props changed)
+
+ // Check if item reference or content changed (including thumbnail)
+ const itemChanged = prevProps.item !== nextProps.item;
+
+ // If item object reference changed, we need to re-render
+ if (itemChanged) {
+ return false; // Props changed, re-render needed
+ }
+
+ // Item reference is same, check other props
+ return (
+ prevProps.item.id === nextProps.item.id &&
+ prevProps.index === nextProps.index &&
+ prevProps.activeId === nextProps.activeId &&
+ prevProps.justMoved === nextProps.justMoved &&
+ prevProps.zoomLevel === nextProps.zoomLevel &&
+ prevProps.activeDragIds.length === nextProps.activeDragIds.length &&
+ prevProps.boxSelectedPageIds.length === nextProps.boxSelectedPageIds.length
+ );
+}) as typeof DraggableItemInner;
+
const DragDropGrid = ({
items,
renderItem,
@@ -259,6 +285,7 @@ const DragDropGrid = ({
getThumbnailData,
zoomLevel = 1.0,
selectedFileIds,
+ onVisibleItemsChange,
}: DragDropGridProps) => {
const itemRefs = useRef