Skip to content

Commit

Permalink
feat(protocol-designer): account for multiples of a module in FormMod…
Browse files Browse the repository at this point in the history
…ule type

closes AUTH-14
  • Loading branch information
jerader committed Mar 28, 2024
1 parent f3ddbb7 commit 528bbb4
Show file tree
Hide file tree
Showing 14 changed files with 318 additions and 418 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { css } from 'styled-components'
import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data'

import {
Flex,
Text,
Expand All @@ -15,6 +17,7 @@ import {
Tooltip,
} from '@opentrons/components'
import type { StyleProps } from '@opentrons/components'
import type { RobotType } from '@opentrons/shared-data'

const EQUIPMENT_OPTION_STYLE = css`
background-color: ${COLORS.white};
Expand Down Expand Up @@ -58,6 +61,7 @@ interface EquipmentOptionProps extends StyleProps {
onClick: React.MouseEventHandler
isSelected: boolean
text: React.ReactNode
robotType: RobotType
image?: React.ReactNode
showCheckbox?: boolean
disabled?: boolean
Expand All @@ -70,6 +74,7 @@ export function EquipmentOption(props: EquipmentOptionProps): JSX.Element {
image = null,
showCheckbox = false,
disabled = false,
robotType,
...styleProps
} = props
const { t } = useTranslation('tooltip')
Expand All @@ -80,7 +85,25 @@ export function EquipmentOption(props: EquipmentOptionProps): JSX.Element {
equpimentOptionStyle = EQUIPMENT_OPTION_DISABLED_STYLE
} else if (isSelected) {
equpimentOptionStyle = EQUIPMENT_OPTION_SELECTED_STYLE
} else equpimentOptionStyle = EQUIPMENT_OPTION_STYLE
} else {
equpimentOptionStyle = EQUIPMENT_OPTION_STYLE
}
let iconInfo: JSX.Element | null = null
if (showCheckbox && !disabled) {
iconInfo = (
<Icon
aria-label={`EquipmentOption_${
isSelected ? 'checkbox-marked' : 'checkbox-blank-outline'
}`}
color={isSelected ? COLORS.blue50 : COLORS.grey50}
size="1.5rem"
name={isSelected ? 'checkbox-marked' : 'checkbox-blank-outline'}
/>
)
} else if (showCheckbox && disabled) {
iconInfo = <Flex width="1.5rem"></Flex>
}

return (
<>
<Flex
Expand All @@ -101,16 +124,7 @@ export function EquipmentOption(props: EquipmentOptionProps): JSX.Element {
{...targetProps}
css={equpimentOptionStyle}
>
{showCheckbox ? (
<Icon
aria-label={`EquipmentOption_${
isSelected ? 'checkbox-marked' : 'checkbox-blank-outline'
}`}
color={isSelected ? COLORS.blue50 : COLORS.grey50}
size="1.5rem"
name={isSelected ? 'checkbox-marked' : 'checkbox-blank-outline'}
/>
) : null}
{iconInfo}
<Flex
justifyContent={JUSTIFY_CENTER}
alignItems={ALIGN_CENTER}
Expand All @@ -128,7 +142,11 @@ export function EquipmentOption(props: EquipmentOptionProps): JSX.Element {
</Flex>
{disabled ? (
<Tooltip {...tooltipProps}>
{t('disabled_no_space_additional_items')}
{t(
robotType === FLEX_ROBOT_TYPE
? 'disabled_no_space_additional_items'
: 'disabled_you_can_add_one_type'
)}
</Tooltip>
) : null}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getIsCrashablePipetteSelected } from '../../../step-forms'
import gripperImage from '../../../images/flex_gripper.png'
import wasteChuteImage from '../../../images/waste_chute.png'
import trashBinImage from '../../../images/flex_trash_bin.png'
import { uuid } from '../../../utils'
import { selectors as featureFlagSelectors } from '../../../feature-flags'
import { CrashInfoBox, ModuleDiagram } from '../../modules'
import { ModuleFields } from '../FilePipettesModal/ModuleFields'
Expand All @@ -48,7 +49,6 @@ import { EquipmentOption } from './EquipmentOption'
import { HandleEnter } from './HandleEnter'

import type { AdditionalEquipment, WizardTileProps } from './types'
import { uuid } from '../../../utils'

export const DEFAULT_SLOT_MAP: { [moduleModel in ModuleModel]?: string } = {
[THERMOCYCLER_MODULE_V2]: 'B1',
Expand All @@ -64,20 +64,10 @@ export const FLEX_SUPPORTED_MODULE_MODELS: ModuleModel[] = [
]

export function ModulesAndOtherTile(props: WizardTileProps): JSX.Element {
const {
formState,
getValues,
setValue,
goBack,
proceed,
control,
trigger,
watch,
} = props
const { getValues, goBack, proceed, watch } = props
const { t } = useTranslation(['modal', 'tooltip'])
const { fields, pipettesByMount, additionalEquipment } = getValues()
const modules = watch('modules')
const { errors, touchedFields } = formState
const robotType = fields.robotType
const moduleRestrictionsDisabled = useSelector(
featureFlagSelectors.getDisableModuleRestrictions
Expand Down Expand Up @@ -106,12 +96,20 @@ export function ModulesAndOtherTile(props: WizardTileProps): JSX.Element {
) != null
: false

const leftPipetteSpecs =
left.pipetteName != null && left.pipetteName !== ''
? getPipetteSpecsV2(left.pipetteName as PipetteName)
: undefined
const rightPipetteSpecs =
right.pipetteName != null && right.pipetteName !== ''
? getPipetteSpecsV2(right.pipetteName as PipetteName)
: undefined

const showHeaterShakerPipetteCollisions =
hasHeaterShakerSelected &&
[
getPipetteSpecsV2(left.pipetteName as PipetteName),
getPipetteSpecsV2(right.pipetteName as PipetteName),
].some(pipetteSpecs => pipetteSpecs && pipetteSpecs.channels !== 1)
[leftPipetteSpecs, rightPipetteSpecs].some(
pipetteSpecs => pipetteSpecs && pipetteSpecs.channels !== 1
)

const crashablePipetteSelected = getIsCrashablePipetteSelected(
pipettesByMount
Expand Down Expand Up @@ -140,20 +138,11 @@ export function ModulesAndOtherTile(props: WizardTileProps): JSX.Element {
gridGap={SPACING.spacing32}
>
<Text as="h2">{t('choose_additional_items')}</Text>
{/* {robotType === OT2_ROBOT_TYPE ? (
<ModuleFields
// @ts-expect-error
errors={errors?.modulesByType ?? null}
values={modulesByType}
onSetFieldValue={setValue}
// @ts-expect-error
touched={touchedFields.modulesByType ?? null}
control={control}
trigger={trigger}
/>
) : ( */}
<FlexModuleFields {...props} />
{/* )} */}
{robotType === OT2_ROBOT_TYPE ? (
<ModuleFields {...props} />
) : (
<FlexModuleFields {...props} />
)}
{robotType === OT2_ROBOT_TYPE && moduleRestrictionsDisabled !== true
? modCrashWarning
: null}
Expand Down Expand Up @@ -196,11 +185,9 @@ export function ModulesAndOtherTile(props: WizardTileProps): JSX.Element {
}

function FlexModuleFields(props: WizardTileProps): JSX.Element {
const { getValues, watch, setValue } = props
const { fields } = getValues()
const { watch, setValue } = props
const modules = watch('modules')
const additionalEquipment = watch('additionalEquipment')
const isFlex = fields.robotType === FLEX_ROBOT_TYPE
const moduleTypesOnDeck =
modules != null ? Object.values(modules).map(module => module.type) : []
const trashBinDisabled = getTrashBinOptionDisabled({
Expand Down Expand Up @@ -229,6 +216,7 @@ function FlexModuleFields(props: WizardTileProps): JSX.Element {
const moduleOnDeck = moduleTypesOnDeck.includes(moduleType)
return (
<EquipmentOption
robotType={FLEX_ROBOT_TYPE}
key={moduleModel}
isSelected={moduleOnDeck}
image={<ModuleDiagram type={moduleType} model={moduleModel} />}
Expand Down Expand Up @@ -266,6 +254,7 @@ function FlexModuleFields(props: WizardTileProps): JSX.Element {
)
})}
<EquipmentOption
robotType={FLEX_ROBOT_TYPE}
onClick={() => handleSetEquipmentOption('gripper')}
isSelected={additionalEquipment.includes('gripper')}
image={
Expand All @@ -277,35 +266,31 @@ function FlexModuleFields(props: WizardTileProps): JSX.Element {
text="Gripper"
showCheckbox
/>
{isFlex ? (
<>
<EquipmentOption
onClick={() => handleSetEquipmentOption('wasteChute')}
isSelected={additionalEquipment.includes('wasteChute')}
image={
<AdditionalItemImage
src={wasteChuteImage}
alt="Opentrons Waste Chute"
/>
}
text="Waste Chute"
showCheckbox
/>
<EquipmentOption
onClick={() => handleSetEquipmentOption('trashBin')}
isSelected={additionalEquipment.includes('trashBin')}
image={
<AdditionalItemImage
src={trashBinImage}
alt="Opentrons Trash Bin"
/>
}
text="Trash Bin"
showCheckbox
disabled={trashBinDisabled}

<EquipmentOption
robotType={FLEX_ROBOT_TYPE}
onClick={() => handleSetEquipmentOption('wasteChute')}
isSelected={additionalEquipment.includes('wasteChute')}
image={
<AdditionalItemImage
src={wasteChuteImage}
alt="Opentrons Waste Chute"
/>
</>
) : null}
}
text="Waste Chute"
showCheckbox
/>
<EquipmentOption
robotType={FLEX_ROBOT_TYPE}
onClick={() => handleSetEquipmentOption('trashBin')}
isSelected={additionalEquipment.includes('trashBin')}
image={
<AdditionalItemImage src={trashBinImage} alt="Opentrons Trash Bin" />
}
text="Trash Bin"
showCheckbox
disabled={trashBinDisabled}
/>
</Flex>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ interface PipetteTipsFieldProps extends UseFormReturn<FormState> {
}

function PipetteTipsField(props: PipetteTipsFieldProps): JSX.Element | null {
const { mount, watch, setValue } = props
const { mount, watch, setValue, getValues } = props
const { t } = useTranslation('modal')
const { fields } = getValues()
const pipettesByMount = watch('pipettesByMount')
const allowAllTipracks = useSelector(getAllowAllTipracks)
const dispatch = useDispatch<ThunkDispatch<BaseState, any, any>>()
Expand Down Expand Up @@ -197,6 +198,7 @@ function PipetteTipsField(props: PipetteTipsFieldProps): JSX.Element | null {
<Flex flexWrap="wrap" gridGap={SPACING.spacing4} alignSelf={ALIGN_CENTER}>
{defaultTiprackOptions.map(o => (
<EquipmentOption
robotType={fields.robotType}
key={o.name}
isSelected={selectedValues.includes(o.value)}
text={o.name}
Expand Down Expand Up @@ -261,6 +263,7 @@ function PipetteTipsField(props: PipetteTipsFieldProps): JSX.Element | null {
>
{customTiprackOptions.map(o => (
<EquipmentOption
robotType={fields.robotType}
key={o.name}
isSelected={selectedValues.includes(o.value)}
text={o.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import '@testing-library/jest-dom/vitest'
import { screen, cleanup } from '@testing-library/react'
import { BORDERS, COLORS } from '@opentrons/components'
import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data'
import { i18n } from '../../../../localization'
import { renderWithProviders } from '../../../../__testing-utils__'
import { EquipmentOption } from '../EquipmentOption'
Expand All @@ -21,6 +22,7 @@ describe('EquipmentOption', () => {
onClick: vi.fn(),
isSelected: false,
text: 'mockText',
robotType: FLEX_ROBOT_TYPE,
}
})
afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,10 @@ const values = {
robotType: FLEX_ROBOT_TYPE,
},
pipettesByMount: {
left: { pipetteName: 'mockPipetteName', tiprackDefURI: ['mocktip'] },
left: { pipetteName: 'p1000_single_flex', tiprackDefURI: ['mocktip'] },
right: { pipetteName: null, tiprackDefURI: null },
} as FormPipettesByMount,
modulesByType: {
heaterShakerModuleType: { onDeck: false, model: null, slot: '1' },
magneticBlockType: { onDeck: false, model: null, slot: '2' },
temperatureModuleType: { onDeck: false, model: null, slot: '3' },
thermocyclerModuleType: { onDeck: false, model: null, slot: '4' },
},
modules: {},
additionalEquipment: ['gripper'],
} as FormState

Expand Down Expand Up @@ -109,12 +104,7 @@ describe('ModulesAndOtherTile', () => {
left: { pipetteName: 'p1000_single', tiprackDefURI: ['mocktip'] },
right: { pipetteName: null, tiprackDefURI: null },
} as FormPipettesByMount,
modulesByType: {
heaterShakerModuleType: { onDeck: false, model: null, slot: '1' },
magneticModuleType: { onDeck: false, model: null, slot: '2' },
temperatureModuleType: { onDeck: false, model: null, slot: '3' },
thermocyclerModuleType: { onDeck: false, model: null, slot: '4' },
},
modules: {},
} as FormState

const mockWizardTileProps: Partial<WizardTileProps> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,15 @@ const values = {
},
right: { pipetteName: null, tiprackDefURI: null },
} as FormPipettesByMount,
modulesByType: {
heaterShakerModuleType: { onDeck: false, model: null, slot: '1' },
magneticBlockType: { onDeck: false, model: null, slot: '2' },
temperatureModuleType: { onDeck: false, model: null, slot: '3' },
thermocyclerModuleType: { onDeck: false, model: null, slot: '4' },
},
modules: {},
additionalEquipment: ['gripper'],
} as FormState

const mockWizardTileProps: Partial<WizardTileProps> = {
goBack: vi.fn(),
proceed: vi.fn(),
watch: vi.fn((name: keyof typeof values) => values[name]) as any,
getValues: vi.fn(() => values) as any,
}

const fixtureTipRack10ul = {
Expand Down Expand Up @@ -154,12 +150,7 @@ describe('PipetteTipsTile', () => {
},
right: { pipetteName: null, tiprackDefURI: null },
} as FormPipettesByMount,
modulesByType: {
heaterShakerModuleType: { onDeck: false, model: null, slot: '1' },
magneticBlockType: { onDeck: false, model: null, slot: '2' },
temperatureModuleType: { onDeck: false, model: null, slot: '3' },
thermocyclerModuleType: { onDeck: false, model: null, slot: '4' },
},
modules: {},
additionalEquipment: ['gripper'],
} as FormState

Expand Down
Loading

0 comments on commit 528bbb4

Please sign in to comment.