@world-engines/authoring-ui 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE +46 -0
  2. package/dist/DesktopAuthoringShell.d.ts +178 -0
  3. package/dist/DesktopAuthoringShell.js +235 -0
  4. package/dist/index.d.ts +23 -0
  5. package/dist/index.js +12 -0
  6. package/dist/ladybug/BrowserGraphCanvasAdapter.d.ts +12 -0
  7. package/dist/ladybug/BrowserGraphCanvasAdapter.js +8 -0
  8. package/dist/ladybug/GraphCanvas.d.ts +121 -0
  9. package/dist/ladybug/GraphCanvas.js +1439 -0
  10. package/dist/ladybug/GraphEdge.d.ts +26 -0
  11. package/dist/ladybug/GraphEdge.js +41 -0
  12. package/dist/ladybug/LadybugExplorer.d.ts +58 -0
  13. package/dist/ladybug/LadybugExplorer.js +83 -0
  14. package/dist/ladybug/LadybugSurfaces.d.ts +25 -0
  15. package/dist/ladybug/LadybugSurfaces.js +9 -0
  16. package/dist/ladybug/LadybugTable.d.ts +15 -0
  17. package/dist/ladybug/LadybugTable.js +26 -0
  18. package/dist/ladybug/entity-props.d.ts +98 -0
  19. package/dist/ladybug/entity-props.js +363 -0
  20. package/dist/ladybug/graph-edge-geometry.d.ts +19 -0
  21. package/dist/ladybug/graph-edge-geometry.js +78 -0
  22. package/dist/ladybug/graph-edge-mask.d.ts +21 -0
  23. package/dist/ladybug/graph-edge-mask.js +52 -0
  24. package/dist/ladybug/graph-groups.d.ts +47 -0
  25. package/dist/ladybug/graph-groups.js +245 -0
  26. package/dist/ladybug/graph-layout-contract.d.ts +34 -0
  27. package/dist/ladybug/graph-layout-contract.js +55 -0
  28. package/dist/ladybug/graph-layout-solver.d.ts +16 -0
  29. package/dist/ladybug/graph-layout-solver.js +96 -0
  30. package/dist/ladybug/graph-layout-wasm.d.ts +9 -0
  31. package/dist/ladybug/graph-layout-wasm.js +88 -0
  32. package/dist/ladybug/graph-layout-worker-protocol.d.ts +18 -0
  33. package/dist/ladybug/graph-layout-worker-protocol.js +1 -0
  34. package/dist/ladybug/graph-layout-worker.d.ts +1 -0
  35. package/dist/ladybug/graph-layout-worker.js +66 -0
  36. package/dist/ladybug/graph-layout.d.ts +2 -0
  37. package/dist/ladybug/graph-layout.js +1 -0
  38. package/dist/ladybug/graph-layout.wasm +0 -0
  39. package/dist/ladybug/graph-node-metrics.d.ts +33 -0
  40. package/dist/ladybug/graph-node-metrics.js +173 -0
  41. package/dist/ladybug/graph-physics.d.ts +49 -0
  42. package/dist/ladybug/graph-physics.js +266 -0
  43. package/dist/ladybug/graph-placement.d.ts +33 -0
  44. package/dist/ladybug/graph-placement.js +253 -0
  45. package/dist/ladybug/graph.css +530 -0
  46. package/dist/ladybug/graph.d.ts +18 -0
  47. package/dist/ladybug/graph.js +9 -0
  48. package/dist/ladybug/score-tags.d.ts +11 -0
  49. package/dist/ladybug/score-tags.js +1 -0
  50. package/dist/map/MapWorkspace.d.ts +14 -0
  51. package/dist/map/MapWorkspace.js +9 -0
  52. package/dist/score/ScoreRadar.d.ts +10 -0
  53. package/dist/score/ScoreRadar.js +66 -0
  54. package/dist/score/index.d.ts +3 -0
  55. package/dist/score/index.js +3 -0
  56. package/dist/score/radar-index.d.ts +3 -0
  57. package/dist/score/radar-index.js +2 -0
  58. package/dist/score/scenario-scoring-features.d.ts +90 -0
  59. package/dist/score/scenario-scoring-features.js +380 -0
  60. package/dist/score/scenario-structural-score.d.ts +76 -0
  61. package/dist/score/scenario-structural-score.js +211 -0
  62. package/dist/score/score-presentation.d.ts +51 -0
  63. package/dist/score/score-presentation.js +44 -0
  64. package/dist/score/score-radar.css +1 -0
  65. package/dist/sync/DownloadPanel.d.ts +42 -0
  66. package/dist/sync/DownloadPanel.js +22 -0
  67. package/dist/sync/SyncSidebar.d.ts +47 -0
  68. package/dist/sync/SyncSidebar.js +31 -0
  69. package/dist/sync/UploadPanel.d.ts +64 -0
  70. package/dist/sync/UploadPanel.js +36 -0
  71. package/dist/trigger/TriggerBlueprint.d.ts +37 -0
  72. package/dist/trigger/TriggerBlueprint.js +26 -0
  73. package/dist/trigger/tmw/TmwBlueprint.d.ts +71 -0
  74. package/dist/trigger/tmw/TmwBlueprint.js +206 -0
  75. package/dist/trigger/tmw/TmwFlowLane.d.ts +22 -0
  76. package/dist/trigger/tmw/TmwFlowLane.js +44 -0
  77. package/dist/trigger/tmw/TmwSourceLane.d.ts +17 -0
  78. package/dist/trigger/tmw/TmwSourceLane.js +38 -0
  79. package/dist/trigger/tmw/TmwTabs.d.ts +20 -0
  80. package/dist/trigger/tmw/TmwTabs.js +8 -0
  81. package/dist/trigger/tmw/TmwTraceLane.d.ts +9 -0
  82. package/dist/trigger/tmw/TmwTraceLane.js +6 -0
  83. package/dist/trigger/tmw/TriggerDebugTab.d.ts +9 -0
  84. package/dist/trigger/tmw/TriggerDebugTab.js +25 -0
  85. package/dist/trigger/tmw/translate.d.ts +3 -0
  86. package/dist/trigger/tmw/translate.js +2 -0
  87. package/dist/view/ViewWorkspace.d.ts +19 -0
  88. package/dist/view/ViewWorkspace.js +8 -0
  89. package/package.json +89 -0
@@ -0,0 +1,26 @@
1
+ import { type EdgeProps } from "reactflow";
2
+ import { type GraphEdgeOcclusionIndex } from "./graph-edge-mask.js";
3
+ export interface SharedGraphEdgeData {
4
+ readonly label: string;
5
+ readonly markerId?: string;
6
+ readonly relationIds?: readonly string[];
7
+ readonly state: "committed" | "pending" | "search" | "disabled";
8
+ readonly sourceRadius?: number;
9
+ readonly targetRadius?: number;
10
+ readonly parallelIndex?: number;
11
+ readonly parallelCount?: number;
12
+ readonly emphasized?: boolean;
13
+ readonly muted?: boolean;
14
+ readonly maskId?: string;
15
+ readonly occlusionIndex?: GraphEdgeOcclusionIndex;
16
+ readonly occlusionZoom?: number;
17
+ readonly onSelect?: () => void;
18
+ readonly onContextMenu?: (event: {
19
+ readonly clientX: number;
20
+ readonly clientY: number;
21
+ preventDefault(): void;
22
+ stopPropagation(): void;
23
+ }) => void;
24
+ }
25
+ /** 共享边只表达投影状态;关系实际写入仍由宿主 adapter 完成。 */
26
+ export declare function GraphEdge({ sourceX, sourceY, targetX, targetY, data }: EdgeProps<SharedGraphEdgeData>): JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EdgeLabelRenderer } from "reactflow";
3
+ import { computeGraphEdgeGeometry } from "./graph-edge-geometry.js";
4
+ import { graphEdgeLabelWorldBounds, isGraphEdgeLabelOccluded } from "./graph-edge-mask.js";
5
+ /** 共享边只表达投影状态;关系实际写入仍由宿主 adapter 完成。 */
6
+ export function GraphEdge({ sourceX, sourceY, targetX, targetY, data }) {
7
+ const state = data?.state ?? "committed";
8
+ const markerId = data?.markerId;
9
+ const geometry = computeGraphEdgeGeometry({
10
+ sourceX,
11
+ sourceY,
12
+ targetX,
13
+ targetY,
14
+ ...(data?.sourceRadius === undefined ? {} : { sourceRadius: data.sourceRadius }),
15
+ ...(data?.targetRadius === undefined ? {} : { targetRadius: data.targetRadius }),
16
+ ...(data?.parallelIndex === undefined ? {} : { parallelIndex: data.parallelIndex }),
17
+ ...(data?.parallelCount === undefined ? {} : { parallelCount: data.parallelCount })
18
+ });
19
+ const select = () => data?.onSelect?.();
20
+ const emphasized = data?.emphasized === true;
21
+ const labelText = data?.label ?? "0";
22
+ const labelOccluded = !emphasized
23
+ && data?.occlusionIndex !== undefined
24
+ && isGraphEdgeLabelOccluded(data.occlusionIndex, graphEdgeLabelWorldBounds(labelText, geometry.labelX, geometry.labelY, data.occlusionZoom ?? 1));
25
+ const className = [
26
+ "shared-graph-edge",
27
+ `shared-graph-edge--${state}`,
28
+ data?.emphasized === true ? "is-emphasized" : "",
29
+ data?.muted === true ? "is-muted" : ""
30
+ ].filter(Boolean).join(" ");
31
+ return _jsxs(_Fragment, { children: [_jsx("path", { className: className, d: geometry.path, fill: "none", markerEnd: markerId === undefined ? undefined : `url(#${markerId})`, mask: !emphasized && data?.maskId !== undefined ? `url(#${data.maskId})` : undefined, onClick: select, onContextMenu: (event) => {
32
+ event.preventDefault();
33
+ data?.onContextMenu?.(event);
34
+ } }), labelOccluded ? null : _jsx(EdgeLabelRenderer, { children: _jsx("button", { "aria-label": `${labelText} 条关系`, className: `shared-graph-edge__label${emphasized ? " is-emphasized" : ""}`, disabled: state === "disabled" || data?.onSelect === undefined, onClick: (event) => { event.stopPropagation(); select(); }, onContextMenu: (event) => {
35
+ event.preventDefault();
36
+ data?.onContextMenu?.(event);
37
+ }, style: {
38
+ position: "absolute",
39
+ transform: `translate(-50%, -50%) translate(${geometry.labelX}px, ${geometry.labelY}px)`
40
+ }, type: "button", children: labelText }) })] });
41
+ }
@@ -0,0 +1,58 @@
1
+ import { type ReactNode } from "react";
2
+ import type { SpatialObjectV1 } from "@world-engines/spatial-authoring";
3
+ import type { LadybugPageProjection } from "../DesktopAuthoringShell.js";
4
+ import { type GraphPosition } from "./GraphCanvas.js";
5
+ import { type LadybugHistoryEntry } from "./LadybugSurfaces.js";
6
+ export interface LadybugNode {
7
+ readonly node_id: string;
8
+ readonly label: string;
9
+ readonly kind: string;
10
+ readonly description: string | null;
11
+ readonly attributes: Readonly<Record<string, unknown>>;
12
+ readonly relations: readonly {
13
+ readonly source_id: string;
14
+ readonly target_id: string;
15
+ readonly kind: string;
16
+ readonly attributes: Readonly<Record<string, unknown>>;
17
+ }[];
18
+ readonly history: readonly Readonly<Record<string, unknown>>[];
19
+ }
20
+ export interface LadybugExplorerProps {
21
+ readonly nodes: readonly LadybugNode[];
22
+ readonly selected_node_id: string | null;
23
+ readonly search_query: string;
24
+ readonly page?: LadybugPageProjection;
25
+ readonly disabled: boolean;
26
+ readonly graph?: {
27
+ readonly capability?: "ready" | "unavailable";
28
+ readonly positions?: Readonly<Record<string, GraphPosition>>;
29
+ readonly pending_node_ids?: readonly string[];
30
+ readonly disabled_node_ids?: readonly string[];
31
+ readonly vector_state_by_node_id?: Readonly<Record<string, "ready" | "pending" | "missing">>;
32
+ };
33
+ readonly onSelect: (node_id: string) => Promise<void>;
34
+ readonly onSearch: (query: string) => Promise<void>;
35
+ readonly onBrowsePage?: (direction: "first" | "next" | "previous") => Promise<void>;
36
+ readonly onSaveEntity: (entity: {
37
+ readonly node_id: string;
38
+ readonly kind: string;
39
+ readonly attributes: Readonly<Record<string, unknown>>;
40
+ }) => Promise<void>;
41
+ readonly onCreateRelation?: (source_id: string, target_id: string, kind: string) => Promise<void>;
42
+ readonly onDeleteRelation?: (source_id: string, target_id: string, kind: string) => Promise<void>;
43
+ readonly onDeleteEntity?: (node_id: string) => Promise<void>;
44
+ readonly onUnsetAttribute?: (node_id: string, key: string) => Promise<void>;
45
+ readonly onCommitGraphPositions?: (positions: Readonly<Record<string, GraphPosition>>) => Promise<void>;
46
+ /** source readback 中的对象用于校验 Ladybug spatial_ref,不形成第二份 spatial 索引。 */
47
+ readonly spatialObjects: readonly SpatialObjectV1[];
48
+ readonly renderGraphCanvas?: (props: LadybugExplorerProps) => ReactNode;
49
+ readonly renderInspector?: (node: LadybugNode | null) => ReactNode;
50
+ readonly history?: {
51
+ readonly entries: readonly LadybugHistoryEntry[];
52
+ readonly can_undo: boolean;
53
+ readonly can_redo: boolean;
54
+ };
55
+ readonly onUndo?: () => Promise<void>;
56
+ readonly onRedo?: () => Promise<void>;
57
+ }
58
+ export declare function LadybugExplorer(props: LadybugExplorerProps): JSX.Element;
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { SharedGraphCanvas } from "./GraphCanvas.js";
4
+ import { LadybugHistory, LadybugInspector } from "./LadybugSurfaces.js";
5
+ import { LadybugTable } from "./LadybugTable.js";
6
+ function parseEntityAttributes(raw) {
7
+ try {
8
+ const parsed = JSON.parse(raw);
9
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
10
+ ? parsed
11
+ : null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ function compareCodepoint(left, right) {
18
+ return left < right ? -1 : left > right ? 1 : 0;
19
+ }
20
+ export function LadybugExplorer(props) {
21
+ const selected = props.nodes.find((node) => node.node_id === props.selected_node_id) ?? null;
22
+ // 老的 projection 只能安全降级为未搜索状态;不能让可选展示字段阻断整个桌面壳。
23
+ const normalizedSearchQuery = typeof props.search_query === "string" ? props.search_query : "";
24
+ const [query, setQuery] = useState(normalizedSearchQuery);
25
+ const [entityId, setEntityId] = useState(selected?.node_id ?? "");
26
+ const [entityKind, setEntityKind] = useState(selected?.kind ?? "");
27
+ const [attributes, setAttributes] = useState(JSON.stringify(selected?.attributes ?? {}, null, 2));
28
+ const [spatialRef, setSpatialRef] = useState("");
29
+ const [spatialRefError, setSpatialRefError] = useState(null);
30
+ const [relationIntent, setRelationIntent] = useState(null);
31
+ const [relationKind, setRelationKind] = useState("");
32
+ useEffect(() => setQuery(normalizedSearchQuery), [normalizedSearchQuery]);
33
+ // 同一实体的 source revision 改变时保留用户尚未提交的草稿;切换实体才换成
34
+ // 新选择的 canonical readback。Inspector 仍始终展示最新节点投影。
35
+ useEffect(() => {
36
+ setEntityId(selected?.node_id ?? "");
37
+ setEntityKind(selected?.kind ?? "");
38
+ setAttributes(JSON.stringify(selected?.attributes ?? {}, null, 2));
39
+ setSpatialRef(typeof selected?.attributes.spatial_ref === "string" ? selected.attributes.spatial_ref : "");
40
+ setSpatialRefError(null);
41
+ }, [selected?.node_id]);
42
+ const parsedAttributes = parseEntityAttributes(attributes);
43
+ const relations = [...new Map(props.nodes.flatMap((node) => node.relations.map((relation) => {
44
+ const id = JSON.stringify([relation.source_id, relation.kind, relation.target_id]);
45
+ return [id, {
46
+ id,
47
+ source: relation.source_id,
48
+ target: relation.target_id,
49
+ label: relation.kind,
50
+ pending: Boolean(props.graph?.pending_node_ids?.includes(relation.source_id) || props.graph?.pending_node_ids?.includes(relation.target_id)),
51
+ disabled: Boolean(props.graph?.disabled_node_ids?.includes(relation.source_id) || props.graph?.disabled_node_ids?.includes(relation.target_id)),
52
+ searchMatch: normalizedSearchQuery.length > 0 && relation.kind.toLowerCase().includes(normalizedSearchQuery.toLowerCase()),
53
+ }];
54
+ }))).values()];
55
+ const attributesError = parsedAttributes === null ? "实体属性必须是 JSON 对象。" : null;
56
+ const graphDisabled = props.disabled || props.graph?.capability === "unavailable";
57
+ const saveEntity = () => {
58
+ if (entityId.trim().length === 0 || entityKind.trim().length === 0)
59
+ return;
60
+ if (parsedAttributes === null)
61
+ return;
62
+ const next = { ...parsedAttributes };
63
+ if (spatialRef.length > 0) {
64
+ if (!props.spatialObjects.some((object) => object.id === spatialRef)) {
65
+ setSpatialRefError("spatial_ref 必须指向当前 source readback 中的空间对象。");
66
+ return;
67
+ }
68
+ next.spatial_ref = spatialRef;
69
+ }
70
+ else {
71
+ delete next.spatial_ref;
72
+ }
73
+ setSpatialRefError(null);
74
+ void props.onSaveEntity({ node_id: entityId.trim(), kind: entityKind.trim(), attributes: next });
75
+ };
76
+ const renderInspectorBody = props.renderInspector ?? ((node) => (_jsxs(_Fragment, { children: [_jsxs("dl", { children: [_jsx("dt", { children: "\u8282\u70B9" }), _jsx("dd", { children: node.label }), _jsx("dt", { children: "\u7C7B\u578B" }), _jsx("dd", { children: node.kind }), _jsx("dt", { children: "\u8BF4\u660E" }), _jsx("dd", { children: node.description ?? "无" }), _jsx("dt", { children: "\u5173\u7CFB" }), _jsx("dd", { children: node.relations.length }), _jsx("dt", { children: "\u5C5E\u6027\u5386\u53F2" }), _jsx("dd", { children: node.history.length })] }), _jsx("ul", { "aria-label": "\u5B9E\u4F53\u5173\u7CFB", children: [...node.relations].sort((left, right) => compareCodepoint(left.source_id, right.source_id) || compareCodepoint(left.kind, right.kind) || compareCodepoint(left.target_id, right.target_id)).map((relation) => {
77
+ const direction = relation.source_id === relation.target_id ? "自环" : relation.source_id === node.node_id ? "出" : "入";
78
+ return _jsxs("li", { children: [_jsx("span", { "aria-label": `关系方向 ${direction}`, children: direction }), " \u00B7 ", relation.source_id, " \u2014", relation.kind, "\u2192 ", relation.target_id, props.onDeleteRelation === undefined ? null : _jsx("button", { disabled: graphDisabled, onClick: () => void props.onDeleteRelation?.(relation.source_id, relation.target_id, relation.kind), type: "button", children: "\u5220\u9664\u5173\u7CFB" })] }, `${relation.source_id}:${relation.kind}:${relation.target_id}`);
79
+ }) }), _jsx("ul", { "aria-label": "\u5B9E\u4F53\u5C5E\u6027", children: Object.keys(node.attributes).sort().map((key) => _jsxs("li", { children: [key, props.onUnsetAttribute === undefined ? null : _jsx("button", { disabled: props.disabled, onClick: () => void props.onUnsetAttribute?.(node.node_id, key), type: "button", children: "\u79FB\u9664\u5C5E\u6027" })] }, key)) }), props.onDeleteEntity === undefined ? null : _jsx("button", { disabled: props.disabled, onClick: () => void props.onDeleteEntity?.(node.node_id), type: "button", children: "\u5220\u9664\u5B9E\u4F53" })] })));
80
+ return (_jsxs("section", { "aria-label": "Ladybug \u56FE\u8C31", "data-testid": "ladybug-explorer", children: [_jsxs("form", { "aria-label": "Ladybug \u641C\u7D22", onSubmit: (event) => { event.preventDefault(); if (query.trim().length > 0)
81
+ void props.onSearch(query.trim()); }, children: [_jsx("input", { "aria-label": "\u641C\u7D22\u5B9E\u4F53", disabled: props.disabled, onChange: (event) => setQuery(event.target.value), value: query }), _jsx("button", { disabled: props.disabled || query.trim().length === 0, type: "submit", children: "\u641C\u7D22" }), props.page?.capability === "ready" && props.page.mode === "search" && props.onBrowsePage !== undefined ? _jsx("button", { disabled: props.disabled, onClick: () => void props.onBrowsePage?.("first"), type: "button", children: "\u8FD4\u56DE\u5168\u91CF\u5B9E\u4F53" }) : null] }), _jsxs("div", { "aria-label": "Ladybug GraphCanvas", children: [props.graph?.capability === "unavailable" ? _jsx("p", { role: "alert", children: "GraphCanvas layout \u6295\u5F71\u4E0D\u53EF\u7528\uFF0C\u5DF2\u7981\u7528\u7F16\u8F91\u4EE5\u4FDD\u62A4 source readback\u3002" }) : null, props.renderGraphCanvas?.(props) ?? _jsx(SharedGraphCanvas, { disabled: graphDisabled, nodes: props.nodes.map((node) => ({ id: node.node_id, label: node.label, kind: node.kind, disabled: Boolean(props.graph?.disabled_node_ids?.includes(node.node_id)), pending: Boolean(props.graph?.pending_node_ids?.includes(node.node_id)), ...(props.graph?.vector_state_by_node_id?.[node.node_id] === undefined ? {} : { vector: props.graph.vector_state_by_node_id[node.node_id] }) })), onCommitPositions: (positions) => void props.onCommitGraphPositions?.(positions), ...(props.onCreateRelation === undefined ? {} : { onRelationDragIntent: (source, target) => setRelationIntent({ source, target }) }), onSelectNode: (id) => void props.onSelect(id), relations: relations, searchQuery: normalizedSearchQuery, selectedNodeId: props.selected_node_id, ...(props.graph?.positions === undefined ? {} : { positions: props.graph.positions }) })] }), _jsx(LadybugTable, { disabled: props.disabled, nodes: props.nodes, onSelect: props.onSelect, selected_node_id: props.selected_node_id, ...(props.page === undefined ? {} : { page: props.page }), ...(props.onBrowsePage === undefined ? {} : { onBrowsePage: props.onBrowsePage }) }), relationIntent === null ? null : _jsxs("form", { "aria-label": "\u65B0\u5EFA\u5173\u7CFB", onSubmit: (event) => { event.preventDefault(); if (relationKind.trim().length === 0 || props.onCreateRelation === undefined)
82
+ return; void props.onCreateRelation(relationIntent.source, relationIntent.target, relationKind.trim()); setRelationIntent(null); setRelationKind(""); }, children: [_jsxs("p", { children: [relationIntent.source, " \u2192 ", relationIntent.target] }), _jsx("input", { "aria-label": "\u65B0\u5173\u7CFB\u7C7B\u578B", onChange: (event) => setRelationKind(event.target.value), value: relationKind }), _jsx("button", { disabled: graphDisabled || relationKind.trim().length === 0 || props.onCreateRelation === undefined, type: "submit", children: "\u521B\u5EFA\u5173\u7CFB" }), _jsx("button", { onClick: () => setRelationIntent(null), type: "button", children: "\u53D6\u6D88" })] }), _jsx(LadybugInspector, { node: selected, renderBody: renderInspectorBody, ...(props.renderInspector === undefined ? {} : { renderEmpty: () => props.renderInspector(null) }) }), _jsx(LadybugHistory, { canRedo: props.history?.can_redo ?? false, canUndo: props.history?.can_undo ?? false, entries: props.history?.entries ?? selected?.history.map((entry, index) => ({ id: `${selected.node_id}:${index}`, label: typeof entry.label === "string" ? entry.label : `历史 ${index + 1}` })) ?? [], ...(props.onRedo === undefined ? {} : { onRedo: () => void props.onRedo?.() }), ...(props.onUndo === undefined ? {} : { onUndo: () => void props.onUndo?.() }) }), _jsxs("form", { "aria-label": "\u5B9E\u4F53\u7F16\u8F91", onSubmit: (event) => { event.preventDefault(); saveEntity(); }, children: [_jsx("input", { "aria-label": "\u5B9E\u4F53 ID", disabled: props.disabled, onChange: (event) => setEntityId(event.target.value), value: entityId }), _jsx("input", { "aria-label": "\u5B9E\u4F53\u7C7B\u578B", disabled: props.disabled, onChange: (event) => setEntityKind(event.target.value), value: entityKind }), _jsx("textarea", { "aria-label": "\u5B9E\u4F53\u5C5E\u6027 JSON", disabled: props.disabled, onChange: (event) => setAttributes(event.target.value), value: attributes }), _jsxs("label", { children: ["spatial_ref", _jsxs("select", { "aria-label": "Ladybug spatial_ref", disabled: props.disabled, onChange: (event) => setSpatialRef(event.target.value), value: spatialRef, children: [_jsx("option", { value: "", children: "\u65E0\u7A7A\u95F4\u5F15\u7528" }), props.spatialObjects.map((object) => _jsxs("option", { value: object.id, children: [object.label, " \u00B7 ", object.id] }, object.id))] })] }), attributesError === null ? null : _jsx("p", { role: "alert", children: attributesError }), spatialRefError === null ? null : _jsx("p", { role: "alert", children: spatialRefError }), _jsx("button", { disabled: props.disabled || attributesError !== null || entityId.trim().length === 0 || entityKind.trim().length === 0, type: "submit", children: "\u4FDD\u5B58\u5B9E\u4F53" })] })] }));
83
+ }
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from "react";
2
+ import type { LadybugNode } from "./LadybugExplorer.js";
3
+ export interface LadybugInspectorProps {
4
+ readonly node: LadybugNode | null;
5
+ readonly disabled?: boolean;
6
+ /** Browser 注入现有成熟编辑 body;共享外壳不读取 Browser store。 */
7
+ readonly renderBody?: (node: LadybugNode) => ReactNode;
8
+ readonly renderEmpty?: () => ReactNode;
9
+ }
10
+ /** Browser 与 Desktop 共用的 Inspector 外壳;宿主差异只存在于注入的数据和 mutation adapter。 */
11
+ export declare function LadybugInspector({ node, renderBody, renderEmpty }: LadybugInspectorProps): JSX.Element;
12
+ export interface LadybugHistoryEntry {
13
+ readonly id: string;
14
+ readonly label: string;
15
+ readonly timestamp?: number;
16
+ }
17
+ export interface LadybugHistoryProps {
18
+ readonly entries: readonly LadybugHistoryEntry[];
19
+ readonly canUndo?: boolean;
20
+ readonly canRedo?: boolean;
21
+ readonly onUndo?: () => void;
22
+ readonly onRedo?: () => void;
23
+ }
24
+ /** 仅渲染 history 投影并发出 intent;OPFS/IndexedDB/ProjectHost 持久化由各宿主 adapter 负责。 */
25
+ export declare function LadybugHistory({ entries, canUndo, canRedo, onUndo, onRedo }: LadybugHistoryProps): JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Browser 与 Desktop 共用的 Inspector 外壳;宿主差异只存在于注入的数据和 mutation adapter。 */
3
+ export function LadybugInspector({ node, renderBody, renderEmpty }) {
4
+ return (_jsx("aside", { "aria-label": "Ladybug Inspector", "data-testid": "ladybug-inspector", style: { display: "contents" }, children: node === null ? renderEmpty?.() ?? _jsx("p", { children: "\u9009\u62E9\u4E00\u4E2A\u8282\u70B9\u4EE5\u67E5\u770B Inspector\u3002" }) : renderBody?.(node) ?? (_jsxs("dl", { children: [_jsx("dt", { children: "\u8282\u70B9" }), _jsx("dd", { children: node.label }), _jsx("dt", { children: "\u7C7B\u578B" }), _jsx("dd", { children: node.kind }), _jsx("dt", { children: "\u8BF4\u660E" }), _jsx("dd", { children: node.description ?? "无" }), _jsx("dt", { children: "\u5173\u7CFB" }), _jsx("dd", { children: node.relations.length }), _jsx("dt", { children: "\u5C5E\u6027\u5386\u53F2" }), _jsx("dd", { children: node.history.length })] })) }));
5
+ }
6
+ /** 仅渲染 history 投影并发出 intent;OPFS/IndexedDB/ProjectHost 持久化由各宿主 adapter 负责。 */
7
+ export function LadybugHistory({ entries, canUndo = false, canRedo = false, onUndo, onRedo }) {
8
+ return (_jsxs("section", { "aria-label": "Ladybug History", "data-testid": "ladybug-history", children: [_jsxs("div", { role: "toolbar", "aria-label": "Ladybug \u5386\u53F2\u64CD\u4F5C", children: [_jsx("button", { disabled: !canUndo || onUndo === undefined, onClick: onUndo, type: "button", children: "\u64A4\u9500" }), _jsx("button", { disabled: !canRedo || onRedo === undefined, onClick: onRedo, type: "button", children: "\u91CD\u505A" })] }), entries.length === 0 ? _jsx("p", { children: "\u6682\u65E0\u5386\u53F2\u8BB0\u5F55\u3002" }) : _jsx("ol", { children: entries.map((entry) => _jsx("li", { children: entry.label }, entry.id)) })] }));
9
+ }
@@ -0,0 +1,15 @@
1
+ import type { LadybugPageProjection } from "../DesktopAuthoringShell.js";
2
+ import type { LadybugNode } from "./LadybugExplorer.js";
3
+ export interface LadybugTableProps {
4
+ readonly disabled: boolean;
5
+ readonly nodes: readonly LadybugNode[];
6
+ readonly selected_node_id: string | null;
7
+ readonly onSelect: (node_id: string) => Promise<void>;
8
+ readonly page?: LadybugPageProjection;
9
+ readonly onBrowsePage?: (direction: "first" | "next" | "previous") => Promise<void>;
10
+ }
11
+ /**
12
+ * ready page 完全消费 Host cursor;旧调用方没有 page projection 时才保留组件内分页,
13
+ * 不能把该兼容路径当作完整数据库浏览能力。
14
+ */
15
+ export declare function LadybugTable(props: LadybugTableProps): JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ const PAGE_SIZE = 20;
4
+ function compareCodepoint(left, right) {
5
+ return left < right ? -1 : left > right ? 1 : 0;
6
+ }
7
+ /**
8
+ * ready page 完全消费 Host cursor;旧调用方没有 page projection 时才保留组件内分页,
9
+ * 不能把该兼容路径当作完整数据库浏览能力。
10
+ */
11
+ export function LadybugTable(props) {
12
+ const [page, setPage] = useState(0);
13
+ const sorted = useMemo(() => [...props.nodes].sort((left, right) => compareCodepoint(left.node_id, right.node_id)
14
+ || compareCodepoint(left.kind, right.kind)
15
+ || compareCodepoint(left.label, right.label)), [props.nodes]);
16
+ const hostPage = props.page?.capability === "ready" ? props.page : null;
17
+ const hostVisible = hostPage === null ? sorted : hostPage.item_ids.flatMap((id) => {
18
+ const node = sorted.find((candidate) => candidate.node_id === id);
19
+ return node === undefined ? [] : [node];
20
+ });
21
+ const pageCount = Math.max(1, Math.ceil(sorted.length / PAGE_SIZE));
22
+ const safePage = Math.min(page, pageCount - 1);
23
+ const visible = hostPage === null ? sorted.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE) : hostVisible;
24
+ useEffect(() => setPage(0), [props.nodes]);
25
+ return (_jsxs("section", { "aria-label": "Ladybug \u5B9E\u4F53\u8868", children: [props.page?.capability === "unavailable" ? _jsx("p", { role: "alert", children: props.page.reason }) : null, _jsx("p", { role: "status", children: hostPage === null ? `当前结果 ${sorted.length} 个实体;第 ${safePage + 1}/${pageCount} 页` : hostPage.mode === "browse" ? `全量实体:本页 ${visible.length} 个,共 ${hostPage.total_count ?? "未知"} 个` : `搜索结果 ${visible.length} 个` }), _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u64CD\u4F5C" }), _jsx("th", { children: "ID" }), _jsx("th", { children: "\u7C7B\u578B" }), _jsx("th", { children: "\u6807\u7B7E" }), _jsx("th", { children: "\u5C5E\u6027" }), _jsx("th", { children: "\u5173\u7CFB" })] }) }), _jsx("tbody", { children: visible.map((node) => _jsxs("tr", { "aria-selected": node.node_id === props.selected_node_id, children: [_jsx("td", { children: _jsx("button", { disabled: props.disabled, onClick: () => void props.onSelect(node.node_id), type: "button", children: "\u9009\u62E9" }) }), _jsx("td", { children: node.node_id }), _jsx("td", { children: node.kind }), _jsx("td", { children: node.label }), _jsx("td", { children: Object.keys(node.attributes).length }), _jsx("td", { children: node.relations.length })] }, node.node_id)) })] }), sorted.length === 0 ? _jsx("p", { children: props.page?.capability === "ready" && props.page.mode === "browse" ? "数据库当前没有实体。" : "没有匹配的实体。" }) : null, hostPage?.mode === "browse" ? _jsx("p", { children: "\u5168\u91CF\u9875\u53EA\u8BFB\u53D6\u5B9E\u4F53\uFF1B\u5173\u7CFB\u5728\u9009\u62E9\u5B9E\u4F53\u540E\u70B9\u67E5\u3002\u5F53\u524D Host \u7684\u70B9\u67E5\u5173\u7CFB\u5C1A\u672A\u63D0\u4F9B\u72EC\u7ACB\u5206\u9875\u3002" }) : null, _jsxs("nav", { "aria-label": "Ladybug \u5B9E\u4F53\u5206\u9875", children: [_jsx("button", { disabled: props.disabled || (hostPage === null ? safePage === 0 : hostPage.mode !== "browse" || !hostPage.has_previous || props.onBrowsePage === undefined), onClick: () => hostPage === null ? setPage((current) => Math.max(0, current - 1)) : void props.onBrowsePage?.("previous"), type: "button", children: "\u4E0A\u4E00\u9875" }), _jsx("button", { disabled: props.disabled || (hostPage === null ? safePage + 1 >= pageCount : hostPage.mode !== "browse" || hostPage.next_cursor === null || props.onBrowsePage === undefined), onClick: () => hostPage === null ? setPage((current) => Math.min(pageCount - 1, current + 1)) : void props.onBrowsePage?.("next"), type: "button", children: "\u4E0B\u4E00\u9875" })] })] }));
26
+ }
@@ -0,0 +1,98 @@
1
+ import type { TriggerDef } from "@world-engines/blocks-editor";
2
+ import type { ViewAccessMode } from "@world-engines/ladybug-bridge";
3
+ import { SERIALIZED_PROP_ENVELOPE_MARKER } from "@world-engines/protocol-ts/entity-prop-envelope";
4
+ export type { ViewAccessMode } from "@world-engines/ladybug-bridge";
5
+ export interface GraphSourceReference {
6
+ readonly document_entity_id: string;
7
+ readonly chunk_entity_id: string;
8
+ readonly chunk_sha256: string;
9
+ readonly source_start: number;
10
+ readonly source_end: number;
11
+ }
12
+ export type GraphTriggerDef = TriggerDef & {
13
+ readonly source_refs?: ReadonlyArray<GraphSourceReference>;
14
+ };
15
+ export type EntityReferenceKind = "node" | "edge" | "attr";
16
+ export interface EntityReferenceTarget {
17
+ readonly kind: EntityReferenceKind;
18
+ readonly id: string;
19
+ readonly attr_key?: string;
20
+ }
21
+ export interface EntityReferenceValue {
22
+ readonly $ref: EntityReferenceTarget;
23
+ }
24
+ export interface ScenarioAssetReferenceValue {
25
+ readonly $asset: string;
26
+ }
27
+ export type EntityRichTextSegment = {
28
+ readonly text: string;
29
+ } | EntityReferenceValue;
30
+ export interface EntityRichTextValue {
31
+ readonly $rich: ReadonlyArray<EntityRichTextSegment>;
32
+ }
33
+ export type EntityPropObject = {
34
+ readonly [subKey: string]: EntityProp;
35
+ };
36
+ export type EntityPropArray = ReadonlyArray<EntityProp>;
37
+ export type EntityPropValue = string | ScenarioAssetReferenceValue | EntityReferenceValue | EntityRichTextValue | EntityPropArray | EntityPropObject;
38
+ export type EntityStateKind = "available" | "disabled" | "frozen";
39
+ export interface FrozenEntry {
40
+ readonly source_id: string;
41
+ readonly prev_state?: EntityStateKind;
42
+ readonly additional_state?: EntityStateKind;
43
+ readonly cancel_condition?: TriggerDef;
44
+ readonly expires_at?: number;
45
+ }
46
+ export interface AttrState {
47
+ readonly id: string;
48
+ readonly value: EntityPropValue;
49
+ readonly enter_when_trigger_id?: string;
50
+ readonly locked_until_trigger_id?: string;
51
+ readonly kind?: EntityStateKind;
52
+ readonly frozen_stack?: ReadonlyArray<FrozenEntry>;
53
+ readonly dead?: boolean;
54
+ readonly disabled?: boolean;
55
+ }
56
+ export interface EntityProp {
57
+ readonly value: EntityPropValue;
58
+ readonly view_access?: ViewAccessMode;
59
+ readonly trigger?: GraphTriggerDef;
60
+ readonly source_refs?: ReadonlyArray<GraphSourceReference>;
61
+ readonly states?: ReadonlyArray<AttrState>;
62
+ readonly title?: string;
63
+ readonly initial_available?: boolean;
64
+ }
65
+ export declare const isPropScalar: (v: EntityPropValue) => v is string;
66
+ export declare const isEntityReferenceValue: (value: unknown) => value is EntityReferenceValue;
67
+ export declare const isScenarioAssetReferenceValue: (value: unknown) => value is ScenarioAssetReferenceValue;
68
+ export declare const isPropArray: (v: EntityPropValue) => v is EntityPropArray;
69
+ export declare const isPropReference: (v: EntityPropValue) => v is EntityReferenceValue;
70
+ export declare const isPropRichText: (v: EntityPropValue) => v is EntityRichTextValue;
71
+ export declare const isPropTree: (v: EntityPropValue) => v is EntityPropObject;
72
+ export declare const propValueToText: (v: EntityPropValue) => string;
73
+ export type PropShape = "scalar" | "tree" | "array" | "reference" | "richtext";
74
+ export declare const propShape: (v: EntityPropValue) => PropShape;
75
+ export declare const normalizeEntityProps: (raw: unknown) => Record<string, EntityProp>;
76
+ export declare const convertPropShape: (v: EntityPropValue, target: PropShape, defaultKey: string) => EntityPropValue;
77
+ export declare const emptyPropValue: (shape: PropShape) => EntityPropValue;
78
+ export type SerializedPropValue = string | ScenarioAssetReferenceValue | EntityReferenceValue | EntityRichTextValue | ReadonlyArray<SerializedProp> | {
79
+ readonly [subKey: string]: SerializedProp;
80
+ };
81
+ export type SerializedProp = SerializedPropValue | {
82
+ readonly [SERIALIZED_PROP_ENVELOPE_MARKER]: true;
83
+ readonly value: SerializedPropValue;
84
+ readonly trigger?: TriggerDef;
85
+ readonly states?: ReadonlyArray<Record<string, unknown>>;
86
+ readonly title?: string;
87
+ readonly initial_available?: boolean;
88
+ readonly source_refs?: ReadonlyArray<GraphSourceReference>;
89
+ readonly view_access?: ViewAccessMode;
90
+ };
91
+ export declare const serializeEntityProps: (props: Record<string, EntityProp>) => Record<string, SerializedProp>;
92
+ export declare const toStringPropsLegacy: (props: Record<string, EntityProp>) => Record<string, string>;
93
+ export declare const propValue: (props: Record<string, EntityProp>, key: string) => string;
94
+ export declare const activeAttrState: (prop: EntityProp) => AttrState | undefined;
95
+ export declare const isPropDead: (prop: EntityProp) => boolean;
96
+ export declare const effectivePropValue: (prop: EntityProp) => EntityPropValue | undefined;
97
+ export declare const effectivePropValueText: (prop: EntityProp) => string;
98
+ export declare const isEntityFullyDead: (props: Record<string, EntityProp>) => boolean;