import type { CSSProperties, ReactElement } from 'react' import type { QaState } from './qa' import type { PendingImage } from './qaImages' import type { BlockView } from './types' import { ChevronDown, ChevronRight, EyeOff } from 'lucide-react' import { Fragment } from 'react' import { AskThread } from './AskThread' import { BlockTools } from './BlockTools' import { CodeEditor } from './CodeEditor' import { KindIcon } from './kindIcons' import { PropertyGrid, PropertyTable } from './PropertyGrid' import { asksOf, historyOf } from './qa' import { TextPreview } from './TextPreview' import { translationTableId } from './viewState' export interface BlockTreeState { version: number editing: ReadonlySet /** 眼下折叠着的块 id、属性行 id 和子行 id(propertyRowId、propertyItemId):用户的选择叠在默认上,见 collapsedIds */ collapsed: ReadonlySet /** 按筛选不画的块(连同子孙),见 applyFilter */ hidden: ReadonlySet /** 本来被筛掉、跳到定义时临时显示的块 */ temporary: ReadonlySet /** 被正在编辑的祖先盖住、不画的块(它们的行在祖先的编辑范围里),见 coveredByEditing */ covered: ReadonlyMap /** 种类叫什么(种类图标的悬停提示和 aria-label):翻译插件给种类起的名字(库规则的种类是规则给的 badge.label),没给就是 id */ kindName: (kind: string) => string /** 刚点了「编辑」、要把光标放进去的块 */ focusId: string | null onFocused: () => void onToggleEdit: (id: string) => void onToggleFold: (id: string) => void /** 问 AI;文件不在工作区文件夹里(问答没地方存)时没有 */ qa?: QaTree } /** 问 AI 的状态和动作,块按自己的记号(anchor)取 */ export interface QaTree { state: QaState onToggle: (anchor: string) => void onClose: (anchor: string) => void /** images 是贴进输入框的图片,随问题一起发 */ onAsk: (anchor: string, question: string, images: PendingImage[]) => void onStop: (askId: string) => void onDelete: (anchor: string, entryId: string) => void /** 换「问 AI」用的 profile(所有块共用),id 同 ProfileOption.id */ onSelectProfile: (id: string) => void } /** 块下面有没有画出来的子块:子块都被筛掉、被盖住时不留空的一层、不给折叠箭头。 */ function hasShownChildren(block: BlockView, tree: BlockTreeState): boolean { return block.children.some(child => shown(child, tree)) } /** 块画不画:被筛掉的、被正在编辑的祖先盖住的都不画。 */ function shown(block: BlockView, tree: BlockTreeState): boolean { return !tree.hidden.has(block.id) && !tree.covered.has(block.id) } /** * 块的种类色写在哪:内置种类按 data-kind 取 CSS 里的配色;库规则认领的块(带 badge)按规则给的颜色, * 暗色、亮色两个都写在 style 上,CSS 按主题挑一个(见 [data-badge])。 */ function kindProps(block: BlockView | undefined): { 'data-kind'?: string, 'data-badge'?: '', 'style'?: CSSProperties } { if (!block) return {} if (!block.badge) return { 'data-kind': block.kind } const { dark, light } = block.badge.color return { 'data-kind': block.kind, 'data-badge': '', 'style': { '--kind-dark': dark, '--kind-light': light } as CSSProperties } } /** * 一层代码块:左边一条竖线标出这一层,每块的子块紧跟在它下面、再往右缩进一层。被筛掉、被盖住的块不画,不留空位,竖线跟着变短。 * owner 是这一层所属的块(顶层没有),悬停它时竖线换成它的种类色。 */ export function BlockLevel({ blocks, owner, tree }: { blocks: BlockView[], owner?: BlockView, tree: BlockTreeState }): ReactElement { return (
{blocks.filter(block => shown(block, tree)).map(block => ( {tree.qa && } {hasShownChildren(block, tree) && !tree.collapsed.has(block.id) && ( )} ))}
) } /** 块下面的问答区,紧跟在块后面、子块前面;输入框、展开状态跟着块的记号走,文档刷新块 id 变了也不丢 */ function BlockQa({ anchor, qa }: { anchor: string, qa: QaTree }): ReactElement { return ( qa.onAsk(anchor, question, images)} onStop={qa.onStop} onDelete={entryId => qa.onDelete(anchor, entryId)} onClose={() => qa.onClose(anchor)} /> ) } /** * 预览状态的内容:函数、类、枚举、导入、常量、变量块和库规则的卡片是属性表,其余是一行译文; * 译文下面依次接着摊开实参的表(跨几行写的调用)、说明。 */ function BlockPreview({ block, tree }: { block: BlockView, tree: BlockTreeState }): ReactElement { if ('properties' in block) return return ( <> {block.table && (
)} {block.doc && } ) } /** 一块:折叠箭头、种类图标、预览内容或源码、右上角的一排图标按钮(BlockTools)。 */ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }): ReactElement { const editing = tree.editing.has(block.id) const collapsed = tree.collapsed.has(block.id) const hasChildren = hasShownChildren(block, tree) const Chevron = collapsed ? ChevronRight : ChevronDown return (
{hasChildren ? ( ) : }
{editing ? ( tree.onToggleEdit(block.id)} /> ) : }
{tree.temporary.has(block.id) && ( 已被筛选隐藏,临时显示 )} tree.onToggleEdit(block.id)} asking={tree.qa?.state.open.includes(block.anchor)} onToggleAsk={tree.qa && (() => tree.qa?.onToggle(block.anchor))} />
) }