improvement(ux): command + enter to run workflow

This commit is contained in:
Emir Karabeg
2025-04-10 15:09:17 -07:00
parent 434d495790
commit d401551af1
2 changed files with 137 additions and 38 deletions
@@ -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() {
<div className="flex ml-1">
{/* Main Run/Debug Button */}
<Button
className={cn(
'gap-2 font-medium',
'bg-[#802FFF] hover:bg-[#7028E6]',
'shadow-[0_0_0_0_#802FFF] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]',
'text-white transition-all duration-200',
(isExecuting || isMultiRunning) &&
!isCancelling &&
'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20',
'disabled:opacity-50 disabled:hover:bg-[#802FFF] disabled:hover:shadow-none',
isDebugModeEnabled || isMultiRunning
? 'rounded py-2 px-4 h-10'
: 'rounded-r-none border-r border-r-[#6420cc] py-2 px-4 h-10'
)}
onClick={isDebugModeEnabled ? handleRunWorkflow : handleMultipleRuns}
disabled={isExecuting || isMultiRunning || isCancelling}
>
{isCancelling ? (
<Loader2 className="h-3.5 w-3.5 animate-spin mr-1.5" />
) : isDebugModeEnabled ? (
<Bug className={cn('h-3.5 w-3.5 mr-1.5', 'fill-current stroke-current')} />
) : (
<Play className={cn('h-3.5 w-3.5', 'fill-current stroke-current')} />
)}
{isCancelling
? 'Cancelling...'
: isMultiRunning
? `Running (${completedRuns}/${runCount})`
: isExecuting
? isDebugging
? 'Debugging'
: 'Running'
: isDebugModeEnabled
? 'Debug'
: runCount === 1
? 'Run'
: `Run (${runCount})`}
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
className={cn(
'gap-2 font-medium',
'bg-[#802FFF] hover:bg-[#7028E6]',
'shadow-[0_0_0_0_#802FFF] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]',
'text-white transition-all duration-200',
(isExecuting || isMultiRunning) &&
!isCancelling &&
'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20',
'disabled:opacity-50 disabled:hover:bg-[#802FFF] disabled:hover:shadow-none',
isDebugModeEnabled || isMultiRunning
? 'rounded py-2 px-4 h-10'
: 'rounded-r-none border-r border-r-[#6420cc] py-2 px-4 h-10'
)}
onClick={isDebugModeEnabled ? handleRunWorkflow : handleMultipleRuns}
disabled={isExecuting || isMultiRunning || isCancelling}
>
{isCancelling ? (
<Loader2 className="h-3.5 w-3.5 animate-spin mr-1.5" />
) : isDebugModeEnabled ? (
<Bug className={cn('h-3.5 w-3.5 mr-1.5', 'fill-current stroke-current')} />
) : (
<Play className={cn('h-3.5 w-3.5', 'fill-current stroke-current')} />
)}
{isCancelling
? 'Cancelling...'
: isMultiRunning
? `Running (${completedRuns}/${runCount})`
: isExecuting
? isDebugging
? 'Debugging'
: 'Running'
: isDebugModeEnabled
? 'Debug'
: runCount === 1
? 'Run'
: `Run (${runCount})`}
</Button>
</TooltipTrigger>
<TooltipContent>
{isDebugModeEnabled
? 'Debug Workflow'
: runCount === 1
? 'Run Workflow'
: `Run Workflow ${runCount} times`}
<span className="text-xs text-muted-foreground ml-1">
{getKeyboardShortcutText('Enter', true)}
</span>
</TooltipContent>
</Tooltip>
{/* Dropdown Trigger - Only show when not in debug mode and not multi-running */}
{!isDebugModeEnabled && !isMultiRunning && (
@@ -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])
}