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(app): add labware selection and volume entry screens #15074

Merged
merged 10 commits into from
May 3, 2024
11 changes: 11 additions & 0 deletions app/src/assets/localization/en/quick_transfer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
{
"all": "All labware",
"aspirate_volume": "Aspirate volume per well (µL)",
"create_new_transfer": "Create new quick transfer",
"left_mount": "Left Mount",
"both_mounts": "Left + Right Mount",
"dispense_volume": "Dispense volume per well (µL)",
"exit_quick_transfer": "Exit quick transfer?",
"lose_all_progress": "You will lose all progress on this quick transfer.",
"right_mount": "Right Mount",
"reservoir": "Reservoirs",
"select_attached_pipette": "Select attached pipette",
"select_dest_labware": "Select destination labware",
"select_dest_wells": "Select destination wells",
Expand All @@ -12,10 +18,15 @@
"set_aspirate_volume": "Set aspirate volume",
"set_dispense_volume": "Set dispense volume",
"set_transfer_volume": "Set transfer volume",
"source_labware": "Source labware in D2",
"use_deck_slots": "<block>Quick transfers use deck slots B2-D2. These slots hold a tip rack, a source labware, and a destination labware.</block><block>Make sure that your deck configuration is up to date to avoid collisions.</block>",
"tip_rack": "Tip rack",
"tubeRack": "Tube racks",
"volume_per_well": "Volume per well (µL)",
"value_out_of_range": "Value must be between {{min}}-{{max}}",
"labware": "Labware",
"pipette_currently_attached": "Quick transfer options depend on the pipettes currently attached to your robot.",
"wellPlate": "Well plates",
"well_selection": "Well selection",
"well_ratio": "Quick transfers with multiple source wells can either be one-to-one (select {{wells}} for this transfer) or consolidate (select 1 destination well)."
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
.simple-keyboard.oddTheme1.hg-theme-default {
width: 100%;
height: 100%;
border-radius: 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Designs for this component show no rounded edges, the default for the keyboard component we use is border-radius 5

background-color: #cbcccc; /* grey35 */
font-family: 'Public Sans', sans-serif;
padding: 8px;
Expand Down
54 changes: 54 additions & 0 deletions app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import {
SPACING,
COLORS,
StyledText,
Flex,
DIRECTION_COLUMN,
TYPOGRAPHY,
} from '@opentrons/components'
import { Modal } from '../../molecules/Modal'
import { SmallButton } from '../../atoms/buttons'

interface ConfirmExitModalProps {
confirmExit: () => void
cancelExit: () => void
}

export const ConfirmExitModal = (props: ConfirmExitModalProps): JSX.Element => {
const { i18n, t } = useTranslation(['quick_transfer', 'shared'])

return (
<Modal
header={{
title: t('exit_quick_transfer'),
iconName: 'alert-circle',
iconColor: COLORS.yellow50,
}}
>
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing10}
width="100%"
>
<StyledText css={TYPOGRAPHY.bodyTextRegular}>
{t('lose_all_progress')}
</StyledText>
<Flex gridGap={SPACING.spacing8}>
<SmallButton
width="50%"
buttonText={i18n.format(t('shared:cancel'), 'capitalize')}
onClick={props.cancelExit}
/>
<SmallButton
width="50%"
buttonText={i18n.format(t('shared:delete'), 'capitalize')}
onClick={props.confirmExit}
buttonType="alert"
/>
</Flex>
</Flex>
</Modal>
)
}
147 changes: 147 additions & 0 deletions app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import {
Flex,
SPACING,
DIRECTION_COLUMN,
DIRECTION_ROW,
COLORS,
POSITION_FIXED,
ALIGN_CENTER,
} from '@opentrons/components'

import { SmallButton, LargeButton, TabbedButton } from '../../atoms/buttons'
import { ChildNavigation } from '../ChildNavigation'
import { getCompatibleLabwareByCategory } from './utils'

import type { LabwareDefinition2 } from '@opentrons/shared-data'
import type {
QuickTransferSetupState,
QuickTransferWizardAction,
} from './types'
import { LabwareFilter } from '../../pages/Labware/types'

interface SelectDestLabwareProps {
onNext: () => void
onBack: () => void
exitButtonProps: React.ComponentProps<typeof SmallButton>
state: QuickTransferSetupState
dispatch: React.Dispatch<QuickTransferWizardAction>
}

export function SelectDestLabware(
props: SelectDestLabwareProps
): JSX.Element | null {
const { onNext, onBack, exitButtonProps, state, dispatch } = props
const { i18n, t } = useTranslation(['quick_transfer', 'shared'])
const labwareDisplayCategoryFilters: LabwareFilter[] = [
'all',
'wellPlate',
'reservoir',
]
if (state.pipette?.channels === 1) {
labwareDisplayCategoryFilters.push('tubeRack')
}
const [selectedCategory, setSelectedCategory] = React.useState<LabwareFilter>(
'all'
)
const [selectedLabware, setSelectedLabware] = React.useState<
LabwareDefinition2 | 'source' | undefined
>(state.destination)

if (state.pipette == null) return null

const compatibleLabwareDefinitions = getCompatibleLabwareByCategory(
state.pipette.channels,
selectedCategory
)

const handleClickNext = (): void => {
// the button will be disabled if this values is null
if (selectedLabware != null) {
dispatch({
type: 'SET_DEST_LABWARE',
labware: selectedLabware,
})
onNext()
}
}
return (
<Flex>
<ChildNavigation
header={t('select_dest_labware')}
buttonText={i18n.format(t('shared:continue'), 'capitalize')}
onClickBack={onBack}
onClickButton={handleClickNext}
secondaryButtonProps={exitButtonProps}
top={SPACING.spacing8}
buttonIsDisabled={selectedLabware == null}
/>
<Flex
flexDirection={DIRECTION_COLUMN}
padding={`${SPACING.spacing16} ${SPACING.spacing60} ${SPACING.spacing40} ${SPACING.spacing60}`}
width="100%"
>
<Flex
gridGap={SPACING.spacing8}
height={SPACING.spacing80}
backgroundColor={COLORS.white}
width="100%"
flexDirection={DIRECTION_ROW}
position={POSITION_FIXED}
top={SPACING.spacing120}
marginBottom={SPACING.spacing24}
alignItems={ALIGN_CENTER}
>
{labwareDisplayCategoryFilters.map(category => (
<TabbedButton
key={category}
title={category}
isSelected={category === selectedCategory}
onClick={() => setSelectedCategory(category)}
height={SPACING.spacing60}
>
{t(category)}
</TabbedButton>
))}
</Flex>
<Flex
gridGap={SPACING.spacing4}
flexDirection={DIRECTION_COLUMN}
marginTop="175px"
>
{selectedCategory === 'all' && state?.source != null ? (
<LargeButton
buttonType={
selectedLabware === 'source' ? 'primary' : 'secondary'
}
onClick={() => {
setSelectedLabware('source')
}}
buttonText={t('source_labware')}
subtext={state.source.metadata.displayName}
/>
) : null}
{compatibleLabwareDefinitions?.map(definition => {
return definition.metadata.displayName != null ? (
<LargeButton
key={`${selectedCategory}-${definition.metadata.displayName}`}
buttonType={
selectedLabware !== 'source' &&
selectedLabware?.metadata.displayName ===
definition.metadata.displayName
? 'primary'
: 'secondary'
}
onClick={() => {
setSelectedLabware(definition)
}}
buttonText={definition.metadata.displayName}
/>
) : null
})}
</Flex>
</Flex>
</Flex>
)
}
59 changes: 59 additions & 0 deletions app/src/organisms/QuickTransferFlow/SelectDestWells.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Flex, SPACING } from '@opentrons/components'

import { SmallButton } from '../../atoms/buttons'
import { ChildNavigation } from '../ChildNavigation'

import type {
QuickTransferSetupState,
QuickTransferWizardAction,
} from './types'

interface SelectDestWellsProps {
onNext: () => void
onBack: () => void
exitButtonProps: React.ComponentProps<typeof SmallButton>
state: QuickTransferSetupState
dispatch: React.Dispatch<QuickTransferWizardAction>
}

export function SelectDestWells(props: SelectDestWellsProps): JSX.Element {
const { onNext, onBack, exitButtonProps, state, dispatch } = props
const { i18n, t } = useTranslation(['quick_transfer', 'shared'])

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),
})
}
onNext()
}
return (
<Flex>
<ChildNavigation
header={t('select_dest_wells')}
onClickBack={onBack}
buttonText={i18n.format(t('shared:continue'), 'capitalize')}
onClickButton={handleClickNext}
buttonIsDisabled={false}
secondaryButtonProps={exitButtonProps}
top={SPACING.spacing8}
/>
<Flex
marginTop={SPACING.spacing120}
padding={`${SPACING.spacing16} ${SPACING.spacing60} ${SPACING.spacing40} ${SPACING.spacing60}`}
>
TODO: Add destination well selection deck map
</Flex>
</Flex>
)
}
Loading
Loading