Skip to content

Commit

Permalink
feat(app): allow custom tiprack selection in deck cal & pipette offse…
Browse files Browse the repository at this point in the history
…t cal

closes #7087
  • Loading branch information
ahiuchingau committed Dec 16, 2020
1 parent 47f7e1e commit 84f3335
Show file tree
Hide file tree
Showing 2 changed files with 323 additions and 0 deletions.
244 changes: 244 additions & 0 deletions app/src/components/CalibrationPanels/ChooseTipRack.js
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>
&nbsp;
<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 app/src/components/CalibrationPanels/ChosenTipRackRender.js
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>
)
}

0 comments on commit 84f3335

Please sign in to comment.