Visual updates

This commit is contained in:
Reece Browne 2025-08-25 23:47:10 +01:00
parent ad47c1dafb
commit 9a55437aad
6 changed files with 146 additions and 84 deletions

View File

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

View File

@ -16,7 +16,7 @@
}
.pageContainer:hover .pageHoverControls {
opacity: 1 !important;
opacity: 0.9 !important;
}
/* Checkbox container - prevent transform inheritance */

View File

@ -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 pagesToRotate = selectedPageNumbers.map(pageNum => {
const page = displayDocument.pages.find(p => p.pageNumber === pageNum);
return page?.id || '';
}).filter(id => id)
: displayDocument.pages.map(p => p.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;

View File

@ -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 (
@ -145,10 +154,10 @@ const PageEditorControls = ({
<div style={{ width: 1, height: 28, backgroundColor: 'var(--mantine-color-gray-3)', margin: '0 8px' }} />
{/* Page Operations */}
<Tooltip label={selectionMode ? "Rotate Selected Left" : "Rotate All Left"}>
<Tooltip label="Rotate Selected Left">
<ActionIcon
onClick={() => 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 = ({
<RotateLeftIcon />
</ActionIcon>
</Tooltip>
<Tooltip label={selectionMode ? "Rotate Selected Right" : "Rotate All Right"}>
<Tooltip label="Rotate Selected Right">
<ActionIcon
onClick={() => 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 = ({
<RotateRightIcon />
</ActionIcon>
</Tooltip>
{selectionMode && (
<Tooltip label="Delete Selected">
<ActionIcon
onClick={onDelete}
disabled={selectedPages.length === 0}
variant={selectedPages.length > 0 ? "light" : "subtle"}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"
size="lg"
>
<DeleteIcon />
</ActionIcon>
</Tooltip>
)}
<Tooltip label={getSplitAllTooltip()}>
<Tooltip label={getSplitTooltip()}>
<ActionIcon
onClick={selectionMode ? onSplit : onSplitAll}
disabled={selectionMode && selectedPages.length === 0}
onClick={onSplit}
disabled={selectedPages.length === 0}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"
@ -196,8 +204,8 @@ const PageEditorControls = ({
</Tooltip>
<Tooltip label={getPageBreakTooltip()}>
<ActionIcon
onClick={selectionMode ? onPageBreak : onPageBreakAll}
disabled={selectionMode && selectedPages.length === 0}
onClick={onPageBreak}
disabled={selectedPages.length === 0}
variant="subtle"
style={{ color: 'var(--mantine-color-dimmed)' }}
radius="md"
@ -206,21 +214,6 @@ const PageEditorControls = ({
<InsertPageBreakIcon />
</ActionIcon>
</Tooltip>
{/* Export Controls */}
{selectionMode && (
<Tooltip label="Export Selected">
<ActionIcon
onClick={onExportSelected}
disabled={exportLoading || selectedPages.length === 0}
variant={selectedPages.length > 0 ? "light" : "subtle"}
radius="md"
size="lg"
>
<DownloadIcon />
</ActionIcon>
</Tooltip>
)}
</div>
</div>
);

View File

@ -402,8 +402,8 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
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,

View File

@ -295,6 +295,26 @@ export default function RightRail() {
)}
{/* Export Selected Pages - page editor only */}
{pageControlsMounted && (
<Tooltip content={t('rightRail.exportSelected', 'Export Selected Pages')} position="left" offset={12} arrow>
<div className={`right-rail-fade ${pageControlsVisible ? 'enter' : 'exit'}`} aria-hidden={!pageControlsVisible}>
<div style={{ display: 'inline-flex' }}>
<ActionIcon
variant="subtle"
radius="md"
className="right-rail-icon"
onClick={() => { 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'}
>
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</div>
</Tooltip>
)}
{/* Close (File Editor: Close Selected | Page Editor: Close PDF) */}
<Tooltip content={currentView === 'pageEditor' ? t('rightRail.closePdf', 'Close PDF') : t('rightRail.closeSelected', 'Close Selected Files')} position="left" offset={12} arrow>
<div>