diff --git a/protocol-designer/src/ProtocolEditor.tsx b/protocol-designer/src/ProtocolEditor.tsx index df486e3eee4..0bbc0f7f5c6 100644 --- a/protocol-designer/src/ProtocolEditor.tsx +++ b/protocol-designer/src/ProtocolEditor.tsx @@ -2,10 +2,10 @@ import { DndProvider } from 'react-dnd' import { HashRouter } from 'react-router-dom' import { HTML5Backend } from 'react-dnd-html5-backend' import { DIRECTION_COLUMN, Flex, OVERFLOW_AUTO } from '@opentrons/components' -import { PortalRoot as TopPortalRoot } from './components/portals/TopPortal' + import { ProtocolRoutes } from './ProtocolRoutes' import { useScreenSizeCheck } from './resources/useScreenSizeCheck' -import { DisabledScreen } from './organisms/DisabledScreen' +import { PortalRoot, DisabledScreen } from './organisms' function ProtocolEditorComponent(): JSX.Element { const isValidSize = useScreenSizeCheck() @@ -14,7 +14,7 @@ function ProtocolEditorComponent(): JSX.Element { id="protocol-editor" style={{ width: '100%', height: '100vh', overflow: OVERFLOW_AUTO }} > - + {!isValidSize && } diff --git a/protocol-designer/src/assets/localization/en/alert.json b/protocol-designer/src/assets/localization/en/alert.json index ece60e77570..e5526ae7356 100644 --- a/protocol-designer/src/assets/localization/en/alert.json +++ b/protocol-designer/src/assets/localization/en/alert.json @@ -29,36 +29,6 @@ }, "hint": { "dont_show_again": "Don't show me again", - "add_liquids_and_labware": { - "title": "Your labware has no liquids in it", - "summary": "In {{deck_setup_step}}, hover on labware to add liquids.", - "step1": "Add liquids", - "step2": "Watch liquids move as you build your protocol" - }, - "deck_setup_explanation": { - "title": "Setting up your protocol", - "body1": "If you look to your left you will see the Protocol Timeline, and that the \"Starting Deck State\" step is listed first.", - "body2": "Before you can build a protocol that tells the robot how to move liquid around, you'll need to tell the Protocol Designer where all of your labware is on the deck, and which liquids start in which wells. As you add steps to your protocol the Protocol Designer will move liquid from the starting positions you defined in the Starting Deck State to new positions.", - "body3": "Hover on empty slots in the deck to add labware. Hover on labware to add liquid to the wells." - }, - "custom_labware_with_modules": { - "title": "Cannot verify compatibility of custom labware", - "body": "The Protocol Designer cannot confirm whether or not custom labware is compatible with any module. Proceed if you are confident your custom labware will fit." - }, - "module_without_labware": { - "title": "Missing labware", - "body": "Your module has no labware on it. We recommend you add labware before proceeding." - }, - "multiple_modules_without_labware": { - "title": "Missing labware", - "body": "One or more module has no labware on it. We recommend you add labware before proceeding" - }, - "export_v8_1_protocol_7_3": { - "title": "Robot and app update may be required", - "body1": "This protocol can only run on app and robot server version", - "body2": "7.3.0 or higher", - "body3": ". Please ensure your robot is updated to the correct version." - }, "unused_hardware": { "title": "Protocol has unused hardware" }, @@ -76,25 +46,6 @@ "li1": "If the Thermocycler lid is closed it may take a very long time to reach a cooler temperature.", "li2": "If the room temperature in your lab is higher than the temperature you have defined, the lid will never reach it. This will stall your protocol indefinitely." }, - "protocol_can_enter_batch_edit": { - "title": "Editing multiple steps", - "body1": "Now that your protocol has multiple steps, you can try using Protocol Designer’s batch edit features.", - - "body2": "To select multiple steps:", - "li1a": "Hold ", - "strong_li1": "SHIFT + Click ", - "li1b": "to select a range of steps", - - "li2a": "Use ", - "strong_li2": "CTRL + Click (PC) or ⌘ + Click (Mac) ", - "li2b": "to select a single additional step.", - - "body3a": "To delete or duplicate multiple steps:", - "body3b": "Use the control bar at the top of the Protocol Timeline.", - - "body4a": "To batch edit Transfer or Mix advanced settings:", - "body4b": "Only include Transfer or Mix steps in your selection." - }, "waste_chute_warning": { "title": "Disposing labware", "body1": "Moving labware to the Waste Chute permanently discards it. You can't use this labware in later steps. During a protocol run, the labware will be dropped in the chute and become irretrievable." diff --git a/protocol-designer/src/components/BatchEditForm/BatchEditMix.tsx b/protocol-designer/src/components/BatchEditForm/BatchEditMix.tsx deleted file mode 100644 index d7bc4c2f32d..00000000000 --- a/protocol-designer/src/components/BatchEditForm/BatchEditMix.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { - Box, - DeprecatedPrimaryButton, - OutlineButton, - LegacyTooltip, - useHoverTooltip, - TOOLTIP_TOP, - TOOLTIP_FIXED, -} from '@opentrons/components' -import { - BlowoutLocationField, - CheckboxRowField, - DelayFields, - FlowRateField, - TipPositionField, - WellOrderField, -} from '../StepEditForm/fields' -import { - getBlowoutLocationOptionsForForm, - getLabwareFieldForPositioningField, -} from '../StepEditForm/utils' -import { FormColumn } from './FormColumn' -// TODO(IL, 2021-03-01): refactor these fragmented style rules (see #7402) -import formStyles from '../forms/forms.module.css' -import styles from '../StepEditForm/StepEditForm.module.css' -import buttonStyles from '../StepEditForm/ButtonRow/styles.module.css' -import type { FieldPropsByName } from '../StepEditForm/types' -import type { WellOrderOption } from '../../form-types' - -interface BatchEditMixProps { - batchEditFormHasChanges: boolean - propsForFields: FieldPropsByName - handleCancel: () => unknown - handleSave: () => unknown -} -export const BatchEditMix = (props: BatchEditMixProps): JSX.Element => { - const { propsForFields, handleCancel, handleSave } = props - const { t } = useTranslation(['form', 'button', 'tooltip']) - const [cancelButtonTargetProps, cancelButtonTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - strategy: TOOLTIP_FIXED, - }) - const [saveButtonTargetProps, saveButtonTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - strategy: TOOLTIP_FIXED, - }) - const disableSave = !props.batchEditFormHasChanges - - const getLabwareIdForPositioningField = (name: string): string | null => { - const labwareField = getLabwareFieldForPositioningField(name) - const labwareId = propsForFields[labwareField]?.value - return labwareId ? String(labwareId) : null - } - - const getPipetteIdForForm = (): string | null => { - const pipetteId = propsForFields.pipette?.value - return pipetteId ? String(pipetteId) : null - } - - const getWellOrderFieldValue = ( - name: string - ): WellOrderOption | null | undefined => { - const val = propsForFields[name]?.value - if (val === 'l2r' || val === 'r2l' || val === 't2b' || val === 'b2t') { - return val - } else { - return null - } - } - - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {t('button:discard_changes')} - - - {t('tooltip:cancel_batch_edit')} - - - - - - {t('button:save')} - - - {t( - `tooltip:save_batch_edit.${ - disableSave ? 'disabled' : 'enabled' - }` - )} - - - - -
- ) -} diff --git a/protocol-designer/src/components/BatchEditForm/BatchEditMoveLiquid.tsx b/protocol-designer/src/components/BatchEditForm/BatchEditMoveLiquid.tsx deleted file mode 100644 index 7a0363b6e8c..00000000000 --- a/protocol-designer/src/components/BatchEditForm/BatchEditMoveLiquid.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { - Box, - DeprecatedPrimaryButton, - OutlineButton, - LegacyTooltip, - useHoverTooltip, - TOOLTIP_TOP, - TOOLTIP_FIXED, -} from '@opentrons/components' -import { - BlowoutLocationField, - CheckboxRowField, - DelayFields, - FlowRateField, - TipPositionField, - WellOrderField, -} from '../StepEditForm/fields' -import { MixFields } from '../StepEditForm/fields/MixFields' -import { - getBlowoutLocationOptionsForForm, - getLabwareFieldForPositioningField, -} from '../StepEditForm/utils' -import { FormColumn } from './FormColumn' -// TODO(IL, 2021-03-01): refactor these fragmented style rules (see #7402) -import formStyles from '../forms/forms.module.css' -import styles from '../StepEditForm/StepEditForm.module.css' -import buttonStyles from '../StepEditForm/ButtonRow/styles.module.css' -import type { FieldPropsByName } from '../StepEditForm/types' -import type { WellOrderOption } from '../../form-types' - -const SourceDestBatchEditMoveLiquidFields = (props: { - prefix: 'aspirate' | 'dispense' - propsForFields: FieldPropsByName -}): JSX.Element => { - const { prefix, propsForFields } = props - const { t } = useTranslation('form') - const addFieldNamePrefix = (name: string): string => `${prefix}_${name}` - - const getLabwareIdForPositioningField = (name: string): string | null => { - const labwareField = getLabwareFieldForPositioningField(name) - const labwareId = propsForFields[labwareField]?.value - return labwareId ? String(labwareId) : null - } - - const getPipetteIdForForm = (): string | null => { - const pipetteId = propsForFields.pipette?.value - return pipetteId ? String(pipetteId) : null - } - - const getWellOrderFieldValue = ( - name: string - ): WellOrderOption | null | undefined => { - const val = propsForFields[name]?.value - if (val === 'l2r' || val === 'r2l' || val === 't2b' || val === 'b2t') { - return val - } else { - return null - } - } - - return ( - - - - - - - - {prefix === 'aspirate' && ( - - )} - - - - - - - {prefix === 'dispense' && ( - - - - )} - - ) -} - -export interface BatchEditMoveLiquidProps { - batchEditFormHasChanges: boolean - propsForFields: FieldPropsByName - handleCancel: () => void - handleSave: () => void -} -export const BatchEditMoveLiquid = ( - props: BatchEditMoveLiquidProps -): JSX.Element => { - const { t } = useTranslation(['button', 'tooltip']) - const { propsForFields, handleCancel, handleSave } = props - const [cancelButtonTargetProps, cancelButtonTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - strategy: TOOLTIP_FIXED, - }) - const [saveButtonTargetProps, saveButtonTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - strategy: TOOLTIP_FIXED, - }) - const disableSave = !props.batchEditFormHasChanges - - return ( -
- - - - - - - - - - {t('discard_changes')} - - - {t('tooltip:cancel_batch_edit')} - - - - - - {t('save')} - - - {t( - `tooltip:save_batch_edit.${ - disableSave ? 'disabled' : 'enabled' - }` - )} - - - - -
- ) -} diff --git a/protocol-designer/src/components/BatchEditForm/FormColumn.tsx b/protocol-designer/src/components/BatchEditForm/FormColumn.tsx deleted file mode 100644 index 684c7654fbb..00000000000 --- a/protocol-designer/src/components/BatchEditForm/FormColumn.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type * as React from 'react' -import { Box } from '@opentrons/components' -// TODO(IL, 2021-03-01): refactor these fragmented style rules (see #7402) -import styles from '../StepEditForm/StepEditForm.module.css' - -export interface FormColumnProps { - children?: React.ReactNode - sectionHeader?: React.ReactNode -} -export const FormColumn = (props: FormColumnProps): JSX.Element => { - return ( - - - - {props.sectionHeader} - - - {props.children} - - ) -} diff --git a/protocol-designer/src/components/BatchEditForm/NoBatchEditSharedSettings.tsx b/protocol-designer/src/components/BatchEditForm/NoBatchEditSharedSettings.tsx deleted file mode 100644 index 326733c6192..00000000000 --- a/protocol-designer/src/components/BatchEditForm/NoBatchEditSharedSettings.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { - Flex, - Text, - ALIGN_CENTER, - C_DARK_GRAY, - JUSTIFY_CENTER, - SPACING_3, -} from '@opentrons/components' - -export const NoBatchEditSharedSettings = (): JSX.Element => { - const { t } = useTranslation('application') - return ( - - - {t('no_batch_edit_shared_settings')} - - - ) -} diff --git a/protocol-designer/src/components/BatchEditForm/__tests__/BatchEditMoveLiquid.test.tsx b/protocol-designer/src/components/BatchEditForm/__tests__/BatchEditMoveLiquid.test.tsx deleted file mode 100644 index 2c68c18480a..00000000000 --- a/protocol-designer/src/components/BatchEditForm/__tests__/BatchEditMoveLiquid.test.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { describe, it } from 'vitest' - -describe('BatchEditMoveLiquid', () => { - it.todo('replace deprecated enzyme test') -}) diff --git a/protocol-designer/src/components/BatchEditForm/__tests__/makeBatchEditFieldProps.test.ts b/protocol-designer/src/components/BatchEditForm/__tests__/makeBatchEditFieldProps.test.ts deleted file mode 100644 index e85878ec81e..00000000000 --- a/protocol-designer/src/components/BatchEditForm/__tests__/makeBatchEditFieldProps.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest' -import noop from 'lodash/noop' -import { makeBatchEditFieldProps } from '../makeBatchEditFieldProps' -import * as stepEditFormUtils from '../../StepEditForm/utils' - -const getFieldDefaultTooltipSpy = vi.spyOn( - stepEditFormUtils, - 'getFieldDefaultTooltip' -) - -const getIndeterminateTooltipSpy = vi.spyOn( - stepEditFormUtils, - 'getFieldIndeterminateTooltip' -) - -vi.mock('react-i18next', () => ({ - useTranslation: vi.fn().mockReturnValue({ - t: (key: string) => key, - }), -})) - -const tMock = (key: string) => key - -beforeEach(() => { - getFieldDefaultTooltipSpy.mockImplementation(name => `tooltip for ${name}`) - getIndeterminateTooltipSpy.mockImplementation(name => `tooltip for ${name}`) -}) - -afterEach(() => { - vi.restoreAllMocks() -}) - -describe('makeBatchEditFieldProps', () => { - it('should create correct props for all fields with the given MultiselectFieldValues obj', () => { - const fieldValues = { - aspirate_flowRate: { - isIndeterminate: false, - value: '1.2', - }, - } - const handleChangeFormInput: any = vi.fn() - - const disabledFields = {} - - const result = makeBatchEditFieldProps( - fieldValues, - disabledFields, - handleChangeFormInput, - tMock - ) - - expect(result).toEqual({ - aspirate_flowRate: { - disabled: false, - errorToShow: null, - isIndeterminate: false, - name: 'aspirate_flowRate', - onFieldBlur: noop, - onFieldFocus: noop, - updateValue: expect.anything(), - value: '1.2', - tooltipContent: 'tooltip for aspirate_flowRate', - }, - }) - - result.aspirate_flowRate.updateValue('42') - expect(handleChangeFormInput).toHaveBeenCalledWith( - 'aspirate_flowRate', - '42' - ) - }) - - it('should make field disabled if it is represented in disabledFields, and show disabled explanation tooltip', () => { - const fieldValues = { - aspirate_flowRate: { - value: '1.2', - isIndeterminate: false, - }, - } - const handleChangeFormInput: any = vi.fn() - - const disabledFields = { - aspirate_flowRate: 'Disabled explanation text here', - } - - const result = makeBatchEditFieldProps( - fieldValues, - disabledFields, - handleChangeFormInput, - tMock - ) - - expect(result.aspirate_flowRate.disabled).toBe(true) - expect(result.aspirate_flowRate.tooltipContent).toBe( - 'Disabled explanation text here' - ) - }) - - it('should make field indeterminate if it is indeterminate', () => { - const fieldValues = { - aspirate_flowRate: { - value: '1.2', - isIndeterminate: true, - }, - } - const handleChangeFormInput: any = vi.fn() - - const disabledFields = {} - - const result = makeBatchEditFieldProps( - fieldValues, - disabledFields, - handleChangeFormInput, - tMock - ) - - expect(result.aspirate_flowRate.isIndeterminate).toBe(true) - }) - - it('should show indeterminate tooltip content for indeterminate checkboxes', () => { - const fieldValues = { - preWetTip: { - value: 'mixed', - isIndeterminate: true, - }, - } - const handleChangeFormInput: any = vi.fn() - - const disabledFields = {} - - const result = makeBatchEditFieldProps( - fieldValues, - disabledFields, - handleChangeFormInput, - tMock - ) - - expect(result.preWetTip.isIndeterminate).toBe(true) - }) - - it('should override indeterminate tooltip content if field is also disabled', () => { - const fieldValues = { - preWetTip: { - value: 'mixed', - isIndeterminate: true, - }, - } - const handleChangeFormInput: any = vi.fn() - - const disabledFields = { - preWetTip: 'Disabled explanation text here', - } - - const result = makeBatchEditFieldProps( - fieldValues, - disabledFields, - handleChangeFormInput, - tMock - ) - - expect(result.preWetTip.isIndeterminate).toBe(true) - expect(result.preWetTip.disabled).toBe(true) - expect(result.preWetTip.tooltipContent).toBe( - 'Disabled explanation text here' - ) - }) -}) diff --git a/protocol-designer/src/components/BatchEditForm/index.tsx b/protocol-designer/src/components/BatchEditForm/index.tsx deleted file mode 100644 index 9d77fd5f6c3..00000000000 --- a/protocol-designer/src/components/BatchEditForm/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { makeBatchEditFieldProps } from './makeBatchEditFieldProps' -import { NoBatchEditSharedSettings } from './NoBatchEditSharedSettings' -import { - getBatchEditSelectedStepTypes, - getMultiSelectDisabledFields, - getMultiSelectFieldValues, - getMultiSelectItemIds, -} from '../../ui/steps/selectors' -import { getBatchEditFormHasUnsavedChanges } from '../../step-forms/selectors' -import { - changeBatchEditField, - resetBatchEditFieldChanges, - saveStepFormsMulti, -} from '../../step-forms/actions' -import { maskField } from '../../steplist/fieldLevel' -import { BatchEditMoveLiquid } from './BatchEditMoveLiquid' -import { BatchEditMix } from './BatchEditMix' -import type { StepFieldName } from '../../steplist/fieldLevel' -import type { ThunkDispatch } from 'redux-thunk' -import type { BaseState } from '../../types' - -export const BatchEditForm = (): JSX.Element => { - const { t } = useTranslation('tooltip') - const dispatch = useDispatch>() - const fieldValues = useSelector(getMultiSelectFieldValues) - const stepTypes = useSelector(getBatchEditSelectedStepTypes) - const disabledFields = useSelector(getMultiSelectDisabledFields) - const selectedStepIds = useSelector(getMultiSelectItemIds) - const batchEditFormHasChanges = useSelector(getBatchEditFormHasUnsavedChanges) - - const handleChangeFormInput = (name: StepFieldName, value: unknown): void => { - const maskedValue = maskField(name, value) - dispatch(changeBatchEditField({ [name]: maskedValue })) - } - - const handleSave = (): void => { - dispatch(saveStepFormsMulti(selectedStepIds)) - } - - const handleCancel = (): void => { - dispatch(resetBatchEditFieldChanges()) - } - - const stepType = stepTypes.length === 1 ? stepTypes[0] : null - - if (stepType !== null && fieldValues !== null && disabledFields !== null) { - // Valid state for using makeBatchEditFieldProps - const propsForFields = makeBatchEditFieldProps( - fieldValues, - disabledFields, - handleChangeFormInput, - t - ) - switch (stepType) { - case 'moveLiquid': - return ( - - ) - case 'mix': - return ( - - ) - } - } - - return -} diff --git a/protocol-designer/src/components/BatchEditForm/makeBatchEditFieldProps.ts b/protocol-designer/src/components/BatchEditForm/makeBatchEditFieldProps.ts deleted file mode 100644 index f84371bf7fd..00000000000 --- a/protocol-designer/src/components/BatchEditForm/makeBatchEditFieldProps.ts +++ /dev/null @@ -1,48 +0,0 @@ -import noop from 'lodash/noop' -import { - getFieldDefaultTooltip, - getFieldIndeterminateTooltip, -} from '../StepEditForm/utils' -import type { - DisabledFields, - MultiselectFieldValues, -} from '../../ui/steps/selectors' -import type { FieldPropsByName } from '../StepEditForm/types' -import type { StepFieldName } from '../../form-types' -export const makeBatchEditFieldProps = ( - fieldValues: MultiselectFieldValues, - disabledFields: DisabledFields, - handleChangeFormInput: (name: string, value: unknown) => void, - t: any -): FieldPropsByName => { - const fieldNames: StepFieldName[] = Object.keys(fieldValues) - return fieldNames.reduce((acc, name) => { - const defaultTooltip = getFieldDefaultTooltip(name, t) - const isIndeterminate = fieldValues[name].isIndeterminate - const indeterminateTooltip = getFieldIndeterminateTooltip(name, t) - let tooltipContent = defaultTooltip // Default to the default content (or blank) - - if (isIndeterminate && indeterminateTooltip) { - tooltipContent = indeterminateTooltip - } - - if (name in disabledFields) { - tooltipContent = disabledFields[name] // Use disabled content if field is disabled, override indeterminate tooltip if applicable - } - - acc[name] = { - disabled: name in disabledFields, - name, - updateValue: value => { - handleChangeFormInput(name, value) - }, - value: fieldValues[name].value, - errorToShow: null, - onFieldBlur: noop, - onFieldFocus: noop, - isIndeterminate: isIndeterminate, - tooltipContent: tooltipContent, - } - return acc - }, {}) -} diff --git a/protocol-designer/src/components/ColorPicker/ColorPicker.module.css b/protocol-designer/src/components/ColorPicker/ColorPicker.module.css deleted file mode 100644 index 61e30efd289..00000000000 --- a/protocol-designer/src/components/ColorPicker/ColorPicker.module.css +++ /dev/null @@ -1,29 +0,0 @@ -.swatch { - display: inline-block; - cursor: pointer; -} - -.swatch:hover, -.swatch_enabled { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.41); -} - -.color { - width: 59px; - height: 24px; - border-radius: 2px; -} - -.popover { - margin-top: 15px; - position: absolute; - right: 24px; -} - -.cover { - position: fixed; - top: 10px; - right: 0; - bottom: 0; - left: 0; -} diff --git a/protocol-designer/src/components/ColorPicker/index.tsx b/protocol-designer/src/components/ColorPicker/index.tsx deleted file mode 100644 index 7b8ee199ab2..00000000000 --- a/protocol-designer/src/components/ColorPicker/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useState } from 'react' -import cx from 'classnames' -import { TwitterPicker } from 'react-color' -import { DEFAULT_LIQUID_COLORS } from '@opentrons/shared-data' -import type { ColorResult } from 'react-color' - -import styles from './ColorPicker.module.css' - -interface ColorPickerProps { - value: string - onChange: (hex: ColorResult['hex']) => void -} - -export function ColorPicker(props: ColorPickerProps): JSX.Element { - const { value, onChange } = props - const [showColorPicker, setShowColorPicker] = useState(false) - - return ( - <> -
-
{ - setShowColorPicker(showColorPicker => !showColorPicker) - }} - > -
-
- {showColorPicker ? ( -
-
{ - setShowColorPicker(false) - }} - /> - { - onChange(color.hex) - }} - /> -
- ) : null} -
- - ) -} diff --git a/protocol-designer/src/components/ComputingSpinner.tsx b/protocol-designer/src/components/ComputingSpinner.tsx deleted file mode 100644 index 71c8b527f91..00000000000 --- a/protocol-designer/src/components/ComputingSpinner.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useSelector } from 'react-redux' -import { css } from 'styled-components' -import { Box, CURSOR_WAIT, POSITION_FIXED } from '@opentrons/components' -import * as fileDataSelectors from '../file-data/selectors' - -const waitCursorStyle = css` - cursor: ${CURSOR_WAIT}; -` - -export const ComputingSpinner = (): JSX.Element | null => { - const showSpinner = useSelector(fileDataSelectors.getTimelineIsBeingComputed) - - return showSpinner ? ( - - ) : null -} diff --git a/protocol-designer/src/components/DeckSetup/DeckSetup.module.css b/protocol-designer/src/components/DeckSetup/DeckSetup.module.css deleted file mode 100644 index 2ecfd7e0a58..00000000000 --- a/protocol-designer/src/components/DeckSetup/DeckSetup.module.css +++ /dev/null @@ -1,22 +0,0 @@ -@import '@opentrons/components/styles'; - -.deck_wrapper { - flex: 1; - margin-top: 1rem; -} - -.deck_header { - font-size: var(--fs-header); /* from legacy --font-header-dark */ - font-weight: var(--fw-semibold); /* from legacy --font-header-dark */ - color: var(--c-font-dark); /* from legacy --font-header-dark */ - text-align: center; - padding: 1rem 0; -} - -.deck_row { - display: flex; - justify-content: center; - align-items: stretch; - max-height: calc(100vh - 3rem); - overflow-y: hidden; -} diff --git a/protocol-designer/src/components/DeckSetup/FlexModuleTag.tsx b/protocol-designer/src/components/DeckSetup/FlexModuleTag.tsx deleted file mode 100644 index 9c5ba9fc7c5..00000000000 --- a/protocol-designer/src/components/DeckSetup/FlexModuleTag.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { - RobotCoordsForeignDiv, - Text, - COLORS, - SPACING, -} from '@opentrons/components' -import type { ModuleDefinition } from '@opentrons/shared-data' - -interface FlexModuleTagProps { - dimensions: ModuleDefinition['dimensions'] - displayName: string -} - -export function FlexModuleTag(props: FlexModuleTagProps): JSX.Element { - const { dimensions, displayName } = props - return ( - - - {displayName} - - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/AdapterControls.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/AdapterControls.tsx deleted file mode 100644 index 3be204f8fd7..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/AdapterControls.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { useRef } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useDrop } from 'react-dnd' -import cx from 'classnames' -import noop from 'lodash/noop' -import { Icon, RobotCoordsForeignDiv } from '@opentrons/components' -import { DND_TYPES } from '../../../constants' -import { - getAdapterLabwareIsAMatch, - getLabwareIsCustom, -} from '../../../utils/labwareModuleCompatibility' -import { - deleteContainer, - moveDeckItem, - openAddLabwareModal, -} from '../../../labware-ingred/actions' -import { selectors as labwareDefSelectors } from '../../../labware-defs' -import { START_TERMINAL_ITEM_ID } from '../../../steplist' -import { BlockedSlot } from './BlockedSlot' - -import type { DropTargetMonitor } from 'react-dnd' -import type { CoordinateTuple, Dimensions } from '@opentrons/shared-data' -import type { TerminalItemId } from '../../../steplist' -import type { LabwareOnDeck } from '../../../step-forms' - -import styles from './LabwareOverlays.module.css' - -interface AdapterControlsProps { - slotPosition: CoordinateTuple - slotBoundingBox: Dimensions - // labwareId is the adapter's labwareId - labwareId: string - allLabware: LabwareOnDeck[] - onDeck: boolean - selectedTerminalItemId?: TerminalItemId | null - handleDragHover?: () => void -} - -interface DroppedItem { - labwareOnDeck: LabwareOnDeck -} - -export const AdapterControls = ( - props: AdapterControlsProps -): JSX.Element | null => { - const { - slotPosition, - slotBoundingBox, - selectedTerminalItemId, - labwareId, - onDeck, - handleDragHover, - allLabware, - } = props - const customLabwareDefs = useSelector( - labwareDefSelectors.getCustomLabwareDefsByURI - ) - const ref = useRef(null) - const dispatch = useDispatch() - - const adapterName = - allLabware.find(labware => labware.id === labwareId)?.def.metadata - .displayName ?? '' - - const [{ itemType, draggedItem, isOver }, drop] = useDrop( - () => ({ - accept: DND_TYPES.LABWARE, - canDrop: (item: DroppedItem) => { - const draggedDef = item.labwareOnDeck?.def - console.assert( - draggedDef, - 'no labware def of dragged item, expected it on drop' - ) - - if (draggedDef != null) { - const isCustomLabware = getLabwareIsCustom( - customLabwareDefs, - item.labwareOnDeck - ) - return ( - getAdapterLabwareIsAMatch( - labwareId, - allLabware, - draggedDef.parameters.loadName - ) || isCustomLabware - ) - } - return true - }, - drop: (item: DroppedItem) => { - const droppedLabware = item - if (droppedLabware.labwareOnDeck != null) { - const droppedSlot = droppedLabware.labwareOnDeck.slot - dispatch(moveDeckItem(droppedSlot, labwareId)) - } - }, - hover: () => { - if (handleDragHover != null) { - handleDragHover() - } - }, - collect: (monitor: DropTargetMonitor) => ({ - itemType: monitor.getItemType(), - isOver: !!monitor.isOver(), - draggedItem: monitor.getItem() as DroppedItem, - }), - }), - [] - ) - - if ( - selectedTerminalItemId !== START_TERMINAL_ITEM_ID || - (itemType !== DND_TYPES.LABWARE && itemType !== null) - ) - return null - const draggedDef = draggedItem?.labwareOnDeck?.def - const isCustomLabware = draggedItem - ? getLabwareIsCustom(customLabwareDefs, draggedItem.labwareOnDeck) - : false - - let slotBlocked: string | null = null - - if (isOver && draggedDef != null && isCustomLabware) { - slotBlocked = 'Custom Labware incompatible with this adapter' - } else if ( - isOver && - draggedDef != null && - !getAdapterLabwareIsAMatch( - labwareId, - allLabware, - draggedDef.parameters.loadName - ) - ) { - slotBlocked = 'Labware incompatible with this adapter' - } - - drop(ref) - - return ( - - {slotBlocked ? ( - - ) : ( - - dispatch(openAddLabwareModal({ slot: labwareId }))} - > - {!isOver && } - {isOver ? 'Place Here' : 'Add Labware'} - - { - window.confirm( - `"Are you sure you want to remove this ${adapterName}?` - ) && dispatch(deleteContainer({ labwareId: labwareId })) - }} - > - {!isOver && } - {'Delete'} - - - )} - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/BlockedSlot.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/BlockedSlot.tsx deleted file mode 100644 index aabb977e2e0..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/BlockedSlot.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { RobotCoordsForeignDiv } from '@opentrons/components' -import styles from './LabwareOverlays.module.css' - -type BlockedSlotMessage = - | 'MODULE_INCOMPATIBLE_SINGLE_LABWARE' - | 'MODULE_INCOMPATIBLE_LABWARE_SWAP' - | 'LABWARE_INCOMPATIBLE_WITH_ADAPTER' - -interface Props { - x: number - y: number - width: number - height: number - message: BlockedSlotMessage -} - -export const BlockedSlot = (props: Props): JSX.Element => { - const { t } = useTranslation('deck') - const { x, y, width, height, message } = props - return ( - - - - {t(`blocked_slot.${message}`)} - - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/BrowseLabware.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/BrowseLabware.tsx deleted file mode 100644 index 06650910a53..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/BrowseLabware.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useTranslation } from 'react-i18next' -import cx from 'classnames' -import { useDispatch } from 'react-redux' -import { Icon } from '@opentrons/components' -import { drillDownOnLabware } from '../../../labware-ingred/actions' -import { resetScrollElements } from '../../../ui/steps/utils' -import styles from './LabwareOverlays.module.css' - -import type { LabwareEntity } from '@opentrons/step-generation' -import type { LabwareOnDeck } from '../../../step-forms' - -interface Props { - labwareOnDeck: LabwareOnDeck | LabwareEntity -} - -export function BrowseLabware(props: Props): JSX.Element | null { - const { t } = useTranslation('deck') - const { labwareOnDeck } = props - const dispatch = useDispatch() - - const drillDown = (): void => { - resetScrollElements() - dispatch(drillDownOnLabware(labwareOnDeck.id)) - } - - if ( - props.labwareOnDeck.def.parameters.isTiprack || - props.labwareOnDeck.def.allowedRoles?.includes('adapter') - ) - return null - return ( - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabware.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabware.tsx deleted file mode 100644 index 3c3e577ecf0..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabware.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import cx from 'classnames' -import { Icon } from '@opentrons/components' -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { useDrag, useDrop } from 'react-dnd' -import { NameThisLabware } from './NameThisLabware' -import { DND_TYPES } from '../../../constants' -import { - deleteContainer, - duplicateLabware, - moveDeckItem, - openIngredientSelector, -} from '../../../labware-ingred/actions' -import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' -import styles from './LabwareOverlays.module.css' -import type { DropTargetMonitor } from 'react-dnd' -import type { ThunkDispatch } from '../../../types' -import type { LabwareOnDeck } from '../../../step-forms' - -interface Props { - labwareOnDeck: LabwareOnDeck - setHoveredLabware: (val?: LabwareOnDeck | null) => void - setDraggedLabware: (val?: LabwareOnDeck | null) => void - swapBlocked: boolean -} - -interface DroppedItem { - labwareOnDeck: LabwareOnDeck -} -export const EditLabware = (props: Props): JSX.Element | null => { - const { - labwareOnDeck, - swapBlocked, - setDraggedLabware, - setHoveredLabware, - } = props - const savedLabware = useSelector(labwareIngredSelectors.getSavedLabware) - const dispatch = useDispatch>() - const { t } = useTranslation('deck') - const ref = React.useRef(null) - - const { isTiprack } = labwareOnDeck.def.parameters - const hasName = savedLabware[labwareOnDeck.id] - const isYetUnnamed = !labwareOnDeck.def.parameters.isTiprack && !hasName - - const editLiquids = (): void => { - dispatch(openIngredientSelector(labwareOnDeck.id)) - } - - const [, drag] = useDrag( - () => ({ - type: DND_TYPES.LABWARE, - item: { labwareOnDeck }, - }), - [labwareOnDeck] - ) - - const [{ draggedLabware, isOver }, drop] = useDrop( - () => ({ - accept: DND_TYPES.LABWARE, - canDrop: (item: DroppedItem) => { - const draggedLabware = item?.labwareOnDeck - const isDifferentSlot = - draggedLabware && draggedLabware.slot !== labwareOnDeck.slot - return isDifferentSlot && !swapBlocked - }, - drop: (item: DroppedItem) => { - const draggedLabware = item?.labwareOnDeck - if (draggedLabware != null) { - dispatch(moveDeckItem(draggedLabware.slot, labwareOnDeck.slot)) - } - }, - hover: () => { - setHoveredLabware(labwareOnDeck) - }, - collect: (monitor: DropTargetMonitor) => ({ - isOver: monitor.isOver(), - draggedLabware: monitor.getItem() as DroppedItem, - }), - }), - [labwareOnDeck] - ) - - React.useEffect(() => { - if (draggedLabware?.labwareOnDeck != null) { - setDraggedLabware(draggedLabware?.labwareOnDeck) - } else { - setHoveredLabware(null) - setDraggedLabware(null) - } - }, [draggedLabware]) - - let contents: React.ReactNode | null = null - - const isBeingDragged = - draggedLabware?.labwareOnDeck?.slot === labwareOnDeck.slot - - if (isYetUnnamed && !isTiprack) { - contents = ( - - ) - } else if (swapBlocked) { - contents = null - } else if (draggedLabware != null) { - contents = null - } else { - contents = ( - <> - {!isTiprack ? ( - - - {t('overlay.edit.name_and_liquids')} - - ) : ( -
- )} - dispatch(duplicateLabware(labwareOnDeck.id))} - > - - {t('overlay.edit.duplicate')} - - { - window.confirm( - `Are you sure you want to permanently delete this ${getLabwareDisplayName( - labwareOnDeck.def - )}?` - ) && dispatch(deleteContainer({ labwareId: labwareOnDeck.id })) - }} - > - - {t('overlay.edit.delete')} - - - ) - } - - drag(drop(ref)) - - return ( -
- {contents} -
- ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabwareOffDeck.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabwareOffDeck.tsx deleted file mode 100644 index c6a5a444d0c..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/EditLabwareOffDeck.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { css } from 'styled-components' -import { - ALIGN_FLEX_START, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Icon, - JUSTIFY_SPACE_AROUND, - POSITION_ABSOLUTE, - SPACING, -} from '@opentrons/components' -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { - deleteContainer, - duplicateLabware, - openIngredientSelector, -} from '../../../labware-ingred/actions' -import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' -import { NameThisLabware } from './NameThisLabware' -import styles from './LabwareOverlays.module.css' - -import type { LabwareEntity } from '@opentrons/step-generation' -import type { ThunkDispatch } from '../../../types' - -const NAME_LABWARE_OVERLAY_STYLE = css` - z-index: 1; - bottom: 0; - position: ${POSITION_ABSOLUTE}; - width: 127.76px; - height: 85.45px; - opacity: 0; - &:hover { - opacity: 1; - } -` - -const REGULAR_OVERLAY_STYLE = css` - z-index: 1; - padding: ${SPACING.spacing8}; - background-color: ${COLORS.black90}${COLORS.opacity90HexCode}; - flex-direction: ${DIRECTION_COLUMN}; - color: ${COLORS.white}; - display: flex; - align-items: ${ALIGN_FLEX_START}; - justify-content: ${JUSTIFY_SPACE_AROUND}; - border-radius: ${BORDERS.borderRadius16}; - bottom: 0; - font-size: 0.7rem; - position: ${POSITION_ABSOLUTE}; - width: 127.76px; - height: 85.45px; - opacity: 0; - &:hover { - opacity: 1; - } -` - -interface EditLabwareOffDeckProps { - labwareEntity: LabwareEntity -} - -export function EditLabwareOffDeck( - props: EditLabwareOffDeckProps -): JSX.Element { - const { labwareEntity } = props - const { t } = useTranslation('deck') - const dispatch = useDispatch>() - const allSavedLabware = useSelector(labwareIngredSelectors.getSavedLabware) - const hasName = allSavedLabware[labwareEntity.id] - const { isTiprack } = labwareEntity.def.parameters - - const isYetUnnamed = isTiprack && !hasName - - const editLiquids = (): void => { - dispatch(openIngredientSelector(labwareEntity.id)) - } - - if (isYetUnnamed && !isTiprack) { - return ( -
- -
- ) - } else { - return ( -
- {!isTiprack ? ( - - - {t('overlay.edit.name_and_liquids')} - - ) : ( - - ) - } -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareControls.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareControls.tsx deleted file mode 100644 index 1c31a962e75..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareControls.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import cx from 'classnames' -import { RobotCoordsForeignDiv } from '@opentrons/components' - -import { START_TERMINAL_ITEM_ID } from '../../../steplist' -import { BlockedSlot } from './BlockedSlot' -import { BrowseLabware } from './BrowseLabware' -import { EditLabware } from './EditLabware' -import { LabwareName } from './LabwareName' -import { LabwareHighlight } from './LabwareHighlight' -import styles from './LabwareOverlays.module.css' - -import type { CoordinateTuple } from '@opentrons/shared-data' -import type { TerminalItemId } from '../../../steplist' -import type { LabwareOnDeck } from '../../../step-forms' - -interface LabwareControlsProps { - labwareOnDeck: LabwareOnDeck - slotPosition: CoordinateTuple - setHoveredLabware: (labware?: LabwareOnDeck | null) => void - setDraggedLabware: (labware?: LabwareOnDeck | null) => void - swapBlocked: boolean - selectedTerminalItemId?: TerminalItemId | null -} - -export const LabwareControls = (props: LabwareControlsProps): JSX.Element => { - const { - labwareOnDeck, - slotPosition, - selectedTerminalItemId, - setHoveredLabware, - setDraggedLabware, - swapBlocked, - } = props - - const canEdit = selectedTerminalItemId === START_TERMINAL_ITEM_ID - const [x, y] = slotPosition - const width = labwareOnDeck.def.dimensions.xDimension - const height = labwareOnDeck.def.dimensions.yDimension - return ( - <> - - - {canEdit ? ( - - ) : ( - - )} - - - {swapBlocked && ( - - )} - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareHighlight.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareHighlight.tsx deleted file mode 100644 index 78d0a8ada5c..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareHighlight.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import cx from 'classnames' -import { useSelector } from 'react-redux' -import { Icon } from '@opentrons/components' -import { getHoveredStepLabware, getHoveredStepId } from '../../../ui/steps' -import { - getLabwareEntities, - getSavedStepForms, -} from '../../../step-forms/selectors' -import { THERMOCYCLER_PROFILE } from '../../../constants' - -import styles from './LabwareOverlays.module.css' -import type { LabwareOnDeck } from '../../../step-forms' - -interface LabwareHighlightProps { - labwareOnDeck: LabwareOnDeck -} - -export const LabwareHighlight = ( - props: LabwareHighlightProps -): JSX.Element | null => { - const { labwareOnDeck } = props - const labwareEntities = useSelector(getLabwareEntities) - const adapterId = - labwareEntities[labwareOnDeck.slot] != null - ? labwareEntities[labwareOnDeck.slot].id - : null - - const highlighted = useSelector(getHoveredStepLabware).includes( - adapterId ?? labwareOnDeck.id - ) - - let isTcProfile = false - const form = useSelector(getSavedStepForms) - const hoveredStepId = useSelector(getHoveredStepId) - const formData = hoveredStepId ? form[hoveredStepId] : null - - if ( - formData && - formData.stepType === 'thermocycler' && - formData.thermocyclerFormType === THERMOCYCLER_PROFILE - ) { - isTcProfile = true - } - if (highlighted) { - return ( -
- {isTcProfile && ( - - )} -
- ) - } - return null -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareName.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareName.tsx deleted file mode 100644 index 15d07bd9886..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareName.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useSelector } from 'react-redux' -import { LabwareNameOverlay, truncateString } from '@opentrons/components' -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { selectors as uiLabwareSelectors } from '../../../ui/labware' -import type { LabwareOnDeck } from '../../../step-forms' -interface LabwareNameProps { - labwareOnDeck: LabwareOnDeck -} - -export function LabwareName(props: LabwareNameProps): JSX.Element { - const { labwareOnDeck } = props - const nicknames = useSelector(uiLabwareSelectors.getLabwareNicknamesById) - const nickname = nicknames[labwareOnDeck.id] - const truncatedNickName = - nickname != null ? truncateString(nickname, 75, 25) : null - const title = truncatedNickName ?? getLabwareDisplayName(labwareOnDeck.def) - return -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareOverlays.module.css b/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareOverlays.module.css deleted file mode 100644 index 676da1993b3..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/LabwareOverlays.module.css +++ /dev/null @@ -1,155 +0,0 @@ -@import '@opentrons/components/styles'; - -.labware_controls { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - justify-content: flex-end; - border-radius: 0.5rem; -} - -.labware_controls.can_edit { - cursor: grab; -} - -.slot_overlay { - position: absolute; - - /* from legacy --absolute-fill */ - top: 0; - - /* from legacy --absolute-fill */ - right: 0; - - /* from legacy --absolute-fill */ - bottom: 0; - - /* from legacy --absolute-fill */ - left: 0; - - /* from legacy --absolute-fill */ - - z-index: 1; - padding: 0.5rem; - background-color: color-mod(var(--c-black) alpha(0.75)); - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: flex-start; - color: white; - font-size: var(--fs-body-1); - border-radius: 0.5rem; -} - -.slot_overlay.with_form { - background-color: color-mod(var(--c-black) alpha(1)); -} - -.slot_overlay.disabled { - background-color: color-mod(var(--c-light-gray) alpha(0.7)); - color: var(--c-font-dark); -} - -.name_input_wrapper { - flex: 1; -} - -.name_input { - padding: 0 0 0 0.25rem; - width: 7rem; -} - -.overlay_button { - flex: 1; - text-decoration: none; - color: white; - display: flex; - align-items: center; - cursor: pointer; -} - -.overlay_button:active { - opacity: 0.5; -} - -.overlay_button.drag_text { - color: var(--c-font-dark); -} - -.button_spacer { - flex: 1; -} - -.overlay_icon { - height: 1rem; - width: 1rem; - margin-right: 0.25rem; -} - -.appear_on_mouseover { - opacity: 0; - - &:hover { - opacity: 1; - } -} - -.appear { - opacity: 1; -} - -.drag_preview { - position: absolute; - cursor: grabbing; -} - -.highlighted_border_div { - position: absolute; - - /* from legacy --absolute-fill */ - top: 0; - - /* from legacy --absolute-fill */ - right: 0; - - /* from legacy --absolute-fill */ - bottom: 0; - - /* from legacy --absolute-fill */ - left: 0; - - /* from legacy --absolute-fill */ - - border-color: var(--c-highlight); - border-width: 3px; - border-style: solid; - border-radius: 0.5rem; -} - -.highlight_fill { - display: flex; - align-items: center; - justify-content: center; - background-color: color-mod(var(--c-highlight) alpha(0.25)); -} - -.thermocycler_icon { - height: 60%; - color: var(--c-selected-dark); -} - -.blocked_slot_background { - fill: rgba(200, 115, 0, 0.75); - stroke: var(--c-red); - rx: 6; -} - -.blocked_slot_content { - height: 100%; - margin: -1.5rem 0.5rem; - color: var(--c-white); - font-size: var(--fs-caption); - text-align: center; - line-height: 1.5; -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/NameThisLabware.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/NameThisLabware.tsx deleted file mode 100644 index 71bf91cf2e5..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/NameThisLabware.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useDispatch } from 'react-redux' -import cx from 'classnames' -import { Icon, useOnClickOutside } from '@opentrons/components' -import { renameLabware } from '../../../labware-ingred/actions' -import styles from './LabwareOverlays.module.css' - -import type { LabwareEntity } from '@opentrons/step-generation' -import type { ThunkDispatch } from '../../../types' -import type { LabwareOnDeck } from '../../../step-forms' - -interface NameThisLabwareProps { - labwareOnDeck: LabwareOnDeck | LabwareEntity - editLiquids: () => void -} - -export function NameThisLabware(props: NameThisLabwareProps): JSX.Element { - const { labwareOnDeck } = props - const dispatch: ThunkDispatch = useDispatch() - const [inputValue, setInputValue] = React.useState('') - const { t } = useTranslation('deck') - - const setLabwareName = (name: string | null | undefined): void => { - dispatch(renameLabware({ labwareId: labwareOnDeck.id, name })) - } - - const saveNickname = (): void => { - setLabwareName(inputValue ?? null) - } - const wrapperRef: React.RefObject = useOnClickOutside({ - onClickOutside: saveNickname, - }) - - const handleChange = (e: React.ChangeEvent): void => { - setInputValue(e.target.value) - } - - const handleKeyUp = (e: React.KeyboardEvent): void => { - if (e.key === 'Enter') { - saveNickname() - } - } - const addLiquids = (): void => { - saveNickname() - props.editLiquids() - } - - return ( - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/SlotControls.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/SlotControls.tsx deleted file mode 100644 index 614d2fc9d55..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/SlotControls.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { useRef } from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector, useDispatch } from 'react-redux' -import noop from 'lodash/noop' -import { useDrop, useDrag } from 'react-dnd' -import cx from 'classnames' -import { Icon, RobotCoordsForeignDiv } from '@opentrons/components' -import { DND_TYPES } from '../../../constants' -import { - getLabwareIsCompatible, - getLabwareIsCustom, -} from '../../../utils/labwareModuleCompatibility' -import { - moveDeckItem, - openAddLabwareModal, -} from '../../../labware-ingred/actions' -import { selectors as labwareDefSelectors } from '../../../labware-defs' -import { START_TERMINAL_ITEM_ID } from '../../../steplist' -import { BlockedSlot } from './BlockedSlot' -import styles from './LabwareOverlays.module.css' - -import type { DropTargetMonitor } from 'react-dnd' -import type { - CoordinateTuple, - Dimensions, - ModuleType, -} from '@opentrons/shared-data' -import type { LabwareOnDeck } from '../../../step-forms' -import type { TerminalItemId } from '../../../steplist' - -interface SlotControlsProps { - slotPosition: CoordinateTuple | null - slotBoundingBox: Dimensions - // NOTE: slotId can be either AddressableAreaName or moduleId - slotId: string - moduleType: ModuleType | null - selectedTerminalItemId?: TerminalItemId | null - handleDragHover?: () => void -} - -interface DroppedItem { - labwareOnDeck: LabwareOnDeck -} - -export const SlotControls = (props: SlotControlsProps): JSX.Element | null => { - const { - slotBoundingBox, - slotPosition, - slotId, - selectedTerminalItemId, - moduleType, - handleDragHover, - } = props - const customLabwareDefs = useSelector( - labwareDefSelectors.getCustomLabwareDefsByURI - ) - const ref = useRef(null) - const dispatch = useDispatch() - - const { t } = useTranslation('deck') - - const [, drag] = useDrag({ - type: DND_TYPES.LABWARE, - item: { labwareOnDeck: null }, - }) - - const [{ draggedItem, itemType, isOver }, drop] = useDrop( - () => ({ - accept: DND_TYPES.LABWARE, - canDrop: (item: DroppedItem) => { - const draggedDef = item?.labwareOnDeck?.def - console.assert( - draggedDef, - 'no labware def of dragged item, expected it on drop' - ) - - if (moduleType != null && draggedDef != null) { - // this is a module slot, prevent drop if the dragged labware is not compatible - const isCustomLabware = getLabwareIsCustom( - customLabwareDefs, - item.labwareOnDeck - ) - - return ( - getLabwareIsCompatible(draggedDef, moduleType) || isCustomLabware - ) - } - return true - }, - drop: (item: DroppedItem) => { - const droppedLabware = item - if (droppedLabware.labwareOnDeck != null) { - const droppedSlot = droppedLabware.labwareOnDeck.slot - dispatch(moveDeckItem(droppedSlot, slotId)) - } - }, - hover: () => { - if (handleDragHover != null) { - handleDragHover() - } - }, - collect: (monitor: DropTargetMonitor) => ({ - itemType: monitor.getItemType(), - isOver: !!monitor.isOver(), - draggedItem: monitor.getItem() as DroppedItem, - }), - }), - [] - ) - - if ( - selectedTerminalItemId !== START_TERMINAL_ITEM_ID || - (itemType !== DND_TYPES.LABWARE && itemType !== null) || - slotPosition == null - ) - return null - - const draggedDef = draggedItem?.labwareOnDeck?.def - - const isCustomLabware = draggedItem - ? getLabwareIsCustom(customLabwareDefs, draggedItem.labwareOnDeck) - : false - - let slotBlocked: string | null = null - if ( - isOver && - moduleType != null && - draggedDef != null && - !getLabwareIsCompatible(draggedDef, moduleType) && - !isCustomLabware - ) { - slotBlocked = 'Labware incompatible with this module' - } - - const isOnHeaterShaker = moduleType === 'heaterShakerModuleType' - const isNoAdapterOption = - moduleType === 'magneticBlockType' || - moduleType === 'magneticModuleType' || - moduleType === 'thermocyclerModuleType' - let overlayText: string = 'add_adapter_or_labware' - if (isOnHeaterShaker) { - overlayText = 'add_adapter' - } else if (isNoAdapterOption) { - overlayText = 'add_labware' - } - - const addLabware = (): void => { - dispatch(openAddLabwareModal({ slot: slotId })) - } - - drag(drop(ref)) - - return ( - - {slotBlocked ? ( - - ) : ( - - - {!isOver && } - {t(`overlay.slot.${isOver ? 'place_here' : overlayText}`)} - - - )} - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/__tests__/SlotControls.test.tsx b/protocol-designer/src/components/DeckSetup/LabwareOverlays/__tests__/SlotControls.test.tsx deleted file mode 100644 index 60972821a90..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/__tests__/SlotControls.test.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { describe, it } from 'vitest' - -describe('SlotControlsComponent', () => { - it.todo('replace deprecated enzyme test') -}) diff --git a/protocol-designer/src/components/DeckSetup/LabwareOverlays/index.ts b/protocol-designer/src/components/DeckSetup/LabwareOverlays/index.ts deleted file mode 100644 index cd857edd17a..00000000000 --- a/protocol-designer/src/components/DeckSetup/LabwareOverlays/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { SlotControls } from './SlotControls' -export { AdapterControls } from './AdapterControls' -export { LabwareControls } from './LabwareControls' diff --git a/protocol-designer/src/components/DeckSetup/NullDeckState.tsx b/protocol-designer/src/components/DeckSetup/NullDeckState.tsx deleted file mode 100644 index f2c26f579cc..00000000000 --- a/protocol-designer/src/components/DeckSetup/NullDeckState.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useMemo } from 'react' -import { getDeckDefinitions } from '@opentrons/shared-data' -import { useTranslation } from 'react-i18next' -import { - FONT_SIZE_BODY_1, - FONT_WEIGHT_BOLD, - RobotCoordsText, - RobotWorkSpace, - TEXT_TRANSFORM_UPPERCASE, -} from '@opentrons/components' -import { - VIEWBOX_HEIGHT, - VIEWBOX_MIN_X, - VIEWBOX_MIN_Y, - VIEWBOX_WIDTH, -} from './constants' -import { DECK_LAYER_BLOCKLIST } from './index' - -import styles from './DeckSetup.module.css' - -export const NullDeckState = (): JSX.Element => { - const deckDef = useMemo(() => getDeckDefinitions().ot2_standard, []) - const { t } = useTranslation('deck') - return ( -
-
- - {() => ( - - {t('inactive_deck')} - - )} - -
-
- ) -} diff --git a/protocol-designer/src/components/DeckSetup/Ot2ModuleTag.tsx b/protocol-designer/src/components/DeckSetup/Ot2ModuleTag.tsx deleted file mode 100644 index 6c5c7d57b5c..00000000000 --- a/protocol-designer/src/components/DeckSetup/Ot2ModuleTag.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { - RobotCoordsForeignDiv, - Text, - COLORS, - SPACING, -} from '@opentrons/components' -import { - getModuleDisplayName, - THERMOCYCLER_MODULE_V1, - THERMOCYCLER_MODULE_V2, -} from '@opentrons/shared-data' -import type { - ModuleDefinition, - ModuleModel, - ModuleOrientation, -} from '@opentrons/shared-data' - -interface Ot2ModuleTagProps { - dimensions: ModuleDefinition['dimensions'] - model: ModuleModel - orientation: ModuleOrientation -} - -export function Ot2ModuleTag(props: Ot2ModuleTagProps): JSX.Element { - const { dimensions, model, orientation } = props - const displayName = getModuleDisplayName(model) - const isThermocyclerModel = - model === THERMOCYCLER_MODULE_V1 || model === THERMOCYCLER_MODULE_V2 - const xDimension = dimensions.labwareInterfaceXDimension ?? 0 - const xCoordinateForOtherMods = - orientation === 'left' - ? -(xDimension / 2) - : dimensions.labwareInterfaceXDimension - - return ( - - - {displayName} - - - ) -} diff --git a/protocol-designer/src/components/DeckSetup/SlotLabels.tsx b/protocol-designer/src/components/DeckSetup/SlotLabels.tsx deleted file mode 100644 index a4262ba26b9..00000000000 --- a/protocol-designer/src/components/DeckSetup/SlotLabels.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { - DeckInfoLabel, - Flex, - JUSTIFY_CENTER, - RobotCoordsForeignObject, - ALIGN_CENTER, - DIRECTION_COLUMN, -} from '@opentrons/components' - -import type { RobotType } from '@opentrons/shared-data' - -interface SlotLabelsProps { - robotType: RobotType - hasStagingAreas: boolean - hasWasteChute: boolean -} - -/** - * This is an almost copy of SlotLabels in @opentrons/components - * in order to keep the changes between PD and the rest - * of the repo separate - */ -export const SlotLabels = ({ - robotType, - hasStagingAreas, - hasWasteChute, -}: SlotLabelsProps): JSX.Element | null => { - return robotType === FLEX_ROBOT_TYPE ? ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - {hasStagingAreas ? ( - - - - ) : null} - - - - ) : null -} diff --git a/protocol-designer/src/components/DeckSetup/__tests__/DeckSetup.test.ts b/protocol-designer/src/components/DeckSetup/__tests__/DeckSetup.test.ts deleted file mode 100644 index 5051d10610a..00000000000 --- a/protocol-designer/src/components/DeckSetup/__tests__/DeckSetup.test.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest' -import { - fixture_96_plate, - fixture_24_tuberack, -} from '@opentrons/shared-data/labware/fixtures/2' -import { - MAGNETIC_MODULE_TYPE, - TEMPERATURE_MODULE_TYPE, - MAGNETIC_MODULE_V1, - TEMPERATURE_MODULE_V1, -} from '@opentrons/shared-data' -import { TEMPERATURE_AT_TARGET } from '@opentrons/step-generation' -import * as labwareModuleCompatibility from '../../../utils/labwareModuleCompatibility' -import { getSwapBlocked } from '../utils' - -import type { MockInstance } from 'vitest' -import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { SwapBlockedArgs } from '../utils' - -describe('DeckSetup', () => { - describe('getSwapBlocked', () => { - const plateInSlot3 = { - labwareDefURI: 'fixture/fixture_96_plate', - id: 'plate123', - slot: '3', - def: fixture_96_plate as LabwareDefinition2, - } - const tuberackInSlot4 = { - labwareDefURI: 'fixture/fixtures_24_tuberack', - id: 'tuberack098', - slot: '4', - def: fixture_24_tuberack as LabwareDefinition2, - } - - const magneticModule = { - id: 'magnet123', - type: MAGNETIC_MODULE_TYPE, - model: MAGNETIC_MODULE_V1, - moduleState: { - type: MAGNETIC_MODULE_TYPE, - engaged: false, - }, - slot: '1', - } - const temperatureModule = { - id: 'temperature098', - type: TEMPERATURE_MODULE_TYPE, - model: TEMPERATURE_MODULE_V1, - moduleState: { - type: TEMPERATURE_MODULE_TYPE, - status: TEMPERATURE_AT_TARGET, - targetTemperature: 45, - }, - slot: '7', - } - - let getLabwareIsCompatibleSpy: MockInstance - beforeEach(() => { - getLabwareIsCompatibleSpy = vi.spyOn( - labwareModuleCompatibility, - 'getLabwareIsCompatible' - ) - }) - - afterEach(() => { - getLabwareIsCompatibleSpy.mockClear() - }) - - it('is not blocked when there is no labware in slot', () => { - const args: SwapBlockedArgs = { - hoveredLabware: null, - draggedLabware: plateInSlot3, - modulesById: {}, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(false) - }) - - it('is not blocked when no dragged labware', () => { - const args: SwapBlockedArgs = { - hoveredLabware: plateInSlot3, - draggedLabware: null, - modulesById: {}, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(false) - }) - - it('is not blocked when dragged labware to swap on module is custom', () => { - tuberackInSlot4.slot = 'magnet123' - getLabwareIsCompatibleSpy.mockReturnValue(false) - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: { - magnet123: magneticModule, - }, - customLabwareDefs: { - 'fixture/fixture_96_plate': fixture_96_plate as LabwareDefinition2, - }, - } - - const isBlocked = getSwapBlocked(args as SwapBlockedArgs) - - expect(isBlocked).toEqual(false) - }) - - it('is blocked when dragged labware on module to swap on another module is not custom and not compatible', () => { - tuberackInSlot4.slot = 'magnet123' - getLabwareIsCompatibleSpy.mockReturnValue(false) - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: { - magnet123: magneticModule, - }, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(true) - }) - - it('is blocked when target labware on module to swap is incompatible with dragged labware', () => { - tuberackInSlot4.slot = 'magnet123' - getLabwareIsCompatibleSpy.mockReturnValue(false) - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: { - magnet123: magneticModule, - }, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(true) - }) - - it('is not blocked when labwares to swap on both modules are compatible', () => { - tuberackInSlot4.slot = 'magnet123' - plateInSlot3.slot = 'temperature098' - getLabwareIsCompatibleSpy.mockReturnValue(true) - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: { - magnet123: magneticModule, - temperature098: temperatureModule, - }, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(false) - }) - - it('is blocked when both dragged and target labwares on the modules are incompatible', () => { - tuberackInSlot4.slot = 'magnet123' - plateInSlot3.slot = 'temperature098' - getLabwareIsCompatibleSpy.mockReturnValue(false) - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: { - magnet123: magneticModule, - temperature098: temperatureModule, - }, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(true) - }) - - it('is not blocked when swapping labware from module with compatible labware on deck slot', () => { - plateInSlot3.slot = 'temperature098' - getLabwareIsCompatibleSpy.mockReturnValue(true) - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: { - temperature098: temperatureModule, - }, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(false) - }) - - it('is not blocked when swapping labware from one deck slot with another labware on deck slot', () => { - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: {}, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(false) - }) - - it('is not blocked when swapping compatible labware on deck slot with labware on module', () => { - tuberackInSlot4.slot = 'magnet123' - getLabwareIsCompatibleSpy.mockReturnValue(true) - const args: SwapBlockedArgs = { - hoveredLabware: tuberackInSlot4, - draggedLabware: plateInSlot3, - modulesById: { - magnet123: magneticModule, - }, - customLabwareDefs: {}, - } - - const isBlocked = getSwapBlocked(args) - - expect(isBlocked).toEqual(false) - }) - }) -}) diff --git a/protocol-designer/src/components/DeckSetup/__tests__/FlexModuleTag.test.tsx b/protocol-designer/src/components/DeckSetup/__tests__/FlexModuleTag.test.tsx deleted file mode 100644 index 4fb9c967c5e..00000000000 --- a/protocol-designer/src/components/DeckSetup/__tests__/FlexModuleTag.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type * as React from 'react' -import { screen } from '@testing-library/react' -import { describe, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { FlexModuleTag } from '../FlexModuleTag' -import type { ModuleDimensions } from '@opentrons/shared-data' - -vi.mock('@opentrons/components', async () => { - const actual = await vi.importActual('@opentrons/components') - return { - ...actual, - RobotCoordsForeignDiv: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), - } -}) - -const render = (props: React.ComponentProps) => { - return renderWithProviders()[0] -} - -const mockDimensions: ModuleDimensions = { - labwareInterfaceXDimension: 5, -} as any - -describe('FlexModuleTag', () => { - it('renders the flex module tag for magnetic block', () => { - render({ - dimensions: mockDimensions, - displayName: 'mock Magnetic Block', - }) - screen.getByText('mock Magnetic Block') - }) - it('renders the flex module tag for heater-shaker', () => { - render({ - dimensions: mockDimensions, - displayName: 'mock Heater-shaker', - }) - screen.getByText('mock Heater-shaker') - }) -}) diff --git a/protocol-designer/src/components/DeckSetup/__tests__/Ot2ModuleTag.test.tsx b/protocol-designer/src/components/DeckSetup/__tests__/Ot2ModuleTag.test.tsx deleted file mode 100644 index 279855682a9..00000000000 --- a/protocol-designer/src/components/DeckSetup/__tests__/Ot2ModuleTag.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it } from 'vitest' -import type * as React from 'react' -import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { Ot2ModuleTag } from '../Ot2ModuleTag' -import type { ModuleDimensions } from '@opentrons/shared-data' - -const render = (props: React.ComponentProps) => { - return renderWithProviders()[0] -} - -const mockDimensions: ModuleDimensions = { - labwareInterfaceXDimension: 5, -} as any - -describe('Ot2ModuleTag', () => { - it('renders module tag for left magnetic module', () => { - render({ - dimensions: mockDimensions, - orientation: 'left', - model: 'magneticModuleV1', - }) - screen.getByText('Magnetic Module GEN1') - }) - it('renders module tag for right heater-shaker', () => { - render({ - dimensions: mockDimensions, - orientation: 'right', - model: 'heaterShakerModuleV1', - }) - screen.getByText('Heater-Shaker Module GEN1') - }) - it('renders module tag for thermocycler', () => { - render({ - dimensions: mockDimensions, - orientation: 'left', - model: 'thermocyclerModuleV1', - }) - screen.getByText('Thermocycler Module GEN1') - }) -}) diff --git a/protocol-designer/src/components/DeckSetup/constants.ts b/protocol-designer/src/components/DeckSetup/constants.ts deleted file mode 100644 index 2037126b253..00000000000 --- a/protocol-designer/src/components/DeckSetup/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const VIEWBOX_MIN_X = -64 -export const VIEWBOX_MIN_Y = -10 -export const VIEWBOX_WIDTH = 520 -export const VIEWBOX_HEIGHT = 414 diff --git a/protocol-designer/src/components/DeckSetup/index.tsx b/protocol-designer/src/components/DeckSetup/index.tsx deleted file mode 100644 index 736753e715a..00000000000 --- a/protocol-designer/src/components/DeckSetup/index.tsx +++ /dev/null @@ -1,677 +0,0 @@ -import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import compact from 'lodash/compact' -import values from 'lodash/values' -import { - COLORS, - DeckFromLayers, - FlexTrash, - Module, - RobotCoordinateSpaceWithRef, - SingleSlotFixture, - StagingAreaFixture, - useOnClickOutside, - WasteChuteFixture, - WasteChuteStagingAreaFixture, -} from '@opentrons/components' -import { MODULES_WITH_COLLISION_ISSUES } from '@opentrons/step-generation' -import { - FLEX_ROBOT_TYPE, - getAddressableAreaFromSlotId, - getDeckDefFromRobotType, - getLabwareHasQuirk, - getModuleDef2, - getModuleDisplayName, - getPositionFromSlotId, - inferModuleOrientationFromSlot, - inferModuleOrientationFromXCoordinate, - isAddressableAreaStandardSlot, - OT2_ROBOT_TYPE, - STAGING_AREA_CUTOUTS, - THERMOCYCLER_MODULE_TYPE, - TRASH_BIN_ADAPTER_FIXTURE, - WASTE_CHUTE_CUTOUT, -} from '@opentrons/shared-data' -import { SPAN7_8_10_11_SLOT } from '../../constants' -import { selectors as labwareDefSelectors } from '../../labware-defs' - -import { selectors as featureFlagSelectors } from '../../feature-flags' -import { getStagingAreaAddressableAreas } from '../../utils' -import { getSlotIdsBlockedBySpanning, getSlotIsEmpty } from '../../step-forms' -import * as labwareIngredActions from '../../labware-ingred/actions' -import { getDeckSetupForActiveItem } from '../../top-selectors/labware-locations' -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' -import { getSelectedTerminalItemId } from '../../ui/steps' -import { getRobotType } from '../../file-data/selectors' -import { BrowseLabwareModal } from '../labware' -import { SlotWarning } from './SlotWarning' -import { LabwareOnDeck } from './LabwareOnDeck' -import { - AdapterControls, - SlotControls, - LabwareControls, -} from './LabwareOverlays' -import { FlexModuleTag } from './FlexModuleTag' -import { Ot2ModuleTag } from './Ot2ModuleTag' -import { SlotLabels } from './SlotLabels' -import { getHasGen1MultiChannelPipette, getSwapBlocked } from './utils' - -import type { - AdditionalEquipmentEntity, - ModuleTemporalProperties, -} from '@opentrons/step-generation' -import type { StagingAreaLocation, TrashCutoutId } from '@opentrons/components' -import type { - AddressableAreaName, - CutoutFixture, - CutoutId, - DeckDefinition, - RobotType, -} from '@opentrons/shared-data' -import type { TerminalItemId } from '../../steplist' -import type { - InitialDeckSetup, - LabwareOnDeck as LabwareOnDeckType, - ModuleOnDeck, -} from '../../step-forms' - -import styles from './DeckSetup.module.css' - -export const DECK_LAYER_BLOCKLIST = [ - 'calibrationMarkings', - 'fixedBase', - 'doorStops', - 'metalFrame', - 'removalHandle', - 'removableDeckOutline', - 'screwHoles', -] - -const OT2_STANDARD_DECK_VIEW_LAYER_BLOCK_LIST: string[] = [ - 'calibrationMarkings', - 'fixedBase', - 'doorStops', - 'metalFrame', - 'removalHandle', - 'removableDeckOutline', - 'screwHoles', -] - -interface ContentsProps { - activeDeckSetup: InitialDeckSetup - selectedTerminalItemId?: TerminalItemId | null - showGen1MultichannelCollisionWarnings: boolean - deckDef: DeckDefinition - robotType: RobotType - stagingAreaCutoutIds: CutoutId[] - trashSlot: string | null -} - -const lightFill = COLORS.grey35 -const darkFill = COLORS.grey60 - -export const DeckSetupContents = (props: ContentsProps): JSX.Element => { - const { - activeDeckSetup, - showGen1MultichannelCollisionWarnings, - deckDef, - robotType, - trashSlot, - stagingAreaCutoutIds, - } = props - // NOTE: handling module<>labware compat when moving labware to empty module - // is handled by SlotControls. - // But when swapping labware when at least one is on a module, we need to be aware - // of not only what labware is being dragged, but also what labware is **being - // hovered over**. The intrinsic state of `react-dnd` is not designed to handle that. - // So we need to use our own state here to determine - // whether swapping will be blocked due to labware<>module compat: - const [hoveredLabware, setHoveredLabware] = React.useState< - LabwareOnDeckType | null | undefined - >(null) - const [draggedLabware, setDraggedLabware] = React.useState< - LabwareOnDeckType | null | undefined - >(null) - - const customLabwareDefs = useSelector( - labwareDefSelectors.getCustomLabwareDefsByURI - ) - const swapBlocked = getSwapBlocked({ - hoveredLabware, - draggedLabware, - modulesById: activeDeckSetup.modules, - customLabwareDefs, - }) - - const handleHoverEmptySlot = React.useCallback(() => { - setHoveredLabware(null) - }, []) - - const slotIdsBlockedBySpanning = getSlotIdsBlockedBySpanning(activeDeckSetup) - - const allLabware: LabwareOnDeckType[] = Object.keys( - activeDeckSetup.labware - ).reduce((acc, labwareId) => { - const labware = activeDeckSetup.labware[labwareId] - return getLabwareHasQuirk(labware.def, 'fixedTrash') - ? acc - : [...acc, labware] - }, []) - - const allModules: ModuleOnDeck[] = values(activeDeckSetup.modules) - - // NOTE: naively hard-coded to show warning north of slots 1 or 3 when occupied by any module - const multichannelWarningSlotIds: AddressableAreaName[] = showGen1MultichannelCollisionWarnings - ? compact([ - allModules.some( - moduleOnDeck => - moduleOnDeck.slot === '1' && - MODULES_WITH_COLLISION_ISSUES.includes(moduleOnDeck.model) - ) - ? deckDef.locations.addressableAreas.find(s => s.id === '4')?.id - : null, - allModules.some( - moduleOnDeck => - moduleOnDeck.slot === '3' && - MODULES_WITH_COLLISION_ISSUES.includes(moduleOnDeck.model) - ) - ? deckDef.locations.addressableAreas.find(s => s.id === '6')?.id - : null, - ]) - : [] - - return ( - <> - {/* all modules */} - {allModules.map(moduleOnDeck => { - const slotId = - moduleOnDeck.slot === SPAN7_8_10_11_SLOT ? '7' : moduleOnDeck.slot - - const slotPosition = getPositionFromSlotId(slotId, deckDef) - if (slotPosition == null) { - console.warn(`no slot ${slotId} for module ${moduleOnDeck.id}`) - return null - } - const moduleDef = getModuleDef2(moduleOnDeck.model) - - const getModuleInnerProps = ( - moduleState: ModuleTemporalProperties['moduleState'] - ): React.ComponentProps['innerProps'] => { - if (moduleState.type === THERMOCYCLER_MODULE_TYPE) { - let lidMotorState = 'unknown' - if (moduleState.lidOpen === true) { - lidMotorState = 'open' - } else if (moduleState.lidOpen === false) { - lidMotorState = 'closed' - } - return { - lidMotorState, - blockTargetTemp: moduleState.blockTargetTemp, - } - } else if ( - 'targetTemperature' in moduleState && - moduleState.type === 'temperatureModuleType' - ) { - return { - targetTemperature: moduleState.targetTemperature, - } - } else if ('targetTemp' in moduleState) { - return { - targetTemp: moduleState.targetTemp, - } - } - } - const labwareLoadedOnModule = allLabware.find( - lw => lw.slot === moduleOnDeck.id - ) - const shouldHideChildren = - moduleOnDeck.moduleState.type === THERMOCYCLER_MODULE_TYPE && - moduleOnDeck.moduleState.lidOpen === false - const labwareInterfaceBoundingBox = { - xDimension: moduleDef.dimensions.labwareInterfaceXDimension ?? 0, - yDimension: moduleDef.dimensions.labwareInterfaceYDimension ?? 0, - zDimension: 0, - } - - const moduleOrientation = inferModuleOrientationFromSlot( - moduleOnDeck.slot - ) - - const isAdapter = labwareLoadedOnModule?.def.allowedRoles?.includes( - 'adapter' - ) - return ( - - {labwareLoadedOnModule != null && !shouldHideChildren ? ( - <> - - {isAdapter ? ( - - ) : ( - - )} - - ) : null} - - {labwareLoadedOnModule == null && - !shouldHideChildren && - !isAdapter ? ( - - ) : null} - {robotType === FLEX_ROBOT_TYPE ? ( - - ) : ( - - )} - - ) - })} - - {/* on-deck warnings */} - {multichannelWarningSlotIds.map(slotId => { - const slotPosition = getPositionFromSlotId(slotId, deckDef) - const slotBoundingBox = getAddressableAreaFromSlotId(slotId, deckDef) - ?.boundingBox - return slotPosition != null && slotBoundingBox != null ? ( - - ) : null - })} - - {/* SlotControls for all empty deck */} - {deckDef.locations.addressableAreas - .filter(addressableArea => { - const stagingAreaAddressableAreas = getStagingAreaAddressableAreas( - stagingAreaCutoutIds - ) - const addressableAreas = - isAddressableAreaStandardSlot(addressableArea.id, deckDef) || - stagingAreaAddressableAreas.includes(addressableArea.id) - return ( - addressableAreas && - !slotIdsBlockedBySpanning.includes(addressableArea.id) && - getSlotIsEmpty(activeDeckSetup, addressableArea.id) && - addressableArea.id !== trashSlot - ) - }) - .map(addressableArea => { - return ( - - ) - })} - - {/* all labware on deck NOT those in modules */} - {allLabware.map(labware => { - if ( - labware.slot === 'offDeck' || - allModules.some(m => m.id === labware.slot) || - allLabware.some(lab => lab.id === labware.slot) - ) - return null - - const slotPosition = getPositionFromSlotId(labware.slot, deckDef) - const slotBoundingBox = getAddressableAreaFromSlotId( - labware.slot, - deckDef - )?.boundingBox - if (slotPosition == null || slotBoundingBox == null) { - console.warn(`no slot ${labware.slot} for labware ${labware.id}!`) - return null - } - const labwareIsAdapter = labware.def.allowedRoles?.includes('adapter') - return ( - - - - {labwareIsAdapter ? ( - - ) : ( - - )} - - - ) - })} - - {/* all adapters on deck and not on module */} - {allLabware.map(labware => { - if ( - allModules.some(m => m.id === labware.slot) || - labware.slot === 'offDeck' - ) - return null - if ( - deckDef.locations.addressableAreas.some( - addressableArea => addressableArea.id === labware.slot - ) - ) { - return null - } - const slotForOnTheDeck = allLabware.find(lab => lab.id === labware.slot) - ?.slot - const slotForOnMod = allModules.find(mod => mod.id === slotForOnTheDeck) - ?.slot - let slotPosition = null - if (slotForOnMod != null) { - slotPosition = getPositionFromSlotId(slotForOnMod, deckDef) - } else if (slotForOnTheDeck != null) { - slotPosition = getPositionFromSlotId(slotForOnTheDeck, deckDef) - } - if (slotPosition == null) { - console.warn(`no slot ${labware.slot} for labware ${labware.id}!`) - return null - } - return ( - - - - - - - ) - })} - - ) -} - -export const LegacyDeckSetup = (): JSX.Element => { - const drilledDown = - useSelector(labwareIngredSelectors.getDrillDownLabwareId) != null - const selectedTerminalItemId = useSelector(getSelectedTerminalItemId) - const activeDeckSetup = useSelector(getDeckSetupForActiveItem) - const _disableCollisionWarnings = useSelector( - featureFlagSelectors.getDisableModuleRestrictions - ) - const trash = Object.values(activeDeckSetup.additionalEquipmentOnDeck).find( - ae => ae.name === 'trashBin' - ) - - const trashSlot = trash?.location - const robotType = useSelector(getRobotType) - const dispatch = useDispatch() - - const _hasGen1MultichannelPipette = React.useMemo( - () => getHasGen1MultiChannelPipette(activeDeckSetup.pipettes), - [activeDeckSetup.pipettes] - ) - const showGen1MultichannelCollisionWarnings = - !_disableCollisionWarnings && _hasGen1MultichannelPipette - - const deckDef = React.useMemo(() => getDeckDefFromRobotType(robotType), []) - const wrapperRef: React.RefObject = useOnClickOutside({ - onClickOutside: () => { - if (drilledDown) dispatch(labwareIngredActions.drillUpFromLabware()) - }, - }) - const trashBinFixtures = [ - { - cutoutId: trash?.location as CutoutId, - cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE, - }, - ] - const wasteChuteFixtures = Object.values( - activeDeckSetup.additionalEquipmentOnDeck - ).filter( - aE => - WASTE_CHUTE_CUTOUT.includes(aE.location as CutoutId) && - aE.name === 'wasteChute' - ) - const stagingAreaFixtures: AdditionalEquipmentEntity[] = Object.values( - activeDeckSetup.additionalEquipmentOnDeck - ).filter( - aE => - STAGING_AREA_CUTOUTS.includes(aE.location as CutoutId) && - aE.name === 'stagingArea' - ) - - const wasteChuteStagingAreaFixtures = Object.values( - activeDeckSetup.additionalEquipmentOnDeck - ).filter( - aE => - STAGING_AREA_CUTOUTS.includes(aE.location as CutoutId) && - aE.name === 'stagingArea' && - aE.location === WASTE_CHUTE_CUTOUT && - wasteChuteFixtures.length > 0 - ) - - const hasWasteChute = - wasteChuteFixtures.length > 0 || wasteChuteStagingAreaFixtures.length > 0 - - const filteredAddressableAreas = deckDef.locations.addressableAreas.filter( - aa => isAddressableAreaStandardSlot(aa.id, deckDef) - ) - return ( -
- {drilledDown && } - -
- - {() => ( - <> - {robotType === OT2_ROBOT_TYPE ? ( - - ) : ( - <> - {filteredAddressableAreas.map(addressableArea => { - const cutoutId = getCutoutIdForAddressableArea( - addressableArea.id, - deckDef.cutoutFixtures - ) - return cutoutId != null ? ( - - ) : null - })} - {stagingAreaFixtures.map(fixture => ( - - ))} - {trash != null - ? trashBinFixtures.map(({ cutoutId }) => - cutoutId != null ? ( - - - - - ) : null - ) - : null} - {wasteChuteFixtures.map(fixture => ( - - ))} - {wasteChuteStagingAreaFixtures.map(fixture => ( - - ))} - - )} - areas.location as CutoutId - )} - {...{ - deckDef, - - showGen1MultichannelCollisionWarnings, - }} - /> - 0} - hasWasteChute={hasWasteChute} - /> - - )} - -
-
- ) -} - -function getCutoutIdForAddressableArea( - addressableArea: AddressableAreaName, - cutoutFixtures: CutoutFixture[] -): CutoutId | null { - return cutoutFixtures.reduce((acc, cutoutFixture) => { - const [cutoutId] = - Object.entries( - cutoutFixture.providesAddressableAreas - ).find(([_cutoutId, providedAAs]) => - providedAAs.includes(addressableArea) - ) ?? [] - return (cutoutId as CutoutId) ?? acc - }, null) -} diff --git a/protocol-designer/src/components/DeckSetup/utils.ts b/protocol-designer/src/components/DeckSetup/utils.ts deleted file mode 100644 index c21607050a1..00000000000 --- a/protocol-designer/src/components/DeckSetup/utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { GEN_ONE_MULTI_PIPETTES } from '@opentrons/shared-data' -import { - getLabwareIsCompatible, - getLabwareIsCustom, -} from '../../utils/labwareModuleCompatibility' -import type { InitialDeckSetup, LabwareOnDeck } from '../../step-forms' -import type { ModuleType } from '@opentrons/shared-data' -import type { LabwareDefByDefURI } from '../../labware-defs' - -export interface SwapBlockedArgs { - hoveredLabware?: LabwareOnDeck | null - draggedLabware?: LabwareOnDeck | null - modulesById: InitialDeckSetup['modules'] - customLabwareDefs: LabwareDefByDefURI -} - -export const getSwapBlocked = (args: SwapBlockedArgs): boolean => { - const { - hoveredLabware, - draggedLabware, - modulesById, - customLabwareDefs, - } = args - if (!hoveredLabware || !draggedLabware) { - return false - } - - const sourceModuleType: ModuleType | null = - modulesById[draggedLabware.slot]?.type || null - const destModuleType: ModuleType | null = - modulesById[hoveredLabware.slot]?.type || null - - const draggedLabwareIsCustom = getLabwareIsCustom( - customLabwareDefs, - draggedLabware - ) - const hoveredLabwareIsCustom = getLabwareIsCustom( - customLabwareDefs, - hoveredLabware - ) - - // dragging custom labware to module gives not compat error - const labwareSourceToDestBlocked = sourceModuleType - ? !getLabwareIsCompatible(hoveredLabware.def, sourceModuleType) && - !hoveredLabwareIsCustom - : false - const labwareDestToSourceBlocked = destModuleType - ? !getLabwareIsCompatible(draggedLabware.def, destModuleType) && - !draggedLabwareIsCustom - : false - - return labwareSourceToDestBlocked || labwareDestToSourceBlocked -} - -export const getHasGen1MultiChannelPipette = ( - pipettes: InitialDeckSetup['pipettes'] -): boolean => { - const pipetteIds = Object.keys(pipettes) - return pipetteIds.some(pipetteId => - GEN_ONE_MULTI_PIPETTES.includes(pipettes[pipetteId]?.name) - ) -} diff --git a/protocol-designer/src/components/DeckSetupManager.tsx b/protocol-designer/src/components/DeckSetupManager.tsx deleted file mode 100644 index bbe87287b41..00000000000 --- a/protocol-designer/src/components/DeckSetupManager.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useSelector } from 'react-redux' -import { - getBatchEditSelectedStepTypes, - getHoveredItem, -} from '../ui/steps/selectors' -import { LegacyDeckSetup } from './DeckSetup' -import { NullDeckState } from './DeckSetup/NullDeckState' -import { OffDeckLabwareButton } from './OffDeckLabwareButton' - -export const DeckSetupManager = (): JSX.Element => { - const batchEditSelectedStepTypes = useSelector(getBatchEditSelectedStepTypes) - const hoveredItem = useSelector(getHoveredItem) - - if (batchEditSelectedStepTypes.length === 0 || hoveredItem !== null) { - // not batch edit mode, or batch edit while item is hovered: show the deck - return ( - <> - - - - ) - } else { - return - } -} diff --git a/protocol-designer/src/components/EditModules.tsx b/protocol-designer/src/components/EditModules.tsx deleted file mode 100644 index 31b392c29a5..00000000000 --- a/protocol-designer/src/components/EditModules.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { useState } from 'react' -import { useSelector, useDispatch } from 'react-redux' -import { - FLEX_ROBOT_TYPE, - HEATERSHAKER_MODULE_TYPE, - MAGNETIC_BLOCK_TYPE, - TEMPERATURE_MODULE_TYPE, -} from '@opentrons/shared-data' -import { - selectors as stepFormSelectors, - actions as stepFormActions, -} from '../step-forms' -import { moveDeckItem } from '../labware-ingred/actions/actions' -import { getRobotType } from '../file-data/selectors' -import { EditMultipleModulesModal } from './modals/EditModulesModal/EditMultipleModulesModal' -import { useBlockingHint } from './Hints/useBlockingHint' -import { MagneticModuleWarningModalContent } from './modals/EditModulesModal/MagneticModuleWarningModalContent' -import { EditModulesModal } from './modals/EditModulesModal' -import type { ModuleModel, ModuleType } from '@opentrons/shared-data' - -export interface EditModulesProps { - moduleToEdit: { - moduleId?: string | null - moduleType: ModuleType - } - onCloseClick: () => void -} - -export interface ModelModuleInfo { - model: ModuleModel - slot: string -} - -export const EditModules = (props: EditModulesProps): JSX.Element => { - const { onCloseClick, moduleToEdit } = props - const { moduleId, moduleType } = moduleToEdit - const _initialDeckSetup = useSelector(stepFormSelectors.getInitialDeckSetup) - const robotType = useSelector(getRobotType) - - const MOAM_MODULE_TYPES: ModuleType[] = [ - TEMPERATURE_MODULE_TYPE, - HEATERSHAKER_MODULE_TYPE, - MAGNETIC_BLOCK_TYPE, - ] - - const showMultipleModuleModal = - robotType === FLEX_ROBOT_TYPE && MOAM_MODULE_TYPES.includes(moduleType) - - const moduleOnDeck = moduleId ? _initialDeckSetup.modules[moduleId] : null - const [ - changeModuleWarningInfo, - displayModuleWarning, - ] = useState(null) - const dispatch = useDispatch() - - const editModuleModel = (selectedModel: ModuleModel): void => { - if (moduleOnDeck?.id != null) { - dispatch( - stepFormActions.editModule({ - id: moduleOnDeck.id, - model: selectedModel, - }) - ) - } else { - console.error( - `cannot edit module model without module id. This shouldn't be able to happen` - ) - } - } - const editModuleSlot = (selectedSlot: string): void => { - if (selectedSlot && moduleOnDeck && moduleOnDeck.slot !== selectedSlot) { - dispatch(moveDeckItem(moduleOnDeck.slot, selectedSlot)) - } - } - - const changeModuleWarning = useBlockingHint({ - hintKey: 'change_magnet_module_model', - handleCancel: () => { - displayModuleWarning(null) - }, - handleContinue: () => { - if (changeModuleWarningInfo) { - editModuleModel(changeModuleWarningInfo.model) - editModuleSlot(changeModuleWarningInfo.slot) - } else { - console.error('no module info set, could not edit module') - } - displayModuleWarning(null) - onCloseClick() - }, - content: , - enabled: changeModuleWarningInfo !== null, - }) - - let modal = ( - - ) - if (showMultipleModuleModal) { - modal = ( - - ) - } - return changeModuleWarning ?? modal -} diff --git a/protocol-designer/src/components/EditableTextField.tsx b/protocol-designer/src/components/EditableTextField.tsx deleted file mode 100644 index 1b5adcb56be..00000000000 --- a/protocol-designer/src/components/EditableTextField.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// TODO: Ian 2018-10-30 if we like this, add it to components library -import * as React from 'react' -import { ClickOutside, Icon, LegacyInputField } from '@opentrons/components' -import styles from './editableTextField.module.css' - -interface Props { - className?: string - value?: string | null - saveEdit: (newValue: string) => unknown -} - -export function EditableTextField(props: Props): JSX.Element { - const { className, value, saveEdit } = props - const [editing, setEditing] = React.useState(false) - const [transientValue, setTransientValue] = React.useState< - string | null | undefined - >(value) - - const enterEditMode = (): void => { - setEditing(true) - setTransientValue(value) - } - const handleCancel = (): void => { - setEditing(false) - setTransientValue(value) - } - - const handleKeyUp = (e: React.KeyboardEvent): void => { - if (e.key === 'Escape') { - handleCancel() - } - } - - const handleSubmit = (): void => { - setEditing(false) - saveEdit(transientValue ?? '') - } - const handleFormSubmit = (e: React.FormEvent): void => { - e.preventDefault() // avoid 'form is not connected' warning - handleSubmit() - } - - const updateValue = (e: React.ChangeEvent): void => { - setTransientValue(e.currentTarget.value) - } - if (editing) { - return ( - - {({ ref }) => ( -
- } - /> - - )} -
- ) - } - - return ( -
-
{value}
- -
- ) -} diff --git a/protocol-designer/src/components/FilePage.module.css b/protocol-designer/src/components/FilePage.module.css deleted file mode 100644 index 787f00b4866..00000000000 --- a/protocol-designer/src/components/FilePage.module.css +++ /dev/null @@ -1,51 +0,0 @@ -@import '@opentrons/components/styles'; - -.file_page { - position: relative; - height: 100%; - min-height: 100vh; -} - -.card_content { - padding: 1rem; -} - -.file_page h1 { - font-size: var(--fs-header); /* from legacy --font-header-dark */ - font-weight: var(--fw-semibold); /* from legacy --font-header-dark */ - color: var(--c-font-dark); /* from legacy --font-header-dark */ -} - -.file_page > * { - margin: 1rem 2.5rem; -} - -.pipette_button_row { - display: flex; - justify-content: center; -} - -.swap_button { - display: block; - margin: 0 0.5rem; - width: 8rem; - - &:focus { - background-color: transparent; - } -} - -.edit_button { - display: block; - margin: 0 0.5rem; - width: 8rem; -} - -.update_button { - width: 8rem; - align-self: flex-start; -} - -.continue_button { - width: 14rem; -} diff --git a/protocol-designer/src/components/FilePage.tsx b/protocol-designer/src/components/FilePage.tsx deleted file mode 100644 index 2167ece1071..00000000000 --- a/protocol-designer/src/components/FilePage.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { useState, useEffect } from 'react' -import { createPortal } from 'react-dom' -import { Controller, useForm } from 'react-hook-form' -import { useTranslation } from 'react-i18next' -import { useSelector, useDispatch } from 'react-redux' -import mapValues from 'lodash/mapValues' -import { format } from 'date-fns' -import cx from 'classnames' - -import { - Card, - FormGroup, - InstrumentGroup, - OutlineButton, - DeprecatedPrimaryButton, - LegacyInputField, -} from '@opentrons/components' -import { resetScrollElements } from '../ui/steps/utils' -import { EditModulesCard } from './modules' -import { EditModules } from './EditModules' - -import styles from './FilePage.module.css' -import modalStyles from '../components/modals/modal.module.css' -import formStyles from '../components/forms/forms.module.css' -import { actions, selectors as fileSelectors } from '../file-data' -import { actions as navActions } from '../navigation' -import { actions as steplistActions } from '../steplist' -import { selectors as stepFormSelectors } from '../step-forms' -import { INITIAL_DECK_SETUP_STEP_ID } from '../constants' -import { FilePipettesModal } from './modals/FilePipettesModal' -import { getTopPortalEl } from './portals/TopPortal' - -import type { ModuleType } from '@opentrons/shared-data' -import type { FileMetadataFields } from '../file-data' - -// TODO(mc, 2020-02-28): explore l10n for these dates -const DATE_ONLY_FORMAT = 'MMM dd, yyyy' -const DATETIME_FORMAT = 'MMM dd, yyyy | h:mm a' -export const FilePage = (): JSX.Element => { - const { t } = useTranslation(['button', 'application']) - const dispatch = useDispatch() - - const formValues = useSelector(fileSelectors.getFileMetadata) - const instruments = useSelector( - stepFormSelectors.getPipettesForInstrumentGroup - ) - const modules = useSelector(stepFormSelectors.getModulesForEditModulesCard) - const initialDeckSetup = useSelector(stepFormSelectors.getInitialDeckSetup) - const [isEditPipetteModalOpen, setEditPipetteModalOpen] = useState( - false - ) - const [moduleToEdit, setModuleToEdit] = useState<{ - moduleType: ModuleType - moduleId?: string | null - } | null>(null) - - const swapPipetteUpdate = mapValues(initialDeckSetup.pipettes, pipette => { - if (!pipette.mount) return pipette.mount - return pipette.mount === 'left' ? 'right' : 'left' - }) - - const openEditPipetteModal = (): void => { - resetScrollElements() - setEditPipetteModalOpen(true) - } - - const closeEditPipetteModal = (): void => { - setEditPipetteModalOpen(false) - } - const handleEditModule = ( - moduleType: ModuleType, - moduleId?: string | null - ): void => { - resetScrollElements() - setModuleToEdit({ moduleType: moduleType, moduleId: moduleId }) - } - - const closeEditModulesModal = (): void => { - setModuleToEdit(null) - } - - const saveFileMetadata = (nextFormValues: FileMetadataFields): void => { - dispatch(actions.saveFileMetadata(nextFormValues)) - setManualDirty(false) - } - const [isManualDirty, setManualDirty] = useState(false) - const { - handleSubmit, - watch, - control, - setValue, - formState: { isDirty }, - } = useForm({ defaultValues: formValues }) - // to ensure that values from watch are up to date if the defaultValues - // change - useEffect(() => { - setValue('protocolName', formValues.protocolName) - setValue('created', formValues.created) - setValue('lastModified', formValues.lastModified) - setValue('author', formValues.author) - setValue('description', formValues.description) - }, [ - formValues.protocolName, - formValues.created, - formValues.lastModified, - formValues.author, - formValues.description, - ]) - - const [created, lastModified, protocolName, author, description] = watch([ - 'created', - 'lastModified', - 'protocolName', - 'author', - 'description', - ]) - return ( -
- -
-
- - {created && format(created, DATE_ONLY_FORMAT)} - - - - {lastModified && format(lastModified, DATETIME_FORMAT)} - -
- -
- - ( - { - setManualDirty(true) - }} - /> - )} - /> - - - - ( - { - setManualDirty(true) - }} - /> - )} - /> - -
- - - ( - { - setManualDirty(true) - }} - /> - )} - /> - -
- - {isManualDirty - ? t('application:update') - : t('application:updated')} - -
-
-
- - -
- -
- - {t('edit')} - - - dispatch( - steplistActions.changeSavedStepForm({ - stepId: INITIAL_DECK_SETUP_STEP_ID, - update: { - pipetteLocationUpdate: swapPipetteUpdate, - }, - }) - ) - } - className={styles.swap_button} - iconName="swap-horizontal" - name="swapPipettes" - disabled={instruments?.left?.pipetteSpecs?.channels === 96} - > - {t('swap')} - -
-
-
- - - -
- dispatch(navActions.navigateToPage('liquids'))} - className={styles.continue_button} - iconName="arrow-right" - name="continueToLiquids" - > - {t('continue_to_liquids')} - -
- {createPortal( - <> - {isEditPipetteModalOpen && ( - - )} - {moduleToEdit != null && ( - - )} - , - getTopPortalEl() - )} -
- ) -} diff --git a/protocol-designer/src/components/FileSidebar/FileSidebar.module.css b/protocol-designer/src/components/FileSidebar/FileSidebar.module.css deleted file mode 100644 index b94d2658d01..00000000000 --- a/protocol-designer/src/components/FileSidebar/FileSidebar.module.css +++ /dev/null @@ -1,16 +0,0 @@ -@import '@opentrons/components/styles'; - -.file_sidebar { - padding: 2rem 4.5rem 0; -} - -.upload_button, -.button { - margin: 0.75rem 0; - width: 100%; -} - -.upload_button input { - position: fixed; - clip: rect(1px 1px 1px 1px); -} diff --git a/protocol-designer/src/components/FileSidebar/FileSidebar.tsx b/protocol-designer/src/components/FileSidebar/FileSidebar.tsx deleted file mode 100644 index 52e312b37e0..00000000000 --- a/protocol-designer/src/components/FileSidebar/FileSidebar.tsx +++ /dev/null @@ -1,475 +0,0 @@ -import * as React from 'react' -import cx from 'classnames' -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { - DeprecatedPrimaryButton, - AlertModal, - OutlineButton, - SidePanel, -} from '@opentrons/components' -import { - actions as loadFileActions, - selectors as loadFileSelectors, -} from '../../load-file' -import { actions, selectors } from '../../navigation' -import { selectors as fileDataSelectors } from '../../file-data' -import { selectors as stepFormSelectors } from '../../step-forms' -import { getRobotType } from '../../file-data/selectors' -import { getAdditionalEquipment } from '../../step-forms/selectors' -import { resetScrollElements } from '../../ui/steps/utils' -import { getMainPagePortalEl } from '../portals/MainPageModalPortal' -import { useBlockingHint } from '../Hints/useBlockingHint' -import { KnowledgeBaseLink } from '../KnowledgeBaseLink' -import { - getUnusedEntities, - getUnusedTrash, - getUnusedStagingAreas, -} from './utils' -import modalStyles from '../modals/modal.module.css' -import styles from './FileSidebar.module.css' - -import type { - CreateCommand, - ProtocolFile, - RobotType, -} from '@opentrons/shared-data' -import type { HintKey } from '../../tutorial' -import type { - InitialDeckSetup, - SavedStepFormState, - ModuleOnDeck, - PipetteOnDeck, -} from '../../step-forms' -import type { ThunkDispatch } from '../../types' -import { createPortal } from 'react-dom' - -export interface AdditionalEquipment { - [additionalEquipmentId: string]: { - name: 'gripper' | 'wasteChute' | 'stagingArea' | 'trashBin' - id: string - location?: string - } -} - -export interface Props { - loadFile: (event: React.ChangeEvent) => unknown - createNewFile?: () => unknown - canDownload: boolean - onDownload: () => unknown - fileData?: ProtocolFile | null - pipettesOnDeck: InitialDeckSetup['pipettes'] - modulesOnDeck: InitialDeckSetup['modules'] - savedStepForms: SavedStepFormState - robotType: RobotType - additionalEquipment: AdditionalEquipment -} - -interface WarningContent { - content: React.ReactNode - heading: string -} - -interface Fixture { - trashBin: boolean - wasteChute: boolean - stagingAreaSlots: string[] -} -interface MissingContent { - noCommands: boolean - pipettesWithoutStep: PipetteOnDeck[] - modulesWithoutStep: ModuleOnDeck[] - gripperWithoutStep: boolean - fixtureWithoutStep: Fixture - t: any -} - -const LOAD_COMMANDS: Array = [ - 'loadLabware', - 'loadModule', - 'loadPipette', - 'loadLiquid', -] - -function getWarningContent({ - noCommands, - pipettesWithoutStep, - modulesWithoutStep, - gripperWithoutStep, - fixtureWithoutStep, - t, -}: MissingContent): WarningContent | null { - if (noCommands) { - return { - content: ( - <> -

{t('export_warnings.no_commands.body1')}

-

- {t('export_warnings.no_commands.body2')} - here. -

- - ), - heading: t('export_warnings.no_commands.heading'), - } - } - - if (gripperWithoutStep) { - return { - content: ( - <> -

{t('export_warnings.unused_gripper.body1')}

-

{t('export_warnings.unused_gripper.body2')}

- - ), - heading: t('export_warnings.unused_gripper.heading'), - } - } - - const pipettesDetails = pipettesWithoutStep - .map(pipette => - pipette.spec.channels === 96 - ? `${pipette.spec.displayName} pipette` - : `${pipette.mount} ${pipette.spec.displayName} pipette` - ) - .join(' and ') - - const unusedModuleCounts = modulesWithoutStep.reduce<{ - [key: string]: number - }>((acc, mod) => { - if (!(mod.type in acc)) { - return { ...acc, [mod.type]: 1 } - } else { - return { ...acc, [mod.type]: acc[mod.type] + 1 } - } - }, {}) - - const modulesDetails = Object.keys(unusedModuleCounts) - // sorting by module count - .sort((k1, k2) => { - if (unusedModuleCounts[k1] < unusedModuleCounts[k2]) { - return 1 - } else if (unusedModuleCounts[k1] > unusedModuleCounts[k2]) { - return -1 - } else { - return 0 - } - }) - .map(modType => - unusedModuleCounts[modType] === 1 - ? t(`modules:module_long_names.${modType}`) - : `${t(`modules:module_long_names.${modType}`)}s` - ) - // transform list of modules with counts to string - .reduce((acc, modName, index, arr) => { - if (arr.length > 2) { - if (index === arr.length - 1) { - return `${acc} and ${modName}` - } else { - return `${acc}${modName}, ` - } - } else if (arr.length === 2) { - return index === 0 ? `${modName} and ` : `${acc}${modName}` - } else { - return modName - } - }, '') - - if (pipettesWithoutStep.length && modulesWithoutStep.length) { - return { - content: ( - <> -

- {t('export_warnings.unused_pipette_and_module.body1', { - modulesDetails, - pipettesDetails, - })} -

-

{t('export_warnings.unused_pipette_and_module.body2')}

- - ), - heading: t('export_warnings.unused_pipette_and_module.heading'), - } - } - - if (pipettesWithoutStep.length) { - return { - content: ( - <> -

- {t('export_warnings.unused_pipette.body1', { - pipettesDetails, - })} -

-

{t('export_warnings.unused_pipette.body2')}

- - ), - heading: t('export_warnings.unused_pipette.heading'), - } - } - - if (modulesWithoutStep.length) { - const moduleCase = - modulesWithoutStep.length > 1 ? 'unused_modules' : 'unused_module' - const slotName = modulesWithoutStep.map(module => module.slot) - return { - content: ( - <> -

- {t(`export_warnings.${moduleCase}.body1`, { - modulesDetails, - slotName: slotName, - })} -

-

{t(`export_warnings.${moduleCase}.body2`)}

- - ), - heading: t(`export_warnings.${moduleCase}.heading`), - } - } - - if (fixtureWithoutStep.trashBin || fixtureWithoutStep.wasteChute) { - return { - content: - (fixtureWithoutStep.trashBin && !fixtureWithoutStep.wasteChute) || - (!fixtureWithoutStep.trashBin && fixtureWithoutStep.wasteChute) ? ( -

- {t('export_warnings.unused_trash.body', { - name: fixtureWithoutStep.trashBin ? 'trash bin' : 'waste chute', - })} -

- ) : ( -

- {t('export_warnings.unused_trash.body_both', { - trashName: 'trash bin', - wasteName: 'waste chute', - })} -

- ), - heading: t('export_warnings.unused_trash.heading'), - } - } - - if (fixtureWithoutStep.stagingAreaSlots.length) { - return { - content: ( - <> -

- {t('export_warnings.unused_staging_area.body1', { - count: fixtureWithoutStep.stagingAreaSlots.length, - slot: fixtureWithoutStep.stagingAreaSlots, - })} -

-

- {t('export_warnings.unused_staging_area.body2', { - count: fixtureWithoutStep.stagingAreaSlots.length, - })} -

- - ), - heading: t('export_warnings.unused_staging_area.heading'), - } - } - - return null -} - -export function v8WarningContent(t: any): JSX.Element { - return ( -
-

- {t(`alert:hint.export_v8_1_protocol_7_3.body1`)}{' '} - {t(`alert:hint.export_v8_1_protocol_7_3.body2`)} - {t(`alert:hint.export_v8_1_protocol_7_3.body3`)} -

-
- ) -} -export function FileSidebar(): JSX.Element { - const fileData = useSelector(fileDataSelectors.createFile) - const currentPage = useSelector(selectors.getCurrentPage) - const canDownload = currentPage !== 'file-splash' - const initialDeckSetup = useSelector(stepFormSelectors.getInitialDeckSetup) - const modulesOnDeck = initialDeckSetup.modules - const pipettesOnDeck = initialDeckSetup.pipettes - const robotType = useSelector(getRobotType) - const additionalEquipment = useSelector(getAdditionalEquipment) - const savedStepForms = useSelector(stepFormSelectors.getSavedStepForms) - const newProtocolModal = useSelector(selectors.getNewProtocolModal) - const hasUnsavedChanges = useSelector(loadFileSelectors.getHasUnsavedChanges) - const canCreateNew = !newProtocolModal - const dispatch: ThunkDispatch = useDispatch() - - const [ - showExportWarningModal, - setShowExportWarningModal, - ] = React.useState(false) - const { t } = useTranslation(['alert', 'modules']) - const isGripperAttached = Object.values(additionalEquipment).some( - equipment => equipment?.name === 'gripper' - ) - const { trashBinUnused, wasteChuteUnused } = getUnusedTrash( - additionalEquipment, - fileData?.commands - ) - - const fixtureWithoutStep: Fixture = { - trashBin: trashBinUnused, - wasteChute: wasteChuteUnused, - stagingAreaSlots: getUnusedStagingAreas( - additionalEquipment, - fileData?.commands - ), - } - const [showBlockingHint, setShowBlockingHint] = React.useState(false) - - const cancelModal = (): void => { - setShowExportWarningModal(false) - } - - const loadFile = ( - fileChangeEvent: React.ChangeEvent - ): void => { - if (!hasUnsavedChanges || window.confirm(t('confirm_import') as string)) { - dispatch(loadFileActions.loadProtocolFile(fileChangeEvent)) - } - } - - const createNewFile = (): void => { - if (canCreateNew) { - dispatch(actions.toggleNewProtocolModal(true)) - } - } - - const nonLoadCommands = - fileData?.commands.filter( - command => !LOAD_COMMANDS.includes(command.commandType) - ) ?? [] - - const gripperInUse = - fileData?.commands.find( - command => - command.commandType === 'moveLabware' && - command.params.strategy === 'usingGripper' - ) != null - - const noCommands = fileData ? nonLoadCommands.length === 0 : true - const pipettesWithoutStep = getUnusedEntities( - pipettesOnDeck, - savedStepForms, - 'pipette', - robotType - ) - const modulesWithoutStep = getUnusedEntities( - modulesOnDeck, - savedStepForms, - 'moduleId', - robotType - ) - const gripperWithoutStep = isGripperAttached && !gripperInUse - - const hasWarning = - noCommands || - modulesWithoutStep.length > 0 || - pipettesWithoutStep.length > 0 || - gripperWithoutStep || - fixtureWithoutStep.trashBin || - fixtureWithoutStep.wasteChute || - fixtureWithoutStep.stagingAreaSlots.length > 0 - - const warning = - hasWarning && - getWarningContent({ - noCommands, - pipettesWithoutStep, - modulesWithoutStep, - gripperWithoutStep, - fixtureWithoutStep, - t, - }) - - const getExportHintContent = (): { - hintKey: HintKey - content: React.ReactNode - } => { - return { - hintKey: 'export_v8_1_protocol_7_3', - content: v8WarningContent(t), - } - } - - const { hintKey, content } = getExportHintContent() - - const blockingExportHint = useBlockingHint({ - enabled: showBlockingHint, - hintKey, - content, - handleCancel: () => { - setShowBlockingHint(false) - }, - handleContinue: () => { - setShowBlockingHint(false) - dispatch(loadFileActions.saveProtocolFile()) - }, - }) - return ( - <> - {blockingExportHint} - {showExportWarningModal && - createPortal( - { - setShowExportWarningModal(false) - setShowBlockingHint(true) - }, - }, - ]} - > - {warning && warning.content} - , - getMainPagePortalEl() - )} - -
- - {t('create_new')} - - - - {t('import')} - - - -
- { - if (hasWarning) { - resetScrollElements() - setShowExportWarningModal(true) - } else { - resetScrollElements() - setShowBlockingHint(true) - } - }} - disabled={!canDownload} - > - {t('export')} - -
-
-
- - ) -} diff --git a/protocol-designer/src/components/FileSidebar/__tests__/FileSidebar.test.tsx b/protocol-designer/src/components/FileSidebar/__tests__/FileSidebar.test.tsx deleted file mode 100644 index c8d2a7c8fe9..00000000000 --- a/protocol-designer/src/components/FileSidebar/__tests__/FileSidebar.test.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest' -import { fireEvent, screen } from '@testing-library/react' -import { FLEX_ROBOT_TYPE, fixtureTiprack300ul } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { createFile, getRobotType } from '../../../file-data/selectors' -import { - getCurrentPage, - getNewProtocolModal, -} from '../../../navigation/selectors' -import { i18n } from '../../../assets/localization' -import { - getAdditionalEquipment, - getInitialDeckSetup, - getSavedStepForms, -} from '../../../step-forms/selectors' -import { toggleNewProtocolModal } from '../../../navigation/actions' -import { getHasUnsavedChanges } from '../../../load-file/selectors' -import { useBlockingHint } from '../../Hints/useBlockingHint' -import { getUnusedStagingAreas } from '../utils/getUnusedStagingAreas' -import { getUnusedTrash } from '../utils/getUnusedTrash' -import { FileSidebar } from '../FileSidebar' -import type { LabwareDefinition2 } from '@opentrons/shared-data' - -vi.mock('../../../step-forms/selectors') -vi.mock('../../../load-file/selectors') -vi.mock('../../../navigation/actions') -vi.mock('../../../navigation/selectors') -vi.mock('../../../file-data/selectors') -vi.mock('../../Hints/useBlockingHint') -vi.mock('../utils/getUnusedStagingAreas') -vi.mock('../utils/getUnusedTrash') -const render = () => { - return renderWithProviders(, { i18nInstance: i18n })[0] -} - -describe('FileSidebar', () => { - beforeEach(() => { - vi.mocked(getUnusedStagingAreas).mockReturnValue([]) - vi.mocked(getUnusedTrash).mockReturnValue({ - trashBinUnused: false, - wasteChuteUnused: false, - }) - vi.mocked(getInitialDeckSetup).mockReturnValue({ - modules: {}, - pipettes: {}, - additionalEquipmentOnDeck: {}, - labware: {}, - }) - vi.mocked(getHasUnsavedChanges).mockReturnValue(false) - vi.mocked(getNewProtocolModal).mockReturnValue(false) - vi.mocked(getSavedStepForms).mockReturnValue({}) - vi.mocked(getAdditionalEquipment).mockReturnValue({}) - vi.mocked(getRobotType).mockReturnValue(FLEX_ROBOT_TYPE) - vi.mocked(getCurrentPage).mockReturnValue('settings-app') - vi.mocked(useBlockingHint).mockReturnValue(null) - vi.mocked(createFile).mockReturnValue({ - commands: [ - { - commandType: 'moveToAddressableArea', - params: { - addressableAreaName: 'movableTrashA3', - pipetteId: 'mockId', - offset: { x: 0, y: 0, z: 0 }, - }, - }, - ], - } as any) - }) - afterEach(() => { - vi.resetAllMocks() - }) - it('renders the file sidebar and exports with blocking hint for exporting', () => { - vi.mocked(useBlockingHint).mockReturnValue(
mock blocking hint
) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - expect(vi.mocked(useBlockingHint)).toHaveBeenCalled() - screen.getByText('mock blocking hint') - }) - it('renders the file sidebar and buttons work as expected with no warning upon export', () => { - render() - screen.getByText('Protocol File') - fireEvent.click(screen.getByRole('button', { name: 'Create New' })) - expect(vi.mocked(toggleNewProtocolModal)).toHaveBeenCalled() - screen.getByText('Import') - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - expect(vi.mocked(useBlockingHint)).toHaveBeenCalled() - }) - it('renders the no commands warning', () => { - vi.mocked(createFile).mockReturnValue({ - commands: [], - } as any) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Your protocol has no steps') - }) - it('renders the unused pipette warning', () => { - vi.mocked(getInitialDeckSetup).mockReturnValue({ - modules: {}, - pipettes: { - pipetteId: { - mount: 'left', - name: 'p1000_96', - id: 'pipetteId', - tiprackLabwareDef: [fixtureTiprack300ul as LabwareDefinition2], - tiprackDefURI: ['mockDefUri'], - spec: { - displayName: 'mock display name', - } as any, - }, - }, - additionalEquipmentOnDeck: {}, - labware: {}, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Unused pipette') - }) - it('renders the unused pieptte and module warning', () => { - vi.mocked(getInitialDeckSetup).mockReturnValue({ - modules: { - moduleId: { - slot: 'A1', - moduleState: {} as any, - id: 'moduleId', - type: 'temperatureModuleType', - model: 'temperatureModuleV2', - }, - }, - pipettes: { - pipetteId: { - mount: 'left', - name: 'p1000_96', - id: 'pipetteId', - tiprackLabwareDef: [fixtureTiprack300ul as LabwareDefinition2], - tiprackDefURI: ['mockDefUri'], - spec: { - displayName: 'mock display name', - } as any, - }, - }, - additionalEquipmentOnDeck: {}, - labware: {}, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Unused pipette and module') - }) - it('renders the unused trash warning', () => { - vi.mocked(getUnusedTrash).mockReturnValue({ - trashBinUnused: true, - wasteChuteUnused: false, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Unused trash') - }) - it('renders the unused waste chute warning', () => { - vi.mocked(getUnusedTrash).mockReturnValue({ - trashBinUnused: false, - wasteChuteUnused: true, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Unused trash') - }) - it('renders the unused staging area slot warning', () => { - vi.mocked(getUnusedStagingAreas).mockReturnValue(['D4']) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('One or more staging area slots are unused') - }) - it('renders the unused gripper warning', () => { - vi.mocked(getAdditionalEquipment).mockReturnValue({ - gripperId: { name: 'gripper', id: 'gripperId' }, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Unused gripper') - }) - it('renders the unused module warning', () => { - vi.mocked(getInitialDeckSetup).mockReturnValue({ - modules: { - moduleId: { - slot: 'A1', - moduleState: {} as any, - id: 'moduleId', - type: 'temperatureModuleType', - model: 'temperatureModuleV2', - }, - }, - pipettes: {}, - additionalEquipmentOnDeck: {}, - labware: {}, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Unused module') - screen.getByText( - 'The Temperature module specified in your protocol in Slot A1 is not currently used in any step. In order to run this protocol you will need to power up and connect the module to your robot.' - ) - }) - it('renders the unused modules warning', () => { - vi.mocked(getInitialDeckSetup).mockReturnValue({ - modules: { - moduleId: { - slot: 'A1', - moduleState: {} as any, - id: 'moduleId', - type: 'temperatureModuleType', - model: 'temperatureModuleV2', - }, - moduleId2: { - slot: 'B1', - moduleState: {} as any, - id: 'moduleId2', - type: 'temperatureModuleType', - model: 'temperatureModuleV2', - }, - }, - pipettes: {}, - additionalEquipmentOnDeck: {}, - labware: {}, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText('Unused modules') - screen.getByText( - 'One or more modules specified in your protocol in Slot(s) A1,B1 are not currently used in any step. In order to run this protocol you will need to power up and connect the modules to your robot.' - ) - }) - it('renders the formatted unused pipettes and modules warning sorted by count', () => { - vi.mocked(getInitialDeckSetup).mockReturnValue({ - modules: { - moduleId1: { - slot: 'A1', - moduleState: {} as any, - id: 'moduleId', - type: 'thermocyclerModuleType', - model: 'thermocyclerModuleV2', - }, - moduleId2: { - slot: 'C3', - moduleState: {} as any, - id: 'moduleId1', - type: 'temperatureModuleType', - model: 'temperatureModuleV2', - }, - moduleId3: { - slot: 'D3', - moduleState: {} as any, - id: 'moduleId2', - type: 'temperatureModuleType', - model: 'temperatureModuleV2', - }, - moduleId4: { - slot: 'C1', - moduleState: {} as any, - id: 'moduleId3', - type: 'heaterShakerModuleType', - model: 'heaterShakerModuleV1', - }, - }, - pipettes: { - pipetteId: { - mount: 'left', - name: 'p1000_96', - id: 'pipetteId', - tiprackLabwareDef: [fixtureTiprack300ul as LabwareDefinition2], - tiprackDefURI: ['mockDefUri'], - spec: { - displayName: 'mock display name', - channels: 96, - } as any, - }, - }, - additionalEquipmentOnDeck: {}, - labware: {}, - }) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export' })) - screen.getByText( - 'The mock display name pipette and Temperature modules, Thermocycler module, and Heater-Shaker module in your protocol are not currently used in any step. In order to run this protocol you will need to attach this pipette as well as power up and connect the module to your robot.' - ) - }) -}) diff --git a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedEntities.test.ts b/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedEntities.test.ts deleted file mode 100644 index 3e2897ec27d..00000000000 --- a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedEntities.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - fixtureP10Single, - fixtureP300Single, -} from '@opentrons/shared-data/pipette/fixtures/name' -import { fixture_tiprack_10_ul } from '@opentrons/shared-data/labware/fixtures/2' -import { - MAGNETIC_MODULE_TYPE, - TEMPERATURE_MODULE_TYPE, - MAGNETIC_MODULE_V1, - TEMPERATURE_MODULE_V1, - MAGNETIC_BLOCK_TYPE, - MAGNETIC_BLOCK_V1, -} from '@opentrons/shared-data' -import { TEMPERATURE_DEACTIVATED } from '@opentrons/step-generation' -import { getUnusedEntities } from '../getUnusedEntities' -import type { SavedStepFormState } from '../../../../step-forms' - -describe('getUnusedEntities', () => { - it('pipette entities not used in steps are returned', () => { - const stepForms: SavedStepFormState = { - step123: { - pipette: 'pipette123', - id: 'step123', - stepType: 'moveLiquid', - }, - } - const pipettesOnDeck = { - pipette123: { - name: 'string', - id: 'pipette123', - tiprackDefURI: 'test', - tiprackLabwareDef: fixture_tiprack_10_ul, - spec: fixtureP10Single, - mount: 'right', - }, - pipette456: { - name: 'string', - id: 'pipette456', - tiprackDefURI: 'test', - tiprackLabwareDef: fixture_tiprack_10_ul, - spec: fixtureP300Single, - mount: 'left', - }, - } - - const result = getUnusedEntities( - pipettesOnDeck, - stepForms, - 'pipette', - 'OT-2 Standard' - ) - - expect(result).toEqual([pipettesOnDeck.pipette456]) - }) - - it('module entities not used in steps are returned', () => { - const stepForms: SavedStepFormState = { - step123: { - moduleId: 'magnet123', - id: 'step123', - magnetAction: 'engage', - engageHeight: '10', - stepType: 'magnet', - stepName: 'magnet', - stepDetails: '', - }, - } - const modulesOnDeck = { - magnet123: { - id: 'magnet123', - type: MAGNETIC_MODULE_TYPE, - model: MAGNETIC_MODULE_V1, - slot: '3', - moduleState: { - type: MAGNETIC_MODULE_TYPE, - engaged: false, - }, - }, - temperature456: { - id: 'temperature456', - type: TEMPERATURE_MODULE_TYPE, - model: TEMPERATURE_MODULE_V1, - moduleState: { - type: TEMPERATURE_MODULE_TYPE, - status: TEMPERATURE_DEACTIVATED, - targetTemperature: null, - }, - slot: '9', - }, - } - - const result = getUnusedEntities( - modulesOnDeck, - stepForms, - 'moduleId', - 'OT-2 Standard' - ) - - expect(result).toEqual([modulesOnDeck.temperature456]) - }) - - it('filters out magnetic block and shows module entities not used in steps are returned for Flex', () => { - const stepForms: SavedStepFormState = { - step123: { - moduleId: 'magnet123', - id: 'step123', - magnetAction: 'engage', - engageHeight: '10', - stepType: 'magnet', - stepName: 'magnet', - stepDetails: '', - }, - } - const modulesOnDeck = { - magnet123: { - id: 'magnet123', - type: MAGNETIC_BLOCK_TYPE, - model: MAGNETIC_BLOCK_V1, - slot: '3', - moduleState: { - type: MAGNETIC_BLOCK_TYPE, - engaged: false, - }, - }, - temperature456: { - id: 'temperature456', - type: TEMPERATURE_MODULE_TYPE, - model: TEMPERATURE_MODULE_V1, - moduleState: { - type: TEMPERATURE_MODULE_TYPE, - status: TEMPERATURE_DEACTIVATED, - targetTemperature: null, - }, - slot: '9', - }, - } - - const result = getUnusedEntities( - modulesOnDeck, - stepForms, - 'moduleId', - 'OT-3 Standard' - ) - - expect(result).toEqual([modulesOnDeck.temperature456]) - }) -}) diff --git a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedStagingAreas.test.ts b/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedStagingAreas.test.ts deleted file mode 100644 index 55160505383..00000000000 --- a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedStagingAreas.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { getUnusedStagingAreas } from '../getUnusedStagingAreas' -import type { CreateCommand } from '@opentrons/shared-data' -import type { AdditionalEquipment } from '../../FileSidebar' - -describe('getUnusedStagingAreas', () => { - it('returns true for unused staging area', () => { - const stagingArea = 'stagingAreaId' - const mockAdditionalEquipment = { - [stagingArea]: { - name: 'stagingArea', - id: stagingArea, - location: 'cutoutA3', - }, - } as AdditionalEquipment - - expect(getUnusedStagingAreas(mockAdditionalEquipment, [])).toEqual(['A4']) - }) - it('returns true for multi unused staging areas', () => { - const stagingArea = 'stagingAreaId' - const stagingArea2 = 'stagingAreaId2' - const mockAdditionalEquipment = { - [stagingArea]: { - name: 'stagingArea', - id: stagingArea, - location: 'cutoutA3', - }, - [stagingArea2]: { - name: 'stagingArea', - id: stagingArea2, - location: 'cutoutB3', - }, - } as AdditionalEquipment - - expect(getUnusedStagingAreas(mockAdditionalEquipment, [])).toEqual([ - 'A4', - 'B4', - ]) - }) - it('returns false for unused staging area', () => { - const stagingArea = 'stagingAreaId' - const mockAdditionalEquipment = { - [stagingArea]: { - name: 'stagingArea', - id: stagingArea, - location: 'cutoutA3', - }, - } as AdditionalEquipment - const mockCommand = [ - { - commandType: 'loadLabware', - params: { location: { addressableAreaName: 'A4' } }, - }, - ] as CreateCommand[] - - expect(getUnusedStagingAreas(mockAdditionalEquipment, mockCommand)).toEqual( - [] - ) - }) -}) diff --git a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedTrash.test.ts b/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedTrash.test.ts deleted file mode 100644 index 658b9d2d7a4..00000000000 --- a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedTrash.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { getUnusedTrash } from '../getUnusedTrash' -import { - EIGHT_CHANNEL_WASTE_CHUTE_ADDRESSABLE_AREA, - ONE_CHANNEL_WASTE_CHUTE_ADDRESSABLE_AREA, -} from '@opentrons/shared-data' -import type { CreateCommand } from '@opentrons/shared-data' -import type { AdditionalEquipment } from '../../FileSidebar' - -describe('getUnusedTrash', () => { - it('returns true for unused trash bin', () => { - const mockTrashId = 'mockTrashId' - const mockTrash = { - [mockTrashId]: { - name: 'trashBin', - id: mockTrashId, - location: 'cutoutA3', - }, - } as AdditionalEquipment - - expect(getUnusedTrash(mockTrash, [])).toEqual({ - trashBinUnused: true, - wasteChuteUnused: false, - }) - }) - it('returns false for unused trash bin', () => { - const mockTrashId = 'mockTrashId' - const mockTrash = { - [mockTrashId]: { - name: 'trashBin', - id: mockTrashId, - location: 'cutoutA3', - }, - } as AdditionalEquipment - const mockCommand = [ - { - commandType: 'moveToAddressableArea', - params: { addressableAreaName: 'movableTrashA3' }, - }, - ] as CreateCommand[] - - expect(getUnusedTrash(mockTrash, mockCommand)).toEqual({ - trashBinUnused: false, - wasteChuteUnused: false, - }) - }) - it('returns true for unused waste chute', () => { - const wasteChute = 'wasteChuteId' - const mockAdditionalEquipment = { - [wasteChute]: { - name: 'wasteChute', - id: wasteChute, - location: 'cutoutD3', - }, - } as AdditionalEquipment - expect(getUnusedTrash(mockAdditionalEquipment, [])).toEqual({ - trashBinUnused: false, - wasteChuteUnused: true, - }) - }) - it('returns false for unused waste chute with single channel', () => { - const wasteChute = 'wasteChuteId' - const mockAdditionalEquipment = { - [wasteChute]: { - name: 'wasteChute', - id: wasteChute, - location: 'cutoutD3', - }, - } as AdditionalEquipment - const mockCommand = [ - { - commandType: 'moveToAddressableArea', - params: { - pipetteId: 'mockId', - addressableAreaName: ONE_CHANNEL_WASTE_CHUTE_ADDRESSABLE_AREA, - }, - }, - ] as CreateCommand[] - expect(getUnusedTrash(mockAdditionalEquipment, mockCommand)).toEqual({ - trashBinUnused: false, - wasteChuteUnused: false, - }) - }) - it('returns false for unused waste chute with 8-channel', () => { - const wasteChute = 'wasteChuteId' - const mockAdditionalEquipment = { - [wasteChute]: { - name: 'wasteChute', - id: wasteChute, - location: 'cutoutD3', - }, - } as AdditionalEquipment - const mockCommand = [ - { - commandType: 'moveToAddressableArea', - params: { - pipetteId: 'mockId', - addressableAreaName: EIGHT_CHANNEL_WASTE_CHUTE_ADDRESSABLE_AREA, - }, - }, - ] as CreateCommand[] - expect(getUnusedTrash(mockAdditionalEquipment, mockCommand)).toEqual({ - trashBinUnused: false, - wasteChuteUnused: false, - }) - }) - it('returns false for unused trash bin with moveToAddressableAreaForDropTip command', () => { - const mockTrashId = 'mockTrashId' - const mockTrash = { - [mockTrashId]: { - name: 'trashBin', - id: mockTrashId, - location: 'cutoutA3', - }, - } as AdditionalEquipment - const mockCommand = [ - { - commandType: 'moveToAddressableAreaForDropTip', - params: { addressableAreaName: 'movableTrashA3', pipetteId: 'mockPip' }, - }, - ] as CreateCommand[] - - expect(getUnusedTrash(mockTrash, mockCommand)).toEqual({ - trashBinUnused: false, - wasteChuteUnused: false, - }) - }) -}) diff --git a/protocol-designer/src/components/FileSidebar/utils/getUnusedEntities.ts b/protocol-designer/src/components/FileSidebar/utils/getUnusedEntities.ts deleted file mode 100644 index 12e34c04e75..00000000000 --- a/protocol-designer/src/components/FileSidebar/utils/getUnusedEntities.ts +++ /dev/null @@ -1,36 +0,0 @@ -import some from 'lodash/some' -import reduce from 'lodash/reduce' -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import type { RobotType } from '@opentrons/shared-data' -import type { SavedStepFormState } from '../../../step-forms' - -/** Pull out all entities never specified by step forms. Assumes that all forms share the entityKey */ -export function getUnusedEntities( - entities: Record, - stepForms: SavedStepFormState, - entityKey: 'pipette' | 'moduleId', - robotType: RobotType -): T[] { - const unusedEntities = reduce( - entities, - (acc, entity: T, entityId): T[] => { - const stepContainsEntity = some( - stepForms, - form => form[entityKey] === entityId - ) - - if ( - robotType === FLEX_ROBOT_TYPE && - entityKey === 'moduleId' && - (entity as any).type === 'magneticBlockType' - ) { - return acc - } - - return stepContainsEntity ? acc : [...acc, entity] - }, - [] as T[] - ) - - return unusedEntities -} diff --git a/protocol-designer/src/components/FileSidebar/utils/getUnusedStagingAreas.ts b/protocol-designer/src/components/FileSidebar/utils/getUnusedStagingAreas.ts deleted file mode 100644 index 9c22d52b679..00000000000 --- a/protocol-designer/src/components/FileSidebar/utils/getUnusedStagingAreas.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { getStagingAreaAddressableAreas } from '../../../utils' -import type { CreateCommand, CutoutId } from '@opentrons/shared-data' -import type { AdditionalEquipment } from '../FileSidebar' - -export const getUnusedStagingAreas = ( - additionalEquipment: AdditionalEquipment, - commands?: CreateCommand[] -): string[] => { - const stagingAreaCutoutIds = Object.values(additionalEquipment) - .filter(equipment => equipment?.name === 'stagingArea') - .map(equipment => { - if (equipment.location == null) { - console.error( - `expected to find staging area slot location with id ${equipment.id} but could not.` - ) - } - return equipment.location ?? '' - }) - - const stagingAreaAddressableAreaNames = getStagingAreaAddressableAreas( - // TODO(jr, 11/13/23): fix AdditionalEquipment['location'] from type string to CutoutId - stagingAreaCutoutIds as CutoutId[] - ) - - const stagingAreaCommandSlots: string[] = stagingAreaAddressableAreaNames.filter( - location => - (commands ?? [])?.some( - command => - (command.commandType === 'loadLabware' && - command.params.location !== 'offDeck' && - 'addressableAreaName' in command.params.location && - command.params.location.addressableAreaName === location) || - (command.commandType === 'moveLabware' && - command.params.newLocation !== 'offDeck' && - 'addressableAreaName' in command.params.newLocation && - command.params.newLocation.addressableAreaName === location) - ) - ? null - : location - ) - return stagingAreaCommandSlots -} diff --git a/protocol-designer/src/components/FileSidebar/utils/getUnusedTrash.ts b/protocol-designer/src/components/FileSidebar/utils/getUnusedTrash.ts deleted file mode 100644 index 5c97305c16b..00000000000 --- a/protocol-designer/src/components/FileSidebar/utils/getUnusedTrash.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - FIXED_TRASH_ID, - MOVABLE_TRASH_ADDRESSABLE_AREAS, - WASTE_CHUTE_ADDRESSABLE_AREAS, -} from '@opentrons/shared-data' -import type { AddressableAreaName, CreateCommand } from '@opentrons/shared-data' -import type { InitialDeckSetup } from '../../../step-forms' - -interface UnusedTrash { - trashBinUnused: boolean - wasteChuteUnused: boolean -} - -export const getUnusedTrash = ( - additionalEquipment: InitialDeckSetup['additionalEquipmentOnDeck'], - commands?: CreateCommand[] -): UnusedTrash => { - const trashBin = Object.values(additionalEquipment).find( - aE => aE.name === 'trashBin' - ) - - const hasTrashBinCommands = - trashBin != null - ? commands?.some( - command => - (command.commandType === 'moveToAddressableArea' && - (MOVABLE_TRASH_ADDRESSABLE_AREAS.includes( - command.params.addressableAreaName as AddressableAreaName - ) || - command.params.addressableAreaName === FIXED_TRASH_ID)) || - command.commandType === 'moveToAddressableAreaForDropTip' - ) - : null - const wasteChute = Object.values(additionalEquipment).find( - aE => aE.name === 'wasteChute' - ) - const hasWasteChuteCommands = - wasteChute != null - ? commands?.some( - command => - (command.commandType === 'moveToAddressableArea' && - WASTE_CHUTE_ADDRESSABLE_AREAS.includes( - command.params.addressableAreaName as AddressableAreaName - )) || - (command.commandType === 'moveLabware' && - command.params.newLocation !== 'offDeck' && - 'addressableAreaName' in command.params.newLocation && - command.params.newLocation.addressableAreaName === - 'gripperWasteChute') - ) - : null - return { - trashBinUnused: trashBin != null && !hasTrashBinCommands, - wasteChuteUnused: wasteChute != null && !hasWasteChuteCommands, - } -} diff --git a/protocol-designer/src/components/FileSidebar/utils/index.ts b/protocol-designer/src/components/FileSidebar/utils/index.ts deleted file mode 100644 index 69cca896b00..00000000000 --- a/protocol-designer/src/components/FileSidebar/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './getUnusedEntities' -export * from './getUnusedStagingAreas' -export * from './getUnusedTrash' diff --git a/protocol-designer/src/components/FormManager/index.tsx b/protocol-designer/src/components/FormManager/index.tsx deleted file mode 100644 index e0241100d94..00000000000 --- a/protocol-designer/src/components/FormManager/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useSelector } from 'react-redux' -import { Box, POSITION_STICKY, C_SELECTED_DARK } from '@opentrons/components' -import { StepEditForm } from '../StepEditForm' -import { BatchEditForm } from '../BatchEditForm' -import { StepSelectionBanner } from '../StepSelectionBanner' -import { getIsMultiSelectMode } from '../../ui/steps/selectors' - -export const FormManager = (): JSX.Element => { - const isMultiSelectMode = useSelector(getIsMultiSelectMode) - - if (isMultiSelectMode) { - return ( - - - - - ) - } - return -} diff --git a/protocol-designer/src/components/Hints/hints.module.css b/protocol-designer/src/components/Hints/hints.module.css deleted file mode 100644 index ebd360b9131..00000000000 --- a/protocol-designer/src/components/Hints/hints.module.css +++ /dev/null @@ -1,71 +0,0 @@ -@import '@opentrons/components/styles'; - -.dont_show_again { - float: left; -} - -.ok_button { - float: right; -} - -.summary { - padding-bottom: 2rem; -} - -.numbered_list { - margin: 0 2rem 2rem 2rem; - - & > li::before { - margin-right: 1rem; - } - - & li span { - position: relative; - left: 1rem; - } -} - -.step_description { - margin-bottom: 1rem; - - & span:first-of-type { - font-weight: var(--fw-semibold); - } -} - -.hint_contents { - font-size: var(--fs-body-2); /* from legacy --font-body-2-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-2-dark */ - color: var(--c-font-dark); /* from legacy --font-body-2-dark */ - line-height: var(--lh-copy); - - & p { - margin-bottom: 1rem; - } -} - -.heading { - font-size: var(--fs-header); /* from legacy --font-header-dark */ - font-weight: var(--fw-semibold); /* from legacy --font-header-dark */ - color: var(--c-font-dark); /* from legacy --font-header-dark */ - margin-bottom: 1rem; -} - -.column_left { - width: 35%; -} - -.column_right { - margin-left: 5%; - width: 60%; -} - -.column_left, -.column_right { - display: inline-block; - vertical-align: top; - - & > * { - width: 100%; - } -} diff --git a/protocol-designer/src/components/Hints/index.tsx b/protocol-designer/src/components/Hints/index.tsx deleted file mode 100644 index cbc40cb0a97..00000000000 --- a/protocol-designer/src/components/Hints/index.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { useState, useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import { createPortal } from 'react-dom' -import { useSelector, useDispatch } from 'react-redux' -import { - AlertModal, - DeprecatedCheckboxField, - Flex, - OutlineButton, - Text, -} from '@opentrons/components' -import { actions, selectors } from '../../tutorial' -import { getMainPagePortalEl } from '../portals/MainPageModalPortal' -import styles from './hints.module.css' -import EXAMPLE_ADD_LIQUIDS_IMAGE from '../../assets/images/example_add_liquids.png' -import EXAMPLE_WATCH_LIQUIDS_MOVE_IMAGE from '../../assets/images/example_watch_liquids_move.png' -import EXAMPLE_BATCH_EDIT_IMAGE from '../../assets/images/announcements/multi_select.gif' -import type { HintKey } from '../../tutorial' - -const HINT_IS_ALERT: HintKey[] = ['add_liquids_and_labware'] - -export const Hints = (): JSX.Element | null => { - const { t } = useTranslation(['alert', 'nav', 'button']) - const [rememberDismissal, setRememberDismissal] = useState(false) - - const toggleRememberDismissal = useCallback(() => { - setRememberDismissal(prevDismissal => !prevDismissal) - }, []) - const hintKey = useSelector(selectors.getHint) - const dispatch = useDispatch() - const removeHint = (hintKey: HintKey): void => { - dispatch(actions.removeHint(hintKey, rememberDismissal)) - } - - const makeHandleCloseClick = (hintKey: HintKey): (() => void) => { - return () => { - removeHint(hintKey) - } - } - - const renderHintContents = (hintKey: HintKey): JSX.Element | null => { - // Only hints that have no outside effects should go here. - // For hints that have an effect, use BlockingHint. - switch (hintKey) { - case 'add_liquids_and_labware': - return ( - <> -
- {t('hint.add_liquids_and_labware.summary', { - deck_setup_step: t('nav:terminal_item.__initial_setup__'), - })} -
- - -
- Step 1: - {t('hint.add_liquids_and_labware.step1')} -
- -
- - -
- Step 2: - {t('hint.add_liquids_and_labware.step2')} -
- -
- - ) - case 'deck_setup_explanation': - return ( - <> -

{t(`hint.${hintKey}.body1`)}

-

{t(`hint.${hintKey}.body2`)}

-

{t(`hint.${hintKey}.body3`)}

- - ) - case 'multiple_modules_without_labware': - case 'module_without_labware': - return ( - <> -

{t(`alert:hint.${hintKey}.body`)}

- - ) - - case 'thermocycler_lid_passive_cooling': - return ( - <> -

- {t(`alert:hint.${hintKey}.body1a`)} - {t(`alert:hint.${hintKey}.strong_body1`)} - {t(`alert:hint.${hintKey}.body1b`)} -

-
    -
  1. - {t(`alert:hint.${hintKey}.li1`)} -
  2. -
  3. - {t(`alert:hint.${hintKey}.li2`)} -
  4. -
- - ) - case 'protocol_can_enter_batch_edit': - return ( - <> - - - - -

{t(`alert:hint.${hintKey}.body1`)}

-

- {`alert:hint.${hintKey}.body2`} -

    -
  1. - {t(`alert:hint.${hintKey}.li1a`)} - {t(`alert:hint.${hintKey}.strong_li1`)} - {t(`alert:hint.${hintKey}.li1b`)} -
  2. -
  3. - {t(`alert:hint.${hintKey}.li2a`)} - {t(`alert:hint.${hintKey}.strong_li2`)} - {t(`alert:hint.${hintKey}.li2b`)} -
  4. -
-

-

- {t(`alert:hint.${hintKey}.body3a`)}
- {t(`alert:hint.${hintKey}.body3b`)} -

-

- {t(`alert:hint.${hintKey}.body4a`)}
- {t(`alert:hint.${hintKey}.body4b`)} -

-
- - ) - case 'waste_chute_warning': - return ( - - {t(`hint.${hintKey}.body1`)} - - ) - default: - return null - } - } - - if (hintKey == null) return null - - const headingText = t(`hint.${hintKey}.title`) - const hintIsAlert = HINT_IS_ALERT.includes(hintKey) - return createPortal( - - {!hintIsAlert ? ( -
{headingText}
- ) : null} -
{renderHintContents(hintKey)}
-
- - - {t('button:ok')} - -
-
, - getMainPagePortalEl() - ) -} diff --git a/protocol-designer/src/components/Hints/useBlockingHint.tsx b/protocol-designer/src/components/Hints/useBlockingHint.tsx deleted file mode 100644 index 6b1283bd234..00000000000 --- a/protocol-designer/src/components/Hints/useBlockingHint.tsx +++ /dev/null @@ -1,100 +0,0 @@ -// BlockingHint is an "are you sure" modal that can be dismissed. -// Instances of BlockingHint need to be individually placed by whatever component -// is controlling the flow that this modal will block, via useBlockingHint. -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { ContinueModal, DeprecatedCheckboxField } from '@opentrons/components' -import { actions, selectors } from '../../tutorial' -import { getMainPagePortalEl } from '../portals/MainPageModalPortal' -import styles from './hints.module.css' -import type { HintKey } from '../../tutorial' - -export interface HintProps { - hintKey: HintKey - handleCancel: () => void - handleContinue: () => void - content: React.ReactNode -} - -// This component handles the checkbox and dispatching `removeHint` action on continue/cancel -export const BlockingHint = (props: HintProps): JSX.Element => { - const { t } = useTranslation('alert') - const { hintKey, handleCancel, handleContinue } = props - const dispatch = useDispatch() - - const [rememberDismissal, setRememberDismissal] = React.useState( - false - ) - - const toggleRememberDismissal = React.useCallback(() => { - setRememberDismissal(prevDismissal => !prevDismissal) - }, []) - - const onCancelClick = (): void => { - dispatch(actions.removeHint(hintKey, rememberDismissal)) - handleCancel() - } - - const onContinueClick = (): void => { - dispatch(actions.removeHint(hintKey, rememberDismissal)) - handleContinue() - } - - return createPortal( - -
{props.content}
-
- -
-
, - getMainPagePortalEl() - ) -} - -export interface HintArgs { - /** `enabled` should be a condition that the parent uses to toggle whether the hint should be active or not. - * If the hint is enabled but has been dismissed, it will automatically call `handleContinue` when enabled. - * useBlockingHint expects the parent to disable the hint on cancel/continue */ - enabled: boolean - hintKey: HintKey - content: React.ReactNode - handleCancel: () => void - handleContinue: () => void -} - -export const useBlockingHint = (args: HintArgs): JSX.Element | null => { - const { enabled, hintKey, handleCancel, handleContinue, content } = args - const isDismissed = useSelector(selectors.getDismissedHints).includes(hintKey) - - if (isDismissed) { - if (enabled) { - handleContinue() - } - return null - } - - if (!enabled) { - return null - } - - return ( - - ) -} diff --git a/protocol-designer/src/components/IngredientsList/IngredientsList.module.css b/protocol-designer/src/components/IngredientsList/IngredientsList.module.css deleted file mode 100644 index d6e76be8236..00000000000 --- a/protocol-designer/src/components/IngredientsList/IngredientsList.module.css +++ /dev/null @@ -1,16 +0,0 @@ -@import '@opentrons/components/styles'; - -.close_icon { - & > svg { - width: 100%; - max-height: 1rem; - } - - &:hover { - color: var(--c-red); - } -} - -.ingredient_row_header { - font-weight: var(--fw-bold); -} diff --git a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.tsx b/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.tsx deleted file mode 100644 index 80afd8f9624..00000000000 --- a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/LabwareDetailsCard.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import cx from 'classnames' -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { selectors as stepFormSelectors } from '../../../step-forms' -import { selectors as uiLabwareSelectors } from '../../../ui/labware' -import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' -import * as labwareIngredActions from '../../../labware-ingred/actions' -import { PDTitledList, PDListItem } from '../../lists' -import { EditableTextField } from '../../EditableTextField' -import styles from './labwareDetailsCard.module.css' -import type { ThunkDispatch } from '../../../types' - -export function LabwareDetailsCard(): JSX.Element { - const { t } = useTranslation('form') - const dispatch = useDispatch>() - const labwareNicknamesById = useSelector( - uiLabwareSelectors.getLabwareNicknamesById - ) - const labwareId = useSelector(labwareIngredSelectors.getSelectedLabwareId) - const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) - const labwareDefDisplayName = - labwareId != null - ? getLabwareDisplayName(labwareEntities[labwareId].def) - : null - - console.assert( - labwareId, - 'Expected labware id to exist in connected labware details card' - ) - - const renameLabware = (name: string): void => { - console.assert( - labwareId, - 'renameLabware in LabwareDetailsCard expected a labwareId' - ) - - if (labwareId) { - dispatch( - labwareIngredActions.renameLabware({ - labwareId: labwareId, - name, - }) - ) - } - } - - return ( - - -
- - {t('generic.labware_type')} - - {labwareDefDisplayName} -
-
- -
- - {t('generic.nickname')} - - -
-
-
- ) -} diff --git a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/labwareDetailsCard.module.css b/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/labwareDetailsCard.module.css deleted file mode 100644 index 76d446c0767..00000000000 --- a/protocol-designer/src/components/IngredientsList/LabwareDetailsCard/labwareDetailsCard.module.css +++ /dev/null @@ -1,22 +0,0 @@ -@import '@opentrons/components/styles'; - -.column_1_3 { - lost-column: 1/3; -} - -.column_2_3 { - lost-column: 2/3; - margin-bottom: 0.2rem; - overflow: hidden; - display: inline-block; - text-overflow: ellipsis; -} - -.row { - lost-utility: clearfix; - text-align: left; -} - -.label { - font-weight: var(--fw-bold); -} diff --git a/protocol-designer/src/components/IngredientsList/index.tsx b/protocol-designer/src/components/IngredientsList/index.tsx deleted file mode 100644 index 125b81e6a10..00000000000 --- a/protocol-designer/src/components/IngredientsList/index.tsx +++ /dev/null @@ -1,210 +0,0 @@ -// TODO: Ian 2018-10-09 figure out what belongs in LiquidsSidebar vs IngredientsList after #2427 -import { useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { IconButton, SidePanel, truncateString } from '@opentrons/components' -import { sortWells } from '@opentrons/shared-data' -import * as wellSelectionSelectors from '../../top-selectors/well-contents' -import { removeWellsContents } from '../../labware-ingred/actions' -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' -import { PDTitledList, PDListItem } from '../lists' -import { TitledListNotes } from '../TitledListNotes' -import { swatchColors } from '../swatchColors' -import { LabwareDetailsCard } from './LabwareDetailsCard/LabwareDetailsCard' -import styles from './IngredientsList.module.css' - -import type { SingleLabwareLiquidState } from '@opentrons/step-generation' -import type { LiquidGroup } from '../../labware-ingred/types' -import type { ThunkDispatch } from '../../types' - -type RemoveWellsContents = (args: { - liquidGroupId: string - wells: string[] -}) => unknown - -// Props used by both IngredientsList and LiquidGroupCard -export interface CommonProps { - removeWellsContents: RemoveWellsContents - selected?: boolean -} - -type LiquidGroupCardProps = CommonProps & { - groupId: string - ingredGroup: LiquidGroup - labwareWellContents: SingleLabwareLiquidState -} - -const LiquidGroupCard = (props: LiquidGroupCardProps): JSX.Element | null => { - const { - ingredGroup, - removeWellsContents, - selected, - groupId, - labwareWellContents, - } = props - const { t } = useTranslation(['card', 'application']) - const showName = ingredGroup.serialize - - const [expanded, setExpanded] = useState(true) - - const toggleAccordion = (): void => { - setExpanded(!expanded) - } - - const wellsWithIngred = Object.keys(labwareWellContents) - .sort(sortWells) - .filter(well => labwareWellContents[well][groupId]) - const liquidDisplayColors = useSelector( - labwareIngredSelectors.getLiquidDisplayColors - ) - - if (wellsWithIngred.length < 1) { - // do not show liquid card if it has no instances for this labware - return null - } - const truncatedName = - ingredGroup.name != null ? truncateString(ingredGroup.name, 25) : null - return ( - } - > - - {t('well')} - {t('application:units.microliter')} - {showName && {t('name')}} - - - - {wellsWithIngred.map((well, i) => { - const wellIngredForCard = labwareWellContents[well][groupId] - const volume = - wellIngredForCard != null ? wellIngredForCard.volume : null - - if (volume == null) { - console.warn( - `Got null-ish volume for well: ${well}, ingred: ${groupId}` - ) - return null - } - - return ( - - ) - })} - - ) -} - -interface IndividProps { - name?: string | null - wellName: string - volume: number - // concentration?: string, - canDelete: boolean - groupId: string - removeWellsContents: RemoveWellsContents -} - -function IngredIndividual(props: IndividProps): JSX.Element { - const { - name, - wellName, - volume, - // concentration, // TODO LATER Ian 2018-02-22: concentration is removed from MVP. Remove all traces of it, or add it back in - canDelete, - groupId, - removeWellsContents, - } = props - const { t } = useTranslation('application') - return ( - - {wellName} - - {Boolean(volume) ? volume + ` ${t('units.microliter')}` : '-'} - - {name != null ? {name} : null} - {canDelete && ( - { - if ( - window.confirm( - t('are_you_sure_delete_well', { well: wellName }) as string - ) - ) - removeWellsContents({ liquidGroupId: groupId, wells: [wellName] }) - }} - /> - )} - - ) -} - -export function IngredientsList(): JSX.Element { - const selectedLabwareId = useSelector( - labwareIngredSelectors.getSelectedLabwareId - ) - const allLabwareWellContents = useSelector( - labwareIngredSelectors.getLiquidsByLabwareId - ) - - const liquidGroupsById = useSelector( - labwareIngredSelectors.getLiquidGroupsById - ) - const selectedIngredientGroupId = useSelector( - wellSelectionSelectors.getSelectedWellsCommonIngredId - ) - const { t } = useTranslation('nav') - const dispatch = useDispatch>() - - const labwareWellContents = - (selectedLabwareId != null - ? allLabwareWellContents[selectedLabwareId] - : null) ?? {} - - return ( - - - - {Object.keys(liquidGroupsById).map(groupIdForCard => ( - { - if (selectedLabwareId != null) { - dispatch( - removeWellsContents({ - labwareId: selectedLabwareId, - liquidGroupId, - wells, - }) - ) - } - }} - labwareWellContents={labwareWellContents} - ingredGroup={liquidGroupsById[groupIdForCard]} - groupId={groupIdForCard} - selected={selectedIngredientGroupId === groupIdForCard} - /> - ))} - - ) -} diff --git a/protocol-designer/src/components/KnowledgeBaseLink/index.tsx b/protocol-designer/src/components/KnowledgeBaseLink/index.tsx deleted file mode 100644 index a21f8a0fc6d..00000000000 --- a/protocol-designer/src/components/KnowledgeBaseLink/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type * as React from 'react' -import { Link } from '@opentrons/components' - -export const KNOWLEDGEBASE_ROOT_URL = - 'https://support.opentrons.com/s/protocol-designer' - -export const links = { - airGap: `https://support.opentrons.com/en/articles/4398106-air-gap`, - multiDispense: `https://support.opentrons.com/en/articles/4170341-paths`, - protocolSteps: `https://support.opentrons.com/s/protocol-designer?tabset-92ba3=2`, - customLabware: `https://support.opentrons.com/en/articles/3136504-creating-custom-labware-definitions`, - recommendedLabware: - 'https://support.opentrons.com/s/article/What-labware-can-I-use-with-my-modules', - pipetteGen1MultiModuleCollision: - 'https://support.opentrons.com/en/articles/4168741-module-placement', - betaReleases: `https://support.opentrons.com/en/articles/3854833-opentrons-beta-software-releases`, - magneticModuleGenerations: - 'http://support.opentrons.com/en/articles/1820112-magnetic-module', -} as const - -interface Props { - to: keyof typeof links - children: React.ReactNode - className?: string -} - -/** Link which opens a page on the knowledge base to a new tab/window */ -export function KnowledgeBaseLink(props: Props): JSX.Element { - return ( - - {props.children} - - ) -} diff --git a/protocol-designer/src/components/LabwareSelectionModal/LabwareItem.tsx b/protocol-designer/src/components/LabwareSelectionModal/LabwareItem.tsx deleted file mode 100644 index 33ba3393765..00000000000 --- a/protocol-designer/src/components/LabwareSelectionModal/LabwareItem.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useTranslation } from 'react-i18next' -import cx from 'classnames' -import { Icon } from '@opentrons/components' -import { PDListItem } from '../lists' -import styles from './styles.module.css' -import { - getLabwareDefURI, - getLabwareDefIsStandard, - getLabwareDisplayName, -} from '@opentrons/shared-data' -import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { IconName } from '@opentrons/components' - -interface Props { - disabled?: boolean | null - icon?: IconName | null - labwareDef: LabwareDefinition2 - onMouseEnter: () => any - onMouseLeave: () => any - selectLabware: (labwareLoadName: string) => unknown -} - -const LABWARE_LIBRARY_PAGE_PATH = 'https://labware.opentrons.com' - -export function LabwareItem(props: Props): JSX.Element { - const { - disabled, - icon, - labwareDef, - onMouseLeave, - onMouseEnter, - selectLabware, - } = props - const { t } = useTranslation('modal') - const displayName = getLabwareDisplayName(labwareDef) - const labwareURI = getLabwareDefURI(labwareDef) - const labwareLoadName = labwareDef.parameters.loadName - - return ( - { - if (!disabled) { - selectLabware(labwareURI) - } - }} - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - > - {icon ? : null} -
- {displayName} -
- {getLabwareDefIsStandard(labwareDef) ? ( - { - e.stopPropagation() - }} - > - {t('labware_selection.view_measurements')} - - ) : null} -
- ) -} diff --git a/protocol-designer/src/components/LabwareSelectionModal/LabwarePreview.tsx b/protocol-designer/src/components/LabwareSelectionModal/LabwarePreview.tsx deleted file mode 100644 index 5d82ec73191..00000000000 --- a/protocol-designer/src/components/LabwareSelectionModal/LabwarePreview.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import reduce from 'lodash/reduce' -import { useTranslation } from 'react-i18next' -import { - Icon, - LabwareRender, - LabeledValue, - RobotWorkSpace, -} from '@opentrons/components' -import { - getLabwareDisplayName, - getLabwareDefIsStandard, -} from '@opentrons/shared-data' -import styles from './styles.module.css' -import type { LabwareDefinition2 } from '@opentrons/shared-data' - -interface Props { - labwareDef?: LabwareDefinition2 | null - moduleCompatibility?: - | 'recommended' - | 'potentiallyCompatible' - | 'notCompatible' - | null -} - -export const LabwarePreview = (props: Props): JSX.Element | null => { - const { labwareDef, moduleCompatibility } = props - const { t } = useTranslation(['modal', 'application']) - if (!labwareDef) return null - const maxVolumes = reduce( - labwareDef.wells, - (acc, well) => acc.add(well.totalLiquidVolume), - new Set() - ) - const formattedVolumes = Array.from(maxVolumes) - .map(vol => `${vol}${t('application:units.microliter')}`) - .join(', ') - - // NOTE: this is a temporary magic value that positions the preview component - // in a fixed place relative to the labware dropdown, while still letting - // it overflow the sidebar nav if necessary - const leftValue = (global.innerWidth - 365) / 2 - 260 - - return ( -
-
-

- {props.labwareDef ? getLabwareDisplayName(props.labwareDef) : ''} -

- {moduleCompatibility != null ? ( -
- {moduleCompatibility === 'recommended' ? ( - - ) : null} - {t(`labware_selection.module_compatibility.${moduleCompatibility}`)} -
- ) : null} -
-
- - {() => } - -
-
- {getLabwareDefIsStandard(labwareDef) && ( - - )} -
- - -
-
-
-
-
- ) -} diff --git a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx b/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx deleted file mode 100644 index 81941119f45..00000000000 --- a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx +++ /dev/null @@ -1,601 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import startCase from 'lodash/startCase' -import reduce from 'lodash/reduce' -import { - useOnClickOutside, - DeprecatedCheckboxField, - Icon, - OutlineButton, -} from '@opentrons/components' -import { - getLabwareDefURI, - getLabwareDefIsStandard, - getIsLabwareAboveHeight, - TEMPERATURE_MODULE_TYPE, - MAGNETIC_MODULE_TYPE, - THERMOCYCLER_MODULE_TYPE, - HEATERSHAKER_MODULE_TYPE, - MAGNETIC_BLOCK_TYPE, - MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM, - getModuleType, - THERMOCYCLER_MODULE_V2, - getAreSlotsHorizontallyAdjacent, - ABSORBANCE_READER_TYPE, -} from '@opentrons/shared-data' -import { - closeLabwareSelector, - createContainer, -} from '../../labware-ingred/actions' -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' -import { - actions as labwareDefActions, - selectors as labwareDefSelectors, -} from '../../labware-defs' -import { selectors as stepFormSelectors } from '../../step-forms' -import { SPAN7_8_10_11_SLOT } from '../../constants' -import { - getLabwareIsCompatible as _getLabwareIsCompatible, - getLabwareCompatibleWithAdapter, - ADAPTER_96_CHANNEL, -} from '../../utils/labwareModuleCompatibility' -import { getPipetteEntities } from '../../step-forms/selectors' -import { getHas96Channel } from '../../utils' -import { getOnlyLatestDefs } from '../../labware-defs/utils' -import { getTopPortalEl } from '../portals/TopPortal' -import { PDTitledList } from '../lists' -import { useBlockingHint } from '../Hints/useBlockingHint' -import { KnowledgeBaseLink } from '../KnowledgeBaseLink' -import { LabwareItem } from './LabwareItem' -import { LabwarePreview } from './LabwarePreview' -import styles from './styles.module.css' - -import type { - LabwareDefinition2, - ModuleType, - ModuleModel, -} from '@opentrons/shared-data' -import type { DeckSlot, ThunkDispatch } from '../../types' -import type { LabwareDefByDefURI } from '../../labware-defs' -import type { ModuleOnDeck } from '../../step-forms' - -export interface Props { - onClose: (e?: any) => unknown - onUploadLabware: (event: React.ChangeEvent) => unknown - selectLabware: (containerType: string) => unknown - customLabwareDefs: LabwareDefByDefURI - /** the slot you're literally adding labware to (may be a module slot) */ - slot?: DeckSlot | null - /** if adding to a module, the slot of the parent (for display) */ - parentSlot?: DeckSlot | null - /** if adding to a module, the module's model */ - moduleModel?: ModuleModel | null - /** tipracks that may be added to deck (depends on pipette<>tiprack assignment) */ - permittedTipracks: string[] - isNextToHeaterShaker: boolean - has96Channel: boolean - adapterLoadName?: string -} - -const LABWARE_CREATOR_URL = 'https://labware.opentrons.com/create' -const CUSTOM_CATEGORY = 'custom' -const adapterCompatibleLabware = 'adapterCompatibleLabware' - -const orderedCategories: string[] = [ - 'tipRack', - 'tubeRack', - 'wellPlate', - 'reservoir', - 'aluminumBlock', - 'adapter', - // 'trash', // NOTE: trash intentionally hidden -] - -const RECOMMENDED_LABWARE_BY_MODULE: { [K in ModuleType]: string[] } = { - [TEMPERATURE_MODULE_TYPE]: [ - 'opentrons_24_aluminumblock_generic_2ml_screwcap', - 'opentrons_96_well_aluminum_block', - 'opentrons_96_aluminumblock_generic_pcr_strip_200ul', - 'opentrons_24_aluminumblock_nest_1.5ml_screwcap', - 'opentrons_24_aluminumblock_nest_1.5ml_snapcap', - 'opentrons_24_aluminumblock_nest_2ml_screwcap', - 'opentrons_24_aluminumblock_nest_2ml_snapcap', - 'opentrons_24_aluminumblock_nest_0.5ml_screwcap', - 'opentrons_aluminum_flat_bottom_plate', - 'opentrons_96_deep_well_temp_mod_adapter', - ], - [MAGNETIC_MODULE_TYPE]: [ - 'nest_96_wellplate_100ul_pcr_full_skirt', - 'nest_96_wellplate_2ml_deep', - 'opentrons_96_wellplate_200ul_pcr_full_skirt', - ], - [THERMOCYCLER_MODULE_TYPE]: [ - 'nest_96_wellplate_100ul_pcr_full_skirt', - 'opentrons_96_wellplate_200ul_pcr_full_skirt', - ], - [HEATERSHAKER_MODULE_TYPE]: [ - 'opentrons_96_deep_well_adapter', - 'opentrons_96_flat_bottom_adapter', - 'opentrons_96_pcr_adapter', - 'opentrons_universal_flat_adapter', - ], - [MAGNETIC_BLOCK_TYPE]: [ - 'nest_96_wellplate_100ul_pcr_full_skirt', - 'nest_96_wellplate_2ml_deep', - 'opentrons_96_wellplate_200ul_pcr_full_skirt', - ], - [ABSORBANCE_READER_TYPE]: [], -} - -export const getLabwareIsRecommended = ( - def: LabwareDefinition2, - moduleModel?: ModuleModel | null -): boolean => { - // special-casing the thermocycler module V2 recommended labware - // since its different from V1 - const moduleType = moduleModel != null ? getModuleType(moduleModel) : null - if (moduleModel === THERMOCYCLER_MODULE_V2) { - return ( - def.parameters.loadName === 'opentrons_96_wellplate_200ul_pcr_full_skirt' - ) - } else { - return moduleType != null - ? RECOMMENDED_LABWARE_BY_MODULE[moduleType].includes( - def.parameters.loadName - ) - : false - } -} -export function LabwareSelectionModal(): JSX.Element | null { - const { t } = useTranslation(['modules', 'modal', 'button', 'alert']) - const dispatch = useDispatch>() - const selectedLabwareSlot = useSelector( - labwareIngredSelectors.selectedAddLabwareSlot - ) - const pipetteEntities = useSelector(getPipetteEntities) - const permittedTipracks = useSelector(stepFormSelectors.getPermittedTipracks) - const customLabwareDefs = useSelector( - labwareDefSelectors.getCustomLabwareDefsByURI - ) - const deckSetup = useSelector(stepFormSelectors.getInitialDeckSetup) - const has96Channel = getHas96Channel(pipetteEntities) - const modulesById = deckSetup.modules - const labwareById = deckSetup.labware - const slot = selectedLabwareSlot === false ? null : selectedLabwareSlot - - const onClose = (): void => { - dispatch(closeLabwareSelector()) - } - const selectLabware = (labwareDefURI: string): void => { - if (slot) { - dispatch( - createContainer({ - slot: slot, - labwareDefURI, - }) - ) - } - } - - const onUploadLabware = ( - fileChangeEvent: React.ChangeEvent - ): void => { - dispatch(labwareDefActions.createCustomLabwareDef(fileChangeEvent)) - } - - const initialModules: ModuleOnDeck[] = Object.keys(modulesById).map( - moduleId => modulesById[moduleId] - ) - const parentModule = - (slot != null && - initialModules.find(moduleOnDeck => moduleOnDeck.id === slot)) || - null - const parentSlot = parentModule != null ? parentModule.slot : null - const moduleModel = parentModule != null ? parentModule.model : null - const isNextToHeaterShaker = initialModules.some( - hardwareModule => - hardwareModule.type === HEATERSHAKER_MODULE_TYPE && - getAreSlotsHorizontallyAdjacent(hardwareModule.slot, parentSlot ?? slot) - ) - const adapterLoadName = Object.values(labwareById) - .filter(labwareOnDeck => slot === labwareOnDeck.id) - .map(labwareOnDeck => labwareOnDeck.def.parameters.loadName)[0] - - const defs = getOnlyLatestDefs() - const moduleType = moduleModel != null ? getModuleType(moduleModel) : null - const URIs = Object.keys(defs) - const [selectedCategory, setSelectedCategory] = React.useState( - null - ) - const [previewedLabware, setPreviewedLabware] = React.useState< - LabwareDefinition2 | null | undefined - >(null) - const [filterRecommended, setFilterRecommended] = React.useState( - false - ) - - const [filterHeight, setFilterHeight] = React.useState(false) - const [enqueuedLabwareType, setEnqueuedLabwareType] = React.useState< - string | null - >(null) - - const blockingCustomLabwareHint = useBlockingHint({ - enabled: enqueuedLabwareType !== null, - hintKey: 'custom_labware_with_modules', - content:

{t(`alert:hint.custom_labware_with_modules.body`)}

, - handleCancel: () => { - setEnqueuedLabwareType(null) - }, - handleContinue: () => { - setEnqueuedLabwareType(null) - if (enqueuedLabwareType !== null) { - // NOTE: this needs to be wrapped for Flow, IRL we know enqueuedLabwareType is not null - // because `enabled` prop above ensures it's !== null. - selectLabware(enqueuedLabwareType) - } else { - console.error( - 'could not select labware because enqueuedLabwareType is null. This should not happen' - ) - } - }, - }) - - const handleSelectCustomLabware = React.useCallback( - (containerType: string) => { - if (moduleType == null) { - selectLabware(containerType) - } else { - // show the BlockingHint - setEnqueuedLabwareType(containerType) - } - }, - [moduleType, selectLabware, setEnqueuedLabwareType] - ) - - // if you're adding labware to a module, check the recommended filter by default - React.useEffect(() => { - setFilterRecommended(moduleType != null) - setFilterHeight(isNextToHeaterShaker) - }, [moduleType, isNextToHeaterShaker]) - - const getLabwareCompatible = React.useCallback( - (def: LabwareDefinition2) => { - // assume that custom (non-standard) labware is (potentially) compatible - if (moduleType == null || !getLabwareDefIsStandard(def)) { - return true - } - return _getLabwareIsCompatible(def, moduleType) - }, - [moduleType] - ) - - const getIsLabwareFiltered = React.useCallback( - (labwareDef: LabwareDefinition2) => { - const { dimensions, parameters } = labwareDef - const { xDimension, yDimension } = dimensions - - const isSmallXDimension = xDimension < 127.75 - const isSmallYDimension = yDimension < 85.48 - const isIrregularSize = isSmallXDimension && isSmallYDimension - - const isAdapter = labwareDef.allowedRoles?.includes('adapter') - const isAdapter96Channel = parameters.loadName === ADAPTER_96_CHANNEL - - return ( - (filterRecommended && - !getLabwareIsRecommended(labwareDef, moduleModel)) || - (filterHeight && - getIsLabwareAboveHeight( - labwareDef, - MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM - )) || - !getLabwareCompatible(labwareDef) || - (isAdapter && - isIrregularSize && - !slot?.includes(HEATERSHAKER_MODULE_TYPE)) || - (isAdapter96Channel && !has96Channel) || - (slot === 'offDeck' && isAdapter) - ) - }, - [filterRecommended, filterHeight, getLabwareCompatible, moduleType, slot] - ) - const getTitleText = (): string => { - if (isNextToHeaterShaker) { - return `Slot ${slot}, Labware to the side of ${t( - `module_long_names.heaterShakerModuleType` - )}` - } - if (adapterLoadName != null) { - const adapterDisplayName = - Object.values(defs).find( - def => def.parameters.loadName === adapterLoadName - )?.metadata.displayName ?? '' - return `Labware on top of the ${adapterDisplayName}` - } - if (parentSlot != null && moduleType != null) { - return `Slot ${parentSlot === SPAN7_8_10_11_SLOT ? '7' : parentSlot}, ${t( - `module_long_names.${moduleType}` - )} Labware` - } - return `Slot ${slot} Labware` - } - - const getLabwareAdapterItem = ( - index: number, - labwareDefUri?: string - ): JSX.Element | null => { - const labwareDef = labwareDefUri != null ? defs[labwareDefUri] : null - return labwareDef != null ? ( - { - setPreviewedLabware(labwareDef) - }} - onMouseLeave={() => { - // @ts-expect-error(sa, 2021-6-22): setPreviewedLabware expects an argument (even if nullsy) - setPreviewedLabware() - }} - /> - ) : null - } - - const customLabwareURIs: string[] = React.useMemo( - () => Object.keys(customLabwareDefs), - [customLabwareDefs] - ) - - const labwareByCategory = React.useMemo(() => { - return reduce< - LabwareDefByDefURI, - { [category: string]: LabwareDefinition2[] } - >( - defs, - (acc, def: typeof defs[keyof typeof defs]) => { - const category: string = def.metadata.displayCategory - // filter out non-permitted tipracks - if ( - category === 'tipRack' && - !permittedTipracks.includes(getLabwareDefURI(def)) - ) { - return acc - } - - return { - ...acc, - [category]: [...(acc[category] || []), def], - } - }, - {} - ) - }, [permittedTipracks]) - - const populatedCategories: { [category: string]: boolean } = React.useMemo( - () => - orderedCategories.reduce( - (acc, category) => - labwareByCategory[category] - ? { - ...acc, - [category]: labwareByCategory[category].some( - def => !getIsLabwareFiltered(def) - ), - } - : acc, - {} - ), - [labwareByCategory, getIsLabwareFiltered] - ) - - const wrapperRef: React.RefObject = useOnClickOutside({ - onClickOutside: () => { - // don't close when clicking on the custom labware hint - if (!enqueuedLabwareType) { - onClose() - } - }, - }) - - // do not render without a slot - if (!slot) return null - - const makeToggleCategory = (category: string) => () => { - setSelectedCategory(selectedCategory === category ? null : category) - } - - const getFilterCheckbox = (): JSX.Element | null => { - if (isNextToHeaterShaker || moduleType != null) { - return ( -
-
Filters
-
- ) => { - isNextToHeaterShaker - ? setFilterHeight(e.currentTarget.checked) - : setFilterRecommended(e.currentTarget.checked) - }} - value={isNextToHeaterShaker ? filterHeight : filterRecommended} - /> - {isNextToHeaterShaker && ( - - )} - - {t( - isNextToHeaterShaker - ? 'modal:labware_selection.heater_shaker_labware_filter' - : 'modal:labware_selection.recommended_labware_filter' - )}{' '} - - here - - . - -
-
- ) - } - return null - } - - let moduleCompatibility: React.ComponentProps< - typeof LabwarePreview - >['moduleCompatibility'] = null - if (previewedLabware && moduleType) { - if (getLabwareIsRecommended(previewedLabware, moduleModel)) { - moduleCompatibility = 'recommended' - } else if (getLabwareCompatible(previewedLabware)) { - moduleCompatibility = 'potentiallyCompatible' - } else { - moduleCompatibility = 'notCompatible' - } - } - - return ( - <> - {createPortal( - , - getTopPortalEl() - )} - {blockingCustomLabwareHint} -
-
{getTitleText()}
- {getFilterCheckbox()} -
    - {customLabwareURIs.length > 0 ? ( - - {customLabwareURIs.map((labwareURI, index) => ( - { - setPreviewedLabware(customLabwareDefs[labwareURI]) - }} - onMouseLeave={() => { - // @ts-expect-error(sa, 2021-6-22): need to pass in a nullsy value - setPreviewedLabware() - }} - /> - ))} - - ) : null} - {adapterLoadName == null ? ( - orderedCategories.map(category => { - const isPopulated = populatedCategories[category] - if (isPopulated) { - return ( - - {labwareByCategory[category]?.map((labwareDef, index) => { - const isFiltered = getIsLabwareFiltered(labwareDef) - if (!isFiltered) { - return ( - { - setPreviewedLabware(labwareDef) - }} - onMouseLeave={() => { - // @ts-expect-error(sa, 2021-6-22): setPreviewedLabware expects an argument (even if nullsy) - setPreviewedLabware() - }} - /> - ) - } - })} - - ) - } - }) - ) : ( - - {has96Channel && adapterLoadName === ADAPTER_96_CHANNEL - ? permittedTipracks.map((tiprackDefUri, index) => { - const labwareDefUri = URIs.find( - defUri => defUri === tiprackDefUri - ) - return getLabwareAdapterItem(index, labwareDefUri) - }) - : getLabwareCompatibleWithAdapter(adapterLoadName).map( - (adapterDefUri, index) => { - const labwareDefUri = URIs.find( - defUri => defUri === adapterDefUri - ) - return getLabwareAdapterItem(index, labwareDefUri) - } - )} - - )} -
- - - {t('button:upload_custom_labware')} - { - onUploadLabware(e) - setSelectedCategory(CUSTOM_CATEGORY) - }} - /> - -
- {t('modal:labware_selection.creating_labware_defs')}{' '} - {/* TODO: Ian 2019-10-15 use LinkOut component once it's in components library, see Opentrons/opentrons#4229 */} - - here - - . -
- - {t('button:close')} -
- - ) -} diff --git a/protocol-designer/src/components/LabwareSelectionModal/__tests__/LabwareSelectionModal.test.tsx b/protocol-designer/src/components/LabwareSelectionModal/__tests__/LabwareSelectionModal.test.tsx deleted file mode 100644 index ab56ace5c46..00000000000 --- a/protocol-designer/src/components/LabwareSelectionModal/__tests__/LabwareSelectionModal.test.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { vi, describe, it, expect, beforeEach } from 'vitest' -import { fireEvent, screen } from '@testing-library/react' -import { - renderWithProviders, - nestedTextMatcher, -} from '../../../__testing-utils__' -import { - getIsLabwareAboveHeight, - MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM, -} from '@opentrons/shared-data' -import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' -import { - ADAPTER_96_CHANNEL, - getLabwareCompatibleWithAdapter, -} from '../../../utils/labwareModuleCompatibility' -import { i18n } from '../../../assets/localization' -import { LabwareSelectionModal } from '../LabwareSelectionModal' -import { - getInitialDeckSetup, - getPermittedTipracks, - getPipetteEntities, -} from '../../../step-forms/selectors' -import { getHas96Channel } from '../../../utils' -import { getCustomLabwareDefsByURI } from '../../../labware-defs/selectors' -import type * as SharedData from '@opentrons/shared-data' - -vi.mock('../../../utils/labwareModuleCompatibility') -vi.mock('../../../step-forms/selectors') -vi.mock('../../../labware-defs/selectors') -vi.mock('../../Hints/useBlockingHint') -vi.mock('../../../utils') -vi.mock('../../../labware-ingred/selectors') -vi.mock('@opentrons/shared-data', async importOriginal => { - const actual = await importOriginal() - return { - ...actual, - getIsLabwareAboveHeight: vi.fn(), - } -}) - -const render = () => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - -const mockTipUri = 'fixture/fixture_tiprack_1000_ul/1' -const mockPermittedTipracks = [mockTipUri] - -describe('LabwareSelectionModal', () => { - beforeEach(() => { - vi.mocked(getLabwareCompatibleWithAdapter).mockReturnValue([]) - vi.mocked(getInitialDeckSetup).mockReturnValue({ - labware: {}, - modules: {}, - pipettes: {}, - additionalEquipmentOnDeck: {}, - }) - vi.mocked(labwareIngredSelectors.selectedAddLabwareSlot).mockReturnValue( - '2' - ) - vi.mocked(getHas96Channel).mockReturnValue(false) - vi.mocked(getPermittedTipracks).mockReturnValue(mockPermittedTipracks) - vi.mocked(getPipetteEntities).mockReturnValue({ - mockPip: { - tiprackLabwareDef: {} as any, - spec: {} as any, - name: 'p1000_single', - id: 'mockId', - tiprackDefURI: [mockTipUri], - }, - }) - vi.mocked(getCustomLabwareDefsByURI).mockReturnValue({}) - }) - it('should NOT filter out labware above 57 mm when the slot is NOT next to a heater shaker', () => { - render() - expect(vi.mocked(getIsLabwareAboveHeight)).not.toHaveBeenCalled() - }) - it('should filter out labware above 57 mm when the slot is next to a heater shaker', () => { - vi.mocked(getInitialDeckSetup).mockReturnValue({ - labware: {}, - modules: { - heaterShaker: { - id: 'mockId', - type: 'heaterShakerModuleType', - model: 'heaterShakerModuleV1', - moduleState: {} as any, - slot: '1', - } as any, - }, - pipettes: {}, - additionalEquipmentOnDeck: {}, - }) - render() - expect(vi.mocked(getIsLabwareAboveHeight)).toHaveBeenCalledWith( - expect.any(Object), - MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM - ) - }) - it.only('should display only permitted tipracks if the 96-channel is attached', () => { - vi.mocked(getHas96Channel).mockReturnValue(true) - vi.mocked(labwareIngredSelectors.selectedAddLabwareSlot).mockReturnValue( - 'adapter' - ) - vi.mocked(getInitialDeckSetup).mockReturnValue({ - labware: { - adapter: { - id: 'adapter', - labwareDefURI: `opentrons/${ADAPTER_96_CHANNEL}/1`, - slot: 'A2', - def: { parameters: { loadName: ADAPTER_96_CHANNEL } } as any, - }, - }, - modules: {}, - pipettes: {}, - additionalEquipmentOnDeck: {}, - }) - render() - fireEvent.click( - screen.getByText(nestedTextMatcher('Adapter Compatible Labware')) - ) - screen.getByText('Opentrons GEB 1000uL Tiprack') - }) -}) diff --git a/protocol-designer/src/components/LabwareSelectionModal/styles.module.css b/protocol-designer/src/components/LabwareSelectionModal/styles.module.css deleted file mode 100644 index 27097e62ee8..00000000000 --- a/protocol-designer/src/components/LabwareSelectionModal/styles.module.css +++ /dev/null @@ -1,181 +0,0 @@ -@import '@opentrons/components/styles'; - -.title { - font-size: var(--fs-header); /* from legacy --font-header-dark */ - font-weight: var(--fw-semibold); /* from legacy --font-header-dark */ - color: var(--c-font-dark); /* from legacy --font-header-dark */ - padding-bottom: 1rem; -} - -.labware_dropdown { - position: absolute; - background-color: var(--c-white); - box-shadow: var(--shadow-lvl-2); - left: 0; - right: 0; - top: 12%; - max-width: 28rem; - margin: auto; - padding: 1rem 2rem; - user-select: none; - z-index: 1; - - & button { - margin-top: 2rem; - float: right; - } -} - -/* ----- */ - -.view_measurements_link, -.labware_name { - padding: 1rem 0.5rem; -} - -.view_measurements_link { - flex: 0.6; - text-transform: uppercase; - text-align: center; - cursor: pointer; - text-decoration: none; - color: inherit; - - &:hover { - background-color: var(--c-bg-hover); - } -} - -.labware_list_item { - display: flex; - padding: 0; - - & .view_measurements_link { - display: none; - } - - &:hover .view_measurements_link { - display: inherit; - } -} - -.labware_preview_wrapper { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - z-index: 10; -} - -.labware_preview { - width: 25.25rem; - height: 12.8125rem; - background-color: var(--c-white); - box-shadow: var(--shadow-lvl-2); - padding: 1rem; - z-index: 100; - display: flex; - justify-content: space-between; - flex-direction: column; -} - -.labware_preview_header { - font-size: var(--fs-header); /* from legacy --font-header-dark */ - font-weight: var(--fw-semibold); /* from legacy --font-header-dark */ - color: var(--c-font-dark); /* from legacy --font-header-dark */ -} - -.labware_preview_module_compat { - font-size: var(--fs-body-2); /* from legacy --font-body-2-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-2-dark */ - color: var(--c-font-dark); /* from legacy --font-body-2-dark */ - display: flex; - align-items: center; -} - -.labware_render_wrapper { - height: 7.125rem; - width: 10.75rem; -} - -.labware_detail_row { - display: flex; - justify-content: space-between; - flex-direction: row; -} - -.labware_detail_column { - display: flex; - justify-content: space-between; - flex-direction: column; - flex: 1; - padding: 0.25rem 1rem; -} - -.labware_name { - text-align: left; -} - -/* TODO: Ian 2019-08-05 copied from FileSidebar.css, consider merging as new component */ -.upload_button { - margin: 0.75rem 0; - width: 100%; -} - -.upload_button input { - position: fixed; - clip: rect(1px 1px 1px 1px); -} - -.upload_helper_copy { - font-size: var(--fs-body-1); /* from legacy --font-body-1-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-1-dark */ - color: var(--c-font-dark); /* from legacy --font-body-1-dark */ -} - -/* TODO: Ian 2019-09-03 similar styles for links exist in multiple projects */ -.link { - color: var(--c-blue); - text-decoration: none; - cursor: pointer; -} - -.labware_item_icon { - flex: 0 1 auto; - margin-left: 0.5rem; -} - -.icon, -.labware_item_icon { - height: 1.25rem; - fill: var(--c-near-black); -} - -.filters_heading { - font-size: var(--fs-body-2); /* from legacy --font-body-2-dark */ - color: var(--c-font-dark); /* from legacy --font-body-2-dark */ - font-weight: var(--fw-semibold); -} - -.filter_checkbox { - display: inline-block; - padding-right: 0.75rem; -} - -.filters_section { - display: flex; - align-items: center; - margin: 0.25rem 0; -} - -.filters_section_copy { - font-size: var(--fs-body-1); /* from legacy --font-body-1-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-1-dark */ - color: var(--c-font-dark); /* from legacy --font-body-1-dark */ - padding-left: 0.15rem; -} - -.disabled { - color: var(--c-font-disabled); -} diff --git a/protocol-designer/src/components/LiquidPlacementForm/LiquidPlacementForm.module.css b/protocol-designer/src/components/LiquidPlacementForm/LiquidPlacementForm.module.css deleted file mode 100644 index 16d370b63e5..00000000000 --- a/protocol-designer/src/components/LiquidPlacementForm/LiquidPlacementForm.module.css +++ /dev/null @@ -1,30 +0,0 @@ -@import '@opentrons/components/styles'; - -/* fields */ - -.field_row { - lost-utility: clearfix; - margin-bottom: 2rem; -} - -.liquid_field { - lost-column: 8/16; -} - -.volume_field { - lost-column: 3/16; -} - -/* buttons */ - -.button_row { - lost-utility: clearfix; - - & > * { - lost-column: 4/16; - } - - & > *:nth-child(2) { - lost-offset: 4/16; - } -} diff --git a/protocol-designer/src/components/LiquidPlacementForm/LiquidPlacementForm.tsx b/protocol-designer/src/components/LiquidPlacementForm/LiquidPlacementForm.tsx deleted file mode 100644 index 1221d479448..00000000000 --- a/protocol-designer/src/components/LiquidPlacementForm/LiquidPlacementForm.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import type * as React from 'react' -import { Controller, useForm } from 'react-hook-form' -import isEmpty from 'lodash/isEmpty' -import { useTranslation } from 'react-i18next' -import { useSelector, useDispatch } from 'react-redux' - -import * as wellContentsSelectors from '../../top-selectors/well-contents' -import * as fieldProcessors from '../../steplist/fieldLevel/processing' -import { - DropdownField, - FormGroup, - OutlineButton, - DeprecatedPrimaryButton, - LegacyInputField, -} from '@opentrons/components' -import styles from './LiquidPlacementForm.module.css' -import formStyles from '../forms/forms.module.css' -import stepEditFormStyles from '../StepEditForm/StepEditForm.module.css' -import { deselectAllWells } from '../../well-selection/actions' -import { - removeWellsContents, - setWellContents, -} from '../../labware-ingred/actions' -import { getSelectedWells } from '../../well-selection/selectors' - -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' - -interface ValidFormValues { - selectedLiquidId: string - volume: string -} -interface LiquidPlacementFormValues { - selectedLiquidId?: string | null - volume?: string | null -} - -export const LiquidPlacementForm = (): JSX.Element | null => { - const { t } = useTranslation(['form', 'button', 'application']) - const selectedWellGroups = useSelector(getSelectedWells) - const selectedWells = Object.keys(selectedWellGroups) - const showForm = !isEmpty(selectedWellGroups) - const dispatch = useDispatch() - const labwareId = useSelector(labwareIngredSelectors.getSelectedLabwareId) - const liquidLocations = useSelector( - labwareIngredSelectors.getLiquidsByLabwareId - ) - const commonSelectedLiquidId = useSelector( - wellContentsSelectors.getSelectedWellsCommonIngredId - ) - const commonSelectedVolume = useSelector( - wellContentsSelectors.getSelectedWellsCommonVolume - ) - const selectedWellsMaxVolume = useSelector( - wellContentsSelectors.getSelectedWellsMaxVolume - ) - const liquidSelectionOptions = useSelector( - labwareIngredSelectors.getLiquidSelectionOptions - ) - - const selectionHasLiquids = Boolean( - labwareId != null && - liquidLocations[labwareId] != null && - Object.keys(selectedWellGroups).some( - well => liquidLocations[labwareId][well] - ) - ) - - const getInitialValues: () => ValidFormValues = () => { - return { - selectedLiquidId: commonSelectedLiquidId ?? '', - volume: - commonSelectedVolume != null ? commonSelectedVolume.toString() : '', - } - } - - const { - handleSubmit, - watch, - control, - setValue, - formState: { errors, touchedFields }, - } = useForm({ - defaultValues: getInitialValues(), - }) - - const selectedLiquidId = watch('selectedLiquidId') - const volume = watch('volume') - - const handleCancelForm = (): void => { - dispatch(deselectAllWells()) - } - - const handleClearWells: () => void = () => { - if (labwareId != null && selectedWells != null && selectionHasLiquids) { - if (global.confirm(t('application:are_you_sure') as string)) { - dispatch( - removeWellsContents({ - labwareId, - wells: selectedWells, - }) - ) - } - } - } - - const handleChangeVolume: ( - e: React.ChangeEvent - ) => void = e => { - const value: string | null | undefined = e.currentTarget.value - const masked = fieldProcessors.composeMaskers( - fieldProcessors.maskToFloat, - fieldProcessors.onlyPositiveNumbers, - fieldProcessors.trimDecimals(1) - )(value) as string - setValue('volume', masked) - } - - const handleSaveForm = (values: LiquidPlacementFormValues): void => { - const volume = Number(values.volume) - const { selectedLiquidId } = values - console.assert( - labwareId != null, - 'when saving liquid placement form, expected a selected labware ID' - ) - console.assert( - selectedWells != null && selectedWells.length > 0, - `when saving liquid placement form, expected selected wells to be array with length > 0 but got ${String( - selectedWells - )}` - ) - console.assert( - selectedLiquidId != null, - `when saving liquid placement form, expected selectedLiquidId to be non-nullsy but got ${String( - selectedLiquidId - )}` - ) - console.assert( - volume > 0, - `when saving liquid placement form, expected volume > 0, got ${volume}` - ) - - if (labwareId != null && selectedLiquidId != null) { - dispatch( - setWellContents({ - liquidGroupId: selectedLiquidId, - labwareId, - wells: selectedWells ?? [], - volume: Number(values.volume), - }) - ) - } - } - - const handleSaveSubmit: ( - values: LiquidPlacementFormValues - ) => void = values => { - handleSaveForm(values) - } - - if (!showForm) return null - - let volumeErrors: string | null = null - if (Boolean(touchedFields.volume)) { - if (volume == null || volume === '0') { - volumeErrors = t('generic.error.more_than_zero') - } else if (parseInt(volume) > selectedWellsMaxVolume) { - volumeErrors = t('liquid_placement.volume_exceeded', { - volume: selectedWellsMaxVolume, - }) - } - } - - return ( -
-
-
- - ( - - )} - /> - - - ( - - )} - /> - -
- -
- - {t('button:clear_wells')} - - - {t('button:cancel')} - - - {t('button:save')} - -
-
-
- ) -} diff --git a/protocol-designer/src/components/LiquidPlacementModal.module.css b/protocol-designer/src/components/LiquidPlacementModal.module.css deleted file mode 100644 index c63a9946758..00000000000 --- a/protocol-designer/src/components/LiquidPlacementModal.module.css +++ /dev/null @@ -1,34 +0,0 @@ -@import '@opentrons/components/styles'; - -.labware { - margin: 2rem auto; - max-width: 50rem; -} - -.liquid_placement_modal { - position: absolute; - - /* from legacy --absolute-fill */ - top: 0; - - /* from legacy --absolute-fill */ - right: 0; - - /* from legacy --absolute-fill */ - bottom: 0; - - /* from legacy --absolute-fill */ - left: 0; - - /* from legacy --absolute-fill */ - - background-color: rgba(0, 0, 0, 0.9); - z-index: 4; - - /* make up lost space for overlay */ - height: 103%; - - &.expanded { - height: 127%; - } -} diff --git a/protocol-designer/src/components/LiquidPlacementModal.tsx b/protocol-designer/src/components/LiquidPlacementModal.tsx deleted file mode 100644 index 307255ae846..00000000000 --- a/protocol-designer/src/components/LiquidPlacementModal.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useState } from 'react' - -import { useDispatch, useSelector } from 'react-redux' -import cx from 'classnames' -import isEmpty from 'lodash/isEmpty' -import { WELL_LABEL_OPTIONS } from '@opentrons/components' - -import { - wellFillFromWellContents, - SelectableLabware, -} from '../components/labware' -import { selectors } from '../labware-ingred/selectors' -import { selectors as stepFormSelectors } from '../step-forms' -import * as wellContentsSelectors from '../top-selectors/well-contents' -import { getSelectedWells } from '../well-selection/selectors' -import { selectWells, deselectWells } from '../well-selection/actions' -import { LiquidPlacementForm } from './LiquidPlacementForm/LiquidPlacementForm' -import { WellSelectionInstructions } from './WellSelectionInstructions' - -import styles from './LiquidPlacementModal.module.css' -import type { WellGroup } from '@opentrons/components' - -export function LiquidPlacementModal(): JSX.Element | null { - const [highlightedWells, setHighlightedWells] = useState({}) - const labwareId = useSelector(selectors.getSelectedLabwareId) - const selectedWells = useSelector(getSelectedWells) - const dispatch = useDispatch() - const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) - const allWellContents = useSelector( - wellContentsSelectors.getWellContentsAllLabware - ) - const liquidNamesById = useSelector(selectors.getLiquidNamesById) - const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) - if (labwareId == null) { - console.assert( - false, - 'LiquidPlacementModal: No labware is selected, and no labwareId was given to LiquidPlacementModal' - ) - return null - } - - const labwareDef = labwareEntities[labwareId]?.def - const wellContents = allWellContents[labwareId] - - return ( -
- - - {labwareDef && ( -
- dispatch(selectWells(wells))} - deselectWells={(wells: WellGroup) => dispatch(deselectWells(wells))} - updateHighlightedWells={(wells: WellGroup) => { - setHighlightedWells(wells) - }} - ingredNames={liquidNamesById} - wellContents={wellContents} - nozzleType={null} - /> -
- )} - - -
- ) -} diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.module.css b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.module.css deleted file mode 100644 index d89e24f154e..00000000000 --- a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.module.css +++ /dev/null @@ -1,26 +0,0 @@ -@import '@opentrons/components/styles'; - -.form_card { - margin: 1rem; - padding: 1rem; -} - -.section { - padding-bottom: 2rem; -} - -.info_text { - padding-bottom: 1.5rem; -} - -.button_row { - lost-utility: clearfix; - - & > * { - lost-column: 1/6; - } - - & > *:nth-child(2) { - lost-offset: 3/6; - } -} diff --git a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx b/protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx deleted file mode 100644 index f7697dc2add..00000000000 --- a/protocol-designer/src/components/LiquidsPage/LiquidEditForm.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import type * as React from 'react' -import { useTranslation } from 'react-i18next' -import { Controller, useForm } from 'react-hook-form' -import { yupResolver } from '@hookform/resolvers/yup' -import { useSelector } from 'react-redux' -import * as Yup from 'yup' -import { - Card, - DeprecatedCheckboxField, - DeprecatedPrimaryButton, - Flex, - FormGroup, - JUSTIFY_END, - LegacyInputField, - OutlineButton, - TYPOGRAPHY, -} from '@opentrons/components' -import { DEPRECATED_WHALE_GREY } from '@opentrons/shared-data' -import { selectors } from '../../labware-ingred/selectors' -import { swatchColors } from '../swatchColors' -import { ColorPicker } from '../ColorPicker' -import styles from './LiquidEditForm.module.css' -import formStyles from '../forms/forms.module.css' - -import type { ColorResult } from 'react-color' -import type { LiquidGroup } from '../../labware-ingred/types' - -interface LiquidEditFormProps { - serialize: boolean - canDelete: boolean - deleteLiquidGroup: () => void - cancelForm: () => void - saveForm: (liquidGroup: LiquidGroup) => void - displayColor?: string - name?: string | null - description?: string | null -} - -interface LiquidEditFormValues { - name: string - displayColor: string - description?: string | null - serialize?: boolean - [key: string]: unknown -} - -function checkColor(hex: string): boolean { - const cleanHex = hex.replace('#', '') - const red = parseInt(cleanHex.slice(0, 2), 16) - const green = parseInt(cleanHex.slice(2, 4), 16) - const blue = parseInt(cleanHex.slice(4, 6), 16) - const luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255 - return luminance < 0.1 || luminance > 0.9 -} - -const INVALID_DISPLAY_COLORS = ['#000000', '#ffffff', DEPRECATED_WHALE_GREY] - -const liquidEditFormSchema: any = Yup.object().shape({ - name: Yup.string().required('liquid name is required'), - displayColor: Yup.string().test( - 'disallowed-color', - 'Invalid display color', - value => { - if (value == null) { - return true - } - return !INVALID_DISPLAY_COLORS.includes(value) - ? !checkColor(value) - : false - } - ), - description: Yup.string(), - serialize: Yup.boolean(), -}) - -export function LiquidEditForm(props: LiquidEditFormProps): JSX.Element { - const { - deleteLiquidGroup, - cancelForm, - canDelete, - saveForm, - displayColor, - name: propName, - description: propDescription, - serialize, - } = props - const selectedLiquid = useSelector(selectors.getSelectedLiquidGroupState) - const nextGroupId = useSelector(selectors.getNextLiquidGroupId) - const liquidId = selectedLiquid.liquidGroupId ?? nextGroupId - const { t } = useTranslation(['form', 'button']) - const initialValues: LiquidEditFormValues = { - name: propName ?? '', - displayColor: displayColor ?? swatchColors(liquidId), - description: propDescription ?? '', - serialize: serialize || false, - } - - const { - handleSubmit, - formState: { errors, touchedFields, isDirty }, - control, - watch, - setValue, - } = useForm({ - defaultValues: initialValues, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - resolver: yupResolver(liquidEditFormSchema), - }) - const name = watch('name') - const description = watch('description') - const color = watch('displayColor') - - const handleLiquidEdits = (values: LiquidEditFormValues): void => { - saveForm({ - name: values.name, - displayColor: values.displayColor, - description: values.description ?? null, - serialize: values.serialize ?? false, - }) - } - - return ( - -
-
-
{t('liquid_edit.details')}
-
- - ( - - )} - /> - - - ( - - )} - /> - - - ( - { - setValue('displayColor', color) - field.onChange(color) - }} - /> - )} - /> - -
- - {errors.displayColor != null ? errors.displayColor.message : null} - -
-
-
- {t('liquid_edit.serialize_title')} -
-

- {t('liquid_edit.serialize_explanation')} -

- ( - ) => { - field.onChange(e) - }} - /> - )} - /> -
- -
- - {t('button:delete')} - - - {t('button:cancel')} - - - {t('button:save')} - -
-
-
- ) -} diff --git a/protocol-designer/src/components/LiquidsPage/LiquidsPageInfo.module.css b/protocol-designer/src/components/LiquidsPage/LiquidsPageInfo.module.css deleted file mode 100644 index 2da07d1398f..00000000000 --- a/protocol-designer/src/components/LiquidsPage/LiquidsPageInfo.module.css +++ /dev/null @@ -1,27 +0,0 @@ -@import '@opentrons/components/styles'; - -.info_wrapper { - font-size: var(--fs-body-2); /* from legacy --font-body-2-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-2-dark */ - color: var(--c-font-dark); /* from legacy --font-body-2-dark */ - text-align: center; - max-width: 38rem; - margin: 2rem auto; -} - -.header { - font-size: var(--fs-header); /* from legacy --font-header-dark */ - font-weight: var(--fw-semibold); /* from legacy --font-header-dark */ - color: var(--c-font-dark); /* from legacy --font-header-dark */ -} - -.instruction { - margin: 2rem 0; - line-height: 1.5; -} - -.inline_icon { - color: var(--c-font-dark); - height: 1.5em; - padding: 0 0.25em; -} diff --git a/protocol-designer/src/components/LiquidsPage/LiquidsPageInfo.tsx b/protocol-designer/src/components/LiquidsPage/LiquidsPageInfo.tsx deleted file mode 100644 index 22f73b55db3..00000000000 --- a/protocol-designer/src/components/LiquidsPage/LiquidsPageInfo.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Icon } from '@opentrons/components' -import styles from './LiquidsPageInfo.module.css' - -export function LiquidsPageInfo(): JSX.Element { - return ( -
-

Define your liquids

- -
- This is your inventory of the reagents and samples your protocol uses. - Use the New Liquid button in the sidebar to start defining liquids. -
- -
- {"After you've added your liquids, continue to the"} - - tab where you can specify where labware and liquids start on your deck. -
-
- ) -} diff --git a/protocol-designer/src/components/LiquidsPage/index.tsx b/protocol-designer/src/components/LiquidsPage/index.tsx deleted file mode 100644 index 761b4ef5d73..00000000000 --- a/protocol-designer/src/components/LiquidsPage/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useDispatch, useSelector } from 'react-redux' - -import * as labwareIngredActions from '../../labware-ingred/actions' -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' -import { LiquidEditForm } from './LiquidEditForm' -import { LiquidsPageInfo } from './LiquidsPageInfo' - -import type { LiquidGroup } from '../../labware-ingred/types' -import type { ThunkDispatch } from '../../types' - -export function LiquidsPage(): JSX.Element { - const dispatch = useDispatch>() - const selectedLiquidGroupState = useSelector( - labwareIngredSelectors.getSelectedLiquidGroupState - ) - const allIngredientGroupFields = useSelector( - labwareIngredSelectors.allIngredientGroupFields - ) - - const liquidGroupId = - selectedLiquidGroupState && selectedLiquidGroupState.liquidGroupId - const selectedIngredFields = - liquidGroupId != null ? allIngredientGroupFields[liquidGroupId] : null - const showForm = Boolean( - selectedLiquidGroupState.liquidGroupId || - selectedLiquidGroupState.newLiquidGroup - ) - const formKey = liquidGroupId || '__new_form__' - - const deleteLiquidGroup = (): void => { - if (liquidGroupId != null) - dispatch(labwareIngredActions.deleteLiquidGroup(liquidGroupId)) - } - const cancelForm = (): void => { - dispatch(labwareIngredActions.deselectLiquidGroup()) - } - - const saveForm = (formData: LiquidGroup): void => { - dispatch( - labwareIngredActions.editLiquidGroup({ - ...formData, - liquidGroupId: liquidGroupId, - }) - ) - } - - return showForm ? ( - - ) : ( - - ) -} diff --git a/protocol-designer/src/components/LiquidsSidebar/index.tsx b/protocol-designer/src/components/LiquidsSidebar/index.tsx deleted file mode 100644 index 174422b1878..00000000000 --- a/protocol-designer/src/components/LiquidsSidebar/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { - DeprecatedPrimaryButton, - SidePanel, - truncateString, -} from '@opentrons/components' -import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' -import * as labwareIngredActions from '../../labware-ingred/actions' -import { PDTitledList } from '../lists' -import { swatchColors } from '../swatchColors' -import listButtonStyles from '../listButtons.module.css' -import styles from './styles.module.css' - -import type { ThunkDispatch } from '../../types' - -export function LiquidsSidebar(): JSX.Element { - const { t } = useTranslation('button') - const selectedLiquidGroup = useSelector( - labwareIngredSelectors.getSelectedLiquidGroupState - ) - const liquids = useSelector(labwareIngredSelectors.allIngredientNamesIds) - const dispatch: ThunkDispatch = useDispatch() - - const selectLiquid = (liquidGroupId: string): void => { - dispatch(labwareIngredActions.selectLiquidGroup(liquidGroupId)) - } - const selectedLiquid = - selectedLiquidGroup && selectedLiquidGroup.liquidGroupId - return ( - - {liquids.map(({ ingredientId, name, displayColor }) => ( - { - selectLiquid(ingredientId) - }} - iconName="circle" - iconProps={{ - style: { - fill: displayColor ?? swatchColors(ingredientId), - }, - ...{ - className: styles.liquid_icon_container, - }, - }} - title={ - truncateString(name ?? '', 25) ?? - `Unnamed Ingredient ${ingredientId}` - } // fallback, should not happen - /> - ))} -
- dispatch(labwareIngredActions.createNewLiquidGroup())} - > - {t('new_liquid')} - -
-
- ) -} diff --git a/protocol-designer/src/components/LiquidsSidebar/styles.module.css b/protocol-designer/src/components/LiquidsSidebar/styles.module.css deleted file mode 100644 index 7f805134826..00000000000 --- a/protocol-designer/src/components/LiquidsSidebar/styles.module.css +++ /dev/null @@ -1,11 +0,0 @@ -@import '@opentrons/components/styles'; - -.liquid_icon_container { - border-style: solid; - border-width: 1px; - border-color: #e3e3e3; - border-radius: 4px; - background-color: var(--c-white); - height: 2.25rem; - padding: 0.5rem; -} diff --git a/protocol-designer/src/components/OffDeckLabwareButton.tsx b/protocol-designer/src/components/OffDeckLabwareButton.tsx deleted file mode 100644 index 585cf6aca12..00000000000 --- a/protocol-designer/src/components/OffDeckLabwareButton.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useState } from 'react' -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { - DeprecatedPrimaryButton, - Flex, - POSITION_ABSOLUTE, - POSITION_RELATIVE, - SPACING, -} from '@opentrons/components' -import { getSelectedTerminalItemId } from '../ui/steps' -import { OffDeckLabwareSlideout } from './OffDeckLabwareSlideout' - -export const OffDeckLabwareButton = (): JSX.Element => { - const selectedTerminalItemId = useSelector(getSelectedTerminalItemId) - const { t } = useTranslation('button') - const [showSlideout, setShowSlideout] = useState(false) - - return ( - - - { - setShowSlideout(true) - }} - > - {t('edit_off_deck')} - - - {showSlideout ? ( - { - setShowSlideout(false) - }} - initialSetupTerminalItemId={ - selectedTerminalItemId === '__initial_setup__' - } - /> - ) : null} - - ) -} diff --git a/protocol-designer/src/components/OffDeckLabwareSlideout.tsx b/protocol-designer/src/components/OffDeckLabwareSlideout.tsx deleted file mode 100644 index 1fd8be9814c..00000000000 --- a/protocol-designer/src/components/OffDeckLabwareSlideout.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { useDispatch, useSelector } from 'react-redux' -import { css } from 'styled-components' -import { useTranslation } from 'react-i18next' -import { - LegacyTooltip, - DeprecatedPrimaryButton, - useHoverTooltip, - Flex, - COLORS, - Icon, - SPACING, - Text, - DIRECTION_COLUMN, - BORDERS, - LabwareRender, - RobotWorkSpace, - LabwareNameOverlay, - RobotCoordsForeignDiv, - ALIGN_CENTER, - JUSTIFY_CENTER, - TYPOGRAPHY, - truncateString, - POSITION_ABSOLUTE, -} from '@opentrons/components' -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { openAddLabwareModal } from '../labware-ingred/actions' -import { getLabwareEntities } from '../step-forms/selectors' -import { selectors } from '../labware-ingred/selectors' -import { getAllWellContentsForActiveItem } from '../top-selectors/well-contents' -import { getRobotStateAtActiveItem } from '../top-selectors/labware-locations' -import { getLabwareNicknamesById } from '../ui/labware/selectors' -import { EditLabwareOffDeck } from './DeckSetup/LabwareOverlays/EditLabwareOffDeck' -import { BrowseLabware } from './DeckSetup/LabwareOverlays/BrowseLabware' -import { Slideout } from './Slideout' -import { wellFillFromWellContents } from './labware' - -interface OffDeckLabwareSlideoutProps { - initialSetupTerminalItemId: boolean - isExpanded: boolean - onCloseClick: () => void -} - -export const OffDeckLabwareSlideout = ( - props: OffDeckLabwareSlideoutProps -): JSX.Element => { - const { t } = useTranslation(['deck', 'button', 'tooltip']) - const [targetProps, tooltipProps] = useHoverTooltip() - const dispatch = useDispatch() - const disabled = props.initialSetupTerminalItemId === false - const robotState = useSelector(getRobotStateAtActiveItem) - const labwareEntities = useSelector(getLabwareEntities) - const allWellContentsForActiveItem = useSelector( - getAllWellContentsForActiveItem - ) - const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) - const labwareNickNames = useSelector(getLabwareNicknamesById) - - const offDeckEntries = - robotState?.labware != null - ? Object.entries(robotState?.labware).filter( - ([key, value]) => value.slot === 'offDeck' - ) - : null - const offDeck = - offDeckEntries != null && offDeckEntries.length > 0 - ? Object.fromEntries(offDeckEntries) - : null - - return ( - - dispatch(openAddLabwareModal({ slot: 'offDeck' }))} - marginTop={SPACING.spacing16} - marginRight={SPACING.spacing16} - disabled={disabled} - > - {t('button:add_off_deck')} - - {disabled ? ( - - {t(`tooltip:disabled_off_deck`)} - - ) : null} -
- } - > - {offDeck == null ? ( - - - {t('off_deck.slideout_empty_state')} - - ) : ( - Object.keys(offDeck).map(labwareId => { - const labwareNickName = labwareNickNames[labwareId] - const truncatedNickName = - labwareNickName != null - ? truncateString(labwareNickName, 75, 25) - : null - const wellContents = - allWellContentsForActiveItem != null - ? allWellContentsForActiveItem[labwareId] - : null - const definition = - labwareEntities[labwareId] != null - ? labwareEntities[labwareId].def - : null - return definition != null ? ( - - {() => ( - <> - - - - {disabled ? ( -
- -
- ) : ( - - )} -
- - )} -
- ) : null - }) - )} - - ) -} diff --git a/protocol-designer/src/components/PrereleaseModeIndicator.tsx b/protocol-designer/src/components/PrereleaseModeIndicator.tsx deleted file mode 100644 index 344a4fbf183..00000000000 --- a/protocol-designer/src/components/PrereleaseModeIndicator.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useSelector } from 'react-redux' -import { Icon } from '@opentrons/components' -import { selectors as featureFlagSelectors } from '../feature-flags' - -export const PrereleaseModeIndicator = (): JSX.Element | null => { - const prereleaseModeEnabled = useSelector( - featureFlagSelectors.getEnabledPrereleaseMode - ) - - return prereleaseModeEnabled === true ? ( -
- -
- ) : null -} diff --git a/protocol-designer/src/components/ProtocolEditor.module.css b/protocol-designer/src/components/ProtocolEditor.module.css deleted file mode 100644 index 21c591d7b38..00000000000 --- a/protocol-designer/src/components/ProtocolEditor.module.css +++ /dev/null @@ -1,27 +0,0 @@ -@import '@opentrons/components/styles'; - -.wrapper { - display: flex; - flex-direction: row; - align-items: stretch; - width: 100%; - min-height: 100vh; -} - -.main_page_wrapper { - /* Wraps TitleBar + page content */ - display: flex; - flex-direction: column; - width: 100%; - - /* somehow ensure flex children shrink rather than overflow */ - overflow-x: hidden; -} - -.main_page_content { - position: absolute; - top: 3rem; - bottom: 0; - width: calc(100% - 22.8125rem); - overflow-y: scroll; -} diff --git a/protocol-designer/src/components/SelectionRect.module.module.css b/protocol-designer/src/components/SelectionRect.module.module.css deleted file mode 100644 index 0f75e7bc043..00000000000 --- a/protocol-designer/src/components/SelectionRect.module.module.css +++ /dev/null @@ -1,21 +0,0 @@ -@import '@opentrons/components/styles'; - -.selection_rect { - pointer-events: none; /* prevents this div from occluding wells during document.elementFromPoint sampling */ -} - -rect.selection_rect { - /* svg version */ - fill: var(--c-selection-overlay); - stroke: var(--c-highlight); - stroke-width: 0.4; -} - -div.selection_rect { - /* normal html version */ - background-color: var(--c-selection-overlay); - position: fixed; - z-index: 1000; - border-radius: 0; - border: 1px solid var(--c-highlight); -} diff --git a/protocol-designer/src/components/SettingsPage/FeatureFlagCard/FeatureFlagCard.tsx b/protocol-designer/src/components/SettingsPage/FeatureFlagCard/FeatureFlagCard.tsx deleted file mode 100644 index 2b05ceb6ce4..00000000000 --- a/protocol-designer/src/components/SettingsPage/FeatureFlagCard/FeatureFlagCard.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { useState } from 'react' -import { createPortal } from 'react-dom' -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import sortBy from 'lodash/sortBy' -import { ContinueModal, Card, ToggleButton } from '@opentrons/components' -import { resetScrollElements } from '../../../ui/steps/utils' -import { - userFacingFlags, - actions as featureFlagActions, - selectors as featureFlagSelectors, -} from '../../../feature-flags' -import { getMainPagePortalEl } from '../../portals/MainPageModalPortal' -import styles from '../SettingsPage.module.css' -import modalStyles from '../../modals/modal.module.css' -import type { FlagTypes } from '../../../feature-flags' - -export function FeatureFlagCard(): JSX.Element { - const flags = useSelector(featureFlagSelectors.getFeatureFlagData) - const dispatch = useDispatch() - - const [modalFlagName, setModalFlagName] = useState(null) - const { t } = useTranslation(['modal', 'card', 'feature_flags']) - - const setFeatureFlags = ( - flags: Partial> - ): void => { - dispatch(featureFlagActions.setFeatureFlags(flags)) - } - - const prereleaseModeEnabled = flags.PRERELEASE_MODE === true - - // @ts-expect-error(sa, 2021-6-21): Object.keys not smart enough to take keys from props.flags - const allFlags: FlagTypes[] = sortBy(Object.keys(flags)) - - const userFacingFlagNames = allFlags.filter(flagName => - userFacingFlags.includes(flagName) - ) - - const prereleaseFlagNames = allFlags.filter( - flagName => !userFacingFlags.includes(flagName) - ) - - const getDescription = (flag: FlagTypes): JSX.Element => { - const RICH_DESCRIPTIONS: Partial> = { - OT_PD_ALLOW_ALL_TIPRACKS: ( - <> -

{t(`feature_flags:${flag}.description`)}

- - ), - OT_PD_DISABLE_MODULE_RESTRICTIONS: ( - <> -

{t(`feature_flags:${flag}.description_1`)}

-

{t(`feature_flags:${flag}.description_2`)}

- - ), - } - return ( - RICH_DESCRIPTIONS[flag] ||

{t(`feature_flags:${flag}.description`)}

- ) - } - - const toFlagRow = (flagName: FlagTypes): JSX.Element => ( -
-
-

- {t(`feature_flags:${flagName}.title`)} -

- { - resetScrollElements() - setModalFlagName(flagName) - }} - /> -
-
- {getDescription(flagName)} -
-
- ) - - const noFlagsFallback = ( -

- No experimental settings are available in this version of Protocol - Designer. -

- ) - - const userFacingFlagRows = userFacingFlagNames.map(toFlagRow) - const prereleaseFlagRows = prereleaseFlagNames.map(toFlagRow) - - let flagSwitchDirection: string = 'on' - - if (modalFlagName) { - const isFlagOn: boolean | null | undefined = flags[modalFlagName] - flagSwitchDirection = isFlagOn ? 'off' : 'on' - } - return ( - <> - {modalFlagName && - createPortal( - { - setModalFlagName(null) - }} - onContinueClick={() => { - setFeatureFlags({ - [modalFlagName as string]: !flags[modalFlagName], - }) - setModalFlagName(null) - }} - > -

- {t(`experimental_feature_warning.${flagSwitchDirection}.body1`)} -

-

- {t(`experimental_feature_warning.${flagSwitchDirection}.body2`)} -

-
, - getMainPagePortalEl() - )} - -
- {userFacingFlagRows.length > 0 ? userFacingFlagRows : noFlagsFallback} -
-
- {prereleaseModeEnabled && ( - -
{prereleaseFlagRows}
-
- )} - - ) -} diff --git a/protocol-designer/src/components/SettingsPage/SettingsApp.tsx b/protocol-designer/src/components/SettingsPage/SettingsApp.tsx deleted file mode 100644 index 20a31121010..00000000000 --- a/protocol-designer/src/components/SettingsPage/SettingsApp.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { - Card, - OutlineButton, - ToggleButton, - LabeledValue, -} from '@opentrons/components' -import { - actions as analyticsActions, - selectors as analyticsSelectors, -} from '../../analytics' -import { - actions as tutorialActions, - selectors as tutorialSelectors, -} from '../../tutorial' -import { OLDEST_MIGRATEABLE_VERSION } from '../../load-file/migration' -import styles from './SettingsPage.module.css' -import { FeatureFlagCard } from './FeatureFlagCard/FeatureFlagCard' - -export function SettingsApp(): JSX.Element { - const dispatch = useDispatch() - const hasOptedIn = useSelector(analyticsSelectors.getHasOptedIn) - const canClearHintDismissals = useSelector( - tutorialSelectors.getCanClearHintDismissals - ) - const _toggleOptedIn = hasOptedIn - ? analyticsActions.optOut - : analyticsActions.optIn - - const { t } = useTranslation(['card', 'application', 'button']) - return ( - <> -
- -
-
- - {/* TODO: BC 2019-02-26 add release notes link here, when there are release notes */} -
-
-
-
-
- -
-
- {t('body.restore_hints')} - - dispatch(tutorialActions.clearAllHintDismissals()) - } - > - {canClearHintDismissals - ? t('button:restore') - : t('button:restored')} - -
-
-
-
-
- -
-
-

{t('toggle.share_session')}

- dispatch(_toggleOptedIn())} - /> -
- -

- {t('body.reason_for_collecting_data')}{' '} - {t('body.data_collected_is_internal')}. -

-
-
-
-
- -
- - ) -} diff --git a/protocol-designer/src/components/SettingsPage/SettingsPage.module.css b/protocol-designer/src/components/SettingsPage/SettingsPage.module.css deleted file mode 100644 index 475bea4ee89..00000000000 --- a/protocol-designer/src/components/SettingsPage/SettingsPage.module.css +++ /dev/null @@ -1,92 +0,0 @@ -@import '@opentrons/components/styles'; - -:root { - --mw-labeled-toggle: 25rem; -} - -.sidebar_item { - background-color: white; - margin: 0.4rem 0.125rem; - color: var(--c-dark-gray); -} - -.page_row { - padding: 0.5rem; -} - -.card_point_list { - margin-top: 1rem; - margin-left: 4rem; -} - -.card_content { - padding: 1rem; - font-size: var(--fs-body-2); /* from legacy --font-body-2-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-2-dark */ - color: var(--c-font-dark); /* from legacy --font-body-2-dark */ - line-height: 1.5; - - & p { - margin-bottom: 1rem; - max-width: 70%; - min-width: 20rem; - line-height: 1.5; - } -} - -.setting_row { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; -} - -.feature_flag_description { - font-size: var(--fs-body-2); /* from legacy --font-body-2-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-2-dark */ - color: var(--c-font-dark); /* from legacy --font-body-2-dark */ - - & p { - margin-bottom: 1rem; - } -} - -.toggle_label { - font-size: var(--fs-body-2); /* from legacy --font-body-2-dark */ - color: var(--c-font-dark); /* from legacy --font-body-2-dark */ - max-width: var(--mw-labeled-toggle); - display: inline-block; - font-weight: var(--fw-semibold); - margin-top: 1rem; -} - -.toggle_wrapper { - display: flex; - justify-content: space-between; -} - -.toggle_button { - float: right; - width: 2rem; - margin-right: 1rem; - padding: 0; - - &:hover { - background-color: transparent; - } -} - -.button { - float: right; -} - -.body_wrapper { - margin: 0.375rem 0 0.625rem; - line-height: 1.5; - padding: 2rem 1rem; -} - -.labeled_value p { - font-size: var(--fs-body-2); - max-width: 100%; -} diff --git a/protocol-designer/src/components/SettingsPage/SettingsSidebar.tsx b/protocol-designer/src/components/SettingsPage/SettingsSidebar.tsx deleted file mode 100644 index b5a4619ffae..00000000000 --- a/protocol-designer/src/components/SettingsPage/SettingsSidebar.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { SidePanel } from '@opentrons/components' -import { selectors } from '../../navigation' -import { PDTitledList } from '../lists' -import styles from './SettingsPage.module.css' - -export const SettingsSidebar = (): JSX.Element => { - const currentPage = useSelector(selectors.getCurrentPage) - const { t } = useTranslation('nav') - return ( - - - - ) -} diff --git a/protocol-designer/src/components/SettingsPage/index.tsx b/protocol-designer/src/components/SettingsPage/index.tsx deleted file mode 100644 index 793914acd33..00000000000 --- a/protocol-designer/src/components/SettingsPage/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useSelector } from 'react-redux' - -import { selectors } from '../../navigation' -import { SettingsApp } from './SettingsApp' - -export { SettingsSidebar } from './SettingsSidebar' - -export function SettingsPage(): JSX.Element { - const currentPage = useSelector(selectors.getCurrentPage) - switch (currentPage) { - // TODO: Ian 2019-08-21 when we have other pages, put them here - case 'settings-app': - default: - return - } -} diff --git a/protocol-designer/src/components/Slideout/index.tsx b/protocol-designer/src/components/Slideout/index.tsx deleted file mode 100644 index 37c3df180fd..00000000000 --- a/protocol-designer/src/components/Slideout/index.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import * as React from 'react' -import { css } from 'styled-components' -import { - ALIGN_CENTER, - Box, - Btn, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - OVERFLOW_WRAP_ANYWHERE, - Overlay, - POSITION_FIXED, - SPACING, - Text, - TYPOGRAPHY, -} from '@opentrons/components' - -export interface SlideoutProps { - title: string | React.ReactElement - children: React.ReactNode - onCloseClick: () => unknown - // isExpanded is for collapse and expand animation - isExpanded?: boolean - footer?: React.ReactNode -} - -const SHARED_STYLE = css` - z-index: 2; - overflow: hidden; - @keyframes slidein { - from { - transform: translateX(100%); - } - to { - transform: translateX(0); - } - } - @keyframes slideout { - from { - transform: translateX(0); - } - to { - transform: translateX(100%); - } - } - @keyframes overlayin { - from { - opacity: 0; - } - to { - opacity: 0.35; - } - } - @keyframes overlayout { - from { - opacity: 0.35; - visibility: visible; - } - to { - opacity: 0; - visibility: hidden; - } - } -` -const EXPANDED_STYLE = css` - ${SHARED_STYLE} - animation: slidein 300ms forwards; -` -const COLLAPSED_STYLE = css` - ${SHARED_STYLE} - animation: slideout 300ms forwards; -` -const INITIALLY_COLLAPSED_STYLE = css` - ${SHARED_STYLE} - animation: slideout 0ms forwards; -` -const OVERLAY_IN_STYLE = css` - ${SHARED_STYLE} - animation: overlayin 300ms forwards; -` -const OVERLAY_OUT_STYLE = css` - ${SHARED_STYLE} - animation: overlayout 300ms forwards; -` -const INITIALLY_OVERLAY_OUT_STYLE = css` - ${SHARED_STYLE} - animation: overlayout 0ms forwards; -` - -const CLOSE_ICON_STYLE = css` - border-radius: 50%; - - &:hover { - background: ${COLORS.grey30}; - } - &:active { - background: ${COLORS.grey35}; - } -` - -export const Slideout = (props: SlideoutProps): JSX.Element => { - const { isExpanded, title, onCloseClick, children, footer } = props - const slideOutRef = React.useRef(null) - const [isReachedBottom, setIsReachedBottom] = React.useState(false) - - const hasBeenExpanded = React.useRef(isExpanded ?? false) - const handleScroll = (): void => { - if (slideOutRef.current == null) return - const { scrollTop, scrollHeight, clientHeight } = slideOutRef.current - if (scrollTop + clientHeight === scrollHeight) { - setIsReachedBottom(true) - } else { - setIsReachedBottom(false) - } - } - - React.useEffect(() => { - handleScroll() - }, [slideOutRef]) - - const handleClose = (): void => { - hasBeenExpanded.current = true - onCloseClick() - } - - const collapsedStyle = hasBeenExpanded.current - ? COLLAPSED_STYLE - : INITIALLY_COLLAPSED_STYLE - const overlayOutStyle = hasBeenExpanded.current - ? OVERLAY_OUT_STYLE - : INITIALLY_OVERLAY_OUT_STYLE - return ( - <> - - - - {typeof title === 'string' ? ( - - - {title} - - - - - - - - ) : ( - <>{title} - )} - - - {children} - - {footer != null ? ( - - {footer} - - ) : null} - - - - ) -} diff --git a/protocol-designer/src/components/StepCreationButton.tsx b/protocol-designer/src/components/StepCreationButton.tsx deleted file mode 100644 index fef01bd8015..00000000000 --- a/protocol-designer/src/components/StepCreationButton.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { - LegacyTooltip, - DeprecatedPrimaryButton, - useHoverTooltip, - TOOLTIP_RIGHT, - TOOLTIP_TOP, - TOOLTIP_FIXED, -} from '@opentrons/components' -import { - HEATERSHAKER_MODULE_TYPE, - MAGNETIC_MODULE_TYPE, - TEMPERATURE_MODULE_TYPE, - THERMOCYCLER_MODULE_TYPE, -} from '@opentrons/shared-data' -import { actions as stepsActions, getIsMultiSelectMode } from '../ui/steps' -import { - selectors as stepFormSelectors, - getIsModuleOnDeck, -} from '../step-forms' -import { getEnableComment } from '../feature-flags/selectors' -import { - ConfirmDeleteModal, - CLOSE_UNSAVED_STEP_FORM, -} from './modals/ConfirmDeleteModal' -import { getMainPagePortalEl } from './portals/MainPageModalPortal' -import { stepIconsByType } from '../form-types' -import styles from './listButtons.module.css' -import type { ThunkDispatch } from 'redux-thunk' -import type { BaseState } from '../types' -import type { StepType } from '../form-types' - -interface StepButtonComponentProps { - children: React.ReactNode - expanded: boolean - disabled: boolean - setExpanded: (expanded: boolean) => void -} - -// TODO: Ian 2019-01-17 move out to centralized step info file - see #2926 - -export const StepCreationButtonComponent = ( - props: StepButtonComponentProps -): JSX.Element => { - const { t } = useTranslation(['tooltip', 'button']) - const { children, expanded, setExpanded, disabled } = props - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - strategy: TOOLTIP_FIXED, - }) - return ( -
{ - setExpanded(false) - }} - {...targetProps} - > - {disabled && ( - - {t(`disabled_step_creation`)} - - )} - { - setExpanded(!expanded) - }} - disabled={disabled} - > - {t('button:add_step')} - - -
{expanded && children}
-
- ) -} - -export interface StepButtonItemProps { - onClick: () => void - stepType: StepType -} - -export function StepButtonItem(props: StepButtonItemProps): JSX.Element { - const { onClick, stepType } = props - const { t } = useTranslation(['tooltip', 'application']) - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_RIGHT, - strategy: TOOLTIP_FIXED, - }) - const tooltipMessage = t(`step_description.${stepType}`) - return ( - <> -
- - {t(`application:stepType.${stepType}`, stepType)} - -
- {tooltipMessage} - - ) -} - -export const StepCreationButton = (): JSX.Element => { - const enableComment = useSelector(getEnableComment) - - const getSupportedSteps = (): Array< - Exclude - > => - enableComment - ? [ - 'comment', - 'moveLabware', - 'moveLiquid', - 'mix', - 'pause', - 'heaterShaker', - 'magnet', - 'temperature', - 'thermocycler', - ] - : [ - 'moveLabware', - 'moveLiquid', - 'mix', - 'pause', - 'heaterShaker', - 'magnet', - 'temperature', - 'thermocycler', - ] - - const currentFormIsPresaved = useSelector( - stepFormSelectors.getCurrentFormIsPresaved - ) - const formHasChanges = useSelector( - stepFormSelectors.getCurrentFormHasUnsavedChanges - ) - const isStepCreationDisabled = useSelector(getIsMultiSelectMode) - const modules = useSelector(stepFormSelectors.getInitialDeckSetup).modules - const isStepTypeEnabled: Record< - Exclude, - boolean - > = { - comment: enableComment, - moveLabware: true, - moveLiquid: true, - mix: true, - pause: true, - magnet: getIsModuleOnDeck(modules, MAGNETIC_MODULE_TYPE), - temperature: getIsModuleOnDeck(modules, TEMPERATURE_MODULE_TYPE), - thermocycler: getIsModuleOnDeck(modules, THERMOCYCLER_MODULE_TYPE), - heaterShaker: getIsModuleOnDeck(modules, HEATERSHAKER_MODULE_TYPE), - } - const [expanded, setExpanded] = React.useState(false) - const [ - enqueuedStepType, - setEnqueuedStepType, - ] = React.useState(null) - const dispatch = useDispatch>() - - const addStep = ( - stepType: StepType - ): ReturnType => - dispatch(stepsActions.addAndSelectStepWithHints({ stepType })) - - const items = getSupportedSteps() - .filter(stepType => isStepTypeEnabled[stepType]) - .map(stepType => ( - { - setExpanded(false) - - if (currentFormIsPresaved || formHasChanges) { - setEnqueuedStepType(stepType) - } else { - addStep(stepType) - } - }} - /> - )) - - return ( - <> - {enqueuedStepType !== null && - createPortal( - { - setEnqueuedStepType(null) - }} - onContinueClick={() => { - if (enqueuedStepType !== null) { - addStep(enqueuedStepType) - setEnqueuedStepType(null) - } - }} - />, - getMainPagePortalEl() - )} - - {items} - - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/ButtonRow/index.tsx b/protocol-designer/src/components/StepEditForm/ButtonRow/index.tsx deleted file mode 100644 index 1f8faf42024..00000000000 --- a/protocol-designer/src/components/StepEditForm/ButtonRow/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useTranslation } from 'react-i18next' -import cx from 'classnames' -import { OutlineButton, DeprecatedPrimaryButton } from '@opentrons/components' - -import modalStyles from '../../modals/modal.module.css' -import styles from './styles.module.css' - -interface ButtonRowProps { - handleClickMoreOptions: () => unknown - handleClose: () => unknown - handleSave: () => unknown - handleDelete: () => unknown - canSave: boolean -} - -export const ButtonRow = (props: ButtonRowProps): JSX.Element => { - const { - handleDelete, - handleClickMoreOptions, - handleClose, - handleSave, - canSave, - } = props - const { t } = useTranslation('button') - return ( -
-
- - {t('delete')} - - - {t('notes')} - -
-
- - {t('close')} - - - {t('save')} - -
-
- ) -} diff --git a/protocol-designer/src/components/StepEditForm/ButtonRow/styles.module.css b/protocol-designer/src/components/StepEditForm/ButtonRow/styles.module.css deleted file mode 100644 index c88f64a61c1..00000000000 --- a/protocol-designer/src/components/StepEditForm/ButtonRow/styles.module.css +++ /dev/null @@ -1,21 +0,0 @@ -:root { - --form-max-width: 55rem; /* 'breakpoint' for gutter */ - --form-button-width: 6.25rem; - --form-button-margin: 0.625rem; -} - -.form_wrapper { - max-width: var(--form-max-width); -} - -.form_button { - width: var(--form-button-width); -} - -.form_button:not(:last-child) { - margin-right: var(--form-button-margin); -} - -.button_auto { - width: auto; -} diff --git a/protocol-designer/src/components/StepEditForm/StepEditForm.module.css b/protocol-designer/src/components/StepEditForm/StepEditForm.module.css deleted file mode 100644 index 439dccbdf8c..00000000000 --- a/protocol-designer/src/components/StepEditForm/StepEditForm.module.css +++ /dev/null @@ -1,448 +0,0 @@ -@import '@opentrons/components/styles'; - -.advanced_settings_panel { - background-color: #f6f6f6; /* TODO Ian 2019-03-15 add to colors.css? */ - margin: -0.5rem -0.75rem 0 -0.75rem; - padding: 0 0.75rem 0 0.75rem; -} - -.form_wrapper { - max-width: 55rem; /* 'breakpoint' for gutter */ -} - -.disposal_vol_wrapper, -.form_row, -.checkbox_column { - margin: 1rem 0; - width: auto; -} - -.form_row { - min-height: 2.25rem; - display: flex; - align-items: flex-start; -} - -.wrap_group { - display: flex; - flex-direction: row; - justify-content: flex-start; -} - -.field, -.small_field, -.large_field { - margin-right: 0.5rem; -} - -.large_field { - width: 12rem; -} - -.small_field { - width: 5.75rem; - min-width: 5.75rem; -} - -.full_width_field { - width: 100%; -} - -/* TODO: Ian 2019-03-25 make this a component library input? */ -.textarea_field { - font-size: var(--fs-body-1); - background-color: var(--c-light-gray); - border-radius: var(--bd-radius-form-field); - padding: 0.25rem 0.25rem 0.25rem 0.5rem; - height: 100%; - width: 100%; - - /* resets */ - border: none; - overflow: auto; - outline: none; - box-shadow: none; - resize: none; -} - -.orphan_field { - margin: 0; -} - -.checkbox_row { - margin-top: 0.5rem; - height: 1.5rem; - display: flex; - flex-direction: row; -} - -.checkbox_field, -.toggle_field { - text-transform: capitalize; -} - -.captioned_field { - margin-bottom: 1rem; -} - -.no_label { - margin-top: 0.5rem; -} - -.form_button { - width: 6.25rem; -} - -.form_button:not(:last-child) { - margin-right: 0.625rem; -} - -.sub_label_no_checkbox { - font-size: var(--fs-body-1); /* from legacy --font-body-1-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-1-dark */ - color: var(--c-font-dark); /* from legacy --font-body-1-dark */ - width: 5rem; - display: flex; - align-items: center; - line-height: 1; - flex: 0 0 auto; - padding: 0 0.5rem; - margin: 0.5rem 0 0.5rem 1.4rem; -} - -.advanced_settings_button_wrapper { - float: right; - height: 2.25rem; - width: 2.25rem; -} - -.advanced_settings_button { - padding: 0.375rem; -} - -.section_wrapper { - display: flex; - justify-content: space-between; - margin-bottom: 0.5rem; - min-height: 4rem; -} - -.section_column { - flex: 1; - max-width: 20rem; -} - -.section_header { - display: flex; - justify-content: space-between; - align-items: baseline; - min-height: 1.25rem; /* Same height with and without gear icon */ - border-bottom: 1px solid var(--c-light-gray); - width: 100%; - padding-top: 0.25rem; -} - -.section_header_text { - /* TODO: Ian 2019-03-20: if it's reused, do typography.css entry for the below? - And add this color to colors.css? */ - color: #666; - letter-spacing: 1px; - font-size: var(--fs-body-2); - font-weight: var(--fw-semibold); - text-transform: uppercase; -} - -.section_header_text_column { - flex: 1; - max-width: 20rem; - color: #666; - letter-spacing: 1px; - font-size: var(--fs-body-2); - font-weight: var(--fw-semibold); - text-transform: uppercase; -} - -.path_option { - width: 2.275rem; - height: 1.55rem; - margin-right: 0.25rem; - cursor: pointer; - border-radius: 2px; - border: 1px solid #c4c4c4; -} - -.path_option.selected { - background-color: var(--c-light-gray); - border-radius: 2px; - border: 1.5px solid var(--c-black); -} - -.path_option.disabled { - color: var(--c-light-gray); - opacity: 0.16; - cursor: default; - pointer-events: inherit; -} - -.path_options { - display: flex; - flex-direction: row; - list-style: none; -} - -.tooltip { - line-height: 1rem; - max-width: 18rem; -} - -.path_tooltip_image { - padding: 0.5rem; -} - -.path_tooltip_image.disabled { - opacity: 0.5; -} - -.path_tooltip_title { - font-size: var(--fs-header); - text-align: center; -} - -.path_tooltip_title.disabled { - opacity: 0.5; -} - -.path_tooltip_subtitle { - font-size: var(--fs-body-2); - text-align: center; -} - -.disposal_vol_wrapper { - min-height: 3.5rem; - margin-bottom: 1rem; -} - -.magnet_section_wrapper { - display: flex; - justify-content: flex-start; - min-height: 4rem; -} - -.magnet_form_group { - flex-basis: 0 1 11rem; - margin: 1rem 4.25rem 1rem 0; -} - -.temperature_section_wrapper { - margin-bottom: 2rem; -} - -.temperature_form_group { - margin: 1rem 0; -} - -.module_labware_text { - max-width: 11rem; - margin-top: 0.5rem; - font-size: var(--fs-body-1); - overflow: hidden; - white-space: pre-wrap; - text-overflow: ellipsis; -} - -.select_module_message { - font-size: var(--fs-body-1); -} - -/* -TODO (ka 2020-1-30): This is a workaround since component library -RadioGroup does not support a disabled option. Should revisit if -and when that is implemented. -*/ -.disabled { - opacity: 0.5; - pointer-events: none; -} - -.diagram_row { - display: flex; - margin: 1rem 0 2rem 14rem; -} - -.tc_step_group { - margin: 1rem 0; -} - -.tc_step_option { - margin-bottom: 0.5rem; -} - -.toggle_form_group { - min-width: 20%; - margin: 0 0 1rem 1.75rem; -} - -.set_plate_latch_form_group { - min-width: 20%; - margin: 0 0 0 1.75rem; -} - -.toggle_row { - display: flex; - flex-direction: row; - align-items: flex-start; -} - -.toggle_field { - margin-right: 1rem; -} - -.toggle_temperature_field { - margin-top: 0.125rem; -} - -.profile_form { - padding-left: 1.75rem; -} - -.profile_settings_group { - margin-right: 1rem; -} - -.profile_settings_lid { - font-size: var(--fs-body-1); - line-height: 2; -} - -.profile_step_labels { - font-size: var(--fs-body-1); /* from legacy --font-form-default */ - color: var(--c-font-dark); /* from legacy --font-form-default */ - display: grid; - grid-template-columns: 12.5rem 7.25rem 7.25rem; - font-weight: var(--fw-semibold); - padding: 1rem 0 0.25rem 2.125rem; -} - -.profile_step_number { - font-size: var(--fs-body-1); /* from legacy --font-body-1-dark */ - font-weight: var(--fw-regular); /* from legacy --font-body-1-dark */ - color: var(--c-font-dark); /* from legacy --font-body-1-dark */ - width: 1.5rem; - text-align: right; - padding: 0.5rem 0.5rem 0 0; -} - -.profile_step_row { - display: flex; - align-items: center; - padding: 0.25rem 0; - - &.cycle { - padding: 0; - } -} - -.profile_step_fields { - display: flex; - align-items: flex-start; - border: var(--bd-light); - width: 100%; - min-width: 36rem; - padding: 0.5rem; -} - -.profile_cycle_fields { - width: 36rem; - border: none; -} - -.step_input_wrapper { - margin-right: 1.5rem; -} - -.title { - width: 11rem; -} - -.profile_field { - width: 5.75rem; -} - -.delete_step_icon { - color: var(--c-med-gray); - width: 1.5rem; - margin-top: 0.25rem; - cursor: pointer; -} - -.profile_cycle_wrapper { - display: flex; - width: 100%; -} - -.cycle_steps { - display: flex; -} - -.cycle_row { - display: flex; - flex-direction: column; - position: relative; - border-right: 1px solid var(--c-light-gray); - padding: 0; - margin-top: 0.25rem; - margin-right: 0.75rem; - - &::before { - content: ''; - background: var(--c-light-gray); - position: absolute; - top: 0; - right: 0; - height: 1px; - width: 1rem; - } - - &::after { - content: ''; - background: var(--c-light-gray); - position: absolute; - bottom: 0; - right: 0; - height: 1px; - width: 1rem; - } -} - -.profile_cycle_group { - border: var(--bd-light); - width: 100%; - padding-right: 0.5rem; - padding-left: 0.375rem; -} - -.cycle_step_delete { - height: 3rem; - - & > .delete_step_icon { - margin-top: 0.75rem; - } -} - -.cycles_field { - margin-top: 1rem; -} - -.add_cycle_step { - text-align: right; - margin: 0.75rem 0 1rem; -} - -.profile_button_group { - padding: 1rem 1.5rem 0; - text-align: right; - - & :first-child { - margin-right: 1.5rem; - } -} diff --git a/protocol-designer/src/components/StepEditForm/StepEditFormComponent.tsx b/protocol-designer/src/components/StepEditForm/StepEditFormComponent.tsx deleted file mode 100644 index 672b562b0ec..00000000000 --- a/protocol-designer/src/components/StepEditForm/StepEditFormComponent.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import type * as React from 'react' -import cx from 'classnames' -import get from 'lodash/get' -import { MoreOptionsModal } from '../modals/MoreOptionsModal' -import { - MixForm, - MoveLabwareForm, - MoveLiquidForm, - PauseForm, - CommentForm, - MagnetForm, - TemperatureForm, - ThermocyclerForm, - HeaterShakerForm, -} from './forms' -import { Alerts } from '../alerts/Alerts' -import { ButtonRow } from './ButtonRow' -import formStyles from '../forms/forms.module.css' -import styles from './StepEditForm.module.css' -import type { StepFieldName } from '../../steplist/fieldLevel' -import type { FormData, StepType } from '../../form-types' -import type { FieldPropsByName, FocusHandlers, StepFormProps } from './types' - -type StepFormMap = { - [K in StepType]?: React.ComponentType | null -} - -const STEP_FORM_MAP: StepFormMap = { - mix: MixForm, - pause: PauseForm, - moveLabware: MoveLabwareForm, - moveLiquid: MoveLiquidForm, - magnet: MagnetForm, - temperature: TemperatureForm, - thermocycler: ThermocyclerForm, - heaterShaker: HeaterShakerForm, - comment: CommentForm, -} - -interface Props { - canSave: boolean - dirtyFields: string[] - focusHandlers: FocusHandlers - focusedField: StepFieldName | null - formData: FormData - propsForFields: FieldPropsByName - handleClose: () => unknown - handleDelete: () => unknown - handleSave: () => unknown - showMoreOptionsModal: boolean - toggleMoreOptionsModal: () => unknown -} - -export const StepEditFormComponent = (props: Props): JSX.Element => { - const { - formData, - focusHandlers, - canSave, - handleClose, - handleDelete, - dirtyFields, - handleSave, - propsForFields, - showMoreOptionsModal, - toggleMoreOptionsModal, - focusedField, - } = props - - const FormComponent: typeof STEP_FORM_MAP[keyof typeof STEP_FORM_MAP] = get( - STEP_FORM_MAP, - formData.stepType - ) - if (!FormComponent) { - // early-exit if step form doesn't exist - return ( -
-
Todo: support {formData && formData.stepType} step
-
- ) - } - - return ( - <> - {showMoreOptionsModal && ( - - )} - -
- - -
- - ) -} diff --git a/protocol-designer/src/components/StepEditForm/__tests__/utils.test.ts b/protocol-designer/src/components/StepEditForm/__tests__/utils.test.ts deleted file mode 100644 index c2d46ce7546..00000000000 --- a/protocol-designer/src/components/StepEditForm/__tests__/utils.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, it, beforeEach } from 'vitest' -import { - SOURCE_WELL_BLOWOUT_DESTINATION, - DEST_WELL_BLOWOUT_DESTINATION, -} from '@opentrons/step-generation' -import { getBlowoutLocationOptionsForForm } from '../utils' -import type { LegacyDropdownOption } from '@opentrons/components' - -describe('getBlowoutLocationOptionsForForm', () => { - let destOption: LegacyDropdownOption - let sourceOption: LegacyDropdownOption - let disabledSourceOption: LegacyDropdownOption - let disabledDestOption: LegacyDropdownOption - const sourceName = 'Source Well' - const destName = 'Destination Well' - - beforeEach(() => { - destOption = { name: destName, value: DEST_WELL_BLOWOUT_DESTINATION } - disabledDestOption = { - name: destName, - value: DEST_WELL_BLOWOUT_DESTINATION, - disabled: true, - } - sourceOption = { name: sourceName, value: SOURCE_WELL_BLOWOUT_DESTINATION } - disabledSourceOption = { - name: sourceName, - value: SOURCE_WELL_BLOWOUT_DESTINATION, - disabled: true, - } - }) - - it('should just return destination option for Mix steps', () => { - const result = getBlowoutLocationOptionsForForm({ stepType: 'mix' }) - expect(result).toEqual([destOption]) - }) - - it('should return enabled source + enabled destination options for moveLiquid steps, "single" path', () => { - const result = getBlowoutLocationOptionsForForm({ - stepType: 'moveLiquid', - path: 'single', - }) - expect(result).toEqual([sourceOption, destOption]) - }) - - it('should return disabled source + enabled destination options for moveLiquid steps, "multiAspirate" path', () => { - const result = getBlowoutLocationOptionsForForm({ - stepType: 'moveLiquid', - path: 'multiAspirate', - }) - expect(result).toEqual([disabledSourceOption, destOption]) - }) - - it('should return enabled source + disabled destination options for moveLiquid steps, "multiDispense" path', () => { - const result = getBlowoutLocationOptionsForForm({ - stepType: 'moveLiquid', - path: 'multiDispense', - }) - expect(result).toEqual([sourceOption, disabledDestOption]) - }) - - it('should return empty array for other step types', () => { - const result = getBlowoutLocationOptionsForForm({ stepType: 'magnet' }) - expect(result).toEqual([]) - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/BlowoutLocationField.tsx b/protocol-designer/src/components/StepEditForm/fields/BlowoutLocationField.tsx deleted file mode 100644 index 99e5846782e..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/BlowoutLocationField.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type * as React from 'react' -import { useSelector } from 'react-redux' -import { DropdownField } from '@opentrons/components' -import cx from 'classnames' -import { selectors as uiLabwareSelectors } from '../../../ui/labware' -import styles from '../StepEditForm.module.css' -import type { Options } from '@opentrons/components' -import type { FieldProps } from '../types' - -type BlowoutLocationDropdownProps = FieldProps & { - options: Options - className?: string -} - -export const BlowoutLocationField = ( - props: BlowoutLocationDropdownProps -): JSX.Element => { - const { - className, - disabled, - onFieldBlur, - onFieldFocus, - updateValue, - value, - } = props - - const disposalOptions = useSelector(uiLabwareSelectors.getDisposalOptions) - const options = [...disposalOptions, ...props.options] - - return ( - ) => { - updateValue(e.currentTarget.value) - }} - /> - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/BlowoutZOffsetField.tsx b/protocol-designer/src/components/StepEditForm/fields/BlowoutZOffsetField.tsx deleted file mode 100644 index 8c6785d6689..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/BlowoutZOffsetField.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useState } from 'react' -import { useSelector } from 'react-redux' -import { - DEST_WELL_BLOWOUT_DESTINATION, - SOURCE_WELL_BLOWOUT_DESTINATION, -} from '@opentrons/step-generation' -import { getWellDepth } from '@opentrons/shared-data' -import { - COLORS, - Flex, - Icon, - LegacyTooltip, - useHoverTooltip, -} from '@opentrons/components' -import { getLabwareEntities } from '../../../step-forms/selectors' -import { ZTipPositionModal } from './TipPositionField/ZTipPositionModal' -import type { FieldProps } from '../types' - -interface BlowoutZOffsetFieldProps extends FieldProps { - destLabwareId: unknown - sourceLabwareId?: unknown - blowoutLabwareId?: unknown -} - -export function BlowoutZOffsetField( - props: BlowoutZOffsetFieldProps -): JSX.Element { - const { - disabled, - value, - destLabwareId, - sourceLabwareId, - blowoutLabwareId, - tooltipContent, - name, - updateValue, - } = props - const [isModalOpen, setModalOpen] = useState(false) - const [targetProps, tooltipProps] = useHoverTooltip() - const labwareEntities = useSelector(getLabwareEntities) - - let labwareId = null - if (blowoutLabwareId === SOURCE_WELL_BLOWOUT_DESTINATION) { - labwareId = sourceLabwareId - } else if (blowoutLabwareId === DEST_WELL_BLOWOUT_DESTINATION) { - labwareId = destLabwareId - } - - const labwareWellDepth = - labwareId != null && labwareEntities[String(labwareId)]?.def != null - ? getWellDepth(labwareEntities[String(labwareId)].def, 'A1') - : 0 - - return ( - <> - {tooltipContent} - {isModalOpen ? ( - { - setModalOpen(false) - }} - name={name} - zValue={Number(value)} - updateValue={updateValue} - wellDepthMm={labwareWellDepth} - /> - ) : null} - { - setModalOpen(true) - } - } - id={`BlowoutZOffsetField_${name}`} - data-testid={`BlowoutZOffsetField_${name}`} - > - - - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/ChangeTipField/getDisabledChangeTipOptions.ts b/protocol-designer/src/components/StepEditForm/fields/ChangeTipField/getDisabledChangeTipOptions.ts deleted file mode 100644 index e3032825128..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/ChangeTipField/getDisabledChangeTipOptions.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getWellRatio } from '../../../../steplist/utils' -import type { PathOption, StepType } from '../../../../form-types' -import type { ChangeTipOptions } from '@opentrons/step-generation' -export interface DisabledChangeTipArgs { - aspirateWells?: string[] - dispenseWells?: string[] - stepType?: StepType - path?: PathOption | null | undefined -} -export const getDisabledChangeTipOptions = ( - args: DisabledChangeTipArgs -): Set | null | undefined => { - const { path, aspirateWells, dispenseWells, stepType } = args - - switch (stepType) { - case 'moveLiquid': { - const wellRatio = getWellRatio(aspirateWells, dispenseWells) - - // ensure wells are selected - if (wellRatio != null && path === 'single') { - if (wellRatio === '1:many') { - return new Set(['perSource']) - } - - return new Set(['perDest']) - } - - // path is multi - return new Set(['perSource', 'perDest']) - } - - case 'mix': { - return new Set(['perSource', 'perDest']) - } - - default: { - console.warn( - `getChangeTipOptions for stepType ${String( - stepType - )} not yet implemented!` - ) - return null - } - } -} diff --git a/protocol-designer/src/components/StepEditForm/fields/ChangeTipField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/ChangeTipField/index.tsx deleted file mode 100644 index f367fabcff6..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/ChangeTipField/index.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { - FormGroup, - SelectField, - LegacyTooltip, - useHoverTooltip, - TOOLTIP_FIXED, -} from '@opentrons/components' -import { getDisabledChangeTipOptions } from './getDisabledChangeTipOptions' -import styles from '../../StepEditForm.module.css' -import type { DisabledChangeTipArgs } from './getDisabledChangeTipOptions' -import type { ChangeTipOptions } from '@opentrons/step-generation' -import type { FieldProps } from '../../types' - -const ALL_CHANGE_TIP_VALUES: ChangeTipOptions[] = [ - 'always', - 'once', - 'perSource', - 'perDest', - 'never', -] -type Props = FieldProps & DisabledChangeTipArgs - -export const ChangeTipField = (props: Props): JSX.Element => { - const { - aspirateWells, - dispenseWells, - name, - path, - stepType, - updateValue, - value, - } = props - const { t } = useTranslation('form') - const disabledOptions = getDisabledChangeTipOptions({ - aspirateWells, - dispenseWells, - path, - stepType, - }) - - const options = ALL_CHANGE_TIP_VALUES.map(value => ({ - value, - isDisabled: disabledOptions ? disabledOptions.has(value) : false, - })) - - return ( - - { - updateValue(value) - }} - formatOptionLabel={({ value }) => ( - - )} - /> - - ) -} - -interface LabelProps { - value: string -} - -const ChangeTipOptionLabel = (props: LabelProps): JSX.Element => { - const { value } = props - const { t } = useTranslation('form') - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: 'bottom-start', - strategy: TOOLTIP_FIXED, - }) - return ( - <> -
- {t(`step_edit_form.field.change_tip.option.${value}`)} - -
- {t(`step_edit_form.field.change_tip.option_tooltip.${value}`)} -
-
-
- - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/CheckboxRowField.tsx b/protocol-designer/src/components/StepEditForm/fields/CheckboxRowField.tsx deleted file mode 100644 index 92483f5cb16..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/CheckboxRowField.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import type * as React from 'react' -import { - DeprecatedCheckboxField, - useHoverTooltip, - LegacyTooltip, - TOOLTIP_TOP, -} from '@opentrons/components' -import cx from 'classnames' -import styles from '../StepEditForm.module.css' -import type { FieldProps } from '../types' -import type { Placement } from '@opentrons/components' - -type CheckboxRowProps = FieldProps & { - children?: React.ReactNode - className?: string - label?: string - tooltipContent?: React.ReactNode - tooltipPlacement?: Placement -} - -export const CheckboxRowField = (props: CheckboxRowProps): JSX.Element => { - const { - children, - className, - disabled, - isIndeterminate, - label, - name, - tooltipContent, - updateValue, - value, - tooltipPlacement = TOOLTIP_TOP, - } = props - - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: tooltipPlacement, - }) - return ( - <> - {tooltipContent && ( - - {tooltipContent} - - )} -
- ) => { - updateValue(!value) - }} - value={disabled ? false : Boolean(value)} - /> - {value && !disabled && !isIndeterminate ? children : null} -
- - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/Configure96ChannelField.tsx b/protocol-designer/src/components/StepEditForm/fields/Configure96ChannelField.tsx deleted file mode 100644 index b39ce3c0ac8..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/Configure96ChannelField.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { ALL, COLUMN } from '@opentrons/shared-data' -import { - FormGroup, - SelectField, - LegacyTooltip, - TOOLTIP_FIXED, - useHoverTooltip, -} from '@opentrons/components' -import { getInitialDeckSetup } from '../../../step-forms/selectors' -import styles from '../StepEditForm.module.css' -import type { StepFormDropdown } from './StepFormDropdownField' - -export function Configure96ChannelField( - props: Omit, 'options'> -): JSX.Element { - const { t } = useTranslation('form') - const { value: dropdownItem, name, updateValue } = props - const deckSetup = useSelector(getInitialDeckSetup) - const tipracks = Object.values(deckSetup.labware).filter( - labware => labware.def.parameters.isTiprack - ) - const tipracksNotOnAdapter = tipracks.filter( - tiprack => deckSetup.labware[tiprack.slot] == null - ) - - const options = [ - { name: 'all', value: ALL }, - { - name: 'column', - value: COLUMN, - isDisabled: tipracksNotOnAdapter.length === 0, - }, - ] - - const [selectedValue, setSelectedValue] = React.useState( - dropdownItem || options[0].value - ) - React.useEffect(() => { - updateValue(selectedValue) - }, [selectedValue]) - - return ( - - { - updateValue(value) - setSelectedValue(value) - }} - formatOptionLabel={({ value, isDisabled }) => ( - - )} - /> - - ) -} - -interface OptionLabelProps { - value: string - disabled?: boolean -} - -const OptionLabel = (props: OptionLabelProps): JSX.Element => { - const { value, disabled } = props - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: 'bottom-start', - strategy: TOOLTIP_FIXED, - }) - const { t } = useTranslation('form') - return ( - <> -
- {t(`step_edit_form.field.nozzles.option.${value}`)} - {disabled ? ( - -
- {t(`step_edit_form.field.nozzles.option_tooltip.${value}`)} -
-
- ) : null} -
- - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/DelayFields.tsx b/protocol-designer/src/components/StepEditForm/fields/DelayFields.tsx deleted file mode 100644 index 41fa2e36a4d..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/DelayFields.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { TextField } from './TextField' -import { CheckboxRowField } from './CheckboxRowField' -import { TipPositionField } from './TipPositionField' -import styles from '../StepEditForm.module.css' -import type { FieldPropsByName } from '../types' -import type { StepFieldName } from '../../../form-types' - -export interface DelayFieldProps { - checkboxFieldName: StepFieldName // TODO(IL, 2021-03-03): strictly, could be DelayCheckboxFields! - labwareId?: string | null - propsForFields: FieldPropsByName - secondsFieldName: StepFieldName // TODO(IL, 2021-03-03): strictly, could be DelaySecondFields! - tipPositionFieldName?: StepFieldName // TODO(IL, 2021-03-03): strictly, could be TipOffsetFields! -} - -export const DelayFields = (props: DelayFieldProps): JSX.Element => { - const { - checkboxFieldName, - secondsFieldName, - tipPositionFieldName, - propsForFields, - labwareId, - } = props - const { t } = useTranslation(['form', 'application']) - return ( - - - {tipPositionFieldName && ( - - )} - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/DisposalVolumeField.tsx b/protocol-designer/src/components/StepEditForm/fields/DisposalVolumeField.tsx deleted file mode 100644 index 0363181edc2..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/DisposalVolumeField.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import type * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import cx from 'classnames' - -import { - FormGroup, - DeprecatedCheckboxField, - DropdownField, - Flex, - DIRECTION_COLUMN, - SPACING, -} from '@opentrons/components' -import { getMaxDisposalVolumeForMultidispense } from '../../../steplist/formLevel/handleFormChange/utils' -import { selectors as stepFormSelectors } from '../../../step-forms' -import { selectors as uiLabwareSelectors } from '../../../ui/labware' -import { getBlowoutLocationOptionsForForm } from '../utils' -import { TextField } from './TextField' -import { FlowRateField } from './FlowRateField' -import { BlowoutZOffsetField } from './BlowoutZOffsetField' - -import type { Options } from '@opentrons/components' -import type { PathOption, StepType } from '../../../form-types' -import type { FieldProps, FieldPropsByName } from '../types' - -import styles from '../StepEditForm.module.css' - -interface DropdownFormFieldProps extends FieldProps { - options: Options - className?: string -} -const DropdownFormField = (props: DropdownFormFieldProps): JSX.Element => { - return ( - { - props.updateValue(e.currentTarget.value) - }} - onFocus={props.onFieldFocus} - /> - ) -} -interface DisposalVolumeFieldProps { - path: PathOption - pipette: string | null - propsForFields: FieldPropsByName - stepType: StepType - volume: string | null - aspirate_airGap_checkbox?: boolean | null - aspirate_airGap_volume?: string | null - tipRack?: string | null -} - -export const DisposalVolumeField = ( - props: DisposalVolumeFieldProps -): JSX.Element => { - const { - path, - stepType, - volume, - pipette, - propsForFields, - aspirate_airGap_checkbox, - aspirate_airGap_volume, - tipRack, - } = props - const { t } = useTranslation(['application', 'form']) - - const disposalOptions = useSelector(uiLabwareSelectors.getDisposalOptions) - const pipetteEntities = useSelector(stepFormSelectors.getPipetteEntities) - const blowoutLocationOptions = getBlowoutLocationOptionsForForm({ - path, - stepType, - }) - const maxDisposalVolume = getMaxDisposalVolumeForMultidispense( - { - aspirate_airGap_checkbox, - aspirate_airGap_volume, - path, - pipette, - volume, - tipRack, - }, - pipetteEntities - ) - const disposalDestinationOptions = [ - ...disposalOptions, - ...blowoutLocationOptions, - ] - - const volumeBoundsCaption = - maxDisposalVolume != null - ? `max ${maxDisposalVolume} ${t('units.microliter')}` - : null - - const volumeField = ( -
- -
- ) - - const { value, updateValue } = propsForFields.disposalVolume_checkbox - return ( - - <> -
- ) => { - updateValue(!value) - }} - /> - {value ? volumeField : null} -
- {value ? ( -
-
{t('blowout')}
- - - - - -
- ) : null} - -
- ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/DropTipField/__tests__/DropTipField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/DropTipField/__tests__/DropTipField.test.tsx deleted file mode 100644 index 195096f0c07..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/DropTipField/__tests__/DropTipField.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import type * as React from 'react' -import { screen } from '@testing-library/react' -import { describe, it, vi, beforeEach, expect } from 'vitest' -import { fixtureTiprack1000ul } from '@opentrons/shared-data' - -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../assets/localization' -import { getAllTiprackOptions } from '../../../../../ui/labware/selectors' -import { getEnableReturnTip } from '../../../../../feature-flags/selectors' -import { - getAdditionalEquipmentEntities, - getLabwareEntities, -} from '../../../../../step-forms/selectors' -import { DropTipField } from '../index' -import type { LabwareDefinition2 } from '@opentrons/shared-data' - -vi.mock('../../../../../step-forms/selectors') -vi.mock('../../../../../ui/labware/selectors') -vi.mock('../../../../../feature-flags/selectors') -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} -const mockDropTip = 'dropTip_location' -const mockTrashBin = 'trashBinId' -const mockLabwareId = 'mockId' -describe('DropTipField', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - name: mockDropTip, - value: mockTrashBin, - updateValue: vi.fn(), - onFieldBlur: vi.fn(), - onFieldFocus: vi.fn(), - disabled: false, - } - - vi.mocked(getAdditionalEquipmentEntities).mockReturnValue({ - [mockTrashBin]: { name: 'trashBin', location: 'A3', id: mockTrashBin }, - }) - vi.mocked(getEnableReturnTip).mockReturnValue(true) - vi.mocked(getAllTiprackOptions).mockReturnValue([ - { name: 'mock tip', value: mockLabwareId }, - ]) - vi.mocked(getLabwareEntities).mockReturnValue({ - [mockLabwareId]: { - id: mockLabwareId, - labwareDefURI: 'mock uri', - def: fixtureTiprack1000ul as LabwareDefinition2, - }, - }) - }) - it('renders the label and dropdown field with trash bin selected as default', () => { - render(props) - screen.getByText('Tip drop location') - screen.getByRole('combobox', { name: '' }) - screen.getByRole('option', { name: 'Trash Bin' }) - screen.getByRole('option', { name: 'mock tip' }) - }) - it('renders dropdown as disabled', () => { - props.disabled = true - render(props) - expect(screen.getByRole('combobox', { name: '' })).toBeDisabled() - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/DropTipField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/DropTipField/index.tsx deleted file mode 100644 index a17e1804576..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/DropTipField/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { DropdownField, FormGroup } from '@opentrons/components' -import { - getAdditionalEquipmentEntities, - getLabwareEntities, -} from '../../../../step-forms/selectors' -import { getAllTiprackOptions } from '../../../../ui/labware/selectors' -import { getEnableReturnTip } from '../../../../feature-flags/selectors' -import type { DropdownOption } from '@opentrons/components' -import type { StepFormDropdown } from '../StepFormDropdownField' - -import styles from '../../StepEditForm.module.css' - -export function DropTipField( - props: Omit, 'options'> & {} -): JSX.Element { - const { - value: dropdownItem, - name, - onFieldBlur, - onFieldFocus, - updateValue, - disabled, - } = props - const { t } = useTranslation('form') - const additionalEquipment = useSelector(getAdditionalEquipmentEntities) - const labwareEntities = useSelector(getLabwareEntities) - const tiprackOptions = useSelector(getAllTiprackOptions) - const enableReturnTip = useSelector(getEnableReturnTip) - - const wasteChute = Object.values(additionalEquipment).find( - aE => aE.name === 'wasteChute' - ) - const trashBin = Object.values(additionalEquipment).find( - aE => aE.name === 'trashBin' - ) - const wasteChuteOption: DropdownOption = { - name: 'Waste Chute', - value: wasteChute?.id ?? '', - } - const trashOption: DropdownOption = { - name: 'Trash Bin', - value: trashBin?.id ?? '', - } - - const options: DropdownOption[] = [] - if (wasteChute != null) options.push(wasteChuteOption) - if (trashBin != null) options.push(trashOption) - - React.useEffect(() => { - if ( - additionalEquipment[String(dropdownItem)] == null && - labwareEntities[String(dropdownItem)] == null - ) { - updateValue(null) - } - }, [dropdownItem]) - - return ( - - ) => { - updateValue(e.currentTarget.value) - }} - /> - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/FlowRateInput.module.css b/protocol-designer/src/components/StepEditForm/fields/FlowRateField/FlowRateInput.module.css deleted file mode 100644 index a809deff4a1..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/FlowRateInput.module.css +++ /dev/null @@ -1,19 +0,0 @@ -@import '@opentrons/components/styles'; - -.description { - padding: 2rem 0; -} - -/* TODO: Ian 2018-08-24 use some `title` prop of a yet-to-be-built modal component - (AlertModal gets us 90% there) */ -.header { - font-size: var(--fs-header); /* from legacy --font-header-dark */ - font-weight: var(--fw-semibold); /* from legacy --font-header-dark */ - color: var(--c-font-dark); /* from legacy --font-header-dark */ -} - -.flow_rate_type_label { - font-weight: var(--fw-semibold); - text-transform: capitalize; - margin-bottom: 1rem; -} diff --git a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/FlowRateInput.tsx b/protocol-designer/src/components/StepEditForm/fields/FlowRateField/FlowRateInput.tsx deleted file mode 100644 index ef54c68f68e..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/FlowRateInput.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import round from 'lodash/round' -import { useTranslation } from 'react-i18next' -import { - AlertModal, - FormGroup, - RadioGroup, - LegacyInputField, - Flex, - useHoverTooltip, - LegacyTooltip, -} from '@opentrons/components' -import { getMainPagePortalEl } from '../../../portals/MainPageModalPortal' -import type { FieldProps } from '../../types' - -import modalStyles from '../../../modals/modal.module.css' -import stepFormStyles from '../../StepEditForm.module.css' -import styles from './FlowRateInput.module.css' - -const DECIMALS_ALLOWED = 1 - -/** When flow rate is falsey (including 0), it means 'use default' */ -export interface FlowRateInputProps extends FieldProps { - defaultFlowRate?: number | null - flowRateType: 'aspirate' | 'dispense' | 'blowout' - label?: string | null - minFlowRate: number - maxFlowRate: number - pipetteDisplayName?: string | null - className?: string -} - -interface State { - isPristine: boolean - modalFlowRate?: string | null - modalUseDefault: boolean - showModal: boolean -} - -export const FlowRateInput = (props: FlowRateInputProps): JSX.Element => { - const { - className, - defaultFlowRate, - disabled, - flowRateType, - isIndeterminate, - label, - maxFlowRate, - minFlowRate, - name, - pipetteDisplayName, - tooltipContent, - } = props - const [targetProps, tooltipProps] = useHoverTooltip() - const { t } = useTranslation(['form', 'application', 'shared']) - const DEFAULT_LABEL = t('step_edit_form.field.flow_rate.label') - - const initialState: State = { - isPristine: true, - modalFlowRate: props.value ? String(props.value) : null, - modalUseDefault: !props.value && !isIndeterminate, - showModal: false, - } - - const [isPristine, setIsPristine] = React.useState( - initialState.isPristine - ) - - const [modalFlowRate, setModalFlowRate] = React.useState< - State['modalFlowRate'] - >(initialState.modalFlowRate) - - const [modalUseDefault, setModalUseDefault] = React.useState< - State['modalUseDefault'] - >(initialState.modalUseDefault) - - const [showModal, setShowModal] = React.useState( - initialState.showModal - ) - - const resetModalState = (): void => { - setShowModal(initialState.showModal) - setModalFlowRate(initialState.modalFlowRate) - setModalUseDefault(initialState.modalUseDefault) - setIsPristine(initialState.isPristine) - } - - const cancelModal = resetModalState - - const openModal = (): void => { - setShowModal(true) - } - - const makeSaveModal = (allowSave: boolean) => (): void => { - setIsPristine(false) - - if (allowSave) { - const newFlowRate = modalUseDefault ? null : Number(modalFlowRate) - props.updateValue(newFlowRate) - resetModalState() - } - } - - const handleChangeRadio = (e: React.ChangeEvent): void => { - setModalUseDefault(e.target.value !== 'custom') - } - - const handleChangeNumber = (e: React.ChangeEvent): void => { - const value = e.target.value - if (value === '' || value === '.' || !Number.isNaN(Number(value))) { - setModalFlowRate(value) - setModalUseDefault(false) - } - } - - const modalFlowRateNum = Number(modalFlowRate) - - // show 0.1 not 0 as minimum, since bottom of range is non-inclusive - const displayMinFlowRate = minFlowRate || Math.pow(10, -DECIMALS_ALLOWED) - const rangeDescription = t('step_edit_form.field.flow_rate.range', { - min: displayMinFlowRate, - max: maxFlowRate, - }) - const outOfBounds = - modalFlowRateNum === 0 || - minFlowRate > modalFlowRateNum || - modalFlowRateNum > maxFlowRate - const correctDecimals = - round(modalFlowRateNum, DECIMALS_ALLOWED) === modalFlowRateNum - const allowSave = modalUseDefault || (!outOfBounds && correctDecimals) - - let errorMessage = null - // validation only happens when "Custom" is selected, not "Default" - // and pristinity only masks the outOfBounds error, not the correctDecimals error - if (!modalUseDefault) { - if (!Number.isNaN(modalFlowRateNum) && !correctDecimals) { - errorMessage = t('step_edit_form.field.flow_rate.error_decimals', { - decimals: `${DECIMALS_ALLOWED}`, - }) - } else if (!isPristine && outOfBounds) { - errorMessage = t('step_edit_form.field.flow_rate.error_out_of_bounds', { - min: displayMinFlowRate, - max: maxFlowRate, - }) - } - } - - const FlowRateInputField = ( - - ) - - const FlowRateModal = - pipetteDisplayName && - createPortal( - -

{DEFAULT_LABEL}

- -
- {t('step_edit_form.field.flow_rate.default_text', { - displayName: pipetteDisplayName, - })} -
- -
- {`${flowRateType} speed`} -
- - -
, - getMainPagePortalEl() - ) - - return ( - <> - {flowRateType === 'blowout' ? ( - - - {tooltipContent} - - ) : ( - - - - )} - - {showModal && FlowRateModal} - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/__tests__/FlowRateField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/FlowRateField/__tests__/FlowRateField.test.tsx deleted file mode 100644 index f1420438aa9..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/__tests__/FlowRateField.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import type * as React from 'react' -import '@testing-library/jest-dom/vitest' -import { describe, it, vi, beforeEach, expect } from 'vitest' -import { fireEvent, screen } from '@testing-library/react' -import { fixtureP100096V2Specs } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../assets/localization' -import { getPipetteEntities } from '../../../../../step-forms/selectors' -import { FlowRateField } from '../index' - -vi.mock('../../../../../step-forms/selectors') -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} -const mockMockId = 'mockId' -describe('FlowRateField', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - disabled: false, - flowRateType: 'aspirate', - volume: 100, - value: null, - name: 'flowRate', - tiprack: 'tipRack:opentrons_flex_96_tiprack_1000ul', - updateValue: vi.fn(), - onFieldBlur: vi.fn(), - onFieldFocus: vi.fn(), - pipetteId: mockMockId, - } - vi.mocked(getPipetteEntities).mockReturnValue({ - [mockMockId]: { - name: 'p50_single_flex', - spec: { - liquids: fixtureP100096V2Specs.liquids, - displayName: 'mockPipDisplayName', - } as any, - id: mockMockId, - tiprackLabwareDef: [ - { - parameters: { - loadName: 'opentrons_flex_96_tiprack_1000ul', - tipLength: 1000, - }, - metadata: { displayName: 'mockDisplayName' }, - } as any, - ], - tiprackDefURI: ['mockDefURI1', 'mockDefURI2'], - }, - }) - }) - it('renders the flowRateInput and clicking on it opens the modal with all the text', () => { - render(props) - screen.getByText('Flow Rate') - fireEvent.click(screen.getByRole('textbox')) - screen.getByText( - 'The default mockPipDisplayName flow rate is optimal for handling aqueous liquids' - ) - screen.getByText('aspirate speed') - screen.getByText('160 μL/s (default)') - screen.getByText('Custom') - screen.getByText('between 0.1 and Infinity') - screen.getByText('Cancel') - screen.getByText('Done') - }) - it('renders the information for blowout field', () => { - props.flowRateType = 'blowout' - render(props) - expect(screen.queryByText('Flow Rate')).not.toBeInTheDocument() - fireEvent.click(screen.getByRole('textbox')) - screen.getByText( - 'The default mockPipDisplayName flow rate is optimal for handling aqueous liquids' - ) - screen.getByText('blowout speed') - screen.getByText('80 μL/s (default)') - screen.getByText('Custom') - screen.getByText('between 0.1 and Infinity') - screen.getByText('Cancel') - screen.getByText('Done') - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/FlowRateField/index.tsx deleted file mode 100644 index 9f489d5bcf0..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/FlowRateField/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { FlowRateInput } from './FlowRateInput' -import { useSelector } from 'react-redux' -import { selectors as stepFormSelectors } from '../../../../step-forms' -import { getMatchingTipLiquidSpecs } from '../../../../utils' -import type { FieldProps } from '../../types' -import type { FlowRateInputProps } from './FlowRateInput' - -interface FlowRateFieldProps extends FieldProps { - flowRateType: FlowRateInputProps['flowRateType'] - volume: unknown - tiprack: unknown - pipetteId?: string | null - className?: FlowRateInputProps['className'] - label?: FlowRateInputProps['label'] -} - -export function FlowRateField(props: FlowRateFieldProps): JSX.Element { - const { - pipetteId, - flowRateType, - value, - volume, - tiprack, - name, - ...passThruProps - } = props - const pipetteEntities = useSelector(stepFormSelectors.getPipetteEntities) - const pipette = pipetteId != null ? pipetteEntities[pipetteId] : null - const pipetteDisplayName = pipette ? pipette.spec.displayName : 'pipette' - const innerKey = `${name}:${String(value || 0)}` - const matchingTipLiquidSpecs = - pipette != null - ? getMatchingTipLiquidSpecs(pipette, volume as number, tiprack as string) - : null - - let defaultFlowRate - if (pipette) { - if (flowRateType === 'aspirate') { - defaultFlowRate = - matchingTipLiquidSpecs?.defaultAspirateFlowRate.default ?? 0 - } else if (flowRateType === 'dispense') { - defaultFlowRate = - matchingTipLiquidSpecs?.defaultDispenseFlowRate.default ?? 0 - } else if (flowRateType === 'blowout') { - defaultFlowRate = - matchingTipLiquidSpecs?.defaultBlowOutFlowRate.default ?? 0 - } - } - return ( - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/LabwareField.tsx b/protocol-designer/src/components/StepEditForm/fields/LabwareField.tsx deleted file mode 100644 index 7a0e1a06efe..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/LabwareField.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useSelector } from 'react-redux' -import { - getDisposalOptions, - getLabwareOptions, -} from '../../../ui/labware/selectors' -import { StepFormDropdown } from './StepFormDropdownField' -import type { FieldProps } from '../types' - -export const LabwareField = (props: FieldProps): JSX.Element => { - const disposalOptions = useSelector(getDisposalOptions) - const options = useSelector(getLabwareOptions) - const allOptions = - props.name === 'dispense_labware' - ? [...options, ...disposalOptions] - : [...options] - - return -} diff --git a/protocol-designer/src/components/StepEditForm/fields/LabwareLocationField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/LabwareLocationField/index.tsx deleted file mode 100644 index 1d38ad8d0a6..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/LabwareLocationField/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import type * as React from 'react' -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { - WASTE_CHUTE_CUTOUT, - getModuleDisplayName, -} from '@opentrons/shared-data' -import { - getAdditionalEquipmentEntities, - getLabwareEntities, - getModuleEntities, -} from '../../../../step-forms/selectors' -import { - getRobotStateAtActiveItem, - getUnoccupiedLabwareLocationOptions, -} from '../../../../top-selectors/labware-locations' -import { StepFormDropdown } from '../StepFormDropdownField' -import { getHasWasteChute } from '../../../labware' - -export function LabwareLocationField( - props: Omit, 'options'> & { - useGripper: boolean - } & { canSave: boolean } & { labware: string } -): JSX.Element { - const { labware, useGripper, value } = props - const { t } = useTranslation('form') - const additionalEquipmentEntities = useSelector( - getAdditionalEquipmentEntities - ) - const labwareEntities = useSelector(getLabwareEntities) - const robotState = useSelector(getRobotStateAtActiveItem) - const moduleEntities = useSelector(getModuleEntities) - const isLabwareOffDeck = - labware != null ? robotState?.labware[labware]?.slot === 'offDeck' : false - - let unoccupiedLabwareLocationsOptions = - useSelector(getUnoccupiedLabwareLocationOptions) ?? [] - - if (useGripper || isLabwareOffDeck) { - unoccupiedLabwareLocationsOptions = unoccupiedLabwareLocationsOptions.filter( - option => option.value !== 'offDeck' - ) - } - - if (!useGripper && getHasWasteChute(additionalEquipmentEntities)) { - unoccupiedLabwareLocationsOptions = unoccupiedLabwareLocationsOptions.filter( - option => option.value !== WASTE_CHUTE_CUTOUT - ) - } - - const location: string = value as string - - const bothFieldsSelected = labware != null && value != null - const labwareDisplayName = - labware != null ? labwareEntities[labware]?.def.metadata.displayName : '' - let locationString = `slot ${location}` - if (location != null) { - if (robotState?.modules[location] != null) { - const moduleSlot = robotState?.modules[location].slot ?? '' - locationString = `${getModuleDisplayName( - moduleEntities[location].model - )} in slot ${moduleSlot}` - } else if (robotState?.labware[location] != null) { - const adapterSlot = robotState?.labware[location].slot - locationString = - robotState?.modules[adapterSlot] != null - ? `${getModuleDisplayName( - moduleEntities[adapterSlot].model - )} in slot ${robotState?.modules[adapterSlot].slot}` - : `slot ${robotState?.labware[location].slot}` ?? '' - } - } - return ( - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/MixFields.tsx b/protocol-designer/src/components/StepEditForm/fields/MixFields.tsx deleted file mode 100644 index 623227b6c9b..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/MixFields.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { CheckboxRowField, TextField } from './' -import styles from '../StepEditForm.module.css' -import type { FieldPropsByName } from '../types' - -export const MixFields = (props: { - propsForFields: FieldPropsByName - checkboxFieldName: string - volumeFieldName: string - timesFieldName: string -}): JSX.Element => { - const { t } = useTranslation(['form', 'application']) - const { - propsForFields, - checkboxFieldName, - volumeFieldName, - timesFieldName, - } = props - - return ( - - - - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/MoveLabwareField.tsx b/protocol-designer/src/components/StepEditForm/fields/MoveLabwareField.tsx deleted file mode 100644 index 137ce6de586..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/MoveLabwareField.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useSelector } from 'react-redux' -import { getMoveLabwareOptions } from '../../../ui/labware/selectors' -import { StepFormDropdown } from './StepFormDropdownField' -import type { FieldProps } from '../types' - -export function MoveLabwareField(props: FieldProps): JSX.Element { - const options = useSelector(getMoveLabwareOptions) - return -} diff --git a/protocol-designer/src/components/StepEditForm/fields/PathField/PathField.tsx b/protocol-designer/src/components/StepEditForm/fields/PathField/PathField.tsx deleted file mode 100644 index 5710963c4b4..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/PathField/PathField.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import type * as React from 'react' -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import cx from 'classnames' -import { - FormGroup, - LegacyTooltip, - useHoverTooltip, -} from '@opentrons/components' -import { selectors as stepFormSelectors } from '../../../../step-forms' -import SINGLE_IMAGE from '../../../../assets/images/path_single_transfers.svg' -import MULTI_DISPENSE_IMAGE from '../../../../assets/images/path_multi_dispense.svg' -import MULTI_ASPIRATE_IMAGE from '../../../../assets/images/path_multi_aspirate.svg' -import { getDisabledPathMap } from './getDisabledPathMap' -import styles from '../../StepEditForm.module.css' -import type { PathOption } from '../../../../form-types' -import type { FieldProps } from '../../types' -import type { DisabledPathMap, ValuesForPath } from './getDisabledPathMap' - -const PATH_ANIMATION_IMAGES = { - single: new URL('../../../../assets/images/path_single.gif', import.meta.url) - .href, - multiAspirate: new URL( - '../../../../assets/images/path_multiAspirate.gif', - import.meta.url - ).href, - multiDispense: new URL( - '../../../../assets/images/path_multiDispense.gif', - import.meta.url - ).href, -} - -const ALL_PATH_OPTIONS: Array<{ name: PathOption; image: string }> = [ - { - name: 'single', - image: SINGLE_IMAGE, - }, - { - name: 'multiAspirate', - image: MULTI_ASPIRATE_IMAGE, - }, - { - name: 'multiDispense', - image: MULTI_DISPENSE_IMAGE, - }, -] - -type PathFieldProps = FieldProps & ValuesForPath - -interface ButtonProps { - children?: React.ReactNode - disabled: boolean - id?: string - selected: boolean - subtitle: string - onClick: (e: React.MouseEvent) => unknown - path: PathOption -} - -const PathButton = (buttonProps: ButtonProps): JSX.Element => { - const { - children, - disabled, - onClick, - id, - path, - selected, - subtitle, - } = buttonProps - const [targetProps, tooltipProps] = useHoverTooltip() - const { t } = useTranslation('form') - const tooltip = ( - -
- {t(`step_edit_form.field.path.title.${path}`)} -
- -
{subtitle}
-
- ) - - const pathButtonData = `PathButton_${selected ? 'selected' : 'deselected'}_${ - disabled ? 'disabled' : 'enabled' - }` - - return ( - <> - {tooltip} -
  • - {children} -
  • - - ) -} - -const getSubtitle = ( - path: PathOption, - disabledPathMap: DisabledPathMap -): string => { - const reasonForDisabled = disabledPathMap && disabledPathMap[path] - return reasonForDisabled || '' -} - -export const PathField = (props: PathFieldProps): JSX.Element => { - const { - aspirate_airGap_checkbox, - aspirate_airGap_volume, - aspirate_wells, - changeTip, - dispense_wells, - pipette, - volume, - value, - updateValue, - tipRack, - } = props - const { t } = useTranslation('form') - const pipetteEntities = useSelector(stepFormSelectors.getPipetteEntities) - const disabledPathMap = getDisabledPathMap( - { - aspirate_airGap_checkbox, - aspirate_airGap_volume, - aspirate_wells, - changeTip, - dispense_wells, - pipette, - volume, - tipRack, - }, - pipetteEntities, - t - ) - return ( - -
      - {ALL_PATH_OPTIONS.map(option => ( - { - updateValue(option.name) - }} - > - - - ))} -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/PathField/getDisabledPathMap.ts b/protocol-designer/src/components/StepEditForm/fields/PathField/getDisabledPathMap.ts deleted file mode 100644 index cce8162be10..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/PathField/getDisabledPathMap.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { getWellRatio } from '../../../../steplist/utils' -import { getPipetteCapacity } from '../../../../pipettes/pipetteData' -import { - volumeInCapacityForMultiDispense, - volumeInCapacityForMultiAspirate, -} from '../../../../steplist/formLevel/handleFormChange/utils' -import type { - ChangeTipOptions, - PipetteEntities, -} from '@opentrons/step-generation' -import type { PathOption } from '../../../../form-types' -export type DisabledPathMap = Partial> | null -export interface ValuesForPath { - aspirate_airGap_checkbox?: boolean | null - aspirate_airGap_volume?: string | null - aspirate_wells?: string[] | null - changeTip: ChangeTipOptions - dispense_wells?: string[] | null - pipette?: string | null - volume?: string | null - tipRack?: string | null -} -export function getDisabledPathMap( - values: ValuesForPath, - pipetteEntities: PipetteEntities, - t: any -): DisabledPathMap { - const { - aspirate_airGap_checkbox, - aspirate_wells, - changeTip, - dispense_wells, - pipette, - tipRack, - } = values - if (!pipette) return null - const wellRatio = getWellRatio(aspirate_wells, dispense_wells) - let disabledPathMap: Partial> = {} - - // changeTip is lowest priority disable reasoning - if (changeTip === 'perDest') { - disabledPathMap = { - ...disabledPathMap, - multiDispense: t( - 'step_edit_form.field.path.subtitle.incompatible_with_per_dest' - ), - } - } else if (changeTip === 'perSource') { - disabledPathMap = { - ...disabledPathMap, - multiAspirate: t( - 'step_edit_form.field.path.subtitle.incompatible_with_per_source' - ), - } - } - - // transfer volume overwrites change tip disable reasoning - const pipetteEntity = pipetteEntities[pipette] - const pipetteCapacity = - pipetteEntity && getPipetteCapacity(pipetteEntity, tipRack) - const volume = Number(values.volume) - const airGapChecked = aspirate_airGap_checkbox - let airGapVolume = airGapChecked ? Number(values.aspirate_airGap_volume) : 0 - airGapVolume = Number.isFinite(airGapVolume) ? airGapVolume : 0 - const withinCapacityForMultiDispense = volumeInCapacityForMultiDispense({ - volume, - pipetteCapacity, - airGapVolume, - }) - const withinCapacityForMultiAspirate = volumeInCapacityForMultiAspirate({ - volume, - pipetteCapacity, - airGapVolume, - }) - - if (!withinCapacityForMultiDispense) { - disabledPathMap = { - ...disabledPathMap, - multiDispense: t('step_edit_form.field.path.subtitle.volume_too_high'), - } - } - - if (!withinCapacityForMultiAspirate) { - disabledPathMap = { - ...disabledPathMap, - multiAspirate: t('step_edit_form.field.path.subtitle.volume_too_high'), - } - } - - // wellRatio overwrites all other disable reasoning - if (wellRatio === '1:many') { - disabledPathMap = { - ...disabledPathMap, - multiAspirate: t('step_edit_form.field.path.subtitle.only_many_to_1'), - } - } else if (wellRatio === 'many:1') { - disabledPathMap = { - ...disabledPathMap, - multiDispense: t('step_edit_form.field.path.subtitle.only_1_to_many'), - } - } else { - disabledPathMap = { - ...disabledPathMap, - multiAspirate: t('step_edit_form.field.path.subtitle.only_many_to_1'), - multiDispense: t('step_edit_form.field.path.subtitle.only_1_to_many'), - } - } - return disabledPathMap -} diff --git a/protocol-designer/src/components/StepEditForm/fields/PickUpTipField/_tests__/PickUpTipField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/PickUpTipField/_tests__/PickUpTipField.test.tsx deleted file mode 100644 index eba32213728..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/PickUpTipField/_tests__/PickUpTipField.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type * as React from 'react' -import { screen } from '@testing-library/react' -import { describe, it, vi, beforeEach, expect } from 'vitest' - -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../assets/localization' -import { getAllTiprackOptions } from '../../../../../ui/labware/selectors' -import { PickUpTipField } from '../index' - -vi.mock('../../../../../ui/labware/selectors') -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} -const mockPickUpTip = 'pickUpTip_location' -const mockLabwareId = 'mockId' -describe('PickUpTipField', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - name: mockPickUpTip, - value: '', - updateValue: vi.fn(), - onFieldBlur: vi.fn(), - onFieldFocus: vi.fn(), - disabled: false, - } - vi.mocked(getAllTiprackOptions).mockReturnValue([ - { name: 'mock tip', value: mockLabwareId }, - ]) - }) - it('renders the label and dropdown field with default pick up tip selected as default', () => { - render(props) - screen.getByText('pick up tip') - screen.getByRole('combobox', { name: '' }) - screen.getByRole('option', { name: 'Default - get next tip' }) - screen.getByRole('option', { name: 'mock tip' }) - }) - it('renders dropdown as disabled', () => { - props.disabled = true - render(props) - expect(screen.getByRole('combobox', { name: '' })).toBeDisabled() - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/PickUpTipField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/PickUpTipField/index.tsx deleted file mode 100644 index f9b89679ac0..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/PickUpTipField/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { DropdownField, FormGroup } from '@opentrons/components' -import { getAllTiprackOptions } from '../../../../ui/labware/selectors' -import type { DropdownOption } from '@opentrons/components' -import type { StepFormDropdown } from '../StepFormDropdownField' - -import styles from '../../StepEditForm.module.css' - -export function PickUpTipField( - props: Omit, 'options'> & {} -): JSX.Element { - const { - value: dropdownItem, - name, - onFieldBlur, - onFieldFocus, - updateValue, - disabled, - } = props - const { t } = useTranslation('form') - const tiprackOptions = useSelector(getAllTiprackOptions) - const defaultOption: DropdownOption = { - name: 'Default - get next tip', - value: '', - } - - return ( - - ) => { - updateValue(e.currentTarget.value) - }} - /> - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/PipetteField.tsx b/protocol-designer/src/components/StepEditForm/fields/PipetteField.tsx deleted file mode 100644 index 10b569a5f23..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/PipetteField.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { FormGroup, DropdownField } from '@opentrons/components' -import { selectors as stepFormSelectors } from '../../../step-forms' -import styles from '../StepEditForm.module.css' -import type { FieldProps } from '../types' - -export const PipetteField = (props: FieldProps): JSX.Element => { - const { onFieldBlur, onFieldFocus, updateValue, value } = props - const pipetteOptions = useSelector( - stepFormSelectors.getEquippedPipetteOptions - ) - - const { t } = useTranslation('form') - return ( - - ) => { - updateValue(e.currentTarget.value) - }} - /> - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/ProfileItemRows.tsx b/protocol-designer/src/components/StepEditForm/fields/ProfileItemRows.tsx deleted file mode 100644 index be535bd0283..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/ProfileItemRows.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import type * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useDispatch } from 'react-redux' -import cx from 'classnames' -import { - Icon, - LegacyInputField, - OutlineButton, - LegacyTooltip, - useConditionalConfirm, - useHoverTooltip, - TOOLTIP_TOP, - TOOLTIP_TOP_END, -} from '@opentrons/components' -import * as steplistActions from '../../../steplist/actions' -import { PROFILE_CYCLE } from '../../../form-types' -import { - getProfileFieldErrors, - maskProfileField, -} from '../../../steplist/fieldLevel' -import { - ConfirmDeleteModal, - DELETE_PROFILE_CYCLE, -} from '../../modals/ConfirmDeleteModal' -import { getDynamicFieldFocusHandlerId } from '../utils' -import styles from '../StepEditForm.module.css' - -import type { - ProfileStepItem, - ProfileItem, - ProfileCycleItem, -} from '../../../form-types' -import type { FocusHandlers } from '../types' - -export const showProfileFieldErrors = ({ - fieldId, - focusedField, - dirtyFields, -}: { - fieldId: string - focusedField?: string | null - dirtyFields: string[] -}): boolean => - !(fieldId === focusedField) && dirtyFields && dirtyFields.includes(fieldId) - -interface ProfileCycleRowProps { - cycleItem: ProfileCycleItem - focusHandlers: FocusHandlers - stepOffset: number -} -export const ProfileCycleRow = (props: ProfileCycleRowProps): JSX.Element => { - const { cycleItem, focusHandlers, stepOffset } = props - const dispatch = useDispatch() - const { t } = useTranslation(['tooltip', 'application']) - const addStepToCycle = (): void => { - dispatch(steplistActions.addProfileStep({ cycleId: cycleItem.id })) - } - - // TODO IMMEDIATELY make conditional - const deleteProfileCycle = (): steplistActions.DeleteProfileCycleAction => - dispatch(steplistActions.deleteProfileCycle({ id: cycleItem.id })) - - const [ - addStepToCycleTargetProps, - addStepToCycleTooltipProps, - ] = useHoverTooltip({ - placement: TOOLTIP_TOP_END, - }) - const { - confirm: confirmDeleteCycle, - showConfirmation: showConfirmDeleteCycle, - cancel: cancelConfirmDeleteCycle, - } = useConditionalConfirm(deleteProfileCycle, true) - - const [deleteCycleTargetProps, deleteCycleTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - }) - - return ( - <> - {showConfirmDeleteCycle && ( - - )} - -
    -
    - {cycleItem.steps.length > 0 && ( -
    -
    - {cycleItem.steps.map((stepItem, index) => { - return ( - - ) - })} -
    - - - dispatch( - steplistActions.editProfileCycle({ - id: cycleItem.id, - fields: { [name]: value }, - }) - ) - } - /> -
    - )} - - {t('profile.add_step_to_cycle')} - -
    - + Step -
    -
    -
    - - {t('profile.delete_cycle')} - - -
    -
    - - ) -} - -export interface ProfileItemRowsProps { - focusHandlers: FocusHandlers - orderedProfileItems: string[] - profileItemsById: { - [key: string]: ProfileItem - } -} - -export const ProfileItemRows = (props: ProfileItemRowsProps): JSX.Element => { - const { orderedProfileItems, profileItemsById } = props - const { t } = useTranslation(['tooltip', 'form']) - const dispatch = useDispatch() - const addProfileCycle = (): void => { - dispatch(steplistActions.addProfileCycle(null)) - } - const addProfileStep = (): void => { - dispatch(steplistActions.addProfileStep(null)) - } - - const [addCycleTargetProps, addCycleTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - }) - const [addStepTargetProps, addStepTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - }) - - let counter = 0 - - const rows = orderedProfileItems.map((itemId, index) => { - const itemFields: ProfileItem = profileItemsById[itemId] - - if (itemFields.type === PROFILE_CYCLE) { - const cycleRow = ( - - ) - - counter += itemFields.steps.length - - return cycleRow - } - counter++ - return ( - - ) - }) - - return ( - <> - {rows.length > 0 && ( -
    -
    Name:
    -
    Temperature:
    -
    Time:
    -
    - )} - {rows} - - {t('profile.add_step')} - - - {t('profile.add_cycle')} - -
    - - {t('form:step_edit_form.field.thermocyclerProfile.add_step_button')} - - - {t('form:step_edit_form.field.thermocyclerProfile.add_cycle_button')} - -
    - - ) -} - -interface ProfileFieldProps { - name: string - focusHandlers: FocusHandlers - profileItem: ProfileItem - units?: React.ReactNode - className?: string - updateValue: (name: string, value: unknown) => unknown -} -const ProfileField = (props: ProfileFieldProps): JSX.Element => { - const { - focusHandlers, - name, - profileItem, - units, - className, - updateValue, - } = props - const value = profileItem[name as keyof ProfileItem] // this is not very safe but I don't know how else to tell TS that name should be keyof ProfileItem without being a discriminated union - const fieldId = getDynamicFieldFocusHandlerId({ - id: profileItem.id, - name, - }) - - const onChange = (e: React.ChangeEvent): void => { - const value = e.currentTarget.value - const maskedValue = maskProfileField(name, value) - updateValue(name, maskedValue) - } - - const showErrors = showProfileFieldErrors({ - fieldId, - focusedField: focusHandlers.focusedField, - dirtyFields: focusHandlers.dirtyFields, - }) - const errors = getProfileFieldErrors(name, value) - const errorToShow = showErrors && errors.length > 0 ? errors.join(', ') : null - - const onBlur = (): void => { - focusHandlers.blur(fieldId) - } - const onFocus = (): void => { - focusHandlers.focus(fieldId) - } - return ( -
    - -
    - ) -} - -interface ProfileStepRowProps { - focusHandlers: FocusHandlers - profileStepItem: ProfileStepItem - stepNumber: number - isCycle?: boolean | null -} - -const ProfileStepRow = (props: ProfileStepRowProps): JSX.Element => { - const { focusHandlers, profileStepItem, isCycle } = props - const { t } = useTranslation(['application', 'tooltip']) - const dispatch = useDispatch() - - const updateStepFieldValue = (name: string, value: unknown): void => { - dispatch( - steplistActions.editProfileStep({ - id: profileStepItem.id, - fields: { [name]: value }, - }) - ) - } - - const deleteProfileStep = (): void => { - dispatch(steplistActions.deleteProfileStep({ id: profileStepItem.id })) - } - const names = [ - 'title', - 'temperature', - 'durationMinutes', - 'durationSeconds', - ] as const - const units: Record = { - title: null, - temperature: t('units.degrees'), - durationMinutes: t('units.minutes'), - durationSeconds: t('units.seconds'), - } - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - }) - const fields = names.map(name => { - const className = name === 'title' ? styles.title : styles.profile_field - return ( - - ) - }) - return ( -
    -
    - {props.stepNumber}. - {fields} -
    -
    - - {t('tooltip:profile.delete_step')} - - -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/RadioGroupField.tsx b/protocol-designer/src/components/StepEditForm/fields/RadioGroupField.tsx deleted file mode 100644 index 05048abf216..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/RadioGroupField.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type * as React from 'react' -import { RadioGroup } from '@opentrons/components' -import type { StepFieldName } from '../../../steplist/fieldLevel' -import type { FieldProps } from '../types' - -interface RadioGroupFieldProps extends FieldProps { - name: StepFieldName - options: React.ComponentProps['options'] - className?: string -} - -export const RadioGroupField = (props: RadioGroupFieldProps): JSX.Element => { - const { - className, - errorToShow, - isIndeterminate, // TODO(IL, 2021-02-05): if we need indeterminate RadioGroupField, we'll want to pass this down into RadioGroup - name, - onFieldBlur, - onFieldFocus, - tooltipContent, - updateValue, - value, - ...radioGroupProps - } = props - return ( - ) => { - updateValue(e.currentTarget.value) - // NOTE(IL, 2020-01-29): to allow the intented pristinity UX, this component "blurs" onchange - if (onFieldBlur) { - onFieldBlur() - } - }} - /> - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/StepFormDropdownField.tsx b/protocol-designer/src/components/StepEditForm/fields/StepFormDropdownField.tsx deleted file mode 100644 index 0237b6d2ebc..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/StepFormDropdownField.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type * as React from 'react' -import { DropdownField } from '@opentrons/components' -import cx from 'classnames' -import styles from '../StepEditForm.module.css' -import type { Options } from '@opentrons/components' -import type { StepFieldName } from '../../../steplist/fieldLevel' -import type { FieldProps } from '../types' - -export interface StepFormDropdownProps extends FieldProps { - options: Options - name: StepFieldName - className?: string -} - -export const StepFormDropdown = (props: StepFormDropdownProps): JSX.Element => { - const { - options, - name, - className, - onFieldBlur, - onFieldFocus, - value, - updateValue, - errorToShow, - } = props - // TODO: BC abstract e.currentTarget.value inside onChange with fn like onChangeValue of type (value: unknown) => {} - // blank out the dropdown if labware id does not exist - const availableOptionIds = options.map(opt => opt.value) - // @ts-expect-error (ce, 2021-06-21) unknown not assignable to string - const fieldValue = availableOptionIds.includes(value) ? String(value) : null - - return ( - ) => { - updateValue(e.currentTarget.value) - }} - /> - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TextField.tsx b/protocol-designer/src/components/StepEditForm/fields/TextField.tsx deleted file mode 100644 index 8db378b24c5..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TextField.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { LegacyInputField } from '@opentrons/components' -import type { FieldProps } from '../types' - -type TextFieldProps = FieldProps & { - className?: string - caption?: string | null - units?: string | null -} - -export const TextField = (props: TextFieldProps): JSX.Element => { - const { - errorToShow, - onFieldBlur, - onFieldFocus, - tooltipContent, - updateValue, - value, - ...otherProps - } = props - - return ( - { - updateValue(e.currentTarget.value) - }} - value={value ? String(value) : null} - /> - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionAllViz.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionAllViz.tsx deleted file mode 100644 index 73604a085f6..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionAllViz.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import round from 'lodash/round' - -import PIPETTE_TIP_IMAGE from '../../../../assets/images/pipette_tip.svg' -import WELL_CROSS_SECTION_IMAGE from '../../../../assets/images/well_cross_section.svg' - -import styles from './TipPositionInput.module.css' - -const WELL_HEIGHT_PIXELS = 145 -const WELL_WIDTH_PIXELS = 100 -const PIXEL_DECIMALS = 2 - -interface TipPositionAllVizProps { - mmFromBottom: number - xPosition: number - wellDepthMm: number - xWidthMm: number -} - -export function TipPositionAllViz(props: TipPositionAllVizProps): JSX.Element { - const { mmFromBottom, xPosition, wellDepthMm, xWidthMm } = props - const fractionOfWellHeight = mmFromBottom / wellDepthMm - const pixelsFromBottom = - Number(fractionOfWellHeight) * WELL_HEIGHT_PIXELS - WELL_HEIGHT_PIXELS - const roundedPixelsFromBottom = round(pixelsFromBottom, PIXEL_DECIMALS) - const bottomPx = wellDepthMm - ? roundedPixelsFromBottom - : mmFromBottom - WELL_HEIGHT_PIXELS - - const xPx = (WELL_WIDTH_PIXELS / xWidthMm) * xPosition - const roundedXPx = round(xPx, PIXEL_DECIMALS) - return ( -
    - - - {props.wellDepthMm !== null && ( - {props.wellDepthMm}mm - )} - -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionInput.module.css b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionInput.module.css deleted file mode 100644 index ef185908342..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionInput.module.css +++ /dev/null @@ -1,77 +0,0 @@ -@import '@opentrons/components/styles'; - -.modal_header { - display: flex; - flex-direction: column; - justify-content: space-evenly; - height: 4rem; -} - -.main_row { - display: flex; - flex-direction: row; - justify-content: space-between; - margin: 1rem 0 2rem; -} - -.position_from_bottom_input { - padding-left: 1.5rem; - width: 6rem; - white-space: nowrap; -} - -.adjustment_buttons { - display: flex; - flex-direction: column; -} - -.adjustment_button { - height: 34px; - width: 34px; - cursor: pointer; - margin-top: 0.5em; -} - -.viz_group { - display: flex; - align-items: flex-end; -} - -.viz_wrapper { - position: relative; - width: 200px; - height: 200px; - display: flex; - flex-direction: column; - justify-content: flex-end; - overflow: hidden; -} - -.pipette_tip_image { - position: relative; -} - -.well_height_label { - font-size: var(--fs-caption); - font-weight: var(--fw-semibold); - color: var(--c-blue); - position: absolute; - bottom: 45px; - align-self: flex-end; -} - -.well_cross_section_image { - position: relative; - left: 9px; -} - -.tip_position_icon { - height: 1.5rem; - width: 1.5rem; - cursor: pointer; - color: #24313f; /* black80 */ -} - -.tip_position_icon:hover { - background-color: #e6e6e6; -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionModal.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionModal.tsx deleted file mode 100644 index ddbdd527c02..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionModal.tsx +++ /dev/null @@ -1,383 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import cx from 'classnames' -import { useTranslation } from 'react-i18next' -import { - AlertModal, - DIRECTION_COLUMN, - Flex, - LegacyInputField, - RadioGroup, - SPACING, - LegacyStyledText, - LegacyTooltip, - useHoverTooltip, -} from '@opentrons/components' -import { getMainPagePortalEl } from '../../../portals/MainPageModalPortal' -import { getIsTouchTipField } from '../../../../form-types' -import { PDAlert } from '../../../alerts/PDAlert' -import { TOO_MANY_DECIMALS, PERCENT_RANGE_TO_SHOW_WARNING } from './constants' -import { TipPositionAllViz } from './TipPositionAllViz' -import * as utils from './utils' - -import styles from './TipPositionInput.module.css' -import modalStyles from '../../../modals/modal.module.css' - -import type { StepFieldName } from '../../../../form-types' - -type Offset = 'x' | 'y' | 'z' -interface PositionSpec { - name: StepFieldName - value: number | null - updateValue: (val?: number | null) => void -} -export type PositionSpecs = Record - -interface TipPositionModalProps { - closeModal: () => void - specs: PositionSpecs - wellDepthMm: number - wellXWidthMm: number - wellYWidthMm: number - isIndeterminate?: boolean -} - -export const TipPositionModal = ( - props: TipPositionModalProps -): JSX.Element | null => { - const { - isIndeterminate, - specs, - wellDepthMm, - wellXWidthMm, - wellYWidthMm, - closeModal, - } = props - const [targetProps, tooltipProps] = useHoverTooltip() - const zSpec = specs.z - const ySpec = specs.y - const xSpec = specs.x - - const { t } = useTranslation(['modal', 'button', 'tooltip']) - - if (zSpec == null || xSpec == null || ySpec == null) { - console.error( - 'expected to find specs for one of the positions but could not' - ) - } - - const defaultMmFromBottom = utils.getDefaultMmFromBottom({ - name: zSpec.name, - wellDepthMm, - }) - - const [zValue, setZValue] = React.useState( - zSpec?.value == null ? String(defaultMmFromBottom) : String(zSpec?.value) - ) - const [yValue, setYValue] = React.useState( - ySpec?.value == null ? null : String(ySpec?.value) - ) - const [xValue, setXValue] = React.useState( - xSpec?.value == null ? null : String(xSpec?.value) - ) - - const [isDefault, setIsDefault] = React.useState( - !isIndeterminate && - zSpec.value === null && - ySpec.value === 0 && - xSpec.value === 0 - ) - // in this modal, pristinity hides the OUT_OF_BOUNDS error only. - const [isPristine, setPristine] = React.useState(true) - - const getMinMaxMmFromBottom = (): { - maxMmFromBottom: number - minMmFromBottom: number - } => { - if (getIsTouchTipField(zSpec?.name ?? '')) { - return { - maxMmFromBottom: utils.roundValue(wellDepthMm, 'up'), - minMmFromBottom: utils.roundValue(wellDepthMm / 2, 'up'), - } - } - return { - maxMmFromBottom: utils.roundValue(wellDepthMm * 2, 'up'), - minMmFromBottom: 0, - } - } - - const { maxMmFromBottom, minMmFromBottom } = getMinMaxMmFromBottom() - const { minValue: yMinWidth, maxValue: yMaxWidth } = utils.getMinMaxWidth( - wellYWidthMm - ) - const { minValue: xMinWidth, maxValue: xMaxWidth } = utils.getMinMaxWidth( - wellXWidthMm - ) - - const createErrors = ( - value: string | null, - min: number, - max: number - ): utils.Error[] => { - return utils.getErrors({ isDefault, minMm: min, maxMm: max, value }) - } - const zErrors = createErrors(zValue, minMmFromBottom, maxMmFromBottom) - const xErrors = createErrors(xValue, xMinWidth, xMaxWidth) - const yErrors = createErrors(yValue, yMinWidth, yMaxWidth) - - const hasErrors = - zErrors.length > 0 || xErrors.length > 0 || yErrors.length > 0 - const hasVisibleErrors = isPristine - ? zErrors.includes(TOO_MANY_DECIMALS) || - xErrors.includes(TOO_MANY_DECIMALS) || - yErrors.includes(TOO_MANY_DECIMALS) - : hasErrors - - const createErrorText = ( - errors: utils.Error[], - min: number, - max: number - ): string | null => { - return utils.getErrorText({ errors, minMm: min, maxMm: max, isPristine, t }) - } - - const roundedXMin = utils.roundValue(xMinWidth, 'up') - const roundedYMin = utils.roundValue(yMinWidth, 'up') - const roundedXMax = utils.roundValue(xMaxWidth, 'down') - const roundedYMax = utils.roundValue(yMaxWidth, 'down') - - const zErrorText = createErrorText(zErrors, minMmFromBottom, maxMmFromBottom) - const xErrorText = createErrorText(xErrors, roundedXMin, roundedXMax) - const yErrorText = createErrorText(yErrors, roundedYMin, roundedYMax) - - const handleDone = (): void => { - setPristine(false) - if (!hasErrors) { - if (isDefault) { - zSpec?.updateValue(null) - xSpec?.updateValue(0) - ySpec?.updateValue(0) - } else { - zSpec?.updateValue(zValue === null ? null : Number(zValue)) - xSpec?.updateValue(xValue === null ? null : Number(xValue)) - ySpec?.updateValue(yValue === null ? null : Number(yValue)) - } - closeModal() - } - } - - const handleCancel = (): void => { - closeModal() - } - - const handleZChange = (newValueRaw: string | number): void => { - // if string, strip non-number characters from string and cast to number - const newValue = - typeof newValueRaw === 'string' - ? newValueRaw.replace(/[^.0-9]/, '') - : String(newValueRaw) - - if (newValue === '.') { - setZValue('0.') - } else { - setZValue(Number(newValue) >= 0 ? newValue : '0') - } - } - - const handleZInputFieldChange = ( - e: React.ChangeEvent - ): void => { - handleZChange(e.currentTarget.value) - } - - const handleXChange = (newValueRaw: string | number): void => { - // if string, strip non-number characters from string and cast to number - const newValue = - typeof newValueRaw === 'string' - ? newValueRaw.replace(/[^-.0-9]/g, '') - : String(newValueRaw) - - if (newValue === '.') { - setXValue('0.') - } else { - setXValue(newValue) - } - } - - const handleXInputFieldChange = ( - e: React.ChangeEvent - ): void => { - handleXChange(e.currentTarget.value) - } - - const handleYChange = (newValueRaw: string | number): void => { - // if string, strip non-number characters from string and cast to number - const newValue = - typeof newValueRaw === 'string' - ? newValueRaw.replace(/[^-.0-9]/g, '') - : String(newValueRaw) - - if (newValue === '.') { - setYValue('0.') - } else { - setYValue(newValue) - } - } - - const handleYInputFieldChange = ( - e: React.ChangeEvent - ): void => { - handleYChange(e.currentTarget.value) - } - const isXValueNearEdge = - xValue != null && - (parseInt(xValue) > PERCENT_RANGE_TO_SHOW_WARNING * xMaxWidth || - parseInt(xValue) < PERCENT_RANGE_TO_SHOW_WARNING * xMinWidth) - const isYValueNearEdge = - yValue != null && - (parseInt(yValue) > PERCENT_RANGE_TO_SHOW_WARNING * yMaxWidth || - parseInt(yValue) < PERCENT_RANGE_TO_SHOW_WARNING * yMinWidth) - const isZValueAtBottom = zValue != null && zValue === '0' - - const TipPositionInputField = !isDefault ? ( - - - - {t('tip_position.field_titles.x_position')} - - - - - - {t('tip_position.field_titles.y_position')} - - - - {t('tooltip:y_position_value')} - - - - - {t('tip_position.field_titles.z_position')} - - - - - ) : null - - // Mix Form's asp/disp tip position field has different default value text - const isMixAspDispField = zSpec?.name === 'mix_mmFromBottom' - - return createPortal( - -
    -

    {t('tip_position.title')}

    -

    {t(`tip_position.body.${zSpec?.name}`)}

    -
    - - {(isXValueNearEdge || isYValueNearEdge || isZValueAtBottom) && - !isDefault ? ( - - - - ) : null} - -
    - - - ) => { - setIsDefault(e.currentTarget.value === 'default') - }} - options={[ - { - name: isMixAspDispField - ? t('tip_position.radio_button.mix') - : t('tip_position.radio_button.default', { - defaultMmFromBottom, - }), - value: 'default', - }, - { - name: t('tip_position.radio_button.custom'), - value: 'custom', - }, - ]} - name="TipPositionOptions" - /> - {TipPositionInputField} - - -
    - -
    -
    -
    -
    , - getMainPagePortalEl() - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionZAxisViz.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionZAxisViz.tsx deleted file mode 100644 index c86364a11c6..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/TipPositionZAxisViz.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import round from 'lodash/round' - -import PIPETTE_TIP_IMAGE from '../../../../assets/images/pipette_tip.svg' -import WELL_CROSS_SECTION_IMAGE from '../../../../assets/images/well_cross_section.svg' - -import styles from './TipPositionInput.module.css' - -const WELL_HEIGHT_PIXELS = 145 -const TIP_X_OFFSET_PIXELS = 22 -const PIXEL_DECIMALS = 2 -interface TipPositionZAxisVizProps { - wellDepthMm: number - mmFromBottom?: number - mmFromTop?: number -} - -export function TipPositionZAxisViz( - props: TipPositionZAxisVizProps -): JSX.Element { - const { mmFromBottom, mmFromTop, wellDepthMm } = props - const positionInTube = mmFromBottom ?? mmFromTop ?? 0 - const fractionOfWellHeight = positionInTube / wellDepthMm - const pixelsFromBottom = - fractionOfWellHeight * WELL_HEIGHT_PIXELS - - (mmFromBottom != null ? WELL_HEIGHT_PIXELS : 0) - const bottomPx = round(pixelsFromBottom, PIXEL_DECIMALS) - - return ( -
    - - {props.wellDepthMm !== null && ( - {props.wellDepthMm}mm - )} - -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/ZTipPositionModal.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/ZTipPositionModal.tsx deleted file mode 100644 index 45b16cb228a..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/ZTipPositionModal.tsx +++ /dev/null @@ -1,292 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import cx from 'classnames' -import { useTranslation } from 'react-i18next' -import { - AlertModal, - Flex, - HandleKeypress, - Icon, - LegacyInputField, - OutlineButton, - RadioGroup, -} from '@opentrons/components' -import { DEFAULT_MM_BLOWOUT_OFFSET_FROM_TOP } from '../../../../constants' -import { getMainPagePortalEl } from '../../../portals/MainPageModalPortal' -import { getIsTouchTipField } from '../../../../form-types' -import { TipPositionZAxisViz } from './TipPositionZAxisViz' -import * as utils from './utils' -import { LARGE_STEP_MM, SMALL_STEP_MM, TOO_MANY_DECIMALS } from './constants' - -import type { StepFieldName } from '../../../../form-types' - -import modalStyles from '../../../modals/modal.module.css' -import styles from './TipPositionInput.module.css' - -interface ZTipPositionModalProps { - closeModal: () => void - zValue: number | null - name: StepFieldName - updateValue: (val?: number | null) => unknown - wellDepthMm: number - isIndeterminate?: boolean -} - -export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element { - const { - isIndeterminate, - name, - wellDepthMm, - zValue, - closeModal, - updateValue, - } = props - const { t } = useTranslation(['modal', 'button']) - - const isBlowout = name === 'blowout_z_offset' - const defaultMm = isBlowout - ? 0 - : utils.getDefaultMmFromBottom({ - name, - wellDepthMm, - }) - - const [value, setValue] = React.useState( - zValue !== null ? String(zValue) : null - ) - const isSetDefault = isBlowout ? zValue === 0 : zValue === null - const [isDefault, setIsDefault] = React.useState( - !isIndeterminate && isSetDefault - ) - // in this modal, pristinity hides the OUT_OF_BOUNDS error only. - const [isPristine, setPristine] = React.useState(true) - - const getMinMaxMmFromBottom = (): { - maxMmFromBottom: number - minMmFromBottom: number - } => { - if (getIsTouchTipField(name)) { - return { - maxMmFromBottom: utils.roundValue(wellDepthMm, 'up'), - minMmFromBottom: utils.roundValue(wellDepthMm / 2, 'up'), - } - } - return { - maxMmFromBottom: utils.roundValue(wellDepthMm * 2, 'up'), - minMmFromBottom: 0, - } - } - const { maxMmFromBottom, minMmFromBottom } = getMinMaxMmFromBottom() - - // For blowout from the top of the well - const minFromTop = DEFAULT_MM_BLOWOUT_OFFSET_FROM_TOP - const maxFromTop = -wellDepthMm - - const minMm = isBlowout ? maxFromTop : minMmFromBottom - const maxMm = isBlowout ? minFromTop : maxMmFromBottom - - const errors = utils.getErrors({ - isDefault, - minMm, - maxMm, - value, - }) - const hasErrors = errors.length > 0 - const hasVisibleErrors = isPristine - ? errors.includes(TOO_MANY_DECIMALS) - : hasErrors - - const errorText = utils.getErrorText({ - errors, - minMm, - maxMm, - isPristine, - t, - }) - - const handleDone = (): void => { - setPristine(false) - - if (!hasErrors) { - if (isDefault) { - updateValue(null) - } else { - updateValue(value === null ? null : Number(value)) - } - closeModal() - } - } - - const handleCancel = (): void => { - closeModal() - } - - const handleChange = (newValueRaw: string | number): void => { - // if string, strip non-number characters from string and cast to number - const newValue = - typeof newValueRaw === 'string' - ? newValueRaw.replace(/[^-.0-9]/, '') - : String(newValueRaw) - - if (newValue === '.') { - setValue('0.') - } else if (newValue === '-0') { - setValue('0') - } else { - isBlowout - ? setValue(newValue) - : setValue(Number(newValue) >= 0 ? newValue : '0') - } - } - - const handleInputFieldChange = ( - e: React.ChangeEvent - ): void => { - handleChange(e.currentTarget.value) - } - - const handleIncrementDecrement = (delta: number): void => { - const prevValue = value === null ? defaultMm : Number(value) - setIsDefault(false) - handleChange(utils.roundValue(prevValue + delta, 'up')) - } - - const makeHandleIncrement = (step: number): (() => void) => () => { - handleIncrementDecrement(step) - } - - const makeHandleDecrement = (step: number): (() => void) => () => { - handleIncrementDecrement(step * -1) - } - - const TipPositionInputField = !isDefault && ( - - ) - - return createPortal( - - -
    -

    {t('tip_position.title')}

    -

    {t(`tip_position.body.${name}`)}

    -
    -
    - -
    - ) => { - setIsDefault(e.currentTarget.value === 'default') - }} - options={[ - { - name: isBlowout - ? t('tip_position.radio_button.blowout') - : t('tip_position.radio_button.default', { - defaultMm, - }), - value: 'default', - }, - { - name: t('tip_position.radio_button.custom'), - value: 'custom', - }, - ]} - name="TipPositionOptions" - /> - {TipPositionInputField} -
    - -
    - {!isDefault ? ( -
    - - - - - - -
    - ) : null} - -
    -
    -
    -
    -
    , - getMainPagePortalEl() - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/TipPositionField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/TipPositionField.test.tsx deleted file mode 100644 index 96896fa7baa..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/TipPositionField.test.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import type * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { fixture96Plate } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../assets/localization' -import { getLabwareEntities } from '../../../../../step-forms/selectors' -import { ZTipPositionModal } from '../ZTipPositionModal' -import { TipPositionModal } from '../TipPositionModal' -import { TipPositionField } from '../index' -import type { LabwareDefinition2 } from '@opentrons/shared-data' - -vi.mock('../../../../../step-forms/selectors') -vi.mock('../ZTipPositionModal') -vi.mock('../TipPositionModal') -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} -const mockDelay = 'aspirate_delay_mmFromBottom' -const mockAspirate = 'aspirate_mmFromBottom' -const mockLabwareId = 'mockId' -describe('TipPositionField', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - zField: mockDelay, - labwareId: mockLabwareId, - propsForFields: { - [mockDelay]: { - name: mockDelay, - value: null, - updateValue: vi.fn(), - tooltipContent: 'mock content', - isIndeterminate: false, - disabled: false, - } as any, - }, - } - vi.mocked(TipPositionModal).mockReturnValue( -
    mock TipPositionModal
    - ) - vi.mocked(ZTipPositionModal).mockReturnValue( -
    mock ZTipPositionModal
    - ) - vi.mocked(getLabwareEntities).mockReturnValue({ - [mockLabwareId]: { - id: mockLabwareId, - labwareDefURI: 'mock uri', - def: fixture96Plate as LabwareDefinition2, - }, - }) - }) - it('renders the input field and header when x and y fields are not provided', () => { - render(props) - screen.getByText('mm') - fireEvent.click(screen.getByRole('textbox', { name: '' })) - expect(screen.getByRole('textbox', { name: '' })).not.toBeDisabled() - screen.getByText('mock ZTipPositionModal') - }) - it('renders the input field but it is disabled', () => { - props = { - ...props, - propsForFields: { - [mockDelay]: { - name: mockDelay, - value: null, - updateValue: vi.fn(), - tooltipContent: 'mock content', - isIndeterminate: false, - disabled: true, - } as any, - }, - } - render(props) - expect(screen.getByRole('textbox', { name: '' })).toBeDisabled() - }) - it('renders the icon when x,y, and z fields are provided', () => { - const mockX = 'aspirate_x_position' - const mockY = 'aspirate_y_position' - props = { - zField: mockAspirate, - xField: mockX, - yField: mockY, - labwareId: mockLabwareId, - propsForFields: { - [mockAspirate]: { - name: mockAspirate, - value: null, - updateValue: vi.fn(), - tooltipContent: 'mock content', - isIndeterminate: false, - disabled: false, - } as any, - [mockX]: { - name: mockX, - value: null, - updateValue: vi.fn(), - } as any, - [mockY]: { - name: mockY, - value: null, - updateValue: vi.fn(), - } as any, - }, - } - render(props) - fireEvent.click(screen.getByTestId('TipPositionIcon_aspirate_mmFromBottom')) - screen.getByText('mock TipPositionModal') - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/TipPositionModal.test.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/TipPositionModal.test.tsx deleted file mode 100644 index 44443796a90..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/TipPositionModal.test.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import type * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../assets/localization' -import { TipPositionModal } from '../TipPositionModal' -import { TipPositionAllViz } from '../TipPositionAllViz' - -vi.mock('../TipPositionAllViz') -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - -const mockUpdateZSpec = vi.fn() -const mockUpdateXSpec = vi.fn() -const mockUpdateYSpec = vi.fn() - -describe('TipPositionModal', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - closeModal: vi.fn(), - wellDepthMm: 50, - wellXWidthMm: 10.3, - wellYWidthMm: 10.5, - isIndeterminate: false, - specs: { - z: { - name: 'aspirate_mmFromBottom', - value: null, - updateValue: mockUpdateZSpec, - }, - y: { - name: 'aspirate_y_position', - value: 0, - updateValue: mockUpdateXSpec, - }, - x: { - name: 'aspirate_x_position', - value: 0, - updateValue: mockUpdateYSpec, - }, - }, - } - vi.mocked(TipPositionAllViz).mockReturnValue(
    mock TipPositionViz
    ) - }) - it('renders the modal text and radio button text', () => { - render(props) - screen.getByText('Tip Positioning') - screen.getByText('Change where in the well the robot aspirates from.') - screen.getByRole('radio', { name: '1 mm from the bottom center (default)' }) - screen.getByRole('radio', { name: 'Custom' }) - fireEvent.click(screen.getByText('cancel')) - expect(props.closeModal).toHaveBeenCalled() - fireEvent.click(screen.getByText('done')) - expect(props.closeModal).toHaveBeenCalled() - expect(mockUpdateXSpec).toHaveBeenCalled() - expect(mockUpdateYSpec).toHaveBeenCalled() - expect(mockUpdateZSpec).toHaveBeenCalled() - }) - it('renders the alert if the x/y position values are too close to the max/min for x value', () => { - props.specs.x.value = 9.7 - render(props) - screen.getByText('warning') - screen.getByText( - 'Tip position is close to the edge of the well and may cause collisions.' - ) - }) - it('renders the alert if the x/y position values are too close to the max/min for y value', () => { - props.specs.y.value = -9.7 - render(props) - screen.getByText('warning') - screen.getByText( - 'Tip position is close to the edge of the well and may cause collisions.' - ) - }) - it('renders the custom options, captions, and visual', () => { - render(props) - fireEvent.click(screen.getByRole('radio', { name: 'Custom' })) - expect(screen.getAllByRole('textbox', { name: '' })).toHaveLength(3) - screen.getByText('X position') - screen.getByText('between -5.1 and 5.1') - screen.getByText('Y position') - screen.getByText('between -5.2 and 5.2') - screen.getByText('Z position') - screen.getByText('between 0 and 100') - screen.getByText('mock TipPositionViz') - }) - it('renders a custom input field and clicks on it, calling the mock updates', () => { - render(props) - fireEvent.click(screen.getByRole('radio', { name: 'Custom' })) - const xInputField = screen.getAllByRole('textbox', { name: '' })[0] - fireEvent.change(xInputField, { target: { value: 3 } }) - const yInputField = screen.getAllByRole('textbox', { name: '' })[1] - fireEvent.change(yInputField, { target: { value: -2 } }) - const zInputField = screen.getAllByRole('textbox', { name: '' })[2] - fireEvent.change(zInputField, { target: { value: 10 } }) - fireEvent.click(screen.getByText('done')) - expect(props.closeModal).toHaveBeenCalled() - expect(mockUpdateXSpec).toHaveBeenCalled() - expect(mockUpdateYSpec).toHaveBeenCalled() - expect(mockUpdateZSpec).toHaveBeenCalled() - }) - it('renders custom input fields and displays error texts', () => { - props = { - ...props, - specs: { - z: { - name: 'aspirate_mmFromBottom', - value: 101, - updateValue: mockUpdateZSpec, - }, - y: { - name: 'aspirate_y_position', - value: -500, - updateValue: mockUpdateXSpec, - }, - x: { - name: 'aspirate_x_position', - value: 10.7, - updateValue: mockUpdateYSpec, - }, - }, - } - render(props) - fireEvent.click(screen.getByText('done')) - // display out of bounds error - screen.getByText('accepted range is 0 to 100') - screen.getByText('accepted range is -5.2 to 5.2') - screen.getByText('accepted range is -5.1 to 5.1') - const xInputField = screen.getAllByRole('textbox', { name: '' })[0] - fireEvent.change(xInputField, { target: { value: 3.55555 } }) - fireEvent.click(screen.getByText('done')) - // display too many decimals error - screen.getByText('a max of 1 decimal place is allowed') - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/ZTipPositionModal.test.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/ZTipPositionModal.test.tsx deleted file mode 100644 index 837739cdc02..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/__tests__/ZTipPositionModal.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../assets/localization' -import { ZTipPositionModal } from '../ZTipPositionModal' -import { TipPositionZAxisViz } from '../TipPositionZAxisViz' - -vi.mock('../TipPositionZAxisViz') -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - -describe('ZTipPositionModal', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - closeModal: vi.fn(), - zValue: -2, - updateValue: vi.fn(), - wellDepthMm: 30, - name: 'blowout_z_offset', - } - vi.mocked(TipPositionZAxisViz).mockReturnValue( -
    mock TipPositionZAxisViz
    - ) - }) - it('renders the text and radio buttons', () => { - render(props) - screen.getByText('Tip Positioning') - screen.getByText('Change where in the well the robot performs the blowout.') - screen.getByRole('radio', { name: '0 mm from the top center (default)' }) - screen.getByRole('radio', { name: 'Custom' }) - fireEvent.click(screen.getByText('cancel')) - expect(props.closeModal).toHaveBeenCalled() - fireEvent.click(screen.getByText('done')) - expect(props.closeModal).toHaveBeenCalled() - expect(props.updateValue).toHaveBeenCalled() - }) - it('renders the custom option, caption, and visual', () => { - render(props) - fireEvent.click(screen.getByRole('radio', { name: 'Custom' })) - expect(screen.getAllByRole('textbox', { name: '' })).toHaveLength(1) - screen.getByText('between -30 and 0') - screen.getByText('mock TipPositionZAxisViz') - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/constants.ts b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/constants.ts deleted file mode 100644 index 528d9a0262e..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const DECIMALS_ALLOWED = 1 -export const SMALL_STEP_MM = 1 -export const LARGE_STEP_MM = 10 -export const TOO_MANY_DECIMALS: 'TOO_MANY_DECIMALS' = 'TOO_MANY_DECIMALS' -export const PERCENT_RANGE_TO_SHOW_WARNING = 0.9 diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx deleted file mode 100644 index f853e12f55f..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/index.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { - COLORS, - Flex, - FormGroup, - Icon, - LegacyInputField, - LegacyTooltip, - useHoverTooltip, -} from '@opentrons/components' -import { getWellsDepth, getWellDimension } from '@opentrons/shared-data' -import { - getIsTouchTipField, - getIsDelayPositionField, -} from '../../../../form-types' -import { selectors as stepFormSelectors } from '../../../../step-forms' -import { TipPositionModal } from './TipPositionModal' -import { getDefaultMmFromBottom } from './utils' -import { ZTipPositionModal } from './ZTipPositionModal' -import type { - TipXOffsetFields, - TipYOffsetFields, - TipZOffsetFields, -} from '../../../../form-types' -import type { UseHoverTooltipTargetProps } from '@opentrons/components' -import type { FieldPropsByName } from '../../types' -import type { PositionSpecs } from './TipPositionModal' - -import stepFormStyles from '../../StepEditForm.module.css' -import styles from './TipPositionInput.module.css' - -interface TipPositionFieldProps { - propsForFields: FieldPropsByName - zField: TipZOffsetFields - xField?: TipXOffsetFields - yField?: TipYOffsetFields - labwareId?: string | null -} - -export function TipPositionField(props: TipPositionFieldProps): JSX.Element { - const { labwareId, propsForFields, zField, xField, yField } = props - const { - name: zName, - value: rawZValue, - updateValue: zUpdateValue, - tooltipContent, - isIndeterminate, - disabled, - } = propsForFields[zField] - - const { t } = useTranslation('application') - const [targetProps, tooltipProps] = useHoverTooltip() - const [isModalOpen, setModalOpen] = React.useState(false) - const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) - const labwareDef = - labwareId != null && labwareEntities[labwareId] != null - ? labwareEntities[labwareId].def - : null - - let wellDepthMm = 0 - let wellXWidthMm = 0 - let wellYWidthMm = 0 - - if (labwareDef != null) { - // NOTE: only taking depth of first well in labware def, UI not currently equipped for multiple depths/widths - const firstWell = labwareDef.wells.A1 - if (firstWell) { - wellDepthMm = getWellsDepth(labwareDef, ['A1']) - wellXWidthMm = getWellDimension(labwareDef, ['A1'], 'x') - wellYWidthMm = getWellDimension(labwareDef, ['A1'], 'y') - } - } - - if ( - (wellDepthMm === 0 || wellXWidthMm === 0 || wellYWidthMm === 0) && - labwareId != null && - labwareDef != null - ) { - console.error( - `expected to find all well dimensions mm with labwareId ${labwareId} but could not` - ) - } - - const handleOpen = (has3Specs: boolean): void => { - if (has3Specs && wellDepthMm && wellXWidthMm && wellYWidthMm) { - setModalOpen(true) - } - if (!has3Specs && wellDepthMm) { - setModalOpen(true) - } - } - const handleClose = (): void => { - setModalOpen(false) - } - const isTouchTipField = getIsTouchTipField(zName) - const isDelayPositionField = getIsDelayPositionField(zName) - let zValue: string | number = '0' - const mmFromBottom = typeof rawZValue === 'number' ? rawZValue : null - if (wellDepthMm !== null) { - // show default value for field in parens if no mmFromBottom value is selected - zValue = - mmFromBottom ?? getDefaultMmFromBottom({ name: zName, wellDepthMm }) - } - let modal = ( - - ) - if (yField != null && xField != null) { - const { - name: xName, - value: rawXValue, - updateValue: xUpdateValue, - } = propsForFields[xField] - const { - name: yName, - value: rawYValue, - updateValue: yUpdateValue, - } = propsForFields[yField] - - const specs: PositionSpecs = { - z: { - name: zName, - value: mmFromBottom, - updateValue: zUpdateValue, - }, - x: { - name: xName, - value: rawXValue != null ? Number(rawXValue) : null, - updateValue: xUpdateValue, - }, - y: { - name: yName, - value: rawYValue != null ? Number(rawYValue) : null, - updateValue: yUpdateValue, - }, - } - - modal = ( - - ) - } - - return ( - <> - {tooltipContent} - {isModalOpen ? modal : null} - - {yField != null && xField != null ? ( - { - handleOpen(true) - } - : () => {} - } - id={`TipPositionIcon_${zName}`} - data-testid={`TipPositionIcon_${zName}`} - width="5rem" - > - - - ) : ( - { - handleOpen(false) - }} - value={String(zValue)} - isIndeterminate={isIndeterminate} - units={t('units.millimeter')} - id={`TipPositionField_${zName}`} - /> - )} - - - ) -} - -interface WrapperProps { - isTouchTipField: boolean - isDelayPositionField: boolean - children: React.ReactNode - disabled: boolean - targetProps: UseHoverTooltipTargetProps -} - -const Wrapper = (props: WrapperProps): JSX.Element => { - const { t } = useTranslation('form') - return props.isTouchTipField || props.isDelayPositionField ? ( -
    {props.children}
    - ) : ( - - - {props.children} - - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/utils.ts b/protocol-designer/src/components/StepEditForm/fields/TipPositionField/utils.ts deleted file mode 100644 index 4648aa78933..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipPositionField/utils.ts +++ /dev/null @@ -1,127 +0,0 @@ -import floor from 'lodash/floor' -import round from 'lodash/round' -import { getIsTouchTipField } from '../../../../form-types' -import { - DEFAULT_MM_FROM_BOTTOM_ASPIRATE, - DEFAULT_MM_FROM_BOTTOM_DISPENSE, - DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP, -} from '../../../../constants' -import { DECIMALS_ALLOWED, TOO_MANY_DECIMALS } from './constants' -import type { StepFieldName } from '../../../../form-types' - -// TODO: Ian + Brian 2019-02-13 this should switch on stepType, not use field -// name to infer step type! -// -// TODO(IL, 2021-03-10): after resolving #7470, use this util instead -// of directly using these constants, wherever these constants are used. See also #7469 -export function getDefaultMmFromBottom(args: { - name: StepFieldName - wellDepthMm: number -}): number { - const { name, wellDepthMm } = args - - switch (name) { - case 'aspirate_mmFromBottom': - return DEFAULT_MM_FROM_BOTTOM_ASPIRATE - - case 'aspirate_delay_mmFromBottom': - return DEFAULT_MM_FROM_BOTTOM_ASPIRATE - - case 'dispense_mmFromBottom': - return DEFAULT_MM_FROM_BOTTOM_DISPENSE - - case 'dispense_delay_mmFromBottom': - return DEFAULT_MM_FROM_BOTTOM_DISPENSE - - case 'mix_mmFromBottom': - // TODO: Ian 2018-11-131 figure out what offset makes most sense for mix - return DEFAULT_MM_FROM_BOTTOM_DISPENSE - - default: - // touch tip fields - console.assert( - getIsTouchTipField(name), - `getDefaultMmFromBottom fn does not know what to do with field ${name}` - ) - return DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP + wellDepthMm - } -} - -export const roundValue = ( - value: number | string | null, - direction: 'up' | 'down' -): number => { - if (value === null) return 0 - - switch (direction) { - case 'up': { - return round(Number(value), DECIMALS_ALLOWED) - } - case 'down': { - return floor(Number(value), DECIMALS_ALLOWED) - } - } -} - -const OUT_OF_BOUNDS: 'OUT_OF_BOUNDS' = 'OUT_OF_BOUNDS' -export type Error = typeof TOO_MANY_DECIMALS | typeof OUT_OF_BOUNDS - -export const getErrorText = (args: { - errors: Error[] - maxMm: number - minMm: number - isPristine: boolean - t: any -}): string | null => { - const { errors, minMm, maxMm, isPristine, t } = args - - if (errors.includes(TOO_MANY_DECIMALS)) { - return t('tip_position.errors.TOO_MANY_DECIMALS') - } else if (!isPristine && errors.includes(OUT_OF_BOUNDS)) { - return t('tip_position.errors.OUT_OF_BOUNDS', { - minMm, - maxMm, - }) - } else { - return null - } -} - -export const getErrors = (args: { - isDefault: boolean - value: string | null - maxMm: number - minMm: number -}): Error[] => { - const { isDefault, value: rawValue, maxMm, minMm } = args - const errors: Error[] = [] - if (isDefault) return errors - - const value = Number(rawValue) - if (rawValue === null || Number.isNaN(value)) { - // blank or otherwise invalid should show this error as a fallback - return [OUT_OF_BOUNDS] - } - const incorrectDecimals = round(value, DECIMALS_ALLOWED) !== value - const outOfBounds = value > maxMm || value < minMm - - if (incorrectDecimals) { - errors.push(TOO_MANY_DECIMALS) - } - if (outOfBounds) { - errors.push(OUT_OF_BOUNDS) - } - return errors -} - -interface MinMaxValues { - minValue: number - maxValue: number -} - -export const getMinMaxWidth = (width: number): MinMaxValues => { - return { - minValue: -width * 0.5, - maxValue: width * 0.5, - } -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TipWellSelectionField/__tests__/TipWellSelectionField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/TipWellSelectionField/__tests__/TipWellSelectionField.test.tsx deleted file mode 100644 index 935856747fc..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipWellSelectionField/__tests__/TipWellSelectionField.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, vi, beforeEach } from 'vitest' - -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../assets/localization' -import { getPipetteEntities } from '../../../../../step-forms/selectors' -import { WellSelectionModal } from '../../WellSelectionField/WellSelectionModal' -import { TipWellSelectionField } from '../index' - -vi.mock('../../../../../step-forms/selectors') -vi.mock('../../WellSelectionField/WellSelectionModal') - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - -const mockPipId = 'mockId' - -describe('TipWellSelectionField', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - name: 'well', - value: [], - updateValue: vi.fn(), - onFieldBlur: vi.fn(), - onFieldFocus: vi.fn(), - disabled: false, - pipetteId: mockPipId, - labwareId: 'mockLabwareId', - nozzles: null, - } - vi.mocked(getPipetteEntities).mockReturnValue({ - [mockPipId]: { - name: 'p50_single_flex', - spec: {} as any, - id: mockPipId, - tiprackLabwareDef: [], - tiprackDefURI: ['mockDefURI1', 'mockDefURI2'], - }, - }) - vi.mocked(WellSelectionModal).mockReturnValue( -
    mock WellSelectionModal
    - ) - }) - it('renders the readOnly input field and clicking on it renders the modal', () => { - render(props) - screen.getByText('wells') - fireEvent.click(screen.getByRole('textbox', { name: '' })) - screen.getByText('mock WellSelectionModal') - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/TipWellSelectionField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/TipWellSelectionField/index.tsx deleted file mode 100644 index 1a059dc8033..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TipWellSelectionField/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { createPortal } from 'react-dom' -import { FormGroup, LegacyInputField } from '@opentrons/components' -import { getPipetteEntities } from '../../../../step-forms/selectors' -import { getNozzleType } from '../../utils' -import { getMainPagePortalEl } from '../../../portals/MainPageModalPortal' -import { WellSelectionModal } from '../WellSelectionField/WellSelectionModal' -import type { StepFormDropdown } from '../StepFormDropdownField' - -import styles from '../../StepEditForm.module.css' - -type TipWellSelectionFieldProps = Omit< - React.ComponentProps, - 'options' -> & { - pipetteId: unknown - labwareId: unknown - nozzles: string | null -} - -export function TipWellSelectionField( - props: TipWellSelectionFieldProps -): JSX.Element { - const { - value: selectedWells, - errorToShow, - name, - updateValue, - disabled, - pipetteId, - labwareId, - nozzles, - } = props - const { t } = useTranslation('form') - const pipetteEntities = useSelector(getPipetteEntities) - const primaryWellCount = - Array.isArray(selectedWells) && selectedWells.length > 0 - ? selectedWells.length.toString() - : null - const [openModal, setOpenModal] = useState(false) - const pipette = pipetteId != null ? pipetteEntities[String(pipetteId)] : null - const nozzleType = getNozzleType(pipette, nozzles) - - return ( - <> - {createPortal( - { - setOpenModal(false) - }} - pipetteId={String(pipetteId)} - updateValue={updateValue} - value={selectedWells} - nozzleType={nozzleType} - />, - - getMainPagePortalEl() - )} - - - { - setOpenModal(true) - }} - /> - - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/TiprackField.tsx b/protocol-designer/src/components/StepEditForm/fields/TiprackField.tsx deleted file mode 100644 index a6bf5dfcb3a..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/TiprackField.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { - FormGroup, - DropdownField, - useHoverTooltip, - LegacyTooltip, - Box, -} from '@opentrons/components' -import { selectors as uiLabwareSelectors } from '../../../ui/labware' -import { getPipetteEntities } from '../../../step-forms/selectors' -import type { FieldProps } from '../types' - -import styles from '../StepEditForm.module.css' - -interface TiprackFieldProps extends FieldProps { - pipetteId?: unknown -} -export function TiprackField(props: TiprackFieldProps): JSX.Element { - const { - name, - value, - onFieldBlur, - onFieldFocus, - updateValue, - pipetteId, - } = props - const { t } = useTranslation(['form', 'tooltip']) - const [targetProps, tooltipProps] = useHoverTooltip() - const pipetteEntities = useSelector(getPipetteEntities) - const options = useSelector(uiLabwareSelectors.getTiprackOptions) - const defaultTiprackUris = - pipetteId != null ? pipetteEntities[pipetteId as string].tiprackDefURI : [] - const tiprackOptions = options.filter(option => - defaultTiprackUris.includes(option.value) - ) - - React.useEffect(() => { - // if default value is not included in the pipette's tiprack uris then - // change it so it is - if (!defaultTiprackUris.includes(value as string)) { - updateValue(defaultTiprackUris[0]) - } - }, [defaultTiprackUris, value, updateValue]) - const hasMissingTiprack = defaultTiprackUris.length > tiprackOptions.length - return ( - - - ) => { - updateValue(e.currentTarget.value) - }} - /> - - {hasMissingTiprack ? ( - - {t('tooltip:missing_tiprack')} - - ) : null} - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/ToggleRowField.tsx b/protocol-designer/src/components/StepEditForm/fields/ToggleRowField.tsx deleted file mode 100644 index e3a8bf4bcdd..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/ToggleRowField.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import cx from 'classnames' - -import { ToggleField } from '@opentrons/components' - -import styles from '../StepEditForm.module.css' - -import type { FieldProps } from '../types' - -type ToggleRowProps = FieldProps & { - offLabel?: string - onLabel?: string - className?: string -} -export const ToggleRowField = (props: ToggleRowProps): JSX.Element => { - const { - updateValue, - value, - name, - offLabel, - onLabel, - disabled, - className, - } = props - return ( - { - updateValue(!value) - }} - disabled={disabled} - /> - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/VolumeField.tsx b/protocol-designer/src/components/StepEditForm/fields/VolumeField.tsx deleted file mode 100644 index 61592213ca6..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/VolumeField.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { - FormGroup, - LegacyTooltip, - useHoverTooltip, - TOOLTIP_TOP, - TOOLTIP_FIXED, -} from '@opentrons/components' -import { getFieldDefaultTooltip } from '../utils' -import { TextField } from './TextField' -import styles from '../StepEditForm.module.css' -import type { StepType } from '../../../form-types' -import type { FieldProps } from '../types' - -type Props = FieldProps & { - stepType: StepType - label: string - className: string -} -export const VolumeField = (props: Props): JSX.Element => { - const { t } = useTranslation(['tooltip', 'application']) - const { stepType, label, className, ...propsForVolumeField } = props - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, - strategy: TOOLTIP_FIXED, - }) - - return ( -
    - - {getFieldDefaultTooltip(propsForVolumeField.name, t)} - - - - -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderInput.module.css b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderInput.module.css deleted file mode 100644 index 0793c50e5fe..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderInput.module.css +++ /dev/null @@ -1,100 +0,0 @@ -@import '@opentrons/components/styles'; - -.well_order_icon { - height: 1.5rem; - width: 1.5rem; - cursor: pointer; - margin: 0.25rem 0; -} - -.icon_with_label { - margin: 0 0.25rem; -} - -.well_order_icon:hover { - background-color: #e6e6e6; -} - -.modal_header { - display: flex; - flex-direction: column; - justify-content: space-evenly; - height: 5rem; -} - -.main_row { - display: flex; - flex-direction: row; - justify-content: space-between; - margin: 3rem 0; -} - -.field_row { - display: flex; - flex-direction: row; - justify-content: space-between; -} - -.well_order_dropdown { - width: 9rem; -} - -.field_spacer { - margin: 6px 12px; -} - -.viz_wrapper { - position: relative; - width: 100px; - height: 100px; -} - -.wells_image { - height: 100px; - width: 100px; - position: relative; -} - -.path_image { - height: 60px; - width: 60px; - position: relative; - left: 20px; - top: -80px; -} - -.b2t_first.l2r_second { - /* default orientation */ -} - -.l2r_first.t2b_second { - transform: rotate(90deg); -} - -.t2b_first.r2l_second { - transform: rotate(180deg); -} - -.r2l_first.b2t_second { - transform: rotate(270deg); -} - -.b2t_first.r2l_second { - filter: FlipH; - transform: scaleX(-1); -} - -.l2r_first.b2t_second { - filter: FlipH; - transform: scaleX(-1) rotate(270deg); -} - -.t2b_first.l2r_second { - filter: FlipH; - transform: scaleX(-1) rotate(180deg); -} - -.r2l_first.t2b_second { - filter: FlipH; - transform: scaleX(-1) rotate(90deg); -} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.tsx b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.tsx deleted file mode 100644 index 0051be0ebb2..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderModal.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import cx from 'classnames' -import { useTranslation } from 'react-i18next' -import { getMainPagePortalEl } from '../../../portals/MainPageModalPortal' -import { - LegacyModal, - OutlineButton, - DeprecatedPrimaryButton, - FormGroup, - DropdownField, -} from '@opentrons/components' -import { WellOrderViz } from './WellOrderViz' -import type { WellOrderOption } from '../../../../form-types' - -import modalStyles from '../../../modals/modal.module.css' -import stepEditStyles from '../../StepEditForm.module.css' -import styles from './WellOrderInput.module.css' - -const DEFAULT_FIRST: WellOrderOption = 't2b' -const DEFAULT_SECOND: WellOrderOption = 'l2r' -const VERTICAL_VALUES: WellOrderOption[] = ['t2b', 'b2t'] -const HORIZONTAL_VALUES: WellOrderOption[] = ['l2r', 'r2l'] -const WELL_ORDER_VALUES: WellOrderOption[] = [ - ...VERTICAL_VALUES, - ...HORIZONTAL_VALUES, -] - -export interface WellOrderModalProps { - isOpen: boolean - closeModal: () => unknown - prefix: 'aspirate' | 'dispense' | 'mix' - firstValue?: WellOrderOption | null - secondValue?: WellOrderOption | null - firstName: string - secondName: string - updateValues: ( - firstValue?: WellOrderOption | null, - secondValue?: WellOrderOption | null - ) => void -} - -interface State { - firstValue: WellOrderOption - secondValue: WellOrderOption -} - -export const ResetButton = (props: { onClick: () => void }): JSX.Element => { - const { t } = useTranslation('button') - return ( - - {t('reset')} - - ) -} - -export const CancelButton = (props: { onClick: () => void }): JSX.Element => { - const { t } = useTranslation('button') - - return ( - - {t('cancel')} - - ) -} -export const DoneButton = (props: { onClick: () => void }): JSX.Element => { - const { t } = useTranslation('button') - - return ( - - {t('done')} - - ) -} - -export const WellOrderModal = ( - props: WellOrderModalProps -): JSX.Element | null => { - const { t } = useTranslation(['form', 'modal']) - const { - isOpen, - closeModal, - firstName, - secondName, - updateValues, - firstValue, - secondValue, - } = props - const getInitialFirstValues = (): { - initialFirstValue: WellOrderOption - initialSecondValue: WellOrderOption - } => { - if (firstValue == null || secondValue == null) { - return { - initialFirstValue: DEFAULT_FIRST, - initialSecondValue: DEFAULT_SECOND, - } - } - return { - initialFirstValue: firstValue, - initialSecondValue: secondValue, - } - } - const { initialFirstValue, initialSecondValue } = getInitialFirstValues() - - const [wellOrder, setWellOrder] = React.useState({ - firstValue: initialFirstValue, - secondValue: initialSecondValue, - }) - - React.useEffect(() => { - setWellOrder({ - firstValue: initialFirstValue, - secondValue: initialSecondValue, - }) - }, [initialFirstValue, initialSecondValue]) - - const applyChanges = (): void => { - updateValues(wellOrder.firstValue, wellOrder.secondValue) - } - - const handleReset = (): void => { - setWellOrder({ firstValue: DEFAULT_FIRST, secondValue: DEFAULT_SECOND }) - applyChanges() - closeModal() - } - - const handleCancel = (): void => { - const { initialFirstValue, initialSecondValue } = getInitialFirstValues() - setWellOrder({ - firstValue: initialFirstValue, - secondValue: initialSecondValue, - }) - closeModal() - } - - const handleDone = (): void => { - applyChanges() - closeModal() - } - - const makeOnChange = (ordinality: 'first' | 'second') => ( - event: React.ChangeEvent - ): void => { - const { value } = event.currentTarget - let nextState: State = { ...wellOrder, [`${ordinality}Value`]: value } - - if (ordinality === 'first') { - if ( - VERTICAL_VALUES.includes(value as WellOrderOption) && - VERTICAL_VALUES.includes(wellOrder.secondValue) - ) { - nextState = { ...nextState, secondValue: HORIZONTAL_VALUES[0] } - } else if ( - HORIZONTAL_VALUES.includes(value as WellOrderOption) && - HORIZONTAL_VALUES.includes(wellOrder.secondValue) - ) { - nextState = { ...nextState, secondValue: VERTICAL_VALUES[0] } - } - } - setWellOrder(nextState) - } - - const isSecondOptionDisabled = (value: WellOrderOption): boolean => { - if (VERTICAL_VALUES.includes(wellOrder.firstValue)) { - return VERTICAL_VALUES.includes(value) - } else if (HORIZONTAL_VALUES.includes(wellOrder.firstValue)) { - return HORIZONTAL_VALUES.includes(value) - } else { - return false - } - } - - if (!isOpen) return null - - return createPortal( - -
    -

    {t('modal:well_order.title')}

    -

    {t('modal:well_order.body')}

    -
    -
    - -
    - ({ - value, - name: t(`step_edit_form.field.well_order.option.${value}`), - }))} - /> - - {t('modal:well_order.then')} - - ({ - value, - name: t(`step_edit_form.field.well_order.option.${value}`), - disabled: isSecondOptionDisabled(value), - }))} - /> -
    -
    - - - -
    -
    - -
    - - -
    -
    -
    , - getMainPagePortalEl() - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderViz.tsx b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderViz.tsx deleted file mode 100644 index 2f1af411cb7..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/WellOrderViz.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import cx from 'classnames' - -import WELLS_IMAGE from '../../../../assets/images/well_order_wells.svg' -import PATH_IMAGE from '../../../../assets/images/well_order_path.svg' - -import type { WellOrderOption } from '../../../../form-types' - -import styles from './WellOrderInput.module.css' - -interface Props { - firstValue: WellOrderOption - secondValue: WellOrderOption -} - -export const WellOrderViz = (props: Props): JSX.Element => { - const { firstValue, secondValue } = props - - return ( -
    - - -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx deleted file mode 100644 index cf54bb141e1..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' -import { css } from 'styled-components' -import { - FormGroup, - Text, - LegacyTooltip, - useHoverTooltip, - FONT_WEIGHT_SEMIBOLD, - FONT_SIZE_BODY_1, - C_LIGHT_GRAY, -} from '@opentrons/components' -import cx from 'classnames' -import ZIG_ZAG_IMAGE from '../../../../assets/images/zig_zag_icon.svg' -import { WellOrderModal } from './WellOrderModal' -import stepEditStyles from '../../StepEditForm.module.css' -import styles from './WellOrderInput.module.css' -import type { FieldProps } from '../../types' -import type { WellOrderOption } from '../../../../form-types' - -export interface WellOrderFieldProps { - className?: string | null - label?: string - prefix: 'aspirate' | 'dispense' | 'mix' - firstValue?: WellOrderOption | null - secondValue?: WellOrderOption | null - firstName: string - secondName: string - updateFirstWellOrder: FieldProps['updateValue'] - updateSecondWellOrder: FieldProps['updateValue'] -} - -export const WellOrderField = (props: WellOrderFieldProps): JSX.Element => { - const { - firstValue, - secondValue, - firstName, - secondName, - updateFirstWellOrder, - updateSecondWellOrder, - } = props - const { t } = useTranslation(['form', 'modal']) - const [isModalOpen, setModalOpen] = useState(false) - - const handleOpen = (): void => { - setModalOpen(true) - } - const handleClose = (): void => { - setModalOpen(false) - } - - const updateValues = (firstValue: unknown, secondValue: unknown): void => { - updateFirstWellOrder(firstValue) - updateSecondWellOrder(secondValue) - } - - const getIconClassNames = (): string[] => { - const iconClassNames = [] - if (firstValue) { - iconClassNames.push(styles[`${firstValue}_first`]) - } - if (secondValue) { - iconClassNames.push(styles[`${secondValue}_second`]) - } - return iconClassNames - } - - const [targetProps, tooltipProps] = useHoverTooltip() - - const className = cx(props.className, { - [styles.small_field]: !props.label, - [stepEditStyles.no_label]: !props.label, - }) - - const mixedWellOrderStyles = css` - font-weight: ${FONT_WEIGHT_SEMIBOLD}; - font-size: ${FONT_SIZE_BODY_1}; - padding-top: 0.5rem; - padding-bottom: 0.325rem; - - &:hover { - background-color: ${C_LIGHT_GRAY}; - } - ` - - return ( - <> - - {t('step_edit_form.field.well_order.label')} - -
    - - {firstValue != null && secondValue != null ? ( - - ) : ( - - {t('step_edit_form.field.well_order.mixed')} - - )} - -
    - - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionField.tsx b/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionField.tsx deleted file mode 100644 index c1ed96d7dad..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionField.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { createPortal } from 'react-dom' -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { FormGroup, LegacyInputField } from '@opentrons/components' -import { COLUMN } from '@opentrons/shared-data' -import { - actions as stepsActions, - getSelectedStepId, - getWellSelectionLabwareKey, -} from '../../../../ui/steps' -import { selectors as stepFormSelectors } from '../../../../step-forms' -import { getMainPagePortalEl } from '../../../portals/MainPageModalPortal' -import { getNozzleType } from '../../utils' -import { WellSelectionModal } from './WellSelectionModal' -import styles from '../../StepEditForm.module.css' - -import type { FieldProps } from '../../types' - -export type Props = FieldProps & { - nozzles: string | null - pipetteId?: string | null - labwareId?: string | null -} - -export const WellSelectionField = (props: Props): JSX.Element => { - const { t } = useTranslation('form') - const { - nozzles, - labwareId, - pipetteId, - onFieldFocus, - value: selectedWells, - updateValue, - onFieldBlur, - name, - disabled, - errorToShow, - } = props - const dispatch = useDispatch() - const stepId = useSelector(getSelectedStepId) - const pipetteEntities = useSelector(stepFormSelectors.getPipetteEntities) - const wellSelectionLabwareKey = useSelector(getWellSelectionLabwareKey) - const primaryWellCount = - Array.isArray(selectedWells) && selectedWells.length > 0 - ? selectedWells.length.toString() - : undefined - const pipette = pipetteId != null ? pipetteEntities[pipetteId] : null - const nozzleType = getNozzleType(pipette, nozzles) - - const getModalKey = (): string => { - return `${String(stepId)}${name}${pipetteId || 'noPipette'}${ - labwareId || 'noLabware' - }` - } - - const onOpen = (key: string): void => { - dispatch(stepsActions.setWellSelectionLabwareKey(key)) - } - const handleOpen = (): void => { - if (onFieldFocus) { - onFieldFocus() - } - if (labwareId && pipetteId) { - onOpen(getModalKey()) - } - } - - const handleClose = (): void => { - if (onFieldBlur) { - onFieldBlur() - } - dispatch(stepsActions.clearWellSelectionLabwareKey()) - } - - const modalKey = getModalKey() - const label = - nozzleType === '8-channel' || nozzleType === COLUMN - ? t('step_edit_form.wellSelectionLabel.columns') - : t('step_edit_form.wellSelectionLabel.wells') - return ( - - - {createPortal( - , - getMainPagePortalEl() - )} - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.module.css b/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.module.css deleted file mode 100644 index 61fb8a26e32..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.module.css +++ /dev/null @@ -1,24 +0,0 @@ -@import '@opentrons/components/styles'; - -.inverted_text { - font-size: var(--fs-body-2); - color: var(--c-white); -} - -.top_row { - lost-utility: clearfix; - padding: 0 1rem; - - & > * { - lost-column: 1/3; - - &:last-child { - lost-offset: 1/3; - } - } -} - -.bottom_row { - padding-top: 2rem; - text-align: center; -} diff --git a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.tsx b/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.tsx deleted file mode 100644 index 4709da773e9..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/WellSelectionField/WellSelectionModal.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import * as React from 'react' -import cx from 'classnames' -import { useSelector } from 'react-redux' -import omit from 'lodash/omit' - -import { - LegacyModal, - OutlineButton, - LabeledValue, - WELL_LABEL_OPTIONS, -} from '@opentrons/components' -import { sortWells } from '@opentrons/shared-data' - -import { arrayToWellGroup } from '../../../../utils' -import * as wellContentsSelectors from '../../../../top-selectors/well-contents' -import { selectors } from '../../../../labware-ingred/selectors' -import { selectors as stepFormSelectors } from '../../../../step-forms' -import { WellSelectionInstructions } from '../../../WellSelectionInstructions' -import { SelectableLabware, wellFillFromWellContents } from '../../../labware' - -import type { LabwareDefinition2, PipetteV2Specs } from '@opentrons/shared-data' -import type { WellGroup } from '@opentrons/components' -import type { ContentsByWell } from '../../../../labware-ingred/types' -import type { WellIngredientNames } from '../../../../steplist/types' -import type { StepFieldName } from '../../../../form-types' -import type { NozzleType } from '../../../../types' - -import styles from './WellSelectionModal.module.css' -import modalStyles from '../../../modals/modal.module.css' - -interface WellSelectionModalProps { - isOpen: boolean - name: StepFieldName - onCloseClick: (e?: React.MouseEvent) => unknown - value: unknown - updateValue: (val: unknown | null | undefined) => void - nozzleType?: NozzleType | null - labwareId?: string | null - pipetteId?: string | null -} - -interface WellSelectionModalComponentProps { - deselectWells: (wellGroup: WellGroup) => unknown - nozzleType: NozzleType | null - handleSave: () => unknown - highlightedWells: WellGroup - ingredNames: WellIngredientNames - onCloseClick: (e?: React.MouseEvent) => unknown - selectedPrimaryWells: WellGroup - selectWells: (wellGroup: WellGroup) => unknown - updateHighlightedWells: (wellGroup: WellGroup) => unknown - wellContents: ContentsByWell - labwareDef?: LabwareDefinition2 | null - pipetteSpec?: PipetteV2Specs | null -} - -const WellSelectionModalComponent = ( - props: WellSelectionModalComponentProps -): JSX.Element => { - const { - deselectWells, - handleSave, - highlightedWells, - ingredNames, - labwareDef, - onCloseClick, - pipetteSpec, - selectedPrimaryWells, - selectWells, - wellContents, - updateHighlightedWells, - nozzleType, - } = props - const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) - - return ( - -
    - - - SAVE SELECTION - -
    - - {labwareDef != null ? ( - - ) : null} - - -
    - ) -} - -export const WellSelectionModal = ( - props: WellSelectionModalProps -): JSX.Element | null => { - const { - isOpen, - labwareId, - onCloseClick, - pipetteId, - nozzleType = null, - updateValue, - } = props - const wellFieldData = props.value - // selector data - const allWellContentsForStep = useSelector( - wellContentsSelectors.getAllWellContentsForActiveItem - ) - - const ingredNames = useSelector(selectors.getLiquidNamesById) - const labwareEntities = useSelector(stepFormSelectors.getLabwareEntities) - const pipetteEntities = useSelector(stepFormSelectors.getPipetteEntities) - - // selector-derived data - const labwareDef = - (labwareId != null ? labwareEntities[labwareId]?.def : null) ?? null - const pipette = pipetteId != null ? pipetteEntities[pipetteId] : null - - const initialSelectedPrimaryWells = Array.isArray(wellFieldData) - ? arrayToWellGroup(wellFieldData as string[]) - : {} - - // component state - const [ - selectedPrimaryWells, - setSelectedPrimaryWells, - ] = React.useState(initialSelectedPrimaryWells) - const [highlightedWells, setHighlightedWells] = React.useState({}) - - // actions - const selectWells = (wells: WellGroup): void => { - setSelectedPrimaryWells(prev => ({ ...prev, ...wells })) - setHighlightedWells({}) - } - - const deselectWells = (deselectedWells: WellGroup): void => { - setSelectedPrimaryWells(prev => omit(prev, Object.keys(deselectedWells))) - setHighlightedWells({}) - } - - const handleSave = (): void => { - const sortedWells = Object.keys(selectedPrimaryWells).sort(sortWells) - updateValue(sortedWells) - onCloseClick() - } - - if (!isOpen) return null - - return ( - - ) -} diff --git a/protocol-designer/src/components/StepEditForm/fields/__tests__/BlowoutZOffsetField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/__tests__/BlowoutZOffsetField.test.tsx deleted file mode 100644 index e994e2f8bed..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/__tests__/BlowoutZOffsetField.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type * as React from 'react' -import { describe, it, vi, beforeEach } from 'vitest' -import { fireEvent, screen } from '@testing-library/react' -import { fixture96Plate } from '@opentrons/shared-data' -import { SOURCE_WELL_BLOWOUT_DESTINATION } from '@opentrons/step-generation' -import { getLabwareEntities } from '../../../../step-forms/selectors' -import { renderWithProviders } from '../../../../__testing-utils__' -import { ZTipPositionModal } from '../TipPositionField/ZTipPositionModal' -import { BlowoutZOffsetField } from '../BlowoutZOffsetField' -import type { LabwareDefinition2 } from '@opentrons/shared-data' - -vi.mock('../../../../step-forms/selectors') -vi.mock('../TipPositionField/ZTipPositionModal') -const render = (props: React.ComponentProps) => { - return renderWithProviders()[0] -} -const mockSourceId = 'sourceId' -describe('BlowoutZOffsetField', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - disabled: false, - value: null, - name: 'blowout_z_offset', - updateValue: vi.fn(), - onFieldBlur: vi.fn(), - onFieldFocus: vi.fn(), - destLabwareId: SOURCE_WELL_BLOWOUT_DESTINATION, - sourceLabwareId: mockSourceId, - blowoutLabwareId: 'blowoutId', - } - vi.mocked(getLabwareEntities).mockReturnValue({ - [mockSourceId]: { - id: 'mockLabwareId', - labwareDefURI: 'mock uri', - def: fixture96Plate as LabwareDefinition2, - }, - }) - vi.mocked(ZTipPositionModal).mockReturnValue( -
    mock ZTipPositionModal
    - ) - }) - it('renders the input field', () => { - render(props) - screen.getByTestId('BlowoutZOffsetField_blowout_z_offset') - }) - it('renders the modal when input field is clicked on', () => { - render(props) - fireEvent.click(screen.getByTestId('BlowoutZOffsetField_blowout_z_offset')) - screen.getByText('mock ZTipPositionModal') - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/__tests__/DelayFields.test.tsx b/protocol-designer/src/components/StepEditForm/fields/__tests__/DelayFields.test.tsx deleted file mode 100644 index 596ddb35eff..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/__tests__/DelayFields.test.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { describe, it } from 'vitest' - -describe('DelayFields', () => { - it.todo('replace deprecated enzyme test') -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/__tests__/TiprackField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/__tests__/TiprackField.test.tsx deleted file mode 100644 index 3ce28a17ee8..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/__tests__/TiprackField.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type * as React from 'react' -import { describe, it, vi, beforeEach } from 'vitest' -import { screen } from '@testing-library/react' -import { i18n } from '../../../../assets/localization' -import { getPipetteEntities } from '../../../../step-forms/selectors' -import { renderWithProviders } from '../../../../__testing-utils__' -import { getTiprackOptions } from '../../../../ui/labware/selectors' -import { TiprackField } from '../TiprackField' - -vi.mock('../../../../ui/labware/selectors') -vi.mock('../../../../step-forms/selectors') - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} -const mockMockId = 'mockId' -describe('TiprackField', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - disabled: false, - value: null, - name: 'tipRackt', - updateValue: vi.fn(), - onFieldBlur: vi.fn(), - onFieldFocus: vi.fn(), - pipetteId: mockMockId, - } - vi.mocked(getPipetteEntities).mockReturnValue({ - [mockMockId]: { - name: 'p50_single_flex', - spec: {} as any, - id: mockMockId, - tiprackLabwareDef: [], - tiprackDefURI: ['mockDefURI1', 'mockDefURI2'], - }, - }) - vi.mocked(getTiprackOptions).mockReturnValue([ - { - value: 'mockDefURI1', - name: 'tiprack1', - }, - { - value: 'mockDefURI2', - name: 'tiprack2', - }, - ]) - }) - it('renders the dropdown field and texts', () => { - render(props) - screen.getByText('tip rack') - screen.getByText('tiprack1') - screen.getByText('tiprack2') - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/__tests__/WellOrderField.test.tsx b/protocol-designer/src/components/StepEditForm/fields/__tests__/WellOrderField.test.tsx deleted file mode 100644 index 7c5518c7489..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/__tests__/WellOrderField.test.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { describe, it } from 'vitest' - -describe('WellOrderField', () => { - it.todo('replace deprecated enzyme test') -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/__tests__/makeSingleEditFieldProps.test.ts b/protocol-designer/src/components/StepEditForm/fields/__tests__/makeSingleEditFieldProps.test.ts deleted file mode 100644 index d0a403bb8d5..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/__tests__/makeSingleEditFieldProps.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { vi, beforeEach, afterEach, expect, describe, it } from 'vitest' -import { makeSingleEditFieldProps } from '../makeSingleEditFieldProps' -import { - getDisabledFields, - getDefaultsForStepType, -} from '../../../../steplist/formLevel' -import { getFieldErrors } from '../../../../steplist/fieldLevel' -import * as stepEditFormUtils from '../../utils' -import type { HydratedFormdata } from '../../../../form-types' - -vi.mock('../../../../steplist/formLevel') -vi.mock('../../../../steplist/fieldLevel') - -const getFieldDefaultTooltipSpy = vi.spyOn( - stepEditFormUtils, - 'getFieldDefaultTooltip' -) - -const getSingleSelectDisabledTooltipSpy = vi.spyOn( - stepEditFormUtils, - 'getSingleSelectDisabledTooltip' -) - -beforeEach(() => { - getFieldDefaultTooltipSpy.mockImplementation(name => `tooltip for ${name}`) - getSingleSelectDisabledTooltipSpy.mockImplementation( - name => `disabled tooltip for ${name}` - ) -}) - -afterEach(() => { - vi.restoreAllMocks() -}) - -describe('makeSingleEditFieldProps', () => { - it('should create correct props for all fields in the given stepType', () => { - const focusedField = 'focused_error_field' - const dirtyFields = ['dirty_error_field', 'focused_error_field'] - - const focus: any = vi.fn() - const blur: any = vi.fn() - const handleChangeFormInput: any = vi.fn() - - const formData: any = { - stepType: 'fakeStepType', - some_field: '123', - disabled_field: '404', - pristine_error_field: '', - dirty_error_field: '', - focused_error_field: '', - } - - vi.mocked(getDisabledFields).mockImplementation( - (form: HydratedFormdata): Set => { - expect(form).toBe(formData) - const disabled = new Set() - disabled.add('disabled_field') - return disabled - } - ) - - vi.mocked(getDefaultsForStepType).mockImplementation(stepType => { - expect(stepType).toEqual('fakeStepType') - return { - some_field: 'default', - disabled_field: 'default', - pristine_error_field: '', - dirty_error_field: '', - focused_error_field: '', - } - }) - - vi.mocked(getFieldErrors).mockImplementation((name, value) => { - // pretend all the '*_error_field' fields have errors - // (though downstream of getFieldErrors, these errors won't be shown - // in errorToShow if field is pristine/focused) - if ( - name === 'pristine_error_field' || - name === 'dirty_error_field' || - name === 'focused_error_field' - ) { - return ['invalid value', 'field is required'] - } - return [] - }) - - const focusHandlers = { - focusedField, - dirtyFields, - focus, - blur, - } - const result = makeSingleEditFieldProps( - focusHandlers, - formData, - handleChangeFormInput, - formData, - [] - ) - expect(result).toEqual({ - some_field: { - disabled: false, - errorToShow: null, - name: 'some_field', - onFieldBlur: expect.anything(), - onFieldFocus: expect.anything(), - updateValue: expect.anything(), - value: '123', - tooltipContent: 'tooltip for some_field', - }, - disabled_field: { - disabled: true, - errorToShow: null, - name: 'disabled_field', - onFieldBlur: expect.anything(), - onFieldFocus: expect.anything(), - updateValue: expect.anything(), - value: '404', - tooltipContent: 'disabled tooltip for disabled_field', - }, - pristine_error_field: { - disabled: false, - errorToShow: null, - name: 'pristine_error_field', - onFieldBlur: expect.anything(), - onFieldFocus: expect.anything(), - updateValue: expect.anything(), - value: '', - tooltipContent: 'tooltip for pristine_error_field', - }, - dirty_error_field: { - disabled: false, - errorToShow: 'invalid value, field is required', - name: 'dirty_error_field', - onFieldBlur: expect.anything(), - onFieldFocus: expect.anything(), - updateValue: expect.anything(), - value: '', - tooltipContent: 'tooltip for dirty_error_field', - }, - focused_error_field: { - disabled: false, - errorToShow: null, - name: 'focused_error_field', - onFieldBlur: expect.anything(), - onFieldFocus: expect.anything(), - updateValue: expect.anything(), - value: '', - tooltipContent: 'tooltip for focused_error_field', - }, - }) - - // ensure the callbacks are wired up - ;[ - 'some_field', - 'disabled_field', - 'pristine_error_field', - 'dirty_error_field', - 'focused_error_field', - ].forEach(name => { - const { onFieldBlur, onFieldFocus, updateValue } = result[name] - - onFieldBlur() - expect(blur).toHaveBeenCalledWith(name) - - onFieldFocus() - expect(focus).toHaveBeenCalledWith(name) - - updateValue('foo') - expect(handleChangeFormInput).toHaveBeenCalledWith(name, 'foo') - - expect(vi.mocked(getFieldErrors)).toHaveBeenCalledWith( - name, - formData[name] - ) - }) - }) -}) diff --git a/protocol-designer/src/components/StepEditForm/fields/index.ts b/protocol-designer/src/components/StepEditForm/fields/index.ts deleted file mode 100644 index d89f07e4d5e..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* Generic Fields */ - -export { CheckboxRowField } from './CheckboxRowField' -export { RadioGroupField } from './RadioGroupField' -export { TextField } from './TextField' - -/* Specialized Fields */ - -export { BlowoutLocationField } from './BlowoutLocationField' -export { BlowoutZOffsetField } from './BlowoutZOffsetField' -export { ChangeTipField } from './ChangeTipField' -export { Configure96ChannelField } from './Configure96ChannelField' -export { DelayFields } from './DelayFields' -export { DisposalVolumeField } from './DisposalVolumeField' -export { DropTipField } from './DropTipField' -export { FlowRateField } from './FlowRateField' -export { LabwareField } from './LabwareField' -export { LabwareLocationField } from './LabwareLocationField' -export { MoveLabwareField } from './MoveLabwareField' -export { PathField } from './PathField/PathField' -export { PickUpTipField } from './PickUpTipField' -export { PipetteField } from './PipetteField' -export { ProfileItemRows } from './ProfileItemRows' -export { StepFormDropdown } from './StepFormDropdownField' -export { TipPositionField } from './TipPositionField' -export { TiprackField } from './TiprackField' -export { TipWellSelectionField } from './TipWellSelectionField' -export { ToggleRowField } from './ToggleRowField' -export { VolumeField } from './VolumeField' -export { WellOrderField } from './WellOrderField' -export { WellSelectionField } from './WellSelectionField/WellSelectionField' diff --git a/protocol-designer/src/components/StepEditForm/fields/makeSingleEditFieldProps.ts b/protocol-designer/src/components/StepEditForm/fields/makeSingleEditFieldProps.ts deleted file mode 100644 index 251f44d3a9d..00000000000 --- a/protocol-designer/src/components/StepEditForm/fields/makeSingleEditFieldProps.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { getFieldErrors } from '../../../steplist/fieldLevel' -import { - getDisabledFields, - getDefaultsForStepType, -} from '../../../steplist/formLevel' -import { - getFieldDefaultTooltip, - getSingleSelectDisabledTooltip, -} from '../utils' -import type { StepFieldName, FormData } from '../../../form-types' -import type { FieldProps, FieldPropsByName, FocusHandlers } from '../types' -interface ShowFieldErrorParams { - name: StepFieldName - focusedField: StepFieldName | null - dirtyFields?: StepFieldName[] -} -export const showFieldErrors = ({ - name, - focusedField, - dirtyFields, -}: ShowFieldErrorParams): boolean | undefined | StepFieldName[] => - !(name === focusedField) && dirtyFields && dirtyFields.includes(name) -export const makeSingleEditFieldProps = ( - focusHandlers: FocusHandlers, - formData: FormData, - handleChangeFormInput: (name: string, value: unknown) => void, - hydratedForm: { [key: string]: any }, // TODO: create real HydratedFormData type - t: any -): FieldPropsByName => { - const { dirtyFields, blur, focusedField, focus } = focusHandlers - const fieldNames: string[] = Object.keys( - getDefaultsForStepType(formData.stepType) - ) - return fieldNames.reduce((acc, name) => { - const disabled = hydratedForm - ? getDisabledFields(hydratedForm).has(name) - : false - const value = formData ? formData[name] : null - const showErrors = showFieldErrors({ - name, - focusedField, - dirtyFields, - }) - const errors = getFieldErrors(name, value) - const errorToShow = - showErrors && errors.length > 0 ? errors.join(', ') : null - - const updateValue = (value: unknown): void => { - handleChangeFormInput(name, value) - } - - const onFieldBlur = (): void => { - blur(name) - } - - const onFieldFocus = (): void => { - focus(name) - } - - const defaultTooltip = getFieldDefaultTooltip(name, t) - const disabledTooltip = getSingleSelectDisabledTooltip( - name, - formData.stepType, - t - ) - const fieldProps: FieldProps = { - disabled, - errorToShow, - name, - updateValue, - value, - onFieldBlur, - onFieldFocus, - tooltipContent: disabled ? disabledTooltip : defaultTooltip, - } - return { ...acc, [name]: fieldProps } - }, {}) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/AspDispSection.tsx b/protocol-designer/src/components/StepEditForm/forms/AspDispSection.tsx deleted file mode 100644 index fd8acbaed20..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/AspDispSection.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - IconButton, - LegacyTooltip, - useHoverTooltip, -} from '@opentrons/components' -import styles from '../StepEditForm.module.css' - -interface Props { - className?: string | null - collapsed?: boolean | null - toggleCollapsed: () => void - prefix: 'aspirate' | 'dispense' - children?: React.ReactNode -} - -export const AspDispSection = (props: Props): JSX.Element => { - const { children, className, collapsed, toggleCollapsed, prefix } = props - const [targetProps, tooltipProps] = useHoverTooltip() - const { t } = useTranslation('tooltip') - return ( - // @ts-expect-error(sa, 2021-7-2): className might be null -
    -
    - {prefix} - - {t('advanced_settings')} - -
    - -
    -
    - {children} -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/CommentForm/index.tsx b/protocol-designer/src/components/StepEditForm/forms/CommentForm/index.tsx deleted file mode 100644 index 80f02c775e5..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/CommentForm/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { FormGroup, SPACING } from '@opentrons/components' -import { TextField } from '../../fields' -import type { StepFormProps } from '../../types' - -import styles from '../../StepEditForm.module.css' - -export function CommentForm(props: StepFormProps): JSX.Element { - const { t } = useTranslation(['tooltip', 'application', 'form']) - - const { propsForFields } = props - - return ( -
    -
    - - {t('application:stepType.comment')} - -
    -
    - - - -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/HeaterShakerForm/index.tsx b/protocol-designer/src/components/StepEditForm/forms/HeaterShakerForm/index.tsx deleted file mode 100644 index 1c851f3608b..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/HeaterShakerForm/index.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import cx from 'classnames' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { - FormGroup, - Flex, - SPACING, - useHoverTooltip, - LegacyTooltip, - TOOLTIP_BOTTOM, -} from '@opentrons/components' -import { getHeaterShakerLabwareOptions } from '../../../../ui/modules/selectors' -import { - ToggleRowField, - TextField, - CheckboxRowField, - StepFormDropdown, -} from '../../fields' -import styles from '../../StepEditForm.module.css' - -import type { StepFormProps } from '../../types' - -export const HeaterShakerForm = (props: StepFormProps): JSX.Element | null => { - const moduleLabwareOptions = useSelector(getHeaterShakerLabwareOptions) - const [targetLatchProps, tooltipLatchProps] = useHoverTooltip({ - placement: TOOLTIP_BOTTOM, - }) - const { t } = useTranslation(['application', 'form']) - const { propsForFields, formData } = props - - return ( -
    - - {t('stepType.heaterShaker')} - - - - - -
    - -
    - - {formData.setHeaterShakerTemperature === true && ( - - )} -
    -
    - - -
    - - {formData.setShake === true && ( - - )} -
    -
    - - - - - -
    - - - - - - - - {propsForFields.latchOpen.disabled && ( - - {propsForFields.latchOpen.tooltipContent} - - )} - -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/MagnetForm.tsx b/protocol-designer/src/components/StepEditForm/forms/MagnetForm.tsx deleted file mode 100644 index 523e6c5f3f5..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/MagnetForm.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { FormGroup } from '@opentrons/components' -import { MAGNETIC_MODULE_V1 } from '@opentrons/shared-data' -import { selectors as uiModuleSelectors } from '../../../ui/modules' -import { getModuleEntities } from '../../../step-forms/selectors' -import { - MAX_ENGAGE_HEIGHT_V1, - MAX_ENGAGE_HEIGHT_V2, - MIN_ENGAGE_HEIGHT_V1, - MIN_ENGAGE_HEIGHT_V2, -} from '../../../constants' -import { TextField, RadioGroupField } from '../fields' -import type { StepFormProps } from '../types' - -import styles from '../StepEditForm.module.css' - -export function MagnetForm(props: StepFormProps): JSX.Element { - const moduleLabwareOptions = useSelector( - uiModuleSelectors.getMagneticLabwareOptions - ) - const moduleEntities = useSelector(getModuleEntities) - const { t } = useTranslation(['application', 'form']) - const { propsForFields, formData } = props - const { magnetAction, moduleId } = formData - - const moduleModel = moduleEntities[moduleId].model - const moduleOption: string | null | undefined = moduleLabwareOptions[0] - ? moduleLabwareOptions[0].name - : 'No magnetic module' - - const defaultEngageHeight = useSelector( - uiModuleSelectors.getMagnetLabwareEngageHeight - ) - const engageHeightMinMax = - moduleModel === MAGNETIC_MODULE_V1 - ? t('magnet_height_caption', { - low: MIN_ENGAGE_HEIGHT_V1, - high: MAX_ENGAGE_HEIGHT_V1, - }) - : t('magnet_height_caption', { - low: MIN_ENGAGE_HEIGHT_V2, - high: MAX_ENGAGE_HEIGHT_V2, - }) - const engageHeightDefault = - defaultEngageHeight != null - ? t('magnet_recommended', { default: defaultEngageHeight }) - : '' - const engageHeightCaption = `${engageHeightMinMax} ${engageHeightDefault}` - - return ( -
    -
    - - {t('stepType.magnet')} - -
    - -
    - -

    {moduleOption}

    -
    - - - - - {magnetAction === 'engage' && ( - - - - )} -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/MixForm.tsx b/protocol-designer/src/components/StepEditForm/forms/MixForm.tsx deleted file mode 100644 index 492ed633423..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/MixForm.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import { useState } from 'react' -import cx from 'classnames' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { FormGroup } from '@opentrons/components' -import { - getLabwareEntities, - getPipetteEntities, -} from '../../../step-forms/selectors' -import { getEnableReturnTip } from '../../../feature-flags/selectors' -import { - BlowoutLocationField, - BlowoutZOffsetField, - ChangeTipField, - CheckboxRowField, - Configure96ChannelField, - DelayFields, - DropTipField, - FlowRateField, - LabwareField, - PickUpTipField, - PipetteField, - TextField, - TipPositionField, - TiprackField, - TipWellSelectionField, - VolumeField, - WellOrderField, - WellSelectionField, -} from '../fields' -import { - getBlowoutLocationOptionsForForm, - getLabwareFieldForPositioningField, -} from '../utils' -import { AspDispSection } from './AspDispSection' - -import type { StepFormProps } from '../types' - -import styles from '../StepEditForm.module.css' - -export const MixForm = (props: StepFormProps): JSX.Element => { - const [collapsed, setCollapsed] = useState(true) - const pipettes = useSelector(getPipetteEntities) - const enableReturnTip = useSelector(getEnableReturnTip) - const labwares = useSelector(getLabwareEntities) - const { t } = useTranslation(['application', 'form']) - - const { propsForFields, formData } = props - const is96Channel = - propsForFields.pipette.value != null && - pipettes[String(propsForFields.pipette.value)].name === 'p1000_96' - const userSelectedPickUpTipLocation = - labwares[String(propsForFields.pickUpTip_location.value)] != null - const userSelectedDropTipLocation = - labwares[String(propsForFields.dropTip_location.value)] != null - - const toggleCollapsed = (): void => { - setCollapsed(prevCollapsed => !prevCollapsed) - } - return ( -
    -
    - {t('stepType.mix')} -
    -
    - - - {is96Channel ? ( - - ) : null} - - - - -
    -
    - - - - -
    -
    - -
    - - -
    - - {!collapsed && ( -
    -
    -
    - - - -
    - -
    - -
    -
    - -
    -
    - - - - - - - - - - -
    -
    -
    - )} - -
    - - {t('form:step_edit_form.section.sterility')} - - - {t('form:step_edit_form.section.dropTip')} - -
    -
    -
    - -
    -
    -
    - - {enableReturnTip - ? t('form:step_edit_form.section.pickUpAndDrop') - : t('form:step_edit_form.section.dropTip')} - -
    -
    - {enableReturnTip ? ( - <> - - {userSelectedPickUpTipLocation ? ( - - ) : null} - - ) : null} - - {userSelectedDropTipLocation ? ( - - ) : null} -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/MoveLabwareForm/index.tsx b/protocol-designer/src/components/StepEditForm/forms/MoveLabwareForm/index.tsx deleted file mode 100644 index 87811db0087..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/MoveLabwareForm/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { - ALIGN_CENTER, - Flex, - FormGroup, - SPACING, - LegacyTooltip, - TOOLTIP_BOTTOM, - TOOLTIP_FIXED, - useHoverTooltip, -} from '@opentrons/components' -import { - LabwareLocationField, - CheckboxRowField, - MoveLabwareField, -} from '../../fields' -import styles from '../../StepEditForm.module.css' -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { getRobotType } from '../../../../file-data/selectors' -import { - getAdditionalEquipment, - getCurrentFormCanBeSaved, -} from '../../../../step-forms/selectors' -import type { StepFormProps } from '../../types' - -export const MoveLabwareForm = (props: StepFormProps): JSX.Element => { - const { propsForFields } = props - const { t } = useTranslation(['application', 'form', 'tooltip']) - const robotType = useSelector(getRobotType) - const canSave = useSelector(getCurrentFormCanBeSaved) - const additionalEquipment = useSelector(getAdditionalEquipment) - const isGripperAttached = Object.values(additionalEquipment).some( - equipment => equipment?.name === 'gripper' - ) - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_BOTTOM, - strategy: TOOLTIP_FIXED, - }) - return ( -
    -
    - - {t('stepType.moveLabware')} - -
    -
    - - - - {robotType === FLEX_ROBOT_TYPE ? ( - - {!isGripperAttached ? ( - - {t('tooltip:step_fields.moveLabware.disabled.gripper_not_used')} - - ) : null} -
    - - - -
    -
    - ) : null} -
    -
    - - - -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/SourceDestFields.tsx b/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/SourceDestFields.tsx deleted file mode 100644 index a47f47eb7d4..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/SourceDestFields.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { getAdditionalEquipmentEntities } from '../../../../step-forms/selectors' - -import { - BlowoutLocationField, - CheckboxRowField, - DelayFields, - FlowRateField, - TextField, - TipPositionField, - WellOrderField, - BlowoutZOffsetField, -} from '../../fields' -import { MixFields } from '../../fields/MixFields' -import { - getBlowoutLocationOptionsForForm, - getLabwareFieldForPositioningField, -} from '../../utils' -import styles from '../../StepEditForm.module.css' - -import type { FormData } from '../../../../form-types' -import type { StepFieldName } from '../../../../steplist/fieldLevel' -import type { FieldPropsByName } from '../../types' - -interface SourceDestFieldsProps { - className?: string | null - prefix: 'aspirate' | 'dispense' - propsForFields: FieldPropsByName - formData: FormData -} - -const makeAddFieldNamePrefix = (prefix: string) => ( - fieldName: string -): StepFieldName => `${prefix}_${fieldName}` - -export const SourceDestFields = (props: SourceDestFieldsProps): JSX.Element => { - const { className, formData, prefix, propsForFields } = props - const { t } = useTranslation(['form', 'application']) - - const additionalEquipmentEntities = useSelector( - getAdditionalEquipmentEntities - ) - const isWasteChuteSelected = - propsForFields.dispense_labware?.value != null - ? additionalEquipmentEntities[ - String(propsForFields.dispense_labware.value) - ]?.name === 'wasteChute' - : false - const isTrashBinSelected = - propsForFields.dispense_labware?.value != null - ? additionalEquipmentEntities[ - String(propsForFields.dispense_labware.value) - ]?.name === 'trashBin' - : false - - const addFieldNamePrefix = makeAddFieldNamePrefix(prefix) - const getDelayFields = (): JSX.Element => ( - - ) - - const hideWellOrderField = - prefix === 'dispense' && (isWasteChuteSelected || isTrashBinSelected) - - const getMixFields = (): JSX.Element => ( - - ) - - return ( - // @ts-expect-error(sa, 2021-7-2): className might be null -
    -
    - - - {hideWellOrderField ? null : ( - - )} -
    - -
    - {prefix === 'aspirate' && ( - <> - - {getMixFields()} - {getDelayFields()} - - )} - {prefix === 'dispense' && ( - <> - {getDelayFields()} - {getMixFields()} - - )} - - {prefix === 'dispense' && ( - - - - - - )} - - - - - - - - -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/SourceDestHeaders.tsx b/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/SourceDestHeaders.tsx deleted file mode 100644 index 7e1aab6c5ff..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/SourceDestHeaders.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { FormGroup } from '@opentrons/components' -import { getAdditionalEquipmentEntities } from '../../../../step-forms/selectors' -import { LabwareField, WellSelectionField } from '../../fields' -import { AspDispSection } from '../AspDispSection' -import type { StepFieldName } from '../../../../steplist/fieldLevel' -import type { FormData } from '../../../../form-types' -import type { FieldPropsByName } from '../../types' - -import styles from '../../StepEditForm.module.css' - -interface Props { - className?: string | null - collapsed?: boolean | null - formData: FormData - prefix: 'aspirate' | 'dispense' - propsForFields: FieldPropsByName - toggleCollapsed: () => void -} - -const makeAddFieldNamePrefix = (prefix: string) => ( - fieldName: string -): StepFieldName => `${prefix}_${fieldName}` - -export const SourceDestHeaders = (props: Props): JSX.Element => { - const { - className, - collapsed, - toggleCollapsed, - prefix, - propsForFields, - formData, - } = props - const { t } = useTranslation('form') - const addFieldNamePrefix = makeAddFieldNamePrefix(prefix) - const additionalEquipmentEntities = useSelector( - getAdditionalEquipmentEntities - ) - const labwareLabel = t(`step_edit_form.labwareLabel.${prefix}`) - const trashOrLabwareId = formData[addFieldNamePrefix('labware')] - const isDisposalLocation = - additionalEquipmentEntities[trashOrLabwareId]?.name === 'wasteChute' || - additionalEquipmentEntities[trashOrLabwareId]?.name === 'trashBin' - - return ( - -
    - - - - {isDisposalLocation ? null : ( - - )} -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/index.tsx b/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/index.tsx deleted file mode 100644 index 959d5b4c99f..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/MoveLiquidForm/index.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' -import cx from 'classnames' -import { useSelector } from 'react-redux' -import { - getLabwareEntities, - getPipetteEntities, -} from '../../../../step-forms/selectors' -import { getEnableReturnTip } from '../../../../feature-flags/selectors' -import { - VolumeField, - PipetteField, - ChangeTipField, - DisposalVolumeField, - PathField, - TiprackField, - DropTipField, - PickUpTipField, - TipWellSelectionField, - Configure96ChannelField, -} from '../../fields' -import { SourceDestFields } from './SourceDestFields' -import { SourceDestHeaders } from './SourceDestHeaders' -import type { StepFormProps } from '../../types' - -import styles from '../../StepEditForm.module.css' - -// TODO: BC 2019-01-25 instead of passing path from here, put it in connect fields where needed -// or question if it even needs path - -export const MoveLiquidForm = (props: StepFormProps): JSX.Element => { - const { propsForFields, formData } = props - const { stepType, path } = formData - const { t } = useTranslation(['application', 'form']) - const [collapsed, _setCollapsed] = useState(true) - const enableReturnTip = useSelector(getEnableReturnTip) - const labwares = useSelector(getLabwareEntities) - const pipettes = useSelector(getPipetteEntities) - const toggleCollapsed = (): void => { - _setCollapsed(!collapsed) - } - const userSelectedPickUpTipLocation = - labwares[String(propsForFields.pickUpTip_location.value)] != null - const userSelectedDropTipLocation = - labwares[String(propsForFields.dropTip_location.value)] != null - - const is96Channel = - propsForFields.pipette.value != null && - pipettes[String(propsForFields.pipette.value)].name === 'p1000_96' - - return ( -
    -
    - - {t('stepType.moveLiquid')} - -
    -
    - - - {is96Channel ? ( - - ) : null} - -
    - -
    - - -
    - - {!collapsed && ( -
    - - -
    - )} - -
    - - {t('form:step_edit_form.section.sterility&motion')} - -
    -
    -
    - - -
    -
    - {path === 'multiDispense' && ( - - )} -
    -
    -
    - - {enableReturnTip - ? t('form:step_edit_form.section.pickUpAndDrop') - : t('form:step_edit_form.section.dropTip')} - -
    -
    - {enableReturnTip ? ( - <> - - {userSelectedPickUpTipLocation ? ( - - ) : null} - - ) : null} - - {userSelectedDropTipLocation && enableReturnTip ? ( - - ) : null} -
    -
    - ) -} diff --git a/protocol-designer/src/components/StepEditForm/forms/PauseForm.tsx b/protocol-designer/src/components/StepEditForm/forms/PauseForm.tsx deleted file mode 100644 index 01fb333f9b2..00000000000 --- a/protocol-designer/src/components/StepEditForm/forms/PauseForm.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import type * as React from 'react' -import { useTranslation } from 'react-i18next' -import cx from 'classnames' -import { useSelector } from 'react-redux' -import { - FormGroup, - useHoverTooltip, - LegacyTooltip, - TOOLTIP_BOTTOM, - TOOLTIP_FIXED, -} from '@opentrons/components' - -import { selectors as uiModuleSelectors } from '../../../ui/modules' -import { - PAUSE_UNTIL_RESUME, - PAUSE_UNTIL_TIME, - PAUSE_UNTIL_TEMP, -} from '../../../constants' -import { TextField, RadioGroupField, StepFormDropdown } from '../fields' -import { getSingleSelectDisabledTooltip } from '../utils' -import styles from '../StepEditForm.module.css' - -import type { StepFormProps } from '../types' - -export const PauseForm = (props: StepFormProps): JSX.Element => { - const tempModuleLabwareOptions = useSelector( - uiModuleSelectors.getTemperatureLabwareOptions - ) - const { t } = useTranslation(['tooltip', 'application', 'form']) - - const heaterShakerModuleLabwareOptions = useSelector( - uiModuleSelectors.getHeaterShakerLabwareOptions - ) - - const moduleLabwareOptions = [ - ...tempModuleLabwareOptions, - ...heaterShakerModuleLabwareOptions, - ] - - const pauseUntilTempEnabled = useSelector( - uiModuleSelectors.getTempModuleIsOnDeck - ) - - const pauseUntilHeaterShakerEnabled = useSelector( - uiModuleSelectors.getHeaterShakerModuleIsOnDeck - ) - - const pauseUntilModuleEnabled = - pauseUntilTempEnabled || pauseUntilHeaterShakerEnabled - - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_BOTTOM, - strategy: TOOLTIP_FIXED, - }) - - const { propsForFields } = props - const { pauseAction } = props.formData - - return ( -
    -
    - - {t('application:stepType.pause')} - -
    - -
    -
    -
    - -
    -
    - -
    - {pauseAction === PAUSE_UNTIL_TIME && ( -
    - - - -
    - )} - - {pauseUntilModuleEnabled ? null : ( - - {getSingleSelectDisabledTooltip( - 'wait_until_temp', - 'pauseAction', - t - )} - - )} -
    -
    - -
    - {pauseAction === PAUSE_UNTIL_TEMP && ( -
    - - - - - - -
    - )} -
    -
    -
    - {pauseAction === PAUSE_UNTIL_TEMP ? null : ( -
    - {/* TODO: Ian 2019-03-25 consider making this a component eg `TextAreaField.js` if used anywhere else */} - -