Skip to content

Commit

Permalink
feat(protocol-designer): Add module slot placement guidance (#4916)
Browse files Browse the repository at this point in the history
closes #4815
  • Loading branch information
Kadee80 authored Feb 7, 2020
1 parent 710cffa commit ae06796
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 37 deletions.
2 changes: 1 addition & 1 deletion protocol-designer/src/components/FilePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import i18n from '../localization'
import { Portal } from './portals/MainPageModalPortal'
import styles from './FilePage.css'
import EditPipettesModal from './modals/EditPipettesModal'
import EditModulesModal from './modals/EditModulesModal'
import { EditModulesModal } from './modals/EditModulesModal'
import { EditModulesCard } from './modules'
import formStyles from '../components/forms/forms.css'
import type { FileMetadataFields } from '../file-data'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const PauseForm = (props: PauseFormProps): React.Element<'div'> => {
)

const pauseUntilTempTooltip = (
<div className={styles.slot_tooltip}>
<div>
{i18n.t(
`tooltip.step_fields.pauseForAmountOfTime.disabled.wait_until_temp`
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
.form_row {
display: flex;
justify-content: flex-start;
align-items: flex-start;
margin: 2rem 0;
}

.option_model {
flex: 0 0 11rem;
flex: 0 1 11rem;
}

.option_slot {
flex: 0 0 11rem;
flex: 0 1 11rem;
margin-left: 3rem;
padding: 0 0.25rem 0.25rem;
}
Expand All @@ -44,3 +45,8 @@
margin-left: 1rem;
}
}

.slot_map_container {
flex: 0 1 13rem;
padding: 0 3rem 0;
}
58 changes: 35 additions & 23 deletions protocol-designer/src/components/modals/EditModulesModal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FormGroup,
DropdownField,
HoverTooltip,
SlotMap,
} from '@opentrons/components'
import {
selectors as stepFormSelectors,
Expand All @@ -35,7 +36,7 @@ type EditModulesProps = {
onCloseClick: () => mixed,
}

export default function EditModulesModal(props: EditModulesProps) {
export function EditModulesModal(props: EditModulesProps) {
const { moduleType, onCloseClick } = props
const _initialDeckSetup = useSelector(stepFormSelectors.getInitialDeckSetup)

Expand Down Expand Up @@ -63,8 +64,9 @@ export default function EditModulesModal(props: EditModulesProps) {
featureFlagSelectors.getDisableModuleRestrictions
)

const occupiedSlotError =
!slotIsEmpty && enableSlotSelection ? 'Selected slot is occupied' : null
const occupiedSlotError = !slotIsEmpty
? `Slot ${selectedSlot} is occupied by another module or by labware incompatible with this module. Remove module or labware from the slot in order to continue.`
: null

const dispatch = useDispatch()

Expand Down Expand Up @@ -118,7 +120,7 @@ export default function EditModulesModal(props: EditModulesProps) {
<PDAlert
alertType="warning"
title={i18n.t('alert.module_placement.SLOT_OCCUPIED.title')}
description={i18n.t('alert.module_placement.SLOT_OCCUPIED.body')}
description={''}
/>
)}
<form>
Expand All @@ -132,25 +134,35 @@ export default function EditModulesModal(props: EditModulesProps) {
/>
</FormGroup>
{showSlotOption && (
<HoverTooltip
placement="bottom"
tooltipComponent={enableSlotSelection ? null : slotOptionTooltip}
>
{hoverTooltipHandlers => (
<div {...hoverTooltipHandlers} className={styles.option_slot}>
<FormGroup label="Position">
<DropdownField
tabIndex={1}
options={getAllModuleSlotsByType(moduleType)}
value={selectedSlot}
disabled={!enableSlotSelection}
onChange={handleSlotChange}
error={occupiedSlotError}
/>
</FormGroup>
</div>
)}
</HoverTooltip>
<>
<HoverTooltip
placement="top"
tooltipComponent={slotOptionTooltip}
>
{hoverTooltipHandlers => (
<div {...hoverTooltipHandlers} className={styles.option_slot}>
<FormGroup label="Position">
<DropdownField
tabIndex={1}
options={getAllModuleSlotsByType(moduleType)}
value={selectedSlot}
disabled={!enableSlotSelection}
onChange={handleSlotChange}
error={occupiedSlotError}
/>
</FormGroup>
</div>
)}
</HoverTooltip>
<div className={styles.slot_map_container}>
{selectedSlot && (
<SlotMap
occupiedSlots={[`${selectedSlot}`]}
isError={Boolean(occupiedSlotError)}
/>
)}
</div>
</>
)}
</div>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import reduce from 'lodash/reduce'
import omit from 'lodash/omit'
import * as React from 'react'
import cx from 'classnames'
import { getCrashablePipetteSelected } from '../../../step-forms'
import { getIsCrashablePipetteSelected } from '../../../step-forms'
import {
Modal,
FormGroup,
Expand Down Expand Up @@ -208,7 +208,7 @@ export class FilePipettesModal extends React.Component<Props, State> {
const canSubmit = pipetteSelectionIsValid && tiprackSelectionIsValid

const showCrashInfoBox =
getCrashablePipetteSelected(this.state.pipettesByMount) &&
getIsCrashablePipetteSelected(this.state.pipettesByMount) &&
this.getCrashableModuleSelected(this.state.modulesByType)

const visibleModules = this.props.thermocyclerEnabled
Expand Down
16 changes: 13 additions & 3 deletions protocol-designer/src/components/modules/EditModulesCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { useSelector } from 'react-redux'
import { Card } from '@opentrons/components'
import {
selectors as stepFormSelectors,
getCrashablePipetteSelected,
getIsCrashablePipetteSelected,
} from '../../step-forms'
import { selectors as featureFlagSelectors } from '../../feature-flags'
import { SUPPORTED_MODULE_TYPES } from '../../modules'
import { THERMOCYCLER } from '../../constants'
import { CrashInfoBox } from './CrashInfoBox'
Expand All @@ -31,9 +32,17 @@ export function EditModulesCard(props: Props) {
stepFormSelectors.getPipettesForEditPipetteForm
)

const moduleRestritionsDisabled = Boolean(
useSelector(featureFlagSelectors.getDisableModuleRestrictions)
)
const crashablePipettesSelected = getIsCrashablePipetteSelected(
pipettesByMount
)

const warningsEnabled =
!moduleRestritionsDisabled && crashablePipettesSelected
const showCrashInfoBox =
getCrashablePipetteSelected(pipettesByMount) &&
(modules.magdeck || modules.tempdeck)
warningsEnabled && (modules.magdeck || modules.tempdeck)

return (
<Card title="Modules">
Expand All @@ -51,6 +60,7 @@ export function EditModulesCard(props: Props) {
<ModuleRow
type={moduleType}
module={moduleData}
showCollisionWarnings={warningsEnabled}
key={i}
openEditModuleModal={openEditModuleModal}
/>
Expand Down
68 changes: 65 additions & 3 deletions protocol-designer/src/components/modules/ModuleRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import i18n from '../../localization'
import { useDispatch } from 'react-redux'
import { actions as stepFormActions } from '../../step-forms'

import { LabeledValue, OutlineButton } from '@opentrons/components'
import {
LabeledValue,
OutlineButton,
SlotMap,
HoverTooltip,
} from '@opentrons/components'
import ModuleDiagram from './ModuleDiagram'
import { SPAN7_8_10_11_SLOT } from '../../constants'
import styles from './styles.css'
Expand All @@ -14,25 +19,63 @@ import type { ModuleOnDeck } from '../../step-forms'

type Props = {
module?: ModuleOnDeck,
showCollisionWarnings?: boolean,
type: ModuleType,
openEditModuleModal: (moduleType: ModuleType, moduleId?: string) => mixed,
}

export function ModuleRow(props: Props) {
const { module, openEditModuleModal } = props
const { module, openEditModuleModal, showCollisionWarnings } = props
const type = module?.type || props.type

const model = module?.model
const slot = module?.slot

/*
TODO (ka 2020-2-3): This logic is very specific to this individual implementation
of SlotMap. Kept it here (for now?) because it spells out the different cases.
*/
let slotDisplayName = null
if (slot) {
let occupiedSlotsForMap: Array<string> = []
let collisionSlots: Array<string> = []
// Populate warnings are enabled (crashable pipette in protocol + !disable module restrictions)
if (showCollisionWarnings && slot === '1') {
collisionSlots = ['4']
} else if (showCollisionWarnings && slot === '3') {
collisionSlots = ['6']
}

// If this module is in a deck slot + is not TC spanning Slot
// add to occupiedSlots
if (slot && slot !== SPAN7_8_10_11_SLOT) {
slotDisplayName = `Slot ${slot}`
occupiedSlotsForMap = [slot]
}
// If this Module is a TC deck slot and spanning
// populate all 4 slots individually
if (slot === SPAN7_8_10_11_SLOT) {
slotDisplayName = 'Slot 7'
occupiedSlotsForMap = ['7', '8', '10', '11']
}

// If collisionSlots are populated, check which slot is occupied
// and render module specific crash warning. This logic assumes
// default module slot placement magnet = Slot1 temperature = Slot3
let collisionTooltipText = null
if (collisionSlots && collisionSlots.includes('4')) {
collisionTooltipText = i18n.t(
`tooltip.edit_module_card.magnetic_module_collision`
)
} else if (collisionSlots && collisionSlots.includes('6')) {
collisionTooltipText = i18n.t(
`tooltip.edit_module_card.temperature_module_collision`
)
}

const collisionTooltip = collisionTooltipText && (
<div className={styles.collision_tolltip}>{collisionTooltipText}</div>
)

const setCurrentModule = (moduleType: ModuleType, moduleId?: string) => () =>
openEditModuleModal(moduleType, moduleId)

Expand Down Expand Up @@ -61,6 +104,25 @@ export function ModuleRow(props: Props) {
<div className={styles.module_col}>
{slot && <LabeledValue label="Position" value={slotDisplayName} />}
</div>
<div className={styles.slot_map}>
{slot && (
<HoverTooltip
placement="bottom"
tooltipComponent={
collisionSlots.length > 0 ? collisionTooltip : null
}
>
{hoverTooltipHandlers => (
<div {...hoverTooltipHandlers}>
<SlotMap
occupiedSlots={occupiedSlotsForMap}
collisionSlots={collisionSlots}
/>
</div>
)}
</HoverTooltip>
)}
</div>
<div className={styles.modules_button_group}>
{module && (
<OutlineButton
Expand Down
11 changes: 10 additions & 1 deletion protocol-designer/src/components/modules/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@
}

.module_col {
flex: 1 0 20%;
flex: 1 0 15%;
padding-top: 0.5rem; /* TODO remove padding above once images are cropped properly */
}

.slot_map {
flex: 1 0 12%;
}

.row_title {
@apply --font-body-2-dark;

Expand Down Expand Up @@ -97,3 +101,8 @@
.link {
@apply --font-body-1-dark;
}

.collision_tolltip {
width: 13rem;
line-height: var(--lh-copy);
}
4 changes: 4 additions & 0 deletions protocol-designer/src/localization/en/tooltip.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
}
}
},
"edit_module_card": {
"magnetic_module_collision": "When magnetic module is in Slot 1, GEN1 8-Channel pipette cannot access Slot 4.",
"temperature_module_collision": "When temperature module is in Slot 3, GEN1 8-Channel pipette cannot access Slot 6. "
},
"edit_module_modal": {
"slot_selection": "To enable non-default/unrestricted positioning, go to Settings > App > Experimental settings."
}
Expand Down
2 changes: 1 addition & 1 deletion protocol-designer/src/step-forms/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const getSlotIsEmpty = (
)
}

export const getCrashablePipetteSelected = (
export const getIsCrashablePipetteSelected = (
pipettesByMount: FormPipettesByMount
) => {
const { left, right } = pipettesByMount
Expand Down

0 comments on commit ae06796

Please sign in to comment.