diff --git a/app/src/assets/localization/en/labware_position_check.json b/app/src/assets/localization/en/labware_position_check.json index 9f6194717df..2e35b9f3572 100644 --- a/app/src/assets/localization/en/labware_position_check.json +++ b/app/src/assets/localization/en/labware_position_check.json @@ -99,6 +99,7 @@ "stored_offsets_for_this_protocol": "Stored Labware Offset data that applies to this protocol", "table_view": "Table View", "tip_rack": "tip rack", + "view_current_offsets": "view current offsets", "view_data": "View data", "what_is_labware_offset_data": "What is labware offset data?" } diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json index 8bb213e4b6f..ba8af45d0dd 100644 --- a/app/src/assets/localization/en/protocol_setup.json +++ b/app/src/assets/localization/en/protocol_setup.json @@ -125,7 +125,7 @@ "must_have_labware_and_pip": "Protocol must load labware and a pipette", "n_a": "N/A", "no_data": "no data", - "no_labware_offset_data": "No Labware Offset Data yet", + "no_labware_offset_data": "no labware offset data yet", "no_modules_specified": "no modules are specified for this protocol.", "no_modules_used_in_this_protocol": "No modules used in this protocol", "no_tiprack_loaded": "Protocol must load a tip rack", diff --git a/app/src/molecules/WizardRequiredEquipmentList/index.tsx b/app/src/molecules/WizardRequiredEquipmentList/index.tsx index 467fd749b66..35b87750b4b 100644 --- a/app/src/molecules/WizardRequiredEquipmentList/index.tsx +++ b/app/src/molecules/WizardRequiredEquipmentList/index.tsx @@ -49,7 +49,6 @@ export function WizardRequiredEquipmentList( > {t('you_will_need')} - ) : ( <> - + {t('you_will_need')} - - {equipmentList.map(requiredEquipmentProps => ( + {equipmentList.length > 1 ? : null} + {equipmentList.map((requiredEquipmentProps, index) => ( ))} {footer != null ? ( @@ -106,12 +110,13 @@ interface RequiredEquipmentCardProps { loadName: string displayName: string subtitle?: string + bottomDivider?: boolean } function RequiredEquipmentCard(props: RequiredEquipmentCardProps): JSX.Element { - const { loadName, displayName, subtitle } = props + const { loadName, displayName, subtitle, bottomDivider = true } = props - let imageSrc: string = labwareImages.generic_custom_tiprack + let imageSrc: string | null = null if (loadName in labwareImages) { imageSrc = labwareImages[loadName as keyof typeof labwareImages] } else if (loadName in equipmentImages) { @@ -125,24 +130,26 @@ function RequiredEquipmentCard(props: RequiredEquipmentCardProps): JSX.Element { alignItems={ALIGN_CENTER} width="100%" > - - {displayName} - + {imageSrc != null ? ( + + {displayName} + + ) : null} - + {bottomDivider ? : null} ) } diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx index 951bcd88be0..36f14a0a7ff 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx @@ -8,6 +8,11 @@ import { COLORS, DIRECTION_COLUMN, SPACING, + Icon, + SIZE_1, + DIRECTION_ROW, + TYPOGRAPHY, + Link, } from '@opentrons/components' import { Line } from '../../../atoms/structure' @@ -20,6 +25,7 @@ import { useRunHasStarted, useProtocolAnalysisErrors, useStoredProtocolAnalysis, + ProtocolCalibrationStatus, } from '../hooks' import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { SetupLabware } from './SetupLabware' @@ -29,6 +35,7 @@ import { SetupModules } from './SetupModules' import { SetupStep } from './SetupStep' import { SetupLiquids } from './SetupLiquids' import { EmptySetupStep } from './EmptySetupStep' +import { HowLPCWorksModal } from './SetupLabwarePositionCheck/HowLPCWorksModal' const ROBOT_CALIBRATION_STEP_KEY = 'robot_calibration_step' as const const MODULE_SETUP_KEY = 'module_setup_step' as const @@ -207,10 +214,10 @@ export function ProtocolRunSetup({ ? setExpandedStepKey(null) : setExpandedStepKey(stepKey) } - calibrationStatusComplete={ - stepKey === ROBOT_CALIBRATION_STEP_KEY && !runHasStarted - ? calibrationStatus.complete - : null + rightElement={ + } > {StepDetailMap[stepKey].stepInternals} @@ -231,3 +238,70 @@ export function ProtocolRunSetup({ ) } + +interface StepRightElementProps { + stepKey: StepKey + calibrationStatus: ProtocolCalibrationStatus + runHasStarted: boolean +} +function StepRightElement(props: StepRightElementProps): JSX.Element | null { + const { stepKey, calibrationStatus, runHasStarted } = props + const { t } = useTranslation('protocol_setup') + + if (stepKey === ROBOT_CALIBRATION_STEP_KEY && !runHasStarted) { + return ( + + + + {calibrationStatus.complete + ? t('calibration_ready') + : t('calibration_needed')} + + + ) + } else if (stepKey === LPC_KEY) { + return + } else { + return null + } +} + +function LearnAboutLPC(): JSX.Element { + const { t } = useTranslation('protocol_setup') + const [showLPCHelpModal, setShowLPCHelpModal] = React.useState(false) + return ( + <> + { + // clicking link shouldn't toggle step expanded state + e.preventDefault() + e.stopPropagation() + setShowLPCHelpModal(true) + }} + > + {t('learn_how_it_works')} + + {showLPCHelpModal ? ( + setShowLPCHelpModal(false)} /> + ) : null} + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx index 038db0c26c7..65ce62c90e1 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx @@ -7,11 +7,11 @@ import { DIRECTION_COLUMN, ALIGN_CENTER, TYPOGRAPHY, - Link, TOOLTIP_LEFT, useHoverTooltip, SecondaryButton, PrimaryButton, + COLORS, } from '@opentrons/components' import { useRunQuery } from '@opentrons/react-api-client' import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' @@ -20,7 +20,6 @@ import { Tooltip } from '../../../../atoms/Tooltip' import { useLPCDisabledReason, useStoredProtocolAnalysis } from '../../hooks' import { CurrentOffsetsTable } from './CurrentOffsetsTable' import { useLaunchLPC } from '../../../LabwarePositionCheck/useLaunchLPC' -import { HowLPCWorksModal } from './HowLPCWorksModal' import { StyledText } from '../../../../atoms/text' interface SetupLabwarePositionCheckProps { @@ -33,7 +32,7 @@ export function SetupLabwarePositionCheck( props: SetupLabwarePositionCheckProps ): JSX.Element { const { robotName, runId, expandLabwareStep } = props - const { t } = useTranslation('protocol_setup') + const { t, i18n } = useTranslation('protocol_setup') const { data: runRecord } = useRunQuery(runId, { staleTime: Infinity }) const currentOffsets = runRecord?.data?.labwareOffsets ?? [] @@ -41,7 +40,6 @@ export function SetupLabwarePositionCheck( const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) const protocolData = robotProtocolAnalysis ?? storedProtocolAnalysis - const [showHelpModal, setShowHelpModal] = React.useState(false) const [targetProps, tooltipProps] = useHoverTooltip({ placement: TOOLTIP_LEFT, }) @@ -56,18 +54,27 @@ export function SetupLabwarePositionCheck( marginTop={SPACING.spacing16} gridGap={SPACING.spacing16} > - - setShowHelpModal(true)} + {currentOffsets.length > 0 ? ( + + ) : ( + - {t('learn_how_it_works')} - + + {i18n.format(t('no_labware_offset_data'), 'capitalize')} + + + )} + {lpcDisabledReason} ) : null} - - {currentOffsets.length > 0 ? ( - - ) : ( - - {t('no_labware_offset_data')} - - )} - - {LPCWizard} - {showHelpModal ? ( - setShowHelpModal(false)} /> - ) : null} ) } diff --git a/app/src/organisms/Devices/ProtocolRun/SetupStep.tsx b/app/src/organisms/Devices/ProtocolRun/SetupStep.tsx index c7808bb373b..dbf0d910c36 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupStep.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupStep.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { @@ -11,7 +10,6 @@ import { DIRECTION_COLUMN, DIRECTION_ROW, JUSTIFY_SPACE_BETWEEN, - SIZE_1, COLORS, SPACING, TYPOGRAPHY, @@ -20,13 +18,20 @@ import { import { StyledText } from '../../../atoms/text' interface SetupStepProps { + /** whether or not to show the full contents of the step */ expanded: boolean + /** always shown text name of the step */ title: React.ReactNode + /** always shown text that provides a one sentence explanation of the contents */ description: string + /** always shown text that sits above title of step (used for step number) */ label: string + /** callback that should toggle the expanded state (managed by parent) */ toggleExpanded: () => void + /** contents to be shown only when expanded */ children: React.ReactNode - calibrationStatusComplete: boolean | null + /** element to be shown (right aligned) regardless of expanded state */ + rightElement: React.ReactNode } const EXPANDED_STYLE = css` @@ -57,10 +62,8 @@ export function SetupStep({ label, toggleExpanded, children, - calibrationStatusComplete, + rightElement, }: SetupStepProps): JSX.Element { - const { t } = useTranslation('protocol_setup') - return ( @@ -100,34 +103,7 @@ export function SetupStep({ - {calibrationStatusComplete !== null ? ( - - - - {calibrationStatusComplete - ? t('calibration_ready') - : t('calibration_needed')} - - - ) : null} + {rightElement} { title = 'stub title', description = 'stub description', label = 'stub label', - calibrationStatusComplete = null, toggleExpanded = toggleExpandedMock, children = , + rightElement =
right element
, }: Partial> = {}) => { return renderWithProviders( { label, toggleExpanded, children, - calibrationStatusComplete, + rightElement, }} />, { i18nInstance: i18n } @@ -55,5 +55,6 @@ describe('SetupStep', () => { getByText('stub label') getByText('stub title') queryAllByText('stub description') + queryAllByText('right element') }) }) diff --git a/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx b/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx index c2fb104b14b..8bba4649b69 100644 --- a/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx @@ -1,6 +1,9 @@ import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' -import { CompletedProtocolAnalysis } from '@opentrons/shared-data' +import { + CompletedProtocolAnalysis, + LabwareDefinition2, +} from '@opentrons/shared-data' import { StyledText } from '../../../atoms/text' import { RobotMotionLoader } from '../RobotMotionLoader' import { getPrepCommands } from './getPrepCommands' @@ -8,11 +11,34 @@ import { useChainRunCommands } from '../../../resources/runs/hooks' import type { RegisterPositionAction } from '../types' import type { Jog } from '../../../molecules/JogControls' import { WizardRequiredEquipmentList } from '../../../molecules/WizardRequiredEquipmentList' -import { GenericWizardTile } from '../../../molecules/GenericWizardTile' import { getIsOnDevice } from '../../../redux/config' +import { NeedHelpLink } from '../../CalibrationPanels' import { useSelector } from 'react-redux' +import { TwoUpTileLayout } from '../TwoUpTileLayout' +import { + ALIGN_CENTER, + Box, + Btn, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + PrimaryButton, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { LabwareOffset } from '@opentrons/api-client' +import { css } from 'styled-components' +import { Portal } from '../../../App/portal' +import { LegacyModalShell } from '../../../molecules/LegacyModal' +import { SmallButton } from '../../../atoms/buttons' +import { TerseOffsetTable } from '../ResultsSummary' +import { getLabwareDefinitionsFromCommands } from '../utils/labware' export const INTERVAL_MS = 3000 + +// 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' export const IntroScreen = (props: { @@ -23,6 +49,7 @@ export const IntroScreen = (props: { handleJog: Jog setFatalError: (errorMessage: string) => void isRobotMoving: boolean + existingOffsets: LabwareOffset[] }): JSX.Element | null => { const { proceed, @@ -30,9 +57,10 @@ export const IntroScreen = (props: { chainRunCommands, isRobotMoving, setFatalError, + existingOffsets, } = props const isOnDevice = useSelector(getIsOnDevice) - const { t } = useTranslation(['labware_position_check', 'shared']) + const { t, i18n } = useTranslation(['labware_position_check', 'shared']) const handleClickStartLPC = (): void => { const prepCommands = getPrepCommands(protocolData) chainRunCommands(prepCommands, false) @@ -50,17 +78,16 @@ export const IntroScreen = (props: { ) } return ( - }} /> } - rightHandBody={ + rightElement={ } - proceedButtonText={t('shared:get_started')} - proceed={handleClickStartLPC} + footer={ + + {isOnDevice ? ( + + ) : ( + + )} + {isOnDevice ? ( + + ) : ( + + {i18n.format(t('shared:get_started'), 'capitalize')} + + )} + + } /> ) } + +const VIEW_OFFSETS_BUTTON_STYLE = css` + ${TYPOGRAPHY.pSemiBold}; + color: ${COLORS.darkBlackEnabled}; + font-size: ${TYPOGRAPHY.fontSize22}; + &:hover { + opacity: 100%; + } + &:active { + opacity: 70%; + } +` +interface ViewOffsetsProps { + existingOffsets: LabwareOffset[] + labwareDefinitions: LabwareDefinition2[] +} +function ViewOffsets(props: ViewOffsetsProps): JSX.Element { + const { existingOffsets, labwareDefinitions } = props + const { t, i18n } = useTranslation('labware_position_check') + const [showOffsetsTable, setShowOffsetsModal] = React.useState(false) + return existingOffsets.length > 0 ? ( + <> + setShowOffsetsModal(true)} + css={VIEW_OFFSETS_BUTTON_STYLE} + aria-label="show labware offsets" + > + + + {i18n.format(t('view_current_offsets'), 'capitalize')} + + + {showOffsetsTable ? ( + + + {i18n.format(t('labware_offset_data'), 'capitalize')} + + } + footer={ + setShowOffsetsModal(false)} + /> + } + > + + + + + + ) : null} + + ) : ( + + ) +} diff --git a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx index 9bcdfb1644f..6ca827aae9d 100644 --- a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx +++ b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx @@ -263,7 +263,9 @@ export const LabwarePositionCheckComponent = ( /> ) } else if (currentStep.section === 'BEFORE_BEGINNING') { - modalContent = + modalContent = ( + + ) } else if ( currentStep.section === 'CHECK_POSITIONS' || currentStep.section === 'CHECK_TIP_RACKS' || diff --git a/app/src/organisms/LabwarePositionCheck/TwoUpTileLayout.tsx b/app/src/organisms/LabwarePositionCheck/TwoUpTileLayout.tsx new file mode 100644 index 00000000000..44ee775f25a --- /dev/null +++ b/app/src/organisms/LabwarePositionCheck/TwoUpTileLayout.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import styled, { css } from 'styled-components' +import { + DIRECTION_COLUMN, + Flex, + SPACING, + JUSTIFY_SPACE_BETWEEN, + DIRECTION_ROW, + TYPOGRAPHY, + JUSTIFY_CENTER, + RESPONSIVENESS, + DISPLAY_INLINE_BLOCK, +} from '@opentrons/components' + +const Title = styled.h1` + ${TYPOGRAPHY.h1Default}; + margin-bottom: ${SPACING.spacing8}; + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + ${TYPOGRAPHY.level4HeaderSemiBold}; + margin-bottom: 0; + height: ${SPACING.spacing40}; + display: ${DISPLAY_INLINE_BLOCK}; + } +` + +const TILE_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + justify-content: ${JUSTIFY_SPACE_BETWEEN}; + padding: ${SPACING.spacing32}; + height: 24.625rem; + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + height: 29.5rem; + } +` +export interface TwoUpTileLayoutProps { + /** main header text on left half */ + title: string + /** paragraph text below title on left half */ + body: React.ReactNode + /** entire contents of the right half */ + rightElement: React.ReactNode + /** footer underneath both halves of content */ + footer: React.ReactNode +} + +export function TwoUpTileLayout(props: TwoUpTileLayoutProps): JSX.Element { + const { title, body, rightElement, footer } = props + return ( + + + + {title} + {body} + + + {rightElement} + + + {footer} + + ) +}