'use client' import type { RefObject } from 'react' import type { TreeApi } from 'react-arborist' import type { TreeNodeData } from '../type' import { useKeyPress } from 'ahooks' import { useCallback, useEffect, useRef } from 'react' import { useWorkflowStore } from '@/app/components/workflow/store' import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, } from '@/app/components/workflow/utils/common' type UseSkillShortcutsOptions = { treeRef: RefObject | null> enabled?: boolean } const TREE_CONTAINER_SELECTOR = '[data-skill-tree-container]' export function useSkillShortcuts({ treeRef, enabled = true, }: UseSkillShortcutsOptions): void { const storeApi = useWorkflowStore() const enabledRef = useRef(enabled) useEffect(() => { enabledRef.current = enabled }, [enabled]) const shouldHandle = useCallback((e: KeyboardEvent) => { if (!enabledRef.current) return false if (isEventTargetInputArea(e.target as HTMLElement)) return false const target = e.target as HTMLElement const isInTreeContainer = target.closest(TREE_CONTAINER_SELECTOR) !== null const hasSelection = (treeRef.current?.selectedNodes.length ?? 0) > 0 return isInTreeContainer || hasSelection }, [treeRef]) const getSelectedNodeIds = useCallback(() => { const tree = treeRef.current if (!tree) return [] return tree.selectedNodes.map(n => n.id) }, [treeRef]) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, (e) => { if (shouldHandle(e)) { const nodeIds = getSelectedNodeIds() if (nodeIds.length > 0) { e.preventDefault() storeApi.getState().copyNodes(nodeIds) } } }, { exactMatch: true, useCapture: true }) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.x`, (e) => { if (shouldHandle(e)) { const nodeIds = getSelectedNodeIds() if (nodeIds.length > 0) { e.preventDefault() storeApi.getState().cutNodes(nodeIds) } } }, { exactMatch: true, useCapture: true }) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, (e) => { if (shouldHandle(e) && storeApi.getState().hasClipboard()) { e.preventDefault() window.dispatchEvent(new CustomEvent('skill:paste')) } }, { exactMatch: true, useCapture: true }) }