Skip to content

Commit

Permalink
feat(protocol-designer): magnetic module change hint wire up (#16498)
Browse files Browse the repository at this point in the history
closes AUTH-797
  • Loading branch information
jerader authored Oct 24, 2024
1 parent 545d0f7 commit a0086cc
Show file tree
Hide file tree
Showing 9 changed files with 481 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
"add_liquid": "Add liquid",
"add_module": "Add a module",
"add_rest": "Add labware and liquids to complete deck setup",
"alter_pause": "You may also need to alter the time you pause while your magnet is engaged.",
"aluminumBlock": "Aluminum block",
"clear_labware": "Clear labware",
"clear_slot": "Clear slot",
"clear": "Clear",
"command_click_to_multi_select": "Command + Click for multi-select",
"convert_gen1_to_gen2": "To convert engage heights from GEN1 to GEN2, divide your engage height by 2.",
"convert_gen2_to_gen1": "To convert engage heights from GEN2 to GEN1, multiply your engage height by 2.",
"custom": "Custom labware definitions",
"customize_slot": "Customize slot",
"deck_hardware": "Deck hardware",
Expand All @@ -25,9 +29,11 @@
"edit_protocol": "Edit protocol",
"edit_slot": "Edit slot",
"edit": "Edit",
"gen1_gen2_different_units": "Switching between GEN1 and GEN2 Magnetic Modules will clear all non-default engage heights from existing magnet steps in your protocol. GEN1 and GEN2 Magnetic Modules do not use the same units.",
"heater_shaker_adjacent_to": "A module is adjacent to this slot. The Heater-Shaker cannot be placed next to a module",
"heater_shaker_adjacent": "A Heater-Shaker is adjacent to this slot. Modules cannot be placed next to a Heater-Shaker",
"heater_shaker_trash": "The heater-shaker cannot be next to the trash bin",
"here": "here.",
"labware": "Labware",
"liquids": "Liquids",
"no_offdeck_labware": "No off-deck labware added",
Expand All @@ -37,8 +43,8 @@
"onDeck": "On deck",
"one_item": "No more than 1 {{hardware}} allowed on the deck at one time",
"only_display_rec": "Only display recommended labware",
"command_click_to_multi_select": "Command + Click for multi-select",
"protocol_starting_deck": "Protocol starting deck",
"read_more_gen1_gen2": "Read more about the differences between GEN1 and GEN2 Magnetic Modules",
"rename_lab": "Rename labware",
"reservoir": "Reservoir",
"shift_click_to_select_all": "Shift + Click to select all",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { fireEvent, screen } from '@testing-library/react'
import { i18n } from '../../../assets/localization'
import { renderWithProviders } from '../../../__testing-utils__'
import { removeHint } from '../../../tutorial/actions'
import { BlockingHintModal } from '..'
import type { ComponentProps } from 'react'

vi.mock('../../../tutorial/actions')

const render = (props: ComponentProps<typeof BlockingHintModal>) => {
return renderWithProviders(<BlockingHintModal {...props} />, {
i18nInstance: i18n,
})[0]
}

describe('BlockingHintModal', () => {
let props: ComponentProps<typeof BlockingHintModal>

beforeEach(() => {
props = {
content: <div>mock content</div>,
handleCancel: vi.fn(),
handleContinue: vi.fn(),
hintKey: 'change_magnet_module_model',
}
})
it('renders the hint with buttons and checkbox', () => {
render(props)
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
expect(props.handleCancel).toHaveBeenCalled()
expect(vi.mocked(removeHint)).toHaveBeenCalled()
fireEvent.click(screen.getByRole('button', { name: 'Continue' }))
expect(props.handleContinue).toHaveBeenCalled()
expect(vi.mocked(removeHint)).toHaveBeenCalled()
screen.getByText('mock content')
})
})
86 changes: 86 additions & 0 deletions protocol-designer/src/organisms/BlockingHintModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useCallback, useState } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import {
ALIGN_CENTER,
COLORS,
Check,
Flex,
JUSTIFY_SPACE_BETWEEN,
Modal,
PrimaryButton,
SPACING,
SecondaryButton,
StyledText,
} from '@opentrons/components'
import { actions } from '../../tutorial'
import { getMainPagePortalEl } from '../../components/portals/MainPageModalPortal'
import type { ReactNode } from 'react'
import type { HintKey } from '../../tutorial'

export interface HintProps {
hintKey: HintKey
handleCancel: () => void
handleContinue: () => void
content: ReactNode
}

export function BlockingHintModal(props: HintProps): JSX.Element {
const { content, hintKey, handleCancel, handleContinue } = props
const { t, i18n } = useTranslation(['alert', 'shared'])
const dispatch = useDispatch()

const [rememberDismissal, setRememberDismissal] = useState<boolean>(false)

const toggleRememberDismissal = useCallback(() => {
setRememberDismissal(prevDismissal => !prevDismissal)
}, [])

const onCancelClick = (): void => {
dispatch(actions.removeHint(hintKey, rememberDismissal))
handleCancel()
}

const onContinueClick = (): void => {
dispatch(actions.removeHint(hintKey, rememberDismissal))
handleContinue()
}

return createPortal(
<Modal
type="warning"
title={t(`hint.${hintKey}.title`)}
onClose={onCancelClick}
footer={
<Flex
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_SPACE_BETWEEN}
padding={SPACING.spacing24}
>
<Flex
alignItems={ALIGN_CENTER}
onClick={toggleRememberDismissal}
gridGap={SPACING.spacing8}
>
<Check isChecked={rememberDismissal} color={COLORS.blue50} />
<StyledText desktopStyle="bodyDefaultRegular">
{t('hint.dont_show_again')}
</StyledText>
</Flex>
<Flex alingItems={ALIGN_CENTER} gridGap={SPACING.spacing8}>
<SecondaryButton onClick={onCancelClick}>
{t('shared:cancel')}
</SecondaryButton>
<PrimaryButton onClick={onContinueClick}>
{i18n.format(t('shared:continue'), 'capitalize')}
</PrimaryButton>
</Flex>
</Flex>
}
>
{content}
</Modal>,
getMainPagePortalEl()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useSelector } from 'react-redux'
import { getDismissedHints } from '../../tutorial/selectors'
import { BlockingHintModal } from './index'
import type { HintKey } from '../../tutorial'

export interface HintProps {
/** `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: HintProps): JSX.Element | null => {
const { enabled, hintKey, handleCancel, handleContinue, content } = args
const isDismissed = useSelector(getDismissedHints).includes(hintKey)

if (isDismissed) {
if (enabled) {
handleContinue()
}
return null
}

if (!enabled) {
return null
}

return (
<BlockingHintModal
hintKey={hintKey}
handleCancel={handleCancel}
handleContinue={handleContinue}
content={content}
/>
)
}
1 change: 1 addition & 0 deletions protocol-designer/src/organisms/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './Alerts'
export * from './AnnouncementModal'
export * from './AssignLiquidsModal'
export * from './BlockingHintModal'
export * from './DefineLiquidsModal'
export * from './EditInstrumentsModal'
export * from './EditNickNameModal'
Expand Down
Loading

0 comments on commit a0086cc

Please sign in to comment.