Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol-designer): wire up substeps for transfer and mix #16383

Merged
merged 9 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading