4-10 树域 Rust 家族化
This commit is contained in:
@@ -117,6 +117,172 @@ describe("buildVisibleRows", () => {
|
||||
expect(rows.filter((r) => r.rowId === "doc:a").length).toBe(1);
|
||||
expect(new Set(rows.map((r) => r.rowId)).size).toBe(rows.length);
|
||||
});
|
||||
|
||||
it("优先消费 Rust file_tree projection 并保留 index 与资源文件夹语义", () => {
|
||||
const rows = buildVisibleRows({
|
||||
fileTreeItems: [
|
||||
{
|
||||
rowId: "doc:page_root",
|
||||
rowKind: "document",
|
||||
nodeId: "page_root",
|
||||
parentNodeId: null,
|
||||
nodeType: "page",
|
||||
projectionKind: "file_tree",
|
||||
title: "根页面",
|
||||
depth: 0,
|
||||
position: 0,
|
||||
childCount: 4,
|
||||
expandable: true,
|
||||
expandedByDefault: true,
|
||||
capabilities: ["expand", "open", "select"],
|
||||
resourceMeta: {
|
||||
resourceKind: "document",
|
||||
documentId: "page_root",
|
||||
workspaceId: "ws_1",
|
||||
iconHint: "page",
|
||||
},
|
||||
iconHint: "page",
|
||||
},
|
||||
{
|
||||
rowId: "index:page_root",
|
||||
rowKind: "index",
|
||||
nodeId: "index:page_root",
|
||||
parentNodeId: "page_root",
|
||||
nodeType: "index",
|
||||
projectionKind: "file_tree",
|
||||
title: "index.md",
|
||||
depth: 1,
|
||||
position: 0,
|
||||
childCount: 0,
|
||||
expandable: false,
|
||||
expandedByDefault: false,
|
||||
capabilities: ["open", "select"],
|
||||
resourceMeta: {
|
||||
resourceKind: "index",
|
||||
documentId: "page_root",
|
||||
workspaceId: "ws_1",
|
||||
iconHint: "index",
|
||||
},
|
||||
iconHint: "index",
|
||||
},
|
||||
{
|
||||
rowId: "asset-folder:mind_1",
|
||||
rowKind: "asset_folder",
|
||||
nodeId: "asset-folder:mind_1",
|
||||
parentNodeId: "page_root",
|
||||
nodeType: "mindmap",
|
||||
projectionKind: "file_tree",
|
||||
title: "头脑风暴",
|
||||
depth: 1,
|
||||
position: 1,
|
||||
childCount: 1,
|
||||
expandable: true,
|
||||
expandedByDefault: false,
|
||||
capabilities: ["expand", "open-asset", "select"],
|
||||
resourceMeta: {
|
||||
resourceKind: "mindmap",
|
||||
documentId: "page_root",
|
||||
assetId: "mind_1",
|
||||
workspaceId: "ws_1",
|
||||
assetKind: "mindmap",
|
||||
iconHint: "mindmap",
|
||||
},
|
||||
iconHint: "mindmap",
|
||||
},
|
||||
{
|
||||
rowId: "asset:asset_child_1",
|
||||
rowKind: "asset",
|
||||
nodeId: "asset:asset_child_1",
|
||||
parentNodeId: "asset-folder:mind_1",
|
||||
nodeType: "asset",
|
||||
projectionKind: "file_tree",
|
||||
title: "节点图片.png",
|
||||
depth: 2,
|
||||
position: 0,
|
||||
childCount: 0,
|
||||
expandable: false,
|
||||
expandedByDefault: false,
|
||||
capabilities: ["open-asset", "select"],
|
||||
resourceMeta: {
|
||||
resourceKind: "asset",
|
||||
documentId: "page_root",
|
||||
assetId: "asset_child_1",
|
||||
workspaceId: "ws_1",
|
||||
assetKind: "image",
|
||||
iconHint: "image",
|
||||
},
|
||||
iconHint: "image",
|
||||
},
|
||||
{
|
||||
rowId: "asset:table_1",
|
||||
rowKind: "asset",
|
||||
nodeId: "asset:table_1",
|
||||
parentNodeId: "page_root",
|
||||
nodeType: "table",
|
||||
projectionKind: "file_tree",
|
||||
title: "预算.luckysheet",
|
||||
depth: 1,
|
||||
position: 2,
|
||||
childCount: 0,
|
||||
expandable: false,
|
||||
expandedByDefault: false,
|
||||
capabilities: ["open-asset", "select"],
|
||||
resourceMeta: {
|
||||
resourceKind: "table",
|
||||
documentId: "page_root",
|
||||
assetId: "table_1",
|
||||
workspaceId: "ws_1",
|
||||
assetKind: "table",
|
||||
iconHint: "table",
|
||||
},
|
||||
iconHint: "table",
|
||||
},
|
||||
],
|
||||
expanded: new Set(["page_root"]),
|
||||
expandedAssetFolderIds: new Set(["mind_1"]),
|
||||
});
|
||||
|
||||
expect(rows.map((row) => `${row.kind}:${row.depth}:${row.rowId}`)).toEqual([
|
||||
"doc:0:doc:page_root",
|
||||
"index:1:index:page_root",
|
||||
"asset-folder:1:asset-folder:mind_1",
|
||||
"asset:2:asset:asset_child_1",
|
||||
"asset:1:asset:table_1",
|
||||
]);
|
||||
expect(rows[0]).toMatchObject({
|
||||
kind: "doc",
|
||||
docId: "page_root",
|
||||
hasChildren: true,
|
||||
isExpanded: true,
|
||||
});
|
||||
expect(rows[2]).toMatchObject({
|
||||
kind: "asset-folder",
|
||||
docId: "page_root",
|
||||
asset: {
|
||||
id: "mind_1",
|
||||
asset_type: "mindmap",
|
||||
file_name: "头脑风暴",
|
||||
},
|
||||
hasChildren: true,
|
||||
isExpanded: true,
|
||||
});
|
||||
expect(rows[3]).toMatchObject({
|
||||
kind: "asset",
|
||||
asset: {
|
||||
id: "asset_child_1",
|
||||
asset_type: "image",
|
||||
file_name: "节点图片.png",
|
||||
},
|
||||
});
|
||||
expect(rows[4]).toMatchObject({
|
||||
kind: "asset",
|
||||
asset: {
|
||||
id: "table_1",
|
||||
asset_type: "luckysheet",
|
||||
file_name: "预算.luckysheet",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseFileTreeRowId", () => {
|
||||
|
||||
@@ -1,28 +1,129 @@
|
||||
"use client";
|
||||
|
||||
import type { KernelFileTreeProjectionItem } from "@/lib/kernel-file-tree";
|
||||
import {
|
||||
getDocIdFromFileTreeItem,
|
||||
resolveFileTreeRowAsset,
|
||||
resolveFileTreeRowNode,
|
||||
} from "@/lib/kernel-file-tree";
|
||||
import type { SidebarTreeNode } from "@/lib/kernel-sidebar";
|
||||
import type { PageTreeProjectionItem } from "@/lib/tree-projection";
|
||||
import type { MediaAsset } from "@/types/media";
|
||||
import type { FileTreeRow } from "./types";
|
||||
import { makeAssetFolderRowId, makeAssetRowId, makeDocRowId, makeIndexRowId } from "./types";
|
||||
|
||||
function buildRowsFromKernelFileTreeProjection(input: {
|
||||
fileTreeItems: KernelFileTreeProjectionItem[];
|
||||
expanded: Set<string>;
|
||||
expandedAssetFolderIds?: Set<string>;
|
||||
nodeById?: Map<string, SidebarTreeNode>;
|
||||
assetById?: Map<string, MediaAsset>;
|
||||
}): FileTreeRow[] {
|
||||
const rows: FileTreeRow[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
input.fileTreeItems.forEach((item) => {
|
||||
if (!item?.rowId || seen.has(item.rowId)) {
|
||||
return;
|
||||
}
|
||||
seen.add(item.rowId);
|
||||
const docId = getDocIdFromFileTreeItem(item);
|
||||
|
||||
switch (item.rowKind) {
|
||||
case "document": {
|
||||
const node = resolveFileTreeRowNode(item, input.nodeById);
|
||||
rows.push({
|
||||
kind: "doc",
|
||||
rowId: makeDocRowId(docId),
|
||||
depth: item.depth,
|
||||
docId,
|
||||
parentDocId: item.parentNodeId,
|
||||
node,
|
||||
hasChildren: item.childCount > 0,
|
||||
isExpanded: input.expanded.has(docId),
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "index": {
|
||||
const node = resolveFileTreeRowNode(item, input.nodeById);
|
||||
rows.push({
|
||||
kind: "index",
|
||||
rowId: makeIndexRowId(docId),
|
||||
depth: item.depth,
|
||||
docId,
|
||||
parentDocId: docId,
|
||||
node,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "asset_folder": {
|
||||
const asset = resolveFileTreeRowAsset(item, input.assetById);
|
||||
rows.push({
|
||||
kind: "asset-folder",
|
||||
rowId: makeAssetFolderRowId(asset.id),
|
||||
depth: item.depth,
|
||||
docId,
|
||||
parentDocId: docId,
|
||||
asset,
|
||||
hasChildren: item.childCount > 0,
|
||||
isExpanded: input.expandedAssetFolderIds?.has(asset.id) ?? false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "asset": {
|
||||
const asset = resolveFileTreeRowAsset(item, input.assetById);
|
||||
rows.push({
|
||||
kind: "asset",
|
||||
rowId: makeAssetRowId(asset.id),
|
||||
depth: item.depth,
|
||||
docId,
|
||||
parentDocId: docId,
|
||||
asset,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
export function buildVisibleRows({
|
||||
fileTreeItems,
|
||||
pageRows,
|
||||
expanded,
|
||||
assetsByDoc,
|
||||
assetChildrenByAssetId,
|
||||
expandedAssetFolderIds,
|
||||
nodeById,
|
||||
assetById,
|
||||
}: {
|
||||
fileTreeItems?: KernelFileTreeProjectionItem[];
|
||||
// 只消费统一 page_tree projection;结构真相不再由文件树自行定义。
|
||||
pageRows: PageTreeProjectionItem[];
|
||||
pageRows?: PageTreeProjectionItem[];
|
||||
expanded: Set<string>;
|
||||
assetsByDoc: Record<string, MediaAsset[]>;
|
||||
assetsByDoc?: Record<string, MediaAsset[]>;
|
||||
assetChildrenByAssetId?: Record<string, MediaAsset[]>;
|
||||
expandedAssetFolderIds?: Set<string>;
|
||||
nodeById?: Map<string, SidebarTreeNode>;
|
||||
assetById?: Map<string, MediaAsset>;
|
||||
}): FileTreeRow[] {
|
||||
if (fileTreeItems && fileTreeItems.length > 0) {
|
||||
return buildRowsFromKernelFileTreeProjection({
|
||||
fileTreeItems,
|
||||
expanded,
|
||||
expandedAssetFolderIds,
|
||||
nodeById,
|
||||
assetById,
|
||||
});
|
||||
}
|
||||
|
||||
const safePageRows = pageRows ?? [];
|
||||
const safeAssetsByDoc = assetsByDoc ?? {};
|
||||
const rows: FileTreeRow[] = [];
|
||||
|
||||
pageRows.forEach((item) => {
|
||||
const assets = assetsByDoc[item.nodeId] ?? [];
|
||||
safePageRows.forEach((item) => {
|
||||
const assets = safeAssetsByDoc[item.nodeId] ?? [];
|
||||
const hasChildren = item.childCount > 0 || assets.length > 0;
|
||||
const isExpanded = expanded.has(item.nodeId);
|
||||
rows.push({
|
||||
|
||||
Reference in New Issue
Block a user