mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 20:48:01 +08:00
add shortcut for open test run panel
This commit is contained in:
parent
1c0068e95b
commit
1949074e2f
@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
@ -32,6 +32,29 @@ export type TestRunMenuRef = {
|
|||||||
toggle: () => void
|
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<TestRunMenuRef, TestRunMenuProps>(({
|
const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({
|
||||||
options,
|
options,
|
||||||
onSelect,
|
onSelect,
|
||||||
@ -39,38 +62,73 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({
|
|||||||
}, ref) => {
|
}, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
const shortcutMappings = useMemo(() => buildShortcutMappings(options), [options])
|
||||||
|
const shortcutKeyById = useMemo(() => {
|
||||||
|
const map = new Map<string, string>()
|
||||||
|
shortcutMappings.forEach(({ option, shortcutKey }) => {
|
||||||
|
map.set(option.id, shortcutKey)
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
}, [shortcutMappings])
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
toggle: () => setOpen(prev => !prev),
|
toggle: () => setOpen(prev => !prev),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const handleSelect = (option: TriggerOption) => {
|
const handleSelect = useCallback((option: TriggerOption) => {
|
||||||
onSelect(option)
|
onSelect(option)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}, [onSelect])
|
||||||
|
|
||||||
const renderOption = (option: TriggerOption, shortcutKey: string) => (
|
useEffect(() => {
|
||||||
<div
|
if (!open)
|
||||||
key={option.id}
|
return
|
||||||
className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
|
||||||
onClick={() => handleSelect(option)}
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
>
|
if (event.defaultPrevented || event.repeat || event.altKey || event.ctrlKey || event.metaKey)
|
||||||
<div className='flex min-w-0 flex-1 items-center'>
|
return
|
||||||
<div className='flex h-6 w-6 shrink-0 items-center justify-center'>
|
|
||||||
{option.icon}
|
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 (
|
||||||
|
<div
|
||||||
|
key={option.id}
|
||||||
|
className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
||||||
|
onClick={() => handleSelect(option)}
|
||||||
|
>
|
||||||
|
<div className='flex min-w-0 flex-1 items-center'>
|
||||||
|
<div className='flex h-6 w-6 shrink-0 items-center justify-center'>
|
||||||
|
{option.icon}
|
||||||
|
</div>
|
||||||
|
<span className='ml-2 truncate'>{option.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className='ml-2 truncate'>{option.name}</span>
|
{shortcutKey && (
|
||||||
|
<ShortcutsName keys={[shortcutKey]} className="ml-2" textColor="secondary" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ShortcutsName keys={[shortcutKey]} className="ml-2" textColor="secondary" />
|
)
|
||||||
</div>
|
}
|
||||||
)
|
|
||||||
|
|
||||||
const hasUserInput = !!options.userInput
|
const hasUserInput = !!options.userInput
|
||||||
const hasTriggers = options.triggers.length > 0
|
const hasTriggers = options.triggers.length > 0
|
||||||
const hasRunAll = !!options.runAll
|
const hasRunAll = !!options.runAll
|
||||||
|
|
||||||
let currentIndex = 0
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
open={open}
|
open={open}
|
||||||
@ -89,16 +147,16 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({
|
|||||||
{t('workflow.common.chooseStartNodeToRun')}
|
{t('workflow.common.chooseStartNodeToRun')}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{hasUserInput && renderOption(options.userInput!, '~')}
|
{hasUserInput && renderOption(options.userInput!)}
|
||||||
|
|
||||||
{(hasTriggers || hasRunAll) && hasUserInput && (
|
{(hasTriggers || hasRunAll) && hasUserInput && (
|
||||||
<div className='mx-3 my-1 h-px bg-divider-subtle' />
|
<div className='mx-3 my-1 h-px bg-divider-subtle' />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasRunAll && renderOption(options.runAll!, String(currentIndex++))}
|
{hasRunAll && renderOption(options.runAll!)}
|
||||||
|
|
||||||
{hasTriggers && options.triggers.map(trigger =>
|
{hasTriggers && options.triggers.map(trigger =>
|
||||||
renderOption(trigger, String(currentIndex++)),
|
renderOption(trigger),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user