Skip to content

Commit

Permalink
feat(protocol-designer): wire up substeps for transfer and mix (#16383)
Browse files Browse the repository at this point in the history
  • Loading branch information
jerader authored Oct 2, 2024
1 parent 44cc303 commit a5f4f04
Show file tree
Hide file tree
Showing 22 changed files with 739 additions and 22 deletions.
13 changes: 12 additions & 1 deletion components/src/atoms/ListItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface ListItemProps extends StyleProps {
/** ListItem contents */
children: React.ReactNode
onClick?: () => void
onMouseEnter?: () => void
onMouseLeave?: () => void
}

const LISTITEM_PROPS_BY_TYPE: Record<
Expand All @@ -40,7 +42,14 @@ const LISTITEM_PROPS_BY_TYPE: Record<
ListItem is used in ODD and helix
**/
export function ListItem(props: ListItemProps): JSX.Element {
const { type, children, onClick, ...styleProps } = props
const {
type,
children,
onClick,
onMouseEnter,
onMouseLeave,
...styleProps
} = props
const listItemProps = LISTITEM_PROPS_BY_TYPE[type]

const LIST_ITEM_STYLE = css`
Expand All @@ -60,6 +69,8 @@ export function ListItem(props: ListItemProps): JSX.Element {
<Flex
data-testid={`ListItem_${type}`}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
css={LIST_ITEM_STYLE}
{...styleProps}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const STYLE_BY_WELL_CONTENTS: {
highlightedWell: {
stroke: COLORS.blue50,
fill: COLORS.transparent,
strokeWidth: 0.5,
strokeWidth: 1,
},
disabledWell: {
stroke: '#C6C6C6', // LEGACY --light-grey-hover
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
{
"add_details": "Add step details",
"aspirated": "Aspirated",
"change_tips": "Change tips",
"default_tip_option": "Default - get next tip",
"delete": "Delete step",
"dispensed": "Dispensed",
"duplicate": "Duplicate step",
"edit_step": "Edit step",
"final_deck_state": "Final deck state",
"from": "from",
"heater_shaker_settings": "Heater-shaker settings",
"in": "in",
"into": "into",
"mix": "Mix",
"module": "Module",
"multiAspirate": "Consolidate path",
"multiDispense": "Distribute path",
Expand All @@ -33,7 +39,9 @@
"shake": "Shake",
"single": "Single path",
"starting_deck_state": "Starting deck state",
"step_substeps": "{{stepType}} details",
"temperature": "Temperature",
"time": "Time",
"view_commands": "View commands"
"view_details": "View details",
"well_name": "Well {{wellName}}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getStagingAreaAddressableAreas } from '../../../utils'
import { editSlotInfo } from '../../../labware-ingred/actions'
import { getRobotType } from '../../../file-data/selectors'
import { getSlotInformation } from '../utils'
import { HighlightLabware } from '../HighlightLabware'
import { DeckItemHover } from './DeckItemHover'
import { SlotOverflowMenu } from './SlotOverflowMenu'
import { HoveredItems } from './HoveredItems'
Expand Down Expand Up @@ -205,6 +206,10 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
y={0}
labwareOnDeck={labwareLoadedOnModule}
/>
<HighlightLabware
labwareOnDeck={labwareLoadedOnModule}
position={[0, 0, 0]}
/>
<DeckItemHover
isSelected={selectedZoomInSlot != null}
hover={hover}
Expand Down Expand Up @@ -314,6 +319,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
y={slotPosition[1]}
labwareOnDeck={labware}
/>
<HighlightLabware labwareOnDeck={labware} position={slotPosition} />
<DeckItemHover
isSelected={selectedZoomInSlot != null}
hover={hover}
Expand Down Expand Up @@ -376,6 +382,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
y={slotPosition[1]}
labwareOnDeck={labware}
/>
<HighlightLabware labwareOnDeck={labware} position={slotPosition} />
<DeckItemHover
isSelected={selectedZoomInSlot != null}
hover={hover}
Expand Down
37 changes: 37 additions & 0 deletions protocol-designer/src/pages/Designer/HighlightLabware.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useSelector } from 'react-redux'
import { getLabwareEntities } from '../../step-forms/selectors'
import { getHoveredStepLabware } from '../../ui/steps'
import { LabwareLabel } from './LabwareLabel'
import type { CoordinateTuple } from '@opentrons/shared-data'
import type { LabwareOnDeck } from '../../step-forms'

interface HighlightLabwareProps {
labwareOnDeck: LabwareOnDeck
position: CoordinateTuple
}

export function HighlightLabware(
props: HighlightLabwareProps
): JSX.Element | null {
const { labwareOnDeck, position } = props
const labwareEntities = useSelector(getLabwareEntities)
const hoveredLabware = useSelector(getHoveredStepLabware)
const adapterId =
labwareEntities[labwareOnDeck.slot] != null
? labwareEntities[labwareOnDeck.slot].id
: null

const highlighted = hoveredLabware.includes(adapterId ?? labwareOnDeck.id)

if (highlighted) {
return (
<LabwareLabel
isSelected={true}
isLast={true}
position={position}
labwareDef={labwareOnDeck.def}
/>
)
}
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import {
ConfirmDeleteModal,
} from '../../../../components/modals/ConfirmDeleteModal'
import { stepIconsByType } from '../../../../form-types'
import {
hoverOnStep,
toggleViewSubstep,
} from '../../../../ui/steps/actions/actions'
import { getOrderedStepIds } from '../../../../step-forms/selectors'
import { StepContainer } from './StepContainer'

Expand All @@ -41,6 +45,7 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
const argsAndErrors = useSelector(stepFormSelectors.getArgsAndErrorsByStepId)[
stepId
]
const selectedStep = useSelector(getSelectedStepId)
const errorStepId = useSelector(fileDataSelectors.getErrorStepId)
const hasError = errorStepId === stepId || argsAndErrors.errors != null
const hasTimelineWarningsPerStep = useSelector(
Expand Down Expand Up @@ -77,17 +82,31 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
dispatch(stepsActions.hoverOnStep(stepId))
const unhighlightStep = (): HoverOnStepAction =>
dispatch(stepsActions.hoverOnStep(null))
const handleSelectStep = (): void => {
selectStep()
if (selectedStep !== stepId) {
dispatch(toggleViewSubstep(null))
dispatch(hoverOnStep(null))
}
}
const handleSelectDoubleStep = (): void => {
selectStepOnDoubleClick()
if (selectedStep !== stepId) {
dispatch(toggleViewSubstep(null))
dispatch(hoverOnStep(null))
}
}

const {
confirm: confirmDoubleClick,
showConfirmation: showConfirmationDoubleClick,
cancel: cancelDoubleClick,
} = useConditionalConfirm(
selectStepOnDoubleClick,
handleSelectDoubleStep,
currentFormIsPresaved || singleEditFormHasUnsavedChanges
)
const { confirm, showConfirmation, cancel } = useConditionalConfirm(
selectStep,
handleSelectStep,
currentFormIsPresaved || singleEditFormHasUnsavedChanges
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
DIRECTION_COLUMN,
DeckInfoLabel,
Flex,
JUSTIFY_SPACE_BETWEEN,
ListButton,
SPACING,
StyledText,
Tag,
} from '@opentrons/components'
import { Substep } from './Substep'
import { formatVolume } from './utils'
import type { AdditionalEquipmentName } from '@opentrons/step-generation'
import type {
StepItemSourceDestRow,
SubstepIdentifier,
WellIngredientNames,
} from '../../../../steplist'

interface MultichannelSubstepProps {
trashName: AdditionalEquipmentName | null
rowGroup: StepItemSourceDestRow[]
ingredNames: WellIngredientNames
stepId: string
substepIndex: number
selectSubstep: (substepIdentifier: SubstepIdentifier) => void
highlighted?: boolean
}

export function MultichannelSubstep(
props: MultichannelSubstepProps
): JSX.Element {
const {
rowGroup,
stepId,
selectSubstep,
substepIndex,
ingredNames,
trashName,
} = props
const { t } = useTranslation('application')
const [collapsed, setCollapsed] = useState<Boolean>(true)
const handleToggleCollapsed = (): void => {
setCollapsed(!collapsed)
}

const firstChannelSource = rowGroup[0].source
const lastChannelSource = rowGroup[rowGroup.length - 1].source
const sourceWellRange = `${
firstChannelSource ? firstChannelSource.well : ''
}:${lastChannelSource ? lastChannelSource.well : ''}`
const firstChannelDest = rowGroup[0].dest
const lastChannelDest = rowGroup[rowGroup.length - 1].dest
const destWellRange = `${
firstChannelDest ? firstChannelDest.well ?? 'Trash' : ''
}:${lastChannelDest ? lastChannelDest.well : ''}`

return (
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing8}
width="100%"
onMouseEnter={() => {
selectSubstep({ stepId, substepIndex })
}}
onMouseLeave={() => {
selectSubstep(null)
}}
>
{/* TODO: need to update this to match designs! */}
<ListButton type="noActive" onClick={handleToggleCollapsed}>
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing4}
width="100%"
>
<Flex
padding={SPACING.spacing12}
justifyContent={JUSTIFY_SPACE_BETWEEN}
width="100%"
alignItems={ALIGN_CENTER}
>
<StyledText desktopStyle="bodyDefaultRegular">Multi</StyledText>
{firstChannelSource != null ? (
<DeckInfoLabel deckLabel={sourceWellRange} />
) : null}
<Tag
text={`${formatVolume(rowGroup[0].volume)} ${t(
'units.microliter'
)}`}
type="default"
/>
{firstChannelDest != null ? (
<DeckInfoLabel deckLabel={destWellRange} />
) : null}
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
{!collapsed &&
rowGroup.map((row, rowKey) => {
return (
<Substep
trashName={trashName}
key={rowKey}
volume={row.volume}
ingredNames={ingredNames}
source={row.source}
dest={row.dest}
stepId={stepId}
substepIndex={substepIndex}
/>
)
})}
</Flex>
</Flex>
</ListButton>
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components'
import { Substep } from './Substep'
import { MultichannelSubstep } from './MultichannelSubstep'
import type {
SourceDestSubstepItem,
SubstepIdentifier,
WellIngredientNames,
} from '../../../../steplist'
import { useSelector } from 'react-redux'
import {
getAdditionalEquipment,
getSavedStepForms,
} from '../../../../step-forms/selectors'

interface PipettingSubstepsProps {
substeps: SourceDestSubstepItem
ingredNames: WellIngredientNames
selectSubstep: (substepIdentifier: SubstepIdentifier) => void
hoveredSubstep?: SubstepIdentifier | null
}

export function PipettingSubsteps(props: PipettingSubstepsProps): JSX.Element {
const { substeps, selectSubstep, hoveredSubstep, ingredNames } = props
const stepId = substeps.parentStepId
const formData = useSelector(getSavedStepForms)[stepId]
const additionalEquipment = useSelector(getAdditionalEquipment)
const destLocationId = formData.dispense_labware
const trashName =
additionalEquipment[destLocationId] != null
? additionalEquipment[destLocationId]?.name
: null

const renderSubsteps = substeps.multichannel
? substeps.multiRows.map((rowGroup, groupKey) => (
<MultichannelSubstep
trashName={trashName}
key={groupKey}
highlighted={
!!hoveredSubstep &&
hoveredSubstep.stepId === substeps.parentStepId &&
hoveredSubstep.substepIndex === groupKey
}
rowGroup={rowGroup}
stepId={substeps.parentStepId}
substepIndex={groupKey}
selectSubstep={selectSubstep}
ingredNames={ingredNames}
/>
))
: substeps.rows.map((row, substepIndex) => (
<Substep
trashName={trashName}
key={substepIndex}
selectSubstep={selectSubstep}
stepId={substeps.parentStepId}
substepIndex={substepIndex}
ingredNames={ingredNames}
volume={row.volume}
source={row.source}
dest={row.dest}
/>
))

return (
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing4}
width="100%"
>
{renderSubsteps}
</Flex>
)
}
Loading

0 comments on commit a5f4f04

Please sign in to comment.