From 9f04a57df8ef1ec07b17f6dda3b70f50599c7c91 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Wed, 8 May 2024 16:23:05 -0400 Subject: [PATCH 01/12] render WellSelection in SelectSourceWells --- .../QuickTransferFlow/SelectSourceWells.tsx | 34 +++- .../organisms/WellSelection/SelectionRect.tsx | 85 +++++++++ app/src/organisms/WellSelection/index.tsx | 180 ++++++++++++++++++ app/src/organisms/WellSelection/types.ts | 52 +++++ app/src/organisms/WellSelection/utils.ts | 78 ++++++++ 5 files changed, 423 insertions(+), 6 deletions(-) create mode 100644 app/src/organisms/WellSelection/SelectionRect.tsx create mode 100644 app/src/organisms/WellSelection/index.tsx create mode 100644 app/src/organisms/WellSelection/types.ts create mode 100644 app/src/organisms/WellSelection/utils.ts diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx index 1cdc2b75595..ee7636fbc11 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx @@ -1,9 +1,11 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { Flex, SPACING } from '@opentrons/components' +import { Flex, JUSTIFY_CENTER, SPACING } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { ChildNavigation } from '../ChildNavigation' +import { ChildNavigation } from '../../organisms/ChildNavigation' +import { WellSelection } from '../../organisms/WellSelection' + +import type { SmallButton } from '../../atoms/buttons' import type { QuickTransferSetupState, @@ -22,6 +24,8 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { const { onNext, onBack, exitButtonProps, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const [selectedWells, setSelectedWells] = React.useState({}) + const handleClickNext = (): void => { // until well selection is implemented, select all wells and proceed to the next step if (state.source?.wells != null) { @@ -32,8 +36,9 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { onNext() } } + return ( - + <> - TODO: Add source well selection deck map + {state.source != null ? ( + { + setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) + }} + deselectWells={wellGroup => { + setSelectedWells(wellGroup) + }} + updateHighlightedWells={wellGroup => { + console.log(wellGroup) + }} + nozzleType={null} + wellContents={{}} + /> + ) : null} - + ) } diff --git a/app/src/organisms/WellSelection/SelectionRect.tsx b/app/src/organisms/WellSelection/SelectionRect.tsx new file mode 100644 index 00000000000..3ca6b5c5b93 --- /dev/null +++ b/app/src/organisms/WellSelection/SelectionRect.tsx @@ -0,0 +1,85 @@ +import * as React from 'react' + +import type { DragRect, GenericRect } from './types' + +interface SelectionRectProps { + onSelectionMove?: (e: MouseEvent, arg: GenericRect) => void + onSelectionDone?: (e: MouseEvent, arg: GenericRect) => void + children?: React.ReactNode +} + +export function SelectionRect(props: SelectionRectProps): JSX.Element { + const { onSelectionMove, onSelectionDone, children } = props + + const [positions, setPositions] = React.useState(null) + const parentRef = React.useRef(null) + + const getRect = (args: DragRect): GenericRect => { + const { xStart, yStart, xDynamic, yDynamic } = args + return { + x0: Math.min(xStart, xDynamic), + x1: Math.max(xStart, xDynamic), + y0: Math.min(yStart, yDynamic), + y1: Math.max(yStart, yDynamic), + } + } + + const handleDrag = (e: MouseEvent): void => { + setPositions(prevPositions => { + if (prevPositions) { + const nextRect = { + ...prevPositions, + xDynamic: e.clientX, + yDynamic: e.clientY, + } + const rect = getRect(nextRect) + onSelectionMove && onSelectionMove(e, rect) + + return nextRect + } + return prevPositions + }) + } + + const handleMouseUp = (e: MouseEvent): void => { + if (!(e instanceof MouseEvent)) { + return + } + const finalRect = positions && getRect(positions) + setPositions(prevPositions => { + return prevPositions === positions ? null : prevPositions + }) + // call onSelectionDone callback with {x0, x1, y0, y1} of final selection rectangle + onSelectionDone && finalRect && onSelectionDone(e, finalRect) + } + + const handleMouseDown: React.MouseEventHandler = e => { + setPositions({ + xStart: e.clientX, + xDynamic: e.clientX, + yStart: e.clientY, + yDynamic: e.clientY, + }) + } + + React.useEffect(() => { + document.addEventListener('mousemove', handleDrag) + document.addEventListener('mouseup', handleMouseUp) + return () => { + document.removeEventListener('mousemove', handleDrag) + document.removeEventListener('mouseup', handleMouseUp) + } + }, [handleDrag, handleMouseUp]) + + return ( +
{ + parentRef.current = ref + }} + style={{ width: '600px' }} + > + {children} +
+ ) +} diff --git a/app/src/organisms/WellSelection/index.tsx b/app/src/organisms/WellSelection/index.tsx new file mode 100644 index 00000000000..9cda6cbe1ca --- /dev/null +++ b/app/src/organisms/WellSelection/index.tsx @@ -0,0 +1,180 @@ +import * as React from 'react' +import reduce from 'lodash/reduce' + +import { LabwareRender, RobotCoordinateSpace } from '@opentrons/components' +import { COLUMN } from '@opentrons/shared-data' +import { + arrayToWellGroup, + getCollidingWells, + getWellSetForMultichannel, +} from './utils' +import { SelectionRect } from './SelectionRect' + +import type { WellMouseEvent, WellGroup } from '@opentrons/components' +import type { ContentsByWell, GenericRect, NozzleType } from './types' + +interface WellSelectionProps { + labwareProps: Omit< + React.ComponentProps, + 'selectedWells' + > + /** array of primary wells. Overrides labwareProps.selectedWells */ + selectedPrimaryWells: WellGroup + selectWells: (wellGroup: WellGroup) => unknown + deselectWells: (wellGroup: WellGroup) => unknown + updateHighlightedWells: (wellGroup: WellGroup) => unknown + nozzleType: NozzleType | null + wellContents: ContentsByWell +} + +type ChannelType = 8 | 96 + +const getChannelsFromNozzleType = (nozzleType: NozzleType): ChannelType => { + if (nozzleType === '8-channel' || nozzleType === COLUMN) { + return 8 + } else { + return 96 + } +} + +export function WellSelection(props: WellSelectionProps): JSX.Element { + const { + labwareProps, + selectedPrimaryWells, + selectWells, + deselectWells, + updateHighlightedWells, + nozzleType, + wellContents, + } = props + const labwareDef = labwareProps.definition + + const _wellsFromSelected: ( + selectedWells: WellGroup + ) => WellGroup = selectedWells => { + // Returns PRIMARY WELLS from the selection. + if (nozzleType != null) { + const channels = getChannelsFromNozzleType(nozzleType) + // for the wells that have been highlighted, + // get all 8-well well sets and merge them + const primaryWells: WellGroup = reduce( + selectedWells, + (acc: WellGroup, _, wellName: string): WellGroup => { + const wellSet = getWellSetForMultichannel( + labwareDef, + wellName, + channels + ) + if (!wellSet) return acc + return { ...acc, [wellSet[0]]: null } + }, + {} + ) + return primaryWells + } + + // single-channel or ingred selection mode + return selectedWells + } + + const _getWellsFromRect: (rect: GenericRect) => WellGroup = rect => { + const selectedWells = getCollidingWells(rect) + return _wellsFromSelected(selectedWells) + } + + const handleSelectionMove: (e: MouseEvent, rect: GenericRect) => void = ( + e, + rect + ) => { + if (!e.shiftKey) { + if (nozzleType != null) { + const channels = getChannelsFromNozzleType(nozzleType) + const selectedWells = _getWellsFromRect(rect) + const allWellsForMulti: WellGroup = reduce( + selectedWells, + (acc: WellGroup, _, wellName: string): WellGroup => { + const wellSetForMulti = + getWellSetForMultichannel(labwareDef, wellName, channels) || [] + const channelWells = arrayToWellGroup(wellSetForMulti) + return { + ...acc, + ...channelWells, + } + }, + {} + ) + updateHighlightedWells(allWellsForMulti) + } else { + updateHighlightedWells(_getWellsFromRect(rect)) + } + } + } + + const handleSelectionDone: (e: MouseEvent, rect: GenericRect) => void = ( + e, + rect + ) => { + const wells = _wellsFromSelected(_getWellsFromRect(rect)) + if (e.shiftKey) { + deselectWells(wells) + } else { + selectWells(wells) + } + } + + const handleMouseEnterWell: (args: WellMouseEvent) => void = args => { + if (nozzleType != null) { + const channels = getChannelsFromNozzleType(nozzleType) + const wellSet = getWellSetForMultichannel( + labwareDef, + args.wellName, + channels + ) + const nextHighlightedWells = arrayToWellGroup(wellSet || []) + nextHighlightedWells && updateHighlightedWells(nextHighlightedWells) + } else { + updateHighlightedWells({ [args.wellName]: null }) + } + } + + // For rendering, show all wells not just primary wells + const allSelectedWells = + nozzleType != null + ? reduce( + selectedPrimaryWells, + (acc, _, wellName): WellGroup => { + const channels = getChannelsFromNozzleType(nozzleType) + const wellSet = getWellSetForMultichannel( + labwareDef, + wellName, + channels + ) + if (!wellSet) return acc + return { ...acc, ...arrayToWellGroup(wellSet) } + }, + {} + ) + : selectedPrimaryWells + + return ( + + + { + updateHighlightedWells({}) + }} + onMouseEnterWell={({ wellName, event }) => { + if (wellContents !== null) { + handleMouseEnterWell({ wellName, event }) + } + }} + /> + + + ) +} diff --git a/app/src/organisms/WellSelection/types.ts b/app/src/organisms/WellSelection/types.ts new file mode 100644 index 00000000000..11d38d57c51 --- /dev/null +++ b/app/src/organisms/WellSelection/types.ts @@ -0,0 +1,52 @@ +export interface DragRect { + xStart: number + yStart: number + xDynamic: number + yDynamic: number +} + +export interface GenericRect { + x0: number + x1: number + y0: number + y1: number +} + +export interface BoundingRect { + x: number + y: number + width: number + height: number +} + +export interface LocationLiquidState { + [ingredGroup: string]: { volume: number } +} + +export interface WellContents { + // eg 'A1', 'A2' etc + wellName?: string + groupIds: string[] + ingreds: LocationLiquidState + highlighted?: boolean + selected?: boolean + maxVolume?: number +} +export type ContentsByWell = { + [wellName: string]: WellContents +} | null + +export const COLUMN = 'COLUMN' +const SINGLE = 'SINGLE' +const ROW = 'ROW' +const QUADRANT = 'QUADRANT' +export const ALL = 'ALL' + +export type NozzleConfigurationStyle = + | typeof COLUMN + | typeof SINGLE + | typeof ROW + | typeof QUADRANT + | typeof ALL + +export type NozzleType = NozzleConfigurationStyle | '8-channel' diff --git a/app/src/organisms/WellSelection/utils.ts b/app/src/organisms/WellSelection/utils.ts new file mode 100644 index 00000000000..7ee20089b41 --- /dev/null +++ b/app/src/organisms/WellSelection/utils.ts @@ -0,0 +1,78 @@ +import { + INTERACTIVE_WELL_DATA_ATTRIBUTE, + makeWellSetHelpers, +} from '@opentrons/shared-data' + +import type { WellGroup } from '@opentrons/components' +import type { WellSetHelpers } from '@opentrons/shared-data' +import type { BoundingRect, GenericRect } from './types' + +// Collision detection for SelectionRect / SelectableLabware +export const rectCollision = ( + rect1: BoundingRect, + rect2: BoundingRect +): boolean => + rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.height + rect1.y > rect2.y + +export function clientRectToBoundingRect(rect: ClientRect): BoundingRect { + return { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height, + } +} + +export const getCollidingWells = (rectPositions: GenericRect): WellGroup => { + // Returns set of selected wells under a collision rect + const { x0, y0, x1, y1 } = rectPositions + const selectionBoundingRect = { + x: Math.min(x0, x1), + y: Math.min(y0, y1), + width: Math.abs(x1 - x0), + height: Math.abs(y1 - y0), + } + // NOTE: querySelectorAll returns a NodeList, so you need to unpack it as an Array to do .filter + const selectableElems: HTMLElement[] = [ + ...document.querySelectorAll( + `[${INTERACTIVE_WELL_DATA_ATTRIBUTE}]` + ), + ] + const collidedElems = selectableElems.filter((selectableElem, i) => + rectCollision( + selectionBoundingRect, + clientRectToBoundingRect(selectableElem.getBoundingClientRect()) + ) + ) + const collidedWellData = collidedElems.reduce( + (acc: WellGroup, elem): WellGroup => { + if ( + INTERACTIVE_WELL_DATA_ATTRIBUTE.replace('data-', '') in elem.dataset + ) { + const wellName = elem.dataset.wellname + return wellName != null ? { ...acc, [wellName]: null } : acc + } + + return acc + }, + {} + ) + return collidedWellData +} +export const arrayToWellGroup = (w: string[]): WellGroup => + w.reduce((acc, wellName) => ({ ...acc, [wellName]: null }), {}) +// cross-PD memoization of well set utils +const wellSetHelpers: WellSetHelpers = makeWellSetHelpers() +const { + canPipetteUseLabware, + getAllWellSetsForLabware, + getWellSetForMultichannel, +} = wellSetHelpers +export { + canPipetteUseLabware, + getAllWellSetsForLabware, + getWellSetForMultichannel, +} From 8d826af409e5a9cfb96608ee689b1412bd85ad58 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Mon, 13 May 2024 08:39:16 -0400 Subject: [PATCH 02/12] hide labware outline, add well fill, well labels, reset button --- app/src/assets/localization/en/shared.json | 1 + .../QuickTransferFlow/SelectSourceWells.tsx | 25 +++++++------ app/src/organisms/WellSelection/index.tsx | 20 +++++++++-- .../hardware-sim/Labware/LabwareRender.tsx | 5 ++- .../labwareInternals/StaticLabware.tsx | 36 ++++++++++++------- 5 files changed, 61 insertions(+), 26 deletions(-) diff --git a/app/src/assets/localization/en/shared.json b/app/src/assets/localization/en/shared.json index 5613508b242..a2cd4e01239 100644 --- a/app/src/assets/localization/en/shared.json +++ b/app/src/assets/localization/en/shared.json @@ -54,6 +54,7 @@ "refresh": "refresh", "remember_my_selection_and_do_not_ask_again": "Remember my selection and don't ask again", "reset_all": "Reset all", + "reset": "Reset", "restart": "restart", "resume": "resume", "return": "return", diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx index ee7636fbc11..f2f1287457c 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx @@ -21,20 +21,25 @@ interface SelectSourceWellsProps { } export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { - const { onNext, onBack, exitButtonProps, state, dispatch } = props + const { onNext, onBack, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) const [selectedWells, setSelectedWells] = React.useState({}) const handleClickNext = (): void => { - // until well selection is implemented, select all wells and proceed to the next step - if (state.source?.wells != null) { - dispatch({ - type: 'SET_SOURCE_WELLS', - wells: Object.keys(state.source.wells), - }) - onNext() - } + dispatch({ + type: 'SET_SOURCE_WELLS', + wells: Object.keys(selectedWells), + }) + onNext() + } + + const resetButtonProps: React.ComponentProps = { + buttonType: 'tertiaryLowLight', + buttonText: t('shared:reset'), + onClick: () => { + setSelectedWells({}) + }, } return ( @@ -45,7 +50,7 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { buttonText={i18n.format(t('shared:continue'), 'capitalize')} onClickButton={handleClickNext} buttonIsDisabled={false} - secondaryButtonProps={exitButtonProps} + secondaryButtonProps={resetButtonProps} top={SPACING.spacing8} /> { + wellFill[wellName] = COLORS.blue35 + }) + Object.keys(allSelectedWells).forEach(wellName => { + wellFill[wellName] = COLORS.blue50 + }) + return ( diff --git a/components/src/hardware-sim/Labware/LabwareRender.tsx b/components/src/hardware-sim/Labware/LabwareRender.tsx index b3133fe1813..853beeab858 100644 --- a/components/src/hardware-sim/Labware/LabwareRender.tsx +++ b/components/src/hardware-sim/Labware/LabwareRender.tsx @@ -58,10 +58,12 @@ export interface LabwareRenderProps { onMouseLeaveWell?: (e: WellMouseEvent) => unknown gRef?: React.RefObject onLabwareClick?: () => void + /** Hide labware outline */ + hideOutline?: boolean } export const LabwareRender = (props: LabwareRenderProps): JSX.Element => { - const { gRef, definition } = props + const { gRef, definition, hideOutline } = props const cornerOffsetFromSlot = definition.cornerOffsetFromSlot const labwareLoadName = definition.parameters.loadName @@ -101,6 +103,7 @@ export const LabwareRender = (props: LabwareRenderProps): JSX.Element => { onMouseLeaveWell={props.onMouseLeaveWell} onLabwareClick={props.onLabwareClick} highlight={props.highlight} + hideOutline={hideOutline} /> {props.wellStroke != null ? ( - - - + + {!hideOutline ? ( + + + + ) : null} {flatMap( - props.definition.ordering, + definition.ordering, (row: string[], i: number, c: string[][]) => { return row.map(wellName => { return ( {isTiprack ? ( - + ) : null} ) From 832c5b3e7201b183de3115c3bf01f81c689c616a Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Mon, 13 May 2024 16:54:00 -0400 Subject: [PATCH 03/12] highlight rectangle selected wells --- .../QuickTransferFlow/SelectSourceWells.tsx | 9 +- .../organisms/WellSelection/SelectionRect.tsx | 8 +- app/src/organisms/WellSelection/index.tsx | 120 ++++++------------ .../hardware-sim/Labware/LabwareRender.tsx | 6 +- .../labwareInternals/StaticLabware.tsx | 4 + .../Labware/labwareInternals/Well.tsx | 4 +- 6 files changed, 58 insertions(+), 93 deletions(-) diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx index f2f1287457c..9eee7a7806d 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx @@ -60,19 +60,12 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { > {state.source != null ? ( { setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) }} - deselectWells={wellGroup => { - setSelectedWells(wellGroup) - }} - updateHighlightedWells={wellGroup => { - console.log(wellGroup) - }} nozzleType={null} - wellContents={{}} /> ) : null} diff --git a/app/src/organisms/WellSelection/SelectionRect.tsx b/app/src/organisms/WellSelection/SelectionRect.tsx index 3ca6b5c5b93..2a8dc1f59d0 100644 --- a/app/src/organisms/WellSelection/SelectionRect.tsx +++ b/app/src/organisms/WellSelection/SelectionRect.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import type { DragRect, GenericRect } from './types' interface SelectionRectProps { - onSelectionMove?: (e: MouseEvent, arg: GenericRect) => void - onSelectionDone?: (e: MouseEvent, arg: GenericRect) => void + onSelectionMove?: (rect: GenericRect) => void + onSelectionDone?: (rect: GenericRect) => void children?: React.ReactNode } @@ -33,7 +33,7 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { yDynamic: e.clientY, } const rect = getRect(nextRect) - onSelectionMove && onSelectionMove(e, rect) + onSelectionMove && onSelectionMove(rect) return nextRect } @@ -50,7 +50,7 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { return prevPositions === positions ? null : prevPositions }) // call onSelectionDone callback with {x0, x1, y0, y1} of final selection rectangle - onSelectionDone && finalRect && onSelectionDone(e, finalRect) + onSelectionDone && finalRect && onSelectionDone(finalRect) } const handleMouseDown: React.MouseEventHandler = e => { diff --git a/app/src/organisms/WellSelection/index.tsx b/app/src/organisms/WellSelection/index.tsx index ec093a2fa34..fd7013d0725 100644 --- a/app/src/organisms/WellSelection/index.tsx +++ b/app/src/organisms/WellSelection/index.tsx @@ -15,21 +15,16 @@ import { } from './utils' import { SelectionRect } from './SelectionRect' -import type { WellMouseEvent, WellFill, WellGroup } from '@opentrons/components' -import type { ContentsByWell, GenericRect, NozzleType } from './types' +import type { WellFill, WellGroup, WellStroke } from '@opentrons/components' +import type { LabwareDefinition2 } from '@opentrons/shared-data' +import type { GenericRect, NozzleType } from './types' interface WellSelectionProps { - labwareProps: Omit< - React.ComponentProps, - 'selectedWells' - > + definition: LabwareDefinition2 /** array of primary wells. Overrides labwareProps.selectedWells */ selectedPrimaryWells: WellGroup selectWells: (wellGroup: WellGroup) => unknown - deselectWells: (wellGroup: WellGroup) => unknown - updateHighlightedWells: (wellGroup: WellGroup) => unknown nozzleType: NozzleType | null - wellContents: ContentsByWell } type ChannelType = 8 | 96 @@ -43,16 +38,9 @@ const getChannelsFromNozzleType = (nozzleType: NozzleType): ChannelType => { } export function WellSelection(props: WellSelectionProps): JSX.Element { - const { - labwareProps, - selectedPrimaryWells, - selectWells, - deselectWells, - updateHighlightedWells, - nozzleType, - wellContents, - } = props - const labwareDef = labwareProps.definition + const { definition, selectedPrimaryWells, selectWells, nozzleType } = props + + const [highlightedWells, setHighlightedWells] = React.useState({}) const _wellsFromSelected: ( selectedWells: WellGroup @@ -66,7 +54,7 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { selectedWells, (acc: WellGroup, _, wellName: string): WellGroup => { const wellSet = getWellSetForMultichannel( - labwareDef, + definition, wellName, channels ) @@ -87,61 +75,36 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { return _wellsFromSelected(selectedWells) } - const handleSelectionMove: (e: MouseEvent, rect: GenericRect) => void = ( - e, - rect - ) => { - if (!e.shiftKey) { - if (nozzleType != null) { - const channels = getChannelsFromNozzleType(nozzleType) - const selectedWells = _getWellsFromRect(rect) - const allWellsForMulti: WellGroup = reduce( - selectedWells, - (acc: WellGroup, _, wellName: string): WellGroup => { - const wellSetForMulti = - getWellSetForMultichannel(labwareDef, wellName, channels) || [] - const channelWells = arrayToWellGroup(wellSetForMulti) - return { - ...acc, - ...channelWells, - } - }, - {} - ) - updateHighlightedWells(allWellsForMulti) - } else { - updateHighlightedWells(_getWellsFromRect(rect)) - } - } - } - - const handleSelectionDone: (e: MouseEvent, rect: GenericRect) => void = ( - e, - rect - ) => { - const wells = _wellsFromSelected(_getWellsFromRect(rect)) - if (e.shiftKey) { - deselectWells(wells) - } else { - selectWells(wells) - } - } - - const handleMouseEnterWell: (args: WellMouseEvent) => void = args => { + const handleSelectionMove: (rect: GenericRect) => void = rect => { if (nozzleType != null) { const channels = getChannelsFromNozzleType(nozzleType) - const wellSet = getWellSetForMultichannel( - labwareDef, - args.wellName, - channels + const selectedWells = _getWellsFromRect(rect) + const allWellsForMulti: WellGroup = reduce( + selectedWells, + (acc: WellGroup, _, wellName: string): WellGroup => { + const wellSetForMulti = + getWellSetForMultichannel(definition, wellName, channels) || [] + const channelWells = arrayToWellGroup(wellSetForMulti) + return { + ...acc, + ...channelWells, + } + }, + {} ) - const nextHighlightedWells = arrayToWellGroup(wellSet || []) - nextHighlightedWells && updateHighlightedWells(nextHighlightedWells) + setHighlightedWells(allWellsForMulti) } else { - updateHighlightedWells({ [args.wellName]: null }) + setHighlightedWells(_getWellsFromRect(rect)) } } + const handleSelectionDone: (rect: GenericRect) => void = rect => { + const wells = _wellsFromSelected(_getWellsFromRect(rect)) + + selectWells(wells) + setHighlightedWells({}) + } + // For rendering, show all wells not just primary wells const allSelectedWells = nozzleType != null @@ -150,7 +113,7 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { (acc, _, wellName): WellGroup => { const channels = getChannelsFromNozzleType(nozzleType) const wellSet = getWellSetForMultichannel( - labwareDef, + definition, wellName, channels ) @@ -162,12 +125,17 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { : selectedPrimaryWells const wellFill: WellFill = {} - Object.keys(labwareProps.definition.wells).forEach(wellName => { + const wellStroke: WellStroke = {} + Object.keys(definition.wells).forEach(wellName => { wellFill[wellName] = COLORS.blue35 + wellStroke[wellName] = COLORS.transparent }) Object.keys(allSelectedWells).forEach(wellName => { wellFill[wellName] = COLORS.blue50 }) + Object.keys(highlightedWells).forEach(wellName => { + wellFill[wellName] = COLORS.blue50 + }) return ( { - updateHighlightedWells({}) - }} - onMouseEnterWell={({ wellName, event }) => { - if (wellContents !== null) { - handleMouseEnterWell({ wellName, event }) - } - }} hideOutline + isInteractive wellLabelOption={WELL_LABEL_OPTIONS.SHOW_LABEL_INSIDE} wellFill={wellFill} + wellStroke={wellStroke} /> diff --git a/components/src/hardware-sim/Labware/LabwareRender.tsx b/components/src/hardware-sim/Labware/LabwareRender.tsx index 853beeab858..b281aa8583f 100644 --- a/components/src/hardware-sim/Labware/LabwareRender.tsx +++ b/components/src/hardware-sim/Labware/LabwareRender.tsx @@ -60,10 +60,12 @@ export interface LabwareRenderProps { onLabwareClick?: () => void /** Hide labware outline */ hideOutline?: boolean + /** Provides well data attribute */ + isInteractive?: boolean } export const LabwareRender = (props: LabwareRenderProps): JSX.Element => { - const { gRef, definition, hideOutline } = props + const { gRef, definition, hideOutline, isInteractive } = props const cornerOffsetFromSlot = definition.cornerOffsetFromSlot const labwareLoadName = definition.parameters.loadName @@ -97,6 +99,7 @@ export const LabwareRender = (props: LabwareRenderProps): JSX.Element => { transform={`translate(${cornerOffsetFromSlot.x}, ${cornerOffsetFromSlot.y})`} ref={gRef} > + {/* TODO(bh, 2024-05-13): refactor rendering of wells - multiple layers of styled wells, DOM ordering determines which are visible */} { onLabwareClick={props.onLabwareClick} highlight={props.highlight} hideOutline={hideOutline} + isInteractive={isInteractive} /> {props.wellStroke != null ? ( unknown /** Optional callback to be executed when mouse leaves a well element */ onMouseLeaveWell?: (e: WellMouseEvent) => unknown + /** Provides well data attribute */ + isInteractive?: boolean } const TipDecoration = React.memo(function TipDecoration(props: { @@ -55,6 +57,7 @@ export function StaticLabwareComponent(props: StaticLabwareProps): JSX.Element { definition, hideOutline = false, highlight, + isInteractive, onLabwareClick, onMouseEnterWell, onMouseLeaveWell, @@ -80,6 +83,7 @@ export function StaticLabwareComponent(props: StaticLabwareProps): JSX.Element { well={definition.wells[wellName]} onMouseEnterWell={onMouseEnterWell} onMouseLeaveWell={onMouseLeaveWell} + isInteractive={isInteractive} {...(isTiprack ? STYLE_BY_WELL_CONTENTS.tipPresent : STYLE_BY_WELL_CONTENTS.defaultWell)} diff --git a/components/src/hardware-sim/Labware/labwareInternals/Well.tsx b/components/src/hardware-sim/Labware/labwareInternals/Well.tsx index 53d3dcdf688..5a7d8760646 100644 --- a/components/src/hardware-sim/Labware/labwareInternals/Well.tsx +++ b/components/src/hardware-sim/Labware/labwareInternals/Well.tsx @@ -16,6 +16,8 @@ export interface WellProps extends StyleProps { /** Optional callback, called with WellMouseEvent args onMouseOver */ onMouseEnterWell?: (e: WellMouseEvent) => unknown onMouseLeaveWell?: (e: WellMouseEvent) => unknown + /** Provides well data attribute */ + isInteractive?: boolean } export function WellComponent(props: WellProps): JSX.Element { @@ -27,10 +29,10 @@ export function WellComponent(props: WellProps): JSX.Element { fill = COLORS.white, onMouseEnterWell, onMouseLeaveWell, + isInteractive = onMouseEnterWell != null || onMouseLeaveWell != null, } = props const { x, y } = well - const isInteractive = onMouseEnterWell != null || onMouseLeaveWell != null const pointerEvents: React.CSSProperties['pointerEvents'] = isInteractive ? 'auto' : 'none' From 8880e03ea83032de26cc73a7dc72ccad383af067 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Thu, 16 May 2024 13:55:37 -0400 Subject: [PATCH 04/12] convert selection rect to touch events --- .../organisms/WellSelection/SelectionRect.tsx | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/app/src/organisms/WellSelection/SelectionRect.tsx b/app/src/organisms/WellSelection/SelectionRect.tsx index 2a8dc1f59d0..69326635b70 100644 --- a/app/src/organisms/WellSelection/SelectionRect.tsx +++ b/app/src/organisms/WellSelection/SelectionRect.tsx @@ -24,13 +24,14 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { } } - const handleDrag = (e: MouseEvent): void => { + const handleDrag = (e: TouchEvent): void => { + const touch = e.touches[0] setPositions(prevPositions => { if (prevPositions) { const nextRect = { ...prevPositions, - xDynamic: e.clientX, - yDynamic: e.clientY, + xDynamic: touch.clientX, + yDynamic: touch.clientY, } const rect = getRect(nextRect) onSelectionMove && onSelectionMove(rect) @@ -41,8 +42,8 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { }) } - const handleMouseUp = (e: MouseEvent): void => { - if (!(e instanceof MouseEvent)) { + const handleTouchEnd = (e: TouchEvent): void => { + if (!(e instanceof TouchEvent)) { return } const finalRect = positions && getRect(positions) @@ -53,30 +54,33 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { onSelectionDone && finalRect && onSelectionDone(finalRect) } - const handleMouseDown: React.MouseEventHandler = e => { + const handleTouchStart: React.TouchEventHandler = e => { + const touch = e.touches[0] setPositions({ - xStart: e.clientX, - xDynamic: e.clientX, - yStart: e.clientY, - yDynamic: e.clientY, + xStart: touch.clientX, + xDynamic: touch.clientX, + yStart: touch.clientY, + yDynamic: touch.clientY, }) } React.useEffect(() => { - document.addEventListener('mousemove', handleDrag) - document.addEventListener('mouseup', handleMouseUp) + document.addEventListener('touchmove', handleDrag) + document.addEventListener('touchend', handleTouchEnd) return () => { - document.removeEventListener('mousemove', handleDrag) - document.removeEventListener('mouseup', handleMouseUp) + document.removeEventListener('touchmove', handleDrag) + document.removeEventListener('touchend', handleTouchEnd) } - }, [handleDrag, handleMouseUp]) + }, [handleDrag, handleTouchEnd]) return (
{ parentRef.current = ref }} + // don't hard-code this width style={{ width: '600px' }} > {children} From ea5234593396e9663e65315f5b57eadb39b823bb Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Thu, 16 May 2024 14:45:19 -0400 Subject: [PATCH 05/12] add mouse events for local development --- .../organisms/WellSelection/SelectionRect.tsx | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/app/src/organisms/WellSelection/SelectionRect.tsx b/app/src/organisms/WellSelection/SelectionRect.tsx index 69326635b70..2851518139e 100644 --- a/app/src/organisms/WellSelection/SelectionRect.tsx +++ b/app/src/organisms/WellSelection/SelectionRect.tsx @@ -24,14 +24,23 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { } } - const handleDrag = (e: TouchEvent): void => { - const touch = e.touches[0] + const handleDrag = (e: TouchEvent | MouseEvent): void => { + let xDynamic: number + let yDynamic: number + if (e instanceof TouchEvent) { + const touch = e.touches[0] + xDynamic = touch.clientX + yDynamic = touch.clientY + } else { + xDynamic = e.clientX + yDynamic = e.clientY + } setPositions(prevPositions => { if (prevPositions) { const nextRect = { ...prevPositions, - xDynamic: touch.clientX, - yDynamic: touch.clientY, + xDynamic, + yDynamic, } const rect = getRect(nextRect) onSelectionMove && onSelectionMove(rect) @@ -42,8 +51,8 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { }) } - const handleTouchEnd = (e: TouchEvent): void => { - if (!(e instanceof TouchEvent)) { + const handleDragEnd = (e: TouchEvent | MouseEvent): void => { + if (!(e instanceof TouchEvent) && !(e instanceof MouseEvent)) { return } const finalRect = positions && getRect(positions) @@ -64,18 +73,32 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { }) } + const handleMouseDown: React.MouseEventHandler = e => { + setPositions({ + xStart: e.clientX, + xDynamic: e.clientX, + yStart: e.clientY, + yDynamic: e.clientY, + }) + } + React.useEffect(() => { document.addEventListener('touchmove', handleDrag) - document.addEventListener('touchend', handleTouchEnd) + document.addEventListener('touchend', handleDragEnd) + document.addEventListener('mousemove', handleDrag) + document.addEventListener('mouseup', handleDragEnd) return () => { document.removeEventListener('touchmove', handleDrag) - document.removeEventListener('touchend', handleTouchEnd) + document.removeEventListener('touchend', handleDragEnd) + document.removeEventListener('mousemove', handleDrag) + document.removeEventListener('mouseup', handleDragEnd) } - }, [handleDrag, handleTouchEnd]) + }, [handleDrag, handleDragEnd]) return (
{ parentRef.current = ref From fa563ddb1fc5e6d3e77abe55d11303b5bd080979 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Thu, 16 May 2024 14:46:41 -0400 Subject: [PATCH 06/12] initialize selected wells to reducer source wells value --- app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx index 9eee7a7806d..a9e76dd6a8a 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx @@ -24,7 +24,12 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { const { onNext, onBack, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - const [selectedWells, setSelectedWells] = React.useState({}) + const sourceWells = state.sourceWells ?? [] + const sourceWellGroup = sourceWells.reduce((acc, well) => { + return { ...acc, [well]: null } + }, {}) + + const [selectedWells, setSelectedWells] = React.useState(sourceWellGroup) const handleClickNext = (): void => { dispatch({ From a52fff1ee8f44d6c8dc905e21c020b1b2c0f85b7 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Thu, 16 May 2024 15:25:53 -0400 Subject: [PATCH 07/12] fix selection rect lint warnings --- .../organisms/WellSelection/SelectionRect.tsx | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/app/src/organisms/WellSelection/SelectionRect.tsx b/app/src/organisms/WellSelection/SelectionRect.tsx index 2851518139e..94e0df02475 100644 --- a/app/src/organisms/WellSelection/SelectionRect.tsx +++ b/app/src/organisms/WellSelection/SelectionRect.tsx @@ -24,44 +24,50 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { } } - const handleDrag = (e: TouchEvent | MouseEvent): void => { - let xDynamic: number - let yDynamic: number - if (e instanceof TouchEvent) { - const touch = e.touches[0] - xDynamic = touch.clientX - yDynamic = touch.clientY - } else { - xDynamic = e.clientX - yDynamic = e.clientY - } - setPositions(prevPositions => { - if (prevPositions) { - const nextRect = { - ...prevPositions, - xDynamic, - yDynamic, + const handleDrag = React.useCallback( + (e: TouchEvent | MouseEvent): void => { + let xDynamic: number + let yDynamic: number + if (e instanceof TouchEvent) { + const touch = e.touches[0] + xDynamic = touch.clientX + yDynamic = touch.clientY + } else { + xDynamic = e.clientX + yDynamic = e.clientY + } + setPositions(prevPositions => { + if (prevPositions != null) { + const nextRect = { + ...prevPositions, + xDynamic, + yDynamic, + } + const rect = getRect(nextRect) + onSelectionMove != null && onSelectionMove(rect) + + return nextRect } - const rect = getRect(nextRect) - onSelectionMove && onSelectionMove(rect) + return prevPositions + }) + }, + [onSelectionMove] + ) - return nextRect + const handleDragEnd = React.useCallback( + (e: TouchEvent | MouseEvent): void => { + if (!(e instanceof TouchEvent) && !(e instanceof MouseEvent)) { + return } - return prevPositions - }) - } - - const handleDragEnd = (e: TouchEvent | MouseEvent): void => { - if (!(e instanceof TouchEvent) && !(e instanceof MouseEvent)) { - return - } - const finalRect = positions && getRect(positions) - setPositions(prevPositions => { - return prevPositions === positions ? null : prevPositions - }) - // call onSelectionDone callback with {x0, x1, y0, y1} of final selection rectangle - onSelectionDone && finalRect && onSelectionDone(finalRect) - } + const finalRect = positions != null ? getRect(positions) : null + setPositions(prevPositions => { + return prevPositions === positions ? null : prevPositions + }) + // call onSelectionDone callback with {x0, x1, y0, y1} of final selection rectangle + onSelectionDone != null && finalRect != null && onSelectionDone(finalRect) + }, + [onSelectionDone, positions] + ) const handleTouchStart: React.TouchEventHandler = e => { const touch = e.touches[0] From bcc11869cca639ce41d5e57a190543128b94d848 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Thu, 16 May 2024 15:43:13 -0400 Subject: [PATCH 08/12] implement basic destination well selection --- .../QuickTransferFlow/SelectDestWells.tsx | 61 +++++++++++++------ .../QuickTransferFlow/SelectSourceWells.tsx | 2 +- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx index 39ef17cffe9..ec6cf14e4d6 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx @@ -1,10 +1,11 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { Flex, SPACING } from '@opentrons/components' +import { Flex, JUSTIFY_CENTER, SPACING } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' import { ChildNavigation } from '../ChildNavigation' +import { WellSelection } from '../../organisms/WellSelection' +import type { SmallButton } from '../../atoms/buttons' import type { QuickTransferSetupState, QuickTransferWizardAction, @@ -13,47 +14,67 @@ import type { interface SelectDestWellsProps { onNext: () => void onBack: () => void - exitButtonProps: React.ComponentProps state: QuickTransferSetupState dispatch: React.Dispatch } export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { - const { onNext, onBack, exitButtonProps, state, dispatch } = props + const { onNext, onBack, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const destinationWells = state.destinationWells ?? [] + const destinationWellGroup = destinationWells.reduce((acc, well) => { + return { ...acc, [well]: null } + }, {}) + + const [selectedWells, setSelectedWells] = React.useState(destinationWellGroup) + const handleClickNext = (): void => { - // until well selection is implemented, select all wells and proceed to the next step - if (state.destination === 'source' && state.source != null) { - dispatch({ - type: 'SET_DEST_WELLS', - wells: Object.keys(state.source.wells), - }) - } else if (state.destination !== 'source' && state.destination != null) { - dispatch({ - type: 'SET_DEST_WELLS', - wells: Object.keys(state.destination.wells), - }) - } + dispatch({ + type: 'SET_DEST_WELLS', + wells: Object.keys(selectedWells), + }) onNext() } + + const resetButtonProps: React.ComponentProps = { + buttonType: 'tertiaryLowLight', + buttonText: t('shared:reset'), + onClick: () => { + setSelectedWells({}) + }, + } + return ( - + <> - TODO: Add destination well selection deck map + {state.destination != null && state.source != null ? ( + { + setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) + }} + // TODO: nozzle type + nozzleType={null} + /> + ) : null} - + ) } diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx index a9e76dd6a8a..178daab961a 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx @@ -15,7 +15,6 @@ import type { interface SelectSourceWellsProps { onNext: () => void onBack: () => void - exitButtonProps: React.ComponentProps state: QuickTransferSetupState dispatch: React.Dispatch } @@ -70,6 +69,7 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { selectWells={wellGroup => { setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) }} + // TODO: nozzle type nozzleType={null} /> ) : null} From dcf40afb712cdc73215d05dec95ae9c22e823af4 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Thu, 16 May 2024 16:02:41 -0400 Subject: [PATCH 09/12] feat(app): create well selection component creates ODD well selection component for use in quick transfer. implements basic source and destination quick transfer well selection. closes PLAT-172, PLAT-175 --- app/src/organisms/WellSelection/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/organisms/WellSelection/index.tsx b/app/src/organisms/WellSelection/index.tsx index fd7013d0725..232af5eb8dd 100644 --- a/app/src/organisms/WellSelection/index.tsx +++ b/app/src/organisms/WellSelection/index.tsx @@ -132,9 +132,11 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { }) Object.keys(allSelectedWells).forEach(wellName => { wellFill[wellName] = COLORS.blue50 + wellStroke[wellName] = COLORS.transparent }) Object.keys(highlightedWells).forEach(wellName => { wellFill[wellName] = COLORS.blue50 + wellStroke[wellName] = COLORS.transparent }) return ( @@ -145,7 +147,6 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { Date: Fri, 17 May 2024 15:00:21 -0400 Subject: [PATCH 10/12] substitute channels for nozzle type --- .../QuickTransferFlow/SelectDestWells.tsx | 3 +- .../QuickTransferFlow/SelectSourceWells.tsx | 3 +- app/src/organisms/WellSelection/index.tsx | 31 ++++++------------ app/src/organisms/WellSelection/types.ts | 32 ------------------- app/src/organisms/WellSelection/utils.ts | 6 ++-- 5 files changed, 16 insertions(+), 59 deletions(-) diff --git a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx index ec6cf14e4d6..a891099f90e 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx @@ -70,8 +70,7 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { selectWells={wellGroup => { setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) }} - // TODO: nozzle type - nozzleType={null} + channels={state.pipette?.channels ?? 1} /> ) : null} diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx index aebeddbdcac..59ee9693c73 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx @@ -68,8 +68,7 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { selectWells={wellGroup => { setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) }} - // TODO: nozzle type - nozzleType={null} + channels={state.pipette?.channels ?? 1} /> ) : null} diff --git a/app/src/organisms/WellSelection/index.tsx b/app/src/organisms/WellSelection/index.tsx index 232af5eb8dd..00615cc3b26 100644 --- a/app/src/organisms/WellSelection/index.tsx +++ b/app/src/organisms/WellSelection/index.tsx @@ -7,7 +7,6 @@ import { RobotCoordinateSpace, WELL_LABEL_OPTIONS, } from '@opentrons/components' -import { COLUMN } from '@opentrons/shared-data' import { arrayToWellGroup, getCollidingWells, @@ -16,29 +15,22 @@ import { import { SelectionRect } from './SelectionRect' import type { WellFill, WellGroup, WellStroke } from '@opentrons/components' -import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { GenericRect, NozzleType } from './types' +import type { + LabwareDefinition2, + PipetteChannels, +} from '@opentrons/shared-data' +import type { GenericRect } from './types' interface WellSelectionProps { definition: LabwareDefinition2 /** array of primary wells. Overrides labwareProps.selectedWells */ selectedPrimaryWells: WellGroup selectWells: (wellGroup: WellGroup) => unknown - nozzleType: NozzleType | null -} - -type ChannelType = 8 | 96 - -const getChannelsFromNozzleType = (nozzleType: NozzleType): ChannelType => { - if (nozzleType === '8-channel' || nozzleType === COLUMN) { - return 8 - } else { - return 96 - } + channels: PipetteChannels } export function WellSelection(props: WellSelectionProps): JSX.Element { - const { definition, selectedPrimaryWells, selectWells, nozzleType } = props + const { definition, selectedPrimaryWells, selectWells, channels } = props const [highlightedWells, setHighlightedWells] = React.useState({}) @@ -46,8 +38,7 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { selectedWells: WellGroup ) => WellGroup = selectedWells => { // Returns PRIMARY WELLS from the selection. - if (nozzleType != null) { - const channels = getChannelsFromNozzleType(nozzleType) + if (channels === 8 || channels === 96) { // for the wells that have been highlighted, // get all 8-well well sets and merge them const primaryWells: WellGroup = reduce( @@ -76,8 +67,7 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { } const handleSelectionMove: (rect: GenericRect) => void = rect => { - if (nozzleType != null) { - const channels = getChannelsFromNozzleType(nozzleType) + if (channels === 8 || channels === 96) { const selectedWells = _getWellsFromRect(rect) const allWellsForMulti: WellGroup = reduce( selectedWells, @@ -107,11 +97,10 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { // For rendering, show all wells not just primary wells const allSelectedWells = - nozzleType != null + channels === 8 || channels === 96 ? reduce( selectedPrimaryWells, (acc, _, wellName): WellGroup => { - const channels = getChannelsFromNozzleType(nozzleType) const wellSet = getWellSetForMultichannel( definition, wellName, diff --git a/app/src/organisms/WellSelection/types.ts b/app/src/organisms/WellSelection/types.ts index 11d38d57c51..6f8500b2194 100644 --- a/app/src/organisms/WellSelection/types.ts +++ b/app/src/organisms/WellSelection/types.ts @@ -18,35 +18,3 @@ export interface BoundingRect { width: number height: number } - -export interface LocationLiquidState { - [ingredGroup: string]: { volume: number } -} - -export interface WellContents { - // eg 'A1', 'A2' etc - wellName?: string - groupIds: string[] - ingreds: LocationLiquidState - highlighted?: boolean - selected?: boolean - maxVolume?: number -} -export type ContentsByWell = { - [wellName: string]: WellContents -} | null - -export const COLUMN = 'COLUMN' -const SINGLE = 'SINGLE' -const ROW = 'ROW' -const QUADRANT = 'QUADRANT' -export const ALL = 'ALL' - -export type NozzleConfigurationStyle = - | typeof COLUMN - | typeof SINGLE - | typeof ROW - | typeof QUADRANT - | typeof ALL - -export type NozzleType = NozzleConfigurationStyle | '8-channel' diff --git a/app/src/organisms/WellSelection/utils.ts b/app/src/organisms/WellSelection/utils.ts index 7ee20089b41..1fb5e66f678 100644 --- a/app/src/organisms/WellSelection/utils.ts +++ b/app/src/organisms/WellSelection/utils.ts @@ -7,7 +7,7 @@ import type { WellGroup } from '@opentrons/components' import type { WellSetHelpers } from '@opentrons/shared-data' import type { BoundingRect, GenericRect } from './types' -// Collision detection for SelectionRect / SelectableLabware +// Collision detection for SelectionRect / WellSelection export const rectCollision = ( rect1: BoundingRect, rect2: BoundingRect @@ -62,9 +62,11 @@ export const getCollidingWells = (rectPositions: GenericRect): WellGroup => { ) return collidedWellData } + export const arrayToWellGroup = (w: string[]): WellGroup => w.reduce((acc, wellName) => ({ ...acc, [wellName]: null }), {}) -// cross-PD memoization of well set utils + +// memoization of well set utils const wellSetHelpers: WellSetHelpers = makeWellSetHelpers() const { canPipetteUseLabware, From 52d34484634c1fef6978964f86e4a8a615ccce31 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Fri, 17 May 2024 16:13:07 -0400 Subject: [PATCH 11/12] tweak selection rect sizing --- .../organisms/QuickTransferFlow/SelectDestWells.tsx | 7 +++++-- .../QuickTransferFlow/SelectSourceWells.tsx | 7 +++++-- app/src/organisms/WellSelection/SelectionRect.tsx | 13 +++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx index a891099f90e..00d7b34d6c4 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { Flex, JUSTIFY_CENTER, SPACING } from '@opentrons/components' +import { Flex, POSITION_FIXED, SPACING } from '@opentrons/components' import { ChildNavigation } from '../ChildNavigation' import { WellSelection } from '../../organisms/WellSelection' @@ -57,9 +57,12 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { top={SPACING.spacing8} /> {state.destination != null && state.source != null ? ( {state.source != null ? ( { + ref={(ref: HTMLDivElement | SVGAElement | null) => { parentRef.current = ref }} - // don't hard-code this width - style={{ width: '600px' }} + justifyContent={JUSTIFY_CENTER} + width="100%" > - {children} -
+ {children} + ) } From 4e5d3a724691b115a89c53e6714ad30967e5c2a9 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Mon, 20 May 2024 10:58:36 -0400 Subject: [PATCH 12/12] remove comment --- app/src/organisms/WellSelection/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/organisms/WellSelection/index.tsx b/app/src/organisms/WellSelection/index.tsx index 00615cc3b26..7b20abae75c 100644 --- a/app/src/organisms/WellSelection/index.tsx +++ b/app/src/organisms/WellSelection/index.tsx @@ -23,7 +23,6 @@ import type { GenericRect } from './types' interface WellSelectionProps { definition: LabwareDefinition2 - /** array of primary wells. Overrides labwareProps.selectedWells */ selectedPrimaryWells: WellGroup selectWells: (wellGroup: WellGroup) => unknown channels: PipetteChannels