From d5c2e97a397a3b24c7e98d184e9faf99fe6e003c Mon Sep 17 00:00:00 2001 From: Jethary Date: Fri, 19 Jan 2024 14:53:35 -0500 Subject: [PATCH] refactor rest of class componetns --- .../src/components/SelectionRect.tsx | 143 ++++++------ .../fields/TipPositionField/index.tsx | 5 +- .../fields/WellOrderField/WellOrderModal.tsx | 211 ++++++++---------- .../fields/WellOrderField/index.tsx | 1 - .../components/labware/SelectableLabware.tsx | 177 +++++++-------- .../src/components/labware/WellTooltip.tsx | 141 ++++++------ 6 files changed, 332 insertions(+), 346 deletions(-) diff --git a/protocol-designer/src/components/SelectionRect.tsx b/protocol-designer/src/components/SelectionRect.tsx index 251470e135b..b6aa5f84415 100644 --- a/protocol-designer/src/components/SelectionRect.tsx +++ b/protocol-designer/src/components/SelectionRect.tsx @@ -1,41 +1,39 @@ import * as React from 'react' import styles from './SelectionRect.css' -import { DragRect, GenericRect } from '../collision-types' +import type { DragRect, GenericRect } from '../collision-types' interface Props { - onSelectionMove?: (e: MouseEvent, arg: GenericRect) => unknown - onSelectionDone?: (e: MouseEvent, arg: GenericRect) => unknown + onSelectionMove?: (e: MouseEvent, arg: GenericRect) => void + onSelectionDone?: (e: MouseEvent, arg: GenericRect) => void svg?: boolean // set true if this is an embedded SVG children?: React.ReactNode originXOffset?: number originYOffset?: number } -interface State { - positions: DragRect | null -} - -export class SelectionRect extends React.Component { - parentRef?: HTMLElement | SVGElement | null - - constructor(props: Props) { - super(props) - this.state = { positions: null } - } - - renderRect(args: DragRect): React.ReactNode { +export const SelectionRect = (props: Props): JSX.Element => { + const parentRef = React.useRef(null) + const { + onSelectionMove, + onSelectionDone, + svg, + children, + originXOffset, + originYOffset, + } = props + const [positions, setPositions] = React.useState(null) + + const renderRect = (args: DragRect): React.ReactNode => { const { xStart, yStart, xDynamic, yDynamic } = args const left = Math.min(xStart, xDynamic) const top = Math.min(yStart, yDynamic) const width = Math.abs(xDynamic - xStart) const height = Math.abs(yDynamic - yStart) - const { originXOffset = 0, originYOffset = 0 } = this.props - if (this.props.svg) { + if (svg) { // calculate ratio btw clientRect bounding box vs svg parent viewBox // WARNING: May not work right if you're nesting SVGs! - const parentRef = this.parentRef - if (!parentRef) { + if (!parentRef.current) { return null } @@ -44,7 +42,7 @@ export class SelectionRect extends React.Component { height: number left: number top: number - } = parentRef.getBoundingClientRect() + } = parentRef.current.getBoundingClientRect() // @ts-expect-error(sa, 2021-7-1): parentRef.closest might return null const viewBox: { width: number; height: number } = parentRef.closest( 'svg' @@ -55,8 +53,8 @@ export class SelectionRect extends React.Component { return ( { ) } - getRect(args: DragRect): GenericRect { + const getRect = (args: DragRect): GenericRect => { const { xStart, yStart, xDynamic, yDynamic } = args // convert internal rect position to more generic form // TODO should this be used in renderRect? @@ -89,74 +87,73 @@ export class SelectionRect extends React.Component { } } - handleMouseDown: React.MouseEventHandler = e => { - document.addEventListener('mousemove', this.handleDrag) - document.addEventListener('mouseup', this.handleMouseUp) - this.setState({ - positions: { - xStart: e.clientX, - xDynamic: e.clientX, - yStart: e.clientY, - yDynamic: e.clientY, - }, + const handleMouseDown: React.MouseEventHandler< + SVGGElement | HTMLElement + > = e => { + document.addEventListener('mousemove', handleDrag) + document.addEventListener('mouseup', handleMouseUp) + setPositions({ + xStart: e.clientX, + xDynamic: e.clientX, + yStart: e.clientY, + yDynamic: e.clientY, }) } - handleDrag: (e: MouseEvent) => void = e => { - if (this.state.positions) { + const handleDrag: (e: MouseEvent) => void = e => { + if (positions) { const nextRect = { - ...this.state.positions, + ...positions, xDynamic: e.clientX, yDynamic: e.clientY, } - this.setState({ positions: nextRect }) - const rect = this.getRect(nextRect) - this.props.onSelectionMove && this.props.onSelectionMove(e, rect) + setPositions(nextRect) + + const rect = getRect(nextRect) + onSelectionMove != null && onSelectionMove(e, rect) } } - handleMouseUp: (e: MouseEvent) => void = e => { + const handleMouseUp: (e: MouseEvent) => void = e => { if (!(e instanceof MouseEvent)) { return } - document.removeEventListener('mousemove', this.handleDrag) - document.removeEventListener('mouseup', this.handleMouseUp) + document.removeEventListener('mousemove', handleDrag) + document.removeEventListener('mouseup', handleMouseUp) - const finalRect = this.state.positions && this.getRect(this.state.positions) + const finalRect = positions != null && getRect(positions) // clear the rectangle - this.setState({ positions: null }) + setPositions(null) // call onSelectionDone callback with {x0, x1, y0, y1} of final selection rectangle - this.props.onSelectionDone && - finalRect && - this.props.onSelectionDone(e, finalRect) + onSelectionDone != null && finalRect && onSelectionDone(e, finalRect) } - render(): React.ReactNode { - const { svg, children } = this.props - - return svg ? ( - { - this.parentRef = ref - }} - > - {children} - {this.state.positions && this.renderRect(this.state.positions)} - - ) : ( -
{ - this.parentRef = ref - }} - > - {this.state.positions && this.renderRect(this.state.positions)} - {children} -
- ) - } + return svg ? ( + { + if (ref) { + parentRef.current = ref + } + }} + > + {children} + {positions != null && renderRect(positions)} + + ) : ( +
{ + if (ref) { + parentRef.current = ref + } + }} + > + {positions && renderRect(positions)} + {children} +
+ ) } diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx index 680c67192c2..922d6f1649d 100644 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx +++ b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx @@ -39,7 +39,10 @@ export function TipPositionField(props: Props): JSX.Element { } = props const [isModalOpen, setModalOpen] = React.useState(false) const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) - const labwareDef = labwareId != null ? labwareEntities[labwareId].def : null + const labwareDef = + labwareId != null && labwareEntities[labwareId] != null + ? labwareEntities[labwareId].def + : null let wellDepthMm: number = 0 if (labwareDef != null) { diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.tsx b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.tsx index 39495ba88f5..939459238ad 100644 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.tsx +++ b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.tsx @@ -37,9 +37,6 @@ export interface WellOrderModalProps { firstValue?: WellOrderOption | null, secondValue?: WellOrderOption | null ) => void - // TODO(jr, 1/18/24): The newest i18next versions only support TypeScript v5 - // so need to type all ts as any for now https://www.i18next.com/overview/typescript - t: any } interface State { @@ -86,27 +83,23 @@ export const DoneButton = (props: { onClick: () => void }): JSX.Element => { ) } -export class WellOrderModal extends React.Component< - WellOrderModalProps, - State -> { - constructor(props: WellOrderModalProps) { - super(props) - const { - initialFirstValue, - initialSecondValue, - } = this.getInitialFirstValues() - this.state = { - firstValue: initialFirstValue, - secondValue: initialSecondValue, - } - } - - getInitialFirstValues: () => { +export const WellOrderModal = ( + props: WellOrderModalProps +): JSX.Element | null => { + const { + isOpen, + closeModal, + firstValue, + secondValue, + firstName, + secondName, + updateValues, + } = props + const { t } = useTranslation(['form', 'modal']) + const getInitialFirstValues: () => { initialFirstValue: WellOrderOption initialSecondValue: WellOrderOption } = () => { - const { firstValue, secondValue } = this.props if (firstValue == null || secondValue == null) { return { initialFirstValue: DEFAULT_FIRST, @@ -119,36 +112,36 @@ export class WellOrderModal extends React.Component< } } - applyChanges: () => void = () => { - this.props.updateValues(this.state.firstValue, this.state.secondValue) + const { initialFirstValue, initialSecondValue } = getInitialFirstValues() + const [state, setState] = React.useState({ + firstValue: initialFirstValue, + secondValue: initialSecondValue, + }) + + const applyChanges = (): void => { + updateValues(state.firstValue, state.secondValue) } - handleReset: () => void = () => { - this.setState( - { firstValue: DEFAULT_FIRST, secondValue: DEFAULT_SECOND }, - this.applyChanges - ) - this.props.closeModal() + const handleReset = (): void => { + setState({ firstValue: DEFAULT_FIRST, secondValue: DEFAULT_SECOND }) + applyChanges() + closeModal() } - handleCancel: () => void = () => { - const { - initialFirstValue, - initialSecondValue, - } = this.getInitialFirstValues() - this.setState({ + const handleCancel = (): void => { + setState({ firstValue: initialFirstValue, secondValue: initialSecondValue, }) - this.props.closeModal() + closeModal() } - handleDone: () => void = () => { - this.applyChanges() - this.props.closeModal() + const handleDone = (): void => { + applyChanges() + closeModal() } - makeOnChange: ( + const makeOnChange: ( ordinality: 'first' | 'second' ) => ( event: React.ChangeEvent @@ -159,96 +152,88 @@ export class WellOrderModal extends React.Component< if (ordinality === 'first') { if ( VERTICAL_VALUES.includes(value as WellOrderOption) && - VERTICAL_VALUES.includes(this.state.secondValue) + VERTICAL_VALUES.includes(state.secondValue) ) { nextState = { ...nextState, secondValue: HORIZONTAL_VALUES[0] } } else if ( HORIZONTAL_VALUES.includes(value as WellOrderOption) && - HORIZONTAL_VALUES.includes(this.state.secondValue) + HORIZONTAL_VALUES.includes(state.secondValue) ) { nextState = { ...nextState, secondValue: VERTICAL_VALUES[0] } } } - this.setState(nextState) + setState(nextState) } - isSecondOptionDisabled: (wellOrderOption: WellOrderOption) => boolean = ( - value: WellOrderOption - ) => { - if (VERTICAL_VALUES.includes(this.state.firstValue)) { + const isSecondOptionDisabled: ( + wellOrderOption: WellOrderOption + ) => boolean = (value: WellOrderOption) => { + if (VERTICAL_VALUES.includes(state.firstValue)) { return VERTICAL_VALUES.includes(value) - } else if (HORIZONTAL_VALUES.includes(this.state.firstValue)) { + } else if (HORIZONTAL_VALUES.includes(state.firstValue)) { return HORIZONTAL_VALUES.includes(value) } else { return false } } - render(): React.ReactNode | null { - if (!this.props.isOpen) return null - - const { firstValue, secondValue } = this.state - const { firstName, secondName, t } = this.props - - return ( - - -
-

{t('modal:well_order.title')}

-

{t('modal:well_order.body')}

-
-
- -
- ({ - value, - name: t(`step_edit_form.field.well_order.option.${value}`), - }))} - /> - - {t('modal:well_order.then')} - - ({ - value, - name: t(`step_edit_form.field.well_order.option.${value}`), - disabled: this.isSecondOptionDisabled(value), - }))} - /> -
-
- - - -
-
- -
- - + if (!isOpen) return null + + return ( + + +
+

{t('modal:well_order.title')}

+

{t('modal:well_order.body')}

+
+
+ +
+ ({ + value, + name: t(`step_edit_form.field.well_order.option.${value}`), + }))} + /> + + {t('modal:well_order.then')} + + ({ + value, + name: t(`step_edit_form.field.well_order.option.${value}`), + disabled: isSecondOptionDisabled(value), + }))} + />
+
+ + + +
+
+ +
+ +
- - - ) - } +
+
+
+ ) } diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx index bfcab5ff3bd..f3867dae2ed 100644 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx +++ b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx @@ -128,7 +128,6 @@ export const WellOrderField = (props: WellOrderFieldProps): JSX.Element => { secondValue={secondValue} firstName={firstName} secondName={secondName} - t={t} /> ) diff --git a/protocol-designer/src/components/labware/SelectableLabware.tsx b/protocol-designer/src/components/labware/SelectableLabware.tsx index 86b61664b19..ffe39799487 100644 --- a/protocol-designer/src/components/labware/SelectableLabware.tsx +++ b/protocol-designer/src/components/labware/SelectableLabware.tsx @@ -42,20 +42,25 @@ const getChannelsFromNozleType = (nozzleType: NozzleType): ChannelType => { } } -export class SelectableLabware extends React.Component { - _getWellsFromRect: (rect: GenericRect) => WellGroup = rect => { - const selectedWells = getCollidingWells(rect) - return this._wellsFromSelected(selectedWells) - } +export const SelectableLabware = (props: Props): JSX.Element => { + const { + labwareProps, + selectedPrimaryWells, + selectWells, + deselectWells, + updateHighlightedWells, + nozzleType, + ingredNames, + wellContents, + } = props + const labwareDef = labwareProps.definition - _wellsFromSelected: ( + const _wellsFromSelected: ( selectedWells: WellGroup ) => WellGroup = selectedWells => { - const labwareDef = this.props.labwareProps.definition - // Returns PRIMARY WELLS from the selection. - if (this.props.nozzleType != null) { - const channels = getChannelsFromNozleType(this.props.nozzleType) + if (nozzleType != null) { + const channels = getChannelsFromNozleType(nozzleType) // for the wells that have been highlighted, // get all 8-well well sets and merge them const primaryWells: WellGroup = reduce( @@ -78,15 +83,19 @@ export class SelectableLabware extends React.Component { return selectedWells } - handleSelectionMove: (e: MouseEvent, rect: GenericRect) => void = ( + const _getWellsFromRect: (rect: GenericRect) => WellGroup = rect => { + const selectedWells = getCollidingWells(rect) + return _wellsFromSelected(selectedWells) + } + + const handleSelectionMove: (e: MouseEvent, rect: GenericRect) => void = ( e, rect ) => { - const labwareDef = this.props.labwareProps.definition if (!e.shiftKey) { - if (this.props.nozzleType != null) { - const channels = getChannelsFromNozleType(this.props.nozzleType) - const selectedWells = this._getWellsFromRect(rect) + if (nozzleType != null) { + const channels = getChannelsFromNozleType(nozzleType) + const selectedWells = _getWellsFromRect(rect) const allWellsForMulti: WellGroup = reduce( selectedWells, (acc: WellGroup, _, wellName: string): WellGroup => { @@ -100,104 +109,90 @@ export class SelectableLabware extends React.Component { }, {} ) - this.props.updateHighlightedWells(allWellsForMulti) + updateHighlightedWells(allWellsForMulti) } else { - this.props.updateHighlightedWells(this._getWellsFromRect(rect)) + updateHighlightedWells(_getWellsFromRect(rect)) } } } - handleSelectionDone: (e: MouseEvent, rect: GenericRect) => void = ( + const handleSelectionDone: (e: MouseEvent, rect: GenericRect) => void = ( e, rect ) => { - const wells = this._wellsFromSelected(this._getWellsFromRect(rect)) + const wells = _wellsFromSelected(_getWellsFromRect(rect)) if (e.shiftKey) { - this.props.deselectWells(wells) + deselectWells(wells) } else { - this.props.selectWells(wells) + selectWells(wells) } } - handleMouseEnterWell: (args: WellMouseEvent) => void = args => { - if (this.props.nozzleType != null) { - const channels = getChannelsFromNozleType(this.props.nozzleType) - const labwareDef = this.props.labwareProps.definition + const handleMouseEnterWell: (args: WellMouseEvent) => void = args => { + if (nozzleType != null) { + const channels = getChannelsFromNozleType(nozzleType) const wellSet = getWellSetForMultichannel( labwareDef, args.wellName, channels ) const nextHighlightedWells = arrayToWellGroup(wellSet || []) - nextHighlightedWells && - this.props.updateHighlightedWells(nextHighlightedWells) + nextHighlightedWells && updateHighlightedWells(nextHighlightedWells) } else { - this.props.updateHighlightedWells({ [args.wellName]: null }) + updateHighlightedWells({ [args.wellName]: null }) } } - handleMouseLeaveWell: (args: WellMouseEvent) => void = args => { - this.props.updateHighlightedWells({}) - } - - render(): React.ReactNode { - const { - labwareProps, - ingredNames, - wellContents, - nozzleType, - selectedPrimaryWells, - } = this.props - // For rendering, show all wells not just primary wells - const allSelectedWells = - nozzleType != null - ? reduce( - selectedPrimaryWells, - (acc, _, wellName): WellGroup => { - const channels = getChannelsFromNozleType(nozzleType) - const wellSet = getWellSetForMultichannel( - this.props.labwareProps.definition, - wellName, - channels - ) - if (!wellSet) return acc - return { ...acc, ...arrayToWellGroup(wellSet) } - }, - {} - ) - : selectedPrimaryWells + // For rendering, show all wells not just primary wells + const allSelectedWells = + nozzleType != null + ? reduce( + selectedPrimaryWells, + (acc, _, wellName): WellGroup => { + const channels = getChannelsFromNozleType(nozzleType) + const wellSet = getWellSetForMultichannel( + labwareDef, + wellName, + channels + ) + if (!wellSet) return acc + return { ...acc, ...arrayToWellGroup(wellSet) } + }, + {} + ) + : selectedPrimaryWells - return ( - - - {({ - makeHandleMouseEnterWell, - handleMouseLeaveWell, - tooltipWellName, - }) => ( - { - this.handleMouseLeaveWell(mouseEventArgs) - handleMouseLeaveWell(mouseEventArgs.event) - }} - onMouseEnterWell={({ wellName, event }) => { - if (wellContents !== null) { - this.handleMouseEnterWell({ wellName, event }) - makeHandleMouseEnterWell( - wellName, - wellContents[wellName]?.ingreds - )(event) - } - }} - /> - )} - - - ) - } + return ( + + + {({ + makeHandleMouseEnterWell, + handleMouseLeaveWell, + tooltipWellName, + }) => ( + { + handleMouseLeaveWell(mouseEventArgs) + updateHighlightedWells({}) + handleMouseLeaveWell(mouseEventArgs.event) + }} + onMouseEnterWell={({ wellName, event }) => { + if (wellContents !== null) { + handleMouseEnterWell({ wellName, event }) + makeHandleMouseEnterWell( + wellName, + wellContents[wellName]?.ingreds + )(event) + } + }} + /> + )} + + + ) } diff --git a/protocol-designer/src/components/labware/WellTooltip.tsx b/protocol-designer/src/components/labware/WellTooltip.tsx index cb88982d99c..16a9200e4a0 100644 --- a/protocol-designer/src/components/labware/WellTooltip.tsx +++ b/protocol-designer/src/components/labware/WellTooltip.tsx @@ -2,11 +2,13 @@ import * as React from 'react' import { Popper, Reference, Manager } from 'react-popper' import cx from 'classnames' +import { LocationLiquidState } from '@opentrons/step-generation' import { Portal } from '../portals/TopPortal' import { PillTooltipContents } from '../steplist/SubstepRow' + import styles from './labware.css' -import { LocationLiquidState } from '@opentrons/step-generation' -import { WellIngredientNames } from '../../steplist/types' + +import type { WellIngredientNames } from '../../steplist/types' const DEFAULT_TOOLTIP_OFFSET = 22 const WELL_BORDER_WIDTH = 4 @@ -40,10 +42,11 @@ const initialState: State = { tooltipOffset: DEFAULT_TOOLTIP_OFFSET, } -export class WellTooltip extends React.Component { - state: State = initialState +export const WellTooltip = (props: Props): JSX.Element => { + const { children, ingredNames } = props + const [state, setState] = React.useState(initialState) - makeHandleMouseEnterWell: ( + const makeHandleMouseEnterWell: ( wellName: string, wellIngreds: LocationLiquidState ) => (e: React.MouseEvent) => void = (wellName, wellIngreds) => e => { @@ -52,7 +55,7 @@ export class WellTooltip extends React.Component { const wellBoundingRect = target.getBoundingClientRect() const { left, top, height, width } = wellBoundingRect if (Object.keys(wellIngreds).length > 0 && left && top) { - this.setState({ + setState({ tooltipX: left + width / 2, tooltipY: top + height / 2, tooltipWellName: wellName, @@ -63,69 +66,73 @@ export class WellTooltip extends React.Component { } } - handleMouseLeaveWell: () => void = () => { - this.setState(initialState) + const handleMouseLeaveWell = (): void => { + setState(initialState) } - render(): React.ReactNode { - const { tooltipX, tooltipY, tooltipOffset } = this.state + const { + tooltipX, + tooltipY, + tooltipOffset, + tooltipWellIngreds, + tooltipWellName, + } = state - return ( - - - - {({ ref }) => ( - -
- - )} - - {this.props.children({ - makeHandleMouseEnterWell: this.makeHandleMouseEnterWell, - handleMouseLeaveWell: this.handleMouseLeaveWell, - tooltipWellName: this.state.tooltipWellName, - })} - {this.state.tooltipWellName && ( - - {({ ref, style, placement, arrowProps }) => { - return ( - -
- -
-
- - ) - }} - + return ( + <> + + + {({ ref }) => ( + +
+ )} - - - ) - } + + {children({ + makeHandleMouseEnterWell: makeHandleMouseEnterWell, + handleMouseLeaveWell: handleMouseLeaveWell, + tooltipWellName: tooltipWellName, + })} + {tooltipWellName && ( + + {({ ref, style, placement, arrowProps }) => { + return ( + +
+ +
+
+ + ) + }} + + )} + + + ) }