From ae7dbda91d3de275e633e944850b07528dab2417 Mon Sep 17 00:00:00 2001 From: Reece Date: Tue, 11 Nov 2025 14:07:58 +0000 Subject: [PATCH] Fix various bugs --- .../core/components/pageEditor/PageEditor.tsx | 4 +- .../components/pageEditor/PageThumbnail.tsx | 30 +++++++------ .../hooks/useEditedDocumentState.ts | 32 +++++++++++--- .../pageEditor/hooks/usePageEditorCommands.ts | 43 +++++++++++-------- .../core/components/shared/AppConfigModal.tsx | 2 - frontend/src/core/types/fileContext.ts | 13 +++++- 6 files changed, 80 insertions(+), 44 deletions(-) diff --git a/frontend/src/core/components/pageEditor/PageEditor.tsx b/frontend/src/core/components/pageEditor/PageEditor.tsx index a2a265cdb..ef27b02c1 100644 --- a/frontend/src/core/components/pageEditor/PageEditor.tsx +++ b/frontend/src/core/components/pageEditor/PageEditor.tsx @@ -140,7 +140,7 @@ const PageEditor = ({ const initialDocument = useInitialPageDocument(); const { document: mergedPdfDocument } = usePageDocument(); - const { editedDocument, setEditedDocument, displayDocument } = useEditedDocumentState({ + const { editedDocument, setEditedDocument, displayDocument, getEditedDocument } = useEditedDocumentState({ initialDocument, mergedPdfDocument, reorderedPages, @@ -204,7 +204,7 @@ const PageEditor = ({ closePdf, } = usePageEditorCommands({ displayDocument, - editedDocument, + getEditedDocument, setEditedDocument, splitPositions, setSplitPositions, diff --git a/frontend/src/core/components/pageEditor/PageThumbnail.tsx b/frontend/src/core/components/pageEditor/PageThumbnail.tsx index 9a746e1c5..c24acf290 100644 --- a/frontend/src/core/components/pageEditor/PageThumbnail.tsx +++ b/frontend/src/core/components/pageEditor/PageThumbnail.tsx @@ -80,6 +80,8 @@ const PageThumbnail: React.FC = ({ zoomLevel = 1.0, justMoved = false, }: PageThumbnailProps) => { + const pageIndex = page.pageNumber - 1; + const [isMouseDown, setIsMouseDown] = useState(false); const [mouseStartPos, setMouseStartPos] = useState<{x: number, y: number} | null>(null); const [isHovered, setIsHovered] = useState(false); @@ -193,13 +195,13 @@ const PageThumbnail: React.FC = ({ e.stopPropagation(); // Create a command to toggle split at this position - const command = createSplitCommand(index); + const command = createSplitCommand(pageIndex); onExecuteCommand(command); - const hasSplit = splitPositions.has(index); + const hasSplit = splitPositions.has(pageIndex); const action = hasSplit ? 'removed' : 'added'; - onSetStatus(`Split marker ${action} after position ${index + 1}`); - }, [index, splitPositions, onExecuteCommand, onSetStatus, createSplitCommand]); + onSetStatus(`Split marker ${action} after position ${pageIndex + 1}`); + }, [pageIndex, splitPositions, onExecuteCommand, onSetStatus, createSplitCommand]); const handleInsertFileAfter = useCallback((e: React.MouseEvent) => { e.stopPropagation(); @@ -282,14 +284,14 @@ const PageThumbnail: React.FC = ({ label: 'Move Left', onClick: (e) => { e.stopPropagation(); - if (index > 0 && !movingPage && !isAnimating) { + if (pageIndex > 0 && !movingPage && !isAnimating) { onSetMovingPage(page.pageNumber); - onReorderPages(page.pageNumber, index - 1); + onReorderPages(page.pageNumber, pageIndex - 1); setTimeout(() => onSetMovingPage(null), 650); onSetStatus(`Moved page ${page.pageNumber} left`); } }, - disabled: index === 0 + disabled: pageIndex === 0 }, { id: 'move-right', @@ -297,14 +299,17 @@ const PageThumbnail: React.FC = ({ label: 'Move Right', onClick: (e) => { e.stopPropagation(); - if (index < totalPages - 1 && !movingPage && !isAnimating) { + if (pageIndex < totalPages - 1 && !movingPage && !isAnimating) { onSetMovingPage(page.pageNumber); - onReorderPages(page.pageNumber, index + 1); + // ReorderPagesCommand expects target index relative to the original array. + // When moving toward the right (higher index), provide desiredIndex + 1 + // so the command's internal adjustment (targetIndex - 1) lands correctly. + onReorderPages(page.pageNumber, pageIndex + 2); setTimeout(() => onSetMovingPage(null), 650); onSetStatus(`Moved page ${page.pageNumber} right`); } }, - disabled: index === totalPages - 1 + disabled: pageIndex === totalPages - 1 }, { id: 'rotate-left', @@ -330,7 +335,7 @@ const PageThumbnail: React.FC = ({ icon: , label: 'Split After', onClick: handleSplit, - hidden: index >= totalPages - 1, + hidden: pageIndex >= totalPages - 1, }, { id: 'insert', @@ -338,7 +343,7 @@ const PageThumbnail: React.FC = ({ label: 'Insert File After', onClick: handleInsertFileAfter, } - ], [index, totalPages, movingPage, isAnimating, page.pageNumber, handleRotateLeft, handleRotateRight, handleDelete, handleSplit, handleInsertFileAfter, onReorderPages, onSetMovingPage, onSetStatus]); + ], [pageIndex, totalPages, movingPage, isAnimating, page.pageNumber, handleRotateLeft, handleRotateRight, handleDelete, handleSplit, handleInsertFileAfter, onReorderPages, onSetMovingPage, onSetStatus]); return (
= ({ ) : thumbnailUrl ? ( {`Page { const [editedDocument, setEditedDocument] = useState(null); + const editedDocumentRef = useRef(null); const pagePositionCacheRef = useRef>(new Map()); const pageNeighborCacheRef = useRef>(new Map()); @@ -45,6 +46,11 @@ export const useEditedDocumentState = ({ clearReorderedPages(); }, [reorderedPages, editedDocument, clearReorderedPages]); + // Keep ref synced so effects can read latest without re-running + useEffect(() => { + editedDocumentRef.current = editedDocument; + }, [editedDocument]); + // Cache page positions to help future insertions preserve intent useEffect(() => { if (!editedDocument) return; @@ -67,12 +73,13 @@ export const useEditedDocumentState = ({ // Keep editedDocument in sync with out-of-band insert/remove events (e.g. uploads finishing) useEffect(() => { - if (!mergedPdfDocument || !editedDocument) return; + const currentEditedDocument = editedDocumentRef.current; + if (!mergedPdfDocument || !currentEditedDocument) return; const sourcePages = mergedPdfDocument.pages; const sourceIds = new Set(sourcePages.map((p) => p.id)); - const prevIds = new Set(editedDocument.pages.map((p) => p.id)); + const prevIds = new Set(currentEditedDocument.pages.map((p) => p.id)); const newPages: PDFPage[] = []; for (const page of sourcePages) { if (!prevIds.has(page.id)) { @@ -81,9 +88,14 @@ export const useEditedDocumentState = ({ } const hasAdditions = newPages.length > 0; + const isEphemeralPage = (page: PDFPage) => { + // Blank pages and placeholders are editor-local pages that don't exist in the source document. + return Boolean(page.isBlankPage || page.isPlaceholder); + }; + let hasRemovals = false; - for (const page of editedDocument.pages) { - if (!sourceIds.has(page.id)) { + for (const page of currentEditedDocument.pages) { + if (!sourceIds.has(page.id) && !isEphemeralPage(page)) { hasRemovals = true; break; } @@ -105,7 +117,7 @@ export const useEditedDocumentState = ({ const nextInsertIndexByFile = new Map(placeholderPositions); if (hasRemovals) { - pages = pages.filter((page) => sourceIds.has(page.id)); + pages = pages.filter((page) => sourceIds.has(page.id) || isEphemeralPage(page)); } if (hasAdditions) { @@ -164,10 +176,15 @@ export const useEditedDocumentState = ({ pages = pages.map((page, index) => ({ ...page, pageNumber: index + 1 })); return { ...prev, pages }; }); - }, [mergedPdfDocument, editedDocument, fileOrderKey, mergedDocSignature]); + }, [mergedPdfDocument, fileOrderKey, mergedDocSignature]); const displayDocument = editedDocument || initialDocument; + const getEditedDocument = useCallback( + () => editedDocumentRef.current, + [] + ); + useEffect(() => { updateCurrentPages(displayDocument?.pages ?? null); }, [displayDocument, updateCurrentPages]); @@ -176,6 +193,7 @@ export const useEditedDocumentState = ({ editedDocument, setEditedDocument, displayDocument, + getEditedDocument, }; }; diff --git a/frontend/src/core/components/pageEditor/hooks/usePageEditorCommands.ts b/frontend/src/core/components/pageEditor/hooks/usePageEditorCommands.ts index 306e5f629..e78641ad3 100644 --- a/frontend/src/core/components/pageEditor/hooks/usePageEditorCommands.ts +++ b/frontend/src/core/components/pageEditor/hooks/usePageEditorCommands.ts @@ -20,7 +20,7 @@ type FileSelectors = ReturnType["selectors"]; interface UsePageEditorCommandsParams { displayDocument: PDFDocument | null; - editedDocument: PDFDocument | null; + getEditedDocument: () => PDFDocument | null; setEditedDocument: React.Dispatch>; splitPositions: Set; setSplitPositions: React.Dispatch>>; @@ -38,7 +38,7 @@ interface UsePageEditorCommandsParams { export const usePageEditorCommands = ({ displayDocument, - editedDocument, + getEditedDocument, setEditedDocument, splitPositions, setSplitPositions, @@ -81,11 +81,12 @@ export const usePageEditorCommands = ({ const createDeleteCommand = useCallback( (pageIds: string[]) => ({ execute: () => { - if (!displayDocument) return; + const currentDocument = getEditedDocument(); + if (!currentDocument) return; const pagesToDelete = pageIds .map((pageId) => { - const page = displayDocument.pages.find((p) => p.id === pageId); + const page = currentDocument.pages.find((p) => p.id === pageId); return page?.pageNumber || 0; }) .filter((num) => num > 0); @@ -93,11 +94,11 @@ export const usePageEditorCommands = ({ if (pagesToDelete.length > 0) { const deleteCommand = new DeletePagesCommand( pagesToDelete, - () => displayDocument, + getEditedDocument, setEditedDocument, (pageNumbers: number[]) => { - const pageIds = getPageIdsFromNumbers(pageNumbers); - setSelectedPageIds(pageIds); + const updatedIds = getPageIdsFromNumbers(pageNumbers); + setSelectedPageIds(updatedIds); }, () => splitPositions, setSplitPositions, @@ -110,8 +111,8 @@ export const usePageEditorCommands = ({ }), [ closePdf, - displayDocument, executeCommandWithTracking, + getEditedDocument, getPageIdsFromNumbers, getPageNumbersFromIds, selectedPageIds, @@ -159,7 +160,7 @@ export const usePageEditorCommands = ({ const deleteCommand = new DeletePagesCommand( selectedPageNumbers, - () => displayDocument, + getEditedDocument, setEditedDocument, (pageNumbers: number[]) => { const pageIds = getPageIdsFromNumbers(pageNumbers); @@ -175,6 +176,7 @@ export const usePageEditorCommands = ({ closePdf, displayDocument, executeCommandWithTracking, + getEditedDocument, getPageIdsFromNumbers, getPageNumbersFromIds, selectedPageIds, @@ -190,7 +192,7 @@ export const usePageEditorCommands = ({ const deleteCommand = new DeletePagesCommand( [pageNumber], - () => displayDocument, + getEditedDocument, setEditedDocument, (pageNumbers: number[]) => { const pageIds = getPageIdsFromNumbers(pageNumbers); @@ -205,7 +207,7 @@ export const usePageEditorCommands = ({ }, [ closePdf, - displayDocument, + getEditedDocument, executeCommandWithTracking, getPageIdsFromNumbers, getPageNumbersFromIds, @@ -274,13 +276,14 @@ export const usePageEditorCommands = ({ const pageBreakCommand = new PageBreakCommand( selectedPageNumbers, - () => displayDocument, + getEditedDocument, setEditedDocument ); executeCommandWithTracking(pageBreakCommand); }, [ displayDocument, executeCommandWithTracking, + getEditedDocument, getPageNumbersFromIds, selectedPageIds, setEditedDocument, @@ -294,10 +297,11 @@ export const usePageEditorCommands = ({ insertAfterPage: number, isFromStorage?: boolean ) => { - if (!editedDocument || files.length === 0) return; + const workingDocument = getEditedDocument(); + if (!workingDocument || files.length === 0) return; try { - const targetPage = editedDocument.pages.find( + const targetPage = workingDocument.pages.find( (p) => p.pageNumber === insertAfterPage ); if (!targetPage) return; @@ -342,12 +346,12 @@ export const usePageEditorCommands = ({ } if (newPages.length > 0) { - const targetIndex = editedDocument.pages.findIndex( + const targetIndex = workingDocument.pages.findIndex( (p) => p.id === targetPage.id ); if (targetIndex >= 0) { - const updatedPages = [...editedDocument.pages]; + const updatedPages = [...workingDocument.pages]; updatedPages.splice(targetIndex + 1, 0, ...newPages); updatedPages.forEach((page, index) => { @@ -355,7 +359,7 @@ export const usePageEditorCommands = ({ }); setEditedDocument({ - ...editedDocument, + ...workingDocument, pages: updatedPages, }); @@ -367,7 +371,7 @@ export const usePageEditorCommands = ({ } }, [ - editedDocument, + getEditedDocument, actions, selectors, updateFileOrderFromPages, @@ -391,7 +395,7 @@ export const usePageEditorCommands = ({ sourcePageNumber, targetIndex, selectedPages, - () => displayDocument, + getEditedDocument, setEditedDocument, (newPages) => updateFileOrderFromPages(newPages) ); @@ -399,6 +403,7 @@ export const usePageEditorCommands = ({ }, [ displayDocument, + getEditedDocument, executeCommandWithTracking, getPageNumbersFromIds, setEditedDocument, diff --git a/frontend/src/core/components/shared/AppConfigModal.tsx b/frontend/src/core/components/shared/AppConfigModal.tsx index 97f1ee295..2cece8aaf 100644 --- a/frontend/src/core/components/shared/AppConfigModal.tsx +++ b/frontend/src/core/components/shared/AppConfigModal.tsx @@ -68,8 +68,6 @@ const AppConfigModal: React.FC = ({ opened, onClose }) => { const isAdmin = config?.isAdmin ?? false; const runningEE = config?.runningEE ?? false; - console.log('[AppConfigModal] Config:', { isAdmin, runningEE, fullConfig: config }); - // Left navigation structure and icons const configNavSections = useMemo(() => createConfigNavSections( diff --git a/frontend/src/core/types/fileContext.ts b/frontend/src/core/types/fileContext.ts index 3ec4380cc..ed2734039 100644 --- a/frontend/src/core/types/fileContext.ts +++ b/frontend/src/core/types/fileContext.ts @@ -89,9 +89,18 @@ export function isStirlingFile(file: File): file is StirlingFile { // Create a StirlingFile from a regular File object export function createStirlingFile(file: File, id?: FileId): StirlingFile { - // Check if file is already a StirlingFile to avoid property redefinition + // If the file already has Stirling metadata and we aren't trying to override it, + // return as–is. When a new id is requested we clone the File so we can embed + // the fresh identifier without mutating the original object. if (isStirlingFile(file)) { - return file; // Already has fileId and quickKey properties + if (!id || file.fileId === id) { + return file; + } + + file = new File([file], file.name, { + type: file.type, + lastModified: file.lastModified, + }); } const fileId = id || createFileId();