From 34b1f1b334333e038021ece6ddaeeabec1d87211 Mon Sep 17 00:00:00 2001 From: Jethary Date: Thu, 12 Dec 2024 15:12:51 -0500 Subject: [PATCH] add some module highlight --- .../src/organisms/DeckLabelSet/index.tsx | 11 +- .../molecules/DropdownStepFormField/index.tsx | 34 +++- .../Designer/DeckSetup/DeckSetupDetails.tsx | 9 + .../pages/Designer/DeckSetup/ModuleLabel.tsx | 8 +- .../src/pages/Designer/HighlightModule.tsx | 47 ++++++ .../StepForm/PipetteFields/LabwareField.tsx | 10 +- .../StepForm/StepFormToolbox.tsx | 15 +- .../StepTools/HeaterShakerTools/index.tsx | 12 +- .../src/ui/steps/actions/actions.ts | 158 +++++++++++++++++- .../src/ui/steps/actions/types.ts | 6 +- protocol-designer/src/ui/steps/reducers.ts | 14 +- 11 files changed, 296 insertions(+), 28 deletions(-) create mode 100644 protocol-designer/src/pages/Designer/HighlightModule.tsx diff --git a/components/src/organisms/DeckLabelSet/index.tsx b/components/src/organisms/DeckLabelSet/index.tsx index 95ff6d2f2f3..ac9972861a6 100644 --- a/components/src/organisms/DeckLabelSet/index.tsx +++ b/components/src/organisms/DeckLabelSet/index.tsx @@ -26,7 +26,7 @@ const DeckLabelSetComponent = ( return ( - + {deckLabels.length > 0 ? deckLabels.map((deckLabel, index) => ( ( DeckLabelSetComponent ) -const StyledBox = styled(Box)` +interface StyledBoxProps { + isZoomed: boolean +} + +const StyledBox = styled(Box)` border-radius: ${BORDERS.borderRadius4}; - border: 1.5px solid ${COLORS.blue50}; + border: ${({ isZoomed }) => + isZoomed ? `1.5px solid ${COLORS.blue50}` : `3px solid ${COLORS.blue50}`}; ` const LabelContainer = styled.div` diff --git a/protocol-designer/src/molecules/DropdownStepFormField/index.tsx b/protocol-designer/src/molecules/DropdownStepFormField/index.tsx index 72b78ff9bb9..09fd0c2d71c 100644 --- a/protocol-designer/src/molecules/DropdownStepFormField/index.tsx +++ b/protocol-designer/src/molecules/DropdownStepFormField/index.tsx @@ -1,5 +1,6 @@ import { useTranslation } from 'react-i18next' import { useEffect } from 'react' +import { useDispatch } from 'react-redux' import { COLORS, DIRECTION_COLUMN, @@ -9,11 +10,9 @@ import { SPACING, StyledText, } from '@opentrons/components' +import { selectSelection } from '../../ui/steps/actions/actions' import type { Options } from '@opentrons/components' import type { FieldProps } from '../../pages/Designer/ProtocolSteps/StepForm/types' -import { useDispatch } from 'react-redux' -import { selectSelection } from '../../ui/steps/actions/actions' -import { Selection } from '../../ui/steps/actions/types' export interface DropdownStepFormFieldProps extends FieldProps { options: Options @@ -39,17 +38,17 @@ export function DropdownStepFormField( onEnter, onExit, onFieldBlur, + name: fieldName, } = props const { t } = useTranslation('tooltip') const dispatch = useDispatch() const availableOptionId = options.find(opt => opt.value === value) - useEffect(() => { if (options.length === 1) { updateValue(options[0].value) } }, []) - + console.log(fieldName) return ( {options.length > 1 || options.length === 0 ? ( @@ -66,9 +65,30 @@ export function DropdownStepFormField( availableOptionId ?? { name: 'Choose option', value: '' } } onClick={value => { - const selection = { id: value, text: 'Selected' } updateValue(value) - dispatch(selectSelection({ selection, mode: 'replace' })) + const selection = { id: value, text: 'Selected' } + if ( + fieldName === 'aspirate_labware' || + fieldName === 'labware' || + fieldName === 'moduleId' + ) { + dispatch( + selectSelection({ + selection: { ...selection, field: '1' }, + mode: 'add', + }) + ) + } else if ( + fieldName === 'dispense_labware' || + fieldName === 'newLocation' + ) { + dispatch( + selectSelection({ + selection: { ...selection, field: '2' }, + mode: 'add', + }) + ) + } }} onEnter={onEnter} onExit={onExit} diff --git a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx index 53a61df3faf..8397f125428 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx @@ -52,6 +52,7 @@ import type { } from '../../../step-forms' import type { DeckSetupTabType } from '../types' import type { Fixture } from './constants' +import { HighlightModule } from '../HighlightModule' interface DeckSetupDetailsProps extends DeckSetupTabType { activeDeckSetup: InitialDeckSetup @@ -271,6 +272,14 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element { /> ) : null} + ) : null })} diff --git a/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx b/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx index d5729991e30..543e7eb6dbb 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx @@ -15,7 +15,9 @@ interface ModuleLabelProps { orientation: 'left' | 'right' isSelected: boolean isLast: boolean + isZoomed?: boolean labwareInfos?: DeckLabelProps[] + labelName?: string } export const ModuleLabel = (props: ModuleLabelProps): JSX.Element => { const { @@ -25,6 +27,8 @@ export const ModuleLabel = (props: ModuleLabelProps): JSX.Element => { isSelected, isLast, labwareInfos = [], + isZoomed = true, + labelName, } = props const labelContainerRef = useRef(null) const [labelContainerHeight, setLabelContainerHeight] = useState(12) @@ -52,14 +56,14 @@ export const ModuleLabel = (props: ModuleLabelProps): JSX.Element => { return ( { + const { moduleModel, position, orientation, moduleId } = props + const hoveredModulSelection = useSelector(getHoveredSelection) + const selectedModuleSelection = useSelector(getSelectedSelection) + const isSelectedModuleSelected = + selectedModuleSelection.find(selected => selected.id === moduleId) != null + const highlighted = hoveredModulSelection.id === moduleId + + let labelText + if (hoveredModulSelection != null && !isSelectedModuleSelected) { + labelText = hoveredModulSelection.text ?? undefined + } else if (isSelectedModuleSelected) { + labelText = selectedModuleSelection[0].text ?? undefined + } + + if (isSelectedModuleSelected || highlighted) { + return ( + + ) + } + return null +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx index 5fb840e980b..85db0102eb0 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx @@ -1,9 +1,10 @@ -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { getDisposalOptions, getLabwareOptions, } from '../../../../../ui/labware/selectors' +import { hoverSelection } from '../../../../../ui/steps/actions/actions' import { DropdownStepFormField } from '../../../../../molecules' import type { FieldProps } from '../types' @@ -12,6 +13,7 @@ export function LabwareField(props: FieldProps): JSX.Element { const { i18n, t } = useTranslation('protocol_steps') const disposalOptions = useSelector(getDisposalOptions) const options = useSelector(getLabwareOptions) + const dispatch = useDispatch() const allOptions = name === 'dispense_labware' ? [...options, ...disposalOptions] @@ -23,6 +25,12 @@ export function LabwareField(props: FieldProps): JSX.Element { name={name} options={allOptions} title={i18n.format(t(`${name}`), 'capitalize')} + onEnter={(id: string) => { + dispatch(hoverSelection({ id, text: 'Select' })) + }} + onExit={() => { + dispatch(hoverSelection({ id: null, text: null })) + }} /> ) } diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx index beb35a7b992..dbbcaf5e648 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx @@ -239,8 +239,8 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element { }) ) dispatch(analyticsEvent(stepDuration)) - dispatch(selectSelection({ id: null, text: null, fieldType: undefined })) - dispatch(hoverSelection({ id: null, text: null, fieldType: undefined })) + dispatch(selectSelection({ selection: null, mode: 'clear' })) + dispatch(hoverSelection({ id: null, text: null })) } else { setShowFormErrors(true) if (tab === 'aspirate' && isDispenseError && !isAspirateError) { @@ -302,7 +302,16 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element { } childrenPadding="0" - onCloseClick={handleClose} + onCloseClick={() => { + handleClose() + dispatch( + selectSelection({ + selection: null, + mode: 'clear', + }) + ) + dispatch(hoverSelection({ id: null, text: null })) + }} closeButton={} confirmButton={ diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/HeaterShakerTools/index.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/HeaterShakerTools/index.tsx index 1577db5da8c..0586a44e16d 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/HeaterShakerTools/index.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/HeaterShakerTools/index.tsx @@ -1,4 +1,4 @@ -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { Box, @@ -16,12 +16,13 @@ import { } from '../../../../../../molecules' import { getFormErrorsMappedToField, getFormLevelError } from '../../utils' import type { StepFormProps } from '../../types' +import { hoverSelection } from '../../../../../../ui/steps/actions/actions' export function HeaterShakerTools(props: StepFormProps): JSX.Element { const { propsForFields, formData, visibleFormErrors } = props const { t } = useTranslation(['application', 'form', 'protocol_steps']) const moduleLabwareOptions = useSelector(getHeaterShakerLabwareOptions) - + const dispatch = useDispatch() const mappedErrorsToField = getFormErrorsMappedToField(visibleFormErrors) return ( @@ -34,6 +35,13 @@ export function HeaterShakerTools(props: StepFormProps): JSX.Element { {...propsForFields.moduleId} options={moduleLabwareOptions} title={t('protocol_steps:module')} + onEnter={(id: string) => { + console.log(id) + dispatch(hoverSelection({ id, text: 'Select' })) + }} + onExit={() => { + dispatch(hoverSelection({ id: null, text: null })) + }} /> => ( + dispatch: ThunkDispatch, + getState: GetState +) => { + const selectStepAction: SelectStepAction = { + type: 'SELECT_STEP', + payload: stepId, + } + dispatch(selectStepAction) + dispatch({ + type: 'POPULATE_FORM', + payload: null, + }) + resetScrollElements() +} export const populateForm = (stepId: StepIdType): ThunkAction => ( dispatch: ThunkDispatch, @@ -129,10 +148,145 @@ export const populateForm = (stepId: StepIdType): ThunkAction => ( mode: 'add', }, }) + } else if (formData.stepType === 'moveLiquid') { + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.aspirate_labware, + text: 'Selected', + field: '1', + }, + mode: 'add', + }, + }) + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.dispense_labware, + text: 'Selected', + field: '2', + }, + mode: 'add', + }, + }) + } else if (formData.stepType === 'mix') { + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.labware, + text: 'Selected', + field: '1', + }, + mode: 'add', + }, + }) + } else if ( + formData.stepType === 'heaterShaker' || + formData.stepType === 'temperature' || + formData.stepType === 'thermocycler' || + formData.stepType === 'magnet' + ) { + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.moduleId, + text: 'Selected', + field: '1', + }, + mode: 'add', + }, + }) } resetScrollElements() } - +export const selectStep = (stepId: StepIdType): ThunkAction => ( + dispatch: ThunkDispatch, + getState: GetState +) => { + const selectStepAction: SelectStepAction = { + type: 'SELECT_STEP', + payload: stepId, + } + dispatch(selectStepAction) + const state = getState() + const formData = { ...stepFormSelectors.getSavedStepForms(state)[stepId] } + dispatch({ + type: 'POPULATE_FORM', + payload: formData, + }) + if (formData.stepType === 'moveLabware') { + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { id: formData.labware, text: 'Selected' }, + mode: 'add', + }, + }) + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { id: formData.newLocation, text: 'New location' }, + mode: 'add', + }, + }) + } else if (formData.stepType === 'moveLiquid') { + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.aspirate_labware, + text: 'Selected', + field: '1', + }, + mode: 'add', + }, + }) + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.dispense_labware, + text: 'Selected', + field: '2', + }, + mode: 'add', + }, + }) + } else if (formData.stepType === 'mix') { + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.labware, + text: 'Selected', + field: '1', + }, + mode: 'add', + }, + }) + } else if ( + formData.stepType === 'heaterShaker' || + formData.stepType === 'temperature' || + formData.stepType === 'thermocycler' || + formData.stepType === 'magnet' + ) { + dispatch({ + type: 'SELECT_SELECTION', + payload: { + selection: { + id: formData.moduleId, + text: 'Selected', + field: '1', + }, + mode: 'add', + }, + }) + } +} // NOTE(sa, 2020-12-11): this is a thunk so that we can populate the batch edit form with things later export const selectMultipleSteps = ( stepIds: StepIdType[], diff --git a/protocol-designer/src/ui/steps/actions/types.ts b/protocol-designer/src/ui/steps/actions/types.ts index f1f6f14b1cd..d531c8d9b61 100644 --- a/protocol-designer/src/ui/steps/actions/types.ts +++ b/protocol-designer/src/ui/steps/actions/types.ts @@ -35,10 +35,14 @@ export interface DuplicateMultipleStepsAction { export interface Selection { id: string | null text: string | null + field?: '1' | '2' } export interface selectSelectionAction { type: 'SELECT_SELECTION' - payload: { selection: Selection | null; mode: 'add' | 'clear' | 'replace' } + payload: { + selection: Selection | null + mode: 'add' | 'clear' | 'replace' + } } export interface hoverSelectionAction { type: 'HOVER_SELECTION' diff --git a/protocol-designer/src/ui/steps/reducers.ts b/protocol-designer/src/ui/steps/reducers.ts index 44d8017d5bb..5b69c8ccf57 100644 --- a/protocol-designer/src/ui/steps/reducers.ts +++ b/protocol-designer/src/ui/steps/reducers.ts @@ -207,7 +207,7 @@ const selectedSelection: Reducer = handleActions( action: { payload: { selection: Selection | null - mode: 'add' | 'clear' | 'replace' + mode: 'add' | 'clear' } } ) => { @@ -216,15 +216,15 @@ const selectedSelection: Reducer = handleActions( switch (mode) { case 'clear': return [] - case 'replace': - return selection != null ? [selection] : state case 'add': { - const exists = state.some(sel => sel.id === selection?.id) - if (selection != null) { - return exists ? state : [...state, selection] - } else { + if (!selection) { return state } + const updatedState = state.filter( + sel => sel.field !== selection.field + ) + + return [...updatedState, selection] } default: return state