import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import ShortcutsName from '../shortcuts-name' export type TriggerOption = { id: string type: 'user_input' | 'schedule' | 'webhook' | 'plugin' | 'all' name: string icon: React.ReactNode nodeId?: string enabled: boolean } export type TestRunOptions = { userInput?: TriggerOption triggers: TriggerOption[] runAll?: TriggerOption } type TestRunMenuProps = { options: TestRunOptions onSelect: (option: TriggerOption) => void children: React.ReactNode } export type TestRunMenuRef = { toggle: () => void } type ShortcutMapping = { option: TriggerOption shortcutKey: string } const buildShortcutMappings = (options: TestRunOptions): ShortcutMapping[] => { const mappings: ShortcutMapping[] = [] if (options.userInput) mappings.push({ option: options.userInput, shortcutKey: '~' }) let numericShortcut = 0 if (options.runAll) mappings.push({ option: options.runAll, shortcutKey: String(numericShortcut++) }) options.triggers.forEach((trigger) => { mappings.push({ option: trigger, shortcutKey: String(numericShortcut++) }) }) return mappings } const TestRunMenu = forwardRef(({ options, onSelect, children, }, ref) => { const { t } = useTranslation() const [open, setOpen] = useState(false) const shortcutMappings = useMemo(() => buildShortcutMappings(options), [options]) const shortcutKeyById = useMemo(() => { const map = new Map() shortcutMappings.forEach(({ option, shortcutKey }) => { map.set(option.id, shortcutKey) }) return map }, [shortcutMappings]) useImperativeHandle(ref, () => ({ toggle: () => setOpen(prev => !prev), })) const handleSelect = useCallback((option: TriggerOption) => { onSelect(option) setOpen(false) }, [onSelect]) useEffect(() => { if (!open) return const handleKeyDown = (event: KeyboardEvent) => { if (event.defaultPrevented || event.repeat || event.altKey || event.ctrlKey || event.metaKey) return const normalizedKey = event.key === '`' ? '~' : event.key const mapping = shortcutMappings.find(({ shortcutKey }) => shortcutKey === normalizedKey) if (mapping) { event.preventDefault() handleSelect(mapping.option) } } window.addEventListener('keydown', handleKeyDown) return () => { window.removeEventListener('keydown', handleKeyDown) } }, [handleSelect, open, shortcutMappings]) const renderOption = (option: TriggerOption) => { const shortcutKey = shortcutKeyById.get(option.id) return (
handleSelect(option)} >
{option.icon}
{option.name}
{shortcutKey && ( )}
) } const hasUserInput = !!options.userInput const hasTriggers = options.triggers.length > 0 const hasRunAll = !!options.runAll return ( setOpen(!open)}>
{children}
{t('workflow.common.chooseStartNodeToRun')}
{hasUserInput && renderOption(options.userInput!)} {(hasTriggers || hasRunAll) && hasUserInput && (
)} {hasRunAll && renderOption(options.runAll!)} {hasTriggers && options.triggers.map(trigger => renderOption(trigger), )}
) }) TestRunMenu.displayName = 'TestRunMenu' export default TestRunMenu