diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index 44a4f0271..3884fdaf5 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -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} /> diff --git a/frontend/src/components/pageEditor/BulkSelectionPanel.tsx b/frontend/src/components/pageEditor/BulkSelectionPanel.tsx index b9ebb8d2c..cc9b8d5f5 100644 --- a/frontend/src/components/pageEditor/BulkSelectionPanel.tsx +++ b/frontend/src/components/pageEditor/BulkSelectionPanel.tsx @@ -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 - {selectedPages.length > 0 && ( + {selectedPageIds.length > 0 && ( - 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(', ') : ''}) )} diff --git a/frontend/src/components/pageEditor/DragDropGrid.tsx b/frontend/src/components/pageEditor/DragDropGrid.tsx index 8928e1a34..da1b1658f 100644 --- a/frontend/src/components/pageEditor/DragDropGrid.tsx +++ b/frontend/src/components/pageEditor/DragDropGrid.tsx @@ -12,10 +12,10 @@ interface DragDropItem { interface DragDropGridProps { 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>) => React.ReactNode; renderSplitMarker?: (item: T, index: number) => React.ReactNode; } diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index 45c0e9717..69818d28d 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -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; - 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(); @@ -152,17 +128,40 @@ 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 | 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 */} ; @@ -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 = ({ 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 = ({ 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 = ({ >; - 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 = ({ index, totalPages, originalFile, - selectedPages, + selectedPageIds, selectionMode, movingPage, isAnimating, @@ -139,7 +139,7 @@ const PageThumbnail: React.FC = ({ getInitialData: () => ({ pageNumber: page.pageNumber, pageId: page.id, - selectedPages: [page.pageNumber] + selectedPageIds: [page.id] }), onDragStart: () => { setIsDragging(true); @@ -185,7 +185,7 @@ const PageThumbnail: React.FC = ({ (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 = ({ // 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 = ({ }} onMouseDown={(e) => { e.stopPropagation(); - onTogglePage(page.pageNumber); + onTogglePage(page.id); }} onMouseUp={(e) => e.stopPropagation()} onDragStart={(e) => { @@ -332,7 +332,7 @@ const PageThumbnail: React.FC = ({ }} > { // Selection is handled by container mouseDown }} diff --git a/frontend/src/components/pageEditor/commands/pageCommands.ts b/frontend/src/components/pageEditor/commands/pageCommands.ts index 1b7cb0932..4c033696e 100644 --- a/frontend/src/components/pageEditor/commands/pageCommands.ts +++ b/frontend/src/components/pageEditor/commands/pageCommands.ts @@ -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 { diff --git a/frontend/src/components/pageEditor/hooks/usePageEditorState.ts b/frontend/src/components/pageEditor/hooks/usePageEditorState.ts index 18b0adafb..203ff8975 100644 --- a/frontend/src/components/pageEditor/hooks/usePageEditorState.ts +++ b/frontend/src/components/pageEditor/hooks/usePageEditorState.ts @@ -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) => 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([]); + const [selectedPageIds, setSelectedPageIds] = useState([]); // Animation state const [movingPage, setMovingPage] = useState(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, diff --git a/frontend/src/components/shared/RightRail.tsx b/frontend/src/components/shared/RightRail.tsx index c4b0326b9..614a60cf3 100644 --- a/frontend/src/components/shared/RightRail.tsx +++ b/frontend/src/components/shared/RightRail.tsx @@ -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() { @@ -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'} > @@ -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'} > diff --git a/frontend/src/hooks/useRainbowTheme.ts b/frontend/src/hooks/useRainbowTheme.ts index 8b272b883..449b07c61 100644 --- a/frontend/src/hooks/useRainbowTheme.ts +++ b/frontend/src/hooks/useRainbowTheme.ts @@ -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(() => { @@ -37,11 +35,11 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb // Save theme to localStorage whenever it changes useEffect(() => { localStorage.setItem('stirling-theme', themeMode); - + // Apply rainbow class to body if in rainbow mode if (themeMode === 'rainbow') { document.body.classList.add('rainbow-mode-active'); - + // Show easter egg notification showRainbowNotification(); } else { @@ -79,7 +77,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb pointer-events: none; transition: opacity 0.3s ease; `; - + document.body.appendChild(notification); // Auto-remove notification after 3 seconds @@ -123,7 +121,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb pointer-events: none; transition: opacity 0.3s ease; `; - + document.body.appendChild(notification); // Auto-remove notification after 2 seconds @@ -146,7 +144,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb } const currentTime = Date.now(); - + // Simple exit from rainbow mode with single click (after cooldown period) if (themeMode === 'rainbow') { setThemeMode('light'); @@ -154,7 +152,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb showExitNotification(); return; } - + // Reset counter if too much time has passed (2.5 seconds) if (currentTime - lastToggleTime.current > 2500) { toggleCount.current = 1; @@ -164,18 +162,18 @@ 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!'); - + // Disable toggle for 3 seconds setIsToggleDisabled(true); setTimeout(() => { setIsToggleDisabled(false); console.log('🌈 Theme toggle re-enabled! Click once to exit rainbow mode.'); }, 3000); - + // Reset counter toggleCount.current = 0; return; @@ -205,4 +203,4 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb activateRainbow, deactivateRainbow, }; -} +} \ No newline at end of file diff --git a/frontend/src/tools/Compress.tsx b/frontend/src/tools/Compress.tsx index 4dbd3d985..91dd89e86 100644 --- a/frontend/src/tools/Compress.tsx +++ b/frontend/src/tools/Compress.tsx @@ -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; diff --git a/frontend/src/tools/Split.tsx b/frontend/src/tools/Split.tsx index 73ebdff17..06c7743fd 100644 --- a/frontend/src/tools/Split.tsx +++ b/frontend/src/tools/Split.tsx @@ -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; diff --git a/frontend/src/types/pageEditor.ts b/frontend/src/types/pageEditor.ts index 21f803021..a890190f8 100644 --- a/frontend/src/types/pageEditor.ts +++ b/frontend/src/types/pageEditor.ts @@ -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; totalPages: number; }