diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 65c13dcb14..39dca866fe 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -3405,14 +3405,6 @@ "count": 2 } }, - "web/app/components/workflow/header/run-mode.tsx": { - "no-console": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/workflow/header/test-run-menu.tsx": { "erasable-syntax-only/enums": { "count": 1 diff --git a/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx index f0b358efff..844405bd87 100644 --- a/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx +++ b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx @@ -340,7 +340,6 @@ vi.mock('../syncing-data-modal', () => ({ vi.mock('../shortcuts/use-workflow-hotkeys', () => ({ useWorkflowHotkeys: workflowHookMocks.useShortcuts, - useWorkflowShortcut: vi.fn(), })) vi.mock('../hooks', () => ({ diff --git a/web/app/components/workflow/header/__tests__/run-mode.spec.tsx b/web/app/components/workflow/header/__tests__/run-mode.spec.tsx index 0a0fe9ec6d..7e2bc8d0b7 100644 --- a/web/app/components/workflow/header/__tests__/run-mode.spec.tsx +++ b/web/app/components/workflow/header/__tests__/run-mode.spec.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from 'react' +import type { TestRunMenuRef } from '../test-run-menu' import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' import { WorkflowRunningStatus } from '@/app/components/workflow/types' @@ -13,6 +14,10 @@ const mockHandleWorkflowRunAllTriggersInWorkflow = vi.fn() const mockHandleStopRun = vi.fn() const mockNotify = vi.fn() const mockTrackEvent = vi.fn() +const hotkeyRegistrations = vi.hoisted(() => new Map void + options?: { ignoreInputs?: boolean } +}>()) let mockWarningNodes: Array<{ id: string }> = [] let mockWorkflowRunningData: { result: { status: WorkflowRunningStatus }, task_id: string } | undefined @@ -42,9 +47,15 @@ vi.mock('@/app/components/workflow/store/workflow', () => ({ selector({ workflowRunningData: mockWorkflowRunningData, isListening: mockIsListening }), })) -vi.mock('@/app/components/workflow/shortcuts/use-workflow-hotkeys', () => ({ - useWorkflowShortcut: vi.fn(), -})) +vi.mock('@tanstack/react-hotkeys', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useHotkey: (hotkey: string, callback: () => void, options?: { ignoreInputs?: boolean }) => { + hotkeyRegistrations.set(hotkey, { callback, options }) + }, + } +}) vi.mock('../../hooks/use-dynamic-test-run-options', () => ({ useDynamicTestRunOptions: () => mockDynamicOptions, @@ -77,21 +88,23 @@ vi.mock('@/app/components/base/icons/src/vender/line/mediaAndDevices', () => ({ vi.mock('../test-run-menu', async (importOriginal) => { const actual = await importOriginal() + const TestRunMenuMock = ({ children, options, onSelect, ref }: { children: ReactNode, options: Array<{ type: TriggerType, nodeId?: string, relatedNodeIds?: string[] }>, onSelect: (option: { type: TriggerType, nodeId?: string, relatedNodeIds?: string[] }) => void, ref?: React.Ref }) => { + React.useImperativeHandle(ref, () => ({ + toggle: vi.fn(), + })) + return ( +
+ + {children} +
+ ) + } + return { ...actual, - default: React.forwardRef(({ children, options, onSelect }: { children: ReactNode, options: Array<{ type: TriggerType, nodeId?: string, relatedNodeIds?: string[] }>, onSelect: (option: { type: TriggerType, nodeId?: string, relatedNodeIds?: string[] }) => void }, ref) => { - React.useImperativeHandle(ref, () => ({ - toggle: vi.fn(), - })) - return ( -
- - {children} -
- ) - }), + default: TestRunMenuMock, } }) @@ -101,6 +114,7 @@ describe('RunMode', () => { mockWarningNodes = [] mockWorkflowRunningData = undefined mockIsListening = false + hotkeyRegistrations.clear() mockDynamicOptions = [ { type: TriggerType.UserInput, nodeId: 'start-node' }, ] @@ -150,4 +164,12 @@ describe('RunMode', () => { expect(screen.getByText(/listening/i))!.toBeInTheDocument() }) + + it('should register the test run menu shortcut as a page command outside text inputs', () => { + render() + + expect(hotkeyRegistrations.get('Alt+R')?.options).toEqual( + expect.objectContaining({ ignoreInputs: true }), + ) + }) }) diff --git a/web/app/components/workflow/header/__tests__/version-history-button.spec.tsx b/web/app/components/workflow/header/__tests__/version-history-button.spec.tsx index 30c1ee9c0c..75606f9216 100644 --- a/web/app/components/workflow/header/__tests__/version-history-button.spec.tsx +++ b/web/app/components/workflow/header/__tests__/version-history-button.spec.tsx @@ -2,7 +2,10 @@ import { act, fireEvent, render, screen } from '@testing-library/react' import VersionHistoryButton from '../version-history-button' let mockTheme: 'light' | 'dark' = 'light' -const workflowShortcutHandlers = vi.hoisted(() => new Map void | Promise>()) +const hotkeyRegistrations = vi.hoisted(() => new Map void + options?: { ignoreInputs?: boolean } +}>()) vi.mock('@/hooks/use-theme', () => ({ default: () => ({ @@ -10,11 +13,15 @@ vi.mock('@/hooks/use-theme', () => ({ }), })) -vi.mock('../../shortcuts/use-workflow-hotkeys', () => ({ - useWorkflowShortcut: (id: string, callback: () => void | Promise) => { - workflowShortcutHandlers.set(id, callback) - }, -})) +vi.mock('@tanstack/react-hotkeys', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useHotkey: (hotkey: string, callback: () => void, options?: { ignoreInputs?: boolean }) => { + hotkeyRegistrations.set(hotkey, { callback, options }) + }, + } +}) vi.mock('@langgenius/dify-ui/tooltip', () => ({ Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}, @@ -25,7 +32,7 @@ vi.mock('@langgenius/dify-ui/tooltip', () => ({ describe('VersionHistoryButton', () => { beforeEach(() => { vi.clearAllMocks() - workflowShortcutHandlers.clear() + hotkeyRegistrations.clear() mockTheme = 'light' }) @@ -43,10 +50,13 @@ describe('VersionHistoryButton', () => { render() await act(async () => { - await workflowShortcutHandlers.get('workflow.version-history')?.() + hotkeyRegistrations.get('Mod+Shift+H')?.callback() }) expect(onClick).toHaveBeenCalledTimes(1) + expect(hotkeyRegistrations.get('Mod+Shift+H')?.options).toEqual( + expect.objectContaining({ ignoreInputs: true }), + ) }) it('should render the tooltip popup content on hover', async () => { diff --git a/web/app/components/workflow/header/run-mode.tsx b/web/app/components/workflow/header/run-mode.tsx index 108d1f98da..7b80025a48 100644 --- a/web/app/components/workflow/header/run-mode.tsx +++ b/web/app/components/workflow/header/run-mode.tsx @@ -1,6 +1,8 @@ import type { TestRunMenuRef, TriggerOption } from './test-run-menu' +import type { EventEmitterValue } from '@/context/event-emitter' import { cn } from '@langgenius/dify-ui/cn' import { toast } from '@langgenius/dify-ui/toast' +import { useHotkey } from '@tanstack/react-hotkeys' import * as React from 'react' import { useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' @@ -8,18 +10,21 @@ import { trackEvent } from '@/app/components/base/amplitude' import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import { useWorkflowRun, useWorkflowRunValidation, useWorkflowStartRun } from '@/app/components/workflow/hooks' import { ShortcutKbd } from '@/app/components/workflow/shortcuts/shortcut-kbd' -import { useWorkflowShortcut } from '@/app/components/workflow/shortcuts/use-workflow-hotkeys' import { useStore } from '@/app/components/workflow/store/workflow' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' import { useEventEmitterContextContext } from '@/context/event-emitter' import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options' +import { TEST_RUN_MENU_HOTKEY } from './shortcuts' import TestRunMenu, { TriggerType } from './test-run-menu' type RunModeProps = { text?: string } +const isWorkflowStopEvent = (value: EventEmitterValue) => + typeof value !== 'string' && value.type === EVENT_WORKFLOW_STOP + const RunMode = ({ text, }: RunModeProps) => { @@ -46,7 +51,9 @@ const RunMode = ({ testRunMenuRef.current?.toggle() }, []) - useWorkflowShortcut('workflow.open-test-run-menu', handleToggleTestRunMenu) + useHotkey(TEST_RUN_MENU_HOTKEY, handleToggleTestRunMenu, { + ignoreInputs: true, + }) const handleStop = useCallback(() => { handleStopRun(workflowRunningData?.task_id || '') @@ -88,15 +95,11 @@ const RunMode = ({ handleWorkflowRunAllTriggersInWorkflow(targetNodeIds) trackEvent('app_start_action_time', { action_type: 'all' }) } - else { - // Placeholder for trigger-specific execution logic for schedule, webhook, plugin types - console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId) - } }, [warningNodes, t, handleWorkflowStartRunInWorkflow, handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, handleWorkflowRunAllTriggersInWorkflow]) const { eventEmitter } = useEventEmitterContextContext() - eventEmitter?.useSubscription((v: any) => { - if (v.type === EVENT_WORKFLOW_STOP) + eventEmitter?.useSubscription((v: EventEmitterValue) => { + if (isWorkflowStopEvent(v)) handleStop() }) @@ -131,7 +134,7 @@ const RunMode = ({ > {text ?? t('common.run', { ns: 'workflow' })} - + ) diff --git a/web/app/components/workflow/header/shortcuts.ts b/web/app/components/workflow/header/shortcuts.ts new file mode 100644 index 0000000000..94637498c5 --- /dev/null +++ b/web/app/components/workflow/header/shortcuts.ts @@ -0,0 +1 @@ +export const TEST_RUN_MENU_HOTKEY = 'Alt+R' diff --git a/web/app/components/workflow/header/version-history-button.tsx b/web/app/components/workflow/header/version-history-button.tsx index 24f895b8b4..f7d2b5d54f 100644 --- a/web/app/components/workflow/header/version-history-button.tsx +++ b/web/app/components/workflow/header/version-history-button.tsx @@ -6,12 +6,14 @@ import { TooltipContent, TooltipTrigger, } from '@langgenius/dify-ui/tooltip' +import { useHotkey } from '@tanstack/react-hotkeys' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import useTheme from '@/hooks/use-theme' import { ShortcutKbd } from '../shortcuts/shortcut-kbd' -import { useWorkflowShortcut } from '../shortcuts/use-workflow-hotkeys' + +const VERSION_HISTORY_HOTKEY = 'Mod+Shift+H' type VersionHistoryButtonProps = { onClick: () => Promise | unknown @@ -24,7 +26,7 @@ const PopupContent = React.memo(() => {
{t('common.versionHistory', { ns: 'workflow' })}
- + ) }) @@ -39,8 +41,10 @@ const VersionHistoryButton: FC = ({ await onClick?.() }, [onClick]) - useWorkflowShortcut('workflow.version-history', () => { - handleViewVersionHistory() + useHotkey(VERSION_HISTORY_HOTKEY, () => { + void handleViewVersionHistory() + }, { + ignoreInputs: true, }) return ( diff --git a/web/app/components/workflow/hooks/__tests__/use-shortcuts.spec.ts b/web/app/components/workflow/hooks/__tests__/use-shortcuts.spec.ts index c1ad40fcf9..63afb47ff0 100644 --- a/web/app/components/workflow/hooks/__tests__/use-shortcuts.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-shortcuts.spec.ts @@ -12,6 +12,9 @@ type KeyPressRegistration = { ignoreInputs?: boolean preventDefault?: boolean stopPropagation?: boolean + meta?: { + scope?: string + } } } @@ -187,6 +190,9 @@ describe('useShortcuts', () => { renderWorkflowHook(() => useWorkflowHotkeys()) const deleteShortcut = findRegistration(registration => registration.keyFilter === 'Delete') + expect(deleteShortcut.options?.meta).toEqual( + expect.objectContaining({ scope: 'workflow-canvas' }), + ) const bodyEvent = createKeyboardEvent() triggerShortcut(deleteShortcut, bodyEvent) diff --git a/web/app/components/workflow/node-actions-menu/shared.tsx b/web/app/components/workflow/node-actions-menu/shared.tsx index 502629396a..7c9d4205dc 100644 --- a/web/app/components/workflow/node-actions-menu/shared.tsx +++ b/web/app/components/workflow/node-actions-menu/shared.tsx @@ -1,6 +1,6 @@ import type { RegisterableHotkey } from '@tanstack/react-hotkeys' import type { ReactNode } from 'react' -import type { WorkflowShortcutId } from '@/app/components/workflow/shortcuts/definitions' +import type { WorkflowCanvasShortcutId } from '@/app/components/workflow/shortcuts/definitions' import { ShortcutKbd } from '@/app/components/workflow/shortcuts/shortcut-kbd' export const NODE_ACTIONS_MENU_WIDTH_CLASS_NAME = 'w-[240px] rounded-lg' @@ -14,7 +14,7 @@ export function NodeActionsMenuItemContent({ }: { children: ReactNode hotkey?: RegisterableHotkey | (string & {}) - shortcut?: WorkflowShortcutId + shortcut?: WorkflowCanvasShortcutId }) { return ( <> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx index 4e9f54455c..b98c6444e4 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx @@ -1,9 +1,11 @@ import type { FC } from 'react' import { Button } from '@langgenius/dify-ui/button' +import { useHotkey } from '@tanstack/react-hotkeys' import * as React from 'react' import { useTranslation } from 'react-i18next' import { ShortcutKbd } from '@/app/components/workflow/shortcuts/shortcut-kbd' -import { useWorkflowShortcut } from '@/app/components/workflow/shortcuts/use-workflow-hotkeys' + +const JSON_SCHEMA_CONFIRM_HOTKEY = 'Mod+Enter' type AdvancedActionsProps = { isConfirmDisabled: boolean @@ -18,7 +20,7 @@ const AdvancedActions: FC = ({ }) => { const { t } = useTranslation() - useWorkflowShortcut('workflow.json-schema-confirm', () => { + useHotkey(JSON_SCHEMA_CONFIRM_HOTKEY, () => { onConfirm() }, { enabled: !isConfirmDisabled, @@ -38,7 +40,7 @@ const AdvancedActions: FC = ({ onClick={onConfirm} > {t('operation.confirm', { ns: 'common' })} - + ) diff --git a/web/app/components/workflow/operator/tip-popup.tsx b/web/app/components/workflow/operator/tip-popup.tsx index 0beb9453bd..7d287f2a06 100644 --- a/web/app/components/workflow/operator/tip-popup.tsx +++ b/web/app/components/workflow/operator/tip-popup.tsx @@ -1,5 +1,5 @@ import type { ReactElement } from 'react' -import type { WorkflowShortcutId } from '../shortcuts/definitions' +import type { WorkflowCanvasShortcutId } from '../shortcuts/definitions' import { Tooltip, TooltipContent, @@ -11,7 +11,7 @@ import { ShortcutKbd } from '../shortcuts/shortcut-kbd' type TipPopupProps = { title: string children: ReactElement - shortcut?: WorkflowShortcutId + shortcut?: WorkflowCanvasShortcutId } const TipPopup = ({ title, diff --git a/web/app/components/workflow/panel-contextmenu.tsx b/web/app/components/workflow/panel-contextmenu.tsx index d9b4e6dd60..5ceb7b843f 100644 --- a/web/app/components/workflow/panel-contextmenu.tsx +++ b/web/app/components/workflow/panel-contextmenu.tsx @@ -9,6 +9,7 @@ import { useCallback, } from 'react' import { useTranslation } from 'react-i18next' +import { TEST_RUN_MENU_HOTKEY } from './header/shortcuts' import { useDSL, useIsChatMode, @@ -120,7 +121,7 @@ export function PanelContextmenu({ onClick={handleRunAction} > {isChatMode ? t('common.debugAndPreview', { ns: 'workflow' }) : t('common.run', { ns: 'workflow' })} - {!isChatMode && } + {!isChatMode && } diff --git a/web/app/components/workflow/shortcuts/definitions.ts b/web/app/components/workflow/shortcuts/definitions.ts index c0566032c3..14571ce1b0 100644 --- a/web/app/components/workflow/shortcuts/definitions.ts +++ b/web/app/components/workflow/shortcuts/definitions.ts @@ -1,11 +1,10 @@ import type { RegisterableHotkey } from '@tanstack/react-hotkeys' -export type WorkflowShortcutId +export type WorkflowCanvasShortcutId = | 'workflow.delete' | 'workflow.copy' | 'workflow.paste' | 'workflow.duplicate' - | 'workflow.open-test-run-menu' | 'workflow.undo' | 'workflow.redo' | 'workflow.pointer-mode' @@ -20,25 +19,23 @@ export type WorkflowShortcutId | 'workflow.zoom-in' | 'workflow.download-import-log' | 'workflow.dim-other-nodes' - | 'workflow.json-schema-confirm' - | 'workflow.version-history' -export type WorkflowHotkeyMeta = { - id: WorkflowShortcutId - scope: 'workflow' +export type WorkflowCanvasHotkeyMeta = { + id: WorkflowCanvasShortcutId + scope: 'workflow-canvas' name: string description: string } -export type WorkflowShortcutDefinition = { - id: WorkflowShortcutId +export type WorkflowCanvasShortcutDefinition = { + id: WorkflowCanvasShortcutId hotkeys: readonly RegisterableHotkey[] displayHotkey?: RegisterableHotkey | (string & {}) name: string description: string } -export const WORKFLOW_SHORTCUTS: Record = { +export const WORKFLOW_CANVAS_SHORTCUTS: Record = { 'workflow.delete': { id: 'workflow.delete', hotkeys: ['Delete', 'Backspace'], @@ -64,12 +61,6 @@ export const WORKFLOW_SHORTCUTS: Record { - const shortcut = WORKFLOW_SHORTCUTS[id] +export const getWorkflowCanvasShortcutDisplayHotkey = (id: WorkflowCanvasShortcutId): RegisterableHotkey | (string & {}) => { + const shortcut = WORKFLOW_CANVAS_SHORTCUTS[id] return shortcut.displayHotkey ?? shortcut.hotkeys[0]! } diff --git a/web/app/components/workflow/shortcuts/shortcut-kbd.tsx b/web/app/components/workflow/shortcuts/shortcut-kbd.tsx index dc1082436d..b2a614ce73 100644 --- a/web/app/components/workflow/shortcuts/shortcut-kbd.tsx +++ b/web/app/components/workflow/shortcuts/shortcut-kbd.tsx @@ -1,13 +1,13 @@ import type { KbdColor } from '@langgenius/dify-ui/kbd' import type { FormatDisplayOptions, RegisterableHotkey } from '@tanstack/react-hotkeys' -import type { WorkflowShortcutId } from './definitions' +import type { WorkflowCanvasShortcutId } from './definitions' import { cn } from '@langgenius/dify-ui/cn' import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd' import { formatForDisplay } from '@tanstack/react-hotkeys' -import { getWorkflowShortcutDisplayHotkey } from './definitions' +import { getWorkflowCanvasShortcutDisplayHotkey } from './definitions' type ShortcutKbdProps = { - shortcut?: WorkflowShortcutId + shortcut?: WorkflowCanvasShortcutId hotkey?: RegisterableHotkey | (string & {}) className?: string textColor?: 'default' | 'secondary' @@ -38,7 +38,7 @@ export const ShortcutKbd = ({ bgColor = 'gray', platform, }: ShortcutKbdProps) => { - const displayHotkey = hotkey ?? (shortcut ? getWorkflowShortcutDisplayHotkey(shortcut) : undefined) + const displayHotkey = hotkey ?? (shortcut ? getWorkflowCanvasShortcutDisplayHotkey(shortcut) : undefined) if (!displayHotkey) return null @@ -52,9 +52,9 @@ export const ShortcutKbd = ({ )} > { - displayKeys.map((key, index) => ( + displayKeys.map(key => ( diff --git a/web/app/components/workflow/shortcuts/use-workflow-hotkeys.ts b/web/app/components/workflow/shortcuts/use-workflow-hotkeys.ts index 7bee7b3119..ff0da5f3e8 100644 --- a/web/app/components/workflow/shortcuts/use-workflow-hotkeys.ts +++ b/web/app/components/workflow/shortcuts/use-workflow-hotkeys.ts @@ -3,7 +3,7 @@ import type { UseHotkeyDefinition, UseHotkeyOptions, } from '@tanstack/react-hotkeys' -import type { WorkflowHotkeyMeta, WorkflowShortcutDefinition, WorkflowShortcutId } from './definitions' +import type { WorkflowCanvasHotkeyMeta, WorkflowCanvasShortcutDefinition } from './definitions' import { useHotkeys, useKeyHold } from '@tanstack/react-hotkeys' import { useCallback, useEffect, useMemo, useRef } from 'react' import { useReactFlow } from 'reactflow' @@ -19,7 +19,7 @@ import { subscribeWorkflowCommand, WorkflowCommand, } from './commands' -import { WORKFLOW_SHORTCUTS } from './definitions' +import { WORKFLOW_CANVAS_SHORTCUTS } from './definitions' const workflowHotkeyOptions = { ignoreInputs: true, @@ -37,7 +37,7 @@ const isInputLikeElement = (element: Element | null) => { } const toHotkeyDefinitions = ( - shortcut: WorkflowShortcutDefinition, + shortcut: WorkflowCanvasShortcutDefinition, callback: HotkeyCallback, options?: UseHotkeyOptions, ): UseHotkeyDefinition[] => { @@ -48,28 +48,14 @@ const toHotkeyDefinitions = ( ...options, meta: { id: shortcut.id, - scope: 'workflow', + scope: 'workflow-canvas', name: shortcut.name, description: shortcut.description, - } satisfies WorkflowHotkeyMeta, + } satisfies WorkflowCanvasHotkeyMeta, }, })) } -export const useWorkflowShortcut = ( - id: WorkflowShortcutId, - callback: HotkeyCallback, - options?: UseHotkeyOptions, -) => { - const shortcut = WORKFLOW_SHORTCUTS[id] - const hotkeys = useMemo( - () => toHotkeyDefinitions(shortcut, callback, options), - [callback, options, shortcut], - ) - - useHotkeys(hotkeys, workflowHotkeyOptions) -} - export const useWorkflowHotkeys = (): void => { const { handleNodesCopy, @@ -139,71 +125,71 @@ export const useWorkflowHotkeys = (): void => { }, [handleToggleMaximizeCanvas]) const hotkeys = useMemo(() => [ - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.delete'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.delete'], () => { handleNodesDelete() handleEdgeDelete() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.copy'], handleCopy, { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.copy'], handleCopy, { preventDefault: false, stopPropagation: false, enabled: !showDebugAndPreviewPanel, }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.paste'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.paste'], () => { handleNodesPaste() }, { enabled: !showDebugAndPreviewPanel, }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.duplicate'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.duplicate'], () => { handleNodesDuplicate() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.undo'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.undo'], () => { handleHistoryBack() }, { enabled: !showDebugAndPreviewPanel && historyShortcutsEnabled, }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.redo'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.redo'], () => { handleHistoryForward() }, { enabled: !showDebugAndPreviewPanel && historyShortcutsEnabled, }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.hand-mode'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.hand-mode'], () => { handleModeHand() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.pointer-mode'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.pointer-mode'], () => { handleModePointer() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.comment-mode'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.comment-mode'], () => { handleModeComment() }, { enabled: isCommentModeAvailable, }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.organize'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.organize'], () => { handleLayout() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.toggle-maximize'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.toggle-maximize'], () => { handleToggleMaximizeCanvas() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.zoom-to-fit'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.zoom-to-fit'], () => { fitView() handleSyncWorkflowDraft() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.zoom-to-100'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.zoom-to-100'], () => { zoomTo(1) handleSyncWorkflowDraft() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.zoom-to-50'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.zoom-to-50'], () => { zoomTo(0.5) handleSyncWorkflowDraft() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.zoom-out'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.zoom-out'], () => { constrainedZoomOut() handleSyncWorkflowDraft() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.zoom-in'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.zoom-in'], () => { constrainedZoomIn() handleSyncWorkflowDraft() }), - ...toHotkeyDefinitions(WORKFLOW_SHORTCUTS['workflow.download-import-log'], () => { + ...toHotkeyDefinitions(WORKFLOW_CANVAS_SHORTCUTS['workflow.download-import-log'], () => { collaborationManager.downloadGraphImportLog() }), ], [