/** * 调用树弹框:盖在编辑区上,预览的卡片留在后面。点遮罩、点叉、按 Esc 关掉,焦点回到打开它的按钮。 * 点树上的一行跳到它在的文件和行。接口在契约文件里没对上时写明原因,不当成没人调用。 */ import type { ReactElement } from 'react' import type { CallJump, CallTree, CallTreeNode, ContractMiss, KindBadge } from './types' import { Box, Braces, Send, X } from 'lucide-react' import { useEffect, useState } from 'react' import { createPortal } from 'react-dom' import { kindProps } from './BlockTree' export function CallTreeDialog({ kind, badge, name, where, status, trees, contractMiss, onClose, onJump }: { /** 当前块的种类,整条路径用这个种类色;库规则的卡片按 badge 的颜色 */ kind: string badge?: KindBadge name: string where: string status: 'loading' | 'empty' | 'ready' trees: readonly CallTree[] /** 接口在契约文件里没对上时有 */ contractMiss?: ContractMiss onClose: () => void /** 点了树上的一行 */ onJump: (jump: CallJump) => void }): ReactElement { const [selected, setSelected] = useState(0) const tree = trees[Math.min(selected, Math.max(trees.length - 1, 0))] useEffect(() => { const onKey = (event: KeyboardEvent) => { if (event.key !== 'Escape') return document.querySelector('[data-call-tree][data-active]')?.focus() onClose() } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [onClose]) function close(): void { document.querySelector('[data-call-tree][data-active]')?.focus() onClose() } return createPortal(
{ if (event.target === event.currentTarget) close() }} >
调用树 {name && {name}} {where && {where}}
{status === 'ready' && trees.length > 1 && (
{trees.map((item, index) => ( ))}
)}
{status === 'loading' &&

正在查调用关系

} {status !== 'loading' && contractMiss && } {status === 'empty' && !contractMiss &&

没有调用它的地方

} {status === 'ready' && tree && }
, document.body, ) } /** * 接口在契约文件里没对上:分不出是哪一条时列出那几条;没有这一条时让人重新生成、列出找到的契约文件; * 一份契约文件都没有时说没找到。 */ function ContractNote({ miss }: { miss: ContractMiss }): ReactElement { if (miss.files.length === 0) return

工作区里没找到接口契约文件(openapi-typescript 生成的 .d.ts 或 openapi.json)

const [text, items] = miss.ambiguous ? ['接口契约文件里有几条都对得上这个接口,按 operationId 也分不出是哪一条', miss.ambiguous] : ['接口契约文件里没有这个接口,重新生成后再看', miss.files] return (

{text}

    {items.map(item =>
  • {item}
  • )}
) } const NODE_ICONS: Record = { class: Box, request: Send } function CallNodes({ node, depth, path, onJump }: { node: CallTreeNode, depth: number, path: string, onJump: (jump: CallJump) => void }): ReactElement { const Icon = NODE_ICONS[node.kind] ?? Braces return ( <>
{node.children.map((child, index) => ( ))} {node.otherCalls !== undefined && node.otherCalls > 0 && (
{`另外 ${node.otherCalls} 处调用`}
)} ) }