Skip to content

Commit 13f9e10

Browse files
authored
feat(app): update pushout screen (#18894)
* feat(app): update pushout screen
1 parent 458b193 commit 13f9e10

File tree

16 files changed

+270
-160
lines changed

16 files changed

+270
-160
lines changed

app/src/assets/localization/en/quick_transfer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@
128128
"pre_wet_tip": "Pre-wet tip",
129129
"push_out_after_dispensing": "Push out after dispensing",
130130
"push_out_description": "Helps ensure all liquid leaves the tip",
131+
"push_out_volume": "Push out volume (µL)",
132+
"push_out_value": "{{volume}} µL",
131133
"push_out": "Push out",
132134
"quick_transfer_volume": "Quick Transfer {{volume}}µL",
133135
"quick_transfer": "Quick transfer",

app/src/organisms/ODD/QuickTransferFlow/Dispense/hooks/useDispenseSettingsConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ export function useDispenseSettingsConfig({
131131
option: 'dispense_push_out',
132132
copy: t('push_out'),
133133
value:
134-
state.pushOut != null && state.pushOut
135-
? t('option_enabled')
134+
state.pushOutDispense != null && state.pushOutDispense.volume != null
135+
? t('push_out_value', { volume: state.pushOutDispense.volume })
136136
: t('option_disabled'),
137137
enabled: true,
138138
onClick: () => {

app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/PushOut.tsx

Lines changed: 129 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import { useState } from 'react'
1+
import { useRef, useState } from 'react'
22
import { createPortal } from 'react-dom'
33
import { useTranslation } from 'react-i18next'
44

55
import {
6+
ALIGN_CENTER,
67
COLORS,
78
DIRECTION_COLUMN,
89
Flex,
10+
InputField,
911
POSITION_FIXED,
1012
RadioButton,
1113
SPACING,
1214
StyledText,
1315
} from '@opentrons/components'
16+
import { getMaxPushOutVolume } from '@opentrons/shared-data'
1417

1518
import { getTopPortalEl } from '/app/App/portal'
19+
import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard'
1620
import { i18n } from '/app/i18n'
1721
import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation'
1822
import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics'
@@ -38,9 +42,15 @@ export function PushOut(props: PushOutProps): JSX.Element {
3842
const { kind, onBack, state, dispatch } = props
3943
const { t } = useTranslation('quick_transfer')
4044
const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial()
45+
const keyboardRef = useRef(null)
4146
const [pushOutIsEnabled, setPushOutIsEnabled] = useState<boolean | null>(
42-
state.pushOut ?? null
47+
state.pushOutDispense?.volume != null
4348
)
49+
const [volume, setVolume] = useState<number | null>(
50+
state.pushOutDispense?.volume ?? null
51+
)
52+
const [currentStep, setCurrentStep] = useState<number>(1)
53+
4454
const enablePreWetTipDisplayItems = [
4555
{
4656
option: true,
@@ -58,58 +68,137 @@ export function PushOut(props: PushOutProps): JSX.Element {
5868
},
5969
]
6070

61-
const handleClickBackOrExit = (): void => {
62-
onBack()
71+
const setSaveOrContinueButtonText =
72+
pushOutIsEnabled && currentStep < 2
73+
? t('shared:continue')
74+
: t('shared:save')
75+
76+
const handleClickBackOrExit = onBack
77+
78+
const handleClickSaveOrContinue = (): void => {
79+
if (currentStep === 1) {
80+
if (!pushOutIsEnabled) {
81+
dispatch({
82+
type: ACTIONS.SET_PUSH_OUT,
83+
pushOutSettings: undefined,
84+
})
85+
trackEventWithRobotSerial({
86+
name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED,
87+
properties: {
88+
setting: `Push-out_${kind}`,
89+
},
90+
})
91+
onBack()
92+
} else {
93+
setCurrentStep(2)
94+
}
95+
} else if (currentStep === 2) {
96+
if (volume != null) {
97+
dispatch({
98+
type: ACTIONS.SET_PUSH_OUT,
99+
pushOutSettings: {
100+
volume,
101+
},
102+
})
103+
trackEventWithRobotSerial({
104+
name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED,
105+
properties: {
106+
setting: `Push-out_${kind}`,
107+
},
108+
})
109+
}
110+
onBack()
111+
}
63112
}
64113

65-
const handleClickSave = (): void => {
66-
dispatch({
67-
type: ACTIONS.SET_PUSH_OUT,
68-
pushOut: !state.pushOut,
69-
})
70-
trackEventWithRobotSerial({
71-
name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED,
72-
properties: {
73-
setting: `Push-out_${kind}`,
74-
},
75-
})
76-
onBack()
114+
let buttonIsDisabled = false
115+
if (currentStep === 2) {
116+
buttonIsDisabled = volume == null
77117
}
78118

119+
const pushOutMaxVolume = getMaxPushOutVolume(state.volume, state.pipette)
120+
const volumeError =
121+
volume != null && volume > pushOutMaxVolume
122+
? t('value_out_of_range', { min: 0, max: pushOutMaxVolume })
123+
: null
124+
79125
return createPortal(
80126
<Flex position={POSITION_FIXED} backgroundColor={COLORS.white} width="100%">
81127
<ChildNavigation
82128
header={t('push_out_after_dispensing')}
83-
buttonText={i18n.format(t('shared:save'), 'capitalize')}
129+
buttonText={i18n.format(setSaveOrContinueButtonText, 'capitalize')}
84130
onClickBack={handleClickBackOrExit}
85-
onClickButton={handleClickSave}
131+
onClickButton={handleClickSaveOrContinue}
86132
top={SPACING.spacing8}
87-
buttonIsDisabled={pushOutIsEnabled === null}
133+
buttonIsDisabled={buttonIsDisabled}
88134
/>
89-
<Flex
90-
marginTop={SPACING.spacing120}
91-
flexDirection={DIRECTION_COLUMN}
92-
padding={`${SPACING.spacing16} ${SPACING.spacing60} ${SPACING.spacing40} ${SPACING.spacing60}`}
93-
gridGap={SPACING.spacing24}
94-
width="100%"
95-
>
96-
<StyledText oddStyle="level4HeaderRegular">
97-
{t('push_out_description')}
98-
</StyledText>
135+
{currentStep === 1 ? (
136+
<Flex
137+
marginTop={SPACING.spacing120}
138+
flexDirection={DIRECTION_COLUMN}
139+
padding={`${SPACING.spacing16} ${SPACING.spacing60} ${SPACING.spacing40} ${SPACING.spacing60}`}
140+
gridGap={SPACING.spacing24}
141+
width="100%"
142+
>
143+
<StyledText oddStyle="level4HeaderRegular">
144+
{t('push_out_description')}
145+
</StyledText>
99146

100-
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
101-
{enablePreWetTipDisplayItems.map(displayItem => (
102-
<RadioButton
103-
key={displayItem.description}
104-
isSelected={pushOutIsEnabled === displayItem.option}
105-
onChange={displayItem.onClick}
106-
buttonValue={displayItem.description}
107-
buttonLabel={displayItem.description}
108-
radioButtonType="large"
147+
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
148+
{enablePreWetTipDisplayItems.map(displayItem => (
149+
<RadioButton
150+
key={displayItem.description}
151+
isSelected={pushOutIsEnabled === displayItem.option}
152+
onChange={displayItem.onClick}
153+
buttonValue={displayItem.description}
154+
buttonLabel={displayItem.description}
155+
radioButtonType="large"
156+
/>
157+
))}
158+
</Flex>
159+
</Flex>
160+
) : null}
161+
{currentStep === 2 ? (
162+
<Flex
163+
alignSelf={ALIGN_CENTER}
164+
gridGap={SPACING.spacing48}
165+
paddingX={SPACING.spacing40}
166+
padding={`${SPACING.spacing16} ${SPACING.spacing40} ${SPACING.spacing40}`}
167+
marginTop="7.75rem"
168+
alignItems={ALIGN_CENTER}
169+
height="22rem"
170+
>
171+
<Flex
172+
width="30.5rem"
173+
height="100%"
174+
gridGap={SPACING.spacing24}
175+
flexDirection={DIRECTION_COLUMN}
176+
marginTop={SPACING.spacing68}
177+
>
178+
<InputField
179+
type="number"
180+
value={volume}
181+
error={volumeError}
182+
title={t('push_out_volume')}
183+
readOnly
184+
/>
185+
</Flex>
186+
<Flex
187+
paddingX={SPACING.spacing24}
188+
height="21.25rem"
189+
marginTop="7.75rem"
190+
>
191+
<NumericalKeyboard
192+
keyboardRef={keyboardRef}
193+
isDecimal
194+
initialValue={String(volume ?? '')}
195+
onChange={e => {
196+
setVolume(Number(e))
197+
}}
109198
/>
110-
))}
199+
</Flex>
111200
</Flex>
112-
</Flex>
201+
) : null}
113202
</Flex>,
114203
getTopPortalEl()
115204
)

app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/__tests__/PushOut.test.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,35 @@ describe('PushOut', () => {
4848
screen.getByText('Helps ensure all liquid leaves the tip')
4949
screen.getByText('Enabled')
5050
screen.getByText('Disabled')
51+
fireEvent.click(screen.getByText('Disabled'))
52+
screen.getByText('Save')
53+
})
54+
55+
it('renders text, button, and keyboard for push out volume', () => {
56+
render(props)
57+
fireEvent.click(screen.getByText('Enabled'))
58+
fireEvent.click(screen.getByText('Continue'))
59+
screen.getByText('Push out volume (µL)')
60+
screen.getByText('Save')
61+
screen.getByRole('button', { name: '1' })
62+
screen.getByRole('button', { name: '5' })
63+
screen.getByRole('button', { name: '9' })
64+
screen.getByRole('button', { name: 'del' })
65+
screen.getByRole('button', { name: '.' })
5166
})
5267

5368
it('should call dispatch when clicking save button', () => {
5469
render(props)
5570
fireEvent.click(screen.getByText('Enabled'))
71+
fireEvent.click(screen.getByText('Continue'))
72+
fireEvent.click(screen.getByRole('button', { name: '1' }))
73+
fireEvent.click(screen.getByRole('button', { name: '0' }))
5674
fireEvent.click(screen.getByText('Save'))
5775
expect(props.dispatch).toHaveBeenCalledWith({
5876
type: 'SET_PUSH_OUT',
59-
pushOut: !props.state.preWetTip,
77+
pushOutSettings: {
78+
volume: 10,
79+
},
6080
})
6181
expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({
6282
name: 'quickTransferSettingSaved',

app/src/organisms/ODD/QuickTransferFlow/README.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,24 @@ export interface QuickTransferSummaryState {
9696
path: PathOption
9797
tipPositionAspirate: number
9898
preWetTip: boolean
99+
pushOutDispense?: { // this has been update - pushOut boolean
100+
volume: number
101+
}
99102
mixOnAspirate?: {
100103
mixVolume: number
101104
repetitions: number
102105
}
103-
submergeAspirate?: {
104-
// this has been added
106+
submergeAspirate?: { // this has been added
105107
speed: number
106108
delayDuration: number
107109
positionFromBottom: number
108110
}
109-
retractAspirate?: {
110-
// this has been added
111+
retractAspirate?: { // this has been added
111112
speed: number
112113
delayDuration: number
113114
positionFromBottom: number
114115
}
115-
delayAspirate?: {
116+
delayAspirate?: { // this has been updated - removed positionFromBottom
116117
delayDuration: number
117118
}
118119
touchTipAspirate?: number
@@ -123,34 +124,34 @@ export interface QuickTransferSummaryState {
123124
mixVolume: number
124125
repetitions: number
125126
}
126-
submergeDispense?: {
127-
// this has been added
127+
submergeDispense?: { // this has been added
128128
speed: number
129129
delayDuration: number
130130
positionFromBottom: number
131131
}
132-
retractDispense?: {
133-
// this has been added
132+
retractDispense?: { // this has been added
134133
speed: number
135134
delayDuration: number
136135
positionFromBottom: number
137136
}
138-
delayDispense?: {
137+
delayDispense?: { // this has been updated - removed positionFromBottom
139138
delayDuration: number
140139
}
141140
touchTipDispense?: number
142141
touchTipDispenseSpeed?: number
143142
disposalVolume?: number
144-
blowOutDispense?: {
145-
// this has been updated
143+
blowOutDispense?: { // this has been added - updated from blowOut
146144
location?: BlowOutLocation
147-
speed: number
145+
flowRate?: number
148146
}
149147
airGapDispense?: number
150148
changeTip: ChangeTipOptions
151149
dropTipLocation: CutoutConfig
152-
liquidClass: LiquidClass // this has been added
153-
pushOut: boolean // this has been added
154-
conditionAspirate?: number // this has been added
155-
}
150+
liquidClass: LiquidClass // this has been added
151+
conditionAspirate?: number // this has been added
152+
disposalVolumeDispenseSettings?: { // this has been added
153+
volume: number
154+
blowOutLocation: BlowOutLocation
155+
flowRate: number
156+
}
156157
```

app/src/organisms/ODD/QuickTransferFlow/reducers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export function quickTransferSummaryReducer(
266266
case 'SET_PUSH_OUT': {
267267
return {
268268
...state,
269-
pushOut: action.pushOut,
269+
pushOutDispense: action.pushOutSettings,
270270
}
271271
}
272272
case 'SET_CONDITION_ASPIRATE': {

app/src/organisms/ODD/QuickTransferFlow/types.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ export interface QuickTransferSummaryState {
6464
path: PathOption
6565
tipPositionAspirate: number
6666
preWetTip: boolean
67-
pushOut: boolean
67+
pushOutDispense?: {
68+
volume: number
69+
}
6870
mixOnAspirate?: {
6971
mixVolume: number
7072
repetitions: number
@@ -315,7 +317,9 @@ interface SetVolumeAction {
315317

316318
interface SetPushOut {
317319
type: typeof ACTIONS.SET_PUSH_OUT
318-
pushOut: boolean
320+
pushOutSettings?: {
321+
volume: number
322+
}
319323
}
320324

321325
interface SetConditionAspirate {

0 commit comments

Comments
 (0)