diff --git a/client/component/src/AxialSelectionConfig.tsx b/client/component/src/AxialSelectionConfig.tsx index 0b43791..b842ccf 100644 --- a/client/component/src/AxialSelectionConfig.tsx +++ b/client/component/src/AxialSelectionConfig.tsx @@ -28,7 +28,11 @@ function AxialSelectionConfig(props: AxialSelectionConfigProps) { return selection.dimension === 0 ? ( - + key="x length" label="x length" @@ -46,7 +50,11 @@ function AxialSelectionConfig(props: AxialSelectionConfigProps) { ) : ( - + key="y length" label="y length" diff --git a/client/component/src/ClearSelectionsBtn.tsx b/client/component/src/ClearSelectionsBtn.tsx index f541edc..d53398c 100644 --- a/client/component/src/ClearSelectionsBtn.tsx +++ b/client/component/src/ClearSelectionsBtn.tsx @@ -1,4 +1,3 @@ -import type BaseSelection from './selections/BaseSelection'; import { Btn } from '@h5web/lib'; import type { SelectionHandler } from './selections/utils'; @@ -6,12 +5,8 @@ import type { SelectionHandler } from './selections/utils'; * Props for the `ClearSelectionsBtn` component. */ interface ClearSelectionsBtnProps { - /** The current selections */ - selections: BaseSelection[]; /** The function to call to update the selections state */ updateSelection: SelectionHandler; - /** The ID of the current selection */ - currentSelectionID: string | null; /** The function to call to update the current selection ID */ updateCurrentSelectionID: (s: string | null) => void; /** Indicates whether the component is disabled (optional) */ diff --git a/client/component/src/PlotToolbar.tsx b/client/component/src/PlotToolbar.tsx index 754bd2a..1b24ea2 100644 --- a/client/component/src/PlotToolbar.tsx +++ b/client/component/src/PlotToolbar.tsx @@ -21,7 +21,6 @@ import { TbAxisX, TbAxisY, TbGridDots } from 'react-icons/tb'; import AspectConfigModal from './AspectConfigModal'; import AxisConfigModal from './AxisConfigModal'; -import type BaseSelection from './selections/BaseSelection'; import { BatonConfigModal } from './BatonConfigModal'; import ClearSelectionsBtn from './ClearSelectionsBtn'; import InteractionModeToggle from './InteractionModeToggle'; @@ -163,14 +162,14 @@ function PlotToolbar(props: PropsWithChildren): JSX.Element { ]; const selectionConfig = SelectionConfig({ - selections: selections as BaseSelection[], + selections, updateSelection, - currentSelectionID: currentSelectionID, + currentSelectionID, updateCurrentSelectionID: setCurrentSelectionID, icon: MdOutlineShapeLine as IIconType, domain: value.dDomain, customDomain: value.dCustomDomain, - showSelectionConfig: showSelectionConfig, + showSelectionConfig, updateShowSelectionConfig: setShowSelectionConfig, hasBaton: selectBaton, }); @@ -306,9 +305,7 @@ function PlotToolbar(props: PropsWithChildren): JSX.Element { overflows.push( diff --git a/client/component/src/SelectionConfig.tsx b/client/component/src/SelectionConfig.tsx index 7f9582d..3869b9e 100644 --- a/client/component/src/SelectionConfig.tsx +++ b/client/component/src/SelectionConfig.tsx @@ -1,5 +1,4 @@ import Modeless from './Modeless'; -import type BaseSelection from './selections/BaseSelection'; import { getSelectionLabel } from './selections/utils'; import AxialSelection from './selections/AxialSelection'; import RectangularSelection from './selections/RectangularSelection'; @@ -37,7 +36,7 @@ export const SELECTION_ICONS = { */ interface SelectionConfigProps { /** The current selections */ - selections: BaseSelection[]; + selections: SelectionBase[]; /** Handles updating selection */ updateSelection?: SelectionHandler; /** The ID of the current selection (optional) */ @@ -73,7 +72,7 @@ function SelectionConfig(props: SelectionConfigProps) { updateSelection, hasBaton, } = props; - let currentSelection: BaseSelection | null = null; + let currentSelection: SelectionBase | null = null; if (selections.length > 0) { currentSelection = selections.find((s) => s.id === currentSelectionID) ?? selections[0]; @@ -86,7 +85,7 @@ function SelectionConfig(props: SelectionConfigProps) { if (currentSelectionID) { const selection = selections.find((s) => s.id === currentSelectionID); if (selection) { - let lastSelection: BaseSelection | undefined; + let lastSelection: SelectionBase | undefined; if (!Object.hasOwn(selections, 'findLast')) { // workaround missing method const oSelections = selections.filter( @@ -118,11 +117,8 @@ function SelectionConfig(props: SelectionConfigProps) { ); if (currentSelection !== null) { - const cSelection: BaseSelection = currentSelection; - const colour = (cSelection.colour ?? - ('defaultColour' in cSelection - ? cSelection.defaultColour - : '#000000')) as string; + const cSelection: SelectionBase = currentSelection; + const colour = cSelection.defaultColour; modeless.push( @@ -148,6 +144,7 @@ function SelectionConfig(props: SelectionConfigProps) { )} ); + const disabled = !hasBaton; modeless.push( key="name" @@ -159,7 +156,7 @@ function SelectionConfig(props: SelectionConfigProps) { updateSelection(cSelection); } }} - disabled={!hasBaton} + disabled={disabled} /> ); modeless.push( @@ -177,39 +174,39 @@ function SelectionConfig(props: SelectionConfigProps) { }} decimalPlaces={2} isValid={(v) => isValidPositiveNumber(v, 1, true)} - disabled={!hasBaton} + disabled={disabled} /> ); - if (AxialSelection.isShape(cSelection as SelectionBase)) { + if (AxialSelection.isShape(cSelection)) { modeless.push( AxialSelectionConfig({ - selection: cSelection as AxialSelection, + selection: cSelection, updateSelection, - disabled: !hasBaton, + disabled, }) ); - } else if (LinearSelection.isShape(cSelection as SelectionBase)) { + } else if (LinearSelection.isShape(cSelection)) { modeless.push( LinearSelectionConfig({ - selection: cSelection as LinearSelection, + selection: cSelection, updateSelection, - disabled: !hasBaton, + disabled, }) ); - } else if (RectangularSelection.isShape(cSelection as SelectionBase)) { + } else if (RectangularSelection.isShape(cSelection)) { modeless.push( RectangularSelectionConfig({ - selection: cSelection as RectangularSelection, + selection: cSelection, updateSelection, - disabled: !hasBaton, + disabled, }) ); - } else if (PolygonalSelection.isShape(cSelection as SelectionBase)) { + } else if (PolygonalSelection.isShape(cSelection)) { modeless.push( PolygonalSelectionConfig({ - selection: cSelection as PolygonalSelection, + selection: cSelection, updateSelection, - disabled: !hasBaton, + disabled, }) ); } @@ -218,7 +215,7 @@ function SelectionConfig(props: SelectionConfigProps) { { if (window.confirm('Clear selection?')) { handleDeleteSelection(); diff --git a/client/component/src/SelectionConfigComponents.tsx b/client/component/src/SelectionConfigComponents.tsx index 7b32089..e0368ce 100644 --- a/client/component/src/SelectionConfigComponents.tsx +++ b/client/component/src/SelectionConfigComponents.tsx @@ -1,15 +1,17 @@ -import type BaseSelection from './selections/BaseSelection'; import type OrientableSelection from './selections/OrientableSelection'; import { isNumber } from './utils'; import LabelledInput from './LabelledInput'; -import { SelectionHandler } from './selections/utils'; +import { SelectionBase, SelectionHandler } from './selections/utils'; /** * Props for the `AngleInput` component. */ interface AngleInputProps { + /** The selection for which the angle value is being configured */ selection: OrientableSelection; + /** Function to handle updating angle of selection */ updateSelection?: SelectionHandler; + /** If input component is disabled (optional) */ disabled?: boolean; } @@ -41,31 +43,33 @@ function AngleInput(props: AngleInputProps) { } /** - * Props for the `XInput` component. + * Props for `XInput` and `YInput` components. */ -interface XInputProps { - selection: BaseSelection; +interface StartInputProps { + /** The selection for which the start value is being configured */ + selection: SelectionBase; + /** Function to handle updating start of selection */ updateSelection?: SelectionHandler; + /** If input component is disabled (optional) */ disabled?: boolean; } /** * Render a labelled inout for x. - * @param {XInputProps} props - The component props. + * @param {StartInputProps} props - The component props. * @returns {JSX.Element} The rendered component. */ -function XInput(props: XInputProps) { +function XInput(props: StartInputProps) { const { selection, updateSelection, disabled } = props; return ( key="x" label="x" - input={selection.vStart.x} + input={selection.start[0]} decimalPlaces={8} updateValue={(x: number) => { - selection.start[0] = x; - selection.vStart.x = x; + selection.setStart(0, x); if (updateSelection) { updateSelection(selection); } @@ -76,35 +80,22 @@ function XInput(props: XInputProps) { ); } -/** - * Props for the `YInput` component. - */ -interface YInputProps { - /** The selection for which the y values are being configured */ - selection: BaseSelection; - /** Function to handle updating y of selection */ - updateSelection?: SelectionHandler; - /** If input component is disabled (optional) */ - disabled?: boolean; -} - /** * Render a labelled input for y. - * @param {YInputProps} props - The component props. + * @param {StartInputProps} props - The component props. * @returns {JSX.Element} The rendered component. */ -function YInput(props: YInputProps) { +function YInput(props: StartInputProps) { const { selection, updateSelection, disabled } = props; return ( key="y" label="y" - input={selection.vStart.y} + input={selection.start[1]} decimalPlaces={8} updateValue={(y: number) => { - selection.start[1] = y; - selection.vStart.y = y; + selection.setStart(1, y); if (updateSelection) { updateSelection(selection); } diff --git a/client/component/src/SelectionTypeDropdown.tsx b/client/component/src/SelectionTypeDropdown.tsx index 1d2e5fb..b66e3d0 100644 --- a/client/component/src/SelectionTypeDropdown.tsx +++ b/client/component/src/SelectionTypeDropdown.tsx @@ -106,6 +106,7 @@ function SelectionTypeDropdown(props: SelectionDropdownProps) { const { value, onSelectionTypeChange, + disabled, options = [ SelectionType.line, SelectionType.rectangle, @@ -122,7 +123,7 @@ function SelectionTypeDropdown(props: SelectionDropdownProps) { onChange={onSelectionTypeChange} options={options} renderOption={(option) => } - disabled={props.disabled} + disabled={disabled} /> ); } diff --git a/client/component/src/selections/BaseSelection.tsx b/client/component/src/selections/BaseSelection.tsx index cb9e1b0..aa7c48e 100644 --- a/client/component/src/selections/BaseSelection.tsx +++ b/client/component/src/selections/BaseSelection.tsx @@ -11,6 +11,7 @@ import type { SelectionBase } from './utils'; export default class BaseSelection implements SelectionBase { id: string; name = ''; + defaultColour: string = '#000000'; // black colour?: string; alpha = 0.3; fixed = false; @@ -28,6 +29,19 @@ export default class BaseSelection implements SelectionBase { return ''; } + setStart(i: number, v: number) { + this.start[i] = v; + if (i == 0) { + this.vStart.x = v; + } else if (i == 1) { + this.vStart.y = v; + } else if (i == 2) { + this.vStart.z = v; + } else { + console.log('Index error (%i > 2) on setting start', i); + } + } + getPoints() { return [this.vStart.clone()]; } @@ -41,22 +55,18 @@ export default class BaseSelection implements SelectionBase { this.fixed = other.fixed; } - setName(name: string) { - this.name = name; - } - - setFixed(fixed: boolean) { - this.fixed = fixed; - } - static createFromSelection(s: BaseSelection) { const l = new BaseSelection([...s.start]); l.setProperties(s); return l; } - onHandleChange(i: number, pos: [number | undefined, number | undefined]) { - console.debug('base: oHC', i, pos); + onHandleChange( + i: number, + pos: [number | undefined, number | undefined], + d?: boolean + ) { + console.debug('base: oHC', i, pos, d); if (i === 0) { const b = BaseSelection.createFromSelection(this); const x = pos[0]; diff --git a/client/component/src/selections/utils.tsx b/client/component/src/selections/utils.tsx index db1a841..52c9ec8 100644 --- a/client/component/src/selections/utils.tsx +++ b/client/component/src/selections/utils.tsx @@ -18,7 +18,6 @@ import { Vector3 } from 'three'; import DvdAxisBox from '../shapes/DvdAxisBox'; import DvdPolyline from '../shapes/DvdPolyline'; import AxialSelection from './AxialSelection'; -import type BaseSelection from './BaseSelection'; import CircularSelection from './CircularSelection'; import CircularSectorialSelection from './CircularSectorialSelection'; import EllipticalSelection from './EllipticalSelection'; @@ -64,6 +63,8 @@ interface SelectionBase { readonly id: string; /** name */ name: string; + /** default colour */ + defaultColour: string; /** outline colour */ colour?: string; /** opacity [0,1] */ @@ -74,8 +75,10 @@ interface SelectionBase { start: [number, number]; /** if true, outline is dashed */ asDashed?: boolean; + /** set start */ + setStart: (i: number, v: number) => void; /** retrieve points */ - getPoints?: () => Vector3[]; + getPoints: () => Vector3[]; /** callback for drag handle movements */ onHandleChange: HandleChangeFunction; /** string representation */ @@ -156,7 +159,7 @@ function createSelection( selectionType: SelectionType, axesFlipped: [boolean, boolean], points: Vector3[] -) { +): SelectionBase { switch (selectionType) { case SelectionType.rectangle: return RectangularSelection.createFromPoints(axesFlipped, points); @@ -226,7 +229,7 @@ function pointsToSelection( points: Vector3[], alpha: number, colour?: string -): BaseSelection { +): SelectionBase { console.debug('Points', selectionType, points); const s = createSelection(selectionType, [false, false], points); s.alpha = alpha; @@ -398,14 +401,8 @@ function SelectionShape(props: SelectionShapeProps) { }, [updateSelection, htmlToDataFunction] ); - if ( - selectionType !== SelectionType.unknown && - selection.getPoints !== undefined - ) { + if (selectionType !== SelectionType.unknown) { const pts = selection.getPoints(); - const defColour = ( - 'defaultColour' in selection ? selection.defaultColour : '#000000' - ) as string; return ( {(...htmlSelection: Vector3[]) => @@ -414,7 +411,7 @@ function SelectionShape(props: SelectionShapeProps) { htmlSelection, selection.alpha, size, - selection.colour ?? defColour, + selection.colour ?? selection.defaultColour, selection.asDashed, selection.fixed || !showHandles, combinedUpdate(selection)