@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.
- package/LICENSE +46 -0
- package/dist/DesktopAuthoringShell.d.ts +178 -0
- package/dist/DesktopAuthoringShell.js +235 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +12 -0
- package/dist/ladybug/BrowserGraphCanvasAdapter.d.ts +12 -0
- package/dist/ladybug/BrowserGraphCanvasAdapter.js +8 -0
- package/dist/ladybug/GraphCanvas.d.ts +121 -0
- package/dist/ladybug/GraphCanvas.js +1439 -0
- package/dist/ladybug/GraphEdge.d.ts +26 -0
- package/dist/ladybug/GraphEdge.js +41 -0
- package/dist/ladybug/LadybugExplorer.d.ts +58 -0
- package/dist/ladybug/LadybugExplorer.js +83 -0
- package/dist/ladybug/LadybugSurfaces.d.ts +25 -0
- package/dist/ladybug/LadybugSurfaces.js +9 -0
- package/dist/ladybug/LadybugTable.d.ts +15 -0
- package/dist/ladybug/LadybugTable.js +26 -0
- package/dist/ladybug/entity-props.d.ts +98 -0
- package/dist/ladybug/entity-props.js +363 -0
- package/dist/ladybug/graph-edge-geometry.d.ts +19 -0
- package/dist/ladybug/graph-edge-geometry.js +78 -0
- package/dist/ladybug/graph-edge-mask.d.ts +21 -0
- package/dist/ladybug/graph-edge-mask.js +52 -0
- package/dist/ladybug/graph-groups.d.ts +47 -0
- package/dist/ladybug/graph-groups.js +245 -0
- package/dist/ladybug/graph-layout-contract.d.ts +34 -0
- package/dist/ladybug/graph-layout-contract.js +55 -0
- package/dist/ladybug/graph-layout-solver.d.ts +16 -0
- package/dist/ladybug/graph-layout-solver.js +96 -0
- package/dist/ladybug/graph-layout-wasm.d.ts +9 -0
- package/dist/ladybug/graph-layout-wasm.js +88 -0
- package/dist/ladybug/graph-layout-worker-protocol.d.ts +18 -0
- package/dist/ladybug/graph-layout-worker-protocol.js +1 -0
- package/dist/ladybug/graph-layout-worker.d.ts +1 -0
- package/dist/ladybug/graph-layout-worker.js +66 -0
- package/dist/ladybug/graph-layout.d.ts +2 -0
- package/dist/ladybug/graph-layout.js +1 -0
- package/dist/ladybug/graph-layout.wasm +0 -0
- package/dist/ladybug/graph-node-metrics.d.ts +33 -0
- package/dist/ladybug/graph-node-metrics.js +173 -0
- package/dist/ladybug/graph-physics.d.ts +49 -0
- package/dist/ladybug/graph-physics.js +266 -0
- package/dist/ladybug/graph-placement.d.ts +33 -0
- package/dist/ladybug/graph-placement.js +253 -0
- package/dist/ladybug/graph.css +530 -0
- package/dist/ladybug/graph.d.ts +18 -0
- package/dist/ladybug/graph.js +9 -0
- package/dist/ladybug/score-tags.d.ts +11 -0
- package/dist/ladybug/score-tags.js +1 -0
- package/dist/map/MapWorkspace.d.ts +14 -0
- package/dist/map/MapWorkspace.js +9 -0
- package/dist/score/ScoreRadar.d.ts +10 -0
- package/dist/score/ScoreRadar.js +66 -0
- package/dist/score/index.d.ts +3 -0
- package/dist/score/index.js +3 -0
- package/dist/score/radar-index.d.ts +3 -0
- package/dist/score/radar-index.js +2 -0
- package/dist/score/scenario-scoring-features.d.ts +90 -0
- package/dist/score/scenario-scoring-features.js +380 -0
- package/dist/score/scenario-structural-score.d.ts +76 -0
- package/dist/score/scenario-structural-score.js +211 -0
- package/dist/score/score-presentation.d.ts +51 -0
- package/dist/score/score-presentation.js +44 -0
- package/dist/score/score-radar.css +1 -0
- package/dist/sync/DownloadPanel.d.ts +42 -0
- package/dist/sync/DownloadPanel.js +22 -0
- package/dist/sync/SyncSidebar.d.ts +47 -0
- package/dist/sync/SyncSidebar.js +31 -0
- package/dist/sync/UploadPanel.d.ts +64 -0
- package/dist/sync/UploadPanel.js +36 -0
- package/dist/trigger/TriggerBlueprint.d.ts +37 -0
- package/dist/trigger/TriggerBlueprint.js +26 -0
- package/dist/trigger/tmw/TmwBlueprint.d.ts +71 -0
- package/dist/trigger/tmw/TmwBlueprint.js +206 -0
- package/dist/trigger/tmw/TmwFlowLane.d.ts +22 -0
- package/dist/trigger/tmw/TmwFlowLane.js +44 -0
- package/dist/trigger/tmw/TmwSourceLane.d.ts +17 -0
- package/dist/trigger/tmw/TmwSourceLane.js +38 -0
- package/dist/trigger/tmw/TmwTabs.d.ts +20 -0
- package/dist/trigger/tmw/TmwTabs.js +8 -0
- package/dist/trigger/tmw/TmwTraceLane.d.ts +9 -0
- package/dist/trigger/tmw/TmwTraceLane.js +6 -0
- package/dist/trigger/tmw/TriggerDebugTab.d.ts +9 -0
- package/dist/trigger/tmw/TriggerDebugTab.js +25 -0
- package/dist/trigger/tmw/translate.d.ts +3 -0
- package/dist/trigger/tmw/translate.js +2 -0
- package/dist/view/ViewWorkspace.d.ts +19 -0
- package/dist/view/ViewWorkspace.js +8 -0
- package/package.json +89 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { TmwDiagnostic, TmwSimulationTraceEntry, TriggerNodeKindV3, TriggerNodeV3 } from "@world-engines/tmw";
|
|
2
|
+
import { type TmwTranslate } from "./translate.js";
|
|
3
|
+
export interface TmwNodeDraft {
|
|
4
|
+
readonly kind: TriggerNodeKindV3;
|
|
5
|
+
readonly stableId: string;
|
|
6
|
+
readonly enabled: boolean;
|
|
7
|
+
readonly domain: "logical_seq" | "world_time" | "presentation_seq" | "real_time";
|
|
8
|
+
readonly dueValue: number;
|
|
9
|
+
readonly effectiveValue: number;
|
|
10
|
+
readonly realTimeClock: "monotonic_session" | "active_play" | "wall_clock";
|
|
11
|
+
readonly wallClockPolicy: "elapse_while_suspended" | "active_session_only" | "reject";
|
|
12
|
+
readonly eventType: string;
|
|
13
|
+
readonly hardFact: boolean;
|
|
14
|
+
readonly conditionValue: boolean;
|
|
15
|
+
readonly capabilityId: string;
|
|
16
|
+
readonly argumentsJson: string;
|
|
17
|
+
readonly executionPolicy: "attempt_once" | "ensure_until";
|
|
18
|
+
readonly rewritePolicy: "normal_only" | "rewrite_only" | "normal_and_rewrite";
|
|
19
|
+
readonly priority: number;
|
|
20
|
+
readonly idempotencyKey: string;
|
|
21
|
+
readonly presentationKind: "atmosphere" | "view" | "music";
|
|
22
|
+
readonly presentationIdentity: string;
|
|
23
|
+
readonly presentationRevision: number;
|
|
24
|
+
readonly presentationIntensity: number;
|
|
25
|
+
readonly presentationVisible: boolean;
|
|
26
|
+
readonly preferredResourceId: string;
|
|
27
|
+
}
|
|
28
|
+
export interface TmwSimulationDraft {
|
|
29
|
+
readonly eventType: string;
|
|
30
|
+
readonly eventPayloadJson: string;
|
|
31
|
+
readonly stateJson: string;
|
|
32
|
+
readonly clockDomain: "logical_seq" | "world_time" | "presentation_seq" | "real_time";
|
|
33
|
+
readonly clockValue: number;
|
|
34
|
+
readonly crossingDirection: "forward" | "backward";
|
|
35
|
+
readonly realTimeClock: "monotonic_session" | "active_play" | "wall_clock";
|
|
36
|
+
readonly presentationPlanId: string;
|
|
37
|
+
readonly presentationSegmentId: string;
|
|
38
|
+
}
|
|
39
|
+
export interface TmwBlueprintProps {
|
|
40
|
+
/** 两端都传入同一 SourceV3 trigger 投影;其余 source 字段不属于 UI。 */
|
|
41
|
+
readonly source: {
|
|
42
|
+
readonly source_revision: number;
|
|
43
|
+
readonly trigger_document: import("@world-engines/tmw").TriggerDocumentV3;
|
|
44
|
+
};
|
|
45
|
+
readonly transactionId: string | null;
|
|
46
|
+
readonly transactionStatus: "idle" | "draft" | "validated" | "simulated" | "committed" | "expired" | "blocked";
|
|
47
|
+
readonly diagnostics: ReadonlyArray<TmwDiagnostic>;
|
|
48
|
+
readonly trace: ReadonlyArray<TmwSimulationTraceEntry>;
|
|
49
|
+
readonly busy: boolean;
|
|
50
|
+
readonly onBegin: () => void;
|
|
51
|
+
readonly onValidate: () => void;
|
|
52
|
+
readonly onSimulate: (draft: TmwSimulationDraft) => void;
|
|
53
|
+
readonly onCommit: () => void;
|
|
54
|
+
readonly capabilityIds: ReadonlyArray<string>;
|
|
55
|
+
readonly onAddNode: (draft: TmwNodeDraft) => void;
|
|
56
|
+
readonly onConnect: (fromStableId: string, toStableId: string) => void;
|
|
57
|
+
readonly onDisconnect: (fromStableId: string, toStableId: string) => void;
|
|
58
|
+
readonly onDeleteNode: (stableId: string) => void;
|
|
59
|
+
readonly onUpdateNode: (node: TriggerNodeV3) => void;
|
|
60
|
+
readonly layoutPositions: Readonly<Record<string, {
|
|
61
|
+
readonly x: number;
|
|
62
|
+
readonly y: number;
|
|
63
|
+
}>>;
|
|
64
|
+
readonly onLayout: (positions: Readonly<Record<string, {
|
|
65
|
+
readonly x: number;
|
|
66
|
+
readonly y: number;
|
|
67
|
+
}>>) => void;
|
|
68
|
+
/** Browser 注入现有 i18n;Desktop 使用同一组件的默认 key。 */
|
|
69
|
+
readonly translate?: TmwTranslate;
|
|
70
|
+
}
|
|
71
|
+
export declare function TmwBlueprint(props: TmwBlueprintProps): JSX.Element;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { getTmwRuntimeDomainCapability } from "@world-engines/tmw";
|
|
4
|
+
import { BlocksWorkspace, tmwConditionNodeFromWorkspace, tmwConditionNodeToWorkspace } from "@world-engines/blocks-editor";
|
|
5
|
+
import { TriggerDebugTab } from "./TriggerDebugTab.js";
|
|
6
|
+
import { TmwTabs, TmwToggle } from "./TmwTabs.js";
|
|
7
|
+
import { defaultTmwTranslate } from "./translate.js";
|
|
8
|
+
import { TmwFlowLane } from "./TmwFlowLane.js";
|
|
9
|
+
import { TmwSourceLane } from "./TmwSourceLane.js";
|
|
10
|
+
import { TmwTraceLane } from "./TmwTraceLane.js";
|
|
11
|
+
const nodeEnabled = (node) => node.enabled !== false;
|
|
12
|
+
const newDraft = (kind) => {
|
|
13
|
+
const stableId = `${kind}.${crypto.randomUUID().replaceAll("-", "").slice(0, 16)}`;
|
|
14
|
+
return {
|
|
15
|
+
kind,
|
|
16
|
+
stableId,
|
|
17
|
+
enabled: true,
|
|
18
|
+
domain: kind === "presentation" ? "presentation_seq" : "logical_seq",
|
|
19
|
+
dueValue: 0,
|
|
20
|
+
effectiveValue: 0,
|
|
21
|
+
realTimeClock: "active_play",
|
|
22
|
+
wallClockPolicy: "active_session_only",
|
|
23
|
+
eventType: "",
|
|
24
|
+
hardFact: false,
|
|
25
|
+
conditionValue: true,
|
|
26
|
+
capabilityId: "",
|
|
27
|
+
argumentsJson: "{}",
|
|
28
|
+
executionPolicy: "attempt_once",
|
|
29
|
+
rewritePolicy: "normal_and_rewrite",
|
|
30
|
+
priority: 0,
|
|
31
|
+
idempotencyKey: `${stableId}.once`,
|
|
32
|
+
presentationKind: "atmosphere",
|
|
33
|
+
presentationIdentity: "",
|
|
34
|
+
presentationRevision: 1,
|
|
35
|
+
presentationIntensity: 0,
|
|
36
|
+
presentationVisible: true,
|
|
37
|
+
preferredResourceId: "",
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const draftFromNode = (node) => {
|
|
41
|
+
const base = newDraft(node.kind);
|
|
42
|
+
const time = node.effective_time;
|
|
43
|
+
const common = {
|
|
44
|
+
...base,
|
|
45
|
+
stableId: node.stable_id,
|
|
46
|
+
enabled: nodeEnabled(node),
|
|
47
|
+
domain: time.domain,
|
|
48
|
+
dueValue: time.due_value,
|
|
49
|
+
effectiveValue: time.effective_value,
|
|
50
|
+
realTimeClock: time.real_time_clock ?? "active_play",
|
|
51
|
+
wallClockPolicy: time.wall_clock_policy ?? "active_session_only",
|
|
52
|
+
};
|
|
53
|
+
if (node.kind === "event")
|
|
54
|
+
return { ...common, eventType: node.event_type, hardFact: node.hard_fact };
|
|
55
|
+
if (node.kind === "condition")
|
|
56
|
+
return { ...common, conditionValue: node.expression.op === "literal" ? node.expression.value : true };
|
|
57
|
+
if (node.kind === "effect")
|
|
58
|
+
return { ...common, capabilityId: node.capability_id, argumentsJson: JSON.stringify(node.arguments, null, 2), executionPolicy: node.execution_policy, rewritePolicy: node.rewrite_policy, priority: node.priority, idempotencyKey: node.idempotency_key };
|
|
59
|
+
if (node.payload.kind === "atmosphere")
|
|
60
|
+
return { ...common, presentationKind: "atmosphere", presentationIdentity: node.payload.atmosphere_id ?? "", presentationIntensity: node.payload.intensity };
|
|
61
|
+
if (node.payload.kind === "view")
|
|
62
|
+
return { ...common, presentationKind: "view", presentationIdentity: node.payload.overlay_id, presentationVisible: node.payload.visible };
|
|
63
|
+
return { ...common, presentationKind: "music", presentationIdentity: node.payload.candidate_set_id, presentationRevision: node.payload.candidate_set_revision, preferredResourceId: node.payload.preferred_resource_id ?? "" };
|
|
64
|
+
};
|
|
65
|
+
const stableIdPattern = /^[a-z0-9](?:[a-z0-9._:-]{0,127})$/u;
|
|
66
|
+
const draftIsValid = (draft) => {
|
|
67
|
+
if (!stableIdPattern.test(draft.stableId)
|
|
68
|
+
|| !Number.isFinite(draft.dueValue)
|
|
69
|
+
|| !Number.isFinite(draft.effectiveValue)
|
|
70
|
+
|| ((draft.domain === "logical_seq" || draft.domain === "presentation_seq")
|
|
71
|
+
&& (!Number.isSafeInteger(draft.dueValue)
|
|
72
|
+
|| draft.dueValue < 0
|
|
73
|
+
|| !Number.isSafeInteger(draft.effectiveValue)
|
|
74
|
+
|| draft.effectiveValue < 0)))
|
|
75
|
+
return false;
|
|
76
|
+
if (draft.domain === "real_time"
|
|
77
|
+
&& draft.realTimeClock !== "wall_clock"
|
|
78
|
+
&& draft.wallClockPolicy !== "active_session_only")
|
|
79
|
+
return false;
|
|
80
|
+
if (draft.kind === "event")
|
|
81
|
+
return stableIdPattern.test(draft.eventType);
|
|
82
|
+
if (draft.kind === "condition")
|
|
83
|
+
return true;
|
|
84
|
+
if (draft.kind === "effect") {
|
|
85
|
+
if (!stableIdPattern.test(draft.capabilityId)
|
|
86
|
+
|| !stableIdPattern.test(draft.idempotencyKey)
|
|
87
|
+
|| !Number.isSafeInteger(draft.priority)
|
|
88
|
+
|| draft.priority < 0)
|
|
89
|
+
return false;
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(draft.argumentsJson);
|
|
92
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (draft.presentationKind === "atmosphere") {
|
|
99
|
+
return draft.presentationIntensity >= 0 && draft.presentationIntensity <= 1
|
|
100
|
+
&& (draft.presentationIdentity === "" || stableIdPattern.test(draft.presentationIdentity));
|
|
101
|
+
}
|
|
102
|
+
if (draft.presentationKind === "view")
|
|
103
|
+
return stableIdPattern.test(draft.presentationIdentity);
|
|
104
|
+
return stableIdPattern.test(draft.presentationIdentity)
|
|
105
|
+
&& Number.isSafeInteger(draft.presentationRevision)
|
|
106
|
+
&& draft.presentationRevision > 0
|
|
107
|
+
&& (draft.preferredResourceId === "" || stableIdPattern.test(draft.preferredResourceId));
|
|
108
|
+
};
|
|
109
|
+
export function TmwBlueprint(props) {
|
|
110
|
+
const t = props.translate ?? defaultTmwTranslate;
|
|
111
|
+
const [lane, setLane] = useState("flow");
|
|
112
|
+
const [debuggerVisible, setDebuggerVisible] = useState(false);
|
|
113
|
+
const [selectedStableId, setSelectedStableId] = useState(null);
|
|
114
|
+
const [nodeDraft, setNodeDraft] = useState(null);
|
|
115
|
+
const [nodeDraftTouched, setNodeDraftTouched] = useState(false);
|
|
116
|
+
const [conditionWorkspaceJson, setConditionWorkspaceJson] = useState("");
|
|
117
|
+
const [conditionWorkspaceError, setConditionWorkspaceError] = useState(null);
|
|
118
|
+
const [simulationDraft, setSimulationDraft] = useState({ eventType: "manual", eventPayloadJson: "{}", stateJson: "{}", clockDomain: "logical_seq", clockValue: 0, crossingDirection: "forward", realTimeClock: "active_play", presentationPlanId: "author.preview", presentationSegmentId: "segment.preview" });
|
|
119
|
+
const simulationDraftValid = (() => {
|
|
120
|
+
try {
|
|
121
|
+
return simulationDraft.eventType.trim().length > 0
|
|
122
|
+
&& Number.isFinite(simulationDraft.clockValue)
|
|
123
|
+
&& (simulationDraft.clockDomain !== "presentation_seq" || (simulationDraft.presentationPlanId.trim().length > 0 && simulationDraft.presentationSegmentId.trim().length > 0))
|
|
124
|
+
&& typeof JSON.parse(simulationDraft.eventPayloadJson) === "object"
|
|
125
|
+
&& typeof JSON.parse(simulationDraft.stateJson) === "object";
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
const patchSimulationDraft = (key, value) => {
|
|
132
|
+
setSimulationDraft((current) => ({ ...current, [key]: value }));
|
|
133
|
+
};
|
|
134
|
+
const { source, diagnostics, trace } = props;
|
|
135
|
+
const productionDomains = getTmwRuntimeDomainCapability("production").produced_clock_domains;
|
|
136
|
+
const unsupportedProductionNodes = [...source.trigger_document.events, ...source.trigger_document.conditions, ...source.trigger_document.effects, ...source.trigger_document.presentations]
|
|
137
|
+
.filter((node) => !productionDomains.includes(node.effective_time.domain));
|
|
138
|
+
const lanes = [
|
|
139
|
+
{ id: "source", label: t("tmw.blueprint.lane.source") },
|
|
140
|
+
{ id: "flow", label: t("tmw.blueprint.lane.flow") },
|
|
141
|
+
{ id: "trace", label: t("tmw.blueprint.lane.trace") },
|
|
142
|
+
];
|
|
143
|
+
const canRevise = props.transactionId !== null
|
|
144
|
+
&& props.transactionStatus !== "committed"
|
|
145
|
+
&& props.transactionStatus !== "expired"
|
|
146
|
+
&& props.transactionStatus !== "blocked";
|
|
147
|
+
const draftMutable = canRevise && !props.busy;
|
|
148
|
+
const selectedCondition = source.trigger_document.conditions.find((condition) => condition.stable_id === selectedStableId) ?? null;
|
|
149
|
+
const selectedNode = [...source.trigger_document.events, ...source.trigger_document.conditions, ...source.trigger_document.effects, ...source.trigger_document.presentations].find((node) => node.stable_id === selectedStableId) ?? null;
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (selectedCondition === null) {
|
|
152
|
+
setConditionWorkspaceJson("");
|
|
153
|
+
setConditionWorkspaceError(null);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
setConditionWorkspaceJson(JSON.stringify(tmwConditionNodeToWorkspace(selectedCondition)));
|
|
157
|
+
setConditionWorkspaceError(null);
|
|
158
|
+
}, [selectedCondition]);
|
|
159
|
+
const saveConditionWorkspace = () => {
|
|
160
|
+
try {
|
|
161
|
+
const condition = tmwConditionNodeFromWorkspace(JSON.parse(conditionWorkspaceJson));
|
|
162
|
+
props.onUpdateNode({
|
|
163
|
+
...condition,
|
|
164
|
+
enabled: selectedCondition === null ? true : nodeEnabled(selectedCondition),
|
|
165
|
+
});
|
|
166
|
+
setConditionWorkspaceError(null);
|
|
167
|
+
}
|
|
168
|
+
catch (cause) {
|
|
169
|
+
setConditionWorkspaceError(cause instanceof Error ? cause.message : "tmw_condition_workspace_invalid");
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (draftMutable)
|
|
174
|
+
return;
|
|
175
|
+
setNodeDraft(null);
|
|
176
|
+
setNodeDraftTouched(false);
|
|
177
|
+
}, [draftMutable]);
|
|
178
|
+
const openDraft = (kind) => {
|
|
179
|
+
if (!draftMutable)
|
|
180
|
+
return;
|
|
181
|
+
setNodeDraft(newDraft(kind));
|
|
182
|
+
setNodeDraftTouched(false);
|
|
183
|
+
};
|
|
184
|
+
const closeDraft = () => {
|
|
185
|
+
setNodeDraft(null);
|
|
186
|
+
setNodeDraftTouched(false);
|
|
187
|
+
};
|
|
188
|
+
const patchDraft = (patch) => {
|
|
189
|
+
if (!draftMutable)
|
|
190
|
+
return;
|
|
191
|
+
setNodeDraftTouched(true);
|
|
192
|
+
setNodeDraft((current) => current === null ? null : { ...current, ...patch });
|
|
193
|
+
};
|
|
194
|
+
const submitDraft = () => {
|
|
195
|
+
if (!draftMutable || !nodeDraftTouched || nodeDraft === null || !draftIsValid(nodeDraft))
|
|
196
|
+
return;
|
|
197
|
+
props.onAddNode(nodeDraft);
|
|
198
|
+
closeDraft();
|
|
199
|
+
};
|
|
200
|
+
return (_jsxs("main", { className: "tmw-blueprint", "aria-label": t("tmw.blueprint.aria"), "data-tmw-blueprint": "v3", children: [_jsxs("header", { className: "tmw-blueprint__header", children: [_jsxs("div", { className: "tmw-blueprint__identity", children: [_jsx("strong", { children: t("tmw.blueprint.title") }), _jsxs("span", { children: [" \u00B7 ", t("tmw.blueprint.source_revision", undefined, { revision: source.source_revision })] }), _jsxs("span", { children: [" \u00B7 ", t(`tmw.blueprint.status.${props.transactionStatus}`)] }), props.transactionId === null ? null : _jsxs("code", { children: [" \u00B7 ", props.transactionId] })] }), _jsxs("div", { className: "tmw-blueprint__view-controls", children: [_jsx(TmwTabs, { items: lanes, value: lane, onSelect: setLane, ariaLabel: t("tmw.blueprint.lanes"), testId: "tmw-blueprint-lanes" }), _jsx(TmwToggle, { active: debuggerVisible, onToggle: () => setDebuggerVisible((visible) => !visible), label: t("tmw.blueprint.debugger"), testId: "tmw-debugger-toggle" })] })] }), _jsxs("div", { className: "tmw-blueprint__toolbar", role: "toolbar", "aria-label": t("tmw.blueprint.transaction_toolbar"), children: [_jsx("button", { type: "button", disabled: props.busy || (props.transactionId !== null
|
|
201
|
+
&& props.transactionStatus !== "committed"
|
|
202
|
+
&& props.transactionStatus !== "expired"), onClick: props.onBegin, children: t("tmw.blueprint.action.begin") }), _jsx("button", { type: "button", disabled: !draftMutable || selectedNode === null || selectedNode.kind === "condition", onClick: () => { if (selectedNode !== null) {
|
|
203
|
+
setNodeDraft(draftFromNode(selectedNode));
|
|
204
|
+
setNodeDraftTouched(false);
|
|
205
|
+
} }, children: "\u7F16\u8F91\u9009\u4E2D\u8282\u70B9" }), _jsx("button", { type: "button", disabled: !draftMutable || selectedNode === null, onClick: () => selectedNode !== null && props.onDeleteNode(selectedNode.stable_id), children: "\u5220\u9664\u9009\u4E2D\u8282\u70B9" }), _jsx("button", { type: "button", disabled: props.busy || props.transactionId === null, onClick: props.onValidate, children: t("tmw.blueprint.action.validate") }), _jsx("input", { "aria-label": "\u6A21\u62DF\u4E8B\u4EF6\u7C7B\u578B", value: simulationDraft.eventType, onChange: (event) => patchSimulationDraft("eventType", event.currentTarget.value) }), _jsx("input", { "aria-label": "\u6A21\u62DF\u4E8B\u4EF6 payload JSON", value: simulationDraft.eventPayloadJson, onChange: (event) => patchSimulationDraft("eventPayloadJson", event.currentTarget.value) }), _jsx("input", { "aria-label": "\u6A21\u62DF\u72B6\u6001 JSON", value: simulationDraft.stateJson, onChange: (event) => patchSimulationDraft("stateJson", event.currentTarget.value) }), _jsxs("select", { "aria-label": "\u6A21\u62DF\u65F6\u949F\u57DF", value: simulationDraft.clockDomain, onChange: (event) => patchSimulationDraft("clockDomain", event.currentTarget.value), children: [_jsx("option", { value: "logical_seq", children: "logical_seq" }), _jsx("option", { value: "world_time", children: "world_time" }), _jsx("option", { value: "presentation_seq", children: "presentation_seq" }), _jsx("option", { value: "real_time", children: "real_time" })] }), _jsx("input", { "aria-label": "\u6A21\u62DF\u65F6\u949F\u503C", type: "number", value: simulationDraft.clockValue, onChange: (event) => patchSimulationDraft("clockValue", Number(event.currentTarget.value)) }), _jsxs("select", { "aria-label": "\u6A21\u62DF\u7A7F\u8D8A\u65B9\u5411", value: simulationDraft.crossingDirection, onChange: (event) => patchSimulationDraft("crossingDirection", event.currentTarget.value), children: [_jsx("option", { value: "forward", children: "forward" }), _jsx("option", { value: "backward", children: "backward" })] }), simulationDraft.clockDomain === "real_time" ? _jsxs("select", { "aria-label": "\u6A21\u62DF\u771F\u5B9E\u65F6\u949F", value: simulationDraft.realTimeClock, onChange: (event) => patchSimulationDraft("realTimeClock", event.currentTarget.value), children: [_jsx("option", { value: "active_play", children: "active_play" }), _jsx("option", { value: "monotonic_session", children: "monotonic_session" }), _jsx("option", { value: "wall_clock", children: "wall_clock" })] }) : null, simulationDraft.clockDomain === "presentation_seq" ? _jsxs(_Fragment, { children: [_jsx("input", { "aria-label": "\u6A21\u62DF\u6F14\u51FA plan ID", value: simulationDraft.presentationPlanId, onChange: (event) => patchSimulationDraft("presentationPlanId", event.currentTarget.value) }), _jsx("input", { "aria-label": "\u6A21\u62DF\u6F14\u51FA segment ID", value: simulationDraft.presentationSegmentId, onChange: (event) => patchSimulationDraft("presentationSegmentId", event.currentTarget.value) })] }) : null, _jsx("button", { type: "button", disabled: props.busy || !simulationDraftValid, onClick: () => props.onSimulate(simulationDraft), children: t("tmw.blueprint.action.simulate") }), _jsx("button", { type: "button", disabled: props.busy || props.transactionId === null || (props.transactionStatus !== "validated" && props.transactionStatus !== "simulated"), onClick: props.onCommit, children: t("tmw.blueprint.action.commit") })] }), unsupportedProductionNodes.length === 0 ? null : (_jsxs("div", { className: "tmw-diagnostic", role: "alert", children: ["\u5F53\u524D\u751F\u4EA7\u8FD0\u884C\u65F6\u6CA1\u6709\u4EE5\u4E0B\u65F6\u949F\u57DF\u7684\u6B63\u5F0F producer\uFF0C\u6587\u6863\u53EF\u7EE7\u7EED\u8BFB\u53D6\u548C\u7F16\u8F91\uFF0C\u4F46\u9A8C\u8BC1\u3001\u7F16\u8BD1\u4E0E\u53D1\u5E03\u4E0D\u4F1A ready\uFF1A", unsupportedProductionNodes.map((node) => `${node.stable_id}=${node.effective_time.domain}`).join(",")] })), selectedCondition === null ? null : (_jsxs("section", { className: "tmw-condition-blocks", "aria-label": "Scratch \u6761\u4EF6\u7F16\u8F91\u5668", children: [_jsxs("header", { className: "tmw-lane-heading", children: [_jsxs("div", { children: [_jsx("strong", { children: "Scratch \u6761\u4EF6" }), _jsx("span", { children: " \u00B7 TriggerDocumentV3" })] }), _jsx("button", { type: "button", disabled: !draftMutable || conditionWorkspaceJson.length === 0, onClick: saveConditionWorkspace, children: "\u4FDD\u5B58\u6761\u4EF6" })] }), conditionWorkspaceError === null ? null : _jsx("div", { role: "alert", className: "tmw-diagnostic", children: conditionWorkspaceError }), _jsx(BlocksWorkspace, { schema: "tmw-condition-v3", ownerKey: selectedCondition.stable_id, workspaceJson: conditionWorkspaceJson, ctx: { event_list: [], entity_list: [] }, onWorkspaceJsonChange: setConditionWorkspaceJson }), _jsx("small", { children: "\u81EA\u7136\u8BED\u8A00\u79EF\u6728\u4EA7\u751F\u8BED\u4E49 Judge \u8BF7\u6C42\uFF1B\u4E0D\u4F1A\u628A\u6587\u672C\u76F4\u63A5\u5199\u6210 true\u3002" })] })), nodeDraft === null ? null : (_jsxs("section", { className: "tmw-node-draft", "aria-label": t("tmw.draft.aria"), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("strong", { children: t("tmw.draft.title", undefined, { kind: t(`tmw.palette.${nodeDraft.kind}.label`) }) }), _jsx("span", { children: t("tmw.draft.explicit_hint") })] }), _jsx("button", { type: "button", onClick: closeDraft, "aria-label": t("common.close"), children: "\u00D7" })] }), _jsxs("div", { className: "tmw-node-draft__grid", children: [_jsxs("label", { className: "tmw-node-draft__checkbox", children: [_jsx("input", { type: "checkbox", checked: nodeDraft.enabled, onChange: (event) => patchDraft({ enabled: event.currentTarget.checked }) }), _jsx("span", { children: "\u542F\u7528" })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.stable_id") }), _jsx("input", { value: nodeDraft.stableId, readOnly: selectedNode?.stable_id === nodeDraft.stableId, onChange: (event) => patchDraft({ stableId: event.currentTarget.value }) })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.domain") }), _jsxs("select", { value: nodeDraft.domain, onChange: (event) => patchDraft({ domain: event.currentTarget.value }), children: [_jsx("option", { value: "logical_seq", children: t("tmw.domain.logical_seq") }), _jsx("option", { value: "world_time", children: t("tmw.domain.world_time") }), _jsx("option", { value: "presentation_seq", children: t("tmw.domain.presentation_seq") }), _jsx("option", { value: "real_time", children: t("tmw.domain.real_time") })] }), !productionDomains.includes(nodeDraft.domain) ? _jsx("small", { role: "status", children: "\u6B64\u57DF\u4EC5\u53EF\u7528\u663E\u5F0F simulation observation \u9884\u89C8\uFF1B\u5F53\u524D\u751F\u4EA7\u7F16\u8BD1\u4F1A\u62D2\u7EDD\u3002" }) : null] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.due_value") }), _jsx("input", { type: "number", value: nodeDraft.dueValue, onChange: (event) => patchDraft({ dueValue: Number(event.currentTarget.value) }) })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.effective_value") }), _jsx("input", { type: "number", value: nodeDraft.effectiveValue, onChange: (event) => patchDraft({ effectiveValue: Number(event.currentTarget.value) }) })] }), nodeDraft.domain === "real_time" ? (_jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.real_time_clock") }), _jsxs("select", { value: nodeDraft.realTimeClock, onChange: (event) => patchDraft({ realTimeClock: event.currentTarget.value }), children: [_jsx("option", { value: "monotonic_session", children: "monotonic_session" }), _jsx("option", { value: "active_play", children: "active_play" }), _jsx("option", { value: "wall_clock", children: "wall_clock" })] })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.wall_clock_policy") }), _jsxs("select", { value: nodeDraft.wallClockPolicy, onChange: (event) => patchDraft({ wallClockPolicy: event.currentTarget.value }), children: [_jsx("option", { value: "active_session_only", children: "active_session_only" }), _jsx("option", { value: "elapse_while_suspended", children: "elapse_while_suspended" }), _jsx("option", { value: "reject", children: "reject" })] })] })] })) : null, nodeDraft.kind === "event" ? (_jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.event_type") }), _jsx("input", { value: nodeDraft.eventType, onChange: (event) => patchDraft({ eventType: event.currentTarget.value }) })] }), _jsxs("label", { className: "tmw-node-draft__checkbox", children: [_jsx("input", { type: "checkbox", checked: nodeDraft.hardFact, onChange: (event) => patchDraft({ hardFact: event.currentTarget.checked }) }), _jsx("span", { children: t("tmw.draft.hard_fact") })] })] })) : null, nodeDraft.kind === "condition" ? (_jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.literal_value") }), _jsxs("select", { value: String(nodeDraft.conditionValue), onChange: (event) => patchDraft({ conditionValue: event.currentTarget.value === "true" }), children: [_jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] })] })) : null, nodeDraft.kind === "effect" ? (_jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.capability") }), _jsxs("select", { value: nodeDraft.capabilityId, onChange: (event) => patchDraft({ capabilityId: event.currentTarget.value }), children: [_jsx("option", { value: "", children: t("tmw.draft.choose_capability") }), props.capabilityIds.map((capabilityId) => _jsx("option", { value: capabilityId, children: capabilityId }, capabilityId))] })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.arguments") }), _jsx("textarea", { value: nodeDraft.argumentsJson, onChange: (event) => patchDraft({ argumentsJson: event.currentTarget.value }) })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.execution_policy") }), _jsxs("select", { value: nodeDraft.executionPolicy, onChange: (event) => patchDraft({ executionPolicy: event.currentTarget.value }), children: [_jsx("option", { value: "attempt_once", children: "attempt_once" }), _jsx("option", { value: "ensure_until", children: "ensure_until" })] })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.rewrite_policy") }), _jsxs("select", { value: nodeDraft.rewritePolicy, onChange: (event) => patchDraft({ rewritePolicy: event.currentTarget.value }), children: [_jsx("option", { value: "normal_only", children: "normal_only" }), _jsx("option", { value: "rewrite_only", children: "rewrite_only" }), _jsx("option", { value: "normal_and_rewrite", children: "normal_and_rewrite" })] })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.priority") }), _jsx("input", { type: "number", min: "0", step: "1", value: nodeDraft.priority, onChange: (event) => patchDraft({ priority: Number(event.currentTarget.value) }) })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.idempotency_key") }), _jsx("input", { value: nodeDraft.idempotencyKey, onChange: (event) => patchDraft({ idempotencyKey: event.currentTarget.value }) })] })] })) : null, nodeDraft.kind === "presentation" ? (_jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.presentation_kind") }), _jsxs("select", { value: nodeDraft.presentationKind, onChange: (event) => patchDraft({ presentationKind: event.currentTarget.value }), children: [_jsx("option", { value: "atmosphere", children: "atmosphere" }), _jsx("option", { value: "view", children: "view" }), _jsx("option", { value: "music", children: "music" })] })] }), _jsxs("label", { children: [_jsx("span", { children: t(`tmw.draft.presentation_identity.${nodeDraft.presentationKind}`) }), _jsx("input", { value: nodeDraft.presentationIdentity, onChange: (event) => patchDraft({ presentationIdentity: event.currentTarget.value }) })] }), nodeDraft.presentationKind === "atmosphere" ? (_jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.intensity") }), _jsx("input", { type: "number", min: "0", max: "1", step: "0.05", value: nodeDraft.presentationIntensity, onChange: (event) => patchDraft({ presentationIntensity: Number(event.currentTarget.value) }) })] })) : null, nodeDraft.presentationKind === "view" ? (_jsxs("label", { className: "tmw-node-draft__checkbox", children: [_jsx("input", { type: "checkbox", checked: nodeDraft.presentationVisible, onChange: (event) => patchDraft({ presentationVisible: event.currentTarget.checked }) }), _jsx("span", { children: t("tmw.draft.visible") })] })) : null, nodeDraft.presentationKind === "music" ? (_jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.candidate_revision") }), _jsx("input", { type: "number", min: "1", step: "1", value: nodeDraft.presentationRevision, onChange: (event) => patchDraft({ presentationRevision: Number(event.currentTarget.value) }) })] }), _jsxs("label", { children: [_jsx("span", { children: t("tmw.draft.preferred_resource") }), _jsx("input", { value: nodeDraft.preferredResourceId, onChange: (event) => patchDraft({ preferredResourceId: event.currentTarget.value }) })] })] })) : null] })) : null] }), _jsxs("footer", { children: [_jsx("button", { type: "button", onClick: closeDraft, children: t("common.cancel") }), _jsx("button", { type: "button", disabled: !draftMutable || !nodeDraftTouched || !draftIsValid(nodeDraft), onClick: submitDraft, children: t("tmw.draft.add") })] })] })), lane === "source" ? (_jsx(TmwSourceLane, { document: source.trigger_document, diagnostics: diagnostics, selectedStableId: selectedStableId, onSelectStableId: setSelectedStableId, canRevise: draftMutable, onAddNode: openDraft, translate: t })) : null, lane === "flow" ? (_jsx(TmwFlowLane, { document: source.trigger_document, selectedStableId: selectedStableId, onSelectStableId: setSelectedStableId, canRevise: draftMutable, onAddNode: openDraft, onConnect: props.onConnect, onDisconnect: props.onDisconnect, onDeleteNode: props.onDeleteNode, layoutPositions: props.layoutPositions, onLayout: props.onLayout, translate: t })) : null, lane === "trace" ? (_jsx(TmwTraceLane, { trace: trace, selectedStableId: selectedStableId, onSelectStableId: setSelectedStableId, translate: t })) : null, debuggerVisible ? (_jsx("div", { className: "tmw-blueprint__debugger", children: _jsx(TriggerDebugTab, { document: source.trigger_document, diagnostics: diagnostics, trace: trace, selectedStableId: selectedStableId, onSelectStableId: setSelectedStableId }) })) : null] }));
|
|
206
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TriggerDocumentV3, TriggerNodeKindV3 } from "@world-engines/tmw";
|
|
2
|
+
import { type TmwTranslate } from "./translate.js";
|
|
3
|
+
export interface TmwFlowLaneProps {
|
|
4
|
+
readonly document: TriggerDocumentV3;
|
|
5
|
+
readonly selectedStableId: string | null;
|
|
6
|
+
readonly onSelectStableId: (stableId: string) => void;
|
|
7
|
+
readonly canRevise: boolean;
|
|
8
|
+
readonly onAddNode: (kind: TriggerNodeKindV3) => void;
|
|
9
|
+
readonly onConnect: (fromStableId: string, toStableId: string) => void;
|
|
10
|
+
readonly onDisconnect: (fromStableId: string, toStableId: string) => void;
|
|
11
|
+
readonly onDeleteNode: (stableId: string) => void;
|
|
12
|
+
readonly layoutPositions: Readonly<Record<string, {
|
|
13
|
+
readonly x: number;
|
|
14
|
+
readonly y: number;
|
|
15
|
+
}>>;
|
|
16
|
+
readonly onLayout: (positions: Readonly<Record<string, {
|
|
17
|
+
readonly x: number;
|
|
18
|
+
readonly y: number;
|
|
19
|
+
}>>) => void;
|
|
20
|
+
readonly translate?: TmwTranslate;
|
|
21
|
+
}
|
|
22
|
+
export declare function TmwFlowLane({ document, selectedStableId, onSelectStableId, canRevise, onAddNode, onConnect, onDisconnect, onDeleteNode, layoutPositions, onLayout, translate, }: TmwFlowLaneProps): JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import ReactFlow, { Background, BackgroundVariant, Controls, MarkerType, MiniMap, applyNodeChanges, } from "reactflow";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { TMW_NODE_PALETTE } from "./TmwSourceLane.js";
|
|
6
|
+
import { defaultTmwTranslate } from "./translate.js";
|
|
7
|
+
export function TmwFlowLane({ document, selectedStableId, onSelectStableId, canRevise, onAddNode, onConnect, onDisconnect, onDeleteNode, layoutPositions, onLayout, translate, }) {
|
|
8
|
+
const t = translate ?? defaultTmwTranslate;
|
|
9
|
+
const graph = useMemo(() => {
|
|
10
|
+
const groups = [
|
|
11
|
+
document.events,
|
|
12
|
+
document.conditions,
|
|
13
|
+
document.effects,
|
|
14
|
+
document.presentations,
|
|
15
|
+
];
|
|
16
|
+
const nodes = groups.flatMap((items, column) => items.map((node, row) => ({
|
|
17
|
+
id: node.stable_id,
|
|
18
|
+
position: layoutPositions[node.stable_id] ?? { x: 80 + column * 280, y: 70 + row * 150 },
|
|
19
|
+
data: {
|
|
20
|
+
label: (_jsxs("div", { className: `tmw-flow-node tmw-flow-node--${node.kind}`, children: [_jsx("span", { children: node.kind }), _jsx("strong", { children: node.stable_id }), _jsx("small", { children: node.effective_time.domain })] })),
|
|
21
|
+
},
|
|
22
|
+
selected: selectedStableId === node.stable_id,
|
|
23
|
+
className: `tmw-reactflow-node tmw-reactflow-node--${node.kind}`,
|
|
24
|
+
})));
|
|
25
|
+
const edges = document.edges.map((edge) => ({
|
|
26
|
+
id: `${edge.from_stable_id}->${edge.to_stable_id}`,
|
|
27
|
+
source: edge.from_stable_id,
|
|
28
|
+
target: edge.to_stable_id,
|
|
29
|
+
markerEnd: { type: MarkerType.ArrowClosed },
|
|
30
|
+
animated: false,
|
|
31
|
+
}));
|
|
32
|
+
return { nodes, edges };
|
|
33
|
+
}, [document, layoutPositions, selectedStableId]);
|
|
34
|
+
const [nodes, setNodes] = useState(graph.nodes);
|
|
35
|
+
useEffect(() => setNodes(graph.nodes), [graph.nodes]);
|
|
36
|
+
const handleNodesChange = (changes) => setNodes((current) => applyNodeChanges(changes, current));
|
|
37
|
+
const persistLayout = () => onLayout(Object.fromEntries(nodes.map((node) => [node.id, node.position])));
|
|
38
|
+
const handleConnect = (connection) => {
|
|
39
|
+
if (connection.source === null || connection.target === null)
|
|
40
|
+
return;
|
|
41
|
+
onConnect(connection.source, connection.target);
|
|
42
|
+
};
|
|
43
|
+
return (_jsxs("section", { className: "tmw-flow-lane", "aria-label": t("tmw.flow.aria"), "data-tmw-lane": "flow", children: [_jsxs("header", { className: "tmw-lane-heading", children: [_jsxs("div", { children: [_jsx("strong", { children: t("tmw.flow.title") }), _jsxs("span", { children: [" \u00B7 ", t("tmw.flow.edge_count", undefined, { count: document.edges.length })] })] }), _jsx("small", { children: t(canRevise ? "tmw.flow.connect_hint" : "tmw.flow.begin_hint") })] }), _jsxs("div", { className: "tmw-flow-lane__palette", role: "toolbar", "aria-label": t("tmw.palette.add_aria"), children: [TMW_NODE_PALETTE.map((item) => (_jsxs("button", { type: "button", disabled: !canRevise, onClick: () => onAddNode(item.kind), children: ["\uFF0B ", t(item.labelKey)] }, item.kind))), _jsx("button", { type: "button", disabled: !canRevise, onClick: () => selectedStableId !== null && onDeleteNode(selectedStableId), children: "\u5220\u9664\u9009\u4E2D\u8282\u70B9" }), _jsx("button", { type: "button", disabled: !canRevise, onClick: persistLayout, children: "\u4FDD\u5B58\u5E03\u5C40" })] }), _jsx("div", { className: "tmw-flow-lane__canvas", "data-testid": "tmw-reactflow-canvas", children: _jsxs(ReactFlow, { nodes: nodes, edges: graph.edges, fitView: true, fitViewOptions: { padding: 0.25 }, nodesDraggable: canRevise, nodesConnectable: canRevise, elementsSelectable: true, onNodeClick: (_, node) => onSelectStableId(node.id), onNodesChange: handleNodesChange, onConnect: handleConnect, onEdgeDoubleClick: (_, edge) => canRevise && onDisconnect(edge.source, edge.target), proOptions: { hideAttribution: true }, children: [_jsx(Background, { variant: BackgroundVariant.Dots, gap: 22, size: 1.4 }), _jsx(Controls, { showInteractive: false }), _jsx(MiniMap, { pannable: true, zoomable: true }), graph.nodes.length === 0 ? (_jsxs("div", { className: "tmw-flow-lane__empty", children: [_jsx("strong", { children: t("tmw.flow.empty_title") }), _jsx("span", { children: t("tmw.flow.empty_help") })] })) : null] }) })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TmwDiagnostic, TriggerDocumentV3, TriggerNodeKindV3 } from "@world-engines/tmw";
|
|
2
|
+
import { type TmwTranslate } from "./translate.js";
|
|
3
|
+
export interface TmwSourceLaneProps {
|
|
4
|
+
readonly document: TriggerDocumentV3;
|
|
5
|
+
readonly diagnostics: ReadonlyArray<TmwDiagnostic>;
|
|
6
|
+
readonly selectedStableId: string | null;
|
|
7
|
+
readonly onSelectStableId: (stableId: string) => void;
|
|
8
|
+
readonly canRevise: boolean;
|
|
9
|
+
readonly onAddNode: (kind: TriggerNodeKindV3) => void;
|
|
10
|
+
readonly translate?: TmwTranslate;
|
|
11
|
+
}
|
|
12
|
+
export declare const TMW_NODE_PALETTE: ReadonlyArray<Readonly<{
|
|
13
|
+
kind: TriggerNodeKindV3;
|
|
14
|
+
labelKey: string;
|
|
15
|
+
descriptionKey: string;
|
|
16
|
+
}>>;
|
|
17
|
+
export declare function TmwSourceLane({ document, diagnostics, selectedStableId, onSelectStableId, canRevise, onAddNode, translate, }: TmwSourceLaneProps): JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { defaultTmwTranslate } from "./translate.js";
|
|
3
|
+
const documentNodes = (document) => [
|
|
4
|
+
...document.events,
|
|
5
|
+
...document.conditions,
|
|
6
|
+
...document.effects,
|
|
7
|
+
...document.presentations,
|
|
8
|
+
];
|
|
9
|
+
export const TMW_NODE_PALETTE = [
|
|
10
|
+
{
|
|
11
|
+
kind: "event",
|
|
12
|
+
labelKey: "tmw.palette.event.label",
|
|
13
|
+
descriptionKey: "tmw.palette.event.description",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
kind: "condition",
|
|
17
|
+
labelKey: "tmw.palette.condition.label",
|
|
18
|
+
descriptionKey: "tmw.palette.condition.description",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
kind: "effect",
|
|
22
|
+
labelKey: "tmw.palette.effect.label",
|
|
23
|
+
descriptionKey: "tmw.palette.effect.description",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
kind: "presentation",
|
|
27
|
+
labelKey: "tmw.palette.presentation.label",
|
|
28
|
+
descriptionKey: "tmw.palette.presentation.description",
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
export function TmwSourceLane({ document, diagnostics, selectedStableId, onSelectStableId, canRevise, onAddNode, translate, }) {
|
|
32
|
+
const t = translate ?? defaultTmwTranslate;
|
|
33
|
+
const nodes = documentNodes(document);
|
|
34
|
+
return (_jsxs("section", { className: "tmw-source-lane", "aria-label": t("tmw.source.aria"), "data-tmw-lane": "source", children: [_jsxs("aside", { className: "tmw-source-lane__palette", "aria-label": t("tmw.palette.aria"), children: [_jsxs("div", { className: "tmw-lane-heading", children: [_jsxs("div", { children: [_jsx("strong", { children: t("tmw.palette.title") }), _jsx("span", { children: t("tmw.palette.authority") })] }), !canRevise ? _jsx("small", { children: t("tmw.palette.begin_hint") }) : null] }), _jsx("div", { className: "tmw-node-palette", children: TMW_NODE_PALETTE.map((item) => (_jsxs("button", { type: "button", className: `tmw-node-palette__item tmw-node-palette__item--${item.kind}`, disabled: !canRevise, onClick: () => onAddNode(item.kind), children: [_jsxs("strong", { children: ["\uFF0B ", t(item.labelKey)] }), _jsx("span", { children: t(item.descriptionKey) })] }, item.kind))) })] }), _jsxs("div", { className: "tmw-source-lane__document", children: [_jsx("header", { className: "tmw-lane-heading", children: _jsxs("div", { children: [_jsx("strong", { children: t("tmw.source.title") }), _jsxs("span", { children: [" \u00B7 ", t("tmw.source.node_count", undefined, { count: nodes.length })] })] }) }), diagnostics.filter((diagnostic) => diagnostic.related_stable_ids.length === 0).map((diagnostic) => (_jsxs("div", { className: "tmw-diagnostic", role: "alert", children: [diagnostic.code, " \u00B7 ", diagnostic.path] }, `${diagnostic.path}:${diagnostic.code}`))), nodes.length === 0 ? (_jsxs("div", { className: "tmw-empty-state", children: [_jsx("strong", { children: t("tmw.source.empty_title") }), _jsx("p", { children: t("tmw.source.empty_help") })] })) : (_jsx("ol", { className: "tmw-source-node-list", children: nodes.map((node) => {
|
|
35
|
+
const nodeDiagnostics = diagnostics.filter((diagnostic) => diagnostic.related_stable_ids.includes(node.stable_id));
|
|
36
|
+
return (_jsxs("li", { children: [_jsxs("button", { type: "button", "aria-pressed": selectedStableId === node.stable_id, onClick: () => onSelectStableId(node.stable_id), children: [_jsx("span", { className: `tmw-node-kind tmw-node-kind--${node.kind}`, children: node.kind }), _jsx("code", { children: node.stable_id }), _jsx("span", { children: node.effective_time.domain })] }), nodeDiagnostics.map((diagnostic) => (_jsxs("div", { className: "tmw-diagnostic", role: "alert", children: [diagnostic.code, " \u00B7 ", diagnostic.path] }, `${diagnostic.path}:${diagnostic.code}`)))] }, node.stable_id));
|
|
37
|
+
}) }))] })] }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export declare function TmwTabs<T extends string>({ items, value, onSelect, ariaLabel, testId }: {
|
|
3
|
+
readonly items: readonly {
|
|
4
|
+
readonly id: T;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
}[];
|
|
7
|
+
readonly value: T;
|
|
8
|
+
readonly onSelect: (value: T) => void;
|
|
9
|
+
readonly ariaLabel: string;
|
|
10
|
+
readonly testId?: string;
|
|
11
|
+
}): JSX.Element;
|
|
12
|
+
export declare function TmwToggle({ active, onToggle, label, testId }: {
|
|
13
|
+
readonly active: boolean;
|
|
14
|
+
readonly onToggle: () => void;
|
|
15
|
+
readonly label: string;
|
|
16
|
+
readonly testId?: string;
|
|
17
|
+
}): JSX.Element;
|
|
18
|
+
export declare function TmwPanel({ children }: {
|
|
19
|
+
readonly children: ReactNode;
|
|
20
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
export function TmwTabs({ items, value, onSelect, ariaLabel, testId }) {
|
|
3
|
+
return _jsx("div", { "aria-label": ariaLabel, "data-testid": testId, role: "tablist", children: items.map((item) => _jsx("button", { "aria-selected": value === item.id, onClick: () => onSelect(item.id), role: "tab", type: "button", children: item.label }, item.id)) });
|
|
4
|
+
}
|
|
5
|
+
export function TmwToggle({ active, onToggle, label, testId }) {
|
|
6
|
+
return _jsx("button", { "aria-pressed": active, "data-testid": testId, onClick: onToggle, type: "button", children: label });
|
|
7
|
+
}
|
|
8
|
+
export function TmwPanel({ children }) { return _jsx(_Fragment, { children: children }); }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TmwSimulationTraceEntry } from "@world-engines/tmw";
|
|
2
|
+
import { type TmwTranslate } from "./translate.js";
|
|
3
|
+
export interface TmwTraceLaneProps {
|
|
4
|
+
readonly trace: ReadonlyArray<TmwSimulationTraceEntry>;
|
|
5
|
+
readonly selectedStableId: string | null;
|
|
6
|
+
readonly onSelectStableId: (stableId: string) => void;
|
|
7
|
+
readonly translate?: TmwTranslate;
|
|
8
|
+
}
|
|
9
|
+
export declare function TmwTraceLane({ trace, selectedStableId, onSelectStableId, translate, }: TmwTraceLaneProps): JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { defaultTmwTranslate } from "./translate.js";
|
|
3
|
+
export function TmwTraceLane({ trace, selectedStableId, onSelectStableId, translate, }) {
|
|
4
|
+
const t = translate ?? defaultTmwTranslate;
|
|
5
|
+
return (_jsxs("section", { className: "tmw-trace-lane", "aria-label": t("tmw.trace.aria"), "data-tmw-lane": "trace", children: [_jsx("header", { className: "tmw-lane-heading", children: _jsxs("div", { children: [_jsx("strong", { children: t("tmw.trace.title") }), _jsxs("span", { children: [" \u00B7 ", t("tmw.trace.step_count", undefined, { count: trace.length })] })] }) }), trace.length === 0 ? (_jsxs("div", { className: "tmw-empty-state", children: [_jsx("strong", { children: t("tmw.trace.empty") }), _jsx("p", { children: t("tmw.trace.empty_help") })] })) : (_jsx("ol", { className: "tmw-trace-list", children: trace.map((entry) => (_jsx("li", { children: _jsxs("button", { type: "button", "aria-pressed": selectedStableId === entry.stable_id, onClick: () => onSelectStableId(entry.stable_id), children: [entry.step, ". ", entry.kind, " \u00B7 ", entry.stable_id, " \u00B7 ", entry.clock_domain, " \u00B7 ", t("tmw.trace.effective"), " ", entry.effective_value, " \u00B7 ", entry.disposition] }) }, `${entry.step}:${entry.stable_id}`))) }))] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type TmwDiagnostic, type TmwSimulationTraceEntry, type TriggerDocumentV3 } from "@world-engines/tmw";
|
|
2
|
+
export interface TriggerDebugTabProps {
|
|
3
|
+
readonly document?: TriggerDocumentV3;
|
|
4
|
+
readonly diagnostics?: ReadonlyArray<TmwDiagnostic>;
|
|
5
|
+
readonly trace?: ReadonlyArray<TmwSimulationTraceEntry>;
|
|
6
|
+
readonly selectedStableId?: string | null;
|
|
7
|
+
readonly onSelectStableId?: (stableId: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function TriggerDebugTab({ document, diagnostics, trace, selectedStableId, onSelectStableId, }: TriggerDebugTabProps): JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { allTriggerNodesV3, } from "@world-engines/tmw";
|
|
4
|
+
import { TmwTabs } from "./TmwTabs.js";
|
|
5
|
+
const DOMAINS = [
|
|
6
|
+
{ id: "logical_seq", label: "逻辑序列" },
|
|
7
|
+
{ id: "world_time", label: "世界时间" },
|
|
8
|
+
{ id: "presentation_seq", label: "演出序列" },
|
|
9
|
+
{ id: "real_time", label: "实时" },
|
|
10
|
+
];
|
|
11
|
+
export function TriggerDebugTab({ document, diagnostics = [], trace = [], selectedStableId = null, onSelectStableId, }) {
|
|
12
|
+
const [domain, setDomain] = useState("logical_seq");
|
|
13
|
+
if (document === undefined) {
|
|
14
|
+
return _jsx("section", { role: "alert", children: "TMW3 SourceV3 \u5C1A\u672A\u901A\u8FC7\u6253\u5F00\u5C4F\u969C\uFF0C\u8C03\u8BD5\u5668\u5DF2\u963B\u65AD\u3002" });
|
|
15
|
+
}
|
|
16
|
+
const nodes = allTriggerNodesV3(document).filter((node) => node.effective_time.domain === domain);
|
|
17
|
+
return (_jsxs("section", { "aria-label": "TMW3 \u56DB\u65F6\u57DF\u8C03\u8BD5\u5668", "data-tmw-debug-domain": domain, children: [_jsx(TmwTabs, { items: DOMAINS, value: domain, onSelect: setDomain, ariaLabel: "\u56DB\u65F6\u57DF" }), nodes.length === 0 ? (_jsxs("p", { children: [domain, " \u65F6\u57DF\u6CA1\u6709\u8282\u70B9\u3002"] })) : (_jsx("ol", { children: nodes.map((node) => {
|
|
18
|
+
const nodeTrace = trace.filter((entry) => entry.stable_id === node.stable_id);
|
|
19
|
+
const nodeDiagnostics = diagnostics.filter((item) => item.related_stable_ids.includes(node.stable_id));
|
|
20
|
+
const effectiveTime = node.effective_time;
|
|
21
|
+
return (_jsxs("li", { children: [_jsxs("button", { type: "button", "aria-pressed": selectedStableId === node.stable_id, onClick: () => onSelectStableId?.(node.stable_id), children: [node.kind, " \u00B7 ", node.stable_id, " \u00B7 \u5230\u671F ", effectiveTime.due_value, " \u00B7 \u751F\u6548", " ", effectiveTime.effective_value, " \u00B7 ", effectiveTime.crossing_policy, effectiveTime.domain === "real_time"
|
|
22
|
+
? ` · ${effectiveTime.real_time_clock} · ${effectiveTime.wall_clock_policy}`
|
|
23
|
+
: null] }), _jsxs("span", { children: [" \u00B7 trace ", nodeTrace.length] }), nodeDiagnostics.map((item) => (_jsxs("div", { role: "alert", children: [item.code, " \u00B7 ", item.path] }, `${item.path}:${item.code}`)))] }, node.stable_id));
|
|
24
|
+
}) })), _jsx("p", { children: "\u8C03\u8BD5\u5668\u53EA\u5C55\u793A P1 \u7EAF\u6A21\u62DF\u5668 trace\uFF0C\u4E0D\u6267\u884C\u751F\u4EA7 effect\u3002" })] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface ViewFile {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly content: string;
|
|
5
|
+
readonly readonly: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ViewWorkspaceProps {
|
|
8
|
+
readonly files: readonly ViewFile[];
|
|
9
|
+
readonly selected_path: string | null;
|
|
10
|
+
readonly disabled: boolean;
|
|
11
|
+
readonly onSelectFile: (path: string) => Promise<void>;
|
|
12
|
+
readonly onSaveFile: (path: string, content: string) => Promise<void>;
|
|
13
|
+
readonly onPreview: () => Promise<void>;
|
|
14
|
+
readonly renderMonaco?: (props: ViewWorkspaceProps & {
|
|
15
|
+
readonly file: ViewFile | null;
|
|
16
|
+
}) => ReactNode;
|
|
17
|
+
readonly renderPreview?: () => ReactNode;
|
|
18
|
+
}
|
|
19
|
+
export declare function ViewWorkspace(props: ViewWorkspaceProps): JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
export function ViewWorkspace(props) {
|
|
4
|
+
const file = props.files.find((item) => item.path === props.selected_path) ?? null;
|
|
5
|
+
const [content, setContent] = useState(file?.content ?? "");
|
|
6
|
+
useEffect(() => setContent(file?.content ?? ""), [file]);
|
|
7
|
+
return (_jsxs("section", { "aria-label": "View \u5DE5\u4F5C\u533A", "data-testid": "view-workspace", children: [_jsxs("nav", { "aria-label": "View \u6587\u4EF6", children: [props.files.length === 0 ? _jsx("p", { children: "\u5F53\u524D View \u6CA1\u6709\u53EF\u7F16\u8F91\u6587\u4EF6\u3002" }) : null, props.files.map((item) => (_jsx("button", { "aria-current": item.path === props.selected_path ? "page" : undefined, disabled: props.disabled, onClick: () => void props.onSelectFile(item.path), type: "button", children: item.path }, item.path)))] }), _jsx("div", { "aria-label": "Monaco \u7F16\u8F91\u5668", children: props.renderMonaco?.({ ...props, file }) ?? (file === null ? _jsx("p", { children: "\u9009\u62E9\u4E00\u4E2A View \u6587\u4EF6\u4EE5\u5F00\u59CB\u624B\u52A8\u7F16\u8F91\u3002" }) : (_jsxs(_Fragment, { children: [_jsx("textarea", { "aria-label": "View \u6E90\u7801", disabled: props.disabled || file.readonly, onChange: (event) => setContent(event.target.value), readOnly: file.readonly, value: content }), _jsx("button", { disabled: props.disabled || file.readonly || content === file.content, onClick: () => void props.onSaveFile(file.path, content), type: "button", children: "\u4FDD\u5B58\u6587\u4EF6" })] }))) }), _jsxs("div", { "aria-label": "View \u9884\u89C8", "data-testid": "view-preview-workspace", children: [_jsx("button", { disabled: props.disabled, onClick: () => void props.onPreview(), type: "button", children: "\u5237\u65B0\u9884\u89C8" }), props.renderPreview?.()] })] }));
|
|
8
|
+
}
|