diff --git a/sim/app/w/[id]/components/control-bar/control-bar.tsx b/sim/app/w/[id]/components/control-bar/control-bar.tsx index 72c785b490..7fcfa2843e 100644 --- a/sim/app/w/[id]/components/control-bar/control-bar.tsx +++ b/sim/app/w/[id]/components/control-bar/control-bar.tsx @@ -44,6 +44,7 @@ import { useGeneralStore } from '@/stores/settings/general/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' +import { getKeyboardShortcutText, useKeyboardShortcuts } from '../../hooks/use-keyboard-shortcuts' import { useWorkflowExecution } from '../../hooks/use-workflow-execution' import { DeploymentControls } from './components/deployment-controls/deployment-controls' import { HistoryDropdownItem } from './components/history-dropdown-item/history-dropdown-item' @@ -108,6 +109,20 @@ export function ControlBar() { const [isCancelling, setIsCancelling] = useState(false) const cancelFlagRef = useRef(false) + // Register keyboard shortcut for running workflow + useKeyboardShortcuts( + () => { + if (!isExecuting && !isMultiRunning && !isCancelling) { + if (isDebugModeEnabled) { + handleRunWorkflow() + } else { + handleMultipleRuns() + } + } + }, + isExecuting || isMultiRunning || isCancelling + ) + // Get notifications for current workflow const workflowNotifications = activeWorkflowId ? getWorkflowNotifications(activeWorkflowId) @@ -820,44 +835,58 @@ export function ControlBar() {
{/* Main Run/Debug Button */} - + + + + + + {isDebugModeEnabled + ? 'Debug Workflow' + : runCount === 1 + ? 'Run Workflow' + : `Run Workflow ${runCount} times`} + + {getKeyboardShortcutText('Enter', true)} + + + {/* Dropdown Trigger - Only show when not in debug mode and not multi-running */} {!isDebugModeEnabled && !isMultiRunning && ( diff --git a/sim/app/w/[id]/hooks/use-keyboard-shortcuts.ts b/sim/app/w/[id]/hooks/use-keyboard-shortcuts.ts new file mode 100644 index 0000000000..cbdcb5b51a --- /dev/null +++ b/sim/app/w/[id]/hooks/use-keyboard-shortcuts.ts @@ -0,0 +1,70 @@ +'use client' + +import { useEffect, useMemo } from 'react' + +/** + * Detect if the current platform is Mac + */ +export function isMacPlatform() { + if (typeof navigator === 'undefined') return false + return navigator.platform.toUpperCase().indexOf('MAC') >= 0 +} + +/** + * Get a formatted keyboard shortcut string for display + * @param key The key part of the shortcut (e.g., "Enter") + * @param requiresCmd Whether the shortcut requires Cmd/Ctrl + * @param requiresShift Whether the shortcut requires Shift + * @param requiresAlt Whether the shortcut requires Alt/Option + */ +export function getKeyboardShortcutText( + key: string, + requiresCmd = false, + requiresShift = false, + requiresAlt = false +) { + const isMac = isMacPlatform() + const cmdKey = isMac ? '⌘' : 'Ctrl' + const altKey = isMac ? '⌥' : 'Alt' + const shiftKey = '⇧' + + const parts: string[] = [] + if (requiresCmd) parts.push(cmdKey) + if (requiresShift) parts.push(shiftKey) + if (requiresAlt) parts.push(altKey) + parts.push(key) + + return parts.join('+') +} + +/** + * Hook to manage keyboard shortcuts + * @param onRunWorkflow - Function to run when Cmd/Ctrl+Enter is pressed + * @param isDisabled - Whether shortcuts should be disabled + */ +export function useKeyboardShortcuts(onRunWorkflow: () => void, isDisabled = false) { + // Memoize the platform detection + const isMac = useMemo(() => isMacPlatform(), []) + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + // Run workflow with Cmd+Enter (Mac) or Ctrl+Enter (Windows/Linux) + if (event.key === 'Enter' && ((isMac && event.metaKey) || (!isMac && event.ctrlKey))) { + // Don't trigger if user is typing in an input, textarea, or contenteditable element + const activeElement = document.activeElement + const isEditableElement = + activeElement instanceof HTMLInputElement || + activeElement instanceof HTMLTextAreaElement || + activeElement?.hasAttribute('contenteditable') + + if (!isEditableElement && !isDisabled) { + event.preventDefault() + onRunWorkflow() + } + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [onRunWorkflow, isDisabled, isMac]) +} \ No newline at end of file