From 4de129c0ece5d72175467e82998460b00c5b6e9d Mon Sep 17 00:00:00 2001 From: koji Date: Sat, 19 Oct 2024 11:16:16 -0400 Subject: [PATCH 1/4] fix(protocol-designer): fix slots availablity functions for multiple modules fix slots availablity functions for multiple modules close RQA-3324 --- .../CreateNewProtocolWizard/SelectModules.tsx | 63 ++++++-- .../pages/CreateNewProtocolWizard/utils.tsx | 139 +++++++++++++++++- 2 files changed, 181 insertions(+), 21 deletions(-) diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx index 4cf2576c25e..c9288629c4f 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx @@ -38,7 +38,13 @@ import { FLEX_SUPPORTED_MODULE_MODELS, OT2_SUPPORTED_MODULE_MODELS, } from './constants' -import { getNumOptions, getNumSlotsAvailable } from './utils' +import { + getNumOptions, + getNumSlotsAvailable, + getModuleDistribution, + getAvailableSlots, + getCanAddModule, +} from './utils' import { HandleEnter } from './HandleEnter' import type { DropdownBorder } from '@opentrons/components' @@ -64,6 +70,11 @@ export function SelectModules(props: WizardTileProps): JSX.Element | null { ? FLEX_SUPPORTED_MODULE_MODELS : OT2_SUPPORTED_MODULE_MODELS + const distribution = getModuleDistribution(modules) + console.log('distribution', distribution) + const availableSlots = getAvailableSlots(distribution) + console.log('availableSlots', availableSlots) + const numSlotsAvailable = getNumSlotsAvailable(modules, additionalEquipment) const hasNoAvailableSlots = numSlotsAvailable === 0 const numMagneticBlocks = @@ -87,21 +98,40 @@ export function SelectModules(props: WizardTileProps): JSX.Element | null { ? [TEMPERATURE_MODULE_TYPE, HEATERSHAKER_MODULE_TYPE, MAGNETIC_BLOCK_TYPE] : [TEMPERATURE_MODULE_TYPE] + // const handleAddModule = (moduleModel: ModuleModel): void => { + // // Need to fix since this condition isn't quite right + // if (hasNoAvailableSlots) { + // makeSnackbar(t('slots_limit_reached') as string) + // } else { + // setValue('modules', { + // ...modules, + // [uuid()]: { + // model: moduleModel, + // type: getModuleType(moduleModel), + // slot: + // robotType === FLEX_ROBOT_TYPE + // ? DEFAULT_SLOT_MAP_FLEX[moduleModel] + // : DEFAULT_SLOT_MAP_OT2[getModuleType(moduleModel)], + // }, + // }) + // } + // } + const handleAddModule = (moduleModel: ModuleModel): void => { - if (hasNoAvailableSlots) { - makeSnackbar(t('slots_limit_reached') as string) - } else { + const moduleType = getModuleType(moduleModel) + const distribution = getModuleDistribution(modules) + + if (getCanAddModule(moduleType, distribution)) { setValue('modules', { ...modules, [uuid()]: { model: moduleModel, - type: getModuleType(moduleModel), - slot: - robotType === FLEX_ROBOT_TYPE - ? DEFAULT_SLOT_MAP_FLEX[moduleModel] - : DEFAULT_SLOT_MAP_OT2[getModuleType(moduleModel)], + type: moduleType, + slot: null, }, }) + } else { + makeSnackbar(t('cannot_add_module') as string) } } @@ -256,13 +286,14 @@ export function SelectModules(props: WizardTileProps): JSX.Element | null { ) }, dropdownType: 'neutral' as DropdownBorder, - filterOptions: getNumOptions( - module.model === 'magneticBlockV1' - ? numSlotsAvailable + - MAGNETIC_BLOCKS_ADJUSTMENT + - module.count - : numSlotsAvailable + module.count - ), + // filterOptions: getNumOptions( + // module.model === 'magneticBlockV1' + // ? numSlotsAvailable + + // MAGNETIC_BLOCKS_ADJUSTMENT + + // module.count + // : numSlotsAvailable + module.count + // ), + filterOptions: getNumOptions(module.type, distribution), } return ( diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx index e0a84c1340c..af8be0c20a3 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx @@ -1,10 +1,12 @@ import { - MAGNETIC_BLOCK_TYPE, - STAGING_AREA_CUTOUTS, - THERMOCYCLER_MODULE_TYPE, getLabwareDefURI, getLabwareDisplayName, getPipetteSpecsV2, + HEATERSHAKER_MODULE_TYPE, + MAGNETIC_BLOCK_TYPE, + STAGING_AREA_CUTOUTS, + TEMPERATURE_MODULE_TYPE, + THERMOCYCLER_MODULE_TYPE, WASTE_CHUTE_CUTOUT, } from '@opentrons/shared-data' import wasteChuteImage from '../../assets/images/waste_chute.png' @@ -15,15 +17,59 @@ import type { LabwareDefByDefURI, LabwareDefinition2, PipetteName, + ModuleType, } from '@opentrons/shared-data' import type { DropdownOption } from '@opentrons/components' import type { AdditionalEquipment, WizardFormState } from './types' +import type { FormModules } from '../../step-forms' const TOTAL_MODULE_SLOTS = 8 const MIDDLE_SLOT_NUM = 4 -export const getNumOptions = (length: number): DropdownOption[] => { - return Array.from({ length }, (_, i) => ({ +// export const getNumOptions = (length: number): DropdownOption[] => { +// return Array.from({ length }, (_, i) => ({ +// name: `${i + 1}`, +// value: `${i + 1}`, +// })) +// } + +export const getNumOptions = ( + moduleType: ModuleType, + distribution: ModuleDistribution +): DropdownOption[] => { + const { tc, hs, mb, tm } = distribution + let maxCount: number + + const totalOccupiedSlots = tc * 2 + hs + tm + mb + const maxSlots = mb === 0 ? 7 : 11 + const availableSlots = maxSlots - totalOccupiedSlots + + switch (moduleType) { + case THERMOCYCLER_MODULE_TYPE: + maxCount = tc === 0 ? 1 : 0 + break + case MAGNETIC_BLOCK_TYPE: + maxCount = Math.min(11 - (tc * 2 + hs + tm), 11) + break + case HEATERSHAKER_MODULE_TYPE: + case TEMPERATURE_MODULE_TYPE: + if (tc === 0) { + maxCount = Math.min( + 7, + availableSlots + (moduleType === HEATERSHAKER_MODULE_TYPE ? hs : tm) + ) + } else { + maxCount = Math.min( + 5, + availableSlots + (moduleType === HEATERSHAKER_MODULE_TYPE ? hs : tm) + ) + } + break + default: + maxCount = 0 + } + + return Array.from({ length: maxCount }, (_, i) => ({ name: `${i + 1}`, value: `${i + 1}`, })) @@ -237,3 +283,86 @@ export const getTrashSlot = (values: WizardFormState): string => { } return unoccupiedSlot?.value } + +interface ModuleDistribution { + tc: number + hs: number + mb: number + tm: number +} + +const TOTAL_SLOTS_WITHOUT_TWO_COL = 7 +const TWO_COL_SLOTS = 4 + +export const getModuleDistribution = ( + modules: FormModules | null +): ModuleDistribution => { + let tc = 0 + let hs = 0 + let mb = 0 + let tm = 0 + + if (modules === null) return { tc, hs, mb, tm } + + Object.values(modules).forEach(module => { + switch (module.type) { + case THERMOCYCLER_MODULE_TYPE: + // TC occupies A1+B1 + tc++ + break + case HEATERSHAKER_MODULE_TYPE: + hs++ + break + case MAGNETIC_BLOCK_TYPE: + mb++ + break + case TEMPERATURE_MODULE_TYPE: + tm++ + break + } + }) + + return { tc, hs, mb, tm } +} + +export const getAvailableSlots = ( + distribution: ModuleDistribution +): { + regular: number + magnetic: number +} => { + if (distribution === null) + return { regular: TOTAL_SLOTS_WITHOUT_TWO_COL, magnetic: TWO_COL_SLOTS } + const { tc, hs, mb, tm } = distribution + + // マグネティックブロック用のスロット + const availableMagneticSlots = 11 - (tc * 2 + hs + tm + mb) + + // 通常のモジュール用のスロット + const availableRegularSlots = 7 - (hs + tm + (tc > 0 ? 2 : 0)) + + return { + regular: Math.max(0, availableRegularSlots), + magnetic: Math.max(0, availableMagneticSlots), + } +} + +export const getCanAddModule = ( + moduleType: ModuleType, + distribution: ModuleDistribution +): boolean => { + const availableSlots = getAvailableSlots(distribution) + const { tc, hs, mb, tm } = distribution + + switch (moduleType) { + case THERMOCYCLER_MODULE_TYPE: + return tc === 0 && availableSlots.regular >= 2 + case MAGNETIC_BLOCK_TYPE: + return availableSlots.magnetic > 0 + case HEATERSHAKER_MODULE_TYPE: + case TEMPERATURE_MODULE_TYPE: + return availableSlots.regular > 0 || (hs + tm < 5 && tc === 0) + default: + return false + } +} From 57cf81cdb5b6112b4dc23922a60b9ecbe4951de5 Mon Sep 17 00:00:00 2001 From: koji Date: Sat, 19 Oct 2024 11:34:16 -0400 Subject: [PATCH 2/4] fix lint errors --- .../src/pages/CreateNewProtocolWizard/SelectModules.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx index c9288629c4f..577687ae765 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx @@ -53,7 +53,7 @@ import type { FormModule, FormModules } from '../../step-forms' import type { WizardTileProps } from './types' const MAX_MAGNETIC_BLOCKS = 4 -const MAGNETIC_BLOCKS_ADJUSTMENT = 3 +// const MAGNETIC_BLOCKS_ADJUSTMENT = 3 export function SelectModules(props: WizardTileProps): JSX.Element | null { const { goBack, proceed, watch, setValue } = props @@ -127,7 +127,10 @@ export function SelectModules(props: WizardTileProps): JSX.Element | null { [uuid()]: { model: moduleModel, type: moduleType, - slot: null, + slot: + robotType === FLEX_ROBOT_TYPE + ? DEFAULT_SLOT_MAP_FLEX[moduleModel] + : DEFAULT_SLOT_MAP_OT2[getModuleType(moduleModel)], }, }) } else { From a0db9029a6543e002cd3400fae024650ae39e388 Mon Sep 17 00:00:00 2001 From: koji Date: Mon, 21 Oct 2024 11:50:33 -0400 Subject: [PATCH 3/4] fix check-js and lint-js errors --- .../src/components/EditModules.tsx | 14 ++++---- .../CreateNewProtocolWizard/SelectModules.tsx | 34 +++---------------- .../pages/CreateNewProtocolWizard/utils.tsx | 23 ++++++------- 3 files changed, 22 insertions(+), 49 deletions(-) diff --git a/protocol-designer/src/components/EditModules.tsx b/protocol-designer/src/components/EditModules.tsx index 6ed7c8a6054..49707bb6030 100644 --- a/protocol-designer/src/components/EditModules.tsx +++ b/protocol-designer/src/components/EditModules.tsx @@ -17,12 +17,12 @@ import { EditMultipleModulesModal } from './modals/EditModulesModal/EditMultiple import { useBlockingHint } from './Hints/useBlockingHint' import { MagneticModuleWarningModalContent } from './modals/EditModulesModal/MagneticModuleWarningModalContent' import { EditModulesModal } from './modals/EditModulesModal' -import type { ModuleModel, ModuleType } from '@opentrons/shared-data' +import type { ModuleModel, } from '@opentrons/shared-data' export interface EditModulesProps { moduleToEdit: { moduleId?: string | null - moduleType: ModuleType + : } onCloseClick: () => void } @@ -35,16 +35,16 @@ export interface ModelModuleInfo { export const EditModules = (props: EditModulesProps): JSX.Element => { const { onCloseClick, moduleToEdit } = props const enableMoam = useSelector(getEnableMoam) - const { moduleId, moduleType } = moduleToEdit + const { moduleId, } = moduleToEdit const _initialDeckSetup = useSelector(stepFormSelectors.getInitialDeckSetup) const robotType = useSelector(getRobotType) - const MOAM_MODULE_TYPES: ModuleType[] = enableMoam + const MOAM_MODULE_TYPES: [] = enableMoam ? [TEMPERATURE_MODULE_TYPE, HEATERSHAKER_MODULE_TYPE, MAGNETIC_BLOCK_TYPE] : [TEMPERATURE_MODULE_TYPE] const showMultipleModuleModal = - robotType === FLEX_ROBOT_TYPE && MOAM_MODULE_TYPES.includes(moduleType) + robotType === FLEX_ROBOT_TYPE && MOAM_MODULE_TYPES.includes() const moduleOnDeck = moduleId ? _initialDeckSetup.modules[moduleId] : null const [ @@ -94,7 +94,7 @@ export const EditModules = (props: EditModulesProps): JSX.Element => { let modal = ( { ) } diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx index 577687ae765..5a9b7610acc 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectModules.tsx @@ -39,7 +39,7 @@ import { OT2_SUPPORTED_MODULE_MODELS, } from './constants' import { - getNumOptions, + getNumOptionsForModules, getNumSlotsAvailable, getModuleDistribution, getAvailableSlots, @@ -53,7 +53,6 @@ import type { FormModule, FormModules } from '../../step-forms' import type { WizardTileProps } from './types' const MAX_MAGNETIC_BLOCKS = 4 -// const MAGNETIC_BLOCKS_ADJUSTMENT = 3 export function SelectModules(props: WizardTileProps): JSX.Element | null { const { goBack, proceed, watch, setValue } = props @@ -98,25 +97,6 @@ export function SelectModules(props: WizardTileProps): JSX.Element | null { ? [TEMPERATURE_MODULE_TYPE, HEATERSHAKER_MODULE_TYPE, MAGNETIC_BLOCK_TYPE] : [TEMPERATURE_MODULE_TYPE] - // const handleAddModule = (moduleModel: ModuleModel): void => { - // // Need to fix since this condition isn't quite right - // if (hasNoAvailableSlots) { - // makeSnackbar(t('slots_limit_reached') as string) - // } else { - // setValue('modules', { - // ...modules, - // [uuid()]: { - // model: moduleModel, - // type: getModuleType(moduleModel), - // slot: - // robotType === FLEX_ROBOT_TYPE - // ? DEFAULT_SLOT_MAP_FLEX[moduleModel] - // : DEFAULT_SLOT_MAP_OT2[getModuleType(moduleModel)], - // }, - // }) - // } - // } - const handleAddModule = (moduleModel: ModuleModel): void => { const moduleType = getModuleType(moduleModel) const distribution = getModuleDistribution(modules) @@ -289,14 +269,10 @@ export function SelectModules(props: WizardTileProps): JSX.Element | null { ) }, dropdownType: 'neutral' as DropdownBorder, - // filterOptions: getNumOptions( - // module.model === 'magneticBlockV1' - // ? numSlotsAvailable + - // MAGNETIC_BLOCKS_ADJUSTMENT + - // module.count - // : numSlotsAvailable + module.count - // ), - filterOptions: getNumOptions(module.type, distribution), + filterOptions: getNumOptionsForModules( + module.type, + distribution + ), } return ( diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx index af8be0c20a3..750272c6ae0 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx @@ -26,14 +26,14 @@ import type { FormModules } from '../../step-forms' const TOTAL_MODULE_SLOTS = 8 const MIDDLE_SLOT_NUM = 4 -// export const getNumOptions = (length: number): DropdownOption[] => { -// return Array.from({ length }, (_, i) => ({ -// name: `${i + 1}`, -// value: `${i + 1}`, -// })) -// } - -export const getNumOptions = ( +export const getNumOptions = (length: number): DropdownOption[] => { + return Array.from({ length }, (_, i) => ({ + name: `${i + 1}`, + value: `${i + 1}`, + })) +} + +export const getNumOptionsForModules = ( moduleType: ModuleType, distribution: ModuleDistribution ): DropdownOption[] => { @@ -95,7 +95,7 @@ export const getNumSlotsAvailable = ( filteredModuleLength = filteredModuleLength + 1 } if (magneticBlocks.length > 0) { - // once blocks exceed 4, then we dont' want to subtract the amount available + // once blocks exceed 4, then we don't want to subtract the amount available // because block can go into the center slots where all other modules/trashes can not const numBlocks = magneticBlocks.length > 4 ? MIDDLE_SLOT_NUM : magneticBlocks.length @@ -334,11 +334,7 @@ export const getAvailableSlots = ( if (distribution === null) return { regular: TOTAL_SLOTS_WITHOUT_TWO_COL, magnetic: TWO_COL_SLOTS } const { tc, hs, mb, tm } = distribution - - // マグネティックブロック用のスロット const availableMagneticSlots = 11 - (tc * 2 + hs + tm + mb) - - // 通常のモジュール用のスロット const availableRegularSlots = 7 - (hs + tm + (tc > 0 ? 2 : 0)) return { @@ -352,6 +348,7 @@ export const getCanAddModule = ( distribution: ModuleDistribution ): boolean => { const availableSlots = getAvailableSlots(distribution) + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { tc, hs, mb, tm } = distribution switch (moduleType) { From f8c3f75be32eeb8fb6b321e4af0be5c38d8e757d Mon Sep 17 00:00:00 2001 From: koji Date: Mon, 21 Oct 2024 11:53:37 -0400 Subject: [PATCH 4/4] Update EditModules.tsx --- protocol-designer/src/components/EditModules.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/protocol-designer/src/components/EditModules.tsx b/protocol-designer/src/components/EditModules.tsx index 49707bb6030..6ed7c8a6054 100644 --- a/protocol-designer/src/components/EditModules.tsx +++ b/protocol-designer/src/components/EditModules.tsx @@ -17,12 +17,12 @@ import { EditMultipleModulesModal } from './modals/EditModulesModal/EditMultiple import { useBlockingHint } from './Hints/useBlockingHint' import { MagneticModuleWarningModalContent } from './modals/EditModulesModal/MagneticModuleWarningModalContent' import { EditModulesModal } from './modals/EditModulesModal' -import type { ModuleModel, } from '@opentrons/shared-data' +import type { ModuleModel, ModuleType } from '@opentrons/shared-data' export interface EditModulesProps { moduleToEdit: { moduleId?: string | null - : + moduleType: ModuleType } onCloseClick: () => void } @@ -35,16 +35,16 @@ export interface ModelModuleInfo { export const EditModules = (props: EditModulesProps): JSX.Element => { const { onCloseClick, moduleToEdit } = props const enableMoam = useSelector(getEnableMoam) - const { moduleId, } = moduleToEdit + const { moduleId, moduleType } = moduleToEdit const _initialDeckSetup = useSelector(stepFormSelectors.getInitialDeckSetup) const robotType = useSelector(getRobotType) - const MOAM_MODULE_TYPES: [] = enableMoam + const MOAM_MODULE_TYPES: ModuleType[] = enableMoam ? [TEMPERATURE_MODULE_TYPE, HEATERSHAKER_MODULE_TYPE, MAGNETIC_BLOCK_TYPE] : [TEMPERATURE_MODULE_TYPE] const showMultipleModuleModal = - robotType === FLEX_ROBOT_TYPE && MOAM_MODULE_TYPES.includes() + robotType === FLEX_ROBOT_TYPE && MOAM_MODULE_TYPES.includes(moduleType) const moduleOnDeck = moduleId ? _initialDeckSetup.modules[moduleId] : null const [ @@ -94,7 +94,7 @@ export const EditModules = (props: EditModulesProps): JSX.Element => { let modal = ( { ) }