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): guide the user through leveling gen2 multis #5348

Merged
merged 12 commits into from
Apr 20, 2020
20 changes: 15 additions & 5 deletions app/src/components/ChangePipette/Instructions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
PipetteModelSpecs,
PipetteDisplayCategory,
} from '@opentrons/shared-data'
import { shouldLevel } from '@opentrons/shared-data'
import type { Mount } from '../../pipettes/types'
import type { Direction } from './types'

Expand Down Expand Up @@ -113,11 +114,20 @@ function Steps(props: Props) {
</div>
)
} else {
stepOne = (
<p>
Attach pipette to mount, <strong>starting with screw 1</strong>.
</p>
)
if (wantedPipette && shouldLevel(wantedPipette)) {
stepOne = (
<p>
<em>Loosely</em> attach pipette to carriage,{' '}
<strong>starting with screw 1</strong>
</p>
)
} else {
stepOne = (
<p>
Attach pipette to mount, <strong>starting with screw 1</strong>.
</p>
)
}
stepTwo =
'Connect the pipette to robot by pushing in the white connector tab.'
}
Expand Down
108 changes: 108 additions & 0 deletions app/src/components/ChangePipette/LevelPipette.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// @flow

import * as React from 'react'
import cx from 'classnames'

import { Icon, ModalPage, PrimaryButton } from '@opentrons/components'
import styles from './styles.css'

import type {
PipetteNameSpecs,
PipetteModelSpecs,
PipetteDisplayCategory,
} from '@opentrons/shared-data'

import type { Mount } from '../../pipettes/types'

// TODO: i18n
const EXIT_BUTTON_MESSAGE = 'confirm pipette is leveled'
const LEVEL_MESSAGE = (displayName: string) => `Next, level the ${displayName}`
const CONNECTED_MESSAGE = (displayName: string) => `${displayName} connected`

type Props = {|
robotName: string,
mount: Mount,
title: string,
subtitle: string,
wantedPipette: PipetteNameSpecs | null,
actualPipette: PipetteModelSpecs | null,
displayName: string,
displayCategory: PipetteDisplayCategory | null,
pipetteModelName: string,
back: () => mixed,
exit: () => mixed,
|}

function Status(props: { displayName: string }) {
const iconName = 'check-circle'
const iconClass = cx(styles.confirm_icon, {
[styles.success]: true,
[styles.failure]: false,
})

return (
<div className={styles.leveling_title}>
<Icon name={iconName} className={iconClass} />
{CONNECTED_MESSAGE(props.displayName)}
</div>
)
}

function ExitButton(props: { exit: () => mixed }) {
return (
<PrimaryButton className={styles.confirm_button} onClick={props.exit}>
{EXIT_BUTTON_MESSAGE}
</PrimaryButton>
)
}

function LevelingInstruction(props: { pipetteName: string }) {
return (
<div className={styles.leveling_instruction}>
{LEVEL_MESSAGE(props.pipetteName)}
</div>
)
}

function LevelingVideo(props: { pipetteName: string, mount: Mount }) {
const { pipetteName, mount } = props
return (
<div className={styles.leveling_video_wrapper}>
<video
className={styles.leveling_video}
autoPlay={true}
loop={true}
controls={true}
>
<source src={require(`./videos/${pipetteName}-${mount}.webm`)} />
</video>
</div>
)
}

export function LevelPipette(props: Props) {
const {
title,
subtitle,
pipetteModelName,
displayName,
mount,
back,
exit,
} = props
return (
<ModalPage
titleBar={{
title: title,
subtitle: subtitle,
back: { onClick: back, disabled: false },
}}
contentsClassName={styles.leveling_modal}
>
<Status displayName={displayName} />
<LevelingInstruction pipetteName={displayName} />
<LevelingVideo pipetteName={pipetteModelName} mount={mount} />
mcous marked this conversation as resolved.
Show resolved Hide resolved
<ExitButton exit={exit} />
</ModalPage>
)
}
46 changes: 30 additions & 16 deletions app/src/components/ChangePipette/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import * as React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import last from 'lodash/last'
import { getPipetteNameSpecs } from '@opentrons/shared-data'
import { getPipetteNameSpecs, shouldLevel } from '@opentrons/shared-data'

import { useDispatchApiRequest, getRequestById, PENDING } from '../../robot-api'
import { getAttachedPipettes } from '../../pipettes'
Expand All @@ -21,6 +21,7 @@ import { ExitAlertModal } from './ExitAlertModal'
import { Instructions } from './Instructions'
import { ConfirmPipette } from './ConfirmPipette'
import { RequestInProgressModal } from './RequestInProgressModal'
import { LevelPipette } from './LevelPipette'

import { ATTACH, DETACH, CLEAR_DECK, INSTRUCTIONS, CONFIRM } from './constants'

Expand Down Expand Up @@ -143,21 +144,34 @@ export function ChangePipette(props: Props) {

const attachedWrong = Boolean(!success && wantedPipette && actualPipette)

return (
<ConfirmPipette
{...{
...basePropsWithPipettes,
success,
attachedWrong,
tryAgain: () => {
setWantedName(null)
setWizardStep(INSTRUCTIONS)
},
back: () => setWizardStep(INSTRUCTIONS),
exit: homeAndExit,
}}
/>
)
if (success && wantedPipette && shouldLevel(wantedPipette)) {
return (
<LevelPipette
{...{
pipetteModelName: actualPipette ? actualPipette.name : '',
...basePropsWithPipettes,
back: () => setWizardStep(INSTRUCTIONS),
exit: homeAndExit,
}}
/>
)
} else {
return (
<ConfirmPipette
{...{
...basePropsWithPipettes,
success,
attachedWrong,
tryAgain: () => {
setWantedName(null)
setWizardStep(INSTRUCTIONS)
},
back: () => setWizardStep(INSTRUCTIONS),
exit: homeAndExit,
}}
/>
)
}
}

// this will never be reached
Expand Down
29 changes: 29 additions & 0 deletions app/src/components/ChangePipette/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,35 @@
padding: 2rem 1.25rem;
}

.leveling_title {
width: 100%;
display: flex;
align-items: center;
margin-bottom: 3rem;
font-weight: var(--fw-semibold);
}

.leveling_instruction {
width: 100%;
margin-bottom: 1.5rem;
}

.leveling_video_wrapper {
max-height: 30rem;
margin-bottom: 1rem;
}

.leveling_video {
max-width: 100%;
}

.leveling_modal {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}

.confirm_status {
display: flex;
align-items: center;
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions shared-data/js/pipettes.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ function comparePipettes(sortBy: Array<SortableProps>) {
return 0
}
}

export function shouldLevel(specs: PipetteNameSpecs) {
return specs.displayCategory === 'GEN2' && specs.channels === 8
}
1 change: 1 addition & 0 deletions webpack-config/lib/base-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module.exports = {
rules.handlebars,
rules.fonts,
rules.images,
rules.videos,
],
},

Expand Down
13 changes: 13 additions & 0 deletions webpack-config/lib/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,17 @@ module.exports = {
},
},
},

// videos
videos: {
test: /\.(?:mp4|webm)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[hash].[ext]',
outputPath: 'videos',
esModule: false,
},
},
},
}