diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index 2c85d8f81..8939f7db4 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -171,7 +171,8 @@ const PageEditor = ({ }, () => splitPositions, setSplitPositions, - () => getPageNumbersFromIds(selectedPageIds) + () => getPageNumbersFromIds(selectedPageIds), + closePdf ); undoManagerRef.current.executeCommand(deleteCommand); } @@ -228,7 +229,8 @@ const PageEditor = ({ }, () => splitPositions, setSplitPositions, - () => selectedPageNumbers + () => selectedPageNumbers, + closePdf ); undoManagerRef.current.executeCommand(deleteCommand); }, [selectedPageIds, displayDocument, splitPositions, getPageNumbersFromIds, getPageIdsFromNumbers]); @@ -246,7 +248,8 @@ const PageEditor = ({ }, () => splitPositions, setSplitPositions, - () => getPageNumbersFromIds(selectedPageIds) + () => getPageNumbersFromIds(selectedPageIds), + closePdf ); undoManagerRef.current.executeCommand(deleteCommand); }, [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds]); diff --git a/frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx b/frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx index 0717459c0..d58d28e4d 100644 --- a/frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx +++ b/frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Button, Text, NumberInput, Group, Flex } from '@mantine/core'; +import { Flex } from '@mantine/core'; import classes from './BulkSelectionPanel.module.css'; import { appendExpression, @@ -9,6 +9,8 @@ import { everyNthExpression, rangeExpression, } from './BulkSelection'; +import SelectPages from './SelectPages'; +import OperatorsSection from './OperatorsSection'; interface AdvancedSelectionPanelProps { csvInput: string; @@ -25,16 +27,32 @@ const AdvancedSelectionPanel = ({ maxPages, advancedOpened, }: AdvancedSelectionPanelProps) => { - // Visibility now controlled by parent - const [firstNValue, setFirstNValue] = useState(''); - const [lastNValue, setLastNValue] = useState(''); - const [everyNthValue, setEveryNthValue] = useState(''); - const [rangeStart, setRangeStart] = useState(''); const [rangeEnd, setRangeEnd] = useState(''); - const [firstNError, setFirstNError] = useState(null); - const [lastNError, setLastNError] = useState(null); - const [rangeError, setRangeError] = useState(null); + const handleRangeEndChange = (val: string | number) => { + const next = typeof val === 'number' ? val : ''; + setRangeEnd(next); + }; + + // Named validation functions + const validatePositiveNumber = (value: number): string | null => { + return value <= 0 ? 'Enter a positive number' : null; + }; + + const validateRangeStart = (start: number): string | null => { + if (start <= 0) return 'Values must be positive'; + if (typeof rangeEnd === 'number' && start > rangeEnd) { + return 'From must be less than or equal to To'; + } + return null; + }; + + const validateRangeEnd = (end: number): string | null => { + if (end <= 0) return 'Values must be positive'; + return null; + }; + + // Named callback functions const applyExpression = (expr: string) => { const nextInput = appendExpression(csvInput, expr); setCsvInput(nextInput); @@ -46,6 +64,28 @@ const AdvancedSelectionPanel = ({ setCsvInput(next); }; + const handleFirstNApply = (value: number) => { + const expr = firstNExpression(value, maxPages); + if (expr) applyExpression(expr); + }; + + const handleLastNApply = (value: number) => { + const expr = lastNExpression(value, maxPages); + if (expr) applyExpression(expr); + }; + + const handleEveryNthApply = (value: number) => { + const expr = everyNthExpression(value); + if (expr) applyExpression(expr); + }; + + const handleRangeApply = (start: number) => { + if (typeof rangeEnd !== 'number') return; + const expr = rangeExpression(start, rangeEnd, maxPages); + if (expr) applyExpression(expr); + setRangeEnd(''); + }; + return ( <> {/* Advanced section */} @@ -54,211 +94,47 @@ const AdvancedSelectionPanel = ({
{/* Cards row */} - {/* First N Pages - Card Style */} -
- First N Pages - {firstNError && ({firstNError})} -
- - { - const next = typeof val === 'number' ? val : ''; - setFirstNValue(next); - if (next === '') setFirstNError(null); - else if (typeof next === 'number' && next <= 0) setFirstNError('Enter a positive number'); - else setFirstNError(null); - }} - min={1} - placeholder="Number of pages" - className={classes.fullWidthInput} - error={Boolean(firstNError)} - /> - - -
-
+ - {/* Range - Card Style */} -
- Range - {rangeError && ({rangeError})} -
- -
- { - const next = typeof val === 'number' ? val : ''; - setRangeStart(next); - const s = typeof next === 'number' ? next : null; - const e = typeof rangeEnd === 'number' ? rangeEnd : null; - if (s !== null && s <= 0) setRangeError('Values must be positive'); - else if (s !== null && e !== null && s > e) setRangeError('From must be less than or equal to To'); - else setRangeError(null); - }} - min={1} - placeholder="From" - error={Boolean(rangeError)} - /> -
-
- { - const next = typeof val === 'number' ? val : ''; - setRangeEnd(next); - const e = typeof next === 'number' ? next : null; - const s = typeof rangeStart === 'number' ? rangeStart : null; - if (e !== null && e <= 0) setRangeError('Values must be positive'); - else if (s !== null && e !== null && s > e) setRangeError('From must be less than or equal to To'); - else setRangeError(null); - }} - min={1} - placeholder="To" - error={Boolean(rangeError)} - /> -
- -
-
-
+ - {/* Last N Pages - Card Style */} -
- Last N Pages - {lastNError && ({lastNError})} -
- - { - const next = typeof val === 'number' ? val : ''; - setLastNValue(next); - if (next === '') setLastNError(null); - else if (typeof next === 'number' && next <= 0) setLastNError('Enter a positive number'); - else setLastNError(null); - }} - min={1} - placeholder="Number of pages" - className={classes.fullWidthInput} - error={Boolean(lastNError)} - /> - - -
-
+ - {/* Every Nth Page - Card Style */} -
- Every Nth Page -
- - setEveryNthValue(typeof val === 'number' ? val : '')} - min={1} - placeholder="Step size" - className={classes.fullWidthInput} - /> - - -
-
+
+ {/* Operators row at bottom */} -
- Add Operators: - - - - - -
+
)} diff --git a/frontend/src/components/pageEditor/bulkSelectionPanel/OperatorsSection.tsx b/frontend/src/components/pageEditor/bulkSelectionPanel/OperatorsSection.tsx new file mode 100644 index 000000000..dbe5bb97e --- /dev/null +++ b/frontend/src/components/pageEditor/bulkSelectionPanel/OperatorsSection.tsx @@ -0,0 +1,49 @@ +import { Button, Text, Group } from '@mantine/core'; +import classes from './BulkSelectionPanel.module.css'; + +interface OperatorsSectionProps { + csvInput: string; + onInsertOperator: (op: 'and' | 'or' | 'not') => void; +} + +const OperatorsSection = ({ csvInput, onInsertOperator }: OperatorsSectionProps) => { + return ( +
+ Add Operators: + + + + + +
+ ); +}; + +export default OperatorsSection; diff --git a/frontend/src/components/pageEditor/bulkSelectionPanel/SelectPages.tsx b/frontend/src/components/pageEditor/bulkSelectionPanel/SelectPages.tsx new file mode 100644 index 000000000..7d26e3091 --- /dev/null +++ b/frontend/src/components/pageEditor/bulkSelectionPanel/SelectPages.tsx @@ -0,0 +1,105 @@ +import { useState } from 'react'; +import { Button, Text, NumberInput, Group } from '@mantine/core'; +import classes from './BulkSelectionPanel.module.css'; + +interface SelectPagesProps { + title: string; + placeholder: string; + onApply: (value: number) => void; + maxPages: number; + validationFn?: (value: number) => string | null; + isRange?: boolean; + rangeEndValue?: number | ''; + onRangeEndChange?: (value: string | number) => void; + rangeEndPlaceholder?: string; +} + +const SelectPages = ({ + title, + placeholder, + onApply, + maxPages, + validationFn, + isRange = false, + rangeEndValue, + onRangeEndChange, + rangeEndPlaceholder, +}: SelectPagesProps) => { + const [value, setValue] = useState(''); + const [error, setError] = useState(null); + + const handleValueChange = (val: string | number) => { + const next = typeof val === 'number' ? val : ''; + setValue(next); + + if (validationFn && typeof next === 'number') { + setError(validationFn(next)); + } else { + setError(null); + } + }; + + const handleApply = () => { + if (value === '' || typeof value !== 'number') return; + onApply(value); + setValue(''); + setError(null); + }; + + const isDisabled = Boolean(error) || value === ''; + + return ( +
+ {title} + {error && ({error})} +
+ + {isRange ? ( + <> +
+ +
+
+ +
+ + ) : ( + + )} + +
+
+
+ ); +}; + +export default SelectPages; diff --git a/frontend/src/components/pageEditor/commands/pageCommands.ts b/frontend/src/components/pageEditor/commands/pageCommands.ts index 96b93aa63..26cb9e09c 100644 --- a/frontend/src/components/pageEditor/commands/pageCommands.ts +++ b/frontend/src/components/pageEditor/commands/pageCommands.ts @@ -59,6 +59,7 @@ export class DeletePagesCommand extends DOMCommand { private originalSelectedPages: number[] = []; private hasExecuted: boolean = false; private pageIdsToDelete: string[] = []; + private onAllPagesDeleted?: () => void; constructor( private pagesToDelete: number[], @@ -67,9 +68,11 @@ export class DeletePagesCommand extends DOMCommand { private setSelectedPages: (pages: number[]) => void, private getSplitPositions: () => Set, private setSplitPositions: (positions: Set) => void, - private getSelectedPages: () => number[] + private getSelectedPages: () => number[], + onAllPagesDeleted?: () => void ) { super(); + this.onAllPagesDeleted = onAllPagesDeleted; } execute(): void { @@ -99,7 +102,13 @@ export class DeletePagesCommand extends DOMCommand { !this.pageIdsToDelete.includes(page.id) ); - if (remainingPages.length === 0) return; // Safety check + if (remainingPages.length === 0) { + // If all pages would be deleted, clear selection/splits and close PDF + this.setSelectedPages([]); + this.setSplitPositions(new Set()); + this.onAllPagesDeleted?.(); + return; + } // Renumber remaining pages remainingPages.forEach((page, index) => {