From 53d305d41db86f123ccf25262de36e04e045260e Mon Sep 17 00:00:00 2001 From: EthanHealy01 Date: Fri, 5 Sep 2025 16:09:49 +0100 Subject: [PATCH] split up the BulkSelectionPanel into multiple components --- .../pageEditor/BulkSelectionPanel.tsx | 364 ++---------------- .../AdvancedSelectionPanel.tsx | 293 ++++++++++++++ .../{ => bulkSelectionPanel}/BulkSelection.ts | 0 .../BulkSelectionPanel.module.css | 0 .../bulkSelectionPanel/PageSelectionInput.tsx | 73 ++++ .../SelectedPagesDisplay.tsx | 35 ++ 6 files changed, 424 insertions(+), 341 deletions(-) create mode 100644 frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx rename frontend/src/components/pageEditor/{ => bulkSelectionPanel}/BulkSelection.ts (100%) rename frontend/src/components/pageEditor/{ => bulkSelectionPanel}/BulkSelectionPanel.module.css (100%) create mode 100644 frontend/src/components/pageEditor/bulkSelectionPanel/PageSelectionInput.tsx create mode 100644 frontend/src/components/pageEditor/bulkSelectionPanel/SelectedPagesDisplay.tsx diff --git a/frontend/src/components/pageEditor/BulkSelectionPanel.tsx b/frontend/src/components/pageEditor/BulkSelectionPanel.tsx index 6449062a6..7eaad3b1d 100644 --- a/frontend/src/components/pageEditor/BulkSelectionPanel.tsx +++ b/frontend/src/components/pageEditor/BulkSelectionPanel.tsx @@ -1,18 +1,9 @@ import { useState, useEffect } from 'react'; -import { Group, TextInput, Button, Text, NumberInput, Flex } from '@mantine/core'; -import LocalIcon from '../shared/LocalIcon'; -import { Tooltip } from '../shared/Tooltip'; -import { usePageSelectionTips } from '../tooltips/usePageSelectionTips'; -import classes from './BulkSelectionPanel.module.css'; +import classes from './bulkSelectionPanel/BulkSelectionPanel.module.css'; import { parseSelectionWithDiagnostics } from '../../utils/bulkselection/parseSelection'; -import { - appendExpression, - insertOperatorSmart, - firstNExpression, - lastNExpression, - everyNthExpression, - rangeExpression, -} from './BulkSelection'; +import PageSelectionInput from './bulkSelectionPanel/PageSelectionInput'; +import SelectedPagesDisplay from './bulkSelectionPanel/SelectedPagesDisplay'; +import AdvancedSelectionPanel from './bulkSelectionPanel/AdvancedSelectionPanel'; interface BulkSelectionPanelProps { csvInput: string; @@ -29,18 +20,7 @@ const BulkSelectionPanel = ({ displayDocument, onUpdatePagesFromCSV, }: BulkSelectionPanelProps) => { - const pageSelectionTips = usePageSelectionTips(); - const [advancedOpened, setAdvancedOpened] = useState(false); - 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 [syntaxError, setSyntaxError] = useState(null); - const maxPages = displayDocument?.pages?.length ?? 0; @@ -59,331 +39,33 @@ const BulkSelectionPanel = ({ } }, [csvInput, maxPages]); - const applyExpression = (expr: string) => { - const nextInput = appendExpression(csvInput, expr); - setCsvInput(nextInput); - onUpdatePagesFromCSV(nextInput); - }; - - const handleNone = () => { + const handleClear = () => { setCsvInput(''); onUpdatePagesFromCSV(); - setFirstNValue(''); - setLastNValue(''); - setEveryNthValue(''); - }; - - const insertOperator = (op: 'and' | 'or' | 'not') => { - const next = insertOperatorSmart(csvInput, op); - setCsvInput(next); }; return (
- {syntaxError && ( - {syntaxError} - )} - - { - const next = e.target.value; - setCsvInput(next); - onUpdatePagesFromCSV(next); - }} - placeholder="1,3,5-10" - rightSection={ - csvInput && ( - - ) - } - label={ - - e.stopPropagation()} align="center" gap="xs" my="sm"> - - Page Selection - - - } - onKeyDown={(e) => e.key === 'Enter' && onUpdatePagesFromCSV()} - className={classes.textInput} - /> - + - {/* Selected pages container */} - {selectedPageIds.length > 0 && ( -
- - 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(', ') : ''}) - -
- )} + - {/* Advanced button */} -
- -
- - {/* Advanced section */} - {advancedOpened && ( -
-
- Advanced Selection - -
-
-
- {/* First N Pages - Card Style */} -
- First N Pages - {firstNError && ({firstNError})} -
- Number of pages: - - { - 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="10" - className={classes.fullWidthInput} - error={Boolean(firstNError)} - /> - - -
-
- - {/* Range - Card Style */} -
- Range - {rangeError && ({rangeError})} -
- -
- From: - { - 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="5" - error={Boolean(rangeError)} - /> -
-
- To: - { - 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="10" - error={Boolean(rangeError)} - /> -
-
- -
-
- - {/* Last N Pages - Card Style */} -
- Last N Pages - {lastNError && ({lastNError})} -
- Number of pages: - - { - 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="10" - className={classes.fullWidthInput} - error={Boolean(lastNError)} - /> - - -
-
- - {/* Every Nth Page - Card Style */} -
- Every Nth Page -
- Step size: - - setEveryNthValue(typeof val === 'number' ? val : '')} - min={1} - placeholder="5" - className={classes.fullWidthInput} - /> - - -
-
-
-
- Add Operators: -
- - - -
-
-
-
- )} -
+ + ); }; diff --git a/frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx b/frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx new file mode 100644 index 000000000..cfdbd2518 --- /dev/null +++ b/frontend/src/components/pageEditor/bulkSelectionPanel/AdvancedSelectionPanel.tsx @@ -0,0 +1,293 @@ +import { useState } from 'react'; +import { Button, Text, NumberInput, Group } from '@mantine/core'; +import classes from './BulkSelectionPanel.module.css'; +import { + appendExpression, + insertOperatorSmart, + firstNExpression, + lastNExpression, + everyNthExpression, + rangeExpression, +} from './BulkSelection'; + +interface AdvancedSelectionPanelProps { + csvInput: string; + setCsvInput: (value: string) => void; + onUpdatePagesFromCSV: (override?: string) => void; + maxPages: number; +} + +const AdvancedSelectionPanel = ({ + csvInput, + setCsvInput, + onUpdatePagesFromCSV, + maxPages, +}: AdvancedSelectionPanelProps) => { + const [advancedOpened, setAdvancedOpened] = useState(false); + 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 applyExpression = (expr: string) => { + const nextInput = appendExpression(csvInput, expr); + setCsvInput(nextInput); + onUpdatePagesFromCSV(nextInput); + }; + + const insertOperator = (op: 'and' | 'or' | 'not') => { + const next = insertOperatorSmart(csvInput, op); + setCsvInput(next); + }; + + return ( + <> + {/* Advanced button */} +
+ +
+ + {/* Advanced section */} + {advancedOpened && ( +
+
+ Advanced Selection + +
+
+
+ {/* First N Pages - Card Style */} +
+ First N Pages + {firstNError && ({firstNError})} +
+ Number of pages: + + { + 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="10" + className={classes.fullWidthInput} + error={Boolean(firstNError)} + /> + + +
+
+ + {/* Range - Card Style */} +
+ Range + {rangeError && ({rangeError})} +
+ +
+ From: + { + 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="5" + error={Boolean(rangeError)} + /> +
+
+ To: + { + 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="10" + error={Boolean(rangeError)} + /> +
+
+ +
+
+ + {/* Last N Pages - Card Style */} +
+ Last N Pages + {lastNError && ({lastNError})} +
+ Number of pages: + + { + 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="10" + className={classes.fullWidthInput} + error={Boolean(lastNError)} + /> + + +
+
+ + {/* Every Nth Page - Card Style */} +
+ Every Nth Page +
+ Step size: + + setEveryNthValue(typeof val === 'number' ? val : '')} + min={1} + placeholder="5" + className={classes.fullWidthInput} + /> + + +
+
+
+
+ Add Operators: +
+ + + +
+
+
+
+ )} + + ); +}; + +export default AdvancedSelectionPanel; diff --git a/frontend/src/components/pageEditor/BulkSelection.ts b/frontend/src/components/pageEditor/bulkSelectionPanel/BulkSelection.ts similarity index 100% rename from frontend/src/components/pageEditor/BulkSelection.ts rename to frontend/src/components/pageEditor/bulkSelectionPanel/BulkSelection.ts diff --git a/frontend/src/components/pageEditor/BulkSelectionPanel.module.css b/frontend/src/components/pageEditor/bulkSelectionPanel/BulkSelectionPanel.module.css similarity index 100% rename from frontend/src/components/pageEditor/BulkSelectionPanel.module.css rename to frontend/src/components/pageEditor/bulkSelectionPanel/BulkSelectionPanel.module.css diff --git a/frontend/src/components/pageEditor/bulkSelectionPanel/PageSelectionInput.tsx b/frontend/src/components/pageEditor/bulkSelectionPanel/PageSelectionInput.tsx new file mode 100644 index 000000000..409015151 --- /dev/null +++ b/frontend/src/components/pageEditor/bulkSelectionPanel/PageSelectionInput.tsx @@ -0,0 +1,73 @@ +import { Group, TextInput, Button, Text, Flex } from '@mantine/core'; +import LocalIcon from '../../shared/LocalIcon'; +import { Tooltip } from '../../shared/Tooltip'; +import { usePageSelectionTips } from '../../tooltips/usePageSelectionTips'; +import classes from './BulkSelectionPanel.module.css'; + +interface PageSelectionInputProps { + csvInput: string; + setCsvInput: (value: string) => void; + onUpdatePagesFromCSV: (override?: string) => void; + onClear: () => void; +} + +const PageSelectionInput = ({ + csvInput, + setCsvInput, + onUpdatePagesFromCSV, + onClear, +}: PageSelectionInputProps) => { + const pageSelectionTips = usePageSelectionTips(); + + return ( + + { + const next = e.target.value; + setCsvInput(next); + onUpdatePagesFromCSV(next); + }} + placeholder="1,3,5-10" + rightSection={ + csvInput && ( + + ) + } + label={ + + e.stopPropagation()} align="center" gap="xs" my="sm"> + + Page Selection + + + } + onKeyDown={(e) => e.key === 'Enter' && onUpdatePagesFromCSV()} + className={classes.textInput} + /> + + ); +}; + +export default PageSelectionInput; diff --git a/frontend/src/components/pageEditor/bulkSelectionPanel/SelectedPagesDisplay.tsx b/frontend/src/components/pageEditor/bulkSelectionPanel/SelectedPagesDisplay.tsx new file mode 100644 index 000000000..26fcfb8da --- /dev/null +++ b/frontend/src/components/pageEditor/bulkSelectionPanel/SelectedPagesDisplay.tsx @@ -0,0 +1,35 @@ +import { Text } from '@mantine/core'; +import classes from './BulkSelectionPanel.module.css'; + +interface SelectedPagesDisplayProps { + selectedPageIds: string[]; + displayDocument?: { pages: { id: string; pageNumber: number }[] }; + syntaxError: string | null; +} + +const SelectedPagesDisplay = ({ + selectedPageIds, + displayDocument, + syntaxError, +}: SelectedPagesDisplayProps) => { + if (selectedPageIds.length === 0 && !syntaxError) { + return null; + } + + return ( +
+ {syntaxError ? ( + {syntaxError} + ) : ( + + 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(', ') : ''}) + + )} +
+ ); +}; + +export default SelectedPagesDisplay;