diff --git a/protocol-designer/src/assets/localization/en/protocol_steps.json b/protocol-designer/src/assets/localization/en/protocol_steps.json index f4eeaf8ff57..a8f3bf2665c 100644 --- a/protocol-designer/src/assets/localization/en/protocol_steps.json +++ b/protocol-designer/src/assets/localization/en/protocol_steps.json @@ -35,5 +35,6 @@ "starting_deck_state": "Starting deck state", "temperature": "Temperature", "time": "Time", - "view_commands": "View commands" + "view_commands": "View commands", + "view_details": "View step details" } diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx index 1a13e910c63..7a07205b25c 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx @@ -1,5 +1,6 @@ import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' +import { useConditionalConfirm } from '@opentrons/components' import * as timelineWarningSelectors from '../../../../top-selectors/timelineWarnings' import { selectors as dismissSelectors } from '../../../../dismiss' import { selectors as stepFormSelectors } from '../../../../step-forms' @@ -11,7 +12,11 @@ import { getSelectedStepId, } from '../../../../ui/steps' import { selectors as fileDataSelectors } from '../../../../file-data' - +import { + CLOSE_STEP_FORM_WITH_CHANGES, + CLOSE_UNSAVED_STEP_FORM, + ConfirmDeleteModal, +} from '../../../../components/modals/ConfirmDeleteModal' import { stepIconsByType } from '../../../../form-types' import { getOrderedStepIds } from '../../../../step-forms/selectors' import { StepContainer } from './StepContainer' @@ -20,6 +25,7 @@ import type { ThunkDispatch } from 'redux-thunk' import type { HoverOnStepAction } from '../../../../ui/steps' import type { StepIdType } from '../../../../form-types' import type { BaseState, ThunkAction } from '../../../../types' +import type { DeleteModalType } from '../../../../components/modals/ConfirmDeleteModal' export interface ConnectedStepInfoProps { stepId: StepIdType @@ -57,7 +63,12 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element { const selected: boolean = multiSelectItemIds?.length ? multiSelectItemIds.includes(stepId) : selectedStepId === stepId - + const currentFormIsPresaved = useSelector( + stepFormSelectors.getCurrentFormIsPresaved + ) + const singleEditFormHasUnsavedChanges = useSelector( + stepFormSelectors.getCurrentFormHasUnsavedChanges + ) const selectStep = (): ThunkAction => dispatch(stepsActions.selectStep(stepId)) const selectStepOnDoubleClick = (): ThunkAction => @@ -67,23 +78,49 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element { const unhighlightStep = (): HoverOnStepAction => dispatch(stepsActions.hoverOnStep(null)) + const handleStepItemSelection = (): void => { + selectStepOnDoubleClick() + } + + const { confirm, showConfirmation, cancel } = useConditionalConfirm( + handleStepItemSelection, + currentFormIsPresaved || singleEditFormHasUnsavedChanges + ) + + const getModalType = (): DeleteModalType => { + if (currentFormIsPresaved) { + return CLOSE_UNSAVED_STEP_FORM + } else { + return CLOSE_STEP_FORM_WITH_CHANGES + } + } + const iconName = stepIconsByType[step.stepType] return ( - + <> + {showConfirmation && ( + + )} + + ) } diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx index a39ab112562..2a9330b4a15 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' import { createPortal } from 'react-dom' import { ALIGN_CENTER, @@ -11,21 +10,17 @@ import { CURSOR_POINTER, Flex, Icon, - JUSTIFY_CENTER, JUSTIFY_SPACE_BETWEEN, JUSTIFY_START, OverflowBtn, SPACING, StyledText, } from '@opentrons/components' -import { getUnsavedForm } from '../../../../step-forms/selectors' import { getTopPortalEl } from '../../../../components/portals/TopPortal' import { StepOverflowMenu } from './StepOverflowMenu' import { capitalizeFirstLetterAfterNumber } from './utils' import type { IconName } from '@opentrons/components' -import { cancelStepForm } from '../../../../steplist/actions' -import { ThunkDispatch } from '../../../../types' const STARTING_DECK_STATE = 'Starting deck state' const FINAL_DECK_STATE = 'Final deck state' @@ -60,8 +55,6 @@ export function StepContainer(props: StepContainerProps): JSX.Element { hasError = false, isStepAfterError = false, } = props - const formData = useSelector(getUnsavedForm) - const dispatch = useDispatch>() const [top, setTop] = React.useState(0) const menuRootRef = React.useRef(null) const [stepOverflowMenu, setStepOverflowMenu] = React.useState(false) @@ -130,7 +123,7 @@ export function StepContainer(props: StepContainerProps): JSX.Element { onClick={onClick} padding={SPACING.spacing12} borderRadius={BORDERS.borderRadius8} - width={formData != null ? '6rem' : '100%'} + width="100%" backgroundColor={backgroundColor} color={color} opacity={isStepAfterError ? '50%' : '100%'} @@ -144,19 +137,17 @@ export function StepContainer(props: StepContainerProps): JSX.Element { {iconName && ( )} - {formData != null ? null : ( - - {capitalizeFirstLetterAfterNumber(title)} - - )} + + {capitalizeFirstLetterAfterNumber(title)} + - {selected && !isStartingOrEndingState && formData == null ? ( + {selected && !isStartingOrEndingState ? ( { dispatch(steplistActions.deleteStep(stepId)) } + const formData = useSelector(getUnsavedForm) const currentFormIsPresaved = useSelector(getCurrentFormIsPresaved) const singleEditFormHasUnsavedChanges = useSelector( getCurrentFormHasUnsavedChanges @@ -99,8 +101,11 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { e.stopPropagation() }} > - {t('edit_step')} + {formData != null ? null : ( + {t('edit_step')} + )} { console.log('wire this up') }} @@ -108,11 +113,12 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { {t('view_commands')} { console.log('wire this up') }} > - {t('add_details')} + {t('view_details')} { diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx index caf1c19f740..7aeabd53802 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx @@ -61,7 +61,7 @@ export const TimelineToolbox = (): JSX.Element => { return ( { } vi.mocked(getCurrentFormIsPresaved).mockReturnValue(false) vi.mocked(getCurrentFormHasUnsavedChanges).mockReturnValue(false) + vi.mocked(getUnsavedForm).mockReturnValue(null) }) it('renders each button and clicking them calls the action', () => { @@ -46,7 +48,7 @@ describe('StepOverflowMenu', () => { fireEvent.click(screen.getByText('Edit step')) expect(vi.mocked(populateForm)).toHaveBeenCalled() fireEvent.click(screen.getByText('View commands')) - fireEvent.click(screen.getByText('Add step details')) - // TODO: wire up view commands and add step details + fireEvent.click(screen.getByText('View step details')) + // TODO: wire up view commands and view step details }) }) diff --git a/protocol-designer/src/ui/steps/actions/__tests__/actions.test.ts b/protocol-designer/src/ui/steps/actions/__tests__/actions.test.ts index 25e4c2e9d91..22868cb6328 100644 --- a/protocol-designer/src/ui/steps/actions/__tests__/actions.test.ts +++ b/protocol-designer/src/ui/steps/actions/__tests__/actions.test.ts @@ -75,6 +75,10 @@ describe('steps actions', () => { type: 'SELECT_STEP', payload: stepId, }, + { + type: 'POPULATE_FORM', + payload: null, + }, ]) }) }) diff --git a/protocol-designer/src/ui/steps/actions/actions.ts b/protocol-designer/src/ui/steps/actions/actions.ts index 417ba8dfa2e..463d8e7caa0 100644 --- a/protocol-designer/src/ui/steps/actions/actions.ts +++ b/protocol-designer/src/ui/steps/actions/actions.ts @@ -110,6 +110,10 @@ export const selectStep = (stepId: StepIdType): ThunkAction => ( payload: stepId, } dispatch(selectStepAction) + dispatch({ + type: 'POPULATE_FORM', + payload: null, + }) resetScrollElements() }