@arronqzy/vue-blueprint 0.1.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/README.md +50 -0
- package/package.json +44 -0
- package/src/BlueprintCanvasContext.ts +71 -0
- package/src/BlueprintNodeConfigSidebar.vue +338 -0
- package/src/blueprint.css +327 -0
- package/src/blueprintNodeTypes.ts +20 -0
- package/src/components/BluePrintVueRoot.vue +73 -0
- package/src/components/BlueprintCanvas.vue +220 -0
- package/src/components/BlueprintContextMenu.vue +114 -0
- package/src/components/BlueprintExecutionLogPanel.vue +294 -0
- package/src/components/BlueprintMetaDialog.vue +80 -0
- package/src/components/BlueprintNodeSwitchTaskDialog.vue +41 -0
- package/src/components/ClockNodeConfigPanel.vue +124 -0
- package/src/components/FetchNodeConfigPanel.vue +559 -0
- package/src/components/FetchUrlAutocomplete.vue +174 -0
- package/src/components/JsonNodeConfigPanel.vue +73 -0
- package/src/components/LogicNodeConfigPanel.vue +73 -0
- package/src/components/ViewElementMultiSelect.vue +50 -0
- package/src/composables/useBlueprintDebugSession.ts +441 -0
- package/src/composables/useBlueprintFlowState.ts +486 -0
- package/src/composables/useBlueprintFlowViewport.ts +65 -0
- package/src/composables/useBlueprintNodeSelectionGuard.ts +41 -0
- package/src/composables/useBlueprintPageLifecycle.ts +244 -0
- package/src/createBlueprintEdgeTypes.ts +10 -0
- package/src/edges/BlueprintSmoothEdge.vue +31 -0
- package/src/env.d.ts +7 -0
- package/src/fetch-config-task-store.ts +206 -0
- package/src/flowCoordinates.ts +19 -0
- package/src/flowDefaults.ts +9 -0
- package/src/graph/blueprint-graph.ts +265 -0
- package/src/graph/document.ts +422 -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/sync-edges.ts +69 -0
- package/src/graph/sync-nodes.ts +110 -0
- package/src/graph/vue-flow-adapter.ts +127 -0
- package/src/index.ts +37 -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/nodes/AndFlowNode.vue +60 -0
- package/src/nodes/BlueprintFlowNode.vue +26 -0
- package/src/nodes/BlueprintNodeCard.vue +155 -0
- package/src/nodes/BlueprintNodeShell.vue +70 -0
- package/src/nodes/ClockFlowNode.vue +60 -0
- package/src/nodes/FetchFlowNode.vue +26 -0
- package/src/nodes/JsonFlowNode.vue +26 -0
- package/src/nodes/LifecycleFlowNode.vue +45 -0
- package/src/nodes/LogicFlowNode.vue +26 -0
- package/src/runtime/document-to-runnable-graph.ts +51 -0
- package/src/runtime/execution-overlay.ts +169 -0
- package/src/types.ts +1 -0
- package/src/utils/cn.ts +3 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { onUnmounted, ref, shallowRef, toValue, watch, type MaybeRefOrGetter, type Ref } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
BlueprintGraphRunner,
|
|
4
|
+
detectBlueprintReferenceCycle,
|
|
5
|
+
type LibraryBlueprintResolver,
|
|
6
|
+
type PageLifecyclePhase,
|
|
7
|
+
} from "@arronqzy/blueprint-dsl";
|
|
8
|
+
|
|
9
|
+
import type { BlueprintGraph } from "../graph/blueprint-graph";
|
|
10
|
+
import { documentToRunnableGraph } from "../runtime/document-to-runnable-graph";
|
|
11
|
+
|
|
12
|
+
const PAGE_BOOT_PHASES: PageLifecyclePhase[] = [
|
|
13
|
+
"created",
|
|
14
|
+
"beforeMount",
|
|
15
|
+
"mounted",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const PAGE_TEARDOWN_PHASES: PageLifecyclePhase[] = [
|
|
19
|
+
"beforeDestroy",
|
|
20
|
+
"destroy",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
async function waitForPagePaint() {
|
|
24
|
+
if (document.readyState !== "complete") {
|
|
25
|
+
await new Promise<void>((resolve) => {
|
|
26
|
+
window.addEventListener("load", () => resolve(), { once: true });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
await new Promise<void>((resolve) => {
|
|
30
|
+
requestAnimationFrame(() => {
|
|
31
|
+
requestAnimationFrame(() => resolve());
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type UseBlueprintPageLifecycleOptions = {
|
|
37
|
+
graph: Ref<BlueprintGraph> | BlueprintGraph;
|
|
38
|
+
active?: Ref<boolean> | boolean;
|
|
39
|
+
enabled?: Ref<boolean> | boolean;
|
|
40
|
+
bootPhases?: PageLifecyclePhase[];
|
|
41
|
+
bootKey?: Ref<unknown> | unknown;
|
|
42
|
+
waitForPageReady?: boolean;
|
|
43
|
+
onUpdated?: Ref<unknown> | unknown;
|
|
44
|
+
resolveLibraryBlueprint?: LibraryBlueprintResolver;
|
|
45
|
+
libraryNameById?: MaybeRefOrGetter<ReadonlyMap<string, string>>;
|
|
46
|
+
rootLibraryBlueprintId?: MaybeRefOrGetter<string | null>;
|
|
47
|
+
onExecutionBlocked?: (message: string) => void;
|
|
48
|
+
onViewScopeUpdate?: (viewElementIds: string[], scope: unknown) => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type RunnerOptions = Pick<
|
|
52
|
+
UseBlueprintPageLifecycleOptions,
|
|
53
|
+
| "resolveLibraryBlueprint"
|
|
54
|
+
| "libraryNameById"
|
|
55
|
+
| "rootLibraryBlueprintId"
|
|
56
|
+
| "onExecutionBlocked"
|
|
57
|
+
| "onViewScopeUpdate"
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
function resolveValue<T>(value: Ref<T> | T): T {
|
|
61
|
+
return value && typeof value === "object" && "value" in value
|
|
62
|
+
? (value as Ref<T>).value
|
|
63
|
+
: (value as T);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveBlueprintName(
|
|
67
|
+
libraryNameById: ReadonlyMap<string, string> | undefined,
|
|
68
|
+
id: string
|
|
69
|
+
) {
|
|
70
|
+
return libraryNameById?.get(id) ?? id;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createRunner(graph: BlueprintGraph, options: RunnerOptions) {
|
|
74
|
+
const rootLibraryBlueprintId = toValue(options.rootLibraryBlueprintId ?? null);
|
|
75
|
+
const libraryNameById = toValue(options.libraryNameById);
|
|
76
|
+
return new BlueprintGraphRunner(
|
|
77
|
+
documentToRunnableGraph(graph.document, {
|
|
78
|
+
libraryNameById,
|
|
79
|
+
}),
|
|
80
|
+
{
|
|
81
|
+
resolveLibraryBlueprint: options.resolveLibraryBlueprint,
|
|
82
|
+
rootLibraryBlueprintId,
|
|
83
|
+
resolveBlueprintName: (id) =>
|
|
84
|
+
resolveBlueprintName(libraryNameById, id),
|
|
85
|
+
onViewScopeUpdate: options.onViewScopeUpdate,
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function emitPhases(
|
|
91
|
+
graph: BlueprintGraph,
|
|
92
|
+
phases: PageLifecyclePhase[],
|
|
93
|
+
options: RunnerOptions
|
|
94
|
+
) {
|
|
95
|
+
if (!phases.length) return;
|
|
96
|
+
|
|
97
|
+
const rootLibraryBlueprintId = toValue(options.rootLibraryBlueprintId ?? null);
|
|
98
|
+
const libraryNameById = toValue(options.libraryNameById);
|
|
99
|
+
|
|
100
|
+
if (options.resolveLibraryBlueprint) {
|
|
101
|
+
const result = await detectBlueprintReferenceCycle({
|
|
102
|
+
rootGraph: documentToRunnableGraph(graph.document, {
|
|
103
|
+
libraryNameById,
|
|
104
|
+
}),
|
|
105
|
+
rootLibraryBlueprintId,
|
|
106
|
+
resolveLibraryBlueprint: options.resolveLibraryBlueprint,
|
|
107
|
+
resolveBlueprintName: (id) =>
|
|
108
|
+
resolveBlueprintName(libraryNameById, id),
|
|
109
|
+
});
|
|
110
|
+
if (!result.ok) {
|
|
111
|
+
options.onExecutionBlocked?.(result.message);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const runner = createRunner(graph, options);
|
|
117
|
+
for (const phase of phases) {
|
|
118
|
+
await runner.emitLifecycle(phase);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function useBlueprintPageLifecycle(options: UseBlueprintPageLifecycleOptions) {
|
|
123
|
+
const graphRef = shallowRef(resolveValue(options.graph));
|
|
124
|
+
const runnerOptionsRef = shallowRef<RunnerOptions>({
|
|
125
|
+
resolveLibraryBlueprint: options.resolveLibraryBlueprint,
|
|
126
|
+
libraryNameById: options.libraryNameById,
|
|
127
|
+
rootLibraryBlueprintId: options.rootLibraryBlueprintId,
|
|
128
|
+
onExecutionBlocked: options.onExecutionBlocked,
|
|
129
|
+
onViewScopeUpdate: options.onViewScopeUpdate,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const bootPhases = options.bootPhases ?? PAGE_BOOT_PHASES;
|
|
133
|
+
const bootPhasesKey = bootPhases.join("|");
|
|
134
|
+
const bootTrigger = options.bootKey ?? "__initial__";
|
|
135
|
+
const bootCompleted = ref(false);
|
|
136
|
+
const hadBoot = ref(false);
|
|
137
|
+
const prevActive = ref<boolean | undefined>(undefined);
|
|
138
|
+
let bootCancelled = false;
|
|
139
|
+
|
|
140
|
+
watch(
|
|
141
|
+
() => resolveValue(options.graph),
|
|
142
|
+
(next) => {
|
|
143
|
+
graphRef.value = next;
|
|
144
|
+
},
|
|
145
|
+
{ deep: true }
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
watch(
|
|
149
|
+
() => ({
|
|
150
|
+
resolveLibraryBlueprint: options.resolveLibraryBlueprint,
|
|
151
|
+
libraryNameById: options.libraryNameById,
|
|
152
|
+
rootLibraryBlueprintId: options.rootLibraryBlueprintId,
|
|
153
|
+
onExecutionBlocked: options.onExecutionBlocked,
|
|
154
|
+
onViewScopeUpdate: options.onViewScopeUpdate,
|
|
155
|
+
}),
|
|
156
|
+
(next) => {
|
|
157
|
+
runnerOptionsRef.value = next;
|
|
158
|
+
},
|
|
159
|
+
{ deep: true }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const runBootCycle = async () => {
|
|
163
|
+
const enabled = resolveValue(options.enabled ?? true);
|
|
164
|
+
if (!enabled) {
|
|
165
|
+
bootCompleted.value = false;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
bootCancelled = false;
|
|
170
|
+
const waitForPageReady = options.waitForPageReady ?? false;
|
|
171
|
+
|
|
172
|
+
if (waitForPageReady) {
|
|
173
|
+
await waitForPagePaint();
|
|
174
|
+
}
|
|
175
|
+
if (bootCancelled) return;
|
|
176
|
+
|
|
177
|
+
await emitPhases(graphRef.value, bootPhases, runnerOptionsRef.value);
|
|
178
|
+
if (bootCancelled) return;
|
|
179
|
+
|
|
180
|
+
const active = resolveValue(options.active ?? true);
|
|
181
|
+
if (active) {
|
|
182
|
+
await emitPhases(graphRef.value, ["activated"], runnerOptionsRef.value);
|
|
183
|
+
}
|
|
184
|
+
if (bootCancelled) return;
|
|
185
|
+
|
|
186
|
+
hadBoot.value = true;
|
|
187
|
+
prevActive.value = active;
|
|
188
|
+
bootCompleted.value = true;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const teardownBoot = () => {
|
|
192
|
+
bootCancelled = true;
|
|
193
|
+
bootCompleted.value = false;
|
|
194
|
+
prevActive.value = undefined;
|
|
195
|
+
if (hadBoot.value) {
|
|
196
|
+
hadBoot.value = false;
|
|
197
|
+
void emitPhases(graphRef.value, PAGE_TEARDOWN_PHASES, runnerOptionsRef.value);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
watch(
|
|
202
|
+
() => [
|
|
203
|
+
bootPhasesKey,
|
|
204
|
+
resolveValue(bootTrigger),
|
|
205
|
+
resolveValue(options.enabled ?? true),
|
|
206
|
+
options.waitForPageReady ?? false,
|
|
207
|
+
],
|
|
208
|
+
() => {
|
|
209
|
+
teardownBoot();
|
|
210
|
+
void runBootCycle();
|
|
211
|
+
},
|
|
212
|
+
{ immediate: true }
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
watch(
|
|
216
|
+
() => [resolveValue(options.active ?? true), bootCompleted.value, resolveValue(options.enabled ?? true)],
|
|
217
|
+
([active, completed, enabled]) => {
|
|
218
|
+
if (!enabled || !completed) return;
|
|
219
|
+
const prev = prevActive.value;
|
|
220
|
+
if (prev === undefined || prev === active) {
|
|
221
|
+
prevActive.value = active;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
prevActive.value = active;
|
|
225
|
+
void emitPhases(
|
|
226
|
+
graphRef.value,
|
|
227
|
+
[active ? "activated" : "deactivated"],
|
|
228
|
+
runnerOptionsRef.value
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
watch(
|
|
234
|
+
() => [bootCompleted.value, resolveValue(options.enabled ?? true), resolveValue(options.onUpdated)],
|
|
235
|
+
([completed, enabled]) => {
|
|
236
|
+
if (!enabled || !completed) return;
|
|
237
|
+
void emitPhases(graphRef.value, ["updated"], runnerOptionsRef.value);
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
onUnmounted(() => {
|
|
242
|
+
teardownBoot();
|
|
243
|
+
});
|
|
244
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { markRaw, type Component } from "vue";
|
|
2
|
+
|
|
3
|
+
import BlueprintSmoothEdge from "./edges/BlueprintSmoothEdge.vue";
|
|
4
|
+
import { BP_FLOW_EDGE_TYPE } from "./graph/sync-edges";
|
|
5
|
+
|
|
6
|
+
export function createBlueprintEdgeTypes(): Record<string, Component> {
|
|
7
|
+
return {
|
|
8
|
+
[BP_FLOW_EDGE_TYPE]: markRaw(BlueprintSmoothEdge),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { BaseEdge, getSmoothStepPath, type EdgeProps } from "@vue-flow/core";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
|
|
5
|
+
const EDGE_STROKE = "#2563eb";
|
|
6
|
+
const EDGE_WIDTH = 2.5;
|
|
7
|
+
|
|
8
|
+
const props = defineProps<EdgeProps>();
|
|
9
|
+
|
|
10
|
+
const path = computed(() => {
|
|
11
|
+
const [edgePath] = getSmoothStepPath({
|
|
12
|
+
sourceX: props.sourceX,
|
|
13
|
+
sourceY: props.sourceY,
|
|
14
|
+
sourcePosition: props.sourcePosition,
|
|
15
|
+
targetX: props.targetX,
|
|
16
|
+
targetY: props.targetY,
|
|
17
|
+
targetPosition: props.targetPosition,
|
|
18
|
+
});
|
|
19
|
+
return edgePath;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const edgeStyle = computed(() => ({
|
|
23
|
+
stroke: EDGE_STROKE,
|
|
24
|
+
strokeWidth: EDGE_WIDTH,
|
|
25
|
+
...(props.style as Record<string, unknown> | undefined),
|
|
26
|
+
}));
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<BaseEdge :path="path" class="bp-edge-visible" :style="edgeStyle" />
|
|
31
|
+
</template>
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { onUnmounted, ref, type Ref } from "vue";
|
|
2
|
+
import type {
|
|
3
|
+
FetchRequestConfig,
|
|
4
|
+
FetchResultValue,
|
|
5
|
+
ParsedSwaggerDocument,
|
|
6
|
+
} from "@arronqzy/blueprint-dsl";
|
|
7
|
+
import { executeFetch } from "@arronqzy/blueprint-dsl";
|
|
8
|
+
|
|
9
|
+
import { loadSwaggerDocument } from "./library/swagger-docs";
|
|
10
|
+
|
|
11
|
+
export type SwaggerLoadTaskStatus = "idle" | "loading" | "error";
|
|
12
|
+
|
|
13
|
+
export type SwaggerLoadTaskRecord = {
|
|
14
|
+
status: SwaggerLoadTaskStatus;
|
|
15
|
+
error?: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type TaskEntry = {
|
|
19
|
+
record: SwaggerLoadTaskRecord;
|
|
20
|
+
abortController: AbortController;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const IDLE_SWAGGER_LOAD_TASK: SwaggerLoadTaskRecord = { status: "idle" };
|
|
24
|
+
const LOADING_SWAGGER_LOAD_TASK: SwaggerLoadTaskRecord = { status: "loading" };
|
|
25
|
+
|
|
26
|
+
const swaggerTasks = new Map<string, TaskEntry>();
|
|
27
|
+
const fetchDebugTasks = new Map<string, FetchDebugTaskEntry>();
|
|
28
|
+
const listeners = new Set<() => void>();
|
|
29
|
+
|
|
30
|
+
export type FetchDebugTaskStatus = "idle" | "loading" | "success" | "error";
|
|
31
|
+
|
|
32
|
+
export type FetchDebugTaskRecord = {
|
|
33
|
+
status: FetchDebugTaskStatus;
|
|
34
|
+
result?: FetchResultValue;
|
|
35
|
+
error?: string | null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type FetchDebugTaskEntry = {
|
|
39
|
+
record: FetchDebugTaskRecord;
|
|
40
|
+
abortController: AbortController;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const IDLE_FETCH_DEBUG_TASK: FetchDebugTaskRecord = { status: "idle" };
|
|
44
|
+
const LOADING_FETCH_DEBUG_TASK: FetchDebugTaskRecord = { status: "loading" };
|
|
45
|
+
|
|
46
|
+
function emit() {
|
|
47
|
+
listeners.forEach((listener) => listener());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function subscribeFetchConfigTasks(listener: () => void) {
|
|
51
|
+
listeners.add(listener);
|
|
52
|
+
return () => listeners.delete(listener);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getSwaggerLoadTask(nodeId: string): SwaggerLoadTaskRecord {
|
|
56
|
+
return swaggerTasks.get(nodeId)?.record ?? IDLE_SWAGGER_LOAD_TASK;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getFetchDebugTask(nodeId: string): FetchDebugTaskRecord {
|
|
60
|
+
return fetchDebugTasks.get(nodeId)?.record ?? IDLE_FETCH_DEBUG_TASK;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function findActiveSwaggerLoadTaskNodeId(): string | null {
|
|
64
|
+
for (const [nodeId, task] of swaggerTasks) {
|
|
65
|
+
if (task.record.status === "loading") return nodeId;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function findActiveFetchDebugTaskNodeId(): string | null {
|
|
71
|
+
for (const [nodeId, task] of fetchDebugTasks) {
|
|
72
|
+
if (task.record.status === "loading") return nodeId;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function findActiveFetchConfigTaskNodeId(): string | null {
|
|
78
|
+
return findActiveSwaggerLoadTaskNodeId() ?? findActiveFetchDebugTaskNodeId();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isSwaggerLoadTaskActive(nodeId: string): boolean {
|
|
82
|
+
return getSwaggerLoadTask(nodeId).status === "loading";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isFetchDebugTaskActive(nodeId: string): boolean {
|
|
86
|
+
return getFetchDebugTask(nodeId).status === "loading";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function cancelSwaggerLoadTask(nodeId: string) {
|
|
90
|
+
const task = swaggerTasks.get(nodeId);
|
|
91
|
+
if (!task) return;
|
|
92
|
+
task.abortController.abort();
|
|
93
|
+
swaggerTasks.delete(nodeId);
|
|
94
|
+
emit();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function cancelFetchDebugTask(nodeId: string) {
|
|
98
|
+
const task = fetchDebugTasks.get(nodeId);
|
|
99
|
+
if (!task) return;
|
|
100
|
+
task.abortController.abort();
|
|
101
|
+
fetchDebugTasks.delete(nodeId);
|
|
102
|
+
emit();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function useFetchConfigTaskSnapshot<T>(
|
|
106
|
+
getSnapshot: () => T
|
|
107
|
+
): Ref<T> {
|
|
108
|
+
const snapshot = ref(getSnapshot()) as Ref<T>;
|
|
109
|
+
const update = () => {
|
|
110
|
+
snapshot.value = getSnapshot();
|
|
111
|
+
};
|
|
112
|
+
const unsubscribe = subscribeFetchConfigTasks(update);
|
|
113
|
+
onUnmounted(unsubscribe);
|
|
114
|
+
return snapshot;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function useSwaggerLoadTask(nodeId: string) {
|
|
118
|
+
return useFetchConfigTaskSnapshot(() => getSwaggerLoadTask(nodeId));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function useFetchDebugTask(nodeId: string) {
|
|
122
|
+
return useFetchConfigTaskSnapshot(() => getFetchDebugTask(nodeId));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function startSwaggerLoadTask(params: {
|
|
126
|
+
nodeId: string;
|
|
127
|
+
docsUrl: string;
|
|
128
|
+
onSuccess: (parsed: ParsedSwaggerDocument, docsUrl: string) => void;
|
|
129
|
+
}) {
|
|
130
|
+
cancelSwaggerLoadTask(params.nodeId);
|
|
131
|
+
|
|
132
|
+
const abortController = new AbortController();
|
|
133
|
+
swaggerTasks.set(params.nodeId, {
|
|
134
|
+
record: LOADING_SWAGGER_LOAD_TASK,
|
|
135
|
+
abortController,
|
|
136
|
+
});
|
|
137
|
+
emit();
|
|
138
|
+
|
|
139
|
+
void (async () => {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = await loadSwaggerDocument(params.docsUrl, abortController.signal);
|
|
142
|
+
if (abortController.signal.aborted) return;
|
|
143
|
+
|
|
144
|
+
swaggerTasks.delete(params.nodeId);
|
|
145
|
+
emit();
|
|
146
|
+
params.onSuccess(parsed, params.docsUrl);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (abortController.signal.aborted) {
|
|
149
|
+
swaggerTasks.delete(params.nodeId);
|
|
150
|
+
emit();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const message =
|
|
155
|
+
error instanceof Error ? error.message : "Swagger 文档解析失败";
|
|
156
|
+
swaggerTasks.set(params.nodeId, {
|
|
157
|
+
record: { status: "error", error: message },
|
|
158
|
+
abortController,
|
|
159
|
+
});
|
|
160
|
+
emit();
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function startFetchDebugTask(params: {
|
|
166
|
+
nodeId: string;
|
|
167
|
+
config: FetchRequestConfig;
|
|
168
|
+
}) {
|
|
169
|
+
cancelFetchDebugTask(params.nodeId);
|
|
170
|
+
|
|
171
|
+
const abortController = new AbortController();
|
|
172
|
+
fetchDebugTasks.set(params.nodeId, {
|
|
173
|
+
record: LOADING_FETCH_DEBUG_TASK,
|
|
174
|
+
abortController,
|
|
175
|
+
});
|
|
176
|
+
emit();
|
|
177
|
+
|
|
178
|
+
void (async () => {
|
|
179
|
+
try {
|
|
180
|
+
const result = await executeFetch(params.config, {
|
|
181
|
+
signal: abortController.signal,
|
|
182
|
+
allowHttpError: true,
|
|
183
|
+
});
|
|
184
|
+
if (abortController.signal.aborted) return;
|
|
185
|
+
|
|
186
|
+
fetchDebugTasks.set(params.nodeId, {
|
|
187
|
+
record: { status: "success", result },
|
|
188
|
+
abortController,
|
|
189
|
+
});
|
|
190
|
+
emit();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (abortController.signal.aborted) {
|
|
193
|
+
fetchDebugTasks.delete(params.nodeId);
|
|
194
|
+
emit();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const message = error instanceof Error ? error.message : "请求失败";
|
|
199
|
+
fetchDebugTasks.set(params.nodeId, {
|
|
200
|
+
record: { status: "error", error: message },
|
|
201
|
+
abortController,
|
|
202
|
+
});
|
|
203
|
+
emit();
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { XYPosition } from "@vue-flow/core";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BP_FLOW_NODE_HEIGHT,
|
|
5
|
+
BP_FLOW_NODE_WIDTH,
|
|
6
|
+
} from "./graph/vue-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 "@vue-flow/core";
|
|
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;
|