Files
superwork/vscode/webview-ui/src/codeReader/BlockTree.tsx
T

140 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { CSSProperties, ReactElement } from 'react'
import type { BlockView } from './types'
import { ChevronDown, ChevronRight, Eye, EyeOff, Pencil } from 'lucide-react'
import { Fragment } from 'react'
import { cn } from '@/lib/utils'
import { CodeEditor } from './CodeEditor'
import { PropertyGrid } from './PropertyGrid'
import { TextPreview } from './TextPreview'
export interface BlockTreeState {
version: number
editing: ReadonlySet<string>
/** 眼下折叠着的块 id、属性行 id 和子行 id(propertyRowId、propertyItemId):用户的选择叠在默认上,见 collapsedIds */
collapsed: ReadonlySet<string>
/** 按筛选不画的块(连同子孙),见 applyFilter */
hidden: ReadonlySet<string>
/** 本来被筛掉、跳到定义时临时显示的块 */
temporary: ReadonlySet<string>
/** 种类标签上写什么:翻译插件给种类起的名字(库规则的种类是规则给的标签),没给就是 id */
kindName: (kind: string) => string
/** 刚点了「编辑」、要把光标放进去的块 */
focusId: string | null
onFocused: () => void
onToggleEdit: (id: string) => void
onToggleFold: (id: string) => void
}
/** 块下面有没有画出来的子块:子块都被筛掉时不留空的一层、不给折叠箭头。 */
function hasShownChildren(block: BlockView, tree: BlockTreeState): boolean {
return block.children.some(child => !tree.hidden.has(child.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 (
<div className="cr-level flex gap-2" {...kindProps(owner)}>
<div className="flex w-3 shrink-0 justify-center">
<div className="cr-guide w-px" />
</div>
<div className="flex min-w-0 flex-1 flex-col gap-2">
{blocks.filter(block => !tree.hidden.has(block.id)).map(block => (
<Fragment key={block.id}>
<BlockCard block={block} tree={tree} />
{hasShownChildren(block, tree) && !tree.collapsed.has(block.id) && (
<BlockLevel blocks={block.children} owner={block} tree={tree} />
)}
</Fragment>
))}
</div>
</div>
)
}
/** 预览状态的内容:函数、类、导入、常量、变量块和库规则认领的块是属性表,其余是一行译文。 */
function BlockPreview({ block, tree }: { block: BlockView, tree: BlockTreeState }): ReactElement {
if ('properties' in block)
return <PropertyGrid blockId={block.id} rows={block.properties} collapsed={tree.collapsed} onToggleFold={tree.onToggleFold} />
return <TextPreview text={block.translation} />
}
/** 一块:折叠箭头、种类标签、预览内容或源码、右边的「编辑 / 预览」切换。 */
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 (
<div
className="cr-block flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2"
{...kindProps(block)}
data-id={block.id}
data-line={block.startLine}
data-editing={editing ? '' : undefined}
>
{hasChildren
? (
<button
type="button"
className="flex h-5 w-3.5 shrink-0 items-center justify-center rounded-sm text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)]"
aria-label={collapsed ? '展开子块' : '折叠子块'}
aria-expanded={!collapsed}
onClick={() => tree.onToggleFold(block.id)}
>
<Chevron size={14} strokeWidth={1.8} />
</button>
)
: <span className="w-3.5 shrink-0" />}
<span className="cr-badge flex h-5 min-w-11 shrink-0 items-center justify-center rounded px-1 text-[11px] leading-[14px] font-medium whitespace-nowrap">
{tree.kindName(block.kind)}
</span>
<div className="min-w-0 flex-1">
{editing
? (
<CodeEditor
block={block}
version={tree.version}
autoFocus={tree.focusId === block.id}
onAutoFocused={tree.onFocused}
onExit={() => tree.onToggleEdit(block.id)}
/>
)
: <BlockPreview block={block} tree={tree} />}
</div>
{tree.temporary.has(block.id) && (
<span className="cr-temporary flex h-5 shrink-0 items-center gap-1 rounded px-1.5 text-[11px] whitespace-nowrap">
<EyeOff size={12} strokeWidth={1.8} aria-hidden />
已被筛选隐藏,临时显示
</span>
)}
<button
type="button"
className={cn(
'flex shrink-0 items-center gap-1 rounded px-2 py-0.5 text-xs leading-4 text-[var(--vscode-descriptionForeground)] hover:bg-[var(--vscode-toolbar-hoverBackground)]',
editing && 'bg-[var(--vscode-toolbar-activeBackground)]',
)}
onClick={() => tree.onToggleEdit(block.id)}
>
{editing ? <Eye size={13} strokeWidth={1.8} /> : <Pencil size={13} strokeWidth={1.8} />}
{editing ? '预览' : '编辑'}
</button>
</div>
)
}