diff --git a/e2e/playwright/testing-segment-overlays.spec.ts b/e2e/playwright/testing-segment-overlays.spec.ts index fa3876d7a..28b51733e 100644 --- a/e2e/playwright/testing-segment-overlays.spec.ts +++ b/e2e/playwright/testing-segment-overlays.spec.ts @@ -1445,7 +1445,7 @@ part001 = startSketchOn(XZ) await page.getByTestId('overlay-menu').click() await page.waitForTimeout(100) - await page.getByText('Remove constraints').click() + await page.getByRole('button', { name: 'Remove constraints' }).click() await editor.expectEditor.toContain(after, { shouldNormalise: true }) diff --git a/known-circular.txt b/known-circular.txt index 45ea02e96..a6ea66441 100644 --- a/known-circular.txt +++ b/known-circular.txt @@ -3,11 +3,11 @@ > dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx • Circular Dependencies - 1) src/lang/std/sketch.ts -> src/lang/modifyAst.ts - 2) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts - 3) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/setAngleLength.tsx -> src/components/SetAngleLengthModal.tsx -> src/lib/useCalculateKclExpression.ts - 4) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts - 5) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts - 6) src/lib/singletons.ts -> src/lang/codeManager.ts - 7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts - 8) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts + 1) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts + 2) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts + 3) src/lib/singletons.ts -> src/lang/codeManager.ts + 4) src/lang/std/sketch.ts -> src/lang/modifyAst.ts + 5) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts + 6) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts + 7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts + 8) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/setAngleLength.tsx -> src/components/SetAngleLengthModal.tsx -> src/lib/useCalculateKclExpression.ts diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx index 589cb8391..424f89f56 100644 --- a/src/Toolbar.tsx +++ b/src/Toolbar.tsx @@ -25,8 +25,8 @@ import type { ToolbarModeName, } from '@src/lib/toolbar' import { isToolbarItemResolvedDropdown, toolbarConfig } from '@src/lib/toolbar' -import { isArray } from '@src/lib/utils' import { commandBarActor } from '@src/lib/singletons' +import { filterEscHotkey } from '@src/lib/hotkeyWrapper' export function Toolbar({ className = '', @@ -253,7 +253,8 @@ export function Toolbar({ !['available', 'experimental'].includes( itemConfig.status ) || - itemConfig.disabled === true, + itemConfig.disabled === true || + itemConfig.disableHotkey === true, status: itemConfig.status, }))} > @@ -410,6 +411,10 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({ contentClassName = '', children, }: ToolbarItemContentsProps) { + /** + * GOTCHA: `useHotkeys` can only register one hotkey listener per component. + * TODO: make a global hotkey registration system. make them editable. + */ useHotkeys( itemConfig.hotkey || '', () => { @@ -469,7 +474,7 @@ const ToolbarItemTooltipShortContent = ({ {title} {hotkey && ( - {displayHotkeys(hotkey)} + {filterEscHotkey(hotkey)} )} @@ -510,7 +515,7 @@ const ToolbarItemTooltipRichContent = ({ {shouldBeEnabled && itemConfig.hotkey ? ( - {displayHotkeys(itemConfig.hotkey)} + {filterEscHotkey(itemConfig.hotkey)} ) : itemConfig.status === 'kcl-only' ? ( <> @@ -573,11 +578,6 @@ const ToolbarItemTooltipRichContent = ({ ) } -// We don't want to display Esc hotkeys to avoid confusion in the Toolbar UI (eg. "EscR") -function displayHotkeys(hotkey: string | string[]) { - return (isArray(hotkey) ? hotkey : [hotkey]).filter((h) => h !== 'Esc') -} - function isToolbarDropdown( item: ToolbarItem | ToolbarDropdown ): item is ToolbarDropdown { diff --git a/src/components/ActionButtonDropdown.tsx b/src/components/ActionButtonDropdown.tsx index bb850f893..c3525cdfc 100644 --- a/src/components/ActionButtonDropdown.tsx +++ b/src/components/ActionButtonDropdown.tsx @@ -3,6 +3,8 @@ import { Popover } from '@headlessui/react' import type { ActionButtonProps } from '@src/components/ActionButton' import { CustomIcon } from '@src/components/CustomIcon' import Tooltip from '@src/components/Tooltip' +import { filterEscHotkey } from '@src/lib/hotkeyWrapper' +import { useHotkeys } from 'react-hotkeys-hook' type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & { name?: string @@ -10,7 +12,7 @@ type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & { splitMenuItems: { id: string label: string - shortcut?: string + hotkey?: string | string[] onClick: () => void disabled?: boolean status?: 'available' | 'unavailable' | 'kcl-only' | 'experimental' @@ -63,54 +65,18 @@ export function ActionButtonDropdown({ - {splitMenuItems.map((item) => ( -
  • - -
  • + {splitMenuItems.map((item, index) => ( + { + item.onClick() + // Close the popover + close() + }} + key={item.label} + /> ))}
    @@ -118,3 +84,68 @@ export function ActionButtonDropdown({ ) } + +function ActionButtonDropdownListItem({ + item, + onClick, +}: { + item: ActionButtonSplitProps['splitMenuItems'][number] + onClick: () => void +}) { + /** + * GOTCHA: `useHotkeys` can only register one hotkey listener per component. + * and since the first item in the dropdown has a top-level button too, + * it already has a hotkey listener, so we should skip it. + * TODO: make a global hotkey registration system. make them editable. + */ + useHotkeys(item.hotkey || '', item.onClick, { + enabled: + ['available', 'experimental'].includes(item.status || '') && + !!item.hotkey && + !item.disabled, + }) + + return ( +
  • + +
  • + ) +} diff --git a/src/lib/hotkeyWrapper.ts b/src/lib/hotkeyWrapper.ts index 82163d12d..69e58e5cd 100644 --- a/src/lib/hotkeyWrapper.ts +++ b/src/lib/hotkeyWrapper.ts @@ -3,7 +3,7 @@ import type { Options } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook' import { codeManager } from '@src/lib/singletons' -import type { Platform } from '@src/lib/utils' +import { isArray, type Platform } from '@src/lib/utils' // Hotkey wrapper wraps hotkeys for the app (outside of the editor) // with hotkeys inside the editor. @@ -86,3 +86,10 @@ export function hotkeyDisplay(hotkey: string, platform: Platform): string { return display } + +/** + * We don't want to display Esc hotkeys to avoid confusion in the Toolbar UI (eg. "EscR") + */ +export function filterEscHotkey(hotkey: string | string[]) { + return (isArray(hotkey) ? hotkey : [hotkey]).filter((h) => h !== 'Esc') +} diff --git a/src/lib/toolbar.ts b/src/lib/toolbar.ts index c4b1c1f86..aa6705e87 100644 --- a/src/lib/toolbar.ts +++ b/src/lib/toolbar.ts @@ -238,7 +238,6 @@ export const toolbarConfig: Record = { icon: 'booleanUnion', status: 'available', title: 'Union', - hotkey: 'Shift + B U', description: 'Combine two or more solids into a single solid.', links: [ { @@ -257,7 +256,6 @@ export const toolbarConfig: Record = { icon: 'booleanSubtract', status: 'available', title: 'Subtract', - hotkey: 'Shift + B S', description: 'Subtract one solid from another.', links: [ { @@ -276,7 +274,6 @@ export const toolbarConfig: Record = { icon: 'booleanIntersect', status: 'available', title: 'Intersect', - hotkey: 'Shift + B I', description: 'Create a solid from the intersection of two solids.', links: [ { @@ -631,7 +628,9 @@ export const toolbarConfig: Record = { isActive: (state) => state.matches({ Sketch: 'Circle three point tool' }), hotkey: (state) => - state.matches({ Sketch: 'Circle three point tool' }) ? 'Esc' : [], + state.matches({ Sketch: 'Circle three point tool' }) + ? ['Alt+C', 'Esc'] + : 'Alt+C', showTitle: false, description: 'Draw a circle defined by three points', links: [], @@ -681,6 +680,10 @@ export const toolbarConfig: Record = { title: 'Center rectangle', description: 'Start drawing a rectangle from its center', links: [], + hotkey: (state) => + state.matches({ Sketch: 'Center Rectangle tool' }) + ? ['Alt+R', 'Esc'] + : 'Alt+R', isActive: (state) => { return state.matches({ Sketch: 'Center Rectangle tool' }) },