diff --git a/app/src/assets/localization/en/protocol_command_text.json b/app/src/assets/localization/en/protocol_command_text.json index 069f6e13886..23b2285bbcc 100644 --- a/app/src/assets/localization/en/protocol_command_text.json +++ b/app/src/assets/localization/en/protocol_command_text.json @@ -17,36 +17,47 @@ "deactivating_tc_lid": "Deactivating Thermocycler lid", "degrees_c": "{{temp}}°C", "disengaging_magnetic_module": "Disengaging Magnetic Module", - "dispense_push_out": "Dispensing {{volume}} µL into well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec and pushing out {{push_out_volume}} µL", "dispense": "Dispensing {{volume}} µL into well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec", "dispense_in_place": "Dispensing {{volume}} µL in place at {{flow_rate}} µL/sec", + "dispense_push_out": "Dispensing {{volume}} µL into well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec and pushing out {{push_out_volume}} µL", "drop_tip": "Dropping tip in {{well_name}} of {{labware}}", "drop_tip_in_place": "Dropping tip in place", "engaging_magnetic_module": "Engaging Magnetic Module", "fixed_trash": "Fixed Trash", "home_gantry": "Homing all gantry, pipette, and plunger axes", "latching_hs_latch": "Latching labware on Heater-Shaker", - "module_in_slot_plural": "{{module}}", + "left": "Left", + "load_labware_info_protocol_setup": "Load {{labware}} in {{module_name}} in Slot {{slot_name}}", + "load_labware_info_protocol_setup_adapter": "Load {{labware}} in {{adapter_name}} in Slot {{slot_name}}", + "load_labware_info_protocol_setup_adapter_module": "Load {{labware}} in {{adapter_name}} in {{module_name}} in Slot {{slot_name}}", + "load_labware_info_protocol_setup_adapter_off_deck": "Load {{labware}} in {{adapter_name}} off deck", + "load_labware_info_protocol_setup_no_module": "Load {{labware}} in Slot {{slot_name}}", + "load_labware_info_protocol_setup_off_deck": "Load {{labware}} off deck", + "load_liquids_info_protocol_setup": "Load {{liquid}} into {{labware}}", + "load_module_protocol_setup": "Load {{module}} in Slot {{slot_name}}", + "load_pipette_protocol_setup": "Load {{pipette_name}} in {{mount_name}} Mount", "module_in_slot": "{{module}} in Slot {{slot_name}}", + "module_in_slot_plural": "{{module}}", + "move_labware": "Move Labware", "move_labware_manually": "Manually move {{labware}} from {{old_location}} to {{new_location}}", "move_labware_on": "Move labware on {{robot_name}}", "move_labware_using_gripper": "Moving {{labware}} using gripper from {{old_location}} to {{new_location}}", - "move_labware": "Move Labware", "move_relative": "Moving {{distance}} mm along {{axis}} axis", + "move_to_addressable_area": "Moving to {{addressable_area}}", + "move_to_addressable_area_drop_tip": "Moving to {{addressable_area}}", "move_to_coordinates": "Moving to (X: {{x}}, Y: {{y}}, Z: {{z}})", "move_to_slot": "Moving to Slot {{slot_name}}", "move_to_well": "Moving to well {{well_name}} of {{labware}} in {{labware_location}}", - "move_to_addressable_area": "Moving to {{addressable_area}}", - "move_to_addressable_area_drop_tip": "Moving to {{addressable_area}}", "notes": "notes", "off_deck": "off deck", "offdeck": "offdeck", "opening_tc_lid": "Opening Thermocycler lid", - "pause_on": "Pause on {{robot_name}}", "pause": "Pause", + "pause_on": "Pause on {{robot_name}}", "pickup_tip": "Picking up tip(s) from {{well_range}} of {{labware}} in {{labware_location}}", "prepare_to_aspirate": "Preparing {{pipette}} to aspirate", "return_tip": "Returning tip to {{well_name}} of {{labware}} in {{labware_location}}", + "right": "Right", "save_position": "Saving position", "set_and_await_hs_shake": "Setting Heater-Shaker to shake at {{rpm}} rpm and waiting until reached", "setting_hs_temp": "Setting Target Temperature of Heater-Shaker to {{temp}}", @@ -58,8 +69,8 @@ "tc_awaiting_for_duration": "Waiting for Thermocycler profile to complete", "tc_run_profile_steps": "temperature: {{celsius}}°C, seconds: {{seconds}}", "tc_starting_profile": "Thermocycler starting {{repetitions}} repetitions of cycle composed of the following steps:", - "trash_bin_in_slot": "Trash Bin in {{slot_name}}", "touch_tip": "Touching tip", + "trash_bin_in_slot": "Trash Bin in {{slot_name}}", "unlatching_hs_latch": "Unlatching labware on Heater-Shaker", "wait_for_duration": "Pausing for {{seconds}} seconds. {{message}}", "wait_for_resume": "Pausing protocol", diff --git a/app/src/assets/localization/en/run_details.json b/app/src/assets/localization/en/run_details.json index 2a3e27cf0f3..9209e9e5fc2 100644 --- a/app/src/assets/localization/en/run_details.json +++ b/app/src/assets/localization/en/run_details.json @@ -49,17 +49,8 @@ "labware_offset_data": "labware offset data", "left": "Left", "listed_values": "Listed values are view-only", - "load_labware_info_protocol_setup": "Load {{labware}} in {{module_name}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_adapter": "Load {{labware}} in {{adapter_name}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_adapter_module": "Load {{labware}} in {{adapter_name}} in {{module_name}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_adapter_off_deck": "Load {{labware}} in {{adapter_name}} off deck", - "load_labware_info_protocol_setup_no_module": "Load {{labware}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_off_deck": "Load {{labware}} off deck", "load_labware_info_protocol_setup_plural": "Load {{labware}} in {{module_name}}", - "load_liquids_info_protocol_setup": "Load {{liquid}} into {{labware}}", - "load_module_protocol_setup": "Load {{module}} in Slot {{slot_name}}", "load_module_protocol_setup_plural": "Load {{module}}", - "load_pipette_protocol_setup": "Load {{pipette_name}} in {{mount_name}} Mount", "loading_data": "Loading data...", "loading_protocol": "Loading Protocol", "location": "location", diff --git a/app/src/molecules/Command/CommandText.tsx b/app/src/molecules/Command/CommandText.tsx index a227b1bf74f..f579c41ea8d 100644 --- a/app/src/molecules/Command/CommandText.tsx +++ b/app/src/molecules/Command/CommandText.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { useTranslation } from 'react-i18next' import { pick } from 'lodash' + import { ALIGN_CENTER, DIRECTION_COLUMN, @@ -11,45 +11,12 @@ import { RESPONSIVENESS, styleProps, } from '@opentrons/components' -import { getPipetteNameSpecs } from '@opentrons/shared-data' -import { - getAddressableAreaDisplayName, - getLabwareName, - getLabwareDisplayLocation, - getFinalLabwareLocation, -} from './utils' -import { LoadCommandText } from './LoadCommandText' -import { PipettingCommandText } from './PipettingCommandText' -import { TemperatureCommandText } from './TemperatureCommandText' -import { MoveLabwareCommandText } from './MoveLabwareCommandText' + +import { useCommandTextString } from './hooks' import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' import type { StyleProps } from '@opentrons/components' import type { CommandTextData } from './types' -import type { TFunction } from 'i18next' - -const SIMPLE_TRANSLATION_KEY_BY_COMMAND_TYPE: { - [commandType in RunTimeCommand['commandType']]?: string -} = { - home: 'home_gantry', - savePosition: 'save_position', - touchTip: 'touch_tip', - 'magneticModule/engage': 'engaging_magnetic_module', - 'magneticModule/disengage': 'disengaging_magnetic_module', - 'temperatureModule/deactivate': 'deactivate_temperature_module', - 'thermocycler/waitForBlockTemperature': 'waiting_for_tc_block_to_reach', - 'thermocycler/waitForLidTemperature': 'waiting_for_tc_lid_to_reach', - 'thermocycler/openLid': 'opening_tc_lid', - 'thermocycler/closeLid': 'closing_tc_lid', - 'thermocycler/deactivateBlock': 'deactivating_tc_block', - 'thermocycler/deactivateLid': 'deactivating_tc_lid', - 'thermocycler/awaitProfileComplete': 'tc_awaiting_for_duration', - 'heaterShaker/deactivateHeater': 'deactivating_hs_heater', - 'heaterShaker/openLabwareLatch': 'unlatching_hs_latch', - 'heaterShaker/closeLabwareLatch': 'latching_hs_latch', - 'heaterShaker/deactivateShaker': 'deactivate_hs_shake', - 'heaterShaker/waitForTemperature': 'waiting_for_hs_to_reach', -} interface LegacySTProps { as?: React.ComponentProps['as'] @@ -64,7 +31,7 @@ interface ModernSTProps { type STProps = LegacySTProps | ModernSTProps -interface Props extends StyleProps { +interface BaseProps extends StyleProps { command: RunTimeCommand commandTextData: CommandTextData robotType: RobotType @@ -72,372 +39,23 @@ interface Props extends StyleProps { propagateCenter?: boolean propagateTextLimit?: boolean } -export function CommandText(props: Props & STProps): JSX.Element | null { - const { - command, - commandTextData, - robotType, - propagateCenter = false, - propagateTextLimit = false, - ...styleProps - } = props - const { t } = useTranslation('protocol_command_text') - const shouldPropagateCenter = props.isOnDevice === true || propagateCenter - const shouldPropagateTextLimit = - props.isOnDevice === true || propagateTextLimit +export function CommandText(props: BaseProps & STProps): JSX.Element | null { + const { commandText, stepTexts } = useCommandTextString({ + ...props, + }) - switch (command.commandType) { - case 'aspirate': - case 'aspirateInPlace': - case 'dispense': - case 'dispenseInPlace': - case 'blowout': - case 'blowOutInPlace': - case 'dropTip': - case 'dropTipInPlace': - case 'pickUpTip': { - return ( - - - - ) - } - case 'loadLabware': - case 'loadPipette': - case 'loadModule': - case 'loadLiquid': { - return ( - - - - ) - } - case 'temperatureModule/setTargetTemperature': - case 'temperatureModule/waitForTemperature': - case 'thermocycler/setTargetBlockTemperature': - case 'thermocycler/setTargetLidTemperature': - case 'heaterShaker/setTargetTemperature': { - return ( - - - - ) - } + switch (props.command.commandType) { case 'thermocycler/runProfile': { - const { profile } = command.params - const steps = profile.map( - ({ holdSeconds, celsius }: { holdSeconds: number; celsius: number }) => - t('tc_run_profile_steps', { - celsius, - seconds: holdSeconds, - }).trim() - ) - return ( - // TODO(sfoster): Command sometimes wraps this in a cascaded display: -webkit-box - // to achieve multiline text clipping with an automatically inserted ellipsis, which works - // everywhere except for here where it overrides this property in the flex since this is - // the only place where CommandText uses a flex. - // The right way to handle this is probably to take the css that's in Command and make it - // live here instead, but that should be done in a followup since it would touch everything. - // See also the margin-left on the
  • s, which is needed to prevent their bullets from - // clipping if a container set overflow: hidden. - - - {t('tc_starting_profile', { - repetitions: Object.keys(steps).length, - })} - - -
      - {shouldPropagateTextLimit ? ( -
    • - {steps[0]} -
    • - ) : ( - steps.map((step: string, index: number) => ( -
    • - {' '} - {step} -
    • - )) - )} -
    -
    -
    - ) - } - case 'heaterShaker/setAndWaitForShakeSpeed': { - const { rpm } = command.params - return ( - - {t('set_and_await_hs_shake', { rpm })} - - ) - } - case 'moveToSlot': { - const { slotName } = command.params - return ( - - {t('move_to_slot', { slot_name: slotName })} - - ) - } - case 'moveRelative': { - const { axis, distance } = command.params - return ( - - {t('move_relative', { axis, distance })} - - ) - } - case 'moveToCoordinates': { - const { coordinates } = command.params - return ( - - {t('move_to_coordinates', coordinates)} - - ) - } - case 'moveToWell': { - const { wellName, labwareId } = command.params - const allPreviousCommands = commandTextData.commands.slice( - 0, - commandTextData.commands.findIndex(c => c.id === command.id) - ) - const labwareLocation = getFinalLabwareLocation( - labwareId, - allPreviousCommands - ) - const displayLocation = - labwareLocation != null - ? getLabwareDisplayLocation( - commandTextData, - labwareLocation, - t as TFunction, - robotType - ) - : '' - return ( - - {t('move_to_well', { - well_name: wellName, - labware: getLabwareName(commandTextData, labwareId), - labware_location: displayLocation, - })} - - ) - } - case 'moveLabware': { - return ( - - - - ) - } - case 'configureForVolume': { - const { volume, pipetteId } = command.params - const pipetteName = commandTextData.pipettes.find( - pip => pip.id === pipetteId - )?.pipetteName - - return ( - - {t('configure_for_volume', { - volume, - pipette: - pipetteName != null - ? getPipetteNameSpecs(pipetteName)?.displayName - : '', - })} - - ) - } - case 'configureNozzleLayout': { - const { configurationParams, pipetteId } = command.params - const pipetteName = commandTextData.pipettes.find( - pip => pip.id === pipetteId - )?.pipetteName - - // TODO (sb, 11/9/23): Add support for other configurations when needed - return ( - - {t('configure_nozzle_layout', { - amount: configurationParams.style === 'COLUMN' ? '8' : 'all', - pipette: - pipetteName != null - ? getPipetteNameSpecs(pipetteName)?.displayName - : '', - })} - - ) - } - case 'prepareToAspirate': { - const { pipetteId } = command.params - const pipetteName = commandTextData.pipettes.find( - pip => pip.id === pipetteId - )?.pipetteName - - return ( - - {t('prepare_to_aspirate', { - pipette: - pipetteName != null - ? getPipetteNameSpecs(pipetteName)?.displayName - : '', - })} - - ) - } - case 'moveToAddressableArea': { - const addressableAreaDisplayName = getAddressableAreaDisplayName( - commandTextData, - command.id, - t as TFunction - ) - - return ( - - {t('move_to_addressable_area', { - addressable_area: addressableAreaDisplayName, - })} - - ) - } - case 'moveToAddressableAreaForDropTip': { - const addressableAreaDisplayName = getAddressableAreaDisplayName( - commandTextData, - command.id, - t as TFunction - ) - return ( - - {t('move_to_addressable_area_drop_tip', { - addressable_area: addressableAreaDisplayName, - })} - - ) - } - case 'touchTip': - case 'home': - case 'savePosition': - case 'magneticModule/engage': - case 'magneticModule/disengage': - case 'temperatureModule/deactivate': - case 'thermocycler/waitForBlockTemperature': - case 'thermocycler/waitForLidTemperature': - case 'thermocycler/openLid': - case 'thermocycler/closeLid': - case 'thermocycler/deactivateBlock': - case 'thermocycler/deactivateLid': - case 'thermocycler/awaitProfileComplete': - case 'heaterShaker/deactivateHeater': - case 'heaterShaker/openLabwareLatch': - case 'heaterShaker/closeLabwareLatch': - case 'heaterShaker/deactivateShaker': - case 'heaterShaker/waitForTemperature': { - const simpleTKey = - SIMPLE_TRANSLATION_KEY_BY_COMMAND_TYPE[command.commandType] - return ( - - {simpleTKey != null ? t(simpleTKey) : null} - - ) - } - case 'waitForDuration': { - const { seconds, message } = command.params - return ( - - {t('wait_for_duration', { seconds, message: message ?? '' })} - - ) - } - case 'pause': // legacy pause command - case 'waitForResume': { - return ( - - {command.params?.message != null && command.params.message !== '' - ? command.params.message - : t('wait_for_resume')} - - ) - } - case 'delay': { - // legacy delay command - const { message = '' } = command.params - if ('waitForResume' in command.params) { - return ( - - {command.params?.message != null && command.params.message !== '' - ? command.params.message - : t('wait_for_resume')} - - ) - } else { - return ( - - {t('wait_for_duration', { - seconds: command.params.seconds, - message, - })} - - ) - } - } - case 'comment': { - const { message } = command.params - return {message} - } - case 'custom': { - const { legacyCommandText } = command.params ?? {} - const sanitizedCommandText = - typeof legacyCommandText === 'object' - ? JSON.stringify(legacyCommandText) - : String(legacyCommandText) return ( - - {legacyCommandText != null - ? sanitizedCommandText - : `${command.commandType}: ${JSON.stringify(command.params)}`} - + ) } default: { - console.warn( - 'CommandText encountered a command with an unrecognized commandType: ', - command - ) - return ( - - {JSON.stringify(command)} - - ) + return {commandText} } } } @@ -473,3 +91,82 @@ function CommandStyledText( ) } } + +type ThermocyclerRunProfileProps = BaseProps & + STProps & { + commandText: string + stepTexts?: string[] + } + +function ThermocyclerRunProfile( + props: ThermocyclerRunProfileProps +): JSX.Element { + const { + isOnDevice, + propagateCenter = false, + propagateTextLimit = false, + commandText, + stepTexts, + ...styleProps + } = props + + const shouldPropagateCenter = isOnDevice === true || propagateCenter + const shouldPropagateTextLimit = isOnDevice === true || propagateTextLimit + + // TODO(sfoster): Command sometimes wraps this in a cascaded display: -webkit-box + // to achieve multiline text clipping with an automatically inserted ellipsis, which works + // everywhere except for here where it overrides this property in the flex since this is + // the only place where CommandText uses a flex. + // The right way to handle this is probably to take the css that's in Command and make it + // live here instead, but that should be done in a followup since it would touch everything. + // See also the margin-left on the
  • s, which is needed to prevent their bullets from + // clipping if a container set overflow: hidden. + return ( + + + {commandText} + + +
      + {shouldPropagateTextLimit ? ( +
    • + {stepTexts?.[0]} +
    • + ) : ( + stepTexts?.map((step: string, index: number) => ( +
    • + {' '} + {step} +
    • + )) + )} +
    +
    +
    + ) +} diff --git a/app/src/molecules/Command/MoveLabwareCommandText.tsx b/app/src/molecules/Command/MoveLabwareCommandText.tsx deleted file mode 100644 index 030177fddfc..00000000000 --- a/app/src/molecules/Command/MoveLabwareCommandText.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA } from '@opentrons/shared-data' -import { getLabwareName } from './utils' -import { getLabwareDisplayLocation } from './utils/getLabwareDisplayLocation' -import { getFinalLabwareLocation } from './utils/getFinalLabwareLocation' -import type { - MoveLabwareRunTimeCommand, - RobotType, -} from '@opentrons/shared-data' -import type { CommandTextData } from './types' -import type { TFunction } from 'i18next' - -interface MoveLabwareCommandTextProps { - command: MoveLabwareRunTimeCommand - commandTextData: CommandTextData - robotType: RobotType -} -export function MoveLabwareCommandText( - props: MoveLabwareCommandTextProps -): JSX.Element { - const { t } = useTranslation('protocol_command_text') - const { command, commandTextData, robotType } = props - const { labwareId, newLocation, strategy } = command.params - - const allPreviousCommands = commandTextData.commands.slice( - 0, - commandTextData.commands.findIndex(c => c.id === command.id) - ) - const oldLocation = getFinalLabwareLocation(labwareId, allPreviousCommands) - const newDisplayLocation = getLabwareDisplayLocation( - commandTextData, - newLocation, - t as TFunction, - robotType - ) - - const location = newDisplayLocation.includes( - GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA - ) - ? 'Waste Chute' - : newDisplayLocation - - return strategy === 'usingGripper' - ? t('move_labware_using_gripper', { - labware: getLabwareName(commandTextData, labwareId), - old_location: - oldLocation != null - ? getLabwareDisplayLocation( - commandTextData, - oldLocation, - t as TFunction, - robotType - ) - : '', - new_location: location, - }) - : t('move_labware_manually', { - labware: getLabwareName(commandTextData, labwareId), - old_location: - oldLocation != null - ? getLabwareDisplayLocation( - commandTextData, - oldLocation, - t as TFunction, - robotType - ) - : '', - new_location: location, - }) -} diff --git a/app/src/molecules/Command/hooks/index.ts b/app/src/molecules/Command/hooks/index.ts new file mode 100644 index 00000000000..6b6545c7689 --- /dev/null +++ b/app/src/molecules/Command/hooks/index.ts @@ -0,0 +1,7 @@ +export { useCommandTextString } from './useCommandTextString' + +export type { + UseCommandTextStringParams, + GetCommandText, + GetCommandTextResult, +} from './useCommandTextString' diff --git a/app/src/molecules/Command/hooks/useCommandTextString/index.tsx b/app/src/molecules/Command/hooks/useCommandTextString/index.tsx new file mode 100644 index 00000000000..157e7035f27 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/index.tsx @@ -0,0 +1,222 @@ +import { useTranslation } from 'react-i18next' +import * as utils from './utils' + +import type { TFunction } from 'i18next' +import type { RunTimeCommand, RobotType } from '@opentrons/shared-data' +import type { CommandTextData } from '../../types' +import type { GetDirectTranslationCommandText } from './utils/getDirectTranslationCommandText' + +export interface UseCommandTextStringParams { + command: RunTimeCommand | null + commandTextData: CommandTextData | null + robotType: RobotType +} + +export type GetCommandText = UseCommandTextStringParams & { t: TFunction } +export interface GetCommandTextResult { + /* The actual command text. Ex "Homing all gantry, pipette, and plunger axes" */ + commandText: string + /* The TC run profile steps. */ + stepTexts?: string[] +} + +// TODO(jh, 07-18-24): Move the testing that covers this from CommandText to a new file, and verify that all commands are +// properly tested. + +// Get the full user-facing command text string from a given command. +export function useCommandTextString( + params: UseCommandTextStringParams +): GetCommandTextResult { + const { command } = params + const { t } = useTranslation('protocol_command_text') + + const fullParams = { ...params, t } + + switch (command?.commandType) { + case 'touchTip': + case 'home': + case 'savePosition': + case 'magneticModule/engage': + case 'magneticModule/disengage': + case 'temperatureModule/deactivate': + case 'thermocycler/waitForBlockTemperature': + case 'thermocycler/waitForLidTemperature': + case 'thermocycler/openLid': + case 'thermocycler/closeLid': + case 'thermocycler/deactivateBlock': + case 'thermocycler/deactivateLid': + case 'thermocycler/awaitProfileComplete': + case 'heaterShaker/deactivateHeater': + case 'heaterShaker/openLabwareLatch': + case 'heaterShaker/closeLabwareLatch': + case 'heaterShaker/deactivateShaker': + case 'heaterShaker/waitForTemperature': + return { + commandText: utils.getDirectTranslationCommandText( + fullParams as GetDirectTranslationCommandText + ), + } + + case 'aspirate': + case 'aspirateInPlace': + case 'dispense': + case 'dispenseInPlace': + case 'blowout': + case 'blowOutInPlace': + case 'dropTip': + case 'dropTipInPlace': + case 'pickUpTip': + return { + commandText: utils.getPipettingCommandText(fullParams), + } + + case 'loadLabware': + case 'loadPipette': + case 'loadModule': + case 'loadLiquid': + return { + commandText: utils.getLoadCommandText(fullParams), + } + + case 'temperatureModule/setTargetTemperature': + case 'temperatureModule/waitForTemperature': + case 'thermocycler/setTargetBlockTemperature': + case 'thermocycler/setTargetLidTemperature': + case 'heaterShaker/setTargetTemperature': + return { + commandText: utils.getTemperatureCommandText({ + ...fullParams, + command, + }), + } + + case 'thermocycler/runProfile': + return utils.getTCRunProfileCommandText({ ...fullParams, command }) + + case 'heaterShaker/setAndWaitForShakeSpeed': + return { + commandText: utils.getHSShakeSpeedCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToSlot': + return { + commandText: utils.getMoveToSlotCommandText({ ...fullParams, command }), + } + + case 'moveRelative': + return { + commandText: utils.getMoveRelativeCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToCoordinates': + return { + commandText: utils.getMoveToCoordinatesCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToWell': + return { + commandText: utils.getMoveToWellCommandText({ ...fullParams, command }), + } + + case 'moveLabware': + return { + commandText: utils.getMoveLabwareCommandText({ + ...fullParams, + command, + }), + } + + case 'configureForVolume': + return { + commandText: utils.getConfigureForVolumeCommandText({ + ...fullParams, + command, + }), + } + + case 'configureNozzleLayout': + return { + commandText: utils.getConfigureNozzleLayoutCommandText({ + ...fullParams, + command, + }), + } + + case 'prepareToAspirate': + return { + commandText: utils.getPrepareToAspirateCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToAddressableArea': + return { + commandText: utils.getMoveToAddressableAreaCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToAddressableAreaForDropTip': + return { + commandText: utils.getMoveToAddressableAreaForDropTipCommandText({ + ...fullParams, + command, + }), + } + + case 'waitForDuration': + return { + commandText: utils.getWaitForDurationCommandText({ + ...fullParams, + command, + }), + } + + case 'pause': // legacy pause command + case 'waitForResume': + return { + commandText: utils.getWaitForResumeCommandText({ + ...fullParams, + command, + }), + } + + case 'delay': + return { + commandText: utils.getDelayCommandText({ ...fullParams, command }), + } + + case 'comment': + return { + commandText: utils.getCommentCommandText({ ...fullParams, command }), + } + + case 'custom': + return { + commandText: utils.getCustomCommandText({ ...fullParams, command }), + } + + case null: + return { commandText: '' } + + default: + console.warn( + 'CommandText encountered a command with an unrecognized commandType: ', + command + ) + return { + commandText: utils.getUnknownCommandText({ ...fullParams, command }), + } + } +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getCommentCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getCommentCommandText.ts new file mode 100644 index 00000000000..3a1b7ce7e8a --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getCommentCommandText.ts @@ -0,0 +1,10 @@ +import type { CommentRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getCommentCommandText({ + command, +}: HandlesCommands): string { + const { message } = command.params + + return message +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureForVolumeCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureForVolumeCommandText.ts new file mode 100644 index 00000000000..1a4ee2e7c0e --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureForVolumeCommandText.ts @@ -0,0 +1,21 @@ +import { getPipetteSpecsV2 } from '@opentrons/shared-data' + +import type { ConfigureForVolumeRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getConfigureForVolumeCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const { volume, pipetteId } = command.params + const pipetteName = commandTextData?.pipettes.find( + pip => pip.id === pipetteId + )?.pipetteName + + return t('configure_for_volume', { + volume, + pipette: + pipetteName != null ? getPipetteSpecsV2(pipetteName)?.displayName : '', + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureNozzleLayoutCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureNozzleLayoutCommandText.ts new file mode 100644 index 00000000000..e6693a4b937 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureNozzleLayoutCommandText.ts @@ -0,0 +1,21 @@ +import { getPipetteSpecsV2 } from '@opentrons/shared-data' + +import type { ConfigureNozzleLayoutRunTimeCommand } from '@opentrons/shared-data' +import type { HandlesCommands } from './types' + +export function getConfigureNozzleLayoutCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const { configurationParams, pipetteId } = command.params + const pipetteName = commandTextData?.pipettes.find( + pip => pip.id === pipetteId + )?.pipetteName + + return t('configure_nozzle_layout', { + amount: configurationParams.style === 'COLUMN' ? '8' : 'all', + pipette: + pipetteName != null ? getPipetteSpecsV2(pipetteName)?.displayName : '', + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getCustomCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getCustomCommandText.ts new file mode 100644 index 00000000000..da6d5a1d506 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getCustomCommandText.ts @@ -0,0 +1,16 @@ +import type { CustomRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getCustomCommandText({ + command, +}: HandlesCommands): string { + const { legacyCommandText } = command.params ?? {} + const sanitizedCommandText = + typeof legacyCommandText === 'object' + ? JSON.stringify(legacyCommandText) + : String(legacyCommandText) + + return legacyCommandText != null + ? sanitizedCommandText + : `${command.commandType}: ${JSON.stringify(command.params)}` +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getDelayCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getDelayCommandText.ts new file mode 100644 index 00000000000..8bb24d99661 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getDelayCommandText.ts @@ -0,0 +1,20 @@ +import type { DeprecatedDelayRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getDelayCommandText({ + command, + t, +}: HandlesCommands): string { + const { message = '' } = command.params + + if ('waitForResume' in command.params) { + return command.params?.message != null && command.params.message !== '' + ? command.params.message + : t('wait_for_resume') + } else { + return t('wait_for_duration', { + seconds: command.params.seconds, + message, + }) + } +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getDirectTranslationCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getDirectTranslationCommandText.ts new file mode 100644 index 00000000000..fd586136e90 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getDirectTranslationCommandText.ts @@ -0,0 +1,44 @@ +import type { RunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +const SIMPLE_TRANSLATION_KEY_BY_COMMAND_TYPE: { + [commandType in RunTimeCommand['commandType']]?: string +} = { + home: 'home_gantry', + savePosition: 'save_position', + touchTip: 'touch_tip', + 'magneticModule/engage': 'engaging_magnetic_module', + 'magneticModule/disengage': 'disengaging_magnetic_module', + 'temperatureModule/deactivate': 'deactivate_temperature_module', + 'thermocycler/waitForBlockTemperature': 'waiting_for_tc_block_to_reach', + 'thermocycler/waitForLidTemperature': 'waiting_for_tc_lid_to_reach', + 'thermocycler/openLid': 'opening_tc_lid', + 'thermocycler/closeLid': 'closing_tc_lid', + 'thermocycler/deactivateBlock': 'deactivating_tc_block', + 'thermocycler/deactivateLid': 'deactivating_tc_lid', + 'thermocycler/awaitProfileComplete': 'tc_awaiting_for_duration', + 'heaterShaker/deactivateHeater': 'deactivating_hs_heater', + 'heaterShaker/openLabwareLatch': 'unlatching_hs_latch', + 'heaterShaker/closeLabwareLatch': 'latching_hs_latch', + 'heaterShaker/deactivateShaker': 'deactivate_hs_shake', + 'heaterShaker/waitForTemperature': 'waiting_for_hs_to_reach', +} + +type HandledCommands = Extract< + RunTimeCommand, + { commandType: keyof typeof SIMPLE_TRANSLATION_KEY_BY_COMMAND_TYPE } +> + +export type GetDirectTranslationCommandText = HandlesCommands + +export function getDirectTranslationCommandText({ + command, + t, +}: GetDirectTranslationCommandText): string { + const simpleTKey = + command != null + ? SIMPLE_TRANSLATION_KEY_BY_COMMAND_TYPE[command.commandType] + : null + + return simpleTKey != null ? t(simpleTKey) : '' +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getHSShakeSpeedCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getHSShakeSpeedCommandText.ts new file mode 100644 index 00000000000..3710e7f0930 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getHSShakeSpeedCommandText.ts @@ -0,0 +1,11 @@ +import type { HeaterShakerSetAndWaitForShakeSpeedRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getHSShakeSpeedCommandText({ + command, + t, +}: HandlesCommands): string { + const { rpm } = command.params + + return t('set_and_await_hs_shake', { rpm }) +} diff --git a/app/src/molecules/Command/LoadCommandText.tsx b/app/src/molecules/Command/hooks/useCommandTextString/utils/getLoadCommandText.ts similarity index 70% rename from app/src/molecules/Command/LoadCommandText.tsx rename to app/src/molecules/Command/hooks/useCommandTextString/utils/getLoadCommandText.ts index 251d0f3d38d..b2da948d58d 100644 --- a/app/src/molecules/Command/LoadCommandText.tsx +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getLoadCommandText.ts @@ -1,48 +1,37 @@ -import { useTranslation } from 'react-i18next' import { getModuleDisplayName, getModuleType, getOccludedSlotCountForModule, - getPipetteNameSpecs, + getPipetteSpecsV2, } from '@opentrons/shared-data' + import { getLabwareName, getPipetteNameOnMount, getModuleModel, getModuleDisplayLocation, getLiquidDisplayName, -} from './utils' - -import type { - RunTimeCommand, - RobotType, - LoadLabwareRunTimeCommand, -} from '@opentrons/shared-data' -import type { CommandTextData } from './types' +} from '../../../utils' -interface LoadCommandTextProps { - command: RunTimeCommand - commandTextData: CommandTextData - robotType: RobotType -} +import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data' +import type { GetCommandText } from '..' -export const LoadCommandText = ({ +export const getLoadCommandText = ({ command, commandTextData, robotType, -}: LoadCommandTextProps): JSX.Element | null => { - const { t } = useTranslation('run_details') - - switch (command.commandType) { + t, +}: GetCommandText): string => { + switch (command?.commandType) { case 'loadPipette': { - const pipetteModel = getPipetteNameOnMount( - commandTextData, - command.params.mount - ) + const pipetteModel = + commandTextData != null + ? getPipetteNameOnMount(commandTextData, command.params.mount) + : null return t('load_pipette_protocol_setup', { pipette_name: pipetteModel != null - ? getPipetteNameSpecs(pipetteModel)?.displayName ?? '' + ? getPipetteSpecsV2(pipetteModel)?.displayName ?? '' : '', mount_name: command.params.mount === 'left' ? t('left') : t('right'), }) @@ -63,10 +52,10 @@ export const LoadCommandText = ({ command.params.location !== 'offDeck' && 'moduleId' in command.params.location ) { - const moduleModel = getModuleModel( - commandTextData, - command.params.location.moduleId - ) + const moduleModel = + commandTextData != null + ? getModuleModel(commandTextData, command.params.location.moduleId) + : null const moduleName = moduleModel != null ? getModuleDisplayName(moduleModel) : '' @@ -79,10 +68,13 @@ export const LoadCommandText = ({ ) : 1, labware: command.result?.definition.metadata.displayName, - slot_name: getModuleDisplayLocation( - commandTextData, - command.params.location.moduleId - ), + slot_name: + commandTextData != null + ? getModuleDisplayLocation( + commandTextData, + command.params.location.moduleId + ) + : null, module_name: moduleName, }) } else if ( @@ -91,7 +83,7 @@ export const LoadCommandText = ({ ) { const labwareId = command.params.location.labwareId const labwareName = command.result?.definition.metadata.displayName - const matchingAdapter = commandTextData.commands.find( + const matchingAdapter = commandTextData?.commands.find( (command): command is LoadLabwareRunTimeCommand => command.commandType === 'loadLabware' && command.result?.labwareId === labwareId @@ -111,24 +103,27 @@ export const LoadCommandText = ({ slot_name: adapterLoc?.slotName, }) } else if (adapterLoc != null && 'moduleId' in adapterLoc) { - const moduleModel = getModuleModel( - commandTextData, - adapterLoc?.moduleId ?? '' - ) + const moduleModel = + commandTextData != null + ? getModuleModel(commandTextData, adapterLoc?.moduleId ?? '') + : null const moduleName = moduleModel != null ? getModuleDisplayName(moduleModel) : '' return t('load_labware_info_protocol_setup_adapter_module', { labware: labwareName, adapter_name: adapterName, module_name: moduleName, - slot_name: getModuleDisplayLocation( - commandTextData, - adapterLoc?.moduleId ?? '' - ), + slot_name: + commandTextData != null + ? getModuleDisplayLocation( + commandTextData, + adapterLoc?.moduleId ?? '' + ) + : null, }) } else { // shouldn't reach here, adapter shouldn't have location type labwareId - return null + return '' } } else { const labware = @@ -148,8 +143,14 @@ export const LoadCommandText = ({ case 'loadLiquid': { const { liquidId, labwareId } = command.params return t('load_liquids_info_protocol_setup', { - liquid: getLiquidDisplayName(commandTextData, liquidId), - labware: getLabwareName(commandTextData, labwareId), + liquid: + commandTextData != null + ? getLiquidDisplayName(commandTextData, liquidId) + : null, + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, }) } default: { @@ -157,7 +158,7 @@ export const LoadCommandText = ({ 'LoadCommandText encountered a command with an unrecognized commandType: ', command ) - return null + return '' } } } diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveLabwareCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveLabwareCommandText.ts new file mode 100644 index 00000000000..71a0ac3e7d6 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveLabwareCommandText.ts @@ -0,0 +1,72 @@ +import { GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA } from '@opentrons/shared-data' + +import { + getLabwareName, + getLabwareDisplayLocation, + getFinalLabwareLocation, +} from '../../../utils' + +import type { MoveLabwareRunTimeCommand } from '@opentrons/shared-data' +import type { HandlesCommands } from './types' + +export function getMoveLabwareCommandText({ + command, + t, + commandTextData, + robotType, +}: HandlesCommands): string { + const { labwareId, newLocation, strategy } = command.params + + const allPreviousCommands = commandTextData?.commands.slice( + 0, + commandTextData.commands.findIndex(c => c.id === command.id) + ) + const oldLocation = + allPreviousCommands != null + ? getFinalLabwareLocation(labwareId, allPreviousCommands) + : null + const newDisplayLocation = + commandTextData != null + ? getLabwareDisplayLocation(commandTextData, newLocation, t, robotType) + : null + + const location = newDisplayLocation?.includes( + GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA + ) + ? 'Waste Chute' + : newDisplayLocation + + return strategy === 'usingGripper' + ? t('move_labware_using_gripper', { + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, + old_location: + oldLocation != null && commandTextData != null + ? getLabwareDisplayLocation( + commandTextData, + oldLocation, + t, + robotType + ) + : '', + new_location: location, + }) + : t('move_labware_manually', { + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, + old_location: + oldLocation != null && commandTextData != null + ? getLabwareDisplayLocation( + commandTextData, + oldLocation, + t, + robotType + ) + : '', + new_location: location, + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveRelativeCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveRelativeCommandText.ts new file mode 100644 index 00000000000..7f3f8bf0aaa --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveRelativeCommandText.ts @@ -0,0 +1,11 @@ +import type { MoveRelativeRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getMoveRelativeCommandText({ + command, + t, +}: HandlesCommands): string { + const { axis, distance } = command.params + + return t('move_relative', { axis, distance }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressabelAreaForDropTipCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressabelAreaForDropTipCommandText.ts new file mode 100644 index 00000000000..5788fbbdf62 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressabelAreaForDropTipCommandText.ts @@ -0,0 +1,19 @@ +import { getAddressableAreaDisplayName } from '../../../utils' + +import type { MoveToAddressableAreaForDropTipRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getMoveToAddressableAreaForDropTipCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const addressableAreaDisplayName = + commandTextData != null + ? getAddressableAreaDisplayName(commandTextData, command.id, t) + : null + + return t('move_to_addressable_area_drop_tip', { + addressable_area: addressableAreaDisplayName, + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressableAreaCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressableAreaCommandText.ts new file mode 100644 index 00000000000..e8366120a23 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressableAreaCommandText.ts @@ -0,0 +1,19 @@ +import { getAddressableAreaDisplayName } from '../../../utils' + +import type { MoveToAddressableAreaRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getMoveToAddressableAreaCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const addressableAreaDisplayName = + commandTextData != null + ? getAddressableAreaDisplayName(commandTextData, command.id, t) + : null + + return t('move_to_addressable_area', { + addressable_area: addressableAreaDisplayName, + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToCoordinatesCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToCoordinatesCommandText.ts new file mode 100644 index 00000000000..a3dc5ace9fe --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToCoordinatesCommandText.ts @@ -0,0 +1,11 @@ +import type { MoveToCoordinatesRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getMoveToCoordinatesCommandText({ + command, + t, +}: HandlesCommands): string { + const { coordinates } = command.params + + return t('move_to_coordinates', coordinates) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToSlotCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToSlotCommandText.ts new file mode 100644 index 00000000000..b66f5d78513 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToSlotCommandText.ts @@ -0,0 +1,11 @@ +import type { MoveToSlotRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getMoveToSlotCommandText({ + command, + t, +}: HandlesCommands): string { + const { slotName } = command.params + + return t('move_to_slot', { slot_name: slotName }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToWellCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToWellCommandText.ts new file mode 100644 index 00000000000..8c191f34b40 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToWellCommandText.ts @@ -0,0 +1,44 @@ +import { + getFinalLabwareLocation, + getLabwareDisplayLocation, + getLabwareName, +} from '../../../utils' + +import type { TFunction } from 'i18next' +import type { MoveToWellRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getMoveToWellCommandText({ + command, + t, + commandTextData, + robotType, +}: HandlesCommands): string { + const { wellName, labwareId } = command.params + const allPreviousCommands = commandTextData?.commands.slice( + 0, + commandTextData.commands.findIndex(c => c.id === command.id) + ) + const labwareLocation = + allPreviousCommands != null + ? getFinalLabwareLocation(labwareId, allPreviousCommands) + : null + const displayLocation = + labwareLocation != null && commandTextData != null + ? getLabwareDisplayLocation( + commandTextData, + labwareLocation, + t as TFunction, + robotType + ) + : '' + + return t('move_to_well', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, + labware_location: displayLocation, + }) +} diff --git a/app/src/molecules/Command/PipettingCommandText.tsx b/app/src/molecules/Command/hooks/useCommandTextString/utils/getPipettingCommandText.ts similarity index 51% rename from app/src/molecules/Command/PipettingCommandText.tsx rename to app/src/molecules/Command/hooks/useCommandTextString/utils/getPipettingCommandText.ts index 3037ce3e2c3..00c9eba08dd 100644 --- a/app/src/molecules/Command/PipettingCommandText.tsx +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getPipettingCommandText.ts @@ -1,50 +1,46 @@ -import { useTranslation } from 'react-i18next' - import { getLabwareDefURI } from '@opentrons/shared-data' -import { getLoadedLabware } from './utils/accessors' +import { getLoadedLabware } from '../../../utils/accessors' import { getLabwareName, getLabwareDisplayLocation, getFinalLabwareLocation, getWellRange, getLabwareDefinitionsFromCommands, -} from './utils' -import type { - PipetteName, - PipettingRunTimeCommand, - RobotType, -} from '@opentrons/shared-data' -import type { CommandTextData } from './types' -import type { TFunction } from 'i18next' +} from '../../../utils' -interface PipettingCommandTextProps { - command: PipettingRunTimeCommand - commandTextData: CommandTextData - robotType: RobotType -} +import type { PipetteName, RunTimeCommand } from '@opentrons/shared-data' +import type { TFunction } from 'i18next' +import type { GetCommandText } from '..' -export const PipettingCommandText = ({ +export const getPipettingCommandText = ({ command, commandTextData, robotType, -}: PipettingCommandTextProps): JSX.Element | null => { - const { t } = useTranslation('protocol_command_text') - + t, +}: GetCommandText): string => { const labwareId = - 'labwareId' in command.params ? command.params.labwareId : '' - const wellName = 'wellName' in command.params ? command.params.wellName : '' + command != null && 'labwareId' in command.params + ? (command.params.labwareId as string) + : '' + const wellName = + command != null && 'wellName' in command.params + ? command.params.wellName + : '' - const allPreviousCommands = commandTextData.commands.slice( + const allPreviousCommands = commandTextData?.commands.slice( 0, - commandTextData.commands.findIndex(c => c.id === command.id) - ) - const labwareLocation = getFinalLabwareLocation( - labwareId, - allPreviousCommands + commandTextData.commands.findIndex(c => c.id === command?.id) ) + const labwareLocation = + allPreviousCommands != null + ? getFinalLabwareLocation( + labwareId, + allPreviousCommands as RunTimeCommand[] + ) + : null const displayLocation = - labwareLocation != null + labwareLocation != null && commandTextData != null ? getLabwareDisplayLocation( commandTextData, labwareLocation, @@ -52,12 +48,15 @@ export const PipettingCommandText = ({ robotType ) : '' - switch (command.commandType) { + switch (command?.commandType) { case 'aspirate': { const { volume, flowRate } = command.params return t('aspirate', { well_name: wellName, - labware: getLabwareName(commandTextData, labwareId), + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, labware_location: displayLocation, volume, flow_rate: flowRate, @@ -68,7 +67,10 @@ export const PipettingCommandText = ({ return pushOut != null ? t('dispense_push_out', { well_name: wellName, - labware: getLabwareName(commandTextData, labwareId), + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, labware_location: displayLocation, volume, flow_rate: flowRate, @@ -76,7 +78,10 @@ export const PipettingCommandText = ({ }) : t('dispense', { well_name: wellName, - labware: getLabwareName(commandTextData, labwareId), + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, labware_location: displayLocation, volume, flow_rate: flowRate, @@ -86,46 +91,67 @@ export const PipettingCommandText = ({ const { flowRate } = command.params return t('blowout', { well_name: wellName, - labware: getLabwareName(commandTextData, labwareId), + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, labware_location: displayLocation, flow_rate: flowRate, }) } case 'dropTip': { - const loadedLabware = getLoadedLabware(commandTextData, labwareId) - const labwareDefinitions = getLabwareDefinitionsFromCommands( - commandTextData.commands - ) - const labwareDef = labwareDefinitions.find( + const loadedLabware = + commandTextData != null + ? getLoadedLabware(commandTextData, labwareId) + : null + const labwareDefinitions = + commandTextData != null + ? getLabwareDefinitionsFromCommands( + commandTextData.commands as RunTimeCommand[] + ) + : null + const labwareDef = labwareDefinitions?.find( lw => getLabwareDefURI(lw) === loadedLabware?.definitionUri ) return Boolean(labwareDef?.parameters.isTiprack) ? t('return_tip', { well_name: wellName, - labware: getLabwareName(commandTextData, labwareId), + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, labware_location: displayLocation, }) : t('drop_tip', { well_name: wellName, - labware: getLabwareName(commandTextData, labwareId), + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, }) } case 'pickUpTip': { const pipetteId = command.params.pipetteId const pipetteName: | PipetteName - | undefined = commandTextData.pipettes.find( + | undefined = commandTextData?.pipettes.find( pipette => pipette.id === pipetteId )?.pipetteName return t('pickup_tip', { - well_range: getWellRange( - pipetteId, - allPreviousCommands, - wellName, - pipetteName - ), - labware: getLabwareName(commandTextData, labwareId), + well_range: + allPreviousCommands != null + ? getWellRange( + pipetteId, + allPreviousCommands as RunTimeCommand[], + wellName as string, + pipetteName + ) + : null, + labware: + commandTextData != null + ? getLabwareName(commandTextData, labwareId) + : null, labware_location: displayLocation, }) } @@ -149,7 +175,7 @@ export const PipettingCommandText = ({ 'PipettingCommandText encountered a command with an unrecognized commandType: ', command ) - return null + return '' } } } diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getPrepareToAspirateCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getPrepareToAspirateCommandText.ts new file mode 100644 index 00000000000..13d32b6b7d6 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getPrepareToAspirateCommandText.ts @@ -0,0 +1,20 @@ +import { getPipetteSpecsV2 } from '@opentrons/shared-data' + +import type { PrepareToAspirateRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getPrepareToAspirateCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const { pipetteId } = command.params + const pipetteName = commandTextData?.pipettes.find( + pip => pip.id === pipetteId + )?.pipetteName + + return t('prepare_to_aspirate', { + pipette: + pipetteName != null ? getPipetteSpecsV2(pipetteName)?.displayName : '', + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getTCRunProfileCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getTCRunProfileCommandText.ts new file mode 100644 index 00000000000..2d279fca850 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getTCRunProfileCommandText.ts @@ -0,0 +1,24 @@ +import type { TCRunProfileRunTimeCommand } from '@opentrons/shared-data/command' +import type { GetCommandTextResult } from '..' +import type { HandlesCommands } from './types' + +export function getTCRunProfileCommandText({ + command, + t, +}: HandlesCommands): GetCommandTextResult { + const { profile } = command.params + + const stepTexts = profile.map( + ({ holdSeconds, celsius }: { holdSeconds: number; celsius: number }) => + t('tc_run_profile_steps', { + celsius, + seconds: holdSeconds, + }).trim() + ) + + const startingProfileText = t('tc_starting_profile', { + repetitions: Object.keys(stepTexts).length, + }) + + return { commandText: startingProfileText, stepTexts } +} diff --git a/app/src/molecules/Command/TemperatureCommandText.tsx b/app/src/molecules/Command/hooks/useCommandTextString/utils/getTemperatureCommandText.ts similarity index 78% rename from app/src/molecules/Command/TemperatureCommandText.tsx rename to app/src/molecules/Command/hooks/useCommandTextString/utils/getTemperatureCommandText.ts index 2d09926add2..ee60a76c289 100644 --- a/app/src/molecules/Command/TemperatureCommandText.tsx +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getTemperatureCommandText.ts @@ -1,23 +1,20 @@ -import { useTranslation } from 'react-i18next' import type { TemperatureModuleAwaitTemperatureCreateCommand, TemperatureModuleSetTargetTemperatureCreateCommand, TCSetTargetBlockTemperatureCreateCommand, TCSetTargetLidTemperatureCreateCommand, HeaterShakerSetTargetTemperatureCreateCommand, + RunTimeCommand, } from '@opentrons/shared-data' +import type { HandlesCommands } from './types' -type TemperatureCreateCommand = +export type TemperatureCreateCommand = | TemperatureModuleSetTargetTemperatureCreateCommand | TemperatureModuleAwaitTemperatureCreateCommand | TCSetTargetBlockTemperatureCreateCommand | TCSetTargetLidTemperatureCreateCommand | HeaterShakerSetTargetTemperatureCreateCommand -interface TemperatureCommandTextProps { - command: TemperatureCreateCommand -} - const T_KEYS_BY_COMMAND_TYPE: { [commandType in TemperatureCreateCommand['commandType']]: string } = { @@ -28,11 +25,17 @@ const T_KEYS_BY_COMMAND_TYPE: { 'heaterShaker/setTargetTemperature': 'setting_hs_temp', } -export const TemperatureCommandText = ({ - command, -}: TemperatureCommandTextProps): JSX.Element | null => { - const { t } = useTranslation('protocol_command_text') +type HandledCommands = Extract< + RunTimeCommand, + { commandType: keyof typeof T_KEYS_BY_COMMAND_TYPE } +> +type GetTemperatureCommandText = HandlesCommands + +export const getTemperatureCommandText = ({ + command, + t, +}: GetTemperatureCommandText): string => { return t(T_KEYS_BY_COMMAND_TYPE[command.commandType], { temp: command.params?.celsius != null diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getUnknownCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getUnknownCommandText.ts new file mode 100644 index 00000000000..4f2346c7c01 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getUnknownCommandText.ts @@ -0,0 +1,5 @@ +import type { GetCommandText } from '..' + +export function getUnknownCommandText({ command }: GetCommandText): string { + return JSON.stringify(command) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForDurationCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForDurationCommandText.ts new file mode 100644 index 00000000000..d3b3136be1f --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForDurationCommandText.ts @@ -0,0 +1,11 @@ +import type { WaitForDurationRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getWaitForDurationCommandText({ + command, + t, +}: HandlesCommands): string { + const { seconds, message } = command.params + + return t('wait_for_duration', { seconds, message: message ?? '' }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForResumeCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForResumeCommandText.ts new file mode 100644 index 00000000000..f1c7b7fcef6 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForResumeCommandText.ts @@ -0,0 +1,11 @@ +import type { WaitForResumeRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +export function getWaitForResumeCommandText({ + command, + t, +}: HandlesCommands): string { + return command.params?.message != null && command.params.message !== '' + ? command.params.message + : t('wait_for_resume') +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts new file mode 100644 index 00000000000..b937e28066d --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts @@ -0,0 +1,22 @@ +export { getLoadCommandText } from './getLoadCommandText' +export { getTemperatureCommandText } from './getTemperatureCommandText' +export { getTCRunProfileCommandText } from './getTCRunProfileCommandText' +export { getHSShakeSpeedCommandText } from './getHSShakeSpeedCommandText' +export { getMoveToSlotCommandText } from './getMoveToSlotCommandText' +export { getMoveRelativeCommandText } from './getMoveRelativeCommandText' +export { getMoveToCoordinatesCommandText } from './getMoveToCoordinatesCommandText' +export { getMoveToWellCommandText } from './getMoveToWellCommandText' +export { getMoveLabwareCommandText } from './getMoveLabwareCommandText' +export { getConfigureForVolumeCommandText } from './getConfigureForVolumeCommandText' +export { getConfigureNozzleLayoutCommandText } from './getConfigureNozzleLayoutCommandText' +export { getPrepareToAspirateCommandText } from './getPrepareToAspirateCommandText' +export { getMoveToAddressableAreaCommandText } from './getMoveToAddressableAreaCommandText' +export { getMoveToAddressableAreaForDropTipCommandText } from './getMoveToAddressabelAreaForDropTipCommandText' +export { getDirectTranslationCommandText } from './getDirectTranslationCommandText' +export { getWaitForDurationCommandText } from './getWaitForDurationCommandText' +export { getWaitForResumeCommandText } from './getWaitForResumeCommandText' +export { getDelayCommandText } from './getDelayCommandText' +export { getCommentCommandText } from './getCommentCommandText' +export { getCustomCommandText } from './getCustomCommandText' +export { getUnknownCommandText } from './getUnknownCommandText' +export { getPipettingCommandText } from './getPipettingCommandText' diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/types.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/types.ts new file mode 100644 index 00000000000..37dde8c783a --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/types.ts @@ -0,0 +1,7 @@ +import type { RunTimeCommand } from '@opentrons/shared-data' +import type { GetCommandText } from '..' + +export type HandlesCommands = Omit< + GetCommandText, + 'command' +> & { command: T } diff --git a/app/src/molecules/Command/index.ts b/app/src/molecules/Command/index.ts index 357acd6b85b..b4223d82beb 100644 --- a/app/src/molecules/Command/index.ts +++ b/app/src/molecules/Command/index.ts @@ -4,3 +4,4 @@ export * from './CommandIcon' export * from './CommandIndex' export * from './utils' export * from './types' +export * from './hooks'