@arronqzy/react-blueprint 1.0.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/.eslintrc.cjs +5 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/aix-ppc64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/android-arm.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/android-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/android-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/darwin-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/darwin-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/freebsd-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/freebsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-arm.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-ia32.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-loong64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-mips64el.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-ppc64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-riscv64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-s390x.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/netbsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/openbsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/sunos-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/win32-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/win32-ia32.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/win32-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-android-arm-eabi.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-android-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-darwin-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-darwin-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-freebsd-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-freebsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm64-musl.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-riscv64-musl.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-s390x-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-x64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-x64-musl.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-win32-arm64-msvc.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-win32-ia32-msvc.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-win32-x64-msvc.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/eslint.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/fsevents.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/react-dom.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/react-is.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-darwin-64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-darwin-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-linux-64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-linux-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-windows-64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-windows-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/typescript.json +1 -0
- package/.turbo/turbo-lint.log +4 -0
- package/dist/assets/index-CneNgzWb.css +1 -0
- package/dist/assets/index-DsVhHV0-.js +226 -0
- package/dist/index.html +14 -0
- package/index.html +13 -0
- package/index.ts +70 -0
- package/package.json +43 -0
- package/postcss.config.cjs +6 -0
- package/src/BlueprintCanvasContext.tsx +61 -0
- package/src/BlueprintContextMenu.tsx +108 -0
- package/src/BlueprintNodeConfigSidebar.tsx +358 -0
- package/src/blueprint.css +321 -0
- package/src/blueprintNodeTypes.ts +20 -0
- package/src/components/BlueprintExecutionLogPanel.tsx +427 -0
- package/src/components/BlueprintLibrarySelect.tsx +68 -0
- package/src/components/BlueprintMetaDialog.tsx +96 -0
- package/src/components/BlueprintNodeSwitchTaskDialog.tsx +58 -0
- package/src/components/BlueprintPanelToolbar.tsx +664 -0
- package/src/components/ClockNodeConfigPanel.tsx +139 -0
- package/src/components/FetchNodeConfigPanel.tsx +716 -0
- package/src/components/FetchUrlAutocomplete.tsx +190 -0
- package/src/components/JsonNodeConfigPanel.tsx +79 -0
- package/src/components/LogicNodeConfigPanel.tsx +80 -0
- package/src/components/ViewElementMultiSelect.tsx +140 -0
- package/src/createBlueprintEdgeTypes.tsx +10 -0
- package/src/dev-seed.ts +34 -0
- package/src/dev.css +38 -0
- package/src/edges/BlueprintSmoothEdge.tsx +35 -0
- package/src/fetch-config-task-store.ts +197 -0
- package/src/flowCoordinates.ts +19 -0
- package/src/flowDefaults.ts +9 -0
- package/src/graph/blueprint-graph.ts +261 -0
- package/src/graph/document.ts +384 -0
- package/src/graph/index.ts +7 -0
- package/src/graph/node-summary.ts +88 -0
- package/src/graph/node-types.ts +9 -0
- package/src/graph/react-flow-adapter.ts +127 -0
- package/src/graph/sync-edges.ts +69 -0
- package/src/graph/sync-nodes.ts +104 -0
- package/src/hooks/useBlueprintDebugSession.ts +515 -0
- package/src/hooks/useBlueprintFlowColorMode.ts +8 -0
- package/src/hooks/useBlueprintFlowState.ts +486 -0
- package/src/hooks/useBlueprintFlowViewport.ts +54 -0
- package/src/hooks/useBlueprintNodeSelectionGuard.ts +63 -0
- package/src/hooks/useBlueprintPageLifecycle.ts +245 -0
- package/src/index.tsx +271 -0
- package/src/library/blueprint-io.ts +108 -0
- package/src/library/blueprint-library-db.ts +112 -0
- package/src/library/execution-log-db.ts +171 -0
- package/src/library/execution-log-settings.ts +50 -0
- package/src/library/swagger-docs.ts +56 -0
- package/src/library/types.ts +35 -0
- package/src/main.tsx +43 -0
- package/src/nodes/AndFlowNode.tsx +59 -0
- package/src/nodes/BlueprintFlowNode.tsx +26 -0
- package/src/nodes/BlueprintNodeCard.tsx +169 -0
- package/src/nodes/BlueprintNodeShell.tsx +45 -0
- package/src/nodes/ClockFlowNode.tsx +67 -0
- package/src/nodes/FetchFlowNode.tsx +26 -0
- package/src/nodes/JsonFlowNode.tsx +26 -0
- package/src/nodes/LifecycleFlowNode.tsx +44 -0
- package/src/nodes/LogicFlowNode.tsx +26 -0
- package/src/runtime/document-to-runnable-graph.ts +48 -0
- package/src/runtime/execution-overlay.ts +169 -0
- package/src/types.ts +1 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.ts +6 -0
- package/tsconfig.json +12 -0
- package/vite.config.ts +16 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
FetchRequestConfig,
|
|
5
|
+
FetchResultValue,
|
|
6
|
+
ParsedSwaggerDocument,
|
|
7
|
+
} from "@arronqzy/blueprint-dsl";
|
|
8
|
+
import { executeFetch } from "@arronqzy/blueprint-dsl";
|
|
9
|
+
|
|
10
|
+
import { loadSwaggerDocument } from "./library/swagger-docs";
|
|
11
|
+
|
|
12
|
+
export type SwaggerLoadTaskStatus = "idle" | "loading" | "error";
|
|
13
|
+
|
|
14
|
+
export type SwaggerLoadTaskRecord = {
|
|
15
|
+
status: SwaggerLoadTaskStatus;
|
|
16
|
+
error?: string | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type TaskEntry = {
|
|
20
|
+
record: SwaggerLoadTaskRecord;
|
|
21
|
+
abortController: AbortController;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const IDLE_SWAGGER_LOAD_TASK: SwaggerLoadTaskRecord = { status: "idle" };
|
|
25
|
+
const LOADING_SWAGGER_LOAD_TASK: SwaggerLoadTaskRecord = { status: "loading" };
|
|
26
|
+
|
|
27
|
+
const swaggerTasks = new Map<string, TaskEntry>();
|
|
28
|
+
const fetchDebugTasks = new Map<string, FetchDebugTaskEntry>();
|
|
29
|
+
const listeners = new Set<() => void>();
|
|
30
|
+
|
|
31
|
+
export type FetchDebugTaskStatus = "idle" | "loading" | "success" | "error";
|
|
32
|
+
|
|
33
|
+
export type FetchDebugTaskRecord = {
|
|
34
|
+
status: FetchDebugTaskStatus;
|
|
35
|
+
result?: FetchResultValue;
|
|
36
|
+
error?: string | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type FetchDebugTaskEntry = {
|
|
40
|
+
record: FetchDebugTaskRecord;
|
|
41
|
+
abortController: AbortController;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const IDLE_FETCH_DEBUG_TASK: FetchDebugTaskRecord = { status: "idle" };
|
|
45
|
+
const LOADING_FETCH_DEBUG_TASK: FetchDebugTaskRecord = { status: "loading" };
|
|
46
|
+
|
|
47
|
+
function emit() {
|
|
48
|
+
listeners.forEach((listener) => listener());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function subscribeFetchConfigTasks(listener: () => void) {
|
|
52
|
+
listeners.add(listener);
|
|
53
|
+
return () => listeners.delete(listener);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getSwaggerLoadTask(nodeId: string): SwaggerLoadTaskRecord {
|
|
57
|
+
return swaggerTasks.get(nodeId)?.record ?? IDLE_SWAGGER_LOAD_TASK;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getFetchDebugTask(nodeId: string): FetchDebugTaskRecord {
|
|
61
|
+
return fetchDebugTasks.get(nodeId)?.record ?? IDLE_FETCH_DEBUG_TASK;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function findActiveSwaggerLoadTaskNodeId(): string | null {
|
|
65
|
+
for (const [nodeId, task] of swaggerTasks) {
|
|
66
|
+
if (task.record.status === "loading") return nodeId;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function findActiveFetchDebugTaskNodeId(): string | null {
|
|
72
|
+
for (const [nodeId, task] of fetchDebugTasks) {
|
|
73
|
+
if (task.record.status === "loading") return nodeId;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function findActiveFetchConfigTaskNodeId(): string | null {
|
|
79
|
+
return findActiveSwaggerLoadTaskNodeId() ?? findActiveFetchDebugTaskNodeId();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isSwaggerLoadTaskActive(nodeId: string): boolean {
|
|
83
|
+
return getSwaggerLoadTask(nodeId).status === "loading";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isFetchDebugTaskActive(nodeId: string): boolean {
|
|
87
|
+
return getFetchDebugTask(nodeId).status === "loading";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function cancelSwaggerLoadTask(nodeId: string) {
|
|
91
|
+
const task = swaggerTasks.get(nodeId);
|
|
92
|
+
if (!task) return;
|
|
93
|
+
task.abortController.abort();
|
|
94
|
+
swaggerTasks.delete(nodeId);
|
|
95
|
+
emit();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function cancelFetchDebugTask(nodeId: string) {
|
|
99
|
+
const task = fetchDebugTasks.get(nodeId);
|
|
100
|
+
if (!task) return;
|
|
101
|
+
task.abortController.abort();
|
|
102
|
+
fetchDebugTasks.delete(nodeId);
|
|
103
|
+
emit();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function useSwaggerLoadTask(nodeId: string): SwaggerLoadTaskRecord {
|
|
107
|
+
const getSnapshot = useCallback(() => getSwaggerLoadTask(nodeId), [nodeId]);
|
|
108
|
+
return useSyncExternalStore(subscribeFetchConfigTasks, getSnapshot, getSnapshot);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function useFetchDebugTask(nodeId: string): FetchDebugTaskRecord {
|
|
112
|
+
const getSnapshot = useCallback(() => getFetchDebugTask(nodeId), [nodeId]);
|
|
113
|
+
return useSyncExternalStore(subscribeFetchConfigTasks, getSnapshot, getSnapshot);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function startSwaggerLoadTask(params: {
|
|
117
|
+
nodeId: string;
|
|
118
|
+
docsUrl: string;
|
|
119
|
+
onSuccess: (parsed: ParsedSwaggerDocument, docsUrl: string) => void;
|
|
120
|
+
}) {
|
|
121
|
+
cancelSwaggerLoadTask(params.nodeId);
|
|
122
|
+
|
|
123
|
+
const abortController = new AbortController();
|
|
124
|
+
swaggerTasks.set(params.nodeId, {
|
|
125
|
+
record: LOADING_SWAGGER_LOAD_TASK,
|
|
126
|
+
abortController,
|
|
127
|
+
});
|
|
128
|
+
emit();
|
|
129
|
+
|
|
130
|
+
void (async () => {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = await loadSwaggerDocument(params.docsUrl, abortController.signal);
|
|
133
|
+
if (abortController.signal.aborted) return;
|
|
134
|
+
|
|
135
|
+
swaggerTasks.delete(params.nodeId);
|
|
136
|
+
emit();
|
|
137
|
+
params.onSuccess(parsed, params.docsUrl);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (abortController.signal.aborted) {
|
|
140
|
+
swaggerTasks.delete(params.nodeId);
|
|
141
|
+
emit();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const message =
|
|
146
|
+
error instanceof Error ? error.message : "Swagger 文档解析失败";
|
|
147
|
+
swaggerTasks.set(params.nodeId, {
|
|
148
|
+
record: { status: "error", error: message },
|
|
149
|
+
abortController,
|
|
150
|
+
});
|
|
151
|
+
emit();
|
|
152
|
+
}
|
|
153
|
+
})();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function startFetchDebugTask(params: {
|
|
157
|
+
nodeId: string;
|
|
158
|
+
config: FetchRequestConfig;
|
|
159
|
+
}) {
|
|
160
|
+
cancelFetchDebugTask(params.nodeId);
|
|
161
|
+
|
|
162
|
+
const abortController = new AbortController();
|
|
163
|
+
fetchDebugTasks.set(params.nodeId, {
|
|
164
|
+
record: LOADING_FETCH_DEBUG_TASK,
|
|
165
|
+
abortController,
|
|
166
|
+
});
|
|
167
|
+
emit();
|
|
168
|
+
|
|
169
|
+
void (async () => {
|
|
170
|
+
try {
|
|
171
|
+
const result = await executeFetch(params.config, {
|
|
172
|
+
signal: abortController.signal,
|
|
173
|
+
allowHttpError: true,
|
|
174
|
+
});
|
|
175
|
+
if (abortController.signal.aborted) return;
|
|
176
|
+
|
|
177
|
+
fetchDebugTasks.set(params.nodeId, {
|
|
178
|
+
record: { status: "success", result },
|
|
179
|
+
abortController,
|
|
180
|
+
});
|
|
181
|
+
emit();
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (abortController.signal.aborted) {
|
|
184
|
+
fetchDebugTasks.delete(params.nodeId);
|
|
185
|
+
emit();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const message = error instanceof Error ? error.message : "请求失败";
|
|
190
|
+
fetchDebugTasks.set(params.nodeId, {
|
|
191
|
+
record: { status: "error", error: message },
|
|
192
|
+
abortController,
|
|
193
|
+
});
|
|
194
|
+
emit();
|
|
195
|
+
}
|
|
196
|
+
})();
|
|
197
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { XYPosition } from "@xyflow/react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BP_FLOW_NODE_HEIGHT,
|
|
5
|
+
BP_FLOW_NODE_WIDTH,
|
|
6
|
+
} from "./graph/react-flow-adapter";
|
|
7
|
+
|
|
8
|
+
/** 屏幕坐标 → 流程坐标,落点以节点中心对齐光标 */
|
|
9
|
+
export function clientToFlowNodePosition(
|
|
10
|
+
screenToFlowPosition: (position: XYPosition) => XYPosition,
|
|
11
|
+
clientX: number,
|
|
12
|
+
clientY: number
|
|
13
|
+
): XYPosition {
|
|
14
|
+
const flow = screenToFlowPosition({ x: clientX, y: clientY });
|
|
15
|
+
return {
|
|
16
|
+
x: flow.x - BP_FLOW_NODE_WIDTH / 2,
|
|
17
|
+
y: flow.y - BP_FLOW_NODE_HEIGHT / 2,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DefaultEdgeOptions } from "@xyflow/react";
|
|
2
|
+
|
|
3
|
+
import { BP_EDGE_STYLE, BP_FLOW_EDGE_TYPE } from "./graph/sync-edges";
|
|
4
|
+
|
|
5
|
+
export const BLUEPRINT_DEFAULT_EDGE_OPTIONS = {
|
|
6
|
+
type: BP_FLOW_EDGE_TYPE,
|
|
7
|
+
zIndex: 1000,
|
|
8
|
+
style: { ...BP_EDGE_STYLE },
|
|
9
|
+
} satisfies DefaultEdgeOptions;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBlueprintDocument,
|
|
3
|
+
createNodeId,
|
|
4
|
+
defaultBlueprintNodeLabel,
|
|
5
|
+
filterInvalidBlueprintEdges,
|
|
6
|
+
patchNodeConfigSource,
|
|
7
|
+
resolveNodeClockConfig,
|
|
8
|
+
resolveNodeFetchConfig,
|
|
9
|
+
resolveNodeJsonConfig,
|
|
10
|
+
resolveNodeLogicConfig,
|
|
11
|
+
sanitizeBlueprintDocument,
|
|
12
|
+
type BlueprintDocument,
|
|
13
|
+
type BlueprintGraphEdge,
|
|
14
|
+
type BlueprintGraphNode,
|
|
15
|
+
} from "./document";
|
|
16
|
+
import {
|
|
17
|
+
BLUEPRINT_NODE_TYPE,
|
|
18
|
+
DEFAULT_LOGIC_NODE_TYPE,
|
|
19
|
+
LIFECYCLE_NODE_TYPE,
|
|
20
|
+
} from "./node-types";
|
|
21
|
+
import type { PageLifecyclePhase } from "@arronqzy/blueprint-dsl";
|
|
22
|
+
|
|
23
|
+
export class BlueprintGraph {
|
|
24
|
+
readonly document: BlueprintDocument;
|
|
25
|
+
|
|
26
|
+
constructor(document: BlueprintDocument = createBlueprintDocument()) {
|
|
27
|
+
this.document = sanitizeBlueprintDocument(document);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static empty(id?: string) {
|
|
31
|
+
return new BlueprintGraph(sanitizeBlueprintDocument(createBlueprintDocument(id)));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static fromDocument(document: BlueprintDocument) {
|
|
35
|
+
return new BlueprintGraph(document);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getNode(nodeId: string): BlueprintGraphNode | undefined {
|
|
39
|
+
return this.document.nodes.find((n) => n.id === nodeId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
addBlueprintNode(position: { x: number; y: number }, label?: string) {
|
|
43
|
+
const node: BlueprintGraphNode = {
|
|
44
|
+
id: createNodeId("bp"),
|
|
45
|
+
role: "blueprint",
|
|
46
|
+
nodeType: BLUEPRINT_NODE_TYPE,
|
|
47
|
+
label: label ?? defaultBlueprintNodeLabel("blueprint"),
|
|
48
|
+
position: { ...position },
|
|
49
|
+
nestedBlueprintId: createNodeId("nested_bp"),
|
|
50
|
+
};
|
|
51
|
+
return this.withDocument({
|
|
52
|
+
...this.document,
|
|
53
|
+
nodes: [...this.document.nodes, node],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
addLogicNode(
|
|
58
|
+
parentBlueprintId: string,
|
|
59
|
+
position: { x: number; y: number },
|
|
60
|
+
label?: string
|
|
61
|
+
) {
|
|
62
|
+
const parent = this.getNode(parentBlueprintId);
|
|
63
|
+
if (!parent || parent.role !== "blueprint") {
|
|
64
|
+
throw new Error(`Parent blueprint node not found: ${parentBlueprintId}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const node: BlueprintGraphNode = {
|
|
68
|
+
id: createNodeId("logic"),
|
|
69
|
+
role: "logic",
|
|
70
|
+
nodeType: DEFAULT_LOGIC_NODE_TYPE,
|
|
71
|
+
label: label ?? defaultBlueprintNodeLabel("logic"),
|
|
72
|
+
position: { ...position },
|
|
73
|
+
parentId: parentBlueprintId,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return this.withDocument({
|
|
77
|
+
...this.document,
|
|
78
|
+
nodes: [...this.document.nodes, node],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
addLifecycleNode(
|
|
83
|
+
parentBlueprintId: string,
|
|
84
|
+
position: { x: number; y: number },
|
|
85
|
+
lifecyclePhase: PageLifecyclePhase = "mounted",
|
|
86
|
+
label?: string
|
|
87
|
+
) {
|
|
88
|
+
const parent = this.getNode(parentBlueprintId);
|
|
89
|
+
if (!parent || parent.role !== "blueprint") {
|
|
90
|
+
throw new Error(`Parent blueprint node not found: ${parentBlueprintId}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const node: BlueprintGraphNode = {
|
|
94
|
+
id: createNodeId("lifecycle"),
|
|
95
|
+
role: "lifecycle",
|
|
96
|
+
nodeType: LIFECYCLE_NODE_TYPE,
|
|
97
|
+
label: label ?? defaultBlueprintNodeLabel("lifecycle"),
|
|
98
|
+
position: { ...position },
|
|
99
|
+
parentId: parentBlueprintId,
|
|
100
|
+
lifecyclePhase,
|
|
101
|
+
configSource: "lifecycle",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return this.withDocument({
|
|
105
|
+
...this.document,
|
|
106
|
+
nodes: [...this.document.nodes, node],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
removeNode(nodeId: string) {
|
|
111
|
+
const target = this.getNode(nodeId);
|
|
112
|
+
if (!target) return this;
|
|
113
|
+
|
|
114
|
+
const removeIds = new Set<string>([nodeId]);
|
|
115
|
+
if (target.role === "blueprint") {
|
|
116
|
+
for (const n of this.document.nodes) {
|
|
117
|
+
if (n.parentId === nodeId) removeIds.add(n.id);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return this.withDocument({
|
|
122
|
+
...this.document,
|
|
123
|
+
nodes: this.document.nodes.filter((n) => !removeIds.has(n.id)),
|
|
124
|
+
edges: this.document.edges.filter(
|
|
125
|
+
(e) => !removeIds.has(e.source) && !removeIds.has(e.target)
|
|
126
|
+
),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
updateNodePosition(nodeId: string, position: { x: number; y: number }) {
|
|
131
|
+
return this.withDocument({
|
|
132
|
+
...this.document,
|
|
133
|
+
nodes: this.document.nodes.map((n) =>
|
|
134
|
+
n.id === nodeId ? { ...n, position: { ...position } } : n
|
|
135
|
+
),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
applyNodePositions(
|
|
140
|
+
updates: Array<{ id: string; position: { x: number; y: number } }>
|
|
141
|
+
) {
|
|
142
|
+
if (updates.length === 0) return this;
|
|
143
|
+
const positionById = new Map(updates.map((u) => [u.id, u.position]));
|
|
144
|
+
return this.withDocument({
|
|
145
|
+
...this.document,
|
|
146
|
+
nodes: this.document.nodes.map((n) => {
|
|
147
|
+
const position = positionById.get(n.id);
|
|
148
|
+
return position ? { ...n, position: { ...position } } : n;
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
addEdge(edge: Omit<BlueprintGraphEdge, "id"> & { id?: string }) {
|
|
154
|
+
const nextEdge: BlueprintGraphEdge = {
|
|
155
|
+
id: edge.id ?? createNodeId("edge"),
|
|
156
|
+
source: edge.source,
|
|
157
|
+
target: edge.target,
|
|
158
|
+
sourceHandle: edge.sourceHandle,
|
|
159
|
+
targetHandle: edge.targetHandle,
|
|
160
|
+
};
|
|
161
|
+
return this.withDocument({
|
|
162
|
+
...this.document,
|
|
163
|
+
edges: [...this.document.edges, nextEdge],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
removeEdge(edgeId: string) {
|
|
168
|
+
if (!this.document.edges.some((e) => e.id === edgeId)) return this;
|
|
169
|
+
return this.withDocument({
|
|
170
|
+
...this.document,
|
|
171
|
+
edges: this.document.edges.filter((e) => e.id !== edgeId),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
replaceEdges(edges: BlueprintGraphEdge[]) {
|
|
176
|
+
return this.withDocument({
|
|
177
|
+
...this.document,
|
|
178
|
+
edges: filterInvalidBlueprintEdges(this.document, edges),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
updateNode(
|
|
183
|
+
nodeId: string,
|
|
184
|
+
patch: Partial<
|
|
185
|
+
Pick<
|
|
186
|
+
BlueprintGraphNode,
|
|
187
|
+
| "label"
|
|
188
|
+
| "role"
|
|
189
|
+
| "nodeType"
|
|
190
|
+
| "configSource"
|
|
191
|
+
| "viewElementId"
|
|
192
|
+
| "viewElementIds"
|
|
193
|
+
| "nestedBlueprintId"
|
|
194
|
+
| "libraryBlueprintId"
|
|
195
|
+
| "lifecyclePhase"
|
|
196
|
+
| "fetchConfig"
|
|
197
|
+
| "jsonConfig"
|
|
198
|
+
| "logicConfig"
|
|
199
|
+
| "clockConfig"
|
|
200
|
+
>
|
|
201
|
+
>
|
|
202
|
+
) {
|
|
203
|
+
const exists = this.getNode(nodeId);
|
|
204
|
+
if (!exists) return this;
|
|
205
|
+
|
|
206
|
+
const configPatch =
|
|
207
|
+
patch.configSource && patch.configSource !== exists.configSource
|
|
208
|
+
? patchNodeConfigSource(exists, patch.configSource)
|
|
209
|
+
: {};
|
|
210
|
+
|
|
211
|
+
return this.withDocument({
|
|
212
|
+
...this.document,
|
|
213
|
+
nodes: this.document.nodes.map((n) => {
|
|
214
|
+
if (n.id !== nodeId) return n;
|
|
215
|
+
const nextNode = { ...n, ...configPatch, ...patch };
|
|
216
|
+
if (
|
|
217
|
+
patch.viewElementIds !== undefined ||
|
|
218
|
+
patch.viewElementId !== undefined
|
|
219
|
+
) {
|
|
220
|
+
const ids =
|
|
221
|
+
patch.viewElementIds !== undefined
|
|
222
|
+
? [...new Set(patch.viewElementIds.filter(Boolean))]
|
|
223
|
+
: patch.viewElementId
|
|
224
|
+
? [patch.viewElementId]
|
|
225
|
+
: [];
|
|
226
|
+
nextNode.viewElementIds = ids.length > 0 ? ids : undefined;
|
|
227
|
+
nextNode.viewElementId = undefined;
|
|
228
|
+
}
|
|
229
|
+
if (patch.fetchConfig) {
|
|
230
|
+
nextNode.fetchConfig = {
|
|
231
|
+
...resolveNodeFetchConfig(n),
|
|
232
|
+
...patch.fetchConfig,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (patch.jsonConfig) {
|
|
236
|
+
nextNode.jsonConfig = {
|
|
237
|
+
...resolveNodeJsonConfig(n),
|
|
238
|
+
...patch.jsonConfig,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (patch.logicConfig) {
|
|
242
|
+
nextNode.logicConfig = {
|
|
243
|
+
...resolveNodeLogicConfig(n),
|
|
244
|
+
...patch.logicConfig,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (patch.clockConfig) {
|
|
248
|
+
nextNode.clockConfig = {
|
|
249
|
+
...resolveNodeClockConfig(n),
|
|
250
|
+
...patch.clockConfig,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return nextNode;
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
withDocument(document: BlueprintDocument) {
|
|
259
|
+
return new BlueprintGraph(document);
|
|
260
|
+
}
|
|
261
|
+
}
|