Fix various bugs

This commit is contained in:
Reece 2025-11-11 14:07:58 +00:00
parent 7c87a108c5
commit ae7dbda91d
6 changed files with 80 additions and 44 deletions

View File

@ -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,

View File

@ -80,6 +80,8 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
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<PageThumbnailProps> = ({
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<PageThumbnailProps> = ({
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<PageThumbnailProps> = ({
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<PageThumbnailProps> = ({
icon: <ContentCutIcon style={{ fontSize: 20 }} />,
label: 'Split After',
onClick: handleSplit,
hidden: index >= totalPages - 1,
hidden: pageIndex >= totalPages - 1,
},
{
id: 'insert',
@ -338,7 +343,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
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 (
<div
@ -444,6 +449,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
) : thumbnailUrl ? (
<PrivateContent>
<img
className="ph-no-capture"
src={thumbnailUrl}
alt={`Page ${page.pageNumber}`}
draggable={false}

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { FileId } from "@app/types/file";
import { PDFDocument, PDFPage } from "@app/types/pageEditor";
@ -21,6 +21,7 @@ export const useEditedDocumentState = ({
updateCurrentPages,
}: UseEditedDocumentStateParams) => {
const [editedDocument, setEditedDocument] = useState<PDFDocument | null>(null);
const editedDocumentRef = useRef<PDFDocument | null>(null);
const pagePositionCacheRef = useRef<Map<string, number>>(new Map());
const pageNeighborCacheRef = useRef<Map<string, string | null>>(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,
};
};

View File

@ -20,7 +20,7 @@ type FileSelectors = ReturnType<typeof useFileState>["selectors"];
interface UsePageEditorCommandsParams {
displayDocument: PDFDocument | null;
editedDocument: PDFDocument | null;
getEditedDocument: () => PDFDocument | null;
setEditedDocument: React.Dispatch<React.SetStateAction<PDFDocument | null>>;
splitPositions: Set<number>;
setSplitPositions: React.Dispatch<React.SetStateAction<Set<number>>>;
@ -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,

View File

@ -68,8 +68,6 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({ 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(

View File

@ -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 asis. 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();