mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-30 20:06:30 +01:00
Fix various bugs
This commit is contained in:
parent
7c87a108c5
commit
ae7dbda91d
@ -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,
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user