diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index a9e20f13e..44a4f0271 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -114,7 +114,6 @@ export default function Workbench() { onSplitAll={pageEditorFunctions.handleSplitAll} onPageBreak={pageEditorFunctions.handlePageBreak} onPageBreakAll={pageEditorFunctions.handlePageBreakAll} - onExportSelected={pageEditorFunctions.onExportSelected} onExportAll={pageEditorFunctions.onExportAll} exportLoading={pageEditorFunctions.exportLoading} selectionMode={pageEditorFunctions.selectionMode} diff --git a/frontend/src/components/pageEditor/PageEditor.module.css b/frontend/src/components/pageEditor/PageEditor.module.css index 8b1c84638..4cbfc05fb 100644 --- a/frontend/src/components/pageEditor/PageEditor.module.css +++ b/frontend/src/components/pageEditor/PageEditor.module.css @@ -16,7 +16,7 @@ } .pageContainer:hover .pageHoverControls { - opacity: 1 !important; + opacity: 0.9 !important; } /* Checkbox container - prevent transform inheritance */ diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index 365ed22a5..dc45a10bd 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -47,6 +47,9 @@ export interface PageEditorProps { handleSplitAll: () => void; handlePageBreak: () => void; handlePageBreakAll: () => void; + handleSelectAll: () => void; + handleDeselectAll: () => void; + handleSetSelectedPages: (pageNumbers: number[]) => void; showExportPreview: (selectedOnly: boolean) => void; onExportSelected: () => void; onExportAll: () => void; @@ -147,10 +150,20 @@ const PageEditor = ({ }; }, []); - // Interface functions for parent component const displayDocument = editedDocument || mergedPdfDocument; + // 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); + setSelectionMode(true); + hasInitializedSelection.current = true; + } + }, [displayDocument, setSelectedPageNumbers, setSelectionMode]); + // DOM-first command handlers const handleRotatePages = useCallback((pageIds: string[], rotation: number) => { const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation); @@ -217,17 +230,15 @@ const PageEditor = ({ }, []); const handleRotate = useCallback((direction: 'left' | 'right') => { - if (!displayDocument) return; + if (!displayDocument || selectedPageNumbers.length === 0) return; const rotation = direction === 'left' ? -90 : 90; - const pagesToRotate = selectionMode && selectedPageNumbers.length > 0 - ? selectedPageNumbers.map(pageNum => { - const page = displayDocument.pages.find(p => p.pageNumber === pageNum); - return page?.id || ''; - }).filter(id => id) - : displayDocument.pages.map(p => p.id); + 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, selectionMode, handleRotatePages]); + }, [displayDocument, selectedPageNumbers, handleRotatePages]); const handleDelete = useCallback(() => { if (!displayDocument || selectedPageNumbers.length === 0) return; @@ -262,6 +273,53 @@ const PageEditor = ({ const handleSplit = useCallback(() => { if (!displayDocument || selectedPageNumbers.length === 0) return; + // Convert selected page numbers to split positions (0-based indices) + const selectedPositions: number[] = []; + selectedPageNumbers.forEach(pageNum => { + const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum); + if (pageIndex !== -1 && pageIndex < displayDocument.pages.length - 1) { + // Only allow splits before the last page + selectedPositions.push(pageIndex); + } + }); + + if (selectedPositions.length === 0) return; + + // Smart toggle logic: follow the majority, default to adding splits if equal + const existingSplitsCount = selectedPositions.filter(pos => splitPositions.has(pos)).length; + const noSplitsCount = selectedPositions.length - existingSplitsCount; + + // Remove splits only if majority already have splits + // If equal (50/50), default to adding splits + const shouldRemoveSplits = existingSplitsCount > noSplitsCount; + + console.log(`Smart split toggle: ${existingSplitsCount} with splits, ${noSplitsCount} without splits → will ${shouldRemoveSplits ? 'remove' : 'add'} splits`); + + const newSplitPositions = new Set(splitPositions); + + if (shouldRemoveSplits) { + // Remove splits from all selected positions + selectedPositions.forEach(pos => newSplitPositions.delete(pos)); + } else { + // Add splits to all selected positions + selectedPositions.forEach(pos => newSplitPositions.add(pos)); + } + + // Create a custom command that sets the final state directly + const smartSplitCommand = { + execute: () => setSplitPositions(newSplitPositions), + undo: () => setSplitPositions(splitPositions), + description: shouldRemoveSplits + ? `Remove ${selectedPositions.length} split(s)` + : `Add ${selectedPositions.length - existingSplitsCount} split(s)` + }; + + undoManagerRef.current.executeCommand(smartSplitCommand); + }, [selectedPageNumbers, displayDocument, splitPositions, setSplitPositions]); + + const handleSplitAll = useCallback(() => { + if (!displayDocument || selectedPageNumbers.length === 0) return; + console.log('Toggle split markers at selected page positions:', selectedPageNumbers); // Convert page numbers to positions (0-based indices) @@ -284,17 +342,6 @@ const PageEditor = ({ } }, [selectedPageNumbers, displayDocument, splitPositions]); - const handleSplitAll = useCallback(() => { - if (!displayDocument) return; - - const splitAllCommand = new SplitAllCommand( - displayDocument.pages.length, - () => splitPositions, - setSplitPositions - ); - undoManagerRef.current.executeCommand(splitAllCommand); - }, [displayDocument, splitPositions]); - const handlePageBreak = useCallback(() => { if (!displayDocument || selectedPageNumbers.length === 0) return; @@ -310,15 +357,18 @@ const PageEditor = ({ }, [selectedPageNumbers, displayDocument]); const handlePageBreakAll = useCallback(() => { - if (!displayDocument) return; + if (!displayDocument || selectedPageNumbers.length === 0) return; - const pageBreakAllCommand = new BulkPageBreakCommand( + console.log('Insert page breaks after selected pages:', selectedPageNumbers); + + const pageBreakCommand = new PageBreakCommand( + selectedPageNumbers, () => displayDocument, setEditedDocument, setSelectedPageNumbers ); - undoManagerRef.current.executeCommand(pageBreakAllCommand); - }, [displayDocument]); + undoManagerRef.current.executeCommand(pageBreakCommand); + }, [selectedPageNumbers, displayDocument]); const handleSelectAll = useCallback(() => { if (!displayDocument) return; diff --git a/frontend/src/components/pageEditor/PageEditorControls.tsx b/frontend/src/components/pageEditor/PageEditorControls.tsx index 070165cdd..3789f715a 100644 --- a/frontend/src/components/pageEditor/PageEditorControls.tsx +++ b/frontend/src/components/pageEditor/PageEditorControls.tsx @@ -31,8 +31,7 @@ interface PageEditorControlsProps { onPageBreak: () => void; onPageBreakAll: () => void; - // Export functions - onExportSelected: () => void; + // Export functions (moved to right rail) onExportAll: () => void; exportLoading: boolean; @@ -57,7 +56,6 @@ const PageEditorControls = ({ onSplitAll, onPageBreak, onPageBreakAll, - onExportSelected, onExportAll, exportLoading, selectionMode, @@ -65,32 +63,43 @@ const PageEditorControls = ({ splitPositions, totalPages }: PageEditorControlsProps) => { - // Calculate split all tooltip text - const getSplitAllTooltip = () => { - if (selectionMode) { + // Calculate split tooltip text using smart toggle logic + const getSplitTooltip = () => { + if (!splitPositions || !totalPages || selectedPages.length === 0) { return "Split Selected"; } - if (!splitPositions || !totalPages) { - return "Split All"; + // Convert selected pages to split positions (same logic as handleSplit) + const selectedSplitPositions = selectedPages.map(pageNum => pageNum - 1).filter(pos => pos < totalPages - 1); + + if (selectedSplitPositions.length === 0) { + return "Split Selected"; } - // Check if all possible splits are active - const allPossibleSplitsCount = totalPages - 1; - const hasAllSplits = splitPositions.size === allPossibleSplitsCount && - Array.from({length: allPossibleSplitsCount}, (_, i) => i).every(pos => splitPositions.has(pos)); + // Smart toggle logic: follow the majority, default to adding splits if equal + const existingSplitsCount = selectedSplitPositions.filter(pos => splitPositions.has(pos)).length; + const noSplitsCount = selectedSplitPositions.length - existingSplitsCount; - return hasAllSplits ? "Remove All Splits" : "Split All"; + // Remove splits only if majority already have splits + // If equal (50/50), default to adding splits + const willRemoveSplits = existingSplitsCount > noSplitsCount; + + if (willRemoveSplits) { + return existingSplitsCount === selectedSplitPositions.length + ? "Remove All Selected Splits" + : "Remove Selected Splits"; + } else { + return existingSplitsCount === 0 + ? "Split Selected" + : "Complete Selected Splits"; + } }; // Calculate page break tooltip text const getPageBreakTooltip = () => { - if (selectionMode) { - return selectedPages.length > 0 - ? `Insert ${selectedPages.length} Page Break${selectedPages.length > 1 ? 's' : ''}` - : "Insert Page Breaks"; - } - return "Insert Page Breaks After All Pages"; + return selectedPages.length > 0 + ? `Insert ${selectedPages.length} Page Break${selectedPages.length > 1 ? 's' : ''}` + : "Insert Page Breaks"; }; return ( @@ -145,10 +154,10 @@ const PageEditorControls = ({
{/* Page Operations */} - + onRotate('left')} - disabled={selectionMode && selectedPages.length === 0} + disabled={selectedPages.length === 0} variant="subtle" style={{ color: 'var(--mantine-color-dimmed)' }} radius="md" @@ -157,10 +166,10 @@ const PageEditorControls = ({ - + onRotate('right')} - disabled={selectionMode && selectedPages.length === 0} + disabled={selectedPages.length === 0} variant="subtle" style={{ color: 'var(--mantine-color-dimmed)' }} radius="md" @@ -169,23 +178,22 @@ const PageEditorControls = ({ - {selectionMode && ( - - 0 ? "light" : "subtle"} - radius="md" - size="lg" - > - - - - )} - + + + + + + - - {/* Export Controls */} - {selectionMode && ( - - 0 ? "light" : "subtle"} - radius="md" - size="lg" - > - - - - )}
); diff --git a/frontend/src/components/pageEditor/PageThumbnail.tsx b/frontend/src/components/pageEditor/PageThumbnail.tsx index 181206664..bc8d9c467 100644 --- a/frontend/src/components/pageEditor/PageThumbnail.tsx +++ b/frontend/src/components/pageEditor/PageThumbnail.tsx @@ -402,8 +402,8 @@ const PageThumbnail: React.FC = ({ bottom: 8, left: '50%', transform: 'translateX(-50%)', - backgroundColor: 'var(--mantine-color-default-hover)', - border: '1px solid rgba(128, 128, 128, 0.3)', + backgroundColor: 'var(--bg-toolbar)', + border: '1px solid var(--border-default)', padding: '6px 12px', borderRadius: 20, opacity: 0, diff --git a/frontend/src/components/shared/RightRail.tsx b/frontend/src/components/shared/RightRail.tsx index 1b25a84ae..c4b0326b9 100644 --- a/frontend/src/components/shared/RightRail.tsx +++ b/frontend/src/components/shared/RightRail.tsx @@ -295,6 +295,26 @@ export default function RightRail() { )} + {/* Export Selected Pages - page editor only */} + {pageControlsMounted && ( + +
+
+ { pageEditorFunctions?.onExportSelected?.(); }} + disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPages?.length || 0) === 0 || pageEditorFunctions?.exportLoading} + aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'} + > + + +
+
+
+ )} + {/* Close (File Editor: Close Selected | Page Editor: Close PDF) */}