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
Next Next commit
feat(app): add source and destination labware selection screens
  • Loading branch information
smb2268 committed Apr 25, 2024
commit 6ab37ba6cd31f5531605d19104c0edbd7d0e9e28
5 changes: 5 additions & 0 deletions app/src/assets/localization/en/quick_transfer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"all": "All labware",
"create_new_transfer": "Create new quick transfer",
"left_mount": "Left Mount",
"both_mounts": "Left + Right Mount",
"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 +14,13 @@
"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",
"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)."
}
143 changes: 143 additions & 0 deletions app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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 {
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 compatibleLabwareDefinitions = getCompatibleLabwareByCategory(
state.pipette,
selectedCategory
)

const [selectedLabware, setSelectedLabware] = React.useState<
LabwareDefinition2 | 'source' | undefined
>(state.destination)

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>
)
}
132 changes: 132 additions & 0 deletions app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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 SelectSourceLabwareProps {
onNext: () => void
onBack: () => void
exitButtonProps: React.ComponentProps<typeof SmallButton>
state: QuickTransferSetupState
dispatch: React.Dispatch<QuickTransferWizardAction>
}

export function SelectSourceLabware(
props: SelectSourceLabwareProps
): JSX.Element {
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 compatibleLabwareDefinitions = getCompatibleLabwareByCategory(
state.pipette,
selectedCategory
)

const [selectedLabware, setSelectedLabware] = React.useState<
LabwareDefinition2 | undefined
>(state.source)

const handleClickNext = (): void => {
// the button will be disabled if this values is null
if (selectedLabware != null) {
dispatch({
type: 'SET_SOURCE_LABWARE',
labware: selectedLabware,
})
onNext()
}
}
return (
<Flex>
<ChildNavigation
header={t('select_source_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"
>
{compatibleLabwareDefinitions?.map(definition => {
return definition.metadata.displayName != null ? (
<LargeButton
key={`${selectedCategory}-${definition.metadata.displayName}`}
buttonType={
selectedLabware?.metadata.displayName ===
definition.metadata.displayName
? 'primary'
: 'secondary'
}
onClick={() => {
setSelectedLabware(definition)
}}
buttonText={definition.metadata.displayName}
/>
) : null
})}
</Flex>
</Flex>
</Flex>
)
}
2 changes: 2 additions & 0 deletions app/src/organisms/QuickTransferFlow/SelectTipRack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function SelectTipRack(props: SelectTipRackProps): JSX.Element {
const handleClickNext = (): void => {
// the button will be disabled if this values is null
if (selectedTipRack != null) {
console.log('submitting tip rack')
dispatch({
type: 'SELECT_TIP_RACK',
tipRack: selectedTipRack,
Expand Down Expand Up @@ -64,6 +65,7 @@ export function SelectTipRack(props: SelectTipRackProps): JSX.Element {

return tipRackDef != null ? (
<LargeButton
key={tipRack}
buttonType={
selectedTipRack === tipRackDef ? 'primary' : 'secondary'
}
Expand Down
22 changes: 22 additions & 0 deletions app/src/organisms/QuickTransferFlow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { ChildNavigation } from '../ChildNavigation'
import { CreateNewTransfer } from './CreateNewTransfer'
import { SelectPipette } from './SelectPipette'
import { SelectTipRack } from './SelectTipRack'
import { SelectSourceLabware } from './SelectSourceLabware'
import { SelectDestLabware } from './SelectDestLabware'
import { quickTransferReducer } from './utils'

import type { QuickTransferSetupState } from './types'
Expand Down Expand Up @@ -82,6 +84,26 @@ export const QuickTransferFlow = (): JSX.Element => {
exitButtonProps={exitButtonProps}
/>
)
} else if (currentStep === 4) {
smb2268 marked this conversation as resolved.
Show resolved Hide resolved
modalContent = (
<SelectSourceLabware
state={state}
dispatch={dispatch}
onBack={() => setCurrentStep(prevStep => prevStep - 1)}
onNext={() => setCurrentStep(prevStep => prevStep + 1)}
exitButtonProps={exitButtonProps}
/>
)
} else if (currentStep === 6) {
modalContent = (
<SelectDestLabware
state={state}
dispatch={dispatch}
onBack={() => setCurrentStep(prevStep => prevStep - 1)}
onNext={() => setCurrentStep(prevStep => prevStep + 1)}
exitButtonProps={exitButtonProps}
/>
)
} else {
modalContent = null
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/organisms/QuickTransferFlow/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface QuickTransferSetupState {
tipRack?: LabwareDefinition2
source?: LabwareDefinition2
sourceWells?: string[]
destination?: LabwareDefinition2
destination?: LabwareDefinition2 | 'source'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We allow users to select either a new labware or use the same labware as they selected for the source

destinationWells?: string[]
volume?: number
}
Expand Down Expand Up @@ -41,7 +41,7 @@ interface SetSourceWellsAction {
}
interface SetDestLabwareAction {
type: typeof ACTIONS.SET_DEST_LABWARE
labware: LabwareDefinition2
labware: LabwareDefinition2 | 'source'
}
interface SetDestWellsAction {
type: typeof ACTIONS.SET_DEST_WELLS
Expand Down
Loading
Loading