@arronqzy/vue-blueprint 0.1.4 → 0.1.6
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/package.json +6 -6
- package/src/BlueprintNodeConfigSidebar.vue +68 -6
- package/src/blueprintNodeTypes.ts +2 -0
- package/src/components/BlueprintCanvas.vue +2 -0
- package/src/components/BlueprintContextMenu.vue +1 -1
- package/src/components/ConfigHintIcon.vue +9 -9
- package/src/components/FetchNodeConfigPanel.vue +65 -7
- package/src/components/FetchUrlAutocomplete.vue +1 -0
- package/src/components/ScopeTemplateAutocompleteHost.vue +231 -0
- package/src/composables/useBlueprintFlowState.ts +51 -5
- package/src/composables/useBlueprintPageLifecycle.ts +99 -10
- package/src/graph/blueprint-graph.ts +8 -0
- package/src/graph/document.ts +135 -8
- package/src/graph/node-summary.ts +20 -0
- package/src/graph/node-types.ts +1 -0
- package/src/graph/sync-nodes.ts +2 -1
- package/src/graph/vue-flow-adapter.ts +4 -0
- package/src/nodes/BlueprintNodeCard.vue +12 -2
- package/src/nodes/BlueprintNodeShell.vue +4 -2
- package/src/nodes/EventFlowNode.vue +31 -0
- package/src/runtime/document-to-runnable-graph.ts +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useI18n } from "@arronqzy/i18n/vue";
|
|
1
2
|
import {
|
|
2
3
|
addEdge,
|
|
3
4
|
applyEdgeChanges,
|
|
@@ -8,10 +9,14 @@ import {
|
|
|
8
9
|
type Node,
|
|
9
10
|
type NodeChange,
|
|
10
11
|
} from "@vue-flow/core";
|
|
12
|
+
import { message } from "ant-design-vue";
|
|
11
13
|
import { onBeforeUnmount, ref, watch, type Ref } from "vue";
|
|
12
14
|
|
|
13
15
|
import type { BlueprintGraph } from "../graph/blueprint-graph";
|
|
14
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
resolveBlueprintConnectionIssue,
|
|
18
|
+
sanitizeBlueprintDocument,
|
|
19
|
+
} from "../graph/document";
|
|
15
20
|
import {
|
|
16
21
|
applyFlowNodePositions,
|
|
17
22
|
BP_EDGE_STYLE,
|
|
@@ -424,6 +429,7 @@ export function useBlueprintFlowState({
|
|
|
424
429
|
}
|
|
425
430
|
|
|
426
431
|
function onConnect(connection: Connection) {
|
|
432
|
+
pendingConnectionIssue = null;
|
|
427
433
|
const resolved = resolveConnection(
|
|
428
434
|
connection,
|
|
429
435
|
nodes.value as Node<BlueprintFlowNodeData>[]
|
|
@@ -456,6 +462,11 @@ export function useBlueprintFlowState({
|
|
|
456
462
|
});
|
|
457
463
|
}
|
|
458
464
|
|
|
465
|
+
const { t } = useI18n();
|
|
466
|
+
let pendingConnectionIssue: ReturnType<
|
|
467
|
+
typeof resolveBlueprintConnectionIssue
|
|
468
|
+
> = null;
|
|
469
|
+
|
|
459
470
|
function isValidConnection(connection: Edge | Connection) {
|
|
460
471
|
if (!connection.source || !connection.target) return false;
|
|
461
472
|
if (connection.source === connection.target) return false;
|
|
@@ -467,11 +478,26 @@ export function useBlueprintFlowState({
|
|
|
467
478
|
}
|
|
468
479
|
|
|
469
480
|
const targetNode = rfNodes.find((n) => n.id === connection.target);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
481
|
+
const sourceNode = rfNodes.find((n) => n.id === connection.source);
|
|
482
|
+
const issue = resolveBlueprintConnectionIssue(
|
|
483
|
+
sourceNode?.data,
|
|
484
|
+
targetNode?.data
|
|
485
|
+
);
|
|
486
|
+
pendingConnectionIssue = issue;
|
|
487
|
+
return !issue;
|
|
488
|
+
}
|
|
473
489
|
|
|
474
|
-
|
|
490
|
+
function onConnectEnd(event?: MouseEvent | TouchEvent) {
|
|
491
|
+
const issue = pendingConnectionIssue;
|
|
492
|
+
pendingConnectionIssue = null;
|
|
493
|
+
if (!issue || !isPointerOverFlowNode(event)) return;
|
|
494
|
+
if (issue === "event-requires-lifecycle") {
|
|
495
|
+
message.warning(t("blueprint.config.eventConnectNeedLifecycle"));
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (issue === "lifecycle-no-input") {
|
|
499
|
+
message.warning(t("blueprint.config.lifecycleNoInput"));
|
|
500
|
+
}
|
|
475
501
|
}
|
|
476
502
|
|
|
477
503
|
return {
|
|
@@ -481,6 +507,26 @@ export function useBlueprintFlowState({
|
|
|
481
507
|
onEdgesChange,
|
|
482
508
|
onNodeDragStop,
|
|
483
509
|
onConnect,
|
|
510
|
+
onConnectEnd,
|
|
484
511
|
isValidConnection,
|
|
485
512
|
};
|
|
486
513
|
}
|
|
514
|
+
|
|
515
|
+
function isPointerOverFlowNode(event?: MouseEvent | TouchEvent) {
|
|
516
|
+
if (!event) return false;
|
|
517
|
+
const point =
|
|
518
|
+
"changedTouches" in event
|
|
519
|
+
? event.changedTouches[0]
|
|
520
|
+
: "clientX" in event
|
|
521
|
+
? event
|
|
522
|
+
: undefined;
|
|
523
|
+
const el =
|
|
524
|
+
point && typeof document !== "undefined"
|
|
525
|
+
? document.elementFromPoint(point.clientX, point.clientY)
|
|
526
|
+
: event.target instanceof Element
|
|
527
|
+
? event.target
|
|
528
|
+
: null;
|
|
529
|
+
return Boolean(
|
|
530
|
+
el?.closest(".vue-flow__node") || el?.closest(".vue-flow__handle")
|
|
531
|
+
);
|
|
532
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { onUnmounted, ref, shallowRef, toValue, watch, type MaybeRefOrGetter, type Ref } from "vue";
|
|
2
2
|
import {
|
|
3
3
|
BlueprintGraphRunner,
|
|
4
|
+
collectArmedViewEventNodeIds,
|
|
4
5
|
detectBlueprintReferenceCycle,
|
|
6
|
+
EVENT_NODE_TYPE,
|
|
7
|
+
LIFECYCLE_NODE_TYPE,
|
|
5
8
|
type LibraryBlueprintResolver,
|
|
6
9
|
type PageLifecyclePhase,
|
|
10
|
+
type ViewEventSignal,
|
|
7
11
|
} from "@arronqzy/blueprint-dsl";
|
|
8
12
|
|
|
9
13
|
import type { BlueprintGraph } from "../graph/blueprint-graph";
|
|
14
|
+
import { resolveRunnableNodeType } from "../graph/document";
|
|
10
15
|
import { documentToRunnableGraph } from "../runtime/document-to-runnable-graph";
|
|
11
16
|
|
|
12
17
|
const PAGE_BOOT_PHASES: PageLifecyclePhase[] = [
|
|
@@ -87,12 +92,30 @@ function createRunner(graph: BlueprintGraph, options: RunnerOptions) {
|
|
|
87
92
|
);
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
function collectArmedIds(
|
|
96
|
+
graph: BlueprintGraph,
|
|
97
|
+
firedPhases: ReadonlySet<string>
|
|
98
|
+
) {
|
|
99
|
+
return collectArmedViewEventNodeIds(
|
|
100
|
+
graph.document.nodes.map((node) => ({
|
|
101
|
+
id: node.id,
|
|
102
|
+
nodeType: resolveRunnableNodeType(node),
|
|
103
|
+
lifecyclePhase: node.lifecyclePhase,
|
|
104
|
+
})),
|
|
105
|
+
graph.document.edges,
|
|
106
|
+
EVENT_NODE_TYPE,
|
|
107
|
+
LIFECYCLE_NODE_TYPE,
|
|
108
|
+
firedPhases
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
90
112
|
async function emitPhases(
|
|
91
113
|
graph: BlueprintGraph,
|
|
92
114
|
phases: PageLifecyclePhase[],
|
|
93
|
-
options: RunnerOptions
|
|
94
|
-
)
|
|
95
|
-
|
|
115
|
+
options: RunnerOptions,
|
|
116
|
+
onPhaseFired?: (phase: PageLifecyclePhase) => void
|
|
117
|
+
): Promise<boolean> {
|
|
118
|
+
if (!phases.length) return true;
|
|
96
119
|
|
|
97
120
|
const rootLibraryBlueprintId = toValue(options.rootLibraryBlueprintId ?? null);
|
|
98
121
|
const libraryNameById = toValue(options.libraryNameById);
|
|
@@ -109,14 +132,16 @@ async function emitPhases(
|
|
|
109
132
|
});
|
|
110
133
|
if (!result.ok) {
|
|
111
134
|
options.onExecutionBlocked?.(result.message);
|
|
112
|
-
return;
|
|
135
|
+
return false;
|
|
113
136
|
}
|
|
114
137
|
}
|
|
115
138
|
|
|
116
139
|
const runner = createRunner(graph, options);
|
|
117
140
|
for (const phase of phases) {
|
|
118
141
|
await runner.emitLifecycle(phase);
|
|
142
|
+
onPhaseFired?.(phase);
|
|
119
143
|
}
|
|
144
|
+
return true;
|
|
120
145
|
}
|
|
121
146
|
|
|
122
147
|
export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOptions) {
|
|
@@ -133,10 +158,22 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
|
|
|
133
158
|
const bootPhasesKey = bootPhases.join("|");
|
|
134
159
|
const bootTrigger = options.bootKey ?? "__initial__";
|
|
135
160
|
const bootCompleted = ref(false);
|
|
161
|
+
const firedLifecyclePhases = ref<ReadonlySet<PageLifecyclePhase>>(new Set());
|
|
136
162
|
const hadBoot = ref(false);
|
|
137
163
|
const prevActive = ref<boolean | undefined>(undefined);
|
|
138
164
|
let bootCancelled = false;
|
|
139
165
|
|
|
166
|
+
const resetFiredPhases = () => {
|
|
167
|
+
firedLifecyclePhases.value = new Set();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const markPhaseFired = (phase: PageLifecyclePhase) => {
|
|
171
|
+
if (firedLifecyclePhases.value.has(phase)) return;
|
|
172
|
+
const next = new Set(firedLifecyclePhases.value);
|
|
173
|
+
next.add(phase);
|
|
174
|
+
firedLifecyclePhases.value = next;
|
|
175
|
+
};
|
|
176
|
+
|
|
140
177
|
watch(
|
|
141
178
|
() => resolveValue(options.graph),
|
|
142
179
|
(next) => {
|
|
@@ -163,10 +200,12 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
|
|
|
163
200
|
const enabled = resolveValue(options.enabled ?? true);
|
|
164
201
|
if (!enabled) {
|
|
165
202
|
bootCompleted.value = false;
|
|
203
|
+
resetFiredPhases();
|
|
166
204
|
return;
|
|
167
205
|
}
|
|
168
206
|
|
|
169
207
|
bootCancelled = false;
|
|
208
|
+
resetFiredPhases();
|
|
170
209
|
const waitForPageReady = options.waitForPageReady ?? false;
|
|
171
210
|
|
|
172
211
|
if (waitForPageReady) {
|
|
@@ -174,12 +213,27 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
|
|
|
174
213
|
}
|
|
175
214
|
if (bootCancelled) return;
|
|
176
215
|
|
|
177
|
-
await emitPhases(
|
|
216
|
+
const bootOk = await emitPhases(
|
|
217
|
+
graphRef.value,
|
|
218
|
+
bootPhases,
|
|
219
|
+
runnerOptionsRef.value,
|
|
220
|
+
(phase) => {
|
|
221
|
+
if (!bootCancelled) markPhaseFired(phase);
|
|
222
|
+
}
|
|
223
|
+
);
|
|
178
224
|
if (bootCancelled) return;
|
|
225
|
+
if (!bootOk) return;
|
|
179
226
|
|
|
180
227
|
const active = resolveValue(options.active ?? true);
|
|
181
228
|
if (active) {
|
|
182
|
-
await emitPhases(
|
|
229
|
+
await emitPhases(
|
|
230
|
+
graphRef.value,
|
|
231
|
+
["activated"],
|
|
232
|
+
runnerOptionsRef.value,
|
|
233
|
+
(phase) => {
|
|
234
|
+
if (!bootCancelled) markPhaseFired(phase);
|
|
235
|
+
}
|
|
236
|
+
);
|
|
183
237
|
}
|
|
184
238
|
if (bootCancelled) return;
|
|
185
239
|
|
|
@@ -191,6 +245,7 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
|
|
|
191
245
|
const teardownBoot = () => {
|
|
192
246
|
bootCancelled = true;
|
|
193
247
|
bootCompleted.value = false;
|
|
248
|
+
resetFiredPhases();
|
|
194
249
|
prevActive.value = undefined;
|
|
195
250
|
if (hadBoot.value) {
|
|
196
251
|
hadBoot.value = false;
|
|
@@ -222,10 +277,12 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
|
|
|
222
277
|
return;
|
|
223
278
|
}
|
|
224
279
|
prevActive.value = active;
|
|
280
|
+
const phase: PageLifecyclePhase = active ? "activated" : "deactivated";
|
|
225
281
|
void emitPhases(
|
|
226
282
|
graphRef.value,
|
|
227
|
-
[
|
|
228
|
-
runnerOptionsRef.value
|
|
283
|
+
[phase],
|
|
284
|
+
runnerOptionsRef.value,
|
|
285
|
+
markPhaseFired
|
|
229
286
|
);
|
|
230
287
|
}
|
|
231
288
|
);
|
|
@@ -234,7 +291,12 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
|
|
|
234
291
|
() => [bootCompleted.value, resolveValue(options.enabled ?? true), resolveValue(options.onUpdated)],
|
|
235
292
|
([completed, enabled]) => {
|
|
236
293
|
if (!enabled || !completed) return;
|
|
237
|
-
void emitPhases(
|
|
294
|
+
void emitPhases(
|
|
295
|
+
graphRef.value,
|
|
296
|
+
["updated"],
|
|
297
|
+
runnerOptionsRef.value,
|
|
298
|
+
markPhaseFired
|
|
299
|
+
);
|
|
238
300
|
}
|
|
239
301
|
);
|
|
240
302
|
|
|
@@ -267,5 +329,32 @@ export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOpti
|
|
|
267
329
|
await runner.triggerNode(id, inputValue);
|
|
268
330
|
}
|
|
269
331
|
|
|
270
|
-
|
|
332
|
+
async function emitViewEvent(payload: ViewEventSignal) {
|
|
333
|
+
if (!payload?.node?.id || !payload.eventType) return;
|
|
334
|
+
const opts = runnerOptionsRef.value;
|
|
335
|
+
const libraryNameById = toValue(opts.libraryNameById);
|
|
336
|
+
const rootLibraryBlueprintId = toValue(opts.rootLibraryBlueprintId ?? null);
|
|
337
|
+
if (opts.resolveLibraryBlueprint) {
|
|
338
|
+
const result = await detectBlueprintReferenceCycle({
|
|
339
|
+
rootGraph: documentToRunnableGraph(graphRef.value.document, {
|
|
340
|
+
libraryNameById,
|
|
341
|
+
}),
|
|
342
|
+
rootLibraryBlueprintId,
|
|
343
|
+
resolveLibraryBlueprint: opts.resolveLibraryBlueprint,
|
|
344
|
+
resolveBlueprintName: (libId) =>
|
|
345
|
+
resolveBlueprintName(libraryNameById, libId),
|
|
346
|
+
});
|
|
347
|
+
if (!result.ok) {
|
|
348
|
+
opts.onExecutionBlocked?.(result.message);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const runner = createRunner(graphRef.value, opts);
|
|
353
|
+
await runner.emitViewEvent(
|
|
354
|
+
payload,
|
|
355
|
+
collectArmedIds(graphRef.value, firedLifecyclePhases.value)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases };
|
|
271
360
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
filterInvalidBlueprintEdges,
|
|
6
6
|
patchNodeConfigSource,
|
|
7
7
|
resolveNodeClockConfig,
|
|
8
|
+
resolveNodeEventConfig,
|
|
8
9
|
resolveNodeFetchConfig,
|
|
9
10
|
resolveNodeJsonConfig,
|
|
10
11
|
resolveNodeLogicConfig,
|
|
@@ -198,6 +199,7 @@ export class BlueprintGraph {
|
|
|
198
199
|
| "jsonConfig"
|
|
199
200
|
| "logicConfig"
|
|
200
201
|
| "clockConfig"
|
|
202
|
+
| "eventConfig"
|
|
201
203
|
>
|
|
202
204
|
>
|
|
203
205
|
) {
|
|
@@ -251,6 +253,12 @@ export class BlueprintGraph {
|
|
|
251
253
|
...patch.clockConfig,
|
|
252
254
|
};
|
|
253
255
|
}
|
|
256
|
+
if (patch.eventConfig) {
|
|
257
|
+
nextNode.eventConfig = {
|
|
258
|
+
...resolveNodeEventConfig(n),
|
|
259
|
+
...patch.eventConfig,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
254
262
|
return {
|
|
255
263
|
...nextNode,
|
|
256
264
|
nodeType: resolveRunnableNodeType(nextNode),
|
package/src/graph/document.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
JsonNodeConfig,
|
|
5
5
|
LogicNodeConfig,
|
|
6
6
|
PageLifecyclePhase,
|
|
7
|
+
ViewEventNodeConfig,
|
|
8
|
+
ViewEventType,
|
|
7
9
|
} from "@arronqzy/blueprint-dsl";
|
|
8
10
|
import {
|
|
9
11
|
BLUEPRINT_NODE_TYPE,
|
|
@@ -14,10 +16,13 @@ import {
|
|
|
14
16
|
DEFAULT_JSON_NODE_CONFIG,
|
|
15
17
|
DEFAULT_LOGIC_NODE_CONFIG,
|
|
16
18
|
DEFAULT_LOGIC_NODE_TYPE,
|
|
19
|
+
DEFAULT_VIEW_EVENT_NODE_CONFIG,
|
|
20
|
+
EVENT_NODE_TYPE,
|
|
17
21
|
FETCH_NODE_TYPE,
|
|
18
22
|
JSON_NODE_TYPE,
|
|
19
23
|
LIFECYCLE_NODE_TYPE,
|
|
20
24
|
normalizeClockConfig,
|
|
25
|
+
normalizeViewEventConfig,
|
|
21
26
|
VIEW_NODE_TYPE,
|
|
22
27
|
} from "@arronqzy/blueprint-dsl";
|
|
23
28
|
|
|
@@ -25,8 +30,9 @@ export type { FetchRequestConfig as BlueprintFetchConfig } from "@arronqzy/bluep
|
|
|
25
30
|
export type { JsonNodeConfig as BlueprintJsonConfig } from "@arronqzy/blueprint-dsl";
|
|
26
31
|
export type { LogicNodeConfig as BlueprintLogicConfig } from "@arronqzy/blueprint-dsl";
|
|
27
32
|
export type { ClockNodeConfig as BlueprintClockConfig } from "@arronqzy/blueprint-dsl";
|
|
33
|
+
export type { ViewEventNodeConfig as BlueprintEventConfig } from "@arronqzy/blueprint-dsl";
|
|
28
34
|
|
|
29
|
-
export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock";
|
|
35
|
+
export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock" | "event";
|
|
30
36
|
|
|
31
37
|
/** 决定右侧配置面板展示哪类配置 */
|
|
32
38
|
export type BlueprintConfigSource =
|
|
@@ -37,7 +43,8 @@ export type BlueprintConfigSource =
|
|
|
37
43
|
| "view"
|
|
38
44
|
| "fetch"
|
|
39
45
|
| "json"
|
|
40
|
-
| "clock"
|
|
46
|
+
| "clock"
|
|
47
|
+
| "event";
|
|
41
48
|
|
|
42
49
|
export type BlueprintGraphNode = {
|
|
43
50
|
id: string;
|
|
@@ -68,6 +75,8 @@ export type BlueprintGraphNode = {
|
|
|
68
75
|
logicConfig?: LogicNodeConfig;
|
|
69
76
|
/** 时钟节点的间隔与时间格式配置 */
|
|
70
77
|
clockConfig?: ClockNodeConfig;
|
|
78
|
+
/** 视图事件节点监听的事件类型 */
|
|
79
|
+
eventConfig?: ViewEventNodeConfig;
|
|
71
80
|
};
|
|
72
81
|
|
|
73
82
|
export function resolveViewElementIds(
|
|
@@ -94,6 +103,7 @@ export function resolveBlueprintConfigSource(
|
|
|
94
103
|
): BlueprintConfigSource {
|
|
95
104
|
if (node.configSource) return node.configSource;
|
|
96
105
|
if (node.role === "lifecycle") return "lifecycle";
|
|
106
|
+
if (node.role === "event") return "event";
|
|
97
107
|
if (node.role === "clock") return "clock";
|
|
98
108
|
if (node.role === "and") return "and";
|
|
99
109
|
if (node.role === "fetch") return "fetch";
|
|
@@ -101,11 +111,60 @@ export function resolveBlueprintConfigSource(
|
|
|
101
111
|
if (node.nodeType === FETCH_NODE_TYPE) return "fetch";
|
|
102
112
|
if (node.nodeType === JSON_NODE_TYPE) return "json";
|
|
103
113
|
if (node.nodeType === CLOCK_NODE_TYPE) return "clock";
|
|
114
|
+
if (node.nodeType === EVENT_NODE_TYPE) return "event";
|
|
104
115
|
if (node.nodeType === VIEW_NODE_TYPE) return "view";
|
|
105
116
|
if (resolveViewElementIds(node).length > 0) return "view";
|
|
106
117
|
return node.role === "logic" ? "logic" : "blueprint";
|
|
107
118
|
}
|
|
108
119
|
|
|
120
|
+
export type BlueprintConfigSourceNode = Pick<
|
|
121
|
+
BlueprintGraphNode,
|
|
122
|
+
"role" | "nodeType" | "configSource" | "viewElementId" | "viewElementIds"
|
|
123
|
+
>;
|
|
124
|
+
|
|
125
|
+
export type BlueprintConnectionIssue =
|
|
126
|
+
| "lifecycle-no-input"
|
|
127
|
+
| "event-requires-lifecycle";
|
|
128
|
+
|
|
129
|
+
/** 连入校验:生命周期无输入;事件节点输入必须全部来自生命周期节点 */
|
|
130
|
+
export function resolveBlueprintConnectionIssue(
|
|
131
|
+
source: BlueprintConfigSourceNode | undefined,
|
|
132
|
+
target: BlueprintConfigSourceNode | undefined
|
|
133
|
+
): BlueprintConnectionIssue | null {
|
|
134
|
+
if (!source || !target) return null;
|
|
135
|
+
const targetSource = resolveBlueprintConfigSource(target);
|
|
136
|
+
if (targetSource === "lifecycle") return "lifecycle-no-input";
|
|
137
|
+
if (
|
|
138
|
+
targetSource === "event" &&
|
|
139
|
+
resolveBlueprintConfigSource(source) !== "lifecycle"
|
|
140
|
+
) {
|
|
141
|
+
return "event-requires-lifecycle";
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function inspectEventNodeInputs(
|
|
147
|
+
nodeId: string,
|
|
148
|
+
nodes: Array<BlueprintConfigSourceNode & { id: string }>,
|
|
149
|
+
edges: Array<{ source: string; target: string }>
|
|
150
|
+
) {
|
|
151
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
152
|
+
let incomingCount = 0;
|
|
153
|
+
let lifecycleCount = 0;
|
|
154
|
+
let nonLifecycleCount = 0;
|
|
155
|
+
for (const edge of edges) {
|
|
156
|
+
if (edge.target !== nodeId) continue;
|
|
157
|
+
incomingCount += 1;
|
|
158
|
+
const source = byId.get(edge.source);
|
|
159
|
+
if (source && resolveRunnableNodeType(source) === LIFECYCLE_NODE_TYPE) {
|
|
160
|
+
lifecycleCount += 1;
|
|
161
|
+
} else {
|
|
162
|
+
nonLifecycleCount += 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { incomingCount, lifecycleCount, nonLifecycleCount };
|
|
166
|
+
}
|
|
167
|
+
|
|
109
168
|
/** 可执行图节点类型:与配置类型对齐,避免文档里残留旧 nodeType */
|
|
110
169
|
export function resolveRunnableNodeType(
|
|
111
170
|
node: Pick<
|
|
@@ -116,6 +175,8 @@ export function resolveRunnableNodeType(
|
|
|
116
175
|
switch (resolveBlueprintConfigSource(node)) {
|
|
117
176
|
case "view":
|
|
118
177
|
return VIEW_NODE_TYPE;
|
|
178
|
+
case "event":
|
|
179
|
+
return EVENT_NODE_TYPE;
|
|
119
180
|
case "lifecycle":
|
|
120
181
|
return LIFECYCLE_NODE_TYPE;
|
|
121
182
|
case "clock":
|
|
@@ -146,6 +207,7 @@ export const BLUEPRINT_CONFIG_TYPE_LABEL_KEYS: Record<
|
|
|
146
207
|
and: "blueprint.node.typeAnd",
|
|
147
208
|
lifecycle: "blueprint.node.typeLifecycle",
|
|
148
209
|
view: "blueprint.node.typeView",
|
|
210
|
+
event: "blueprint.node.typeEvent",
|
|
149
211
|
fetch: "blueprint.node.typeFetch",
|
|
150
212
|
json: "blueprint.node.typeJson",
|
|
151
213
|
clock: "blueprint.node.typeClock",
|
|
@@ -164,6 +226,14 @@ export const BLUEPRINT_LIFECYCLE_PHASE_KEYS: Record<PageLifecyclePhase, string>
|
|
|
164
226
|
blueprintActivated: "blueprint.node.phaseBlueprintActivated",
|
|
165
227
|
};
|
|
166
228
|
|
|
229
|
+
export const BLUEPRINT_VIEW_EVENT_TYPE_KEYS: Record<ViewEventType, string> = {
|
|
230
|
+
click: "blueprint.node.eventClick",
|
|
231
|
+
dblclick: "blueprint.node.eventDblclick",
|
|
232
|
+
contextmenu: "blueprint.node.eventContextmenu",
|
|
233
|
+
mouseenter: "blueprint.node.eventMouseenter",
|
|
234
|
+
mouseleave: "blueprint.node.eventMouseleave",
|
|
235
|
+
};
|
|
236
|
+
|
|
167
237
|
function defaultTranslate(): TranslateFn {
|
|
168
238
|
return tForLocale(resolveLocale());
|
|
169
239
|
}
|
|
@@ -183,6 +253,11 @@ export function getLifecyclePhaseLabel(
|
|
|
183
253
|
return key ? t(key) : phase;
|
|
184
254
|
}
|
|
185
255
|
|
|
256
|
+
export function getViewEventTypeLabel(t: TranslateFn, eventType: ViewEventType): string {
|
|
257
|
+
const key = BLUEPRINT_VIEW_EVENT_TYPE_KEYS[eventType];
|
|
258
|
+
return key ? t(key) : eventType;
|
|
259
|
+
}
|
|
260
|
+
|
|
186
261
|
export function resolveBlueprintNodeTypeLabel(
|
|
187
262
|
node: Pick<
|
|
188
263
|
BlueprintGraphNode,
|
|
@@ -243,6 +318,15 @@ export function resolveNodeClockConfig(
|
|
|
243
318
|
});
|
|
244
319
|
}
|
|
245
320
|
|
|
321
|
+
export function resolveNodeEventConfig(
|
|
322
|
+
node: Pick<BlueprintGraphNode, "eventConfig">
|
|
323
|
+
): ViewEventNodeConfig {
|
|
324
|
+
return normalizeViewEventConfig({
|
|
325
|
+
...DEFAULT_VIEW_EVENT_NODE_CONFIG,
|
|
326
|
+
...node.eventConfig,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
246
330
|
export type BlueprintGraphEdge = {
|
|
247
331
|
id: string;
|
|
248
332
|
source: string;
|
|
@@ -275,12 +359,18 @@ const noInputTargetNodeIds = (document: BlueprintDocument) =>
|
|
|
275
359
|
.map((node) => node.id)
|
|
276
360
|
);
|
|
277
361
|
|
|
278
|
-
|
|
362
|
+
function isLifecycleSourceNode(node: BlueprintGraphNode | undefined): boolean {
|
|
363
|
+
return Boolean(node) && resolveRunnableNodeType(node!) === LIFECYCLE_NODE_TYPE;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function isEventTargetNode(node: BlueprintGraphNode | undefined): boolean {
|
|
367
|
+
return Boolean(node) && resolveBlueprintConfigSource(node!) === "event";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** 生命周期节点无输入口;事件节点输入必须来自生命周期节点 */
|
|
279
371
|
export function sanitizeBlueprintDocument(
|
|
280
372
|
document: BlueprintDocument
|
|
281
373
|
): BlueprintDocument {
|
|
282
|
-
const blockedTargets = noInputTargetNodeIds(document);
|
|
283
|
-
|
|
284
374
|
const nodes = document.nodes.map((node) => {
|
|
285
375
|
let next = node;
|
|
286
376
|
if (node.viewElementId && !node.viewElementIds?.length) {
|
|
@@ -297,6 +387,9 @@ export function sanitizeBlueprintDocument(
|
|
|
297
387
|
if (next.role === "lifecycle") {
|
|
298
388
|
return { ...next, configSource: "lifecycle" as const };
|
|
299
389
|
}
|
|
390
|
+
if (next.role === "event") {
|
|
391
|
+
return { ...next, configSource: "event" as const };
|
|
392
|
+
}
|
|
300
393
|
if (next.role === "clock") {
|
|
301
394
|
return { ...next, configSource: "clock" as const };
|
|
302
395
|
}
|
|
@@ -309,13 +402,16 @@ export function sanitizeBlueprintDocument(
|
|
|
309
402
|
if (next.role === "and") {
|
|
310
403
|
return { ...next, configSource: "and" as const };
|
|
311
404
|
}
|
|
312
|
-
if (next.configSource === "lifecycle" || next.configSource === "clock") {
|
|
405
|
+
if (next.configSource === "lifecycle" || next.configSource === "clock" || next.configSource === "event") {
|
|
313
406
|
return { ...next, configSource: undefined };
|
|
314
407
|
}
|
|
315
408
|
return next;
|
|
316
409
|
});
|
|
317
410
|
|
|
318
|
-
const edges =
|
|
411
|
+
const edges = filterInvalidBlueprintEdges(
|
|
412
|
+
{ ...document, nodes },
|
|
413
|
+
document.edges
|
|
414
|
+
);
|
|
319
415
|
|
|
320
416
|
return { ...document, nodes, edges };
|
|
321
417
|
}
|
|
@@ -325,7 +421,15 @@ export function filterInvalidBlueprintEdges(
|
|
|
325
421
|
edges: BlueprintGraphEdge[]
|
|
326
422
|
): BlueprintGraphEdge[] {
|
|
327
423
|
const blockedTargets = noInputTargetNodeIds(document);
|
|
328
|
-
|
|
424
|
+
const byId = new Map(document.nodes.map((node) => [node.id, node]));
|
|
425
|
+
return edges.filter((edge) => {
|
|
426
|
+
if (blockedTargets.has(edge.target)) return false;
|
|
427
|
+
const target = byId.get(edge.target);
|
|
428
|
+
if (isEventTargetNode(target)) {
|
|
429
|
+
return isLifecycleSourceNode(byId.get(edge.source));
|
|
430
|
+
}
|
|
431
|
+
return true;
|
|
432
|
+
});
|
|
329
433
|
}
|
|
330
434
|
|
|
331
435
|
/** 切换配置类型时同步节点 role / nodeType,确保生命周期节点渲染为无输入口 */
|
|
@@ -347,6 +451,7 @@ export function patchNodeConfigSource(
|
|
|
347
451
|
jsonConfig: undefined,
|
|
348
452
|
logicConfig: undefined,
|
|
349
453
|
clockConfig: undefined,
|
|
454
|
+
eventConfig: undefined,
|
|
350
455
|
};
|
|
351
456
|
}
|
|
352
457
|
|
|
@@ -363,6 +468,7 @@ export function patchNodeConfigSource(
|
|
|
363
468
|
fetchConfig: undefined,
|
|
364
469
|
jsonConfig: undefined,
|
|
365
470
|
logicConfig: undefined,
|
|
471
|
+
eventConfig: undefined,
|
|
366
472
|
};
|
|
367
473
|
}
|
|
368
474
|
|
|
@@ -379,6 +485,7 @@ export function patchNodeConfigSource(
|
|
|
379
485
|
jsonConfig: undefined,
|
|
380
486
|
logicConfig: undefined,
|
|
381
487
|
clockConfig: undefined,
|
|
488
|
+
eventConfig: undefined,
|
|
382
489
|
};
|
|
383
490
|
}
|
|
384
491
|
|
|
@@ -395,6 +502,7 @@ export function patchNodeConfigSource(
|
|
|
395
502
|
jsonConfig: resolveNodeJsonConfig(node),
|
|
396
503
|
logicConfig: undefined,
|
|
397
504
|
clockConfig: undefined,
|
|
505
|
+
eventConfig: undefined,
|
|
398
506
|
};
|
|
399
507
|
}
|
|
400
508
|
|
|
@@ -411,6 +519,7 @@ export function patchNodeConfigSource(
|
|
|
411
519
|
jsonConfig: undefined,
|
|
412
520
|
logicConfig: undefined,
|
|
413
521
|
clockConfig: undefined,
|
|
522
|
+
eventConfig: undefined,
|
|
414
523
|
};
|
|
415
524
|
}
|
|
416
525
|
|
|
@@ -427,6 +536,7 @@ export function patchNodeConfigSource(
|
|
|
427
536
|
jsonConfig: undefined,
|
|
428
537
|
logicConfig: resolveNodeLogicConfig(node),
|
|
429
538
|
clockConfig: undefined,
|
|
539
|
+
eventConfig: undefined,
|
|
430
540
|
};
|
|
431
541
|
}
|
|
432
542
|
|
|
@@ -443,6 +553,22 @@ export function patchNodeConfigSource(
|
|
|
443
553
|
jsonConfig: undefined,
|
|
444
554
|
logicConfig: undefined,
|
|
445
555
|
clockConfig: undefined,
|
|
556
|
+
eventConfig: undefined,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (configSource === "event") {
|
|
561
|
+
return {
|
|
562
|
+
role: "event",
|
|
563
|
+
nodeType: EVENT_NODE_TYPE,
|
|
564
|
+
configSource: "event",
|
|
565
|
+
eventConfig: resolveNodeEventConfig(node),
|
|
566
|
+
lifecyclePhase: undefined,
|
|
567
|
+
libraryBlueprintId: undefined,
|
|
568
|
+
fetchConfig: undefined,
|
|
569
|
+
jsonConfig: undefined,
|
|
570
|
+
logicConfig: undefined,
|
|
571
|
+
clockConfig: undefined,
|
|
446
572
|
};
|
|
447
573
|
}
|
|
448
574
|
|
|
@@ -456,5 +582,6 @@ export function patchNodeConfigSource(
|
|
|
456
582
|
jsonConfig: undefined,
|
|
457
583
|
logicConfig: undefined,
|
|
458
584
|
clockConfig: undefined,
|
|
585
|
+
eventConfig: undefined,
|
|
459
586
|
};
|
|
460
587
|
}
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
resolveNodeJsonConfig,
|
|
11
11
|
resolveNodeLogicConfig,
|
|
12
12
|
resolveViewElementIds,
|
|
13
|
+
resolveNodeEventConfig,
|
|
14
|
+
getViewEventTypeLabel,
|
|
13
15
|
} from "./document";
|
|
14
16
|
import type { BlueprintFlowNodeData } from "./vue-flow-adapter";
|
|
15
17
|
|
|
@@ -94,6 +96,24 @@ export function resolveBlueprintNodeSummary(
|
|
|
94
96
|
}
|
|
95
97
|
return t("blueprint.node.summaryViewMany", { count: ids.length });
|
|
96
98
|
}
|
|
99
|
+
case "event": {
|
|
100
|
+
const ids = resolveViewElementIds(data);
|
|
101
|
+
const types = resolveNodeEventConfig(data).eventTypes;
|
|
102
|
+
const typeLabel = types.map((type) => getViewEventTypeLabel(t, type)).join(" / ");
|
|
103
|
+
if (ids.length === 0) {
|
|
104
|
+
return truncate(
|
|
105
|
+
t("blueprint.node.summaryEventNoView", { events: typeLabel }),
|
|
106
|
+
44
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return truncate(
|
|
110
|
+
t("blueprint.node.summaryEvent", {
|
|
111
|
+
events: typeLabel,
|
|
112
|
+
count: ids.length,
|
|
113
|
+
}),
|
|
114
|
+
44
|
|
115
|
+
);
|
|
116
|
+
}
|
|
97
117
|
case "and":
|
|
98
118
|
return t("blueprint.node.summaryAnd");
|
|
99
119
|
default:
|