Feature/v2/page editor selection persistance (#4306)

Fixed page editor selection persistance
Fixed delete sleected
Fixed display issue on some tools on reset settings call

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
Reece Browne 2025-08-26 17:26:30 +01:00 committed by GitHub
parent 47ccb6a6ed
commit 68d59fd377
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 192 additions and 171 deletions

View File

@ -117,7 +117,8 @@ export default function Workbench() {
onExportAll={pageEditorFunctions.onExportAll}
exportLoading={pageEditorFunctions.exportLoading}
selectionMode={pageEditorFunctions.selectionMode}
selectedPages={pageEditorFunctions.selectedPages}
selectedPageIds={pageEditorFunctions.selectedPageIds}
displayDocument={pageEditorFunctions.displayDocument}
splitPositions={pageEditorFunctions.splitPositions}
totalPages={pageEditorFunctions.totalPages}
/>

View File

@ -4,14 +4,16 @@ import { Group, TextInput, Button, Text } from '@mantine/core';
interface BulkSelectionPanelProps {
csvInput: string;
setCsvInput: (value: string) => void;
selectedPages: number[];
selectedPageIds: string[];
displayDocument?: { pages: { id: string; pageNumber: number }[] };
onUpdatePagesFromCSV: () => void;
}
const BulkSelectionPanel = ({
csvInput,
setCsvInput,
selectedPages,
selectedPageIds,
displayDocument,
onUpdatePagesFromCSV,
}: BulkSelectionPanelProps) => {
return (
@ -30,9 +32,12 @@ const BulkSelectionPanel = ({
Apply
</Button>
</Group>
{selectedPages.length > 0 && (
{selectedPageIds.length > 0 && (
<Text size="sm" c="dimmed" mt="sm">
Selected: {selectedPages.length} pages
Selected: {selectedPageIds.length} pages ({displayDocument ? selectedPageIds.map(id => {
const page = displayDocument.pages.find(p => p.id === id);
return page?.pageNumber || 0;
}).filter(n => n > 0).join(', ') : ''})
</Text>
)}
</>

View File

@ -12,10 +12,10 @@ interface DragDropItem {
interface DragDropGridProps<T extends DragDropItem> {
items: T[];
selectedItems: number[];
selectedItems: string[];
selectionMode: boolean;
isAnimating: boolean;
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => void;
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
renderItem: (item: T, index: number, refs: React.MutableRefObject<Map<string, HTMLDivElement>>) => React.ReactNode;
renderSplitMarker?: (item: T, index: number) => React.ReactNode;
}

View File

@ -7,7 +7,7 @@ import {
import { useTranslation } from "react-i18next";
import { useFileState, useFileActions, useCurrentFile, useFileSelection } from "../../contexts/FileContext";
import { ModeType } from "../../contexts/NavigationContext";
import { PDFDocument, PDFPage } from "../../types/pageEditor";
import { PDFDocument, PDFPage, PageEditorFunctions } from "../../types/pageEditor";
import { ProcessedFile as EnhancedProcessedFile } from "../../types/processing";
import { pdfExportService } from "../../services/pdfExportService";
import { documentManipulationService } from "../../services/documentManipulationService";
@ -36,31 +36,7 @@ import { usePageDocument } from './hooks/usePageDocument';
import { usePageEditorState } from './hooks/usePageEditorState';
export interface PageEditorProps {
onFunctionsReady?: (functions: {
handleUndo: () => void;
handleRedo: () => void;
canUndo: boolean;
canRedo: boolean;
handleRotate: (direction: 'left' | 'right') => void;
handleDelete: () => void;
handleSplit: () => void;
handleSplitAll: () => void;
handlePageBreak: () => void;
handlePageBreakAll: () => void;
handleSelectAll: () => void;
handleDeselectAll: () => void;
handleSetSelectedPages: (pageNumbers: number[]) => void;
showExportPreview: (selectedOnly: boolean) => void;
onExportSelected: () => void;
onExportAll: () => void;
applyChanges: () => void;
exportLoading: boolean;
selectionMode: boolean;
selectedPages: number[];
splitPositions: Set<number>;
totalPages: number;
closePdf: () => void;
}) => void;
onFunctionsReady?: (functions: PageEditorFunctions) => void;
}
const PageEditor = ({
@ -100,8 +76,8 @@ const PageEditor = ({
// UI state management
const {
selectionMode, selectedPageNumbers, movingPage, isAnimating, splitPositions, exportLoading,
setSelectionMode, setSelectedPageNumbers, setMovingPage, setIsAnimating, setSplitPositions, setExportLoading,
selectionMode, selectedPageIds, movingPage, isAnimating, splitPositions, exportLoading,
setSelectionMode, setSelectedPageIds, setMovingPage, setIsAnimating, setSplitPositions, setExportLoading,
togglePage, toggleSelectAll, animateReorder
} = usePageEditorState();
@ -153,16 +129,39 @@ const PageEditor = ({
// Interface functions for parent component
const displayDocument = editedDocument || mergedPdfDocument;
// Utility functions to convert between page IDs and page numbers
const getPageNumbersFromIds = useCallback((pageIds: string[]): number[] => {
if (!displayDocument) return [];
return pageIds.map(id => {
const page = displayDocument.pages.find(p => p.id === id);
return page?.pageNumber || 0;
}).filter(num => num > 0);
}, [displayDocument]);
const getPageIdsFromNumbers = useCallback((pageNumbers: number[]): string[] => {
if (!displayDocument) return [];
return pageNumbers.map(num => {
const page = displayDocument.pages.find(p => p.pageNumber === num);
return page?.id || '';
}).filter(id => id !== '');
}, [displayDocument]);
// Convert selectedPageIds to numbers for components that still need numbers
const selectedPageNumbers = useMemo(() =>
getPageNumbersFromIds(selectedPageIds),
[selectedPageIds, getPageNumbersFromIds]
);
// Select all pages by default when document initially loads
const hasInitializedSelection = useRef(false);
useEffect(() => {
if (displayDocument && displayDocument.pages.length > 0 && !hasInitializedSelection.current) {
const allPageNumbers = Array.from({ length: displayDocument.pages.length }, (_, i) => i + 1);
setSelectedPageNumbers(allPageNumbers);
const allPageIds = displayDocument.pages.map(p => p.id);
setSelectedPageIds(allPageIds);
setSelectionMode(true);
hasInitializedSelection.current = true;
}
}, [displayDocument, setSelectedPageNumbers, setSelectionMode]);
}, [displayDocument, setSelectedPageIds, setSelectionMode]);
// DOM-first command handlers
const handleRotatePages = useCallback((pageIds: string[], rotation: number) => {
@ -192,15 +191,18 @@ const PageEditor = ({
pagesToDelete,
() => displayDocument,
setEditedDocument,
setSelectedPageNumbers,
(pageNumbers: number[]) => {
const pageIds = getPageIdsFromNumbers(pageNumbers);
setSelectedPageIds(pageIds);
},
() => splitPositions,
setSplitPositions,
() => selectedPageNumbers
() => getPageNumbersFromIds(selectedPageIds)
);
undoManagerRef.current.executeCommand(deleteCommand);
}
}
}), [displayDocument, splitPositions, selectedPageNumbers]);
}), [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds]);
const createSplitCommand = useCallback((position: number) => ({
execute: () => {
@ -230,30 +232,32 @@ const PageEditor = ({
}, []);
const handleRotate = useCallback((direction: 'left' | 'right') => {
if (!displayDocument || selectedPageNumbers.length === 0) return;
if (!displayDocument || selectedPageIds.length === 0) return;
const rotation = direction === 'left' ? -90 : 90;
const pagesToRotate = selectedPageNumbers.map(pageNum => {
const page = displayDocument.pages.find(p => p.pageNumber === pageNum);
return page?.id || '';
}).filter(id => id);
handleRotatePages(pagesToRotate, rotation);
}, [displayDocument, selectedPageNumbers, handleRotatePages]);
handleRotatePages(selectedPageIds, rotation);
}, [displayDocument, selectedPageIds, handleRotatePages]);
const handleDelete = useCallback(() => {
if (!displayDocument || selectedPageNumbers.length === 0) return;
if (!displayDocument || selectedPageIds.length === 0) return;
// Convert selected page IDs to page numbers for the command
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
const deleteCommand = new DeletePagesCommand(
selectedPageNumbers,
() => displayDocument,
setEditedDocument,
setSelectedPageNumbers,
(pageNumbers: number[]) => {
const pageIds = getPageIdsFromNumbers(pageNumbers);
setSelectedPageIds(pageIds);
},
() => splitPositions,
setSplitPositions,
() => selectedPageNumbers
);
undoManagerRef.current.executeCommand(deleteCommand);
}, [selectedPageNumbers, displayDocument, splitPositions]);
}, [selectedPageIds, displayDocument, splitPositions, getPageNumbersFromIds, getPageIdsFromNumbers]);
const handleDeletePage = useCallback((pageNumber: number) => {
if (!displayDocument) return;
@ -262,18 +266,22 @@ const PageEditor = ({
[pageNumber],
() => displayDocument,
setEditedDocument,
setSelectedPageNumbers,
(pageNumbers: number[]) => {
const pageIds = getPageIdsFromNumbers(pageNumbers);
setSelectedPageIds(pageIds);
},
() => splitPositions,
setSplitPositions,
() => selectedPageNumbers
() => getPageNumbersFromIds(selectedPageIds)
);
undoManagerRef.current.executeCommand(deleteCommand);
}, [displayDocument, splitPositions, selectedPageNumbers]);
}, [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds]);
const handleSplit = useCallback(() => {
if (!displayDocument || selectedPageNumbers.length === 0) return;
if (!displayDocument || selectedPageIds.length === 0) return;
// Convert selected page numbers to split positions (0-based indices)
// Convert selected page IDs to page numbers, then to split positions (0-based indices)
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
const selectedPositions: number[] = [];
selectedPageNumbers.forEach(pageNum => {
const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum);
@ -314,12 +322,13 @@ const PageEditor = ({
};
undoManagerRef.current.executeCommand(smartSplitCommand);
}, [selectedPageNumbers, displayDocument, splitPositions, setSplitPositions]);
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds]);
const handleSplitAll = useCallback(() => {
if (!displayDocument || selectedPageNumbers.length === 0) return;
if (!displayDocument || selectedPageIds.length === 0) return;
// Convert selected page numbers to split positions (0-based indices)
// Convert selected page IDs to page numbers, then to split positions (0-based indices)
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
const selectedPositions: number[] = [];
selectedPageNumbers.forEach(pageNum => {
const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum);
@ -359,31 +368,35 @@ const PageEditor = ({
};
undoManagerRef.current.executeCommand(smartSplitCommand);
}, [selectedPageNumbers, displayDocument, splitPositions, setSplitPositions]);
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds]);
const handlePageBreak = useCallback(() => {
if (!displayDocument || selectedPageNumbers.length === 0) return;
if (!displayDocument || selectedPageIds.length === 0) return;
// Convert selected page IDs to page numbers for the command
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
const pageBreakCommand = new PageBreakCommand(
selectedPageNumbers,
() => displayDocument,
setEditedDocument,
setSelectedPageNumbers
setEditedDocument
);
undoManagerRef.current.executeCommand(pageBreakCommand);
}, [selectedPageNumbers, displayDocument]);
}, [selectedPageIds, displayDocument, getPageNumbersFromIds]);
const handlePageBreakAll = useCallback(() => {
if (!displayDocument || selectedPageNumbers.length === 0) return;
if (!displayDocument || selectedPageIds.length === 0) return;
// Convert selected page IDs to page numbers for the command
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
const pageBreakCommand = new PageBreakCommand(
selectedPageNumbers,
() => displayDocument,
setEditedDocument,
setSelectedPageNumbers
setEditedDocument
);
undoManagerRef.current.executeCommand(pageBreakCommand);
}, [selectedPageNumbers, displayDocument]);
}, [selectedPageIds, displayDocument, getPageNumbersFromIds]);
const handleInsertFiles = useCallback(async (files: File[], insertAfterPage: number) => {
if (!displayDocument || files.length === 0) return;
@ -400,21 +413,25 @@ const PageEditor = ({
const handleSelectAll = useCallback(() => {
if (!displayDocument) return;
const allPageNumbers = Array.from({ length: displayDocument.pages.length }, (_, i) => i + 1);
setSelectedPageNumbers(allPageNumbers);
}, [displayDocument]);
const allPageIds = displayDocument.pages.map(p => p.id);
toggleSelectAll(allPageIds);
}, [displayDocument, toggleSelectAll]);
const handleDeselectAll = useCallback(() => {
setSelectedPageNumbers([]);
}, []);
setSelectedPageIds([]);
}, [setSelectedPageIds]);
const handleSetSelectedPages = useCallback((pageNumbers: number[]) => {
setSelectedPageNumbers(pageNumbers);
}, []);
const pageIds = getPageIdsFromNumbers(pageNumbers);
setSelectedPageIds(pageIds);
}, [getPageIdsFromNumbers, setSelectedPageIds]);
const handleReorderPages = useCallback((sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => {
const handleReorderPages = useCallback((sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => {
if (!displayDocument) return;
// Convert selectedPageIds to page numbers for the reorder command
const selectedPages = selectedPageIds ? getPageNumbersFromIds(selectedPageIds) : undefined;
const reorderCommand = new ReorderPagesCommand(
sourcePageNumber,
targetIndex,
@ -423,7 +440,7 @@ const PageEditor = ({
setEditedDocument
);
undoManagerRef.current.executeCommand(reorderCommand);
}, [displayDocument]);
}, [displayDocument, getPageNumbersFromIds]);
// Helper function to collect source files for multi-file export
const getSourceFiles = useCallback((): Map<string, File> | null => {
@ -466,7 +483,7 @@ const PageEditor = ({
}, [activeFileIds, selectors, displayDocument]);
const onExportSelected = useCallback(async () => {
if (!displayDocument || selectedPageNumbers.length === 0) return;
if (!displayDocument || selectedPageIds.length === 0) return;
setExportLoading(true);
try {
@ -480,11 +497,11 @@ const PageEditor = ({
// For selected pages export, we work with the first document (or single document)
const documentWithDOMState = Array.isArray(processedDocuments) ? processedDocuments[0] : processedDocuments;
// Step 2: Convert selected page numbers to page IDs from the document with DOM state
const selectedPageIds = selectedPageNumbers.map(pageNum => {
const page = documentWithDOMState.pages.find(p => p.pageNumber === pageNum);
return page?.id || '';
}).filter(id => id);
// Step 2: Use the already selected page IDs
// Filter to only include IDs that exist in the document with DOM state
const validSelectedPageIds = selectedPageIds.filter(pageId =>
documentWithDOMState.pages.some(p => p.id === pageId)
);
// Step 3: Export with pdfExportService
@ -494,12 +511,12 @@ const PageEditor = ({
? await pdfExportService.exportPDFMultiFile(
documentWithDOMState,
sourceFiles,
selectedPageIds,
validSelectedPageIds,
{ selectedOnly: true, filename: exportFilename }
)
: await pdfExportService.exportPDF(
documentWithDOMState,
selectedPageIds,
validSelectedPageIds,
{ selectedOnly: true, filename: exportFilename }
);
@ -511,7 +528,7 @@ const PageEditor = ({
console.error('Export failed:', error);
setExportLoading(false);
}
}, [displayDocument, selectedPageNumbers, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]);
}, [displayDocument, selectedPageIds, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]);
const onExportAll = useCallback(async () => {
if (!displayDocument) return;
@ -606,7 +623,7 @@ const PageEditor = ({
const closePdf = useCallback(() => {
actions.clearAllFiles();
undoManagerRef.current.clear();
setSelectedPageNumbers([]);
setSelectedPageIds([]);
setSelectionMode(false);
}, [actions]);
@ -646,7 +663,8 @@ const PageEditor = ({
applyChanges,
exportLoading,
selectionMode,
selectedPages: selectedPageNumbers,
selectedPageIds,
displayDocument: displayDocument || undefined,
splitPositions,
totalPages: displayDocument?.pages.length || 0,
closePdf,
@ -655,7 +673,7 @@ const PageEditor = ({
}, [
onFunctionsReady, handleUndo, handleRedo, canUndo, canRedo, handleRotate, handleDelete, handleSplit, handleSplitAll,
handlePageBreak, handlePageBreakAll, handleSelectAll, handleDeselectAll, handleSetSelectedPages, handleExportPreview, onExportSelected, onExportAll, applyChanges, exportLoading,
selectionMode, selectedPageNumbers, splitPositions, displayDocument?.pages.length, closePdf
selectionMode, selectedPageIds, splitPositions, displayDocument?.pages.length, closePdf
]);
// Display all pages - use edited or original document
@ -745,7 +763,7 @@ const PageEditor = ({
{/* Pages Grid */}
<DragDropGrid
items={displayedPages}
selectedItems={selectedPageNumbers}
selectedItems={selectedPageIds}
selectionMode={selectionMode}
isAnimating={isAnimating}
onReorderPages={handleReorderPages}
@ -756,7 +774,7 @@ const PageEditor = ({
index={index}
totalPages={displayDocument.pages.length}
originalFile={(page as any).originalFileId ? selectors.getFile((page as any).originalFileId) : undefined}
selectedPages={selectedPageNumbers}
selectedPageIds={selectedPageIds}
selectionMode={selectionMode}
movingPage={movingPage}
isAnimating={isAnimating}

View File

@ -37,7 +37,8 @@ interface PageEditorControlsProps {
// Selection state
selectionMode: boolean;
selectedPages: number[];
selectedPageIds: string[];
displayDocument?: { pages: { id: string; pageNumber: number }[] };
// Split state (for tooltip logic)
splitPositions?: Set<number>;
@ -59,18 +60,23 @@ const PageEditorControls = ({
onExportAll,
exportLoading,
selectionMode,
selectedPages,
selectedPageIds,
displayDocument,
splitPositions,
totalPages
}: PageEditorControlsProps) => {
// Calculate split tooltip text using smart toggle logic
const getSplitTooltip = () => {
if (!splitPositions || !totalPages || selectedPages.length === 0) {
if (!splitPositions || !totalPages || selectedPageIds.length === 0) {
return "Split Selected";
}
// Convert selected pages to split positions (same logic as handleSplit)
const selectedSplitPositions = selectedPages.map(pageNum => pageNum - 1).filter(pos => pos < totalPages - 1);
const selectedPageNumbers = displayDocument ? selectedPageIds.map(id => {
const page = displayDocument.pages.find(p => p.id === id);
return page?.pageNumber || 0;
}).filter(num => num > 0) : [];
const selectedSplitPositions = selectedPageNumbers.map(pageNum => pageNum - 1).filter(pos => pos < totalPages - 1);
if (selectedSplitPositions.length === 0) {
return "Split Selected";
@ -97,8 +103,8 @@ const PageEditorControls = ({
// Calculate page break tooltip text
const getPageBreakTooltip = () => {
return selectedPages.length > 0
? `Insert ${selectedPages.length} Page Break${selectedPages.length > 1 ? 's' : ''}`
return selectedPageIds.length > 0
? `Insert ${selectedPageIds.length} Page Break${selectedPageIds.length > 1 ? 's' : ''}`
: "Insert Page Breaks";
};
@ -157,7 +163,7 @@ const PageEditorControls = ({
<Tooltip label="Rotate Selected Left">
<ActionIcon
onClick={() => onRotate('left')}
disabled={selectedPages.length === 0}
disabled={selectedPageIds.length === 0}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"
@ -169,7 +175,7 @@ const PageEditorControls = ({
<Tooltip label="Rotate Selected Right">
<ActionIcon
onClick={() => onRotate('right')}
disabled={selectedPages.length === 0}
disabled={selectedPageIds.length === 0}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"
@ -181,7 +187,7 @@ const PageEditorControls = ({
<Tooltip label="Delete Selected">
<ActionIcon
onClick={onDelete}
disabled={selectedPages.length === 0}
disabled={selectedPageIds.length === 0}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"
@ -193,7 +199,7 @@ const PageEditorControls = ({
<Tooltip label={getSplitTooltip()}>
<ActionIcon
onClick={onSplit}
disabled={selectedPages.length === 0}
disabled={selectedPageIds.length === 0}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"
@ -205,7 +211,7 @@ const PageEditorControls = ({
<Tooltip label={getPageBreakTooltip()}>
<ActionIcon
onClick={onPageBreak}
disabled={selectedPages.length === 0}
disabled={selectedPageIds.length === 0}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"

View File

@ -19,13 +19,13 @@ interface PageThumbnailProps {
index: number;
totalPages: number;
originalFile?: File;
selectedPages: number[];
selectedPageIds: string[];
selectionMode: boolean;
movingPage: number | null;
isAnimating: boolean;
pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => void;
onTogglePage: (pageNumber: number) => void;
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
onTogglePage: (pageId: string) => void;
onAnimateReorder: () => void;
onExecuteCommand: (command: { execute: () => void }) => void;
onSetStatus: (status: string) => void;
@ -45,7 +45,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
index,
totalPages,
originalFile,
selectedPages,
selectedPageIds,
selectionMode,
movingPage,
isAnimating,
@ -139,7 +139,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
getInitialData: () => ({
pageNumber: page.pageNumber,
pageId: page.id,
selectedPages: [page.pageNumber]
selectedPageIds: [page.id]
}),
onDragStart: () => {
setIsDragging(true);
@ -185,7 +185,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
(dragElementRef.current as any).__dragCleanup();
}
}
}, [page.id, page.pageNumber, pageRefs, selectionMode, selectedPages, pdfDocument.pages, onReorderPages]);
}, [page.id, page.pageNumber, pageRefs, selectionMode, selectedPageIds, pdfDocument.pages, onReorderPages]);
// DOM command handlers
const handleRotateLeft = useCallback((e: React.MouseEvent) => {
@ -263,12 +263,12 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
// If mouse moved less than 5 pixels, consider it a click (not a drag)
if (distance < 5 && !isDragging) {
onTogglePage(page.pageNumber);
onTogglePage(page.id);
}
setIsMouseDown(false);
setMouseStartPos(null);
}, [isMouseDown, mouseStartPos, isDragging, page.pageNumber, onTogglePage]);
}, [isMouseDown, mouseStartPos, isDragging, page.id, onTogglePage]);
const handleMouseLeave = useCallback(() => {
setIsMouseDown(false);
@ -323,7 +323,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
}}
onMouseDown={(e) => {
e.stopPropagation();
onTogglePage(page.pageNumber);
onTogglePage(page.id);
}}
onMouseUp={(e) => e.stopPropagation()}
onDragStart={(e) => {
@ -332,7 +332,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
}}
>
<Checkbox
checked={Array.isArray(selectedPages) ? selectedPages.includes(page.pageNumber) : false}
checked={Array.isArray(selectedPageIds) ? selectedPageIds.includes(page.id) : false}
onChange={() => {
// Selection is handled by container mouseDown
}}

View File

@ -403,8 +403,7 @@ export class PageBreakCommand extends DOMCommand {
constructor(
private selectedPageNumbers: number[],
private getCurrentDocument: () => PDFDocument | null,
private setDocument: (doc: PDFDocument) => void,
private setSelectedPages: (pages: number[]) => void
private setDocument: (doc: PDFDocument) => void
) {
super();
}
@ -455,19 +454,7 @@ export class PageBreakCommand extends DOMCommand {
this.setDocument(updatedDocument);
// Maintain existing selection by mapping original selected pages to their new positions
const updatedSelection: number[] = [];
this.selectedPageNumbers.forEach(originalPageNum => {
// Find the original page by matching the page ID from the original document
const originalPage = this.originalDocument?.pages[originalPageNum - 1];
if (originalPage) {
const foundPage = newPages.find(page => page.id === originalPage.id && !page.isBlankPage);
if (foundPage) {
updatedSelection.push(foundPage.pageNumber);
}
}
});
this.setSelectedPages(updatedSelection);
// No need to maintain selection - page IDs remain stable, so selection persists automatically
}
undo(): void {

View File

@ -3,7 +3,7 @@ import { useState, useCallback } from 'react';
export interface PageEditorState {
// Selection state
selectionMode: boolean;
selectedPageNumbers: number[];
selectedPageIds: string[];
// Animation state
movingPage: number | null;
@ -17,15 +17,15 @@ export interface PageEditorState {
// Actions
setSelectionMode: (mode: boolean) => void;
setSelectedPageNumbers: (pages: number[]) => void;
setSelectedPageIds: (pages: string[]) => void;
setMovingPage: (pageNumber: number | null) => void;
setIsAnimating: (animating: boolean) => void;
setSplitPositions: (positions: Set<number>) => void;
setExportLoading: (loading: boolean) => void;
// Helper functions
togglePage: (pageNumber: number) => void;
toggleSelectAll: (totalPages: number) => void;
togglePage: (pageId: string) => void;
toggleSelectAll: (allPageIds: string[]) => void;
animateReorder: () => void;
}
@ -36,7 +36,7 @@ export interface PageEditorState {
export function usePageEditorState(): PageEditorState {
// Selection state
const [selectionMode, setSelectionMode] = useState(false);
const [selectedPageNumbers, setSelectedPageNumbers] = useState<number[]>([]);
const [selectedPageIds, setSelectedPageIds] = useState<string[]>([]);
// Animation state
const [movingPage, setMovingPage] = useState<number | null>(null);
@ -49,20 +49,19 @@ export function usePageEditorState(): PageEditorState {
const [exportLoading, setExportLoading] = useState(false);
// Helper functions
const togglePage = useCallback((pageNumber: number) => {
setSelectedPageNumbers(prev =>
prev.includes(pageNumber)
? prev.filter(n => n !== pageNumber)
: [...prev, pageNumber]
const togglePage = useCallback((pageId: string) => {
setSelectedPageIds(prev =>
prev.includes(pageId)
? prev.filter(id => id !== pageId)
: [...prev, pageId]
);
}, []);
const toggleSelectAll = useCallback((totalPages: number) => {
if (!totalPages) return;
const toggleSelectAll = useCallback((allPageIds: string[]) => {
if (!allPageIds.length) return;
const allPageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
setSelectedPageNumbers(prev =>
prev.length === allPageNumbers.length ? [] : allPageNumbers
setSelectedPageIds(prev =>
prev.length === allPageIds.length ? [] : allPageIds
);
}, []);
@ -74,7 +73,7 @@ export function usePageEditorState(): PageEditorState {
return {
// State
selectionMode,
selectedPageNumbers,
selectedPageIds,
movingPage,
isAnimating,
splitPositions,
@ -82,7 +81,7 @@ export function usePageEditorState(): PageEditorState {
// Setters
setSelectionMode,
setSelectedPageNumbers,
setSelectedPageIds,
setMovingPage,
setIsAnimating,
setSplitPositions,

View File

@ -47,7 +47,7 @@ export default function RightRail() {
if (currentView === 'pageEditor') {
// Use PageEditor's own state
const totalItems = pageEditorFunctions?.totalPages || 0;
const selectedCount = pageEditorFunctions?.selectedPages?.length || 0;
const selectedCount = pageEditorFunctions?.selectedPageIds?.length || 0;
return { totalItems, selectedCount };
}
@ -147,12 +147,15 @@ export default function RightRail() {
// Sync csvInput with PageEditor's selected pages
useEffect(() => {
const sortedPageNumbers = Array.isArray(pageEditorFunctions?.selectedPages)
? [...pageEditorFunctions.selectedPages].sort((a, b) => a - b)
const sortedPageNumbers = Array.isArray(pageEditorFunctions?.selectedPageIds) && pageEditorFunctions.displayDocument
? pageEditorFunctions.selectedPageIds.map(id => {
const page = pageEditorFunctions.displayDocument!.pages.find(p => p.id === id);
return page?.pageNumber || 0;
}).filter(num => num > 0).sort((a, b) => a - b)
: [];
const newCsvInput = sortedPageNumbers.join(', ');
setCsvInput(newCsvInput);
}, [pageEditorFunctions?.selectedPages]);
}, [pageEditorFunctions?.selectedPageIds]);
// Clear CSV input when files change (use stable signature to avoid ref churn)
useEffect(() => {
@ -262,7 +265,8 @@ export default function RightRail() {
<BulkSelectionPanel
csvInput={csvInput}
setCsvInput={setCsvInput}
selectedPages={Array.isArray(pageEditorFunctions?.selectedPages) ? pageEditorFunctions.selectedPages : []}
selectedPageIds={Array.isArray(pageEditorFunctions?.selectedPageIds) ? pageEditorFunctions.selectedPageIds : []}
displayDocument={pageEditorFunctions?.displayDocument}
onUpdatePagesFromCSV={updatePagesFromCSV}
/>
</div>
@ -284,7 +288,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={() => { pageEditorFunctions?.handleDelete?.(); }}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPages?.length || 0) === 0}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0}
aria-label={typeof t === 'function' ? t('rightRail.deleteSelected', 'Delete Selected Pages') : 'Delete Selected Pages'}
>
<LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />
@ -305,7 +309,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={() => { pageEditorFunctions?.onExportSelected?.(); }}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPages?.length || 0) === 0 || pageEditorFunctions?.exportLoading}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading}
aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'}
>
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />

View File

@ -11,8 +11,6 @@ interface RainbowThemeHook {
deactivateRainbow: () => void;
}
const allowRainbowMode = false; // Override to allow/disallow fun
export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): RainbowThemeHook {
// Get theme from localStorage or use initial
const [themeMode, setThemeMode] = useState<ThemeMode>(() => {
@ -164,7 +162,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
lastToggleTime.current = currentTime;
// Easter egg: Activate rainbow mode after 10 rapid toggles
if (allowRainbowMode && toggleCount.current >= 10) {
if (toggleCount.current >= 10) {
setThemeMode('rainbow');
console.log('🌈 RAINBOW MODE ACTIVATED! 🌈 You found the secret easter egg!');
console.log('🌈 Button will be disabled for 3 seconds, then click once to exit!');

View File

@ -50,9 +50,9 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const handleSettingsReset = () => {
compressOperation.resetResults();
onPreviewFile?.(null);
actions.setMode("compress");
};
onPreviewFile?.(null); };
const hasFiles = selectedFiles.length > 0;
const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null;

View File

@ -28,13 +28,14 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
onPreviewFile?.(null);
}, [splitParams.parameters]);
useEffect(() => {
useEffect(() => {
// Reset results when selected files change (user selected different files)
if (selectedFiles.length > 0) {
splitOperation.resetResults();
onPreviewFile?.(null);
}
}, [selectedFiles]);
const handleSplit = async () => {
try {
await splitOperation.executeOperation(splitParams.parameters, selectedFiles);
@ -56,8 +57,7 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const handleSettingsReset = () => {
splitOperation.resetResults();
onPreviewFile?.(null);
actions.setMode("split");
};
};
const hasFiles = selectedFiles.length > 0;
const hasResults = splitOperation.files.length > 0 || splitOperation.downloadUrl !== null;

View File

@ -56,11 +56,14 @@ export interface PageEditorFunctions {
handleSelectAll: () => void;
handleDeselectAll: () => void;
handleSetSelectedPages: (pageNumbers: number[]) => void;
showExportPreview: (selectedOnly: boolean) => void;
onExportSelected: () => void;
onExportAll: () => void;
applyChanges: () => void;
exportLoading: boolean;
selectionMode: boolean;
selectedPages: number[];
selectedPageIds: string[];
displayDocument?: PDFDocument;
splitPositions: Set<number>;
totalPages: number;
}