From d78d1c3a4042686c458420c3b68482b63efb9385 Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Mon, 27 Jan 2025 10:07:38 -0500 Subject: [PATCH 1/6] refactor(app): refactor LPC redux store to key off labwareURI See LPCLabwareInfo type. All changes are a refactor to support that new interface...mostly. LPC will not work on this commit, but it's about as granular a commit as possible before implementing the redesigned flow. --- .../LPCFlows/LPCFlows.tsx | 12 +- .../LPCFlows/hooks/index.ts | 1 + .../LPCFlows/hooks/useLPCLabwareInfo/index.ts | 100 ++++++ .../LPCFlows/hooks/useLPCLabwareInfo/utils.ts | 78 ++++ .../LPCFlows/useLPCFlows.ts | 31 +- .../LPCWizardContainer.tsx | 6 +- .../LabwarePositionCheck/LPCWizardFlex.tsx | 42 +-- .../LabwarePositionCheck/constants.ts | 7 - .../useLPCCommands/useHandlePrepModules.ts | 6 +- ...useHandleValidMoveToMaintenancePosition.ts | 6 +- .../hooks/useLPCInitialState/index.ts | 24 +- .../utils/getActivePipetteId.ts | 21 ++ .../getLPCSteps/getProbeBasedLPCSteps.ts | 95 ----- .../utils/getLPCSteps/index.ts | 30 -- .../hooks/useLPCInitialState/utils/index.ts | 2 +- .../LabwarePositionCheck/redux/index.ts | 1 - .../LabwarePositionCheck/redux/types.ts | 11 - .../steps/BeforeBeginning/index.tsx | 22 +- .../steps/CheckItem/index.tsx | 27 +- .../steps/ResultsSummary/index.tsx | 9 +- .../LabwarePositionCheck/types/index.ts | 1 - .../LabwarePositionCheck/types/steps.ts | 48 --- app/src/redux/protocol-runs/actions/lpc.ts | 33 ++ .../constants/{lpc.ts => lpc/actions.ts} | 3 + .../protocol-runs/constants/lpc/index.ts | 2 + .../protocol-runs/constants/lpc/steps.ts | 25 ++ app/src/redux/protocol-runs/reducer/lpc.ts | 64 +++- .../protocol-runs/reducer/transforms/lpc.ts | 86 +++-- .../protocol-runs/selectors/lpc/index.ts | 1 + .../protocol-runs/selectors/lpc/labware.ts | 332 ++++++++---------- .../protocol-runs/selectors/lpc/pipettes.ts | 57 ++- .../protocol-runs/selectors/lpc/steps.ts | 15 + .../protocol-runs/selectors/lpc/transforms.ts | 58 ++- app/src/redux/protocol-runs/types/lpc.ts | 95 ++++- 34 files changed, 780 insertions(+), 571 deletions(-) create mode 100644 app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/index.ts create mode 100644 app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts create mode 100644 app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/utils.ts delete mode 100644 app/src/organisms/LabwarePositionCheck/constants.ts create mode 100644 app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts delete mode 100644 app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/getProbeBasedLPCSteps.ts delete mode 100644 app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/index.ts delete mode 100644 app/src/organisms/LabwarePositionCheck/redux/index.ts delete mode 100644 app/src/organisms/LabwarePositionCheck/redux/types.ts delete mode 100644 app/src/organisms/LabwarePositionCheck/types/steps.ts rename app/src/redux/protocol-runs/constants/{lpc.ts => lpc/actions.ts} (58%) create mode 100644 app/src/redux/protocol-runs/constants/lpc/index.ts create mode 100644 app/src/redux/protocol-runs/constants/lpc/steps.ts create mode 100644 app/src/redux/protocol-runs/selectors/lpc/steps.ts diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx b/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx index 5c7ab9bdf3c..5dd041ede77 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx @@ -4,20 +4,28 @@ import type { RobotType, CompletedProtocolAnalysis, DeckConfiguration, + LabwareDefinition2, } from '@opentrons/shared-data' import type { LabwareOffset } from '@opentrons/api-client' +import type { LPCLabwareInfo } from '/app/redux/protocol-runs' + +// Inject the props specific to the legacy LPC flows, too. +export interface LegacySupportLPCFlowsProps extends LPCFlowsProps { + existingOffsets: LabwareOffset[] +} export interface LPCFlowsProps { onCloseClick: () => void runId: string robotType: RobotType deckConfig: DeckConfiguration - existingOffsets: LabwareOffset[] + labwareDefs: LabwareDefinition2[] + labwareInfo: LPCLabwareInfo mostRecentAnalysis: CompletedProtocolAnalysis protocolName: string maintenanceRunId: string } -export function LPCFlows(props: LPCFlowsProps): JSX.Element { +export function LPCFlows(props: LegacySupportLPCFlowsProps): JSX.Element { return } diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/index.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/index.ts new file mode 100644 index 00000000000..93372f3db52 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/index.ts @@ -0,0 +1 @@ +export { useLPCLabwareInfo } from './useLPCLabwareInfo' diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts new file mode 100644 index 00000000000..a6ca76618b0 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts @@ -0,0 +1,100 @@ +import { useMemo } from 'react' +import isEqual from 'lodash/isEqual' + +import { getUniqueLabwareLocationComboInfo } from './utils' + +import type { LabwareOffset } from '@opentrons/api-client' +import type { LPCLabwareInfo } from '/app/redux/protocol-runs' +import type { GetUniqueLocationComboInfoParams } from './utils' +import type { LabwareLocationCombo } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' + +type UseLPCLabwareInfoProps = GetUniqueLocationComboInfoParams & { + currentOffsets: LabwareOffset[] +} +// TODO(jh, 01-22-25): This interface will change substantially the switch to /labwareOffsets. + +// Structures LPC-able labware info for injection into LPC flows. +export function useLPCLabwareInfo({ + currentOffsets, + labwareDefs, + protocolData, +}: UseLPCLabwareInfoProps): LPCLabwareInfo { + const lwURIs = getLabwareURIsFromAnalysis(protocolData) + const lwLocationCombos = useMemo( + () => + getUniqueLabwareLocationComboInfo({ + labwareDefs, + protocolData, + }), + [labwareDefs != null, protocolData != null] + ) + + return useMemo( + () => getLPCLabwareInfoFrom(lwURIs, currentOffsets, lwLocationCombos), + [lwURIs.length, currentOffsets.length, lwLocationCombos.length] + ) +} + +export function getLabwareURIsFromAnalysis( + analysis: UseLPCLabwareInfoProps['protocolData'] +): string[] { + return analysis?.labware.map(lwInfo => lwInfo.definitionUri) ?? [] +} + +// NOTE: This is largely a temporary adapter that resolves the app's current way of getting offset data (scraping the run record) +// and the end goal of treating labware as first class citizens. Most of this code will be replaced +// once the app implements the new /labwareOffsets HTTP API. +export function getLPCLabwareInfoFrom( + lwURIs: string[], + currentOffsets: LabwareOffset[], + lwLocationCombos: LabwareLocationCombo[] +): LPCLabwareInfo { + return Object.fromEntries( + currentOffsets + .filter(offset => lwURIs.includes(offset.definitionUri)) + .map(offsetInfo => { + const { + id: offsetId, + definitionUri, + location, + ...restInfo + } = offsetInfo + + const { moduleId, labwareId, adapterId } = getMatchingLocationCombo( + lwLocationCombos, + offsetInfo + ) ?? { + labwareId: '', + } + + return [ + offsetId, + { + existingOffset: { ...restInfo }, + workingOffset: null, + locationDetails: { + ...location, + labwareId, + moduleId, + adapterId, + definitionUri, + }, + }, + ] + }) + ) +} + +// Get the location combo that matches the info provided from a LabwareOffset. +export function getMatchingLocationCombo( + combos: LabwareLocationCombo[], + offsetInfo: LabwareOffset +): LabwareLocationCombo | null { + return ( + combos.find( + combo => + combo.definitionUri === offsetInfo.definitionUri && + isEqual(combo.location, offsetInfo.location) + ) ?? null + ) +} diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/utils.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/utils.ts new file mode 100644 index 00000000000..0558b5f6601 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/utils.ts @@ -0,0 +1,78 @@ +import { isEqual } from 'lodash' + +import { getLabwareDefURI, getPipetteNameSpecs } from '@opentrons/shared-data' + +import { STEP } from '/app/organisms/LabwarePositionCheck/constants' +import { getLabwareLocationCombos } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' + +import type { + CompletedProtocolAnalysis, + LabwareDefinition2, + LoadedPipette, +} from '@opentrons/shared-data' +import type { CheckPositionsStep } from '/app/organisms/LabwarePositionCheck/types' +import type { LabwareLocationCombo } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' + +// export function getProbeBasedLPCSteps( +// params: GetLPCStepsParams +// ): LabwarePositionCheckStep[] { +// const { protocolData } = params +// +// return [ +// { section: NAV_STEPS.BEFORE_BEGINNING }, +// { +// section: NAV_STEPS.ATTACH_PROBE, +// pipetteId: getPrimaryPipetteId(protocolData.pipettes), +// }, +// ...getUniqueLabwareLocationComboInfo(params), +// { +// section: NAV_STEPS.DETACH_PROBE, +// pipetteId: getPrimaryPipetteId(protocolData.pipettes), +// }, +// { section: NAV_STEPS.RESULTS_SUMMARY }, +// ] +// } +export interface GetUniqueLocationComboInfoParams { + protocolData: CompletedProtocolAnalysis | null + labwareDefs: LabwareDefinition2[] | null +} + +export function getUniqueLabwareLocationComboInfo({ + labwareDefs, + protocolData, +}: GetUniqueLocationComboInfoParams): LabwareLocationCombo[] { + if (protocolData == null || labwareDefs == null) { + return [] + } + + const { commands, labware, modules = [] } = protocolData + const labwareLocationCombos = getLabwareLocationCombos( + commands, + labware, + modules + ) + + // Filter out duplicate labware and labware that is not LPC-able. + return labwareLocationCombos.reduce( + (acc, labwareLocationCombo) => { + const labwareDef = labwareDefs.find( + def => getLabwareDefURI(def) === labwareLocationCombo.definitionUri + ) + if ( + (labwareDef?.allowedRoles ?? []).includes('adapter') || + (labwareDef?.allowedRoles ?? []).includes('lid') + ) { + return acc + } + // remove duplicate definitionUri in same location + const comboAlreadyExists = acc.some( + accLocationCombo => + labwareLocationCombo.definitionUri === + accLocationCombo.definitionUri && + isEqual(labwareLocationCombo.location, accLocationCombo.location) + ) + return comboAlreadyExists ? acc : [...acc, labwareLocationCombo] + }, + [] + ) +} diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts index 1b31d79de0a..836faf02327 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts @@ -1,5 +1,6 @@ -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { getLabwareDefinitionsFromCommands } from '@opentrons/components' import { useCreateMaintenanceRunLabwareDefinitionMutation, useDeleteMaintenanceRunMutation, @@ -8,14 +9,18 @@ import { import { useCreateTargetedMaintenanceRunMutation, - useNotifyRunQuery, useMostRecentCompletedAnalysis, + useNotifyRunQuery, } from '/app/resources/runs' import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { useLPCLabwareInfo } from '/app/organisms/LabwarePositionCheck/LPCFlows/hooks' import type { RobotType } from '@opentrons/shared-data' -import type { LPCFlowsProps } from '/app/organisms/LabwarePositionCheck/LPCFlows/LPCFlows' -import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import type { + LegacySupportLPCFlowsProps, + LPCFlowsProps, +} from '/app/organisms/LabwarePositionCheck/LPCFlows/LPCFlows' interface UseLPCFlowsBase { showLPC: boolean @@ -29,7 +34,7 @@ interface UseLPCFlowsIdle extends UseLPCFlowsBase { } interface UseLPCFlowsLaunched extends UseLPCFlowsBase { showLPC: true - lpcProps: LPCFlowsProps + lpcProps: LegacySupportLPCFlowsProps isLaunchingLPC: false } export type UseLPCFlowsResult = UseLPCFlowsIdle | UseLPCFlowsLaunched @@ -49,11 +54,21 @@ export function useLPCFlows({ const [isLaunching, setIsLaunching] = useState(false) const [hasCreatedLPCRun, setHasCreatedLPCRun] = useState(false) - const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) const deckConfig = useNotifyDeckConfigurationQuery().data + const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) const currentOffsets = runRecord?.data?.labwareOffsets ?? [] const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + const labwareDefs = useMemo( + () => getLabwareDefinitionsFromCommands(mostRecentAnalysis?.commands ?? []), + [mostRecentAnalysis != null] + ) + const labwareInfo = useLPCLabwareInfo({ + currentOffsets, + labwareDefs, + protocolData: mostRecentAnalysis, + }) + useMonitorMaintenanceRunForDeletion({ maintenanceRunId, setMaintenanceRunId }) const { @@ -130,10 +145,12 @@ export function useLPCFlows({ runId, robotType, deckConfig, - existingOffsets: currentOffsets, + labwareDefs, + labwareInfo, mostRecentAnalysis, protocolName, maintenanceRunId, + existingOffsets: currentOffsets, }, } : { launchLPC, isLaunchingLPC: isLaunching, lpcProps: null, showLPC } diff --git a/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx b/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx index 2215cd14bc6..d5a703a79ec 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx +++ b/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx @@ -3,9 +3,11 @@ import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data' import { LPCWizardFlex } from './LPCWizardFlex' import { LegacyLabwarePositionCheck } from '/app/organisms/LegacyLabwarePositionCheck' -import type { LPCFlowsProps } from '/app/organisms/LabwarePositionCheck/LPCFlows' +import type { LegacySupportLPCFlowsProps } from '/app/organisms/LabwarePositionCheck/LPCFlows' -export function LPCWizardContainer(props: LPCFlowsProps): JSX.Element { +export function LPCWizardContainer( + props: LegacySupportLPCFlowsProps +): JSX.Element { switch (props.robotType) { case FLEX_ROBOT_TYPE: return diff --git a/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx b/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx index 4706c2eaaf8..e4d6dea692e 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx +++ b/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx @@ -20,8 +20,12 @@ import { useLPCCommands, useLPCInitialState, } from '/app/organisms/LabwarePositionCheck/hooks' -import { NAV_STEPS } from '/app/organisms/LabwarePositionCheck/constants' -import { closeLPC, proceedStep } from '/app/redux/protocol-runs' +import { + closeLPC, + proceedStep, + LPC_STEP, + selectCurrentStep, +} from '/app/redux/protocol-runs' import { getIsOnDevice } from '/app/redux/config' import type { LPCFlowsProps } from '/app/organisms/LabwarePositionCheck/LPCFlows' @@ -34,7 +38,6 @@ export interface LPCWizardFlexProps extends Omit {} export function LPCWizardFlex(props: LPCWizardFlexProps): JSX.Element { const { onCloseClick, ...rest } = props - // TODO(jh, 01-14-25): Also inject goBack functionality once designs are finalized. const proceed = (): void => { dispatch(proceedStep(props.runId)) } @@ -118,18 +121,13 @@ function LPCWizardHeader({ function LPCWizardContent(props: LPCWizardContentProps): JSX.Element { const { t } = useTranslation('shared') - const currentStep = useSelector( - (state: State) => - state.protocolRuns[props.runId]?.lpc?.steps.current ?? null - ) + const currentStep = useSelector(selectCurrentStep(props.runId)) const { isRobotMoving, errorMessage, showExitConfirmation, } = props.commandUtils - // TODO(jh, 01-14-25): Handle open door behavior. - // Handle special cases that are shared by multiple steps first. if (isRobotMoving) { return @@ -146,24 +144,26 @@ function LPCWizardContent(props: LPCWizardContentProps): JSX.Element { } // Handle step-based routing. - switch (currentStep.section) { - case NAV_STEPS.BEFORE_BEGINNING: - return + switch (currentStep) { + case LPC_STEP.BEFORE_BEGINNING: + return - case NAV_STEPS.CHECK_POSITIONS: - return + case LPC_STEP.ATTACH_PROBE: + return - case NAV_STEPS.ATTACH_PROBE: - return + // TOME TODO: This gets entirely rewritten. + case LPC_STEP.HANDLE_LABWARE: + return - case NAV_STEPS.DETACH_PROBE: - return + case LPC_STEP.DETACH_PROBE: + return - case NAV_STEPS.RESULTS_SUMMARY: - return + // TOME TODO: This gets rewritten, too. + case LPC_STEP.LPC_COMPLETE: + return default: console.error('Unhandled LPC step.') - return + return } } diff --git a/app/src/organisms/LabwarePositionCheck/constants.ts b/app/src/organisms/LabwarePositionCheck/constants.ts deleted file mode 100644 index 9ccd9b81eef..00000000000 --- a/app/src/organisms/LabwarePositionCheck/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const NAV_STEPS = { - BEFORE_BEGINNING: 'BEFORE_BEGINNING', - ATTACH_PROBE: 'ATTACH_PROBE', - CHECK_POSITIONS: 'CHECK_POSITIONS', - DETACH_PROBE: 'DETACH_PROBE', - RESULTS_SUMMARY: 'RESULTS_SUMMARY', -} as const diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts index 0f944bf74f0..5e3064cf043 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts @@ -1,7 +1,7 @@ import { useSelector } from 'react-redux' import { modulePrepCommands } from './commands' -import { NAV_STEPS } from '/app/organisms/LabwarePositionCheck/constants' +import { STEP } from '/app/organisms/LabwarePositionCheck/constants' import { selectActiveLwInitialPosition } from '/app/redux/protocol-runs' import type { CreateCommand } from '@opentrons/shared-data' @@ -31,7 +31,7 @@ export function useHandlePrepModules({ ): Promise => { const initialPosition = selectInitialPositionFrom(step) - if (step?.section === NAV_STEPS.CHECK_POSITIONS) { + if (step?.section === STEP.CHECK_POSITIONS) { const prepCommands: CreateCommand[] = modulePrepCommands({ step, }) @@ -39,7 +39,7 @@ export function useHandlePrepModules({ if ( initialPosition == null && // Only run these commands during the appropriate step. - step.section === NAV_STEPS.CHECK_POSITIONS && + step.section === STEP.CHECK_POSITIONS && prepCommands.length > 0 ) { return chainLPCCommands(prepCommands, false) diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts index 180cf73f892..52f3e8031f9 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts @@ -1,5 +1,5 @@ import { moveToMaintenancePosition } from '/app/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands' -import { NAV_STEPS } from '/app/organisms/LabwarePositionCheck/constants' +import { STEP } from '/app/organisms/LabwarePositionCheck/constants' import type { CommandData } from '@opentrons/api-client' import type { LoadedPipette } from '@opentrons/shared-data' @@ -23,8 +23,8 @@ export function useHandleValidMoveToMaintenancePosition({ step: LabwarePositionCheckStep | null ): Promise => { if ( - step?.section === NAV_STEPS.ATTACH_PROBE || - step?.section === NAV_STEPS.DETACH_PROBE + step?.section === STEP.ATTACH_PROBE || + step?.section === STEP.DETACH_PROBE ) { return chainLPCCommands(moveToMaintenancePosition(pipette), false) } else { diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts index c39cc30d305..650617534c5 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts @@ -1,12 +1,9 @@ import { useEffect } from 'react' import { useDispatch } from 'react-redux' -import { getLabwareDefinitionsFromCommands } from '@opentrons/components' +import { startLPC, LPC_STEPS } from '/app/redux/protocol-runs' +import { getActivePipetteId } from './utils' -import { startLPC } from '/app/redux/protocol-runs' -import { getLPCSteps } from './utils' - -import type { RunTimeCommand } from '@opentrons/shared-data' import type { LPCWizardState } from '/app/redux/protocol-runs' import type { LPCWizardFlexProps } from '/app/organisms/LabwarePositionCheck/LPCWizardFlex' @@ -16,29 +13,24 @@ export interface UseLPCInitialStateProps export function useLPCInitialState({ mostRecentAnalysis, runId, + labwareDefs, ...rest }: UseLPCInitialStateProps): void { const dispatch = useDispatch() useEffect(() => { - const protocolCommands: RunTimeCommand[] = mostRecentAnalysis.commands - const labwareDefs = getLabwareDefinitionsFromCommands(protocolCommands) - const LPCSteps = getLPCSteps({ - protocolData: mostRecentAnalysis, - labwareDefs, - }) + const activePipetteId = getActivePipetteId(mostRecentAnalysis.pipettes) const initialState: LPCWizardState = { ...rest, protocolData: mostRecentAnalysis, labwareDefs, - workingOffsets: [], + activePipetteId, steps: { currentStepIndex: 0, - totalStepCount: LPCSteps.length, - current: LPCSteps[0], - all: LPCSteps, - next: LPCSteps[1], + totalStepCount: LPC_STEPS.length, + // TOME TODO: make a selector for the current step! + all: LPC_STEPS, }, } diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts new file mode 100644 index 00000000000..0dc1bd57c7e --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts @@ -0,0 +1,21 @@ +import { getPipetteNameSpecs } from '@opentrons/shared-data' + +import type { LoadedPipette } from '@opentrons/shared-data' + +// TOME TODO: Actually handle this throwing an error. + +// Return the pipetteId for the pipette in the protocol with the highest channel count. +export function getActivePipetteId(pipettes: LoadedPipette[]): string { + if (pipettes.length < 1) { + throw new Error( + 'no pipettes in protocol, cannot determine primary pipette for LPC' + ) + } + + return pipettes.reduce((acc, pip) => { + return (getPipetteNameSpecs(acc.pipetteName)?.channels ?? 0) > + (getPipetteNameSpecs(pip.pipetteName)?.channels ?? 0) + ? pip + : acc + }, pipettes[0]).id +} diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/getProbeBasedLPCSteps.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/getProbeBasedLPCSteps.ts deleted file mode 100644 index b6184663762..00000000000 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/getProbeBasedLPCSteps.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { isEqual } from 'lodash' - -import { getLabwareDefURI, getPipetteNameSpecs } from '@opentrons/shared-data' - -import { NAV_STEPS } from '../../../../constants' -import { getLabwareLocationCombos } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' - -import type { LoadedPipette } from '@opentrons/shared-data' -import type { - LabwarePositionCheckStep, - CheckPositionsStep, -} from '/app/organisms/LabwarePositionCheck/types' -import type { LabwareLocationCombo } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' -import type { GetLPCStepsParams } from '.' - -export function getProbeBasedLPCSteps( - params: GetLPCStepsParams -): LabwarePositionCheckStep[] { - const { protocolData } = params - - return [ - { section: NAV_STEPS.BEFORE_BEGINNING }, - { - section: NAV_STEPS.ATTACH_PROBE, - pipetteId: getPrimaryPipetteId(protocolData.pipettes), - }, - ...getAllCheckSectionSteps(params), - { - section: NAV_STEPS.DETACH_PROBE, - pipetteId: getPrimaryPipetteId(protocolData.pipettes), - }, - { section: NAV_STEPS.RESULTS_SUMMARY }, - ] -} - -function getPrimaryPipetteId(pipettes: LoadedPipette[]): string { - if (pipettes.length < 1) { - throw new Error( - 'no pipettes in protocol, cannot determine primary pipette for LPC' - ) - } - - return pipettes.reduce((acc, pip) => { - return (getPipetteNameSpecs(acc.pipetteName)?.channels ?? 0) > - (getPipetteNameSpecs(pip.pipetteName)?.channels ?? 0) - ? pip - : acc - }, pipettes[0]).id -} - -function getAllCheckSectionSteps({ - labwareDefs, - protocolData, -}: GetLPCStepsParams): CheckPositionsStep[] { - const { pipettes, commands, labware, modules = [] } = protocolData - const labwareLocationCombos = getLabwareLocationCombos( - commands, - labware, - modules - ) - const labwareLocations = labwareLocationCombos.reduce( - (acc, labwareLocationCombo) => { - const labwareDef = labwareDefs.find( - def => getLabwareDefURI(def) === labwareLocationCombo.definitionUri - ) - if ( - (labwareDef?.allowedRoles ?? []).includes('adapter') || - (labwareDef?.allowedRoles ?? []).includes('lid') - ) { - return acc - } - // remove duplicate definitionUri in same location - const comboAlreadyExists = acc.some( - accLocationCombo => - labwareLocationCombo.definitionUri === - accLocationCombo.definitionUri && - isEqual(labwareLocationCombo.location, accLocationCombo.location) - ) - return comboAlreadyExists ? acc : [...acc, labwareLocationCombo] - }, - [] - ) - - return labwareLocations.map( - ({ location, labwareId, moduleId, adapterId, definitionUri }) => ({ - section: NAV_STEPS.CHECK_POSITIONS, - labwareId: labwareId, - pipetteId: getPrimaryPipetteId(pipettes), - location, - moduleId, - adapterId, - definitionUri, - }) - ) -} diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/index.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/index.ts deleted file mode 100644 index 7121d2cdf98..00000000000 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getLPCSteps/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getProbeBasedLPCSteps } from './getProbeBasedLPCSteps' - -import type { - CompletedProtocolAnalysis, - LabwareDefinition2, -} from '@opentrons/shared-data' -import type { LabwarePositionCheckStep } from '/app/organisms/LabwarePositionCheck/types' - -export interface GetLPCStepsParams { - protocolData: CompletedProtocolAnalysis - labwareDefs: LabwareDefinition2[] -} - -// Prepare all LPC steps for injection. -export function getLPCSteps( - params: GetLPCStepsParams -): LabwarePositionCheckStep[] { - if ('pipettes' in params.protocolData) { - if (params.protocolData.pipettes.length === 0) { - throw new Error( - 'no pipettes loaded within protocol, labware position check cannot be performed' - ) - } else { - return getProbeBasedLPCSteps(params) - } - } else { - console.error('expected pipettes to be in protocol data') - return [] - } -} diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/index.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/index.ts index 3fd9dba02b5..06380987129 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/index.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/index.ts @@ -1 +1 @@ -export * from './getLPCSteps' +export * from './getActivePipetteId' diff --git a/app/src/organisms/LabwarePositionCheck/redux/index.ts b/app/src/organisms/LabwarePositionCheck/redux/index.ts deleted file mode 100644 index 51a3b4100a9..00000000000 --- a/app/src/organisms/LabwarePositionCheck/redux/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../types' diff --git a/app/src/organisms/LabwarePositionCheck/redux/types.ts b/app/src/organisms/LabwarePositionCheck/redux/types.ts deleted file mode 100644 index d40d18d91d7..00000000000 --- a/app/src/organisms/LabwarePositionCheck/redux/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { LabwarePositionCheckStep } from '/app/organisms/LabwarePositionCheck/types' - -// TODO(jh, 01-16-25): Remove this once `steps` are refactored out of Redux. - -export interface StepsInfo { - currentStepIndex: number - totalStepCount: number - current: LabwarePositionCheckStep - next: LabwarePositionCheckStep | null - all: LabwarePositionCheckStep[] -} diff --git a/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx index c63568c4c60..592a75ba839 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx @@ -14,15 +14,14 @@ import { TwoUpTileLayout } from './TwoUpTileLayout' import { ViewOffsets } from './ViewOffsets' import { SmallButton } from '/app/atoms/buttons' import { getIsOnDevice } from '/app/redux/config' -import { selectActivePipette } from '/app/redux/protocol-runs' +import { + selectActivePipette, + selectLabwareOffsetsForAllLw, +} from '/app/redux/protocol-runs' -import type { - LPCStepProps, - BeforeBeginningStep, - LabwarePositionCheckStep, -} from '/app/organisms/LabwarePositionCheck/types' import type { State } from '/app/redux/types' import type { LPCWizardState } from '/app/redux/protocol-runs' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' // TODO(BC, 09/01/23): replace updated support article link for LPC on OT-2/Flex const SUPPORT_PAGE_URL = 'https://support.opentrons.com/s/ot2-calibration' @@ -31,15 +30,12 @@ export function BeforeBeginning({ runId, proceed, commandUtils, -}: LPCStepProps): JSX.Element { +}: LPCWizardContentProps): JSX.Element { const { t, i18n } = useTranslation(['labware_position_check', 'shared']) const isOnDevice = useSelector(getIsOnDevice) - const activePipette = useSelector((state: State) => { - const step = state.protocolRuns[runId]?.lpc?.steps - .current as LabwarePositionCheckStep - return selectActivePipette(step, runId, state) ?? null - }) - const { protocolName, labwareDefs, existingOffsets } = useSelector( + const activePipette = useSelector(selectActivePipette(runId)) + const existingOffsets = useSelector(selectLabwareOffsetsForAllLw(runId)) + const { protocolName, labwareDefs } = useSelector( (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState ) const { createStartLPCHandler, toggleRobotMoving } = commandUtils diff --git a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/CheckItem/index.tsx index fa7366af6fc..535fa992ff0 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/CheckItem/index.tsx @@ -8,7 +8,6 @@ import { getLabwareDisplayLocation, } from '@opentrons/components' -import { NAV_STEPS } from '/app/organisms/LabwarePositionCheck/constants' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { UnorderedList } from '/app/molecules/UnorderedList' import { @@ -26,18 +25,12 @@ import { import { getIsOnDevice } from '/app/redux/config' import type { DisplayLocationParams } from '@opentrons/components' -import type { - CheckPositionsStep, - LPCStepProps, -} from '/app/organisms/LabwarePositionCheck/types' import type { State } from '/app/redux/types' import type { LPCWizardState } from '/app/redux/protocol-runs' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' -export function CheckItem( - props: LPCStepProps -): JSX.Element { - const { runId, proceed, commandUtils, step } = props - const { labwareId, location } = step +export function CheckItem(props: LPCWizardContentProps): JSX.Element { + const { runId, proceed, commandUtils } = props const { handleJog, handleCheckItemsPrepModules, @@ -56,15 +49,9 @@ export function CheckItem( const { t } = useTranslation(['labware_position_check', 'shared']) const { t: commandTextT } = useTranslation('protocol_command_text') - const pipette = useSelector( - (state: State) => selectActivePipette(step, runId, state) ?? null - ) - const initialPosition = useSelector((state: State) => - selectActiveLwInitialPosition(step, runId, state) - ) - const isLwTiprack = useSelector((state: State) => - selectIsActiveLwTipRack(runId, state) - ) + const pipette = useSelector(selectActivePipette(runId)) + const initialPosition = useSelector(selectActiveLwInitialPosition(runId)) + const isLwTiprack = useSelector(selectIsActiveLwTipRack(runId)) const buildDisplayParams = (): Omit< DisplayLocationParams, @@ -122,7 +109,7 @@ export function CheckItem( ) }) .then(() => { - if (steps.next?.section === NAV_STEPS.CHECK_POSITIONS) { + if (steps.next?.section === STEP.CHECK_POSITIONS) { return handleCheckItemsPrepModules(steps.next) } else { return handleValidMoveToMaintenancePosition(pipette, steps.next) diff --git a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx index f63d5f8518d..7be0611b74c 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx @@ -28,20 +28,15 @@ import { SmallButton } from '/app/atoms/buttons' import { LabwareOffsetTabs } from '/app/organisms/LabwareOffsetTabs' import { TableComponent } from './TableComponent' -import type { - LPCStepProps, - ResultsSummaryStep, -} from '/app/organisms/LabwarePositionCheck/types' import type { State } from '/app/redux/types' import type { LPCWizardState } from '/app/redux/protocol-runs' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' // TODO(jh, 01-08-25): This support link will likely need updating as a part of RPRD-173, too. const LPC_HELP_LINK_URL = 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' -export function ResultsSummary( - props: LPCStepProps -): JSX.Element { +export function ResultsSummary(props: LPCWizardContentProps): JSX.Element { const { commandUtils, runId } = props const isOnDevice = useSelector(getIsOnDevice) const { protocolData } = useSelector( diff --git a/app/src/organisms/LabwarePositionCheck/types/index.ts b/app/src/organisms/LabwarePositionCheck/types/index.ts index 4da2755de80..f71cb81b5a5 100644 --- a/app/src/organisms/LabwarePositionCheck/types/index.ts +++ b/app/src/organisms/LabwarePositionCheck/types/index.ts @@ -1,2 +1 @@ -export * from './steps' export * from './content' diff --git a/app/src/organisms/LabwarePositionCheck/types/steps.ts b/app/src/organisms/LabwarePositionCheck/types/steps.ts deleted file mode 100644 index 3cc781aebff..00000000000 --- a/app/src/organisms/LabwarePositionCheck/types/steps.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { LabwareOffsetLocation } from '@opentrons/api-client' -import type { NAV_STEPS } from '../constants' -import type { LPCWizardContentProps } from './content' - -export type LabwarePositionCheckStep = - | BeforeBeginningStep - | AttachProbeStep - | CheckPositionsStep - | DetachProbeStep - | ResultsSummaryStep - -export type LPCStepProps = Omit< - LPCWizardContentProps, - 'step' -> & { - step: Extract -} - -export interface PerformLPCStep { - pipetteId: string - labwareId: string - location: LabwareOffsetLocation - definitionUri: string - adapterId?: string - moduleId?: string -} - -export interface BeforeBeginningStep { - section: typeof NAV_STEPS.BEFORE_BEGINNING -} - -export interface AttachProbeStep { - section: typeof NAV_STEPS.ATTACH_PROBE - pipetteId: string -} - -export interface CheckPositionsStep extends PerformLPCStep { - section: typeof NAV_STEPS.CHECK_POSITIONS -} - -export interface DetachProbeStep { - section: typeof NAV_STEPS.DETACH_PROBE - pipetteId: string -} - -export interface ResultsSummaryStep { - section: typeof NAV_STEPS.RESULTS_SUMMARY -} diff --git a/app/src/redux/protocol-runs/actions/lpc.ts b/app/src/redux/protocol-runs/actions/lpc.ts index 5ec472e094c..c33386366c0 100644 --- a/app/src/redux/protocol-runs/actions/lpc.ts +++ b/app/src/redux/protocol-runs/actions/lpc.ts @@ -4,6 +4,9 @@ import { SET_FINAL_POSITION, START_LPC, FINISH_LPC, + GO_BACK_STEP, + SET_SELECTED_LABWARE, + CLEAR_SELECTED_LABWARE, } from '../constants' import type { @@ -14,12 +17,42 @@ import type { PositionParams, ProceedStepAction, FinishLPCAction, + GoBackStepAction, + LPCLabwareLocationDetails, + SelectedLabwareAction, + ClearSelectedLabwareAction, } from '../types' export const proceedStep = (runId: string): ProceedStepAction => ({ type: PROCEED_STEP, payload: { runId }, }) + +export const goBackStep = (runId: string): GoBackStepAction => ({ + type: GO_BACK_STEP, + payload: { runId }, +}) + +export const setSelectedLabware = ( + runId: string, + labwareUri: string, + location: LPCLabwareLocationDetails +): SelectedLabwareAction => ({ + type: SET_SELECTED_LABWARE, + payload: { + runId, + labwareUri, + location, + }, +}) + +export const clearSelectedLabware = ( + runId: string +): ClearSelectedLabwareAction => ({ + type: CLEAR_SELECTED_LABWARE, + payload: { runId }, +}) + export const setInitialPosition = ( runId: string, params: PositionParams diff --git a/app/src/redux/protocol-runs/constants/lpc.ts b/app/src/redux/protocol-runs/constants/lpc/actions.ts similarity index 58% rename from app/src/redux/protocol-runs/constants/lpc.ts rename to app/src/redux/protocol-runs/constants/lpc/actions.ts index 669c8ec503a..355d024e962 100644 --- a/app/src/redux/protocol-runs/constants/lpc.ts +++ b/app/src/redux/protocol-runs/constants/lpc/actions.ts @@ -1,5 +1,8 @@ export const START_LPC = 'START_LPC' export const FINISH_LPC = 'FINISH_LPC' export const PROCEED_STEP = 'PROCEED_STEP' +export const GO_BACK_STEP = 'GO_BACK_STEP' +export const SET_SELECTED_LABWARE = 'SET_SELECTED_LABWARE' +export const CLEAR_SELECTED_LABWARE = 'CLEAR_SELECTED_LABWARE' export const SET_INITIAL_POSITION = 'SET_INITIAL_POSITION' export const SET_FINAL_POSITION = 'SET_FINAL_POSITION' diff --git a/app/src/redux/protocol-runs/constants/lpc/index.ts b/app/src/redux/protocol-runs/constants/lpc/index.ts new file mode 100644 index 00000000000..3469ff9cd4a --- /dev/null +++ b/app/src/redux/protocol-runs/constants/lpc/index.ts @@ -0,0 +1,2 @@ +export * from './actions' +export * from './steps' diff --git a/app/src/redux/protocol-runs/constants/lpc/steps.ts b/app/src/redux/protocol-runs/constants/lpc/steps.ts new file mode 100644 index 00000000000..e2ce6f337e1 --- /dev/null +++ b/app/src/redux/protocol-runs/constants/lpc/steps.ts @@ -0,0 +1,25 @@ +/** + * A step is associated with a view or multiple views that are a part of the + * core flow. They are driven by CTA and are not side effects of robot state. + * + * Advancing a step advances the step counter, going back a step lowers + * the step counter. If advancing/going back to a different view does not alter the step counter, + * then the view should either be associated with an existing step or should be independent of any step (ex, "robot in motion"). + * + */ +export const LPC_STEP = { + BEFORE_BEGINNING: 'BEFORE_BEGINNING', + ATTACH_PROBE: 'ATTACH_PROBE', + HANDLE_LABWARE: 'HANDLE_LABWARE', + DETACH_PROBE: 'DETACH_PROBE', + LPC_COMPLETE: 'LPC_COMPLETE', +} as const + +// All LPC steps, in order. +export const LPC_STEPS = [ + LPC_STEP.BEFORE_BEGINNING, + LPC_STEP.ATTACH_PROBE, + LPC_STEP.HANDLE_LABWARE, + LPC_STEP.DETACH_PROBE, + LPC_STEP.LPC_COMPLETE, +] diff --git a/app/src/redux/protocol-runs/reducer/lpc.ts b/app/src/redux/protocol-runs/reducer/lpc.ts index ca27fac6273..bcc29a2636d 100644 --- a/app/src/redux/protocol-runs/reducer/lpc.ts +++ b/app/src/redux/protocol-runs/reducer/lpc.ts @@ -1,15 +1,22 @@ import { PROCEED_STEP, + SET_SELECTED_LABWARE, SET_INITIAL_POSITION, SET_FINAL_POSITION, FINISH_LPC, START_LPC, + GO_BACK_STEP, } from '../constants' -import { updateWorkingOffset } from './transforms' +import { updateOffsetsForURI } from './transforms' -import type { LPCWizardAction, LPCWizardState } from '../types' +import type { + LPCWizardAction, + LPCWizardState, + SelectedLabwareInfo, +} from '../types' // TODO(jh, 01-17-25): A lot of this state should live above the LPC slice, in the general protocolRuns slice instead. +// We should make selectors for that state, too! export function LPCReducer( state: LPCWizardState | undefined, action: LPCWizardAction @@ -27,28 +34,65 @@ export function LPCReducer( ? currentStepIndex + 1 : currentStepIndex - const nextStepIdx = - newStepIdx + 1 < totalStepCount ? newStepIdx + 1 : null - const nextStep = - nextStepIdx != null ? state.steps.all[nextStepIdx] : null + return { + ...state, + steps: { + ...state.steps, + currentStepIndex: newStepIdx, + }, + } + } + + case GO_BACK_STEP: { + const { currentStepIndex } = state.steps + const newStepIdx = currentStepIndex > 0 ? currentStepIndex - 1 : 0 return { ...state, steps: { ...state.steps, currentStepIndex: newStepIdx, - current: state.steps.all[newStepIdx], - next: nextStep, + }, + } + } + + case SET_SELECTED_LABWARE: { + const lwUri = action.payload.labwareUri + const thisLwInfo = state.labwareInfo.labware[lwUri] + + const selectedLabware: SelectedLabwareInfo = { + uri: action.payload.labwareUri, + id: thisLwInfo.id, + locationDetails: action.payload.location, + } + + return { + ...state, + labwareInfo: { + ...state.labwareInfo, + selectedLabware, }, } } case SET_INITIAL_POSITION: - case SET_FINAL_POSITION: + case SET_FINAL_POSITION: { + const lwUri = action.payload.labwareUri + return { ...state, - workingOffsets: updateWorkingOffset(state.workingOffsets, action), + labwareInfo: { + ...state.labwareInfo, + labware: { + ...state.labwareInfo.labware, + [lwUri]: { + ...state.labwareInfo.labware[lwUri], + offsetDetails: updateOffsetsForURI(state, action), + }, + }, + }, } + } case FINISH_LPC: return undefined diff --git a/app/src/redux/protocol-runs/reducer/transforms/lpc.ts b/app/src/redux/protocol-runs/reducer/transforms/lpc.ts index 3d08fadee62..7ca2eece148 100644 --- a/app/src/redux/protocol-runs/reducer/transforms/lpc.ts +++ b/app/src/redux/protocol-runs/reducer/transforms/lpc.ts @@ -1,47 +1,65 @@ +import type { + LPCWizardAction, + LPCWizardState, + OffsetDetails, +} from '../../types' import isEqual from 'lodash/isEqual' +import { + SET_FINAL_POSITION, + SET_INITIAL_POSITION, +} from '/app/redux/protocol-runs' -import type { LPCWizardAction, WorkingOffset } from '../../types' - -export function updateWorkingOffset( - workingOffsets: WorkingOffset[], +// Handle positional updates, only updating the working offset that matches the location specified in the action. +export function updateOffsetsForURI( + state: LPCWizardState, action: Extract< LPCWizardAction, { type: 'SET_INITIAL_POSITION' | 'SET_FINAL_POSITION' } > -): WorkingOffset[] { +): OffsetDetails[] { const { type, payload } = action - const { labwareId, location, position } = payload - const existingRecordIndex = workingOffsets.findIndex( - record => - record.labwareId === labwareId && isEqual(record.location, location) + const { labwareUri, position, location } = payload + const { offsetDetails } = state.labwareInfo.labware[labwareUri] + const relevantDetailsIdx = offsetDetails.findIndex(detail => + isEqual(location, detail.locationDetails) ) - if (existingRecordIndex < 0) { - return [ - ...workingOffsets, - { - labwareId, - location, - initialPosition: type === 'SET_INITIAL_POSITION' ? position : null, - finalPosition: type === 'SET_FINAL_POSITION' ? position : null, - }, - ] + if (relevantDetailsIdx < 0) { + console.warn(`No matching location found for ${labwareUri}`) + return offsetDetails } else { - const updatedOffset = { - ...workingOffsets[existingRecordIndex], - ...(type === 'SET_INITIAL_POSITION' && { - initialPosition: position, - finalPosition: null, - }), - ...(type === 'SET_FINAL_POSITION' && { - finalPosition: position, - }), - } - - return [ - ...workingOffsets.slice(0, existingRecordIndex), - updatedOffset, - ...workingOffsets.slice(existingRecordIndex + 1), + const relevantDetail = offsetDetails[relevantDetailsIdx] + const newOffsetDetails = [ + ...offsetDetails.slice(0, relevantDetailsIdx), + ...offsetDetails.slice(relevantDetailsIdx + 1), ] + + if (relevantDetail.workingOffset == null) { + const newWorkingDetail = { + initialPosition: type === SET_INITIAL_POSITION ? position : null, + finalPosition: type === SET_FINAL_POSITION ? position : null, + } + + return [ + ...newOffsetDetails, + { ...relevantDetail, workingOffset: newWorkingDetail }, + ] + } else { + const newWorkingDetail = + type === SET_INITIAL_POSITION + ? { + initialPosition: position, + finalPosition: null, + } + : { + ...relevantDetail.workingOffset, + finalPosition: position, + } + + return [ + ...newOffsetDetails, + { ...relevantDetail, workingOffset: newWorkingDetail }, + ] + } } } diff --git a/app/src/redux/protocol-runs/selectors/lpc/index.ts b/app/src/redux/protocol-runs/selectors/lpc/index.ts index 5bd4a518ac2..0b9e54e22e1 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/index.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/index.ts @@ -1,2 +1,3 @@ export * from './labware' export * from './pipettes' +export * from './steps' diff --git a/app/src/redux/protocol-runs/selectors/lpc/labware.ts b/app/src/redux/protocol-runs/selectors/lpc/labware.ts index afce4ae46e1..c8ed8d6c005 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/labware.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/labware.ts @@ -1,90 +1,63 @@ import { createSelector } from 'reselect' -import isEqual from 'lodash/isEqual' import { getIsTiprack, getLabwareDisplayName, - getLabwareDefURI, - getVectorSum, getVectorDifference, + getVectorSum, IDENTITY_VECTOR, } from '@opentrons/shared-data' -import { getCurrentOffsetForLabwareInLocation } from '/app/transformations/analysis' -import { getItemLabwareDef } from './transforms' +import { + getItemLabwareDef, + getSelectedLabwareOffsetDetails, + getOffsetDetailsForAllLabware, + getItemLabwareDefFrom, +} from './transforms' import type { Selector } from 'reselect' -import type { VectorOffset, LabwareOffsetLocation } from '@opentrons/api-client' -import type { LabwareDefinition2, Coordinates } from '@opentrons/shared-data' +import type { + LabwareOffsetLocation, + VectorOffset, + LabwareOffset, +} from '@opentrons/api-client' import type { State } from '../../../types' - -// TODO(jh, 01-16-25): Revisit once LPC `step` refactors are completed. -// eslint-disable-next-line opentrons/no-imports-across-applications -import type { LabwarePositionCheckStep } from '/app/organisms/LabwarePositionCheck/types' - -// TODO(jh, 01-13-25): Remove the explicit type casting after restructuring "step". -// TODO(jh, 01-17-25): As LPC selectors become finalized, wrap them in createSelector. +import type { Coordinates, LabwareDefinition2 } from '@opentrons/shared-data' +import type { LabwareDetails } from '/app/redux/protocol-runs' export const selectActiveLwInitialPosition = ( - step: LabwarePositionCheckStep | null, - runId: string, - state: State -): VectorOffset | null => { - const { workingOffsets } = state.protocolRuns[runId]?.lpc ?? {} - - if (step != null && workingOffsets != null) { - const labwareId = 'labwareId' in step ? step.labwareId : '' - const location = 'location' in step ? step.location : '' - - return ( - workingOffsets.find( - o => - o.labwareId === labwareId && - isEqual(o.location, location) && - o.initialPosition != null - )?.initialPosition ?? null - ) - } else { - if (workingOffsets == null) { - console.warn('LPC state not initalized before selector use.') - } + runId: string +): Selector => + createSelector( + (state: State) => getSelectedLabwareOffsetDetails(runId, state), + details => { + const workingOffset = details?.workingOffset - return null - } -} + if (workingOffset == null) { + console.warn('Working offset for active labware not set.') + return null + } else { + return workingOffset.initialPosition + } + } + ) export const selectActiveLwExistingOffset = ( - runId: string, - state: State -): VectorOffset => { - const { existingOffsets, steps } = state.protocolRuns[runId]?.lpc ?? {} - - if (existingOffsets == null || steps == null) { - console.warn('LPC state not initalized before selector use.') - return IDENTITY_VECTOR - } else if ( - !('labwareId' in steps.current) || - !('location' in steps.current) || - !('slotName' in steps.current.location) - ) { - console.warn( - `No labwareId or location in current step: ${steps.current.section}` - ) - return IDENTITY_VECTOR - } else { - const lwUri = getLabwareDefURI( - getItemLabwareDefFrom(runId, state) as LabwareDefinition2 - ) - - return ( - getCurrentOffsetForLabwareInLocation( - existingOffsets, - lwUri, - steps.current.location - )?.vector ?? IDENTITY_VECTOR - ) - } -} + runId: string +): Selector => + createSelector( + (state: State) => getSelectedLabwareOffsetDetails(runId, state), + details => { + const existingVector = details?.existingOffset.vector + + if (existingVector == null) { + console.warn('No existing offset vector found for active labware') + return IDENTITY_VECTOR + } else { + return existingVector ?? IDENTITY_VECTOR + } + } + ) export interface SelectOffsetsToApplyResult { definitionUri: string @@ -96,156 +69,135 @@ export const selectOffsetsToApply = ( runId: string ): Selector => createSelector( - (state: State) => state.protocolRuns[runId]?.lpc?.workingOffsets, + (state: State) => getOffsetDetailsForAllLabware(runId, state), (state: State) => state.protocolRuns[runId]?.lpc?.protocolData, - (state: State) => state.protocolRuns[runId]?.lpc?.existingOffsets, - (workingOffsets, protocolData, existingOffsets) => { - if ( - workingOffsets == null || - protocolData == null || - existingOffsets == null - ) { + (allDetails, protocolData): SelectOffsetsToApplyResult[] => { + if (protocolData == null) { console.warn('LPC state not initalized before selector use.') return [] } - return workingOffsets.map( - ({ initialPosition, finalPosition, labwareId, location }) => { - const definitionUri = - protocolData.labware.find(l => l.id === labwareId)?.definitionUri ?? - null + return allDetails.flatMap( + ({ workingOffset, existingOffset, locationDetails }) => { + const definitionUri = locationDetails.definitionUri + const { initialPosition, finalPosition } = workingOffset ?? {} if ( finalPosition == null || initialPosition == null || definitionUri == null ) { - throw new Error( - `cannot create offset for labware with id ${labwareId}, in location ${JSON.stringify( - location - )}, with initial position ${String( - initialPosition - )}, and final position ${String(finalPosition)}` - ) - } else { - const existingOffset = - getCurrentOffsetForLabwareInLocation( - existingOffsets, - definitionUri, - location - )?.vector ?? IDENTITY_VECTOR - const vector = getVectorSum( - existingOffset, - getVectorDifference(finalPosition, initialPosition) + console.error( + `Cannot generate offsets for labware with incomplete details. ID: ${locationDetails.labwareId}` ) - return { definitionUri, location, vector } + return [] } + + const existingOffsetVector = existingOffset.vector + const finalVector = getVectorSum( + existingOffsetVector, + getVectorDifference(finalPosition, initialPosition) + ) + return [ + { + definitionUri, + location: { ...locationDetails }, + vector: finalVector, + }, + ] } ) } ) export const selectIsActiveLwTipRack = ( - runId: string, - state: State -): boolean => { - const { current } = state.protocolRuns[runId]?.lpc?.steps ?? {} - - if (current != null && 'labwareId' in current) { - return getIsTiprack( - getItemLabwareDefFrom(runId, state) as LabwareDefinition2 - ) - } else { - console.warn( - 'No labwareId in step or LPC state not initalized before selector use.' - ) - return false - } -} + runId: string +): Selector => + createSelector( + (state: State) => getItemLabwareDefFrom(runId, state), + def => (def != null ? getIsTiprack(def) : false) + ) -export const selectLwDisplayName = (runId: string, state: State): string => { - const { current } = state.protocolRuns[runId]?.lpc?.steps ?? {} - - if (current != null && 'labwareId' in current) { - return getLabwareDisplayName( - getItemLabwareDefFrom(runId, state) as LabwareDefinition2 - ) - } else { - console.warn( - 'No labwareId in step or LPC state not initalized before selector use.' - ) - return '' - } -} +export const selectLwDisplayName = (runId: string): Selector => + createSelector( + (state: State) => getItemLabwareDefFrom(runId, state), + def => (def != null ? getLabwareDisplayName(def) : '') + ) export const selectActiveAdapterDisplayName = ( - runId: string, - state: State -): string => { - const { protocolData, labwareDefs, steps } = - state.protocolRuns[runId]?.lpc ?? {} - - if (protocolData == null || labwareDefs == null || steps == null) { - console.warn('LPC state not initialized before selector use.') - return '' - } - - return 'adapterId' in steps.current && steps.current.adapterId != null - ? getItemLabwareDef({ - labwareId: steps.current.adapterId, - loadedLabware: protocolData.labware, - labwareDefs, - })?.metadata.displayName ?? '' - : '' -} + runId: string +): Selector => + createSelector( + (state: State) => + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware, + (state: State) => state?.protocolRuns[runId]?.lpc?.labwareDefs, + (state: State) => state?.protocolRuns[runId]?.lpc?.protocolData, + (selectedLabware, labwareDefs, analysis) => { + const adapterId = selectedLabware?.locationDetails.adapterId + + if (selectedLabware == null || labwareDefs == null || analysis == null) { + console.warn('No selected labware or store not properly initialized.') + return '' + } + + return adapterId != null + ? getItemLabwareDef({ + labwareId: adapterId, + loadedLabware: analysis.labware, + labwareDefs, + })?.metadata.displayName ?? '' + : '' + } + ) +export const selectLabwareOffsetsForAllLw = ( + runId: string +): Selector => + createSelector( + (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, + (labware): LabwareOffset[] => { + if (labware == null) { + console.warn('Labware info not initialized in state') + return [] + } + + return Object.values(labware).flatMap((details: LabwareDetails) => + details.offsetDetails.map(offsetDetail => ({ + id: details.id, + createdAt: offsetDetail.existingOffset.createdAt, + definitionUri: offsetDetail.locationDetails.definitionUri, + location: { + slotName: offsetDetail.locationDetails.slotName, + moduleModel: offsetDetail.locationDetails.moduleModel, + definitionUri: offsetDetail.locationDetails.definitionUri, + }, + vector: offsetDetail.existingOffset.vector, + })) + ) + } + ) export const selectItemLabwareDef = ( runId: string ): Selector => createSelector( - (state: State) => state.protocolRuns[runId]?.lpc?.steps.current, + (state: State) => + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware, (state: State) => state.protocolRuns[runId]?.lpc?.labwareDefs, (state: State) => state.protocolRuns[runId]?.lpc?.protocolData.labware, - (current, labwareDefs, loadedLabware) => { - const labwareId = - current != null && 'labwareId' in current ? current.labwareId : '' - - if (labwareId === '' || labwareDefs == null || loadedLabware == null) { - console.warn( - `No labwareId associated with step: ${current?.section} or LPC state not initialized before selector use.` - ) + (selectedLabware, labwareDefs, loadedLabware) => { + if ( + selectedLabware == null || + labwareDefs == null || + loadedLabware == null + ) { + console.warn('No selected labware or store not properly initialized.') return null + } else { + return getItemLabwareDef({ + labwareId: selectedLabware.id, + labwareDefs, + loadedLabware, + }) } - - return getItemLabwareDef({ - labwareId, - labwareDefs, - loadedLabware, - }) } ) - -const getItemLabwareDefFrom = ( - runId: string, - state: State -): LabwareDefinition2 | null => { - const current = state.protocolRuns[runId]?.lpc?.steps.current - const labwareDefs = state.protocolRuns[runId]?.lpc?.labwareDefs - const loadedLabware = state.protocolRuns[runId]?.lpc?.protocolData.labware - - const labwareId = - current != null && 'labwareId' in current ? current.labwareId : '' - - if (labwareId === '' || labwareDefs == null || loadedLabware == null) { - console.warn( - `No labwareId associated with step: ${current?.section} or LPC state not initialized before selector use.` - ) - return null - } - - return getItemLabwareDef({ - labwareId, - labwareDefs, - loadedLabware, - }) -} diff --git a/app/src/redux/protocol-runs/selectors/lpc/pipettes.ts b/app/src/redux/protocol-runs/selectors/lpc/pipettes.ts index 1070e80946e..748081b18cb 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/pipettes.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/pipettes.ts @@ -1,39 +1,36 @@ +import { createSelector } from 'reselect' + import { getPipetteNameSpecs } from '@opentrons/shared-data' +import type { Selector } from 'reselect' import type { LoadedPipette, PipetteChannels } from '@opentrons/shared-data' - -// TODO(jh, 01-16-25): Revisit once LPC `step` refactors are completed. -// eslint-disable-next-line opentrons/no-imports-across-applications -import type { LabwarePositionCheckStep } from '/app/organisms/LabwarePositionCheck/types' import type { State } from '../../../types' export const selectActivePipette = ( - step: LabwarePositionCheckStep, - runId: string, - state: State -): LoadedPipette | null => { - const { protocolData } = state.protocolRuns[runId]?.lpc ?? {} - const pipetteId = 'pipetteId' in step ? step.pipetteId : '' - - if (pipetteId === '') { - console.warn(`No matching pipette found for pipetteId ${pipetteId}`) - } else if (protocolData == null) { - console.warn('LPC state not initalized before selector use.') - } - - return ( - protocolData?.pipettes.find(pipette => pipette.id === pipetteId) ?? null + runId: string +): Selector => + createSelector( + (state: State) => state.protocolRuns[runId]?.lpc?.activePipetteId, + (state: State) => state.protocolRuns[runId]?.lpc?.protocolData, + (activePipetteId, protocolData) => { + if (activePipetteId == null || protocolData == null) { + console.warn('LPC state not initalized before selector use.') + return null + } else { + return ( + protocolData?.pipettes.find( + pipette => pipette.id === activePipetteId + ) ?? null + ) + } + } ) -} export const selectActivePipetteChannelCount = ( - step: LabwarePositionCheckStep, - runId: string, - state: State -): PipetteChannels => { - const pipetteName = selectActivePipette(step, runId, state)?.pipetteName - - return pipetteName != null - ? getPipetteNameSpecs(pipetteName)?.channels ?? 1 - : 1 -} + runId: string +): Selector => + createSelector( + (state: State) => selectActivePipette(runId)(state)?.pipetteName, + pipetteName => + pipetteName != null ? getPipetteNameSpecs(pipetteName)?.channels ?? 1 : 1 + ) diff --git a/app/src/redux/protocol-runs/selectors/lpc/steps.ts b/app/src/redux/protocol-runs/selectors/lpc/steps.ts new file mode 100644 index 00000000000..a4d34160140 --- /dev/null +++ b/app/src/redux/protocol-runs/selectors/lpc/steps.ts @@ -0,0 +1,15 @@ +import { createSelector } from 'reselect' + +import { LPC_STEP } from '/app/redux/protocol-runs' + +import type { Selector } from 'reselect' +import type { State } from '../../../types' +import type { LPCStep } from '/app/redux/protocol-runs' + +export const selectCurrentStep = (runId: string): Selector => + createSelector( + (state: State) => state.protocolRuns[runId]?.lpc?.steps.currentStepIndex, + (state: State) => state.protocolRuns[runId]?.lpc?.steps.all, + (currentIdx, allSteps) => + allSteps?.[currentIdx ?? 0] ?? LPC_STEP.BEFORE_BEGINNING + ) diff --git a/app/src/redux/protocol-runs/selectors/lpc/transforms.ts b/app/src/redux/protocol-runs/selectors/lpc/transforms.ts index 780e5336133..08bcd4437a8 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/transforms.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/transforms.ts @@ -1,9 +1,13 @@ +import isEqual from 'lodash/isEqual' + import { getLabwareDefURI } from '@opentrons/shared-data' import type { CompletedProtocolAnalysis, LabwareDefinition2, } from '@opentrons/shared-data' +import type { State } from '/app/redux/types' +import type { LabwareDetails, OffsetDetails } from '/app/redux/protocol-runs' interface GetLabwareDefsForLPCParams { labwareId: string @@ -11,11 +15,11 @@ interface GetLabwareDefsForLPCParams { labwareDefs: LabwareDefinition2[] } -export function getItemLabwareDef({ +export const getItemLabwareDef = ({ labwareId, loadedLabware, labwareDefs, -}: GetLabwareDefsForLPCParams): LabwareDefinition2 | null { +}: GetLabwareDefsForLPCParams): LabwareDefinition2 | null => { const labwareDefUri = loadedLabware.find(l => l.id === labwareId)?.definitionUri ?? null @@ -27,3 +31,53 @@ export function getItemLabwareDef({ labwareDefs.find(def => getLabwareDefURI(def) === labwareDefUri) ?? null ) } + +export const getSelectedLabwareOffsetDetails = ( + runId: string, + state: State +): OffsetDetails | null => { + const selectedLabware = + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware + const offsetDetails = + state.protocolRuns[runId]?.lpc?.labwareInfo.labware[ + selectedLabware?.uri ?? '' + ].offsetDetails + + return ( + offsetDetails?.find(offset => + isEqual(offset.workingOffset, selectedLabware?.locationDetails) + ) ?? null + ) +} + +export const getItemLabwareDefFrom = ( + runId: string, + state: State +): LabwareDefinition2 | null => { + const selectedLabware = + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware + const labwareDefs = state?.protocolRuns[runId]?.lpc?.labwareDefs + const analysis = state?.protocolRuns[runId]?.lpc?.protocolData + + if (selectedLabware == null || labwareDefs == null || analysis == null) { + console.warn('No selected labware or store not properly initialized.') + return null + } else { + return getItemLabwareDef({ + labwareId: selectedLabware.id, + labwareDefs, + loadedLabware: analysis.labware, + }) + } +} + +export const getOffsetDetailsForAllLabware = ( + runId: string, + state: State +): OffsetDetails[] => { + const labware = state?.protocolRuns[runId]?.lpc?.labwareInfo.labware ?? {} + + return Object(labware).values( + (details: LabwareDetails) => details.offsetDetails + ) +} diff --git a/app/src/redux/protocol-runs/types/lpc.ts b/app/src/redux/protocol-runs/types/lpc.ts index aec42e6eb98..a5c3db84476 100644 --- a/app/src/redux/protocol-runs/types/lpc.ts +++ b/app/src/redux/protocol-runs/types/lpc.ts @@ -1,38 +1,78 @@ import type { DeckConfiguration, + ModuleModel, LabwareDefinition2, CompletedProtocolAnalysis, } from '@opentrons/shared-data' -import type { - LabwareOffsetLocation, - VectorOffset, - LabwareOffset, -} from '@opentrons/api-client' +import type { VectorOffset } from '@opentrons/api-client' +import type { LPC_STEP } from '/app/redux/protocol-runs' -// TODO(jh, 01-16-25): Make sure there's no cross importing after `steps` is refactored. -// eslint-disable-next-line opentrons/no-imports-across-applications -import type { StepsInfo } from '/app/organisms/LabwarePositionCheck/redux/types' +type LabwareURI = string +type LabwareId = string -export interface PositionParams { - labwareId: string - location: LabwareOffsetLocation - position: VectorOffset | null +export type LPCStep = keyof typeof LPC_STEP + +export interface StepInfo { + currentStepIndex: number + totalStepCount: number + all: LPCStep[] +} + +export interface ExistingOffset { + createdAt: string + vector: VectorOffset } export interface WorkingOffset { - labwareId: string - location: LabwareOffsetLocation initialPosition: VectorOffset | null finalPosition: VectorOffset | null } +export interface PositionParams { + labwareUri: string + location: LPCLabwareLocationDetails + position: VectorOffset | null +} + +export interface LPCLabwareLocationDetails { + labwareId: string + definitionUri: string + slotName: string + moduleModel?: ModuleModel + moduleId?: string + adapterId?: string +} + +// TODO(jh, 01-23-25): Revisit working/existing/initialOffsets once API rework becomes more finalized. +export interface OffsetDetails { + existingOffset: ExistingOffset + workingOffset: WorkingOffset | null + locationDetails: LPCLabwareLocationDetails +} + +export interface LabwareDetails { + id: LabwareId + offsetDetails: OffsetDetails[] +} + +export interface SelectedLabwareInfo { + uri: LabwareURI + id: LabwareId + locationDetails: LPCLabwareLocationDetails +} + +export interface LPCLabwareInfo { + selectedLabware: SelectedLabwareInfo | null + labware: Record +} + export interface LPCWizardState { - workingOffsets: WorkingOffset[] + steps: StepInfo + activePipetteId: string + labwareInfo: LPCLabwareInfo protocolData: CompletedProtocolAnalysis labwareDefs: LabwareDefinition2[] deckConfig: DeckConfiguration - steps: StepsInfo - existingOffsets: LabwareOffset[] protocolName: string maintenanceRunId: string } @@ -52,6 +92,25 @@ export interface ProceedStepAction { payload: { runId: string } } +export interface GoBackStepAction { + type: 'GO_BACK_STEP' + payload: { runId: string } +} + +export interface SelectedLabwareAction { + type: 'SET_SELECTED_LABWARE' + payload: { + runId: string + labwareUri: LabwareURI + location: LPCLabwareLocationDetails + } +} + +export interface ClearSelectedLabwareAction { + type: 'CLEAR_SELECTED_LABWARE' + payload: { runId: string } +} + export interface InitialPositionAction { type: 'SET_INITIAL_POSITION' payload: PositionParams & { runId: string } @@ -65,6 +124,8 @@ export interface FinalPositionAction { export type LPCWizardAction = | StartLPCAction | FinishLPCAction + | SelectedLabwareAction | InitialPositionAction | FinalPositionAction | ProceedStepAction + | GoBackStepAction From 68d11cd38305953255019e8cadd67eedd7f5d508 Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Wed, 29 Jan 2025 16:49:16 -0500 Subject: [PATCH 2/6] refactor(app): enable view support for labware selection Roughly implements the new steps and labware restructuring from the previous commit. There are currently no default offsets. Everything is a labware specific offset. --- .../en/labware_position_check.json | 29 ++-- .../getLPCLabwareInfoFrom.ts | 92 ++++++++++ ...s => getUniqueLabwareLocationComboInfo.ts} | 24 +-- .../LPCFlows/hooks/useLPCLabwareInfo/index.ts | 80 ++------- .../LabwarePositionCheck/LPCWizardFlex.tsx | 10 +- .../hooks/useLPCCommands/commands/labware.ts | 14 +- .../hooks/useLPCCommands/commands/modules.ts | 32 ++-- .../hooks/useLPCCommands/commands/pipettes.ts | 10 +- .../useHandleConfirmLwFinalPosition.ts | 24 +-- .../useHandleConfirmLwModulePlacement.ts | 45 ++--- .../hooks/useLPCCommands/useHandleJog.ts | 14 +- .../useLPCCommands/useHandlePrepModules.ts | 48 ++--- .../useLPCCommands/useHandleProbeCommands.ts | 50 +++--- .../useHandleResetLwModulesOnDeck.ts | 13 +- .../hooks/useLPCCommands/useHandleStartLPC.ts | 17 +- ...useHandleValidMoveToMaintenancePosition.ts | 21 +-- .../steps/AttachProbe.tsx | 41 ++--- .../steps/BeforeBeginning/index.tsx | 6 +- .../steps/DetachProbe.tsx | 34 ++-- .../index.tsx => HandleLabware/CheckItem.tsx} | 76 ++++---- .../EditOffset}/LiveOffsetValue.tsx | 7 +- .../EditOffset}/index.tsx | 53 ++---- .../AppliedLocationOffsetsContainer.tsx | 133 ++++++++++++++ .../DefaultLocationOffset.tsx | 60 +++++++ .../HandleLabware/LPCLabwareDetails/index.tsx | 16 ++ .../HandleLabware/LPCLabwareList/index.tsx | 64 +++++++ .../PlaceItemInstruction.tsx | 25 ++- .../PrepareSpace.tsx | 36 ++-- .../steps/HandleLabware/contants.ts | 12 ++ .../steps/HandleLabware/index.tsx | 33 ++++ .../steps/LPCComplete/index.tsx | 13 ++ .../steps/ResultsSummary/OffsetTable.tsx | 147 ---------------- .../steps/ResultsSummary/TableComponent.tsx | 38 ---- .../steps/ResultsSummary/index.tsx | 164 ------------------ .../LabwarePositionCheck/steps/index.ts | 4 +- .../LegacyApplyHistoricOffsets/index.tsx | 2 +- app/src/redux/protocol-runs/actions/lpc.ts | 17 +- .../protocol-runs/constants/lpc/actions.ts | 1 + app/src/redux/protocol-runs/reducer/lpc.ts | 33 +++- .../protocol-runs/selectors/lpc/labware.ts | 110 ++++++++++-- .../protocol-runs/selectors/lpc/transforms.ts | 4 +- app/src/redux/protocol-runs/types/lpc.ts | 43 ++++- 42 files changed, 882 insertions(+), 813 deletions(-) create mode 100644 app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts rename app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/{utils.ts => getUniqueLabwareLocationComboInfo.ts} (68%) rename app/src/organisms/LabwarePositionCheck/steps/{CheckItem/index.tsx => HandleLabware/CheckItem.tsx} (73%) rename app/src/organisms/LabwarePositionCheck/steps/{CheckItem/JogToWell => HandleLabware/EditOffset}/LiveOffsetValue.tsx (92%) rename app/src/organisms/LabwarePositionCheck/steps/{CheckItem/JogToWell => HandleLabware/EditOffset}/index.tsx (87%) create mode 100644 app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx create mode 100644 app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx create mode 100644 app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/index.tsx create mode 100644 app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx rename app/src/organisms/LabwarePositionCheck/steps/{CheckItem => HandleLabware}/PlaceItemInstruction.tsx (77%) rename app/src/organisms/LabwarePositionCheck/steps/{CheckItem => HandleLabware}/PrepareSpace.tsx (81%) create mode 100644 app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts create mode 100644 app/src/organisms/LabwarePositionCheck/steps/HandleLabware/index.tsx create mode 100644 app/src/organisms/LabwarePositionCheck/steps/LPCComplete/index.tsx delete mode 100644 app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/OffsetTable.tsx delete mode 100644 app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/TableComponent.tsx delete mode 100644 app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx diff --git a/app/src/assets/localization/en/labware_position_check.json b/app/src/assets/localization/en/labware_position_check.json index 4072826650a..7cdd8cce2d1 100644 --- a/app/src/assets/localization/en/labware_position_check.json +++ b/app/src/assets/localization/en/labware_position_check.json @@ -2,7 +2,9 @@ "adapter_in_mod_in_slot": "{{adapter}} in {{module}} in {{slot}}", "adapter_in_slot": "{{adapter}} in {{slot}}", "adapter_in_tc": "{{adapter}} in {{module}}", + "add": "Add", "all_modules_and_labware_from_protocol": "All modules and labware used in the protocol {{protocol_name}}", + "applied_location_offsets": "Applied Location Offsets", "applied_offset_data": "Applied Labware Offset data", "apply_offset_data": "Apply labware offset data", "apply_offsets": "apply offsets", @@ -24,9 +26,10 @@ "confirm_position_and_move": "Confirm position, move to slot {{next_slot}}", "confirm_position_and_pick_up_tip": "Confirm position, pick up tip", "confirm_position_and_return_tip": "Confirm position, return tip to Slot {{next_slot}} and home", + "default_labware_offset": "Default Labware Offset", "detach_probe": "Remove calibration probe", - "ensure_nozzle_position_odd": "Ensure that the {{tip_type}} is centered above and level with {{item_location}}. If it isn't, tap Move pipette and then jog the pipette until it is properly aligned.", "ensure_nozzle_position_desktop": "Ensure that the {{tip_type}} is centered above and level with {{item_location}}. If it isn't, use the controls below or your keyboard to jog the pipette until it is properly aligned.", + "ensure_nozzle_position_odd": "Ensure that the {{tip_type}} is centered above and level with {{item_location}}. If it isn't, tap Move pipette and then jog the pipette until it is properly aligned.", "exit_screen_confirm_exit": "Exit and discard all labware offsets", "exit_screen_go_back": "Go back to labware position check", "exit_screen_subtitle": "If you exit now, all labware offsets will be discarded. This cannot be undone.", @@ -35,9 +38,10 @@ "install_probe": "Take the calibration probe from its storage location. Ensure its collar is fully unlocked. Push the pipette ejector up and press the probe firmly onto the {{location}} pipette nozzle as far as it can go. Twist the collar to lock the probe. Test that the probe is secure by gently pulling it back and forth.", "jog_controls_adjustment": "Need to make an adjustment?", "jupyter_notebook": "Jupyter Notebook", + "labware": "labware", "labware_display_location_text": "Deck Slot {{slot}}", - "labware_offset_data": "labware offset data", "labware_offset": "Labware Offset", + "labware_offset_data": "labware offset data", "labware_offsets_deleted_warning": "Once you begin Labware Position Check, previously created Labware Offsets will be discarded.", "labware_offsets_summary_labware": "Labware", "labware_offsets_summary_location": "Location", @@ -46,23 +50,23 @@ "labware_position_check_description": "Labware Position Check is a guided workflow that checks every labware on the deck for an added degree of precision in your protocol.Labware Position Check first checks tip racks, and then checks all other labware used in your protocol.", "labware_position_check_overview": "Labware Position Check Overview", "labware_position_check_title": "Labware Position Check", - "labware_step_detail_labware_plural": "The tips should be centered above column 1 in {{labware_name}} and level with the top of the labware.", "labware_step_detail_labware": "The tip should be centered above A1 in {{labware_name}} and level with the top of the labware.", + "labware_step_detail_labware_plural": "The tips should be centered above column 1 in {{labware_name}} and level with the top of the labware.", "labware_step_detail_link": "See how to tell if the pipette is centered", "labware_step_detail_modal_heading": "How to tell if the pipette is centered and level", + "labware_step_detail_modal_nozzle": "To ensure that the nozzle is centered, check from a second side of your OT-2.", "labware_step_detail_modal_nozzle_image_1_text": "Viewed from front, it appears centered...", "labware_step_detail_modal_nozzle_image_2_nozzle_text": "Nozzle is not centered", "labware_step_detail_modal_nozzle_image_2_text": "...but viewed from side, it requires adjustment", + "labware_step_detail_modal_nozzle_or_tip": "To ensure the nozzle or tip is level with the top of the labware, position yourself at eye-level and/or slide a sheet of paper between the nozzle and tip.", "labware_step_detail_modal_nozzle_or_tip_image_1_text": "Viewed from standing height, it appears level...", "labware_step_detail_modal_nozzle_or_tip_image_2_nozzle_text": "Nozzle is not level", "labware_step_detail_modal_nozzle_or_tip_image_2_text": "... but viewed from eye-level, it requires adjustment", "labware_step_detail_modal_nozzle_or_tip_image_3_text": "If you’re having trouble, slide 1 sheet of printer paper between the nozzle and the tip. A single piece of paper should barely pass between them.", - "labware_step_detail_modal_nozzle_or_tip": "To ensure the nozzle or tip is level with the top of the labware, position yourself at eye-level and/or slide a sheet of paper between the nozzle and tip.", - "labware_step_detail_modal_nozzle": "To ensure that the nozzle is centered, check from a second side of your OT-2.", - "labware_step_detail_tiprack_plural": "The pipette nozzles should be centered above column 1 in {{tiprack_name}} and level with the top of the tips.", "labware_step_detail_tiprack": "The pipette nozzle should be centered above A1 in {{tiprack_name}} and level with the top of the tip.", - "labware": "labware", + "labware_step_detail_tiprack_plural": "The pipette nozzles should be centered above column 1 in {{tiprack_name}} and level with the top of the tips.", "learn_more": "Learn more", + "legacy_no_offset_data": "No offset data available", "location": "location", "lpc_complete_summary_screen_heading": "Labware Position Check Complete", "module_display_location_text": "{{moduleName}} in Deck Slot {{slot}}", @@ -73,10 +77,10 @@ "new_labware_offset_data": "New labware offset data", "ninety_six_probe_location": "A1 (back left corner)", "no_labware_offsets": "No Labware Offset", + "no_offset_data": "No offset data", "no_offset_data_available": "No labware offset data available", "no_offset_data_on_robot": "This robot has no useable labware offset data for this run.", - "no_offset_data": "No offset data available", - "offsets": "offsets", + "offsets": "Offsets", "pick_up_tip_from_rack_in_location": "Pick up tip from tip rack in {{location}}", "picking_up_tip_title": "Picking up tip in slot {{slot}}", "pipette_nozzle": "pipette nozzle furthest from you", @@ -98,13 +102,14 @@ "robot_has_no_offsets_from_previous_runs": "Labware offset data references previous protocol run labware locations to save you time. If all the labware in this protocol have been checked in previous runs, that data will be applied to this run. You can add new offsets with Labware Position Check in later steps.", "robot_has_offsets_from_previous_runs": "This robot has offsets for labware used in this protocol. If you apply these offsets, you can still adjust them with Labware Position Check.", "robot_in_motion": "Stand back, robot is in motion.", - "run_labware_position_check": "run labware position check", "run": "Run", + "run_labware_position_check": "run labware position check", "secondary_pipette_tipracks_section": "Check tip racks with {{secondary_mount}} Pipette", "see_how_offsets_work": "See how labware offsets work", - "slot_location": "slot location", - "slot_name": "slot {{slotName}}", + "select_labware_from_list": "Select a labware from the list to check its stored offset data", "slot": "Slot {{slotName}}", + "slot_location": "Slot Location", + "slot_name": "slot {{slotName}}", "start_position_check": "begin labware position check, move to Slot {{initial_labware_slot}}", "stored_offset_data": "Apply Stored Labware Offset Data?", "stored_offsets_for_this_protocol": "Stored Labware Offset data that applies to this protocol", diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts new file mode 100644 index 00000000000..7060eaa547f --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts @@ -0,0 +1,92 @@ +import isEqual from 'lodash/isEqual' + +import { getLabwareDisplayName, getLabwareDefURI } from '@opentrons/shared-data' + +import type { LabwareDefinition2 } from '@opentrons/shared-data' +import type { LPCLabwareInfo, OffsetDetails } from '/app/redux/protocol-runs' +import type { LabwareLocationCombo } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' +import type { UseLPCLabwareInfoProps } from '.' + +interface GetLPCLabwareInfoParams { + lwURIs: string[] + currentOffsets: UseLPCLabwareInfoProps['currentOffsets'] + lwLocationCombos: LabwareLocationCombo[] + labwareDefs: UseLPCLabwareInfoProps['labwareDefs'] +} + +export function getLPCLabwareInfoFrom( + params: GetLPCLabwareInfoParams +): LPCLabwareInfo { + return { selectedLabware: null, labware: getLabwareInfoRecords(params) } +} + +function getLabwareInfoRecords( + params: GetLPCLabwareInfoParams +): LPCLabwareInfo['labware'] { + const labwareDetails: LPCLabwareInfo['labware'] = {} + + params.lwURIs.forEach(uri => { + if (!(uri in labwareDetails)) { + labwareDetails[uri] = { + id: getALabwareIdFromUri({ ...params, uri }), + displayName: getDisplayNameFromUri({ ...params, uri }), + offsetDetails: getOffsetDetailsForLabware({ ...params, uri }), + } + } + }) + + return labwareDetails +} + +type GetLPCLabwareInfoForURI = Omit & { + uri: string +} + +function getALabwareIdFromUri({ + uri, + lwLocationCombos, +}: GetLPCLabwareInfoForURI): string { + return ( + lwLocationCombos.find(combo => combo.definitionUri === uri)?.labwareId ?? '' + ) +} + +function getDisplayNameFromUri({ + uri, + labwareDefs, +}: GetLPCLabwareInfoForURI): string { + const matchedDef = labwareDefs?.find( + def => getLabwareDefURI(def) === uri + ) as LabwareDefinition2 + + return getLabwareDisplayName(matchedDef) +} + +// NOTE: A lot of the logic here acts as temporary adapter that resolves the app's current way of getting offset data (scraping the run record) +// and the end goal of treating labware as first class citizens. +function getOffsetDetailsForLabware({ + currentOffsets, + lwLocationCombos, + uri, +}: GetLPCLabwareInfoForURI): OffsetDetails[] { + return lwLocationCombos.flatMap(comboInfo => { + const { definitionUri, location, ...restInfo } = comboInfo + + const existingOffset = + currentOffsets.find( + offset => + uri === offset.definitionUri && + isEqual(offset.location, comboInfo.location) + ) ?? null + + return { + existingOffset: existingOffset ?? null, + workingOffset: null, + locationDetails: { + ...location, + ...restInfo, + definitionUri, + }, + } + }) +} diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/utils.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getUniqueLabwareLocationComboInfo.ts similarity index 68% rename from app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/utils.ts rename to app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getUniqueLabwareLocationComboInfo.ts index 0558b5f6601..36339823640 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/utils.ts +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getUniqueLabwareLocationComboInfo.ts @@ -1,37 +1,15 @@ import { isEqual } from 'lodash' -import { getLabwareDefURI, getPipetteNameSpecs } from '@opentrons/shared-data' +import { getLabwareDefURI } from '@opentrons/shared-data' -import { STEP } from '/app/organisms/LabwarePositionCheck/constants' import { getLabwareLocationCombos } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' import type { CompletedProtocolAnalysis, LabwareDefinition2, - LoadedPipette, } from '@opentrons/shared-data' -import type { CheckPositionsStep } from '/app/organisms/LabwarePositionCheck/types' import type { LabwareLocationCombo } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' -// export function getProbeBasedLPCSteps( -// params: GetLPCStepsParams -// ): LabwarePositionCheckStep[] { -// const { protocolData } = params -// -// return [ -// { section: NAV_STEPS.BEFORE_BEGINNING }, -// { -// section: NAV_STEPS.ATTACH_PROBE, -// pipetteId: getPrimaryPipetteId(protocolData.pipettes), -// }, -// ...getUniqueLabwareLocationComboInfo(params), -// { -// section: NAV_STEPS.DETACH_PROBE, -// pipetteId: getPrimaryPipetteId(protocolData.pipettes), -// }, -// { section: NAV_STEPS.RESULTS_SUMMARY }, -// ] -// } export interface GetUniqueLocationComboInfoParams { protocolData: CompletedProtocolAnalysis | null labwareDefs: LabwareDefinition2[] | null diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts index a6ca76618b0..56e4bbaf697 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/index.ts @@ -1,16 +1,16 @@ import { useMemo } from 'react' -import isEqual from 'lodash/isEqual' -import { getUniqueLabwareLocationComboInfo } from './utils' +import { getUniqueLabwareLocationComboInfo } from './getUniqueLabwareLocationComboInfo' +import { getLPCLabwareInfoFrom } from './getLPCLabwareInfoFrom' import type { LabwareOffset } from '@opentrons/api-client' import type { LPCLabwareInfo } from '/app/redux/protocol-runs' -import type { GetUniqueLocationComboInfoParams } from './utils' -import type { LabwareLocationCombo } from '/app/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos' +import type { GetUniqueLocationComboInfoParams } from './getUniqueLabwareLocationComboInfo' -type UseLPCLabwareInfoProps = GetUniqueLocationComboInfoParams & { +export type UseLPCLabwareInfoProps = GetUniqueLocationComboInfoParams & { currentOffsets: LabwareOffset[] } + // TODO(jh, 01-22-25): This interface will change substantially the switch to /labwareOffsets. // Structures LPC-able labware info for injection into LPC flows. @@ -19,6 +19,8 @@ export function useLPCLabwareInfo({ labwareDefs, protocolData, }: UseLPCLabwareInfoProps): LPCLabwareInfo { + // Analysis-derived data is the source of truth, because we must account for labware that has offsets AND account for labware + // that does not have offsets. This will change with the LPC HTTP API refactors. const lwURIs = getLabwareURIsFromAnalysis(protocolData) const lwLocationCombos = useMemo( () => @@ -30,71 +32,19 @@ export function useLPCLabwareInfo({ ) return useMemo( - () => getLPCLabwareInfoFrom(lwURIs, currentOffsets, lwLocationCombos), + () => + getLPCLabwareInfoFrom({ + lwURIs, + currentOffsets, + lwLocationCombos, + labwareDefs, + }), [lwURIs.length, currentOffsets.length, lwLocationCombos.length] ) } -export function getLabwareURIsFromAnalysis( +function getLabwareURIsFromAnalysis( analysis: UseLPCLabwareInfoProps['protocolData'] ): string[] { return analysis?.labware.map(lwInfo => lwInfo.definitionUri) ?? [] } - -// NOTE: This is largely a temporary adapter that resolves the app's current way of getting offset data (scraping the run record) -// and the end goal of treating labware as first class citizens. Most of this code will be replaced -// once the app implements the new /labwareOffsets HTTP API. -export function getLPCLabwareInfoFrom( - lwURIs: string[], - currentOffsets: LabwareOffset[], - lwLocationCombos: LabwareLocationCombo[] -): LPCLabwareInfo { - return Object.fromEntries( - currentOffsets - .filter(offset => lwURIs.includes(offset.definitionUri)) - .map(offsetInfo => { - const { - id: offsetId, - definitionUri, - location, - ...restInfo - } = offsetInfo - - const { moduleId, labwareId, adapterId } = getMatchingLocationCombo( - lwLocationCombos, - offsetInfo - ) ?? { - labwareId: '', - } - - return [ - offsetId, - { - existingOffset: { ...restInfo }, - workingOffset: null, - locationDetails: { - ...location, - labwareId, - moduleId, - adapterId, - definitionUri, - }, - }, - ] - }) - ) -} - -// Get the location combo that matches the info provided from a LabwareOffset. -export function getMatchingLocationCombo( - combos: LabwareLocationCombo[], - offsetInfo: LabwareOffset -): LabwareLocationCombo | null { - return ( - combos.find( - combo => - combo.definitionUri === offsetInfo.definitionUri && - isEqual(combo.location, offsetInfo.location) - ) ?? null - ) -} diff --git a/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx b/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx index e4d6dea692e..11cb64c88e6 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx +++ b/app/src/organisms/LabwarePositionCheck/LPCWizardFlex.tsx @@ -7,10 +7,10 @@ import { ModalShell } from '@opentrons/components' import { getTopPortalEl } from '/app/App/portal' import { BeforeBeginning, - CheckItem, + HandleLabware, AttachProbe, DetachProbe, - ResultsSummary, + LPCComplete, } from '/app/organisms/LabwarePositionCheck/steps' import { ExitConfirmation } from './ExitConfirmation' import { RobotMotionLoader } from './RobotMotionLoader' @@ -151,16 +151,14 @@ function LPCWizardContent(props: LPCWizardContentProps): JSX.Element { case LPC_STEP.ATTACH_PROBE: return - // TOME TODO: This gets entirely rewritten. case LPC_STEP.HANDLE_LABWARE: - return + return case LPC_STEP.DETACH_PROBE: return - // TOME TODO: This gets rewritten, too. case LPC_STEP.LPC_COMPLETE: - return + return default: console.error('Unhandled LPC step.') diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/labware.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/labware.ts index 95cc6db6ccc..fb5b8275cb1 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/labware.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/labware.ts @@ -1,14 +1,10 @@ import type { CreateCommand } from '@opentrons/shared-data' -import type { CheckPositionsStep } from '/app/organisms/LabwarePositionCheck/types' +import type { OffsetLocationDetails } from '/app/redux/protocol-runs' -export interface BuildMoveLabwareOffDeckParams { - step: CheckPositionsStep -} - -export function moveLabwareOffDeckCommands({ - step, -}: BuildMoveLabwareOffDeckParams): CreateCommand[] { - const { adapterId, labwareId } = step +export function moveLabwareOffDeckCommands( + offsetLocationDetails: OffsetLocationDetails +): CreateCommand[] { + const { adapterId, labwareId } = offsetLocationDetails return adapterId != null ? [ diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/modules.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/modules.ts index 6fd4f57c1d3..4982886d5f6 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/modules.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/modules.ts @@ -5,27 +5,23 @@ import { THERMOCYCLER_MODULE_TYPE, } from '@opentrons/shared-data' -import type { CheckPositionsStep } from '/app/organisms/LabwarePositionCheck/types' import type { CompletedProtocolAnalysis, CreateCommand, } from '@opentrons/shared-data' -import type { LabwareOffsetLocation } from '@opentrons/api-client' +import type { OffsetLocationDetails } from '/app/redux/protocol-runs' -export interface BuildModulePrepCommandsParams { - step: CheckPositionsStep -} - -export function modulePrepCommands({ - step, -}: BuildModulePrepCommandsParams): CreateCommand[] { - const { moduleId, location } = step +export function modulePrepCommands( + offsetLocationDetails: OffsetLocationDetails +): CreateCommand[] { + const { moduleId, moduleModel } = offsetLocationDetails const moduleType = (moduleId != null && + moduleModel != null && 'moduleModel' in location && location.moduleModel != null && - getModuleType(location.moduleModel)) ?? + getModuleType(moduleModel)) ?? null if (moduleId == null || moduleType == null) { @@ -79,11 +75,9 @@ export const moduleInitDuringLPCCommands = ( // Not all modules require cleanup after each labware LPC. export const moduleCleanupDuringLPCCommands = ( - step: CheckPositionsStep + offsetLocationDetails: OffsetLocationDetails ): CreateCommand[] => { - const { moduleId, location } = step - - return [...heaterShakerCleanupCommands(moduleId, location)] + return [...heaterShakerCleanupCommands(offsetLocationDetails)] } const heaterShakerInitCommands = ( @@ -127,14 +121,16 @@ const thermocyclerInitCommands = ( } const heaterShakerCleanupCommands = ( - moduleId: string | undefined, - location: LabwareOffsetLocation + offsetLocationDetails: OffsetLocationDetails ): CreateCommand[] => { + const { moduleId, moduleModel } = offsetLocationDetails + const moduleType = (moduleId != null && + moduleModel != null && 'moduleModel' in location && location.moduleModel != null && - getModuleType(location.moduleModel)) ?? + getModuleType(moduleModel)) ?? null return moduleId != null && diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/pipettes.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/pipettes.ts index fba1f7b025f..0bf32bdcbe0 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/pipettes.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands/pipettes.ts @@ -5,8 +5,8 @@ import type { LoadedPipette, MotorAxes, } from '@opentrons/shared-data' -import type { CheckPositionsStep } from '/app/organisms/LabwarePositionCheck/types' import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' +import type { OffsetLocationDetails } from '/app/redux/protocol-runs' const PROBE_LENGTH_MM = 44.5 @@ -15,9 +15,10 @@ export const savePositionCommands = (pipetteId: string): CreateCommand[] => [ ] export const moveToWellCommands = ( - step: CheckPositionsStep + offsetLocationDetails: OffsetLocationDetails, + pipetteId: string ): CreateCommand[] => { - const { pipetteId, labwareId } = step + const { labwareId } = offsetLocationDetails return [ { @@ -115,7 +116,6 @@ export const moveToMaintenancePosition = ( } export const verifyProbeAttachmentAndHomeCommands = ( - pipetteId: string, pipette: LoadedPipette | null ): CreateCommand[] => { const pipetteMount = pipette?.mount @@ -125,7 +125,7 @@ export const verifyProbeAttachmentAndHomeCommands = ( { commandType: 'verifyTipPresence', params: { - pipetteId, + pipetteId: pipette?.id ?? '', expectedState: 'present', followSingularSensor: 'primary', }, diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwFinalPosition.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwFinalPosition.ts index 9c159b4c518..133bab43a8e 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwFinalPosition.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwFinalPosition.ts @@ -11,7 +11,7 @@ import type { CreateCommand, } from '@opentrons/shared-data' import type { UseLPCCommandWithChainRunChildProps } from './types' -import type { BuildMoveLabwareOffDeckParams } from './commands' +import type { OffsetLocationDetails } from '/app/redux/protocol-runs' interface UseHandleConfirmPositionProps extends UseLPCCommandWithChainRunChildProps { @@ -22,10 +22,8 @@ export interface UseHandleConfirmPositionResult { /* Initiate commands to return specific modules to a post-run condition before * non-plunger homing the utilized pipette and saving the LPC position. */ handleConfirmLwFinalPosition: ( - params: BuildMoveLabwareOffDeckParams & { - onSuccess: () => void - pipette: LoadedPipette | null - } + offsetLocationDetails: OffsetLocationDetails, + pipette: LoadedPipette ) => Promise } @@ -34,26 +32,20 @@ export function useHandleConfirmLwFinalPosition({ chainLPCCommands, }: UseHandleConfirmPositionProps): UseHandleConfirmPositionResult { const handleConfirmLwFinalPosition = ( - params: BuildMoveLabwareOffDeckParams & { - onSuccess: () => void - pipette: LoadedPipette | null - } + offsetLocationDetails: OffsetLocationDetails, + pipette: LoadedPipette ): Promise => { - const { onSuccess, pipette, step } = params - const { pipetteId } = step - const confirmCommands: CreateCommand[] = [ - ...savePositionCommands(pipetteId), + ...savePositionCommands(pipette.id), ...retractPipetteAxesSequentiallyCommands(pipette), - ...moduleCleanupDuringLPCCommands(step), - ...moveLabwareOffDeckCommands(params), + ...moduleCleanupDuringLPCCommands(offsetLocationDetails), + ...moveLabwareOffDeckCommands(offsetLocationDetails), ] return chainLPCCommands(confirmCommands, false).then(responses => { const firstResponse = responses[0] if (firstResponse.data.commandType === 'savePosition') { const { position } = firstResponse.data?.result ?? { position: null } - onSuccess() return Promise.resolve(position) } else { diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwModulePlacement.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwModulePlacement.ts index eb58121b6bf..b37763a032f 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwModulePlacement.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleConfirmLwModulePlacement.ts @@ -10,7 +10,7 @@ import type { CreateCommand, } from '@opentrons/shared-data' import type { UseLPCCommandWithChainRunChildProps } from './types' -import type { CheckPositionsStep } from '/app/organisms/LabwarePositionCheck/types' +import type { OffsetLocationDetails } from '/app/redux/protocol-runs' export interface UseHandleConfirmPlacementProps extends UseLPCCommandWithChainRunChildProps { @@ -21,7 +21,8 @@ export interface UseHandleConfirmPlacementResult { /* Initiate commands to finalize pre-protocol run conditions for specific modules before moving the pipette to the initial LPC position. */ handleConfirmLwModulePlacement: ( - params: BuildMoveLabwareCommandParams + offsetLocationDetails: OffsetLocationDetails, + pipetteId: string ) => Promise } @@ -31,14 +32,13 @@ export function useHandleConfirmLwModulePlacement({ setErrorMessage, }: UseHandleConfirmPlacementProps): UseHandleConfirmPlacementResult { const handleConfirmLwModulePlacement = ( - params: BuildMoveLabwareCommandParams + offsetLocationDetails: OffsetLocationDetails, + pipetteId: string ): Promise => { - const { pipetteId } = params.step - const confirmCommands: CreateCommand[] = [ - ...buildMoveLabwareCommand(params), + ...buildMoveLabwareCommand(offsetLocationDetails), ...moduleInitDuringLPCCommands(mostRecentAnalysis), - ...moveToWellCommands(params.step), + ...moveToWellCommands(offsetLocationDetails, pipetteId), ...savePositionCommands(pipetteId), ] @@ -62,17 +62,21 @@ export function useHandleConfirmLwModulePlacement({ return { handleConfirmLwModulePlacement } } -interface BuildMoveLabwareCommandParams { - step: CheckPositionsStep -} +function buildMoveLabwareCommand( + offsetLocationDetails: OffsetLocationDetails +): MoveLabwareCreateCommand[] { + const { labwareId, moduleId, adapterId, slotName } = offsetLocationDetails -function buildMoveLabwareCommand({ - step, -}: BuildMoveLabwareCommandParams): MoveLabwareCreateCommand[] { - const { labwareId, moduleId, adapterId, location } = step + // TODO(jh, 01-29-25): Once default offsets are implemented, we'll have to load them + // into a slot somehow. Talk to Design. + const locationSpecificSlotName = slotName as string - const newLocation = - moduleId != null ? { moduleId } : { slotName: location.slotName } + const newLocationLabware = + moduleId != null ? { moduleId } : { slotName: locationSpecificSlotName } + const newLocationAdapter = + adapterId != null + ? { labwareId: adapterId } + : { slotName: locationSpecificSlotName } if (adapterId != null) { return [ @@ -80,7 +84,7 @@ function buildMoveLabwareCommand({ commandType: 'moveLabware' as const, params: { labwareId: adapterId, - newLocation, + newLocation: newLocationLabware, strategy: 'manualMoveWithoutPause', }, }, @@ -88,10 +92,7 @@ function buildMoveLabwareCommand({ commandType: 'moveLabware' as const, params: { labwareId, - newLocation: - adapterId != null - ? { labwareId: adapterId } - : { slotName: location.slotName }, + newLocation: newLocationAdapter, strategy: 'manualMoveWithoutPause', }, }, @@ -102,7 +103,7 @@ function buildMoveLabwareCommand({ commandType: 'moveLabware' as const, params: { labwareId, - newLocation, + newLocation: newLocationLabware, strategy: 'manualMoveWithoutPause', }, }, diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleJog.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleJog.ts index 9d49cd7835e..d88617262ae 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleJog.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleJog.ts @@ -4,6 +4,7 @@ import { useSelector } from 'react-redux' import { useCreateMaintenanceCommandMutation } from '@opentrons/react-api-client' import { moveRelativeCommand } from './commands' +import { selectActivePipette } from '/app/redux/protocol-runs' import type { Coordinates } from '@opentrons/shared-data' import type { @@ -12,7 +13,6 @@ import type { Sign, StepSize, } from '/app/molecules/JogControls/types' -import type { State } from '/app/redux/types' import type { UseLPCCommandChildProps } from './types' const JOG_COMMAND_TIMEOUT_MS = 10000 @@ -33,11 +33,10 @@ export function useHandleJog({ maintenanceRunId, setErrorMessage, }: UseHandleJogProps): UseHandleJogResult { - const { current: currentStep } = - useSelector((state: State) => state.protocolRuns[runId]?.lpc?.steps) ?? {} - const [isJogging, setIsJogging] = useState(false) const [jogQueue, setJogQueue] = useState Promise>>([]) + const pipette = useSelector(selectActivePipette(runId)) + const pipetteId = pipette?.id const { createMaintenanceCommand: createSilentCommand, } = useCreateMaintenanceCommandMutation() @@ -50,11 +49,6 @@ export function useHandleJog({ onSuccess?: (position: Coordinates | null) => void ): Promise => { return new Promise((resolve, reject) => { - const pipetteId = - currentStep != null && 'pipetteId' in currentStep - ? currentStep.pipetteId - : null - if (pipetteId != null) { createSilentCommand({ maintenanceRunId, @@ -81,7 +75,7 @@ export function useHandleJog({ } }) }, - [currentStep, maintenanceRunId, createSilentCommand, setErrorMessage] + [pipetteId, maintenanceRunId, createSilentCommand, setErrorMessage] ) const processJogQueue = useCallback((): void => { diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts index 5e3064cf043..6e87000b46f 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandlePrepModules.ts @@ -1,56 +1,34 @@ -import { useSelector } from 'react-redux' - import { modulePrepCommands } from './commands' -import { STEP } from '/app/organisms/LabwarePositionCheck/constants' -import { selectActiveLwInitialPosition } from '/app/redux/protocol-runs' +import type { CommandData, VectorOffset } from '@opentrons/api-client' import type { CreateCommand } from '@opentrons/shared-data' import type { UseLPCCommandWithChainRunChildProps } from './types' -import type { LabwarePositionCheckStep } from '/app/organisms/LabwarePositionCheck/types' -import type { CommandData } from '@opentrons/api-client' -import type { State } from '/app/redux/types' +import type { OffsetLocationDetails } from '/app/redux/protocol-runs' export interface UseHandlePrepModulesResult { handleCheckItemsPrepModules: ( - step: LabwarePositionCheckStep | null + offsetLocationDetails: OffsetLocationDetails, + initialPosition: VectorOffset | null ) => Promise } // Prep module(s) before LPCing a specific labware involving module(s). export function useHandlePrepModules({ - runId, chainLPCCommands, }: UseLPCCommandWithChainRunChildProps): UseHandlePrepModulesResult { - const selectInitialPositionFrom = useSelector( - (state: State) => (step: LabwarePositionCheckStep | null) => - selectActiveLwInitialPosition(step, runId, state) - ) - const handleCheckItemsPrepModules = ( - step: LabwarePositionCheckStep | null + offsetLocationDetails: OffsetLocationDetails, + initialPosition: VectorOffset | null ): Promise => { - const initialPosition = selectInitialPositionFrom(step) - - if (step?.section === STEP.CHECK_POSITIONS) { - const prepCommands: CreateCommand[] = modulePrepCommands({ - step, - }) + const prepCommands: CreateCommand[] = modulePrepCommands( + offsetLocationDetails + ) - if ( - initialPosition == null && - // Only run these commands during the appropriate step. - step.section === STEP.CHECK_POSITIONS && - prepCommands.length > 0 - ) { - return chainLPCCommands(prepCommands, false) - } else { - return Promise.resolve([]) - } + if (initialPosition == null && prepCommands.length > 0) { + return chainLPCCommands(prepCommands, false) + } else { + return Promise.resolve([]) } - - return Promise.reject( - new Error(`Cannot prep modules during unsupported step: ${step?.section}`) - ) } return { handleCheckItemsPrepModules } diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleProbeCommands.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleProbeCommands.ts index 8fe77d5c60a..9c3abf18996 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleProbeCommands.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleProbeCommands.ts @@ -9,15 +9,14 @@ import type { CreateCommand, LoadedPipette } from '@opentrons/shared-data' import type { UseLPCCommandWithChainRunChildProps } from './types' export interface UseProbeCommandsResult { - createProbeAttachmentHandler: ( - pipetteId: string, + handleProbeAttachment: ( pipette: LoadedPipette | null, onSuccess: () => void - ) => () => Promise - createProbeDetachmentHandler: ( + ) => Promise + handleProbeDetachment: ( pipette: LoadedPipette | null, onSuccess: () => void - ) => () => Promise + ) => Promise unableToDetect: boolean setShowUnableToDetect: (canDetect: boolean) => void } @@ -27,45 +26,42 @@ export function useHandleProbeCommands({ }: UseLPCCommandWithChainRunChildProps): UseProbeCommandsResult { const [showUnableToDetect, setShowUnableToDetect] = useState(false) - const createProbeAttachmentHandler = ( - pipetteId: string, + const handleProbeAttachment = ( pipette: LoadedPipette | null, onSuccess: () => void - ): (() => Promise) => { + ): Promise => { const attachmentCommands: CreateCommand[] = [ - ...verifyProbeAttachmentAndHomeCommands(pipetteId, pipette), + ...verifyProbeAttachmentAndHomeCommands(pipette), ] - return () => - chainLPCCommands(attachmentCommands, false, true) - .catch(() => { - setShowUnableToDetect(true) - return Promise.reject(new Error('Unable to detect probe.')) - }) - .then(() => { - setShowUnableToDetect(false) - onSuccess() - }) + return chainLPCCommands(attachmentCommands, false, true) + .catch(() => { + setShowUnableToDetect(true) + return Promise.reject(new Error('Unable to detect probe.')) + }) + .then(() => { + setShowUnableToDetect(false) + onSuccess() + }) } - const createProbeDetachmentHandler = ( + const handleProbeDetachment = ( pipette: LoadedPipette | null, onSuccess: () => void - ): (() => Promise) => { + ): Promise => { const detatchmentCommands: CreateCommand[] = [ ...retractPipetteAxesSequentiallyCommands(pipette), ] - return () => - chainLPCCommands(detatchmentCommands, false).then(() => { - onSuccess() - }) + return chainLPCCommands(detatchmentCommands, false).then(() => { + onSuccess() + }) } return { - createProbeAttachmentHandler, + handleProbeAttachment, unableToDetect: showUnableToDetect, setShowUnableToDetect, - createProbeDetachmentHandler, + handleProbeDetachment, } } diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleResetLwModulesOnDeck.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleResetLwModulesOnDeck.ts index b64965e0cc9..0aa61672967 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleResetLwModulesOnDeck.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleResetLwModulesOnDeck.ts @@ -6,14 +6,11 @@ import { import type { CreateCommand } from '@opentrons/shared-data' import type { UseLPCCommandWithChainRunChildProps } from './types' -import type { - BuildMoveLabwareOffDeckParams, - BuildModulePrepCommandsParams, -} from './commands' +import type { OffsetLocationDetails } from '/app/redux/protocol-runs' export interface UseHandleResetLwModulesOnDeckResult { handleResetLwModulesOnDeck: ( - params: BuildModulePrepCommandsParams & BuildMoveLabwareOffDeckParams + offsetLocationDetails: OffsetLocationDetails ) => Promise } @@ -21,12 +18,12 @@ export function useHandleResetLwModulesOnDeck({ chainLPCCommands, }: UseLPCCommandWithChainRunChildProps): UseHandleResetLwModulesOnDeckResult { const handleResetLwModulesOnDeck = ( - params: BuildModulePrepCommandsParams & BuildMoveLabwareOffDeckParams + offsetLocationDetails: OffsetLocationDetails ): Promise => { const resetCommands: CreateCommand[] = [ - ...modulePrepCommands(params), + ...modulePrepCommands(offsetLocationDetails), ...fullHomeCommands(), - ...moveLabwareOffDeckCommands(params as BuildMoveLabwareOffDeckParams), + ...moveLabwareOffDeckCommands(offsetLocationDetails), ] return chainLPCCommands(resetCommands, false).then(() => Promise.resolve()) diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleStartLPC.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleStartLPC.ts index df3270555f6..382870b33bc 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleStartLPC.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleStartLPC.ts @@ -14,20 +14,20 @@ import type { import type { UseLPCCommandWithChainRunChildProps } from './types' export interface UseHandleStartLPCResult { - createStartLPCHandler: ( + handleStartLPC: ( pipette: LoadedPipette | null, onSuccess: () => void - ) => () => Promise + ) => Promise } export function useHandleStartLPC({ chainLPCCommands, mostRecentAnalysis, }: UseLPCCommandWithChainRunChildProps): UseHandleStartLPCResult { - const createStartLPCHandler = ( + const handleStartLPC = ( pipette: LoadedPipette | null, onSuccess: () => void - ): (() => Promise) => { + ): Promise => { const startCommands: CreateCommand[] = [ ...buildInstrumentLabwarePrepCommands(mostRecentAnalysis), ...moduleInitBeforeAnyLPCCommands(mostRecentAnalysis), @@ -35,13 +35,12 @@ export function useHandleStartLPC({ ...moveToMaintenancePosition(pipette), ] - return () => - chainLPCCommands(startCommands, false).then(() => { - onSuccess() - }) + return chainLPCCommands(startCommands, false).then(() => { + onSuccess() + }) } - return { createStartLPCHandler } + return { handleStartLPC } } // Load all pipettes and labware into the maintenance run by utilizing the protocol resource. diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts index 52f3e8031f9..f549d860984 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCCommands/useHandleValidMoveToMaintenancePosition.ts @@ -1,16 +1,13 @@ import { moveToMaintenancePosition } from '/app/organisms/LabwarePositionCheck/hooks/useLPCCommands/commands' -import { STEP } from '/app/organisms/LabwarePositionCheck/constants' import type { CommandData } from '@opentrons/api-client' import type { LoadedPipette } from '@opentrons/shared-data' -import type { LabwarePositionCheckStep } from '/app/organisms/LabwarePositionCheck/types' import type { UseLPCCommandWithChainRunChildProps } from '/app/organisms/LabwarePositionCheck/hooks/useLPCCommands/types' export interface UseHandleValidMoveToMaintenancePositionResult { /* Only move to maintenance position during probe steps. */ handleValidMoveToMaintenancePosition: ( - pipette: LoadedPipette | null, - step: LabwarePositionCheckStep | null + pipette: LoadedPipette | null ) => Promise } @@ -19,21 +16,9 @@ export function useHandleValidMoveToMaintenancePosition({ }: UseLPCCommandWithChainRunChildProps): UseHandleValidMoveToMaintenancePositionResult { return { handleValidMoveToMaintenancePosition: ( - pipette: LoadedPipette | null, - step: LabwarePositionCheckStep | null + pipette: LoadedPipette | null ): Promise => { - if ( - step?.section === STEP.ATTACH_PROBE || - step?.section === STEP.DETACH_PROBE - ) { - return chainLPCCommands(moveToMaintenancePosition(pipette), false) - } else { - return Promise.reject( - new Error( - `Does not move to maintenance position if step is not a probe step. Step: ${step?.section}` - ) - ) - } + return chainLPCCommands(moveToMaintenancePosition(pipette), false) }, } } diff --git a/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx b/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx index 5ff197f899d..a428308e2d2 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx @@ -12,8 +12,9 @@ import { import { ProbeNotAttached } from '/app/organisms/PipetteWizardFlows/ProbeNotAttached' import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { + type OffsetLocationDetails, selectActivePipette, - selectActivePipetteChannelCount, + selectActivePipetteChannelCount, type SelectedLabwareInfo, selectSelectedLabwareInfo, selectSelectedLwInitialPosition, } from '/app/redux/protocol-runs' import { getIsOnDevice } from '/app/redux/config' @@ -21,9 +22,7 @@ import attachProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach import attachProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_8.webm' import attachProbe96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_96.webm' -import type { AttachProbeStep, LPCStepProps } from '../types' -import type { State } from '/app/redux/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' const StyledVideo = styled.video` padding-top: ${SPACING.spacing4}; @@ -44,33 +43,23 @@ export function AttachProbe({ runId, proceed, commandUtils, - step, -}: LPCStepProps): JSX.Element { +}: LPCWizardContentProps): JSX.Element { const { t, i18n } = useTranslation(['labware_position_check', 'shared']) const isOnDevice = useSelector(getIsOnDevice) - const { steps } = useSelector( - (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState - ) - const { pipetteId } = step + const pipette = useSelector(selectActivePipette(runId)) const { - createProbeAttachmentHandler, + handleProbeAttachment, handleCheckItemsPrepModules, toggleRobotMoving, setShowUnableToDetect, unableToDetect, } = commandUtils - const pipette = useSelector((state: State) => - selectActivePipette(step, runId, state) - ) - const channels = useSelector((state: State) => - selectActivePipetteChannelCount(step, runId, state) - ) - - const handleProbeAttached = createProbeAttachmentHandler( - pipetteId, - pipette, - proceed - ) + const channels = useSelector(selectActivePipetteChannelCount(runId)) + const initialPosition = useSelector(selectSelectedLwInitialPosition(runId)) + const lwInfo = useSelector( + selectSelectedLabwareInfo(runId) + ) as SelectedLabwareInfo + const offsetLocationDetails = lwInfo.offsetLocationDetails as OffsetLocationDetails const { probeLocation, probeVideoSrc } = ((): { probeLocation: string @@ -91,14 +80,14 @@ export function AttachProbe({ const handleProbeCheck = (): void => { void toggleRobotMoving(true) - .then(() => handleProbeAttached()) + .then(() => handleProbeAttachment(pipette, proceed)) .finally(() => toggleRobotMoving(false)) } const handleProceed = (): void => { void toggleRobotMoving(true) - .then(() => handleProbeAttached()) - .then(() => handleCheckItemsPrepModules(steps.next)) + .then(() => handleProbeAttachment(pipette, proceed)) + .then(() => handleCheckItemsPrepModules(offsetLocationDetails, initialPosition)) .finally(() => toggleRobotMoving(false)) } diff --git a/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx index 592a75ba839..8050347eeb4 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx @@ -38,9 +38,7 @@ export function BeforeBeginning({ const { protocolName, labwareDefs } = useSelector( (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState ) - const { createStartLPCHandler, toggleRobotMoving } = commandUtils - - const handleStartLPC = createStartLPCHandler(activePipette, proceed) + const { handleStartLPC, toggleRobotMoving } = commandUtils const requiredEquipmentList = [ { @@ -55,7 +53,7 @@ export function BeforeBeginning({ const handleProceed = (): void => { void toggleRobotMoving(true) - .then(() => handleStartLPC()) + .then(() => handleStartLPC(activePipette, proceed)) .finally(() => toggleRobotMoving(false)) } diff --git a/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx b/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx index 1659fe375db..3042cc39c36 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { useSelector } from 'react-redux' @@ -19,9 +20,7 @@ import detachProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach import detachProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_8.webm' import detachProbe96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_96.webm' -import type { DetachProbeStep, LPCStepProps } from '../types' -import type { State } from '/app/redux/types' -import type { StepsInfo } from '/app/organisms/LabwarePositionCheck/redux/types' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' const StyledVideo = styled.video` padding-top: ${SPACING.spacing4}; @@ -42,18 +41,21 @@ export const DetachProbe = ({ runId, proceed, commandUtils, -}: LPCStepProps): JSX.Element => { +}: LPCWizardContentProps): JSX.Element => { const { t, i18n } = useTranslation(['labware_position_check', 'shared']) - const { current: currentStep } = useSelector( - (state: State) => state.protocolRuns[runId]?.lpc?.steps as StepsInfo - ) - const { createProbeDetachmentHandler, toggleRobotMoving } = commandUtils - const pipette = useSelector((state: State) => - selectActivePipette(currentStep, runId, state) - ) - const channels = useSelector((state: State) => - selectActivePipetteChannelCount(currentStep, runId, state) - ) + const { + handleProbeDetachment, + toggleRobotMoving, + handleValidMoveToMaintenancePosition, + } = commandUtils + const pipette = useSelector(selectActivePipette(runId)) + const channels = useSelector(selectActivePipetteChannelCount(runId)) + + useEffect(() => { + void toggleRobotMoving(true) + .then(() => handleValidMoveToMaintenancePosition(pipette)) + .finally(() => toggleRobotMoving(false)) + }, []) const probeVideoSrc = ((): string => { switch (channels) { @@ -66,11 +68,9 @@ export const DetachProbe = ({ } })() - const handleProbeDetached = createProbeDetachmentHandler(pipette, proceed) - const handleProceed = (): void => { void toggleRobotMoving(true) - .then(() => handleProbeDetached()) + .then(() => handleProbeDetachment(pipette, proceed)) .finally(() => toggleRobotMoving(false)) } diff --git a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/CheckItem.tsx similarity index 73% rename from app/src/organisms/LabwarePositionCheck/steps/CheckItem/index.tsx rename to app/src/organisms/LabwarePositionCheck/steps/HandleLabware/CheckItem.tsx index 535fa992ff0..d9c27c796d6 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/CheckItem.tsx @@ -1,5 +1,6 @@ import { Trans, useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' +import { useEffect } from 'react' import { DIRECTION_COLUMN, @@ -11,47 +12,66 @@ import { import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { UnorderedList } from '/app/molecules/UnorderedList' import { + clearSelectedLabware, setFinalPosition, setInitialPosition, } from '/app/redux/protocol-runs/actions' -import { JogToWell } from './JogToWell' +import { JogToWell } from './EditOffset' import { PrepareSpace } from './PrepareSpace' import { PlaceItemInstruction } from './PlaceItemInstruction' import { - selectActiveLwInitialPosition, + selectSelectedLwInitialPosition, selectActivePipette, - selectIsActiveLwTipRack, + selectIsSelectedLwTipRack, + selectSelectedLabwareInfo, } from '/app/redux/protocol-runs' import { getIsOnDevice } from '/app/redux/config' import type { DisplayLocationParams } from '@opentrons/components' +import type { LoadedPipette } from '@opentrons/shared-data' import type { State } from '/app/redux/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' +import type { + LPCWizardState, + OffsetLocationDetails, + SelectedLabwareInfo, +} from '/app/redux/protocol-runs' import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' export function CheckItem(props: LPCWizardContentProps): JSX.Element { - const { runId, proceed, commandUtils } = props + const { runId, commandUtils } = props const { handleJog, handleCheckItemsPrepModules, handleConfirmLwModulePlacement, handleConfirmLwFinalPosition, handleResetLwModulesOnDeck, - handleValidMoveToMaintenancePosition, toggleRobotMoving, } = commandUtils const dispatch = useDispatch() const isOnDevice = useSelector(getIsOnDevice) - const { protocolData, labwareDefs, steps } = useSelector( + const { protocolData, labwareDefs } = useSelector( (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState ) const { t } = useTranslation(['labware_position_check', 'shared']) const { t: commandTextT } = useTranslation('protocol_command_text') - const pipette = useSelector(selectActivePipette(runId)) - const initialPosition = useSelector(selectActiveLwInitialPosition(runId)) - const isLwTiprack = useSelector(selectIsActiveLwTipRack(runId)) + const pipette = useSelector(selectActivePipette(runId)) as LoadedPipette + const pipetteId = pipette.id + const initialPosition = useSelector(selectSelectedLwInitialPosition(runId)) + const isLwTiprack = useSelector(selectIsSelectedLwTipRack(runId)) + const lwInfo = useSelector( + selectSelectedLabwareInfo(runId) + ) as SelectedLabwareInfo + const offsetLocationDetails = lwInfo.offsetLocationDetails as OffsetLocationDetails + + useEffect(() => { + void toggleRobotMoving(true) + .then(() => + handleCheckItemsPrepModules(offsetLocationDetails, initialPosition) + ) + .finally(() => toggleRobotMoving(false)) + }, []) const buildDisplayParams = (): Omit< DisplayLocationParams, @@ -61,7 +81,7 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { loadedModules: protocolData.modules, loadedLabwares: protocolData.labware, robotType: FLEX_ROBOT_TYPE, - location, + location: offsetLocationDetails, }) const slotOnlyDisplayLocation = getLabwareDisplayLocation({ @@ -76,12 +96,14 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { const handlePrepareProceed = (): void => { void toggleRobotMoving(true) - .then(() => handleConfirmLwModulePlacement({ step })) + .then(() => + handleConfirmLwModulePlacement(offsetLocationDetails, pipetteId) + ) .then(position => { dispatch( setInitialPosition(runId, { - labwareId, - location, + labwareUri: lwInfo.uri, + location: offsetLocationDetails, position, }) ) @@ -92,40 +114,30 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { // TODO(jh, 01-14-25): Revisit next step injection after refactoring the store (after designs settle). const handleJogProceed = (): void => { void toggleRobotMoving(true) - .then(() => - handleConfirmLwFinalPosition({ - step, - onSuccess: proceed, - pipette, - }) - ) + .then(() => handleConfirmLwFinalPosition(offsetLocationDetails, pipette)) .then(position => { dispatch( setFinalPosition(runId, { - labwareId, - location, + labwareUri: lwInfo.uri, + location: offsetLocationDetails, position, }) ) }) .then(() => { - if (steps.next?.section === STEP.CHECK_POSITIONS) { - return handleCheckItemsPrepModules(steps.next) - } else { - return handleValidMoveToMaintenancePosition(pipette, steps.next) - } + dispatch(clearSelectedLabware(runId)) }) .finally(() => toggleRobotMoving(false)) } const handleGoBack = (): void => { void toggleRobotMoving(true) - .then(() => handleResetLwModulesOnDeck({ step })) + .then(() => handleResetLwModulesOnDeck(offsetLocationDetails)) .then(() => { dispatch( setInitialPosition(runId, { - labwareId, - location, + labwareUri: lwInfo.uri, + location: offsetLocationDetails, position: null, }) ) @@ -182,12 +194,14 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { isLwTiprack={isLwTiprack} slotOnlyDisplayLocation={slotOnlyDisplayLocation} fullDisplayLocation={fullDisplayLocation} + labwareInfo={lwInfo} {...props} />, ]} /> } confirmPlacement={handlePrepareProceed} + labwareInfo={lwInfo} {...props} /> )} diff --git a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/JogToWell/LiveOffsetValue.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/LiveOffsetValue.tsx similarity index 92% rename from app/src/organisms/LabwarePositionCheck/steps/CheckItem/JogToWell/LiveOffsetValue.tsx rename to app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/LiveOffsetValue.tsx index 18ead803548..3ea93e2774d 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/JogToWell/LiveOffsetValue.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/LiveOffsetValue.tsx @@ -19,10 +19,7 @@ import { import { getIsOnDevice } from '/app/redux/config' import type { StyleProps } from '@opentrons/components' -import type { - CheckPositionsStep, - LPCStepProps, -} from '/app/organisms/LabwarePositionCheck/types' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' interface OffsetVectorProps extends StyleProps { x: number @@ -31,7 +28,7 @@ interface OffsetVectorProps extends StyleProps { } export function LiveOffsetValue( - props: OffsetVectorProps & LPCStepProps + props: OffsetVectorProps & LPCWizardContentProps ): JSX.Element { const { x, y, z, ...styleProps } = props const { i18n, t } = useTranslation('labware_position_check') diff --git a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/JogToWell/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/index.tsx similarity index 87% rename from app/src/organisms/LabwarePositionCheck/steps/CheckItem/JogToWell/index.tsx rename to app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/index.tsx index 26a0dd599e6..099b84026ff 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/JogToWell/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/index.tsx @@ -35,33 +35,28 @@ import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import { JogControls } from '/app/molecules/JogControls' import { LiveOffsetValue } from './LiveOffsetValue' import { - selectActiveLwExistingOffset, - selectActiveLwInitialPosition, + selectSelectedLwExistingOffset, + selectSelectedLwInitialPosition, selectActivePipette, - selectIsActiveLwTipRack, - selectItemLabwareDef, + selectIsSelectedLwTipRack, + selectSelectedLabwareDef, } from '/app/redux/protocol-runs' import { getIsOnDevice } from '/app/redux/config' +import levelProbeWithTip from '/app/assets/images/lpc_level_probe_with_tip.svg' +import levelProbeWithLabware from '/app/assets/images/lpc_level_probe_with_labware.svg' + import type { ReactNode } from 'react' import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { VectorOffset } from '@opentrons/api-client' import type { Jog } from '/app/molecules/JogControls' -import type { - CheckPositionsStep, - LPCStepProps, -} from '/app/organisms/LabwarePositionCheck/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' - -import levelProbeWithTip from '/app/assets/images/lpc_level_probe_with_tip.svg' -import levelProbeWithLabware from '/app/assets/images/lpc_level_probe_with_labware.svg' -import type { State } from '/app/redux/types' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' const DECK_MAP_VIEWBOX = '-10 -10 150 105' const LPC_HELP_LINK_URL = 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' -interface JogToWellProps extends LPCStepProps { +interface JogToWellProps extends LPCWizardContentProps { header: ReactNode body: ReactNode handleConfirmPosition: () => void @@ -79,30 +74,18 @@ export function JogToWell(props: JogToWellProps): JSX.Element { handleJog, } = props const { t } = useTranslation(['labware_position_check', 'shared']) - const { steps } = useSelector( - (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState - ) - const { current: currentStep } = steps const isOnDevice = useSelector(getIsOnDevice) - const initialPosition = useSelector( - (state: State) => - selectActiveLwInitialPosition(currentStep, runId, state) ?? - IDENTITY_VECTOR - ) - const pipetteName = useSelector( - (state: State) => - selectActivePipette(currentStep, runId, state)?.pipetteName ?? - 'p1000_single' - ) + const initialPosition = + useSelector(selectSelectedLwInitialPosition(runId)) ?? IDENTITY_VECTOR + const pipetteName = + useSelector(selectActivePipette(runId))?.pipetteName ?? 'p1000_single' const itemLwDef = useSelector( - selectItemLabwareDef(runId) - ) as LabwareDefinition2 // Safe if component only used with CheckItem step. - const isTipRack = useSelector((state: State) => - selectIsActiveLwTipRack(runId, state) - ) - const activeLwExistingOffset = useSelector((state: State) => - selectActiveLwExistingOffset(runId, state) + selectSelectedLabwareDef(runId) + ) as LabwareDefinition2 + const isTipRack = useSelector(selectIsSelectedLwTipRack(runId)) + const activeLwExistingOffset = useSelector( + selectSelectedLwExistingOffset(runId) ) const [joggedPosition, setJoggedPosition] = useState( diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx new file mode 100644 index 00000000000..7acf865eb5b --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx @@ -0,0 +1,133 @@ +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' +import { css } from 'styled-components' + +import { + Flex, + StyledText, + ListButton, + SPACING, + getLabwareDisplayLocation, + JUSTIFY_SPACE_BETWEEN, +} from '@opentrons/components' +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' + +import { + selectSelectedLabwareDisplayName, + selectSelectedLabwareInfo, + selectSelectedOffsetDetails, + setSelectedLabware, +} from '/app/redux/protocol-runs' +import { InterventionInfo } from '/app/molecules/InterventionModal/InterventionContent' + +import type { State } from '/app/redux/types' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' +import type { + LPCWizardState, + OffsetDetails, + SelectedLabwareInfo, +} from '/app/redux/protocol-runs' + +export function AppliedLocationOffsetsContainer( + props: LPCWizardContentProps +): JSX.Element { + const offsetDetails = useSelector(selectSelectedOffsetDetails(props.runId)) + + return ( + + {offsetDetails.map(offset => ( + + ))} + + ) +} + +interface LabwareLocationItemProps extends LPCWizardContentProps { + offsetDetail: OffsetDetails +} + +function LabwareLocationItemContainer( + props: LabwareLocationItemProps +): JSX.Element { + const { t } = useTranslation('labware_position_check') + + return ( + + + {t('slot_location')} + {t('offsets')} + + + {/* Gives extra scrollable space. */} + + + ) +} + +function LabwareLocationItem({ + runId, + offsetDetail, +}: LabwareLocationItemProps): JSX.Element { + const { t: commandTextT } = useTranslation('protocol_command_text') + const dispatch = useDispatch() + + const { protocolData } = useSelector( + (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState + ) + const displayName = useSelector(selectSelectedLabwareDisplayName(runId)) + const selectedLw = useSelector( + selectSelectedLabwareInfo(runId) + ) as SelectedLabwareInfo + + const slotCopy = getLabwareDisplayLocation({ + t: commandTextT, + loadedModules: protocolData.modules, + loadedLabwares: protocolData.labware, + robotType: FLEX_ROBOT_TYPE, + location: { slotName: offsetDetail.locationDetails.slotName }, + detailLevel: 'slot-only', + }) + + const handleOnClick = (): void => { + dispatch( + setSelectedLabware(runId, selectedLw.uri, offsetDetail.locationDetails) + ) + } + + return ( + + + + + + ) +} + +const APPLIED_LOCATION_CONTAINER_STYLE = css` + grid-gap: ${SPACING.spacing24}; +` + +const HEADER_STYLE = css` + padding: 0 1.375rem; + grid-gap: 3.813rem; +` + +const LOCATION_ITEM_CONTAINER_STYLE = css` + grid-gap: ${SPACING.spacing8}; +` + +const BUTTON_TEXT_STYLE = css` + justify-content: ${JUSTIFY_SPACE_BETWEEN}; +` + +const BOX_STYLE = css` + height: ${SPACING.spacing40}; +` diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx new file mode 100644 index 00000000000..6ad56705895 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx @@ -0,0 +1,60 @@ +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' + +import { + ListButton, + Flex, + Tag, + StyledText, + PrimaryButton, + Icon, + SPACING, + DIRECTION_COLUMN, + JUSTIFY_SPACE_BETWEEN, + ALIGN_CENTER, +} from '@opentrons/components' + +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' + +export function DefaultLocationOffset( + props: LPCWizardContentProps +): JSX.Element { + const { t } = useTranslation('labware_position_check') + + return ( + + + + {t('default_labware_offset')} + + + + + + {t('add')} + + + + + ) +} + +const BUTTON_ALL_CONTENT_STYLE = css` + grid-gap: ${SPACING.spacing24}; + justify-content: ${JUSTIFY_SPACE_BETWEEN}; +` + +const BUTTON_LEFT_CONTENT_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing8}; +` + +const BUTTON_TEXT_CONTAINER_STYLE = css` + justify-content: ${JUSTIFY_SPACE_BETWEEN}; + align-items: ${ALIGN_CENTER}; +` + +const ADD_ICON_STYLE = css` + width: 1.75rem; + height: 1.75rem; +` diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/index.tsx new file mode 100644 index 00000000000..2aff38442e7 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/index.tsx @@ -0,0 +1,16 @@ +import { Flex } from '@opentrons/components' + +import { AppliedLocationOffsetsContainer } from './AppliedLocationOffsetsContainer' +import { DefaultLocationOffset } from './DefaultLocationOffset' +import { LIST_CONTAINER_STYLE } from '/app/organisms/LabwarePositionCheck/steps/HandleLabware/contants' + +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' + +export function LPCLabwareDetails(props: LPCWizardContentProps): JSX.Element { + return ( + + + + + ) +} diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx new file mode 100644 index 00000000000..8133d3bca15 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx @@ -0,0 +1,64 @@ +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' + +import { Flex, StyledText, SPACING, ListButton } from '@opentrons/components' + +import { + selectAllLabwareInfo, + setSelectedLabwareName, +} from '/app/redux/protocol-runs' +import { LIST_CONTAINER_STYLE } from '/app/organisms/LabwarePositionCheck/steps/HandleLabware/contants' + +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' +import type { LabwareDetails } from '/app/redux/protocol-runs' + +export function LPCLabwareList(props: LPCWizardContentProps): JSX.Element { + const { t } = useTranslation('labware_position_check') + const labwareInfo = useSelector(selectAllLabwareInfo(props.runId)) + + return ( + + {t('select_labware_from_list')} + + {Object.entries(labwareInfo).map(([uri, info]) => ( + + ))} + + + ) +} + +interface LabwareItemProps extends LPCWizardContentProps { + uri: string + info: LabwareDetails +} + +function LabwareItem({ uri, info, runId }: LabwareItemProps): JSX.Element { + const dispatch = useDispatch() + + const handleOnClick = (): void => { + dispatch(setSelectedLabwareName(runId, uri)) + } + + return ( + + + {info.displayName} + + + ) +} + +const LIST_STYLE = css` + grid-gap: ${SPACING.spacing8}; +` + +const BUTTON_TEXT_STYLE = css` + grid-gap: ${SPACING.spacing24}; +` diff --git a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/PlaceItemInstruction.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PlaceItemInstruction.tsx similarity index 77% rename from app/src/organisms/LabwarePositionCheck/steps/CheckItem/PlaceItemInstruction.tsx rename to app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PlaceItemInstruction.tsx index 7fc4487b278..2ac53d07757 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/PlaceItemInstruction.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PlaceItemInstruction.tsx @@ -5,37 +5,32 @@ import { TYPOGRAPHY, LegacyStyledText } from '@opentrons/components' import { selectActiveAdapterDisplayName, - selectLwDisplayName, + selectSelectedLwDisplayName, } from '/app/redux/protocol-runs' -import type { State } from '/app/redux/types' -import type { - CheckPositionsStep, - LPCStepProps, -} from '/app/organisms/LabwarePositionCheck/types' +import type { SelectedLabwareInfo } from '/app/redux/protocol-runs' -interface PlaceItemInstructionProps extends LPCStepProps { +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' + +interface PlaceItemInstructionProps extends LPCWizardContentProps { isLwTiprack: boolean slotOnlyDisplayLocation: string fullDisplayLocation: string + labwareInfo: SelectedLabwareInfo } export function PlaceItemInstruction({ runId, - step, isLwTiprack, slotOnlyDisplayLocation, fullDisplayLocation, + labwareInfo, }: PlaceItemInstructionProps): JSX.Element { const { t } = useTranslation('labware_position_check') - const { adapterId } = step - const labwareDisplayName = useSelector((state: State) => - selectLwDisplayName(runId, state) - ) - const adapterDisplayName = useSelector((state: State) => - selectActiveAdapterDisplayName(runId, state) - ) + const { adapterId } = labwareInfo.offsetLocationDetails ?? { adapterId: null } + const labwareDisplayName = useSelector(selectSelectedLwDisplayName(runId)) + const adapterDisplayName = useSelector(selectActiveAdapterDisplayName(runId)) if (isLwTiprack) { return ( diff --git a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/PrepareSpace.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareSpace.tsx similarity index 81% rename from app/src/organisms/LabwarePositionCheck/steps/CheckItem/PrepareSpace.tsx rename to app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareSpace.tsx index d308d986a11..bf7afa1c11a 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/CheckItem/PrepareSpace.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareSpace.tsx @@ -24,25 +24,27 @@ import { import { SmallButton } from '/app/atoms/buttons' import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' -import { selectItemLabwareDef } from '/app/redux/protocol-runs' +import { selectSelectedLabwareDef } from '/app/redux/protocol-runs' import { getIsOnDevice } from '/app/redux/config' import type { ReactNode } from 'react' import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { - CheckPositionsStep, - LPCStepProps, -} from '/app/organisms/LabwarePositionCheck/types' import type { State } from '/app/redux/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' +import type { + LPCWizardState, + OffsetLocationDetails, + SelectedLabwareInfo, +} from '/app/redux/protocol-runs' +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' const LPC_HELP_LINK_URL = 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' -interface PrepareSpaceProps extends LPCStepProps { +interface PrepareSpaceProps extends LPCWizardContentProps { header: ReactNode body: ReactNode confirmPlacement: () => void + labwareInfo: SelectedLabwareInfo } export function PrepareSpace({ @@ -50,16 +52,18 @@ export function PrepareSpace({ header, body, confirmPlacement, + labwareInfo, }: PrepareSpaceProps): JSX.Element { const { i18n, t } = useTranslation(['labware_position_check', 'shared']) - const { protocolData, deckConfig, steps } = useSelector( + const { protocolData, deckConfig } = useSelector( (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState ) const isOnDevice = useSelector(getIsOnDevice) const labwareDef = useSelector( - selectItemLabwareDef(runId) + selectSelectedLabwareDef(runId) ) as LabwareDefinition2 // CheckItem always has lwId on step. - const { location } = steps.current as CheckPositionsStep // safely enforced by iface + const offsetLocationDetails = labwareInfo.offsetLocationDetails as OffsetLocationDetails + const { moduleModel } = offsetLocationDetails return ( @@ -74,20 +78,16 @@ export function PrepareSpace({ modulesOnDeck={protocolData.modules.map(mod => ({ moduleModel: mod.model, moduleLocation: mod.location, - nestedLabwareDef: - 'moduleModel' in location && location.moduleModel != null - ? labwareDef - : null, + nestedLabwareDef: moduleModel != null ? labwareDef : null, innerProps: - 'moduleModel' in location && - location.moduleModel != null && - getModuleType(location.moduleModel) === THERMOCYCLER_MODULE_TYPE + moduleModel != null && + getModuleType(moduleModel) === THERMOCYCLER_MODULE_TYPE ? { lidMotorState: 'open' } : {}, }))} labwareOnDeck={[ { - labwareLocation: location, + labwareLocation: offsetLocationDetails, definition: labwareDef, }, ].filter( diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts new file mode 100644 index 00000000000..d1243fd402b --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts @@ -0,0 +1,12 @@ +import { css } from 'styled-components' + +import { SPACING } from '@opentrons/components' + +/** + * Styles + */ + +export const LIST_CONTAINER_STYLE = css` + padding: ${SPACING.spacing32} ${SPACING.spacing60}; + grid: ${SPACING.spacing24}; +` diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/index.tsx new file mode 100644 index 00000000000..e168b19df09 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/index.tsx @@ -0,0 +1,33 @@ +import { useSelector } from 'react-redux' + +import { + selectSelectedLabwareFlowType, + selectSelectedLabwareInfo, +} from '/app/redux/protocol-runs' +import { CheckItem } from './CheckItem' +import { LPCLabwareList } from './LPCLabwareList' +import { LPCLabwareDetails } from './LPCLabwareDetails' + +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' + +export function HandleLabware(props: LPCWizardContentProps): JSX.Element { + const selectedLw = useSelector(selectSelectedLabwareInfo(props.runId)) + const offsetFlowType = useSelector(selectSelectedLabwareFlowType(props.runId)) + + if (selectedLw == null) { + return + } else if (selectedLw.offsetLocationDetails == null) { + return + } else { + switch (offsetFlowType) { + case 'default': + return + case 'location-specific': + return + default: { + console.error(`Unexpected offsetFlowType: ${offsetFlowType}`) + return + } + } + } +} diff --git a/app/src/organisms/LabwarePositionCheck/steps/LPCComplete/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/LPCComplete/index.tsx new file mode 100644 index 00000000000..f40e117dba0 --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/steps/LPCComplete/index.tsx @@ -0,0 +1,13 @@ +import { useEffect } from 'react' + +import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' + +export function LPCComplete(props: LPCWizardContentProps): JSX.Element { + useEffect(() => { + setTimeout(() => { + props.onCloseClick() + }, 5000) + }, []) + + return <>LPC COMPLETE +} diff --git a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/OffsetTable.tsx b/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/OffsetTable.tsx deleted file mode 100644 index d35c3d2f885..00000000000 --- a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/OffsetTable.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { Fragment } from 'react' -import styled from 'styled-components' -import isEqual from 'lodash/isEqual' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' - -import { FLEX_ROBOT_TYPE, IDENTITY_VECTOR } from '@opentrons/shared-data' -import { - BORDERS, - COLORS, - Flex, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - getLabwareDisplayLocation, -} from '@opentrons/components' - -import { selectLwDisplayName } from '/app/redux/protocol-runs' - -import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { LabwareOffsetCreateData } from '@opentrons/api-client' -import type { - LPCStepProps, - ResultsSummaryStep, -} from '/app/organisms/LabwarePositionCheck/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' -import type { State } from '/app/redux/types' - -interface OffsetTableProps extends LPCStepProps { - offsets: LabwareOffsetCreateData[] - labwareDefinitions: LabwareDefinition2[] -} - -export function OffsetTable({ - offsets, - runId, - labwareDefinitions, -}: OffsetTableProps): JSX.Element { - const { protocolData } = useSelector( - (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState - ) - const lwDisplayName = useSelector((state: State) => - selectLwDisplayName(runId, state) - ) - - const { t } = useTranslation('labware_position_check') - - return ( - - - - {t('location')} - {t('labware')} - {t('labware_offset_data')} - - - - - {offsets.map(({ location, vector }, index) => { - const displayLocation = getLabwareDisplayLocation({ - location, - allRunDefs: labwareDefinitions, - detailLevel: 'full', - t, - loadedModules: protocolData.modules, - loadedLabwares: protocolData.labware, - robotType: FLEX_ROBOT_TYPE, - }) - - return ( - - - - {displayLocation} - - - - {lwDisplayName} - - - {isEqual(vector, IDENTITY_VECTOR) ? ( - {t('no_labware_offsets')} - ) : ( - - {[vector.x, vector.y, vector.z].map((axis, index) => ( - - 0 ? SPACING.spacing8 : 0} - marginRight={SPACING.spacing4} - fontWeight={TYPOGRAPHY.fontWeightSemiBold} - > - {['X', 'Y', 'Z'][index]} - - - {axis.toFixed(1)} - - - ))} - - )} - - - ) - })} - -
- ) -} - -const Table = styled('table')` - ${TYPOGRAPHY.labelRegular} - table-layout: auto; - width: 100%; - border-spacing: 0 ${SPACING.spacing4}; - margin: ${SPACING.spacing16} 0; - text-align: left; -` - -const TableHeader = styled('th')` - text-transform: ${TYPOGRAPHY.textTransformUppercase}; - color: ${COLORS.black90}; - font-weight: ${TYPOGRAPHY.fontWeightRegular}; - font-size: ${TYPOGRAPHY.fontSizeCaption}; - padding: ${SPACING.spacing4}; -` - -const TableRow = styled('tr')` - background-color: ${COLORS.grey20}; -` - -const TableDatum = styled('td')` - padding: ${SPACING.spacing4}; - white-space: break-spaces; - text-overflow: wrap; -` - -const LeftRoundedTableDatum = styled(TableDatum)` - border-radius: ${BORDERS.borderRadius4} 0 0 ${BORDERS.borderRadius4}; -` - -const RightRoundedTableDatum = styled(TableDatum)` - border-radius: 0 ${BORDERS.borderRadius4} ${BORDERS.borderRadius4} 0; -` diff --git a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/TableComponent.tsx b/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/TableComponent.tsx deleted file mode 100644 index 90ddb8edcf1..00000000000 --- a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/TableComponent.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useSelector } from 'react-redux' - -import { TerseOffsetTable } from '/app/organisms/TerseOffsetTable' -import { OffsetTable } from './OffsetTable' -import { getIsOnDevice } from '/app/redux/config' - -import type { LabwareOffsetCreateData } from '@opentrons/api-client' -import type { - LPCStepProps, - ResultsSummaryStep, -} from '/app/organisms/LabwarePositionCheck/types' -import type { State } from '/app/redux/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' - -interface TableComponentProps extends LPCStepProps { - offsetsToApply: LabwareOffsetCreateData[] -} - -export function TableComponent(props: TableComponentProps): JSX.Element { - const { offsetsToApply, runId } = props - const isOnDevice = useSelector(getIsOnDevice) - const { labwareDefs } = useSelector( - (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState - ) - - return isOnDevice ? ( - - ) : ( - - ) -} diff --git a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx deleted file mode 100644 index 7be0611b74c..00000000000 --- a/app/src/organisms/LabwarePositionCheck/steps/ResultsSummary/index.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import styled, { css } from 'styled-components' -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - ALIGN_FLEX_END, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - OVERFLOW_AUTO, - PrimaryButton, - RESPONSIVENESS, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' -import { PythonLabwareOffsetSnippet } from '/app/molecules/PythonLabwareOffsetSnippet' -import { - getIsLabwareOffsetCodeSnippetsOn, - getIsOnDevice, -} from '/app/redux/config' -import { SmallButton } from '/app/atoms/buttons' -import { LabwareOffsetTabs } from '/app/organisms/LabwareOffsetTabs' -import { TableComponent } from './TableComponent' - -import type { State } from '/app/redux/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' -import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' - -// TODO(jh, 01-08-25): This support link will likely need updating as a part of RPRD-173, too. -const LPC_HELP_LINK_URL = - 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' - -export function ResultsSummary(props: LPCWizardContentProps): JSX.Element { - const { commandUtils, runId } = props - const isOnDevice = useSelector(getIsOnDevice) - const { protocolData } = useSelector( - (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState - ) - const { - isApplyingOffsets, - handleApplyOffsetsAndClose, - buildOffsetsToApply, - toggleRobotMoving, - } = commandUtils - const { i18n, t } = useTranslation('labware_position_check') - const offsetsToApply = buildOffsetsToApply() - const isLabwareOffsetCodeSnippetsOn = useSelector( - getIsLabwareOffsetCodeSnippetsOn - ) - - const handleProceed = (): void => { - void toggleRobotMoving(true).then(() => - handleApplyOffsetsAndClose(offsetsToApply) - ) - } - - return ( - - -
{t('new_labware_offset_data')}
- {isLabwareOffsetCodeSnippetsOn ? ( - - } - JupyterComponent={ - - } - CommandLineComponent={ - - } - marginTop={SPACING.spacing16} - /> - ) : ( - - )} -
- {isOnDevice ? ( - - ) : ( - - - - - {isApplyingOffsets ? ( - - ) : null} - - {i18n.format(t('apply_offsets'), 'capitalize')} - - - - - )} -
- ) -} - -const PARENT_CONTAINER_STYLE = css` - flex-direction: ${DIRECTION_COLUMN}; - justify-content: ${JUSTIFY_SPACE_BETWEEN}; - padding: ${SPACING.spacing32}; - min-height: 29.5rem; -` - -const SHARED_CONTAINER_STYLE = css` - flex-direction: ${DIRECTION_COLUMN}; - max-height: 20rem; - overflow-y: ${OVERFLOW_AUTO}; - - &::-webkit-scrollbar { - width: 0.75rem; - background-color: transparent; - } - &::-webkit-scrollbar-thumb { - background: ${COLORS.grey50}; - border-radius: 11px; - } -` - -const DESKTOP_BUTTON_STYLE = css` - width: 100%; - margin-top: ${SPACING.spacing32}; - justify-content: ${JUSTIFY_SPACE_BETWEEN}; - align-items: ${ALIGN_CENTER}; -` - -const Header = styled.h1` - ${TYPOGRAPHY.h1Default} - - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - ${TYPOGRAPHY.level4HeaderSemiBold} - } -` diff --git a/app/src/organisms/LabwarePositionCheck/steps/index.ts b/app/src/organisms/LabwarePositionCheck/steps/index.ts index 9bf7efbad46..62bf40d486f 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/index.ts +++ b/app/src/organisms/LabwarePositionCheck/steps/index.ts @@ -1,5 +1,5 @@ export { BeforeBeginning } from './BeforeBeginning' +export { HandleLabware } from './HandleLabware' export { AttachProbe } from './AttachProbe' -export { CheckItem } from './CheckItem' export { DetachProbe } from './DetachProbe' -export { ResultsSummary } from './ResultsSummary' +export { LPCComplete } from './LPCComplete' diff --git a/app/src/organisms/LegacyApplyHistoricOffsets/index.tsx b/app/src/organisms/LegacyApplyHistoricOffsets/index.tsx index de216131c79..fc60b5b8a64 100644 --- a/app/src/organisms/LegacyApplyHistoricOffsets/index.tsx +++ b/app/src/organisms/LegacyApplyHistoricOffsets/index.tsx @@ -97,7 +97,7 @@ export function LegacyApplyHistoricOffsets( - {t(noOffsetData ? 'no_offset_data' : 'apply_offset_data')} + {t(noOffsetData ? 'legacy_no_offset_data' : 'apply_offset_data')} } diff --git a/app/src/redux/protocol-runs/actions/lpc.ts b/app/src/redux/protocol-runs/actions/lpc.ts index c33386366c0..fdb735aa9d0 100644 --- a/app/src/redux/protocol-runs/actions/lpc.ts +++ b/app/src/redux/protocol-runs/actions/lpc.ts @@ -7,6 +7,7 @@ import { GO_BACK_STEP, SET_SELECTED_LABWARE, CLEAR_SELECTED_LABWARE, + SET_SELECTED_LABWARE_NAME, } from '../constants' import type { @@ -18,9 +19,10 @@ import type { ProceedStepAction, FinishLPCAction, GoBackStepAction, - LPCLabwareLocationDetails, SelectedLabwareAction, ClearSelectedLabwareAction, + SelectedLabwareNameAction, + OffsetLocationDetails, } from '../types' export const proceedStep = (runId: string): ProceedStepAction => ({ @@ -33,10 +35,21 @@ export const goBackStep = (runId: string): GoBackStepAction => ({ payload: { runId }, }) +export const setSelectedLabwareName = ( + runId: string, + labwareUri: string +): SelectedLabwareNameAction => ({ + type: SET_SELECTED_LABWARE_NAME, + payload: { + runId, + labwareUri, + }, +}) + export const setSelectedLabware = ( runId: string, labwareUri: string, - location: LPCLabwareLocationDetails + location: OffsetLocationDetails | null ): SelectedLabwareAction => ({ type: SET_SELECTED_LABWARE, payload: { diff --git a/app/src/redux/protocol-runs/constants/lpc/actions.ts b/app/src/redux/protocol-runs/constants/lpc/actions.ts index 355d024e962..eabfe333062 100644 --- a/app/src/redux/protocol-runs/constants/lpc/actions.ts +++ b/app/src/redux/protocol-runs/constants/lpc/actions.ts @@ -2,6 +2,7 @@ export const START_LPC = 'START_LPC' export const FINISH_LPC = 'FINISH_LPC' export const PROCEED_STEP = 'PROCEED_STEP' export const GO_BACK_STEP = 'GO_BACK_STEP' +export const SET_SELECTED_LABWARE_NAME = 'SET_SELECTED_LABWARE_NAME' export const SET_SELECTED_LABWARE = 'SET_SELECTED_LABWARE' export const CLEAR_SELECTED_LABWARE = 'CLEAR_SELECTED_LABWARE' export const SET_INITIAL_POSITION = 'SET_INITIAL_POSITION' diff --git a/app/src/redux/protocol-runs/reducer/lpc.ts b/app/src/redux/protocol-runs/reducer/lpc.ts index bcc29a2636d..84c2de8d864 100644 --- a/app/src/redux/protocol-runs/reducer/lpc.ts +++ b/app/src/redux/protocol-runs/reducer/lpc.ts @@ -6,6 +6,8 @@ import { FINISH_LPC, START_LPC, GO_BACK_STEP, + SET_SELECTED_LABWARE_NAME, + CLEAR_SELECTED_LABWARE, } from '../constants' import { updateOffsetsForURI } from './transforms' @@ -56,6 +58,25 @@ export function LPCReducer( } } + case SET_SELECTED_LABWARE_NAME: { + const lwUri = action.payload.labwareUri + const thisLwInfo = state.labwareInfo.labware[lwUri] + + const selectedLabware: SelectedLabwareInfo = { + uri: action.payload.labwareUri, + id: thisLwInfo.id, + offsetLocationDetails: null, + } + + return { + ...state, + labwareInfo: { + ...state.labwareInfo, + selectedLabware, + }, + } + } + case SET_SELECTED_LABWARE: { const lwUri = action.payload.labwareUri const thisLwInfo = state.labwareInfo.labware[lwUri] @@ -63,7 +84,7 @@ export function LPCReducer( const selectedLabware: SelectedLabwareInfo = { uri: action.payload.labwareUri, id: thisLwInfo.id, - locationDetails: action.payload.location, + offsetLocationDetails: action.payload.location, } return { @@ -75,6 +96,16 @@ export function LPCReducer( } } + case CLEAR_SELECTED_LABWARE: { + return { + ...state, + labwareInfo: { + ...state.labwareInfo, + selectedLabware: null, + }, + } + } + case SET_INITIAL_POSITION: case SET_FINAL_POSITION: { const lwUri = action.payload.labwareUri diff --git a/app/src/redux/protocol-runs/selectors/lpc/labware.ts b/app/src/redux/protocol-runs/selectors/lpc/labware.ts index c8ed8d6c005..b4ab5e54825 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/labware.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/labware.ts @@ -12,7 +12,7 @@ import { getItemLabwareDef, getSelectedLabwareOffsetDetails, getOffsetDetailsForAllLabware, - getItemLabwareDefFrom, + getSelectedLabwareDefFrom, } from './transforms' import type { Selector } from 'reselect' @@ -23,9 +23,49 @@ import type { } from '@opentrons/api-client' import type { State } from '../../../types' import type { Coordinates, LabwareDefinition2 } from '@opentrons/shared-data' -import type { LabwareDetails } from '/app/redux/protocol-runs' +import type { + LabwareDetails, + LPCFlowType, + LPCLabwareInfo, + OffsetDetails, + SelectedLabwareInfo, +} from '/app/redux/protocol-runs' + +export const selectAllLabwareInfo = ( + runId: string +): Selector => + createSelector( + (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, + labware => labware ?? {} + ) + +export const selectSelectedLabwareInfo = ( + runId: string +): Selector => + createSelector( + (state: State) => + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware, + selectedLabware => selectedLabware ?? null + ) -export const selectActiveLwInitialPosition = ( +export const selectSelectedOffsetDetails = ( + runId: string +): Selector => + createSelector( + (state: State) => + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware?.uri, + (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, + (uri, lw) => { + if (uri == null || lw == null) { + console.warn('Failed to access labware details.') + return [] + } else { + return lw[uri].offsetDetails ?? [] + } + } + ) + +export const selectSelectedLwInitialPosition = ( runId: string ): Selector => createSelector( @@ -34,7 +74,6 @@ export const selectActiveLwInitialPosition = ( const workingOffset = details?.workingOffset if (workingOffset == null) { - console.warn('Working offset for active labware not set.') return null } else { return workingOffset.initialPosition @@ -42,13 +81,13 @@ export const selectActiveLwInitialPosition = ( } ) -export const selectActiveLwExistingOffset = ( +export const selectSelectedLwExistingOffset = ( runId: string ): Selector => createSelector( (state: State) => getSelectedLabwareOffsetDetails(runId, state), details => { - const existingVector = details?.existingOffset.vector + const existingVector = details?.existingOffset?.vector if (existingVector == null) { console.warn('No existing offset vector found for active labware') @@ -85,7 +124,8 @@ export const selectOffsetsToApply = ( if ( finalPosition == null || initialPosition == null || - definitionUri == null + definitionUri == null || + existingOffset == null ) { console.error( `Cannot generate offsets for labware with incomplete details. ID: ${locationDetails.labwareId}` @@ -110,17 +150,56 @@ export const selectOffsetsToApply = ( } ) -export const selectIsActiveLwTipRack = ( +// TOME TODO: You can break your selectors down into files along offsets, maybe booleans, etc. +export const selectSelectedLabwareFlowType = ( + runId: string +): Selector => + createSelector( + (state: State) => + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware, + selectedLabware => { + if (selectedLabware?.offsetLocationDetails == null) { + return null + } else { + if (selectedLabware.offsetLocationDetails.slotName == null) { + return 'default' + } else { + return 'location-specific' + } + } + } + ) + +export const selectSelectedLabwareDisplayName = ( + runId: string +): Selector => + createSelector( + (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, + (state: State) => + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware?.uri, + (lw, uri) => { + if (lw == null || uri == null) { + console.warn('Cannot access invalid labware') + return '' + } else { + return lw[uri].displayName + } + } + ) + +export const selectIsSelectedLwTipRack = ( runId: string ): Selector => createSelector( - (state: State) => getItemLabwareDefFrom(runId, state), + (state: State) => getSelectedLabwareDefFrom(runId, state), def => (def != null ? getIsTiprack(def) : false) ) -export const selectLwDisplayName = (runId: string): Selector => +export const selectSelectedLwDisplayName = ( + runId: string +): Selector => createSelector( - (state: State) => getItemLabwareDefFrom(runId, state), + (state: State) => getSelectedLabwareDefFrom(runId, state), def => (def != null ? getLabwareDisplayName(def) : '') ) @@ -133,7 +212,7 @@ export const selectActiveAdapterDisplayName = ( (state: State) => state?.protocolRuns[runId]?.lpc?.labwareDefs, (state: State) => state?.protocolRuns[runId]?.lpc?.protocolData, (selectedLabware, labwareDefs, analysis) => { - const adapterId = selectedLabware?.locationDetails.adapterId + const adapterId = selectedLabware?.offsetLocationDetails?.adapterId if (selectedLabware == null || labwareDefs == null || analysis == null) { console.warn('No selected labware or store not properly initialized.') @@ -150,6 +229,7 @@ export const selectActiveAdapterDisplayName = ( } ) +// TODO(jh, 01-29-25): Revisit this once "View Offsets" is refactored out of LPC. export const selectLabwareOffsetsForAllLw = ( runId: string ): Selector => @@ -164,19 +244,19 @@ export const selectLabwareOffsetsForAllLw = ( return Object.values(labware).flatMap((details: LabwareDetails) => details.offsetDetails.map(offsetDetail => ({ id: details.id, - createdAt: offsetDetail.existingOffset.createdAt, + createdAt: offsetDetail?.existingOffset?.createdAt ?? '', definitionUri: offsetDetail.locationDetails.definitionUri, location: { slotName: offsetDetail.locationDetails.slotName, moduleModel: offsetDetail.locationDetails.moduleModel, definitionUri: offsetDetail.locationDetails.definitionUri, }, - vector: offsetDetail.existingOffset.vector, + vector: offsetDetail?.existingOffset?.vector ?? IDENTITY_VECTOR, })) ) } ) -export const selectItemLabwareDef = ( +export const selectSelectedLabwareDef = ( runId: string ): Selector => createSelector( diff --git a/app/src/redux/protocol-runs/selectors/lpc/transforms.ts b/app/src/redux/protocol-runs/selectors/lpc/transforms.ts index 08bcd4437a8..f4b2302c4d9 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/transforms.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/transforms.ts @@ -45,12 +45,12 @@ export const getSelectedLabwareOffsetDetails = ( return ( offsetDetails?.find(offset => - isEqual(offset.workingOffset, selectedLabware?.locationDetails) + isEqual(offset.workingOffset, selectedLabware?.offsetLocationDetails) ) ?? null ) } -export const getItemLabwareDefFrom = ( +export const getSelectedLabwareDefFrom = ( runId: string, state: State ): LabwareDefinition2 | null => { diff --git a/app/src/redux/protocol-runs/types/lpc.ts b/app/src/redux/protocol-runs/types/lpc.ts index a5c3db84476..a34098b31f5 100644 --- a/app/src/redux/protocol-runs/types/lpc.ts +++ b/app/src/redux/protocol-runs/types/lpc.ts @@ -12,6 +12,8 @@ type LabwareId = string export type LPCStep = keyof typeof LPC_STEP +export type LPCFlowType = 'default' | 'location-specific' + export interface StepInfo { currentStepIndex: number totalStepCount: number @@ -30,35 +32,52 @@ export interface WorkingOffset { export interface PositionParams { labwareUri: string - location: LPCLabwareLocationDetails + location: OffsetLocationDetails position: VectorOffset | null } -export interface LPCLabwareLocationDetails { +interface LPCLabwareOffsetDetails { labwareId: string definitionUri: string - slotName: string moduleModel?: ModuleModel moduleId?: string adapterId?: string } +export interface LPCLabwareOffsetDefaultDetails + extends LPCLabwareOffsetDetails { + slotName: null +} + +export interface LPCLabwareOffsetAppliedLocationDetails + extends LPCLabwareOffsetDetails { + slotName: string +} + // TODO(jh, 01-23-25): Revisit working/existing/initialOffsets once API rework becomes more finalized. export interface OffsetDetails { - existingOffset: ExistingOffset + existingOffset: ExistingOffset | null workingOffset: WorkingOffset | null - locationDetails: LPCLabwareLocationDetails + locationDetails: LPCLabwareOffsetAppliedLocationDetails } export interface LabwareDetails { id: LabwareId + displayName: string offsetDetails: OffsetDetails[] } +export type OffsetLocationDetails = + | LPCLabwareOffsetDefaultDetails + | LPCLabwareOffsetAppliedLocationDetails + export interface SelectedLabwareInfo { uri: LabwareURI id: LabwareId - locationDetails: LPCLabwareLocationDetails + /* Indicates the type of LPC offset flow the user is performing, a "default" flow, a "location-specific" flow, or no active flow. + * There is no `slotName` when a user performs the default offset flow. + * Until the user is in a default or location-specific offset flow, there is no location. * */ + offsetLocationDetails: OffsetLocationDetails | null } export interface LPCLabwareInfo { @@ -97,12 +116,20 @@ export interface GoBackStepAction { payload: { runId: string } } +export interface SelectedLabwareNameAction { + type: 'SET_SELECTED_LABWARE_NAME' + payload: { + runId: string + labwareUri: LabwareURI + } +} + export interface SelectedLabwareAction { type: 'SET_SELECTED_LABWARE' payload: { runId: string labwareUri: LabwareURI - location: LPCLabwareLocationDetails + location: OffsetLocationDetails | null } } @@ -124,7 +151,9 @@ export interface FinalPositionAction { export type LPCWizardAction = | StartLPCAction | FinishLPCAction + | SelectedLabwareNameAction | SelectedLabwareAction + | ClearSelectedLabwareAction | InitialPositionAction | FinalPositionAction | ProceedStepAction From ad749ef379f17bad8dc23004c1dcc0d6f2877570 Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 30 Jan 2025 13:15:32 -0500 Subject: [PATCH 3/6] cleanup --- .../hooks/useLPCInitialState/index.ts | 1 - .../utils/getActivePipetteId.ts | 3 +- .../steps/AttachProbe.tsx | 12 +- .../steps/BeforeBeginning/index.tsx | 5 +- .../steps/DetachProbe.tsx | 2 + .../steps/HandleLabware/CheckItem.tsx | 42 +++--- .../steps/HandleLabware/EditOffset/index.tsx | 2 +- .../AppliedLocationOffsetsContainer.tsx | 50 ++++--- .../DefaultLocationOffset.tsx | 10 +- .../HandleLabware/LPCLabwareList/index.tsx | 9 +- .../index.tsx} | 10 +- .../steps/HandleLabware/contants.ts | 5 +- app/src/redux/protocol-runs/actions/lpc.ts | 10 ++ .../protocol-runs/constants/lpc/actions.ts | 1 + app/src/redux/protocol-runs/reducer/index.ts | 4 + app/src/redux/protocol-runs/reducer/lpc.ts | 8 ++ .../selectors/lpc/labware/index.ts | 2 + .../lpc/{labware.ts => labware/info.ts} | 129 +---------------- .../selectors/lpc/labware/offsets.ts | 134 ++++++++++++++++++ .../protocol-runs/selectors/lpc/transforms.ts | 2 +- app/src/redux/protocol-runs/types/lpc.ts | 9 +- 21 files changed, 255 insertions(+), 195 deletions(-) rename app/src/organisms/LabwarePositionCheck/steps/HandleLabware/{PrepareSpace.tsx => PrepareLabware/index.tsx} (94%) create mode 100644 app/src/redux/protocol-runs/selectors/lpc/labware/index.ts rename app/src/redux/protocol-runs/selectors/lpc/{labware.ts => labware/info.ts} (53%) create mode 100644 app/src/redux/protocol-runs/selectors/lpc/labware/offsets.ts diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts index 650617534c5..9362a9e8319 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/index.ts @@ -29,7 +29,6 @@ export function useLPCInitialState({ steps: { currentStepIndex: 0, totalStepCount: LPC_STEPS.length, - // TOME TODO: make a selector for the current step! all: LPC_STEPS, }, } diff --git a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts index 0dc1bd57c7e..901f5080fb0 100644 --- a/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts +++ b/app/src/organisms/LabwarePositionCheck/hooks/useLPCInitialState/utils/getActivePipetteId.ts @@ -2,10 +2,9 @@ import { getPipetteNameSpecs } from '@opentrons/shared-data' import type { LoadedPipette } from '@opentrons/shared-data' -// TOME TODO: Actually handle this throwing an error. - // Return the pipetteId for the pipette in the protocol with the highest channel count. export function getActivePipetteId(pipettes: LoadedPipette[]): string { + // TODO(jh, 01-30-25): Actually handle the error here if it were to happen. if (pipettes.length < 1) { throw new Error( 'no pipettes in protocol, cannot determine primary pipette for LPC' diff --git a/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx b/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx index a428308e2d2..da84e634b34 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/AttachProbe.tsx @@ -12,9 +12,8 @@ import { import { ProbeNotAttached } from '/app/organisms/PipetteWizardFlows/ProbeNotAttached' import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { - type OffsetLocationDetails, selectActivePipette, - selectActivePipetteChannelCount, type SelectedLabwareInfo, selectSelectedLabwareInfo, selectSelectedLwInitialPosition, + selectActivePipetteChannelCount, } from '/app/redux/protocol-runs' import { getIsOnDevice } from '/app/redux/config' @@ -49,17 +48,11 @@ export function AttachProbe({ const pipette = useSelector(selectActivePipette(runId)) const { handleProbeAttachment, - handleCheckItemsPrepModules, toggleRobotMoving, setShowUnableToDetect, unableToDetect, } = commandUtils const channels = useSelector(selectActivePipetteChannelCount(runId)) - const initialPosition = useSelector(selectSelectedLwInitialPosition(runId)) - const lwInfo = useSelector( - selectSelectedLabwareInfo(runId) - ) as SelectedLabwareInfo - const offsetLocationDetails = lwInfo.offsetLocationDetails as OffsetLocationDetails const { probeLocation, probeVideoSrc } = ((): { probeLocation: string @@ -86,8 +79,7 @@ export function AttachProbe({ const handleProceed = (): void => { void toggleRobotMoving(true) - .then(() => handleProbeAttachment(pipette, proceed)) - .then(() => handleCheckItemsPrepModules(offsetLocationDetails, initialPosition)) + .then(() => handleProbeAttachment(pipette, proceed)) .finally(() => toggleRobotMoving(false)) } diff --git a/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx index 8050347eeb4..cfda3476d6b 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/BeforeBeginning/index.tsx @@ -20,7 +20,6 @@ import { } from '/app/redux/protocol-runs' import type { State } from '/app/redux/types' -import type { LPCWizardState } from '/app/redux/protocol-runs' import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' // TODO(BC, 09/01/23): replace updated support article link for LPC on OT-2/Flex @@ -36,8 +35,8 @@ export function BeforeBeginning({ const activePipette = useSelector(selectActivePipette(runId)) const existingOffsets = useSelector(selectLabwareOffsetsForAllLw(runId)) const { protocolName, labwareDefs } = useSelector( - (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState - ) + (state: State) => state.protocolRuns[runId]?.lpc + ) ?? { protocolName: '', labwareDefs: [] } const { handleStartLPC, toggleRobotMoving } = commandUtils const requiredEquipmentList = [ diff --git a/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx b/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx index 3042cc39c36..d6261151e7a 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/DetachProbe.tsx @@ -51,6 +51,8 @@ export const DetachProbe = ({ const pipette = useSelector(selectActivePipette(runId)) const channels = useSelector(selectActivePipetteChannelCount(runId)) + // TODO(jh, 01-30-25): This will break the flows, but currently, DetachProbe is inaccessible. + // This onClick behavior should be tied directly to the "exit" button. useEffect(() => { void toggleRobotMoving(true) .then(() => handleValidMoveToMaintenancePosition(pipette)) diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/CheckItem.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/CheckItem.tsx index d9c27c796d6..b8eabaa54ab 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/CheckItem.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/CheckItem.tsx @@ -1,6 +1,5 @@ import { Trans, useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' -import { useEffect } from 'react' import { DIRECTION_COLUMN, @@ -12,12 +11,13 @@ import { import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { UnorderedList } from '/app/molecules/UnorderedList' import { + applyOffset, clearSelectedLabware, setFinalPosition, setInitialPosition, } from '/app/redux/protocol-runs/actions' -import { JogToWell } from './EditOffset' -import { PrepareSpace } from './PrepareSpace' +import { EditOffset } from './EditOffset' +import { PrepareLabware } from './PrepareLabware' import { PlaceItemInstruction } from './PlaceItemInstruction' import { selectSelectedLwInitialPosition, @@ -41,7 +41,6 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { const { runId, commandUtils } = props const { handleJog, - handleCheckItemsPrepModules, handleConfirmLwModulePlacement, handleConfirmLwFinalPosition, handleResetLwModulesOnDeck, @@ -60,18 +59,10 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { const pipetteId = pipette.id const initialPosition = useSelector(selectSelectedLwInitialPosition(runId)) const isLwTiprack = useSelector(selectIsSelectedLwTipRack(runId)) - const lwInfo = useSelector( + const selectedLwInfo = useSelector( selectSelectedLabwareInfo(runId) ) as SelectedLabwareInfo - const offsetLocationDetails = lwInfo.offsetLocationDetails as OffsetLocationDetails - - useEffect(() => { - void toggleRobotMoving(true) - .then(() => - handleCheckItemsPrepModules(offsetLocationDetails, initialPosition) - ) - .finally(() => toggleRobotMoving(false)) - }, []) + const offsetLocationDetails = selectedLwInfo.offsetLocationDetails as OffsetLocationDetails const buildDisplayParams = (): Omit< DisplayLocationParams, @@ -102,7 +93,7 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { .then(position => { dispatch( setInitialPosition(runId, { - labwareUri: lwInfo.uri, + labwareUri: selectedLwInfo.uri, location: offsetLocationDetails, position, }) @@ -111,19 +102,23 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { .finally(() => toggleRobotMoving(false)) } - // TODO(jh, 01-14-25): Revisit next step injection after refactoring the store (after designs settle). const handleJogProceed = (): void => { void toggleRobotMoving(true) .then(() => handleConfirmLwFinalPosition(offsetLocationDetails, pipette)) .then(position => { dispatch( setFinalPosition(runId, { - labwareUri: lwInfo.uri, + labwareUri: selectedLwInfo.uri, location: offsetLocationDetails, position, }) ) }) + // TODO(jh, 01-30-25): This entire sequence of dispatches can be reduced to one dispatch + // after the API changes, but should be separate until then. See APPLY_OFFSET comment in LPC reducer. + .then(() => { + dispatch(applyOffset(runId, selectedLwInfo.uri)) + }) .then(() => { dispatch(clearSelectedLabware(runId)) }) @@ -136,7 +131,7 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { .then(() => { dispatch( setInitialPosition(runId, { - labwareUri: lwInfo.uri, + labwareUri: selectedLwInfo.uri, location: offsetLocationDetails, position: null, }) @@ -145,11 +140,10 @@ export function CheckItem(props: LPCWizardContentProps): JSX.Element { .finally(() => toggleRobotMoving(false)) } - // TODO(jh 01-15-24): These should be separate steps, but let's wait for designs to settle. return ( {initialPosition != null ? ( - ) : ( - , ]} /> } confirmPlacement={handlePrepareProceed} - labwareInfo={lwInfo} - {...props} + selectedLwInfo={selectedLwInfo} /> )} diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/index.tsx index 099b84026ff..8ca3186b54e 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/index.tsx @@ -64,7 +64,7 @@ interface JogToWellProps extends LPCWizardContentProps { handleJog: Jog } -export function JogToWell(props: JogToWellProps): JSX.Element { +export function EditOffset(props: JogToWellProps): JSX.Element { const { runId, header, diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx index 7acf865eb5b..1b7917f6ae3 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/AppliedLocationOffsetsContainer.tsx @@ -6,19 +6,20 @@ import { Flex, StyledText, ListButton, + DeckInfoLabel, SPACING, getLabwareDisplayLocation, JUSTIFY_SPACE_BETWEEN, + DIRECTION_COLUMN, } from '@opentrons/components' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { - selectSelectedLabwareDisplayName, selectSelectedLabwareInfo, + selectSelectedLwInitialPosition, selectSelectedOffsetDetails, setSelectedLabware, } from '/app/redux/protocol-runs' -import { InterventionInfo } from '/app/molecules/InterventionModal/InterventionContent' import type { State } from '/app/redux/types' import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types' @@ -42,6 +43,8 @@ export function AppliedLocationOffsetsContainer( offsetDetail={offset} /> ))} + {/* Gives extra scrollable space. */} + ) } @@ -58,12 +61,12 @@ function LabwareLocationItemContainer( return ( - {t('slot_location')} - {t('offsets')} + + {t('slot_location')} + + {t('offsets')} - {/* Gives extra scrollable space. */} - ) } @@ -71,47 +74,61 @@ function LabwareLocationItemContainer( function LabwareLocationItem({ runId, offsetDetail, + commandUtils, }: LabwareLocationItemProps): JSX.Element { const { t: commandTextT } = useTranslation('protocol_command_text') + const { toggleRobotMoving, handleCheckItemsPrepModules } = commandUtils const dispatch = useDispatch() const { protocolData } = useSelector( (state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState ) - const displayName = useSelector(selectSelectedLabwareDisplayName(runId)) const selectedLw = useSelector( selectSelectedLabwareInfo(runId) ) as SelectedLabwareInfo + const initialPosition = useSelector(selectSelectedLwInitialPosition(runId)) const slotCopy = getLabwareDisplayLocation({ t: commandTextT, loadedModules: protocolData.modules, loadedLabwares: protocolData.labware, robotType: FLEX_ROBOT_TYPE, - location: { slotName: offsetDetail.locationDetails.slotName }, + location: { slotName: offsetDetail.locationDetails.slotName as string }, detailLevel: 'slot-only', }) const handleOnClick = (): void => { - dispatch( - setSelectedLabware(runId, selectedLw.uri, offsetDetail.locationDetails) - ) + void toggleRobotMoving(true) + .then(() => { + dispatch( + setSelectedLabware( + runId, + selectedLw.uri, + offsetDetail.locationDetails + ) + ) + }) + .then(() => + handleCheckItemsPrepModules( + offsetDetail.locationDetails, + initialPosition + ) + ) + .finally(() => toggleRobotMoving(false)) } return ( - + {/* TODO(jh, 01-30-31): Add a new detail level to getLabwareDisplayLocation instead of slicing. */} + ) } const APPLIED_LOCATION_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; grid-gap: ${SPACING.spacing24}; ` @@ -121,6 +138,7 @@ const HEADER_STYLE = css` ` const LOCATION_ITEM_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; grid-gap: ${SPACING.spacing8}; ` diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx index 6ad56705895..617c3f28d9c 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareDetails/DefaultLocationOffset.tsx @@ -25,8 +25,12 @@ export function DefaultLocationOffset( - {t('default_labware_offset')} - + + {t('default_labware_offset')} + + + + @@ -42,6 +46,7 @@ export function DefaultLocationOffset( const BUTTON_ALL_CONTENT_STYLE = css` grid-gap: ${SPACING.spacing24}; justify-content: ${JUSTIFY_SPACE_BETWEEN}; + width: 100%; ` const BUTTON_LEFT_CONTENT_STYLE = css` @@ -50,6 +55,7 @@ const BUTTON_LEFT_CONTENT_STYLE = css` ` const BUTTON_TEXT_CONTAINER_STYLE = css` + grid-gap: ${SPACING.spacing8}; justify-content: ${JUSTIFY_SPACE_BETWEEN}; align-items: ${ALIGN_CENTER}; ` diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx index 8133d3bca15..da16d7232bc 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/LPCLabwareList/index.tsx @@ -2,7 +2,13 @@ import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' -import { Flex, StyledText, SPACING, ListButton } from '@opentrons/components' +import { + Flex, + StyledText, + SPACING, + ListButton, + DIRECTION_COLUMN, +} from '@opentrons/components' import { selectAllLabwareInfo, @@ -56,6 +62,7 @@ function LabwareItem({ uri, info, runId }: LabwareItemProps): JSX.Element { } const LIST_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; grid-gap: ${SPACING.spacing8}; ` diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareSpace.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareLabware/index.tsx similarity index 94% rename from app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareSpace.tsx rename to app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareLabware/index.tsx index bf7afa1c11a..02143241727 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareSpace.tsx +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/PrepareLabware/index.tsx @@ -44,15 +44,15 @@ interface PrepareSpaceProps extends LPCWizardContentProps { header: ReactNode body: ReactNode confirmPlacement: () => void - labwareInfo: SelectedLabwareInfo + selectedLwInfo: SelectedLabwareInfo } -export function PrepareSpace({ +export function PrepareLabware({ runId, header, body, confirmPlacement, - labwareInfo, + selectedLwInfo, }: PrepareSpaceProps): JSX.Element { const { i18n, t } = useTranslation(['labware_position_check', 'shared']) const { protocolData, deckConfig } = useSelector( @@ -61,8 +61,8 @@ export function PrepareSpace({ const isOnDevice = useSelector(getIsOnDevice) const labwareDef = useSelector( selectSelectedLabwareDef(runId) - ) as LabwareDefinition2 // CheckItem always has lwId on step. - const offsetLocationDetails = labwareInfo.offsetLocationDetails as OffsetLocationDetails + ) as LabwareDefinition2 + const offsetLocationDetails = selectedLwInfo.offsetLocationDetails as OffsetLocationDetails const { moduleModel } = offsetLocationDetails return ( diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts index d1243fd402b..4d2daa14776 100644 --- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts +++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/contants.ts @@ -1,12 +1,13 @@ import { css } from 'styled-components' -import { SPACING } from '@opentrons/components' +import { SPACING, DIRECTION_COLUMN } from '@opentrons/components' /** * Styles */ export const LIST_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; padding: ${SPACING.spacing32} ${SPACING.spacing60}; - grid: ${SPACING.spacing24}; + grid-gap: ${SPACING.spacing24}; ` diff --git a/app/src/redux/protocol-runs/actions/lpc.ts b/app/src/redux/protocol-runs/actions/lpc.ts index fdb735aa9d0..d5a90ec07b5 100644 --- a/app/src/redux/protocol-runs/actions/lpc.ts +++ b/app/src/redux/protocol-runs/actions/lpc.ts @@ -8,6 +8,7 @@ import { SET_SELECTED_LABWARE, CLEAR_SELECTED_LABWARE, SET_SELECTED_LABWARE_NAME, + APPLY_OFFSET, } from '../constants' import type { @@ -23,6 +24,7 @@ import type { ClearSelectedLabwareAction, SelectedLabwareNameAction, OffsetLocationDetails, + ApplyOffsetAction, } from '../types' export const proceedStep = (runId: string): ProceedStepAction => ({ @@ -82,6 +84,14 @@ export const setFinalPosition = ( payload: { ...params, runId }, }) +export const applyOffset = ( + runId: string, + labwareUri: string +): ApplyOffsetAction => ({ + type: APPLY_OFFSET, + payload: { runId, labwareUri }, +}) + export const startLPC = ( runId: string, state: LPCWizardState diff --git a/app/src/redux/protocol-runs/constants/lpc/actions.ts b/app/src/redux/protocol-runs/constants/lpc/actions.ts index eabfe333062..6fd006a9759 100644 --- a/app/src/redux/protocol-runs/constants/lpc/actions.ts +++ b/app/src/redux/protocol-runs/constants/lpc/actions.ts @@ -7,3 +7,4 @@ export const SET_SELECTED_LABWARE = 'SET_SELECTED_LABWARE' export const CLEAR_SELECTED_LABWARE = 'CLEAR_SELECTED_LABWARE' export const SET_INITIAL_POSITION = 'SET_INITIAL_POSITION' export const SET_FINAL_POSITION = 'SET_FINAL_POSITION' +export const APPLY_OFFSET = 'APPLY_OFFSET' diff --git a/app/src/redux/protocol-runs/reducer/index.ts b/app/src/redux/protocol-runs/reducer/index.ts index 48facfbe8d1..6626b30bc0b 100644 --- a/app/src/redux/protocol-runs/reducer/index.ts +++ b/app/src/redux/protocol-runs/reducer/index.ts @@ -32,6 +32,10 @@ export const protocolRunReducer: Reducer = ( case Constants.START_LPC: case Constants.FINISH_LPC: case Constants.PROCEED_STEP: + case Constants.GO_BACK_STEP: + case Constants.SET_SELECTED_LABWARE_NAME: + case Constants.SET_SELECTED_LABWARE: + case Constants.CLEAR_SELECTED_LABWARE: case Constants.SET_INITIAL_POSITION: case Constants.SET_FINAL_POSITION: { const runId = action.payload.runId diff --git a/app/src/redux/protocol-runs/reducer/lpc.ts b/app/src/redux/protocol-runs/reducer/lpc.ts index 84c2de8d864..60c766b7295 100644 --- a/app/src/redux/protocol-runs/reducer/lpc.ts +++ b/app/src/redux/protocol-runs/reducer/lpc.ts @@ -8,6 +8,7 @@ import { GO_BACK_STEP, SET_SELECTED_LABWARE_NAME, CLEAR_SELECTED_LABWARE, + APPLY_OFFSET, } from '../constants' import { updateOffsetsForURI } from './transforms' @@ -125,6 +126,13 @@ export function LPCReducer( } } + case APPLY_OFFSET: { + // TODO(jh, 01-30-25): Update the existing offset in the store, and clear the + // the working offset state. This will break the legacy LPC "apply all offsets" + // functionality, so this must be implemented simultaneously with the API changes. + break + } + case FINISH_LPC: return undefined diff --git a/app/src/redux/protocol-runs/selectors/lpc/labware/index.ts b/app/src/redux/protocol-runs/selectors/lpc/labware/index.ts new file mode 100644 index 00000000000..0cb1066ebb1 --- /dev/null +++ b/app/src/redux/protocol-runs/selectors/lpc/labware/index.ts @@ -0,0 +1,2 @@ +export * from './offsets' +export * from './info' diff --git a/app/src/redux/protocol-runs/selectors/lpc/labware.ts b/app/src/redux/protocol-runs/selectors/lpc/labware/info.ts similarity index 53% rename from app/src/redux/protocol-runs/selectors/lpc/labware.ts rename to app/src/redux/protocol-runs/selectors/lpc/labware/info.ts index b4ab5e54825..c546300ad8e 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/labware.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/labware/info.ts @@ -1,33 +1,20 @@ import { createSelector } from 'reselect' -import { - getIsTiprack, - getLabwareDisplayName, - getVectorDifference, - getVectorSum, - IDENTITY_VECTOR, -} from '@opentrons/shared-data' +import { getIsTiprack, getLabwareDisplayName } from '@opentrons/shared-data' import { getItemLabwareDef, getSelectedLabwareOffsetDetails, - getOffsetDetailsForAllLabware, getSelectedLabwareDefFrom, -} from './transforms' +} from '../transforms' import type { Selector } from 'reselect' -import type { - LabwareOffsetLocation, - VectorOffset, - LabwareOffset, -} from '@opentrons/api-client' -import type { State } from '../../../types' +import type { LabwareOffsetLocation, VectorOffset } from '@opentrons/api-client' +import type { State } from '/app/redux/types' import type { Coordinates, LabwareDefinition2 } from '@opentrons/shared-data' import type { - LabwareDetails, LPCFlowType, LPCLabwareInfo, - OffsetDetails, SelectedLabwareInfo, } from '/app/redux/protocol-runs' @@ -48,23 +35,6 @@ export const selectSelectedLabwareInfo = ( selectedLabware => selectedLabware ?? null ) -export const selectSelectedOffsetDetails = ( - runId: string -): Selector => - createSelector( - (state: State) => - state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware?.uri, - (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, - (uri, lw) => { - if (uri == null || lw == null) { - console.warn('Failed to access labware details.') - return [] - } else { - return lw[uri].offsetDetails ?? [] - } - } - ) - export const selectSelectedLwInitialPosition = ( runId: string ): Selector => @@ -81,76 +51,12 @@ export const selectSelectedLwInitialPosition = ( } ) -export const selectSelectedLwExistingOffset = ( - runId: string -): Selector => - createSelector( - (state: State) => getSelectedLabwareOffsetDetails(runId, state), - details => { - const existingVector = details?.existingOffset?.vector - - if (existingVector == null) { - console.warn('No existing offset vector found for active labware') - return IDENTITY_VECTOR - } else { - return existingVector ?? IDENTITY_VECTOR - } - } - ) - export interface SelectOffsetsToApplyResult { definitionUri: string location: LabwareOffsetLocation vector: Coordinates } -export const selectOffsetsToApply = ( - runId: string -): Selector => - createSelector( - (state: State) => getOffsetDetailsForAllLabware(runId, state), - (state: State) => state.protocolRuns[runId]?.lpc?.protocolData, - (allDetails, protocolData): SelectOffsetsToApplyResult[] => { - if (protocolData == null) { - console.warn('LPC state not initalized before selector use.') - return [] - } - - return allDetails.flatMap( - ({ workingOffset, existingOffset, locationDetails }) => { - const definitionUri = locationDetails.definitionUri - const { initialPosition, finalPosition } = workingOffset ?? {} - - if ( - finalPosition == null || - initialPosition == null || - definitionUri == null || - existingOffset == null - ) { - console.error( - `Cannot generate offsets for labware with incomplete details. ID: ${locationDetails.labwareId}` - ) - return [] - } - - const existingOffsetVector = existingOffset.vector - const finalVector = getVectorSum( - existingOffsetVector, - getVectorDifference(finalPosition, initialPosition) - ) - return [ - { - definitionUri, - location: { ...locationDetails }, - vector: finalVector, - }, - ] - } - ) - } - ) - -// TOME TODO: You can break your selectors down into files along offsets, maybe booleans, etc. export const selectSelectedLabwareFlowType = ( runId: string ): Selector => @@ -229,33 +135,6 @@ export const selectActiveAdapterDisplayName = ( } ) -// TODO(jh, 01-29-25): Revisit this once "View Offsets" is refactored out of LPC. -export const selectLabwareOffsetsForAllLw = ( - runId: string -): Selector => - createSelector( - (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, - (labware): LabwareOffset[] => { - if (labware == null) { - console.warn('Labware info not initialized in state') - return [] - } - - return Object.values(labware).flatMap((details: LabwareDetails) => - details.offsetDetails.map(offsetDetail => ({ - id: details.id, - createdAt: offsetDetail?.existingOffset?.createdAt ?? '', - definitionUri: offsetDetail.locationDetails.definitionUri, - location: { - slotName: offsetDetail.locationDetails.slotName, - moduleModel: offsetDetail.locationDetails.moduleModel, - definitionUri: offsetDetail.locationDetails.definitionUri, - }, - vector: offsetDetail?.existingOffset?.vector ?? IDENTITY_VECTOR, - })) - ) - } - ) export const selectSelectedLabwareDef = ( runId: string ): Selector => diff --git a/app/src/redux/protocol-runs/selectors/lpc/labware/offsets.ts b/app/src/redux/protocol-runs/selectors/lpc/labware/offsets.ts new file mode 100644 index 00000000000..ad0f85f3c83 --- /dev/null +++ b/app/src/redux/protocol-runs/selectors/lpc/labware/offsets.ts @@ -0,0 +1,134 @@ +import { createSelector } from 'reselect' + +import { + getVectorDifference, + getVectorSum, + IDENTITY_VECTOR, +} from '@opentrons/shared-data' + +import { + getSelectedLabwareOffsetDetails, + getOffsetDetailsForAllLabware, +} from '../transforms' + +import type { Selector } from 'reselect' +import type { VectorOffset, LabwareOffset } from '@opentrons/api-client' +import type { State } from '/app/redux/types' +import type { + LabwareDetails, + OffsetDetails, + SelectOffsetsToApplyResult, +} from '/app/redux/protocol-runs' + +export const selectSelectedOffsetDetails = ( + runId: string +): Selector => + createSelector( + (state: State) => + state.protocolRuns[runId]?.lpc?.labwareInfo.selectedLabware?.uri, + (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, + (uri, lw) => { + if (uri == null || lw == null) { + console.warn('Failed to access labware details.') + return [] + } else { + return lw[uri].offsetDetails ?? [] + } + } + ) + +export const selectSelectedLwExistingOffset = ( + runId: string +): Selector => + createSelector( + (state: State) => getSelectedLabwareOffsetDetails(runId, state), + details => { + const existingVector = details?.existingOffset?.vector + + if (existingVector == null) { + console.warn('No existing offset vector found for active labware') + return IDENTITY_VECTOR + } else { + return existingVector ?? IDENTITY_VECTOR + } + } + ) + +export const selectOffsetsToApply = ( + runId: string +): Selector => + createSelector( + (state: State) => getOffsetDetailsForAllLabware(runId, state), + (state: State) => state.protocolRuns[runId]?.lpc?.protocolData, + (allDetails, protocolData): SelectOffsetsToApplyResult[] => { + if (protocolData == null) { + console.warn('LPC state not initalized before selector use.') + return [] + } + + return allDetails.flatMap( + ({ workingOffset, existingOffset, locationDetails }) => { + const definitionUri = locationDetails.definitionUri + const { initialPosition, finalPosition } = workingOffset ?? {} + + if ( + finalPosition == null || + initialPosition == null || + definitionUri == null || + existingOffset == null || + // The slotName is null when applying a default offset. This condition + // is effectively a stub to maintain compatability with the legacy HTTP API, + // and will be refactored soon. + locationDetails.slotName == null + ) { + console.error( + `Cannot generate offsets for labware with incomplete details. ID: ${locationDetails.labwareId}` + ) + return [] + } + + const existingOffsetVector = existingOffset.vector + const finalVector = getVectorSum( + existingOffsetVector, + getVectorDifference(finalPosition, initialPosition) + ) + return [ + { + definitionUri, + location: { ...locationDetails }, + vector: finalVector, + }, + ] + } + ) + } + ) + +// TODO(jh, 01-29-25): Revisit this once "View Offsets" is refactored out of LPC. +export const selectLabwareOffsetsForAllLw = ( + runId: string +): Selector => + createSelector( + (state: State) => state.protocolRuns[runId]?.lpc?.labwareInfo.labware, + (labware): LabwareOffset[] => { + if (labware == null) { + console.warn('Labware info not initialized in state') + return [] + } + + return Object.values(labware).flatMap((details: LabwareDetails) => + details.offsetDetails.map(offsetDetail => ({ + id: details.id, + createdAt: offsetDetail?.existingOffset?.createdAt ?? '', + definitionUri: offsetDetail.locationDetails.definitionUri, + location: { + slotName: + offsetDetail.locationDetails.slotName ?? 'DEFAULT_OFFSET_STUB', + moduleModel: offsetDetail.locationDetails.moduleModel, + definitionUri: offsetDetail.locationDetails.definitionUri, + }, + vector: offsetDetail?.existingOffset?.vector ?? IDENTITY_VECTOR, + })) + ) + } + ) diff --git a/app/src/redux/protocol-runs/selectors/lpc/transforms.ts b/app/src/redux/protocol-runs/selectors/lpc/transforms.ts index f4b2302c4d9..9705fd82b17 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/transforms.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/transforms.ts @@ -45,7 +45,7 @@ export const getSelectedLabwareOffsetDetails = ( return ( offsetDetails?.find(offset => - isEqual(offset.workingOffset, selectedLabware?.offsetLocationDetails) + isEqual(offset.locationDetails, selectedLabware?.offsetLocationDetails) ) ?? null ) } diff --git a/app/src/redux/protocol-runs/types/lpc.ts b/app/src/redux/protocol-runs/types/lpc.ts index a34098b31f5..a0691d37a73 100644 --- a/app/src/redux/protocol-runs/types/lpc.ts +++ b/app/src/redux/protocol-runs/types/lpc.ts @@ -54,11 +54,10 @@ export interface LPCLabwareOffsetAppliedLocationDetails slotName: string } -// TODO(jh, 01-23-25): Revisit working/existing/initialOffsets once API rework becomes more finalized. export interface OffsetDetails { existingOffset: ExistingOffset | null workingOffset: WorkingOffset | null - locationDetails: LPCLabwareOffsetAppliedLocationDetails + locationDetails: OffsetLocationDetails } export interface LabwareDetails { @@ -148,6 +147,11 @@ export interface FinalPositionAction { payload: PositionParams & { runId: string } } +export interface ApplyOffsetAction { + type: 'APPLY_OFFSET' + payload: { runId: string; labwareUri: LabwareURI } +} + export type LPCWizardAction = | StartLPCAction | FinishLPCAction @@ -156,5 +160,6 @@ export type LPCWizardAction = | ClearSelectedLabwareAction | InitialPositionAction | FinalPositionAction + | ApplyOffsetAction | ProceedStepAction | GoBackStepAction From 89709d9bbacd5fc336305c820db3dacb8423c121 Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 30 Jan 2025 14:25:00 -0500 Subject: [PATCH 4/6] test --- .../ProtocolRun/__tests__/ProtocolRunSetup.test.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx index 84e7fb82e65..0dd2bc9557b 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx @@ -33,6 +33,7 @@ import { useRobot, useIsFlex } from '/app/redux-resources/robots' import { useRequiredSetupStepsInOrder } from '/app/redux-resources/runs' import { useStoredProtocolAnalysis } from '/app/resources/analysis' import { getMissingSetupSteps } from '/app/redux/protocol-runs' +import { useLPCFlows } from '/app/organisms/LabwarePositionCheck' import { SetupLabware } from '../SetupLabware' import { SetupRobotCalibration } from '../SetupRobotCalibration' @@ -67,6 +68,7 @@ vi.mock('/app/resources/deck_configuration/hooks') vi.mock('/app/redux-resources/robots') vi.mock('/app/redux-resources/runs') vi.mock('/app/resources/analysis') +vi.mock('/app/organisms/LabwarePositionCheck') vi.mock('@opentrons/shared-data', async importOriginal => { const actualSharedData = await importOriginal() return { @@ -186,6 +188,12 @@ describe('ProtocolRunSetup', () => { when(vi.mocked(useModuleCalibrationStatus)) .calledWith(ROBOT_NAME, RUN_ID) .thenReturn({ complete: true }) + vi.mocked(useLPCFlows).mockReturnValue({ + launchLPC: vi.fn(), + lpcProps: null, + showLPC: false, + isLaunchingLPC: false, + }) }) afterEach(() => { vi.resetAllMocks() From 0ea7da00a4b6b0f8f706e8c00743a93f7b643618 Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Fri, 31 Jan 2025 10:49:48 -0500 Subject: [PATCH 5/6] kind is useful --- .../LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts | 1 + app/src/redux/protocol-runs/selectors/lpc/labware/info.ts | 2 +- app/src/redux/protocol-runs/types/lpc.ts | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts index 7060eaa547f..2d55747c2e6 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/hooks/useLPCLabwareInfo/getLPCLabwareInfoFrom.ts @@ -86,6 +86,7 @@ function getOffsetDetailsForLabware({ ...location, ...restInfo, definitionUri, + kind: 'location-specific', }, } }) diff --git a/app/src/redux/protocol-runs/selectors/lpc/labware/info.ts b/app/src/redux/protocol-runs/selectors/lpc/labware/info.ts index 3c8a5ab2ab4..1dce07cc514 100644 --- a/app/src/redux/protocol-runs/selectors/lpc/labware/info.ts +++ b/app/src/redux/protocol-runs/selectors/lpc/labware/info.ts @@ -70,7 +70,7 @@ export const selectSelectedLabwareFlowType = ( if (selectedLabware?.offsetLocationDetails == null) { return null } else { - if (selectedLabware.offsetLocationDetails.slotName == null) { + if (selectedLabware.offsetLocationDetails.kind === 'default') { return 'default' } else { return 'location-specific' diff --git a/app/src/redux/protocol-runs/types/lpc.ts b/app/src/redux/protocol-runs/types/lpc.ts index a0691d37a73..6dbff4fddc2 100644 --- a/app/src/redux/protocol-runs/types/lpc.ts +++ b/app/src/redux/protocol-runs/types/lpc.ts @@ -13,6 +13,7 @@ type LabwareId = string export type LPCStep = keyof typeof LPC_STEP export type LPCFlowType = 'default' | 'location-specific' +export type LPCOffsetKind = 'default' | 'location-specific' | 'hardcoded' export interface StepInfo { currentStepIndex: number @@ -37,6 +38,7 @@ export interface PositionParams { } interface LPCLabwareOffsetDetails { + kind: LPCOffsetKind labwareId: string definitionUri: string moduleModel?: ModuleModel @@ -47,11 +49,13 @@ interface LPCLabwareOffsetDetails { export interface LPCLabwareOffsetDefaultDetails extends LPCLabwareOffsetDetails { slotName: null + kind: 'default' } export interface LPCLabwareOffsetAppliedLocationDetails extends LPCLabwareOffsetDetails { slotName: string + kind: 'location-specific' } export interface OffsetDetails { From 723d910c1527f51617284f650404f9797b866bae Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Mon, 3 Feb 2025 12:32:29 -0500 Subject: [PATCH 6/6] asterisk --- app/src/redux/protocol-runs/types/lpc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/redux/protocol-runs/types/lpc.ts b/app/src/redux/protocol-runs/types/lpc.ts index 6dbff4fddc2..843ae3e8aaf 100644 --- a/app/src/redux/protocol-runs/types/lpc.ts +++ b/app/src/redux/protocol-runs/types/lpc.ts @@ -79,7 +79,7 @@ export interface SelectedLabwareInfo { id: LabwareId /* Indicates the type of LPC offset flow the user is performing, a "default" flow, a "location-specific" flow, or no active flow. * There is no `slotName` when a user performs the default offset flow. - * Until the user is in a default or location-specific offset flow, there is no location. * */ + * Until the user is in a default or location-specific offset flow, there are no location details. */ offsetLocationDetails: OffsetLocationDetails | null }