-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(app): allow custom tiprack selection in deck cal & pipette offse…
…t cal closes #7087
- Loading branch information
1 parent
47f7e1e
commit 84f3335
Showing
2 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
// @flow | ||
import * as React from 'react' | ||
import { useSelector } from 'react-redux' | ||
import isEqual from 'lodash/isEqual' | ||
|
||
import { | ||
ALIGN_FLEX_START, | ||
BORDER_SOLID_MEDIUM, | ||
Box, | ||
DISPLAY_INLINE, | ||
DIRECTION_COLUMN, | ||
Flex, | ||
FONT_HEADER_DARK, | ||
FONT_SIZE_BODY_1, | ||
FONT_SIZE_BODY_2, | ||
JUSTIFY_SPACE_BETWEEN, | ||
JUSTIFY_CENTER, | ||
POSITION_RELATIVE, | ||
PrimaryBtn, | ||
Select, | ||
SPACING_1, | ||
SPACING_2, | ||
SPACING_3, | ||
SPACING_4, | ||
Text, | ||
TEXT_TRANSFORM_UPPERCASE, | ||
TEXT_TRANSFORM_CAPITALIZE, | ||
FONT_WEIGHT_SEMIBOLD, | ||
ALIGN_CENTER, | ||
} from '@opentrons/components' | ||
import * as Sessions from '../../sessions' | ||
import { NeedHelpLink } from './NeedHelpLink' | ||
import { ChosenTipRackRender } from './ChosenTipRackRender' | ||
import { getLatestLabwareDef } from '../../getLabware' | ||
import { getCustomTipRackDefinitions } from '../../custom-labware' | ||
import { getAttachedPipettes } from '../../pipettes' | ||
import { getLabwareDefURI } from '@opentrons/shared-data' | ||
|
||
import type { Intent } from './types' | ||
import type { AttachedPipette } from '../../pipettes/types' | ||
import type { SessionType, CalibrationLabware } from '../../sessions/types' | ||
import type { State } from '../../types' | ||
import type { SelectOptionOrGroup, SelectOption } from '@opentrons/components' | ||
import type { LabwareDefinition2 } from '@opentrons/shared-data' | ||
import styles from './styles.css' | ||
import { INTENT_PIPETTE_OFFSET } from './constants' | ||
|
||
const HEADER = 'choose tip rack' | ||
const INTRO = 'Choose what tip rack you would like to use to calibrate your' | ||
const PIP_OFFSET_INTRO_FRAGMENT = 'Pipette Offset' | ||
const DECK_CAL_INTRO_FRAGMENT = 'Deck' | ||
|
||
const PROMPT = | ||
'Want to use a tip rack that is not listed here? Go to More > Custom Labware to add labware.' | ||
|
||
const CALIBRATED_LABEL = 'calibrated' | ||
const NOT_YET_CALIBRATED_LABEL = 'not yet calibrated' | ||
|
||
const SELECT_TIP_RACK = 'select tip rack' | ||
const NOTE_HEADER = 'Please note:' | ||
const NOTE_BODY = | ||
'Opentrons cannot guarantee accuracy with third party tip racks.' | ||
const USE_THIS_TIP_RACK = 'use this tip rack' | ||
|
||
const introContentByType: SessionType => string = sessionType => { | ||
switch (sessionType) { | ||
case Sessions.SESSION_TYPE_DECK_CALIBRATION: | ||
return `${INTRO} ${DECK_CAL_INTRO_FRAGMENT}.` | ||
case Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION: | ||
return `${INTRO} ${PIP_OFFSET_INTRO_FRAGMENT}.` | ||
default: | ||
return 'This panel is shown in error' | ||
} | ||
} | ||
|
||
function formatOptionsFromLabwareDef(lw: LabwareDefinition2) { | ||
return { | ||
label: lw.metadata.displayName, | ||
value: getLabwareDefURI(lw), | ||
} | ||
} | ||
|
||
function formatOptionsFromLabwareUri(uri: string) { | ||
const loadName = uri.split('/')[1] | ||
const labwareDef = getLatestLabwareDef(loadName) | ||
return labwareDef | ||
? { label: labwareDef.metadata.displayName, value: uri } | ||
: null | ||
} | ||
|
||
function getOptionsByIntent( | ||
tipRackOptions: array<SelectOptionOrGroup>, | ||
intent: ?Intent | ||
) { | ||
switch (intent) { | ||
case INTENT_PIPETTE_OFFSET: { | ||
// TODO: find out which tiprack has been calbirated using lodash.partition | ||
return [ | ||
{ label: CALIBRATED_LABEL, options: [] }, | ||
{ label: NOT_YET_CALIBRATED_LABEL, options: tipRackOptions }, | ||
] | ||
} | ||
default: | ||
return tipRackOptions | ||
} | ||
} | ||
|
||
type ChooseTipRackProps = {| | ||
tipRack: CalibrationLabware, | ||
mount: string, | ||
sessionType: SessionType, | ||
robotName: string, | ||
sessionTipRack: SelectOption | null, | ||
handleSessionTiprack: (arg: SelectOption) => mixed, | ||
closeModal: () => mixed, | ||
intent?: Intent, | ||
|} | ||
|
||
export function ChooseTipRack(props: ChooseTipRackProps): React.Node { | ||
const { | ||
tipRack, | ||
sessionType, | ||
mount, | ||
robotName, | ||
sessionTipRack, | ||
handleSessionTiprack, | ||
closeModal, | ||
intent, | ||
} = props | ||
|
||
const attachedPipette = useSelector( | ||
(state: State) => getAttachedPipettes(state, robotName)[mount] | ||
) | ||
const customTipRacks = useSelector(getCustomTipRackDefinitions) | ||
const customTipRackOptions = customTipRacks.map(formatOptionsFromLabwareDef) | ||
|
||
const opentronsTipRackUris = attachedPipette.modelSpecs.defaultTipracks | ||
const opentronsTipRackOptions = opentronsTipRackUris.map( | ||
formatOptionsFromLabwareUri | ||
) | ||
|
||
const groupOptions = getOptionsByIntent( | ||
opentronsTipRackOptions.concat(customTipRackOptions), | ||
intent | ||
) | ||
console.log(groupOptions) | ||
|
||
const [selectedValue, setSelectedValue] = React.useState<SelectOption>( | ||
formatOptionsFromLabwareDef(tipRack.definition) | ||
) | ||
|
||
const handleValueChange = (selected: SelectOption | null, _) => { | ||
selected && setSelectedValue(selected) | ||
} | ||
const handleUseTipRack = () => { | ||
if (!isEqual(sessionTipRack, selectedValue.value)) { | ||
handleSessionTiprack(selectedValue) | ||
} | ||
closeModal() | ||
} | ||
const introText = introContentByType(sessionType) | ||
return ( | ||
<Flex | ||
key={'chooseTipRack'} | ||
marginTop={SPACING_2} | ||
marginBottom={SPACING_3} | ||
flexDirection={DIRECTION_COLUMN} | ||
alignItems={ALIGN_FLEX_START} | ||
position={POSITION_RELATIVE} | ||
fontSize={FONT_SIZE_BODY_2} | ||
width="100%" | ||
> | ||
<Flex width="100%" justifyContent={JUSTIFY_SPACE_BETWEEN}> | ||
<Text | ||
css={FONT_HEADER_DARK} | ||
marginBottom={SPACING_3} | ||
textTransform={TEXT_TRANSFORM_UPPERCASE} | ||
> | ||
{HEADER} | ||
</Text> | ||
<NeedHelpLink /> | ||
</Flex> | ||
<Box marginBottom={SPACING_4}> | ||
<Text marginBottom={SPACING_3}>{introText}</Text> | ||
<Text>{PROMPT}</Text> | ||
</Box> | ||
<Flex | ||
width="80%" | ||
marginBottom={SPACING_2} | ||
justifyContent={JUSTIFY_CENTER} | ||
flexDirection={DIRECTION_COLUMN} | ||
alignSelf={ALIGN_CENTER} | ||
> | ||
<Flex | ||
height="16rem" | ||
border={BORDER_SOLID_MEDIUM} | ||
paddingTop={SPACING_4} | ||
justifyContent={JUSTIFY_SPACE_BETWEEN} | ||
marginBottom={SPACING_2} | ||
> | ||
<Box width="55%" paddingLeft={SPACING_4}> | ||
<Text | ||
textTransform={TEXT_TRANSFORM_CAPITALIZE} | ||
fontWeight={FONT_WEIGHT_SEMIBOLD} | ||
marginBottom={SPACING_1} | ||
> | ||
{SELECT_TIP_RACK} | ||
</Text> | ||
<Select | ||
className={styles.select_tiprack_menu} | ||
options={groupOptions} | ||
onChange={handleValueChange} | ||
value={selectedValue} | ||
/> | ||
</Box> | ||
<Box width="45%" height="100%"> | ||
<ChosenTipRackRender | ||
selectedValue={selectedValue} | ||
intent={intent} | ||
/> | ||
</Box> | ||
</Flex> | ||
<Flex marginBottom={SPACING_4} fontSize={FONT_SIZE_BODY_1}> | ||
<Text | ||
display={DISPLAY_INLINE} | ||
fontWeight={FONT_WEIGHT_SEMIBOLD} | ||
textTransform={TEXT_TRANSFORM_UPPERCASE} | ||
> | ||
{NOTE_HEADER} | ||
</Text> | ||
| ||
<Text>{NOTE_BODY}</Text> | ||
</Flex> | ||
</Flex> | ||
<PrimaryBtn | ||
alignSelf={ALIGN_CENTER} | ||
width="50%" | ||
onClick={handleUseTipRack} | ||
> | ||
{USE_THIS_TIP_RACK} | ||
</PrimaryBtn> | ||
</Flex> | ||
) | ||
} |
79 changes: 79 additions & 0 deletions
79
app/src/components/CalibrationPanels/ChosenTipRackRender.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// @flow | ||
import * as React from 'react' | ||
import { css } from 'styled-components' | ||
|
||
import { | ||
Box, | ||
Flex, | ||
Text, | ||
ALIGN_CENTER, | ||
C_MED_DARK_GRAY, | ||
DIRECTION_COLUMN, | ||
FONT_SIZE_BODY_1, | ||
FONT_SIZE_BODY_2, | ||
FONT_STYLE_ITALIC, | ||
JUSTIFY_CENTER, | ||
SPACING_2, | ||
SPACING_3, | ||
TEXT_ALIGN_CENTER, | ||
} from '@opentrons/components' | ||
import { labwareImages } from './labwareImages' | ||
|
||
import type { Intent } from './types' | ||
import type { SelectOption } from '@opentrons/components' | ||
|
||
const TIP_LENGTH_CALIBRATED_PROMPT = 'Calibrated on' | ||
const TIP_LENGTH_UNCALIBRATED_PROMPT = | ||
'Not yet calibrated. You will calibrate this tip length before proceeding to Pipette Offset Calibration.' | ||
|
||
export type ChosenTipRackRenderProps = {| | ||
selectedValue: SelectOption, | ||
intent?: Intent, | ||
|} | ||
|
||
export function ChosenTipRackRender(props: ChosenTipRackRenderProps): React.Node { | ||
const { selectedValue, intent } = props | ||
const loadName = selectedValue.value.split('/')[1] | ||
const displayName = selectedValue.label | ||
const calibrated = true // TODO: figure out if it's actually calibrated only if intent is pipette offset | ||
|
||
const imageSrc = | ||
loadName in labwareImages | ||
? labwareImages[loadName] | ||
: labwareImages['generic_custom_tiprack'] | ||
return ( | ||
<Flex | ||
height="100%" | ||
flexDirection={DIRECTION_COLUMN} | ||
alignItems={ALIGN_CENTER} | ||
justifyContent={JUSTIFY_CENTER} | ||
paddingRight={SPACING_2} | ||
paddingBottom={SPACING_3} | ||
fontSize={FONT_SIZE_BODY_2} | ||
> | ||
<img | ||
css={css` | ||
max-width: 50%; | ||
max-height: 80%; | ||
flex: 0 1 5rem; | ||
display: block; | ||
margin-bottom: 1rem; | ||
`} | ||
src={imageSrc} | ||
/> | ||
<Box> | ||
<Text textAlign={TEXT_ALIGN_CENTER} marginBottom={SPACING_2}>{displayName}</Text> | ||
<Text | ||
color={C_MED_DARK_GRAY} | ||
fontSize={FONT_SIZE_BODY_1} | ||
fontStyle={FONT_STYLE_ITALIC} | ||
textAlign={TEXT_ALIGN_CENTER} | ||
> | ||
{calibrated | ||
? TIP_LENGTH_CALIBRATED_PROMPT | ||
: TIP_LENGTH_UNCALIBRATED_PROMPT} | ||
</Text> | ||
</Box> | ||
</Flex> | ||
) | ||
} |