From ae0679662d5f2ae8e3ea742e3ef31687257a258f Mon Sep 17 00:00:00 2001 From: Katie Adee Date: Fri, 7 Feb 2020 11:58:38 -0500 Subject: [PATCH] feat(protocol-designer): Add module slot placement guidance (#4916) closes #4815 --- protocol-designer/src/components/FilePage.js | 2 +- .../StepEditForm/forms/PauseForm.js | 2 +- .../modals/EditModulesModal/EditModules.css | 10 ++- .../modals/EditModulesModal/index.js | 58 +++++++++------- .../modals/FilePipettesModal/index.js | 4 +- .../src/components/modules/EditModulesCard.js | 16 ++++- .../src/components/modules/ModuleRow.js | 68 ++++++++++++++++++- .../src/components/modules/styles.css | 11 ++- .../src/localization/en/tooltip.json | 4 ++ protocol-designer/src/step-forms/utils.js | 2 +- 10 files changed, 140 insertions(+), 37 deletions(-) diff --git a/protocol-designer/src/components/FilePage.js b/protocol-designer/src/components/FilePage.js index d430e771c47..38bd06e914e 100644 --- a/protocol-designer/src/components/FilePage.js +++ b/protocol-designer/src/components/FilePage.js @@ -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' diff --git a/protocol-designer/src/components/StepEditForm/forms/PauseForm.js b/protocol-designer/src/components/StepEditForm/forms/PauseForm.js index 7f2fb313cb6..9c590a605dc 100644 --- a/protocol-designer/src/components/StepEditForm/forms/PauseForm.js +++ b/protocol-designer/src/components/StepEditForm/forms/PauseForm.js @@ -37,7 +37,7 @@ export const PauseForm = (props: PauseFormProps): React.Element<'div'> => { ) const pauseUntilTempTooltip = ( -
+
{i18n.t( `tooltip.step_fields.pauseForAmountOfTime.disabled.wait_until_temp` )} diff --git a/protocol-designer/src/components/modals/EditModulesModal/EditModules.css b/protocol-designer/src/components/modals/EditModulesModal/EditModules.css index e8ad61d8bdd..0633999e25e 100644 --- a/protocol-designer/src/components/modals/EditModulesModal/EditModules.css +++ b/protocol-designer/src/components/modals/EditModulesModal/EditModules.css @@ -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; } @@ -44,3 +45,8 @@ margin-left: 1rem; } } + +.slot_map_container { + flex: 0 1 13rem; + padding: 0 3rem 0; +} diff --git a/protocol-designer/src/components/modals/EditModulesModal/index.js b/protocol-designer/src/components/modals/EditModulesModal/index.js index 498e57fd814..bf440cb5a12 100644 --- a/protocol-designer/src/components/modals/EditModulesModal/index.js +++ b/protocol-designer/src/components/modals/EditModulesModal/index.js @@ -9,6 +9,7 @@ import { FormGroup, DropdownField, HoverTooltip, + SlotMap, } from '@opentrons/components' import { selectors as stepFormSelectors, @@ -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) @@ -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() @@ -118,7 +120,7 @@ export default function EditModulesModal(props: EditModulesProps) { )}
@@ -132,25 +134,35 @@ export default function EditModulesModal(props: EditModulesProps) { /> {showSlotOption && ( - - {hoverTooltipHandlers => ( -
- - - -
- )} -
+ <> + + {hoverTooltipHandlers => ( +
+ + + +
+ )} +
+
+ {selectedSlot && ( + + )} +
+ )}
diff --git a/protocol-designer/src/components/modals/FilePipettesModal/index.js b/protocol-designer/src/components/modals/FilePipettesModal/index.js index 567219e5be9..549c9fbfcef 100644 --- a/protocol-designer/src/components/modals/FilePipettesModal/index.js +++ b/protocol-designer/src/components/modals/FilePipettesModal/index.js @@ -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, @@ -208,7 +208,7 @@ export class FilePipettesModal extends React.Component { const canSubmit = pipetteSelectionIsValid && tiprackSelectionIsValid const showCrashInfoBox = - getCrashablePipetteSelected(this.state.pipettesByMount) && + getIsCrashablePipetteSelected(this.state.pipettesByMount) && this.getCrashableModuleSelected(this.state.modulesByType) const visibleModules = this.props.thermocyclerEnabled diff --git a/protocol-designer/src/components/modules/EditModulesCard.js b/protocol-designer/src/components/modules/EditModulesCard.js index f4d065578f5..63a6d3f8402 100644 --- a/protocol-designer/src/components/modules/EditModulesCard.js +++ b/protocol-designer/src/components/modules/EditModulesCard.js @@ -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' @@ -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 ( @@ -51,6 +60,7 @@ export function EditModulesCard(props: Props) { diff --git a/protocol-designer/src/components/modules/ModuleRow.js b/protocol-designer/src/components/modules/ModuleRow.js index fbaa6df5aaf..acab8b23b46 100644 --- a/protocol-designer/src/components/modules/ModuleRow.js +++ b/protocol-designer/src/components/modules/ModuleRow.js @@ -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' @@ -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 = [] + let collisionSlots: Array = [] + // 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 && ( +
{collisionTooltipText}
+ ) + const setCurrentModule = (moduleType: ModuleType, moduleId?: string) => () => openEditModuleModal(moduleType, moduleId) @@ -61,6 +104,25 @@ export function ModuleRow(props: Props) {
{slot && }
+
+ {slot && ( + 0 ? collisionTooltip : null + } + > + {hoverTooltipHandlers => ( +
+ +
+ )} +
+ )} +
{module && ( App > Experimental settings." } diff --git a/protocol-designer/src/step-forms/utils.js b/protocol-designer/src/step-forms/utils.js index f2ad5feaacf..b448c2169ab 100644 --- a/protocol-designer/src/step-forms/utils.js +++ b/protocol-designer/src/step-forms/utils.js @@ -131,7 +131,7 @@ export const getSlotIsEmpty = ( ) } -export const getCrashablePipetteSelected = ( +export const getIsCrashablePipetteSelected = ( pipettesByMount: FormPipettesByMount ) => { const { left, right } = pipettesByMount