import type { ReactElement } from 'react' import type { Inline, InlinePart, ItemDiff, ItemsDiff, PropertyItem, PropertyRow, Removed, RowsDiff } from './types' import { ChevronDown, ChevronRight } from 'lucide-react' import { Fragment } from 'react' import { DiffInline, DiffText } from './DiffText' import { inlineText } from './segments' import { SegmentText } from './SegmentText' import { subRowsLayout, tableCells, tableTemplate } from './subRows' import { TextPreview } from './TextPreview' import { propertyItemId, propertyRowId, tableId } from './viewState' /** 列表子行里除最后一列以外各列的宽度(字典的键),最后一列占满剩下的;编辑区窄时按比例收,不把最后一列挤没 */ const CELL_WIDTHS = ['min(150px, 28%)', 'min(220px, 38%)'] function gridTemplate(count: number): string { const fixed = Array.from({ length: count - 1 }, (_, i) => CELL_WIDTHS[i] ?? CELL_WIDTHS[CELL_WIDTHS.length - 1]) return [...fixed, 'minmax(0, 1fr)'].join(' ') } /** 眼下折叠着哪些属性行、子行,点折叠箭头时切换哪个。 */ interface Folding { collapsed: ReadonlySet onToggleFold: (id: string) => void } /** 删掉的行下面的子行:总是展开,点了也不收(旧内容只是看看)。 */ const FIXED: Folding = { collapsed: new Set(), onToggleFold: () => {} } /** 表格行第一格前的记号:加的 +、删的 −,没变的空一格,列照样对齐。 */ const SIGNS = { add: '+', del: '−', none: ' ' } as const /** 删掉的行(子行)里排在新的第 after 行后面的那些,带着它在删掉的行里的序号(页面上认它用)。 */ function removedAt(removed: readonly Removed[] | undefined, after: number): { value: T, index: number }[] { return (removed ?? []).flatMap((entry, index) => entry.after === after ? [{ value: entry.value, index }] : []) } /** 一格的字:这一格的值改了就逐截标(DiffInline),否则照常。 */ function CellText({ cell, parts }: { cell: Inline[], parts: InlinePart[] | undefined }): ReactElement { return parts ? : } /** * 子行的第一列(字典的键):带下一层的子行前面是折叠箭头(图标、大小、颜色同属性行的),点箭头或这一列切换; * 同一层里不带下一层的子行在同样的位置留空,第一列才对得齐。 */ function ItemHead({ id, cell, foldable, folding }: { id: string, cell: Inline[], foldable: boolean, folding: Folding }): ReactElement { if (!foldable) { return (

) } const folded = folding.collapsed.has(id) const Chevron = folded ? ChevronRight : ChevronDown return ( ) } /** * 展开在一行或一个子行(parentId)下面的一层子行:左边一条竖线,每个子行几列对齐。 * 带下一层的子行(字典 / 对象的值又是字典 / 对象),下一层展开在它下面、再往右缩进一层,默认展开。 * 标差异时(marks)新加的子行淡绿底、左边 +,删掉的放回原来的位置、淡红底划掉、左边 −,改了的格子逐截标。 */ function PropertyItems({ parentId, items, folding, marks }: { parentId: string, items: PropertyItem[], folding: Folding, marks?: ItemsDiff }): ReactElement { const nested = items.some(item => (item.items?.length ?? 0) > 0) /** 一个子行:删掉的(change 是 del)不带 id、下面的子行总是展开 */ const itemRow = (item: PropertyItem, id: string, change: 'add' | 'del' | undefined, mark: ItemDiff | undefined): ReactElement => { const children = item.items ?? [] const folds = change === 'del' ? FIXED : folding return ( <>
{item.cells.map((cell, i) => i === 0 && nested ? 0 && change !== 'del'} folding={folds} /> : (

))}
{children.length > 0 && !folds.collapsed.has(id) && (change === 'del' ? (
) : )} ) } const removed = (after: number): ReactElement[] => removedAt(marks?.removed, after) .map(({ value, index }) => {itemRow(value, `${parentId}/x${index}`, 'del', undefined)}) return (
{removed(-1)} {items.map((item, index) => { const mark = marks?.changed[index] return ( {itemRow(item, propertyItemId(parentId, index), mark?.added && 'add', mark)} {removed(index)} ) })}
) } /** * 给了列名的子行(输入参数、数据表的字段)排成表格:第一行是表头(列名),下面每个子行一行;外框圆角,格子之间、行之间一条分隔线。 * 列宽按整列的内容定(见 tableTemplate),最后一列分剩下的,长了在格子里换行。一行译文下面摊开实参的表也用它; * 行带 table(实参的值里跨几行写的调用)时,那张表排在最后一格里、文字下面,往右缩一点,一层层往下套。 * 标差异时(marks)有加行、删行的表每行第一格前多一个记号位(+ / −,没变的空着),加的行淡绿底,删的行放回原来的位置、淡红底划掉。 */ export function PropertyTable({ parentId, columns, items, marks }: { parentId: string, columns: string[], items: PropertyItem[], marks?: ItemsDiff }): ReactElement { // 有加行、删行的表每行第一格前留一个记号位 const signed = marks !== undefined && (marks.removed.length > 0 || Object.values(marks.changed).some(mark => mark.added)) /** 一行:删掉的(change 是 del)不带 id,套着的表不再标 */ const tableRow = (item: PropertyItem, id: string, change: 'add' | 'del' | undefined, mark: ItemDiff | undefined): ReactElement => { const cells = tableCells(item, columns) const text = (cell: Inline[], i: number): ReactElement => ( <> {signed && i === 0 && {SIGNS[change ?? 'none']}} ) return (
{cells.map((cell, i) => item.table && i === cells.length - 1 ? (

{text(cell, i)}

) :

{text(cell, i)}

)}
) } const removed = (after: number): ReactElement[] => removedAt(marks?.removed, after) .map(({ value, index }) => {tableRow(value, `${parentId}/x${index}`, 'del', undefined)}) return (
{columns.map((name, i) => {name})}
{removed(-1)} {items.map((item, index) => { const mark = marks?.changed[index] return ( {tableRow(item, propertyItemId(parentId, index), mark?.added && 'add', mark)} {removed(index)} ) })}
) } /** * 函数、类、枚举、导入、常量、变量块和库规则的卡片的预览:属性名一列对齐,值按词性着色;输入参数、导入项、字典 / 对象的值下面展开子行, * 给了列名的(输入参数、数据表的字段)排成表格,没给的排成列表。 * 标差异时(marks)新加的行淡绿底、左边 +,删掉的行放回原来的位置、淡红底划掉、左边 −,值改了的按旧值 → 新值或逐词标。 */ export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks }: { blockId: string rows: PropertyRow[] collapsed: ReadonlySet onToggleFold: (id: string) => void /** 标差异时这张属性表改了哪些行 */ marks?: RowsDiff }): ReactElement { const removed = (after: number): ReactElement[] => removedAt(marks?.removed, after) .map(({ value, index }) => ) return (
{removed(-1)} {rows.map((row, index) => { const layout = subRowsLayout(row) const id = propertyRowId(blockId, index) const folded = collapsed.has(id) const Chevron = folded ? ChevronRight : ChevronDown const mark = marks?.changed[index] return (
{layout ? ( ) : {row.label}} {mark?.value ? : }
{!folded && layout === 'table' && } {!folded && layout === 'list' && } {removed(index)}
) })}
) } /** 属性表里删掉的一行:整行淡红底、划掉,左边 −;它下面的子行照样摊开、划掉。 */ function RemovedRow({ id, row }: { id: string, row: PropertyRow }): ReactElement { const layout = subRowsLayout(row) return ( <>
{row.label}
{layout && (
{layout === 'table' ? : }
)} ) }