Skip to content

Commit

Permalink
feat(app, components): add modal for stacked entities (#15895)
Browse files Browse the repository at this point in the history
To give clarity to the contents of labware/adapter/module stacks, here,
I add a modal when clicking
a stack on Labware setup deck map (for both Desktop and ODD). Each
element of the stack will be
highlighted described in a list item containing the element's name,
optional nickname, and isometric
SVG or PNG representation depending on its type.

Closes [PLAT-376](https://opentrons.atlassian.net/browse/PLAT-376),
[PLAT-378](https://opentrons.atlassian.net/browse/PLAT-378)
  • Loading branch information
ncdiehl11 authored Aug 7, 2024
1 parent 4693d04 commit 8ce3880
Show file tree
Hide file tree
Showing 14 changed files with 503 additions and 79 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@
"setup_is_view_only": "Setup is view-only once run has started",
"slot_location": "Slot {{slotName}}",
"slot_number": "Slot Number",
"stacked_slot": "Stacked slot",
"start_run": "Start run",
"status": "Status",
"step": "STEP {{index}}",
Expand Down
2 changes: 1 addition & 1 deletion app/src/molecules/Modal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { IconName, StyleProps } from '@opentrons/components'
export type ModalSize = 'small' | 'medium' | 'large'

export interface ModalHeaderBaseProps extends StyleProps {
title: string
title: string | JSX.Element
onClick?: React.MouseEventHandler
hasExitIcon?: boolean
iconName?: IconName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { css } from 'styled-components'
import {
ALIGN_CENTER,
Box,
COLORS,
DeckInfoLabel,
DIRECTION_COLUMN,
Flex,
JUSTIFY_CENTER,
JUSTIFY_SPACE_BETWEEN,
LabwareStackRender,
SPACING,
StyledText,
} from '@opentrons/components'
import { Modal } from '../../../../molecules/Modal'
import { getIsOnDevice } from '../../../../redux/config'
import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import { LegacyModal } from '../../../../molecules/LegacyModal'
import { getLocationInfoNames } from '../utils/getLocationInfoNames'
import { getSlotLabwareDefinition } from '../utils/getSlotLabwareDefinition'
import { Divider } from '../../../../atoms/structure'
import { getModuleImage } from '../SetupModuleAndDeck/utils'
import { getModuleDisplayName } from '@opentrons/shared-data'
import tiprackAdapter from '../../../../assets/images/labware/opentrons_flex_96_tiprack_adapter.png'

const HIDE_SCROLLBAR = css`
::-webkit-scrollbar {
display: none;
}
`

interface LabwareStackModalProps {
labwareIdTop: string
runId: string
closeModal: () => void
}

export const LabwareStackModal = (
props: LabwareStackModalProps
): JSX.Element | null => {
const { labwareIdTop, runId, closeModal } = props
const { t } = useTranslation('protocol_setup')
const isOnDevice = useSelector(getIsOnDevice)
const protocolData = useMostRecentCompletedAnalysis(runId)
if (protocolData == null) {
return null
}
const commands = protocolData?.commands ?? []
const {
slotName,
adapterName,
adapterId,
moduleModel,
labwareName,
labwareNickname,
} = getLocationInfoNames(labwareIdTop, commands)

const topDefinition = getSlotLabwareDefinition(labwareIdTop, commands)
const adapterDef = getSlotLabwareDefinition(adapterId ?? '', commands)
const moduleDisplayName =
moduleModel != null ? getModuleDisplayName(moduleModel) : null ?? ''
const tiprackAdapterImg = (
<img width="156px" height="130px" src={tiprackAdapter} />
)
const moduleImg =
moduleModel != null ? (
<img width="156px" height="130px" src={getModuleImage(moduleModel)} />
) : null

return isOnDevice ? (
<Modal
onOutsideClick={closeModal}
header={{
title: (
<Flex gridGap={SPACING.spacing4}>
<DeckInfoLabel deckLabel={slotName} />
<DeckInfoLabel iconName="stacked" />
</Flex>
),
onClick: closeModal,
}}
>
<Flex
flexDirection={DIRECTION_COLUMN}
css={HIDE_SCROLLBAR}
overflowY="scroll"
gridGap={SPACING.spacing16}
width="41.675rem"
>
<>
<Flex
alignItems={ALIGN_CENTER}
height="6.875rem"
gridGap={SPACING.spacing32}
>
<LabwareStackLabel
isOnDevice
text={labwareName}
subText={labwareNickname}
/>
<LabwareStackRender
definitionTop={topDefinition}
definitionBottom={adapterDef}
highlightBottom={false}
highlightTop={true}
/>
</Flex>
<Divider marginY={SPACING.spacing16} />
</>
{adapterDef != null ? (
<>
<Flex
alignItems={ALIGN_CENTER}
height="6.875rem"
gridGap={SPACING.spacing32}
>
<LabwareStackLabel text={adapterName ?? ''} isOnDevice />
{adapterDef.parameters.loadName ===
'opentrons_flex_96_tiprack_adapter' ? (
tiprackAdapterImg
) : (
<LabwareStackRender
definitionTop={topDefinition}
definitionBottom={adapterDef}
highlightBottom={true}
highlightTop={false}
/>
)}
</Flex>
{moduleModel != null ? (
<Divider marginY={SPACING.spacing16} />
) : null}
</>
) : null}
{moduleModel != null ? (
<Flex
alignItems={ALIGN_CENTER}
height="6.875rem"
gridGap={SPACING.spacing32}
>
<LabwareStackLabel text={moduleDisplayName} isOnDevice />
{moduleImg}
</Flex>
) : null}
</Flex>
</Modal>
) : (
<LegacyModal
onClose={closeModal}
closeOnOutsideClick
title={
<Flex gridGap={SPACING.spacing8}>
<DeckInfoLabel deckLabel={slotName} />
<DeckInfoLabel iconName="stacked" />
<StyledText>{t('stacked_slot')}</StyledText>
</Flex>
}
childrenPadding={0}
>
<Box padding={SPACING.spacing24} backgroundColor={COLORS.white}>
<Flex flexDirection={DIRECTION_COLUMN}>
<>
<Flex
alignItems={ALIGN_CENTER}
height="6.875rem"
justifyContent={JUSTIFY_SPACE_BETWEEN}
>
<LabwareStackLabel text={labwareName} subText={labwareNickname} />
<LabwareStackRender
definitionTop={topDefinition}
definitionBottom={adapterDef}
highlightBottom={false}
highlightTop={true}
/>
</Flex>
<Divider marginY={SPACING.spacing16} />
</>
{adapterDef != null ? (
<>
<Flex
alignItems={ALIGN_CENTER}
height="6.875rem"
justifyContent={JUSTIFY_SPACE_BETWEEN}
>
<LabwareStackLabel text={adapterName ?? ''} />
<LabwareStackRender
definitionTop={topDefinition}
definitionBottom={adapterDef}
highlightBottom={true}
highlightTop={false}
/>
</Flex>
<Divider marginY={SPACING.spacing16} />
</>
) : null}
{moduleModel != null ? (
<Flex
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_SPACE_BETWEEN}
height="6.875rem"
>
<LabwareStackLabel text={moduleDisplayName} />
{moduleImg}
</Flex>
) : null}
</Flex>
</Box>
</LegacyModal>
)
}

interface LabwareStackLabelProps {
text: string
subText?: string
isOnDevice?: boolean
}
function LabwareStackLabel(props: LabwareStackLabelProps): JSX.Element {
const { text, subText, isOnDevice = false } = props
return isOnDevice ? (
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing4}
width="28rem"
flex="0 0 auto"
justifyContent={JUSTIFY_CENTER}
>
<StyledText oddStyle="bodyTextBold">{text}</StyledText>
{subText != null ? (
<StyledText oddStyle="bodyTextRegular" color={COLORS.grey60}>
{subText}
</StyledText>
) : null}
</Flex>
) : (
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing4}
width="14.75rem"
flex="0 0 auto"
>
<StyledText desktopStyle="bodyLargeSemiBold">{text}</StyledText>
{subText != null ? (
<StyledText desktopStyle="bodyDefaultRegular" color={COLORS.grey60}>
{subText}
</StyledText>
) : null}
</Flex>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {
CompletedProtocolAnalysis,
ProtocolAnalysisOutput,
} from '@opentrons/shared-data'
import { LabwareStackModal } from './LabwareStackModal'

interface SetupLabwareMapProps {
runId: string
Expand All @@ -38,6 +39,11 @@ export function SetupLabwareMap({
protocolAnalysis,
}: SetupLabwareMapProps): JSX.Element | null {
// early return null if no protocol analysis
const [
labwareStackDetailsLabwareId,
setLabwareStackDetailsLabwareId,
] = React.useState<string | null>(null)

if (protocolAnalysis == null) return null

const commands = protocolAnalysis.commands
Expand Down Expand Up @@ -76,7 +82,15 @@ export function SetupLabwareMap({

nestedLabwareDef: topLabwareDefinition,
moduleChildren: (
<>
// open modal
<g
onClick={() => {
if (topLabwareDefinition != null) {
setLabwareStackDetailsLabwareId(topLabwareId)
}
}}
cursor="pointer"
>
{topLabwareDefinition != null && topLabwareId != null ? (
<LabwareInfoOverlay
definition={topLabwareDefinition}
Expand All @@ -85,7 +99,7 @@ export function SetupLabwareMap({
runId={runId}
/>
) : null}
</>
</g>
),
}
})
Expand Down Expand Up @@ -143,6 +157,15 @@ export function SetupLabwareMap({
commands={commands}
/>
</Flex>
{labwareStackDetailsLabwareId != null && (
<LabwareStackModal
labwareIdTop={labwareStackDetailsLabwareId}
runId={runId}
closeModal={() => {
setLabwareStackDetailsLabwareId(null)
}}
/>
)}
</Flex>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ export function SetupLiquidsMap(
setHoverLabwareId('')
}}
onClick={() => {
if (labwareHasLiquid) setLiquidDetailsLabwareId(topLabwareId)
if (labwareHasLiquid) {
setLiquidDetailsLabwareId(topLabwareId)
}
}}
cursor={labwareHasLiquid ? 'pointer' : ''}
>
Expand Down Expand Up @@ -169,8 +171,9 @@ export function SetupLiquidsMap(
setHoverLabwareId('')
}}
onClick={() => {
if (labwareHasLiquid)
if (labwareHasLiquid) {
setLiquidDetailsLabwareId(topLabwareId)
}
}}
cursor={labwareHasLiquid ? 'pointer' : ''}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ describe('getLocationInfoNames', () => {
labwareName: LABWARE_DISPLAY_NAME,
moduleModel: MOCK_MODEL,
adapterName: ADAPTER_DISPLAY_NAME,
adapterId: ADAPTER_ID,
}
expect(
getLocationInfoNames(LABWARE_ID, MOCK_ADAPTER_MOD_COMMANDS as any)
Expand All @@ -161,6 +162,7 @@ describe('getLocationInfoNames', () => {
slotName: SLOT,
labwareName: LABWARE_DISPLAY_NAME,
adapterName: ADAPTER_DISPLAY_NAME,
adapterId: ADAPTER_ID,
}
expect(
getLocationInfoNames(LABWARE_ID, MOCK_ADAPTER_COMMANDS as any)
Expand Down
Loading

0 comments on commit 8ce3880

Please sign in to comment.