Skip to content

Commit

Permalink
feat(protocol-designer): keyboard hot key display (#16477)
Browse files Browse the repository at this point in the history
closes AUTH-881
  • Loading branch information
jerader authored Oct 15, 2024
1 parent 1edb0fb commit b0e5188
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@
"OT_PD_ENABLE_RETURN_TIP": {
"title": "Enable return tip",
"description": "You can choose which tip to pick up and where to drop tip."
},
"OT_PD_ENABLE_HOT_KEYS_DISPLAY": {
"title": "Timeline editing tips",
"description": "Show tips for working with steps next to the protocol timeline"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"deck_hardware": "Deck hardware",
"define_liquid": "Define a liquid",
"done": "Done",
"double_click_to_edit": "Double-click to edit",
"duplicate": "Duplicate labware",
"edit_hw_lw": "Edit hardware/labware",
"edit_labware": "Edit labware",
Expand All @@ -36,9 +37,11 @@
"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",
"rename_lab": "Rename labware",
"reservoir": "Reservoir",
"shift_click_to_select_all": "Shift + Click to select all",
"starting_deck_state": "Starting deck state",
"tc_slots_occupied_flex": "The Thermocycler needs slots A1 and B1. Slot A1 is occupied",
"tc_slots_occupied_ot2": "The Thermocycler needs slots 7, 8, 10, and 11. One or more of those slots is occupied",
Expand Down
2 changes: 2 additions & 0 deletions protocol-designer/src/feature-flags/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const initialFlags: Flags = {
OT_PD_ENABLE_MOAM: process.env.OT_PD_ENABLE_MOAM === '1' || false,
OT_PD_ENABLE_COMMENT: process.env.OT_PD_ENABLE_COMMENT === '1' || false,
OT_PD_ENABLE_RETURN_TIP: process.env.OT_PD_ENABLE_RETURN_TIP === '1' || false,
OT_PD_ENABLE_HOT_KEYS_DISPLAY:
process.env.OT_PD_ENABLE_HOT_KEYS_DISPLAY === '1' || true,
}
// @ts-expect-error(sa, 2021-6-10): cannot use string literals as action type
// TODO IMMEDIATELY: refactor this to the old fashioned way if we cannot have type safety: https://github.com/redux-utilities/redux-actions/issues/282#issuecomment-595163081
Expand Down
4 changes: 4 additions & 0 deletions protocol-designer/src/feature-flags/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ export const getEnableReturnTip: Selector<boolean> = createSelector(
getFeatureFlagData,
flags => flags.OT_PD_ENABLE_RETURN_TIP ?? false
)
export const getEnableHotKeysDisplay: Selector<boolean> = createSelector(
getFeatureFlagData,
flags => flags.OT_PD_ENABLE_HOT_KEYS_DISPLAY ?? false
)
2 changes: 2 additions & 0 deletions protocol-designer/src/feature-flags/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ export type FlagTypes =
| 'OT_PD_ENABLE_MOAM'
| 'OT_PD_ENABLE_COMMENT'
| 'OT_PD_ENABLE_RETURN_TIP'
| 'OT_PD_ENABLE_HOT_KEYS_DISPLAY'
// flags that are not in this list only show in prerelease mode
export const userFacingFlags: FlagTypes[] = [
'OT_PD_DISABLE_MODULE_RESTRICTIONS',
'OT_PD_ALLOW_ALL_TIPRACKS',
'OT_PD_ENABLE_HOT_KEYS_DISPLAY',
]
export const allFlags: FlagTypes[] = [
...userFacingFlags,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { describe, it, vi, beforeEach, expect } from 'vitest'
import '@testing-library/jest-dom/vitest'
import { fireEvent, screen } from '@testing-library/react'
import { i18n } from '../../../../assets/localization'
import { renderWithProviders } from '../../../../__testing-utils__'
import { getUnsavedForm } from '../../../../step-forms/selectors'
import { getSelectedSubstep } from '../../../../ui/steps/selectors'
import { getEnableHotKeysDisplay } from '../../../../feature-flags/selectors'
import { DeckSetupContainer } from '../../DeckSetup'
import { OffDeck } from '../../Offdeck'
import { ProtocolSteps } from '..'
Expand All @@ -15,15 +17,12 @@ vi.mock('../../../../ui/steps/selectors')
vi.mock('../StepForm')
vi.mock('../../DeckSetup')
vi.mock('../Timeline')

vi.mock('../../../../assets/localization', () => ({
t: vi.fn().mockReturnValue({
t: (key: string) => key,
}),
}))
vi.mock('../../../../feature-flags/selectors')

const render = () => {
return renderWithProviders(<ProtocolSteps />)[0]
return renderWithProviders(<ProtocolSteps />, {
i18nInstance: i18n,
})[0]
}

describe('ProtocolSteps', () => {
Expand All @@ -36,6 +35,7 @@ describe('ProtocolSteps', () => {
vi.mocked(getUnsavedForm).mockReturnValue(null)
vi.mocked(getSelectedSubstep).mockReturnValue(null)
vi.mocked(SubstepsToolbox).mockReturnValue(<div>mock SubstepsToolbox</div>)
vi.mocked(getEnableHotKeysDisplay).mockReturnValue(true)
})

it('renders each component in ProtocolSteps', () => {
Expand All @@ -47,7 +47,7 @@ describe('ProtocolSteps', () => {
it('renders the toggle when formData is null', () => {
render()
screen.getByText('mock DeckSetupContainer')
fireEvent.click(screen.getByText('offDeck'))
fireEvent.click(screen.getByText('Off-deck'))
screen.getByText('mock OffDeck')
})

Expand All @@ -57,12 +57,19 @@ describe('ProtocolSteps', () => {
id: 'mockId',
})
render()
expect(screen.queryByText('offDeck')).not.toBeInTheDocument()
expect(screen.queryByText('Off-deck')).not.toBeInTheDocument()
})

it('renders the substepToolbox when selectedSubstep is not null', () => {
vi.mocked(getSelectedSubstep).mockReturnValue('mockId')
render()
screen.getByText('mock SubstepsToolbox')
})

it('renders the hot keys display', () => {
render()
screen.getByText('Double-click to edit')
screen.getByText('Shift + Click to select all')
screen.getByText('Command + Click for multi-select')
})
})
16 changes: 15 additions & 1 deletion protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,29 @@ import { useSelector } from 'react-redux'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
Box,
COLORS,
DIRECTION_COLUMN,
Flex,
JUSTIFY_CENTER,
POSITION_FIXED,
SPACING,
Tag,
ToggleGroup,
} from '@opentrons/components'
import { getUnsavedForm } from '../../../step-forms/selectors'
import { getSelectedSubstep } from '../../../ui/steps/selectors'
import { getEnableHotKeysDisplay } from '../../../feature-flags/selectors'
import { DeckSetupContainer } from '../DeckSetup'
import { OffDeck } from '../Offdeck'
import { TimelineToolbox, SubstepsToolbox } from './Timeline'
import { StepForm } from './StepForm'

export function ProtocolSteps(): JSX.Element {
const { t } = useTranslation(['starting_deck_state'])
const { t } = useTranslation('starting_deck_state')
const formData = useSelector(getUnsavedForm)
const selectedSubstep = useSelector(getSelectedSubstep)
const enableHoyKeyDisplay = useSelector(getEnableHotKeysDisplay)
const leftString = t('onDeck')
const rightString = t('offDeck')
const [deckView, setDeckView] = useState<
Expand Down Expand Up @@ -67,6 +72,15 @@ export function ProtocolSteps(): JSX.Element {
) : (
<OffDeck tab="protocolSteps" />
)}
{enableHoyKeyDisplay ? (
<Box position={POSITION_FIXED} left="20.25rem" bottom="0.75rem">
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
<Tag text={t('double_click_to_edit')} type="default" />
<Tag text={t('shift_click_to_select_all')} type="default" />
<Tag text={t('command_click_to_multi_select')} type="default" />
</Flex>
</Box>
) : null}
</Flex>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ describe('Settings', () => {
screen.getByText('User settings')
screen.getByText('Hints')
screen.getByText('Reset all hints and tips notifications')
screen.getByText('Timeline editing tips')
screen.getByText(
'Show tips for working with steps next to the protocol timeline'
)
screen.getByText('Reset hints')
screen.getByText('Privacy')
screen.getByText('Share sessions with Opentrons')
Expand Down Expand Up @@ -87,7 +91,7 @@ describe('Settings', () => {
screen.getByText(
'Turn off all restrictions on module placement and related pipette crash guidance.'
)
fireEvent.click(screen.getByTestId('btn_PRERELEASE_MODE'))
fireEvent.click(screen.getByLabelText('Settings_PRERELEASE_MODE'))
expect(vi.mocked(setFeatureFlags)).toHaveBeenCalled()
})
})
55 changes: 37 additions & 18 deletions protocol-designer/src/pages/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ import {
actions as tutorialActions,
selectors as tutorialSelectors,
} from '../../tutorial'
import { ToggleButton } from '../../atoms/ToggleButton'
import { BUTTON_LINK_STYLE } from '../../atoms'
import { actions as featureFlagActions } from '../../feature-flags'
import { getFeatureFlagData } from '../../feature-flags/selectors'
import type { FlagTypes } from '../../feature-flags'

const HOT_KEY_FLAG = 'OT_PD_ENABLE_HOT_KEYS_DISPLAY'

export function Settings(): JSX.Element {
const dispatch = useDispatch()
const { t } = useTranslation(['feature_flags', 'shared'])
Expand Down Expand Up @@ -61,10 +64,6 @@ export function Settings(): JSX.Element {
}

const toFlagRow = (flagName: FlagTypes): JSX.Element => {
const iconName = Boolean(flags[flagName])
? 'ot-toggle-input-on'
: 'ot-toggle-input-off'

return (
<Flex key={flagName} justifyContent={JUSTIFY_SPACE_BETWEEN}>
<Flex flexDirection={DIRECTION_COLUMN}>
Expand All @@ -75,29 +74,22 @@ export function Settings(): JSX.Element {
{getDescription(flagName)}
</StyledText>
</Flex>
<Btn
role="switch"
aria-checked={Boolean(flags[flagName])}
data-testid={`btn_${flagName}`}
size="2rem"
css={
Boolean(flags[flagName])
? TOGGLE_ENABLED_STYLES
: TOGGLE_DISABLED_STYLES
}
<ToggleButton
label={`Settings_${flagName}`}
toggledOn={Boolean(flags[flagName])}
onClick={() => {
setFeatureFlags({
[flagName as string]: !flags[flagName],
})
}}
>
<Icon name={iconName} size="1rem" />
</Btn>
/>
</Flex>
)
}

const prereleaseFlagRows = allFlags.map(toFlagRow)
const prereleaseFlagRows = allFlags
.filter(flag => flag !== 'OT_PD_ENABLE_HOT_KEYS_DISPLAY')
.map(toFlagRow)

return (
<>
Expand Down Expand Up @@ -199,6 +191,33 @@ export function Settings(): JSX.Element {
</StyledText>
</Btn>
</Flex>
<Flex
borderRadius={BORDERS.borderRadius4}
backgroundColor={COLORS.grey10}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
justifyContent={JUSTIFY_SPACE_BETWEEN}
alignItems={ALIGN_CENTER}
>
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText desktopStyle="bodyDefaultSemiBold">
{t('OT_PD_ENABLE_HOT_KEYS_DISPLAY.title')}
</StyledText>
<Flex color={COLORS.grey60}>
<StyledText desktopStyle="bodyDefaultRegular">
{t('OT_PD_ENABLE_HOT_KEYS_DISPLAY.description')}
</StyledText>
</Flex>
</Flex>
<ToggleButton
label="Settings_hotKeys"
toggledOn={Boolean(flags[HOT_KEY_FLAG])}
onClick={() => {
setFeatureFlags({
OT_PD_ENABLE_HOT_KEYS_DISPLAY: !flags[HOT_KEY_FLAG],
})
}}
/>
</Flex>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<StyledText desktopStyle="bodyLargeSemiBold">
Expand Down

0 comments on commit b0e5188

Please sign in to comment.