@arronqzy/react-blueprint 1.0.1 → 1.0.4
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 +103 -0
- package/index.ts +2 -1
- package/package.json +7 -6
- package/src/BlueprintContextMenu.tsx +5 -3
- package/src/BlueprintNodeConfigSidebar.tsx +59 -57
- package/src/blueprint.css +21 -15
- package/src/components/BlueprintExecutionLogPanel.tsx +32 -30
- package/src/components/BlueprintLibrarySelect.tsx +11 -4
- package/src/components/BlueprintMetaDialog.tsx +20 -10
- package/src/components/BlueprintNodeSwitchTaskDialog.tsx +14 -8
- package/src/components/BlueprintPanelToolbar.tsx +50 -42
- package/src/components/ClockNodeConfigPanel.tsx +19 -12
- package/src/components/FetchNodeConfigPanel.tsx +40 -34
- package/src/components/FetchUrlAutocomplete.tsx +3 -1
- package/src/components/JsonNodeConfigPanel.tsx +15 -7
- package/src/components/LogicNodeConfigPanel.tsx +13 -7
- package/src/components/ViewElementMultiSelect.tsx +25 -18
- package/src/dev-seed.ts +2 -2
- package/src/fetch-config-task-store.ts +8 -2
- package/src/graph/blueprint-graph.ts +5 -1
- package/src/graph/document.ts +97 -22
- package/src/graph/node-summary.ts +32 -18
- package/src/graph/sync-nodes.ts +3 -1
- package/src/hooks/useBlueprintDebugSession.ts +24 -22
- package/src/hooks/useBlueprintFlowColorMode.ts +30 -6
- package/src/hooks/useBlueprintPageLifecycle.ts +29 -1
- package/src/library/blueprint-io.ts +12 -3
- package/src/library/swagger-docs.ts +15 -5
- package/src/main.tsx +12 -5
- package/src/nodes/AndFlowNode.tsx +7 -5
- package/src/nodes/BlueprintFlowNode.tsx +4 -2
- package/src/nodes/BlueprintNodeCard.tsx +4 -2
- package/src/nodes/BlueprintNodeShell.tsx +4 -2
- package/src/nodes/ClockFlowNode.tsx +5 -3
- package/src/nodes/FetchFlowNode.tsx +4 -2
- package/src/nodes/JsonFlowNode.tsx +4 -2
- package/src/nodes/LifecycleFlowNode.tsx +5 -3
- package/src/nodes/LogicFlowNode.tsx +4 -2
- package/src/runtime/document-to-runnable-graph.ts +9 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState, type ChangeEvent } from "react";
|
|
2
2
|
import { validateJsonString } from "@arronqzy/blueprint-dsl";
|
|
3
3
|
import type { JsonNodeConfig } from "@arronqzy/blueprint-dsl";
|
|
4
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
4
5
|
|
|
5
6
|
import type { BlueprintGraphNode } from "../graph/document";
|
|
6
7
|
import { resolveNodeJsonConfig } from "../graph/document";
|
|
@@ -27,6 +28,7 @@ export function JsonNodeConfigPanel({
|
|
|
27
28
|
node,
|
|
28
29
|
onUpdateNode,
|
|
29
30
|
}: JsonNodeConfigPanelProps) {
|
|
31
|
+
const { t } = useI18n();
|
|
30
32
|
const jsonConfig = resolveNodeJsonConfig(node);
|
|
31
33
|
const [draftError, setDraftError] = useState<string | null>(null);
|
|
32
34
|
|
|
@@ -51,15 +53,17 @@ export function JsonNodeConfigPanel({
|
|
|
51
53
|
|
|
52
54
|
return (
|
|
53
55
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
54
|
-
<div className="font-medium text-foreground">
|
|
56
|
+
<div className="font-medium text-foreground">
|
|
57
|
+
{t("blueprint.config.jsonTitle")}
|
|
58
|
+
</div>
|
|
55
59
|
<p className="text-[11px] text-muted-foreground">
|
|
56
|
-
|
|
57
|
-
array)并从输出口发出<strong>真信号</strong>;解析失败则发出
|
|
58
|
-
<strong>假信号</strong>。
|
|
60
|
+
{t("blueprint.config.jsonHint")}
|
|
59
61
|
</p>
|
|
60
62
|
|
|
61
63
|
<label className="block space-y-1">
|
|
62
|
-
<span className="text-muted-foreground">
|
|
64
|
+
<span className="text-muted-foreground">
|
|
65
|
+
{t("blueprint.config.jsonContent")}
|
|
66
|
+
</span>
|
|
63
67
|
<textarea
|
|
64
68
|
value={jsonConfig.jsonString}
|
|
65
69
|
onChange={handleChange}
|
|
@@ -69,9 +73,13 @@ export function JsonNodeConfigPanel({
|
|
|
69
73
|
placeholder={'{\n "key": "value"\n}'}
|
|
70
74
|
/>
|
|
71
75
|
{parseError ? (
|
|
72
|
-
<p className="text-[11px] text-destructive">
|
|
76
|
+
<p className="text-[11px] text-destructive">
|
|
77
|
+
{t("blueprint.config.jsonFormatError", { error: parseError })}
|
|
78
|
+
</p>
|
|
73
79
|
) : (
|
|
74
|
-
<p className="text-[11px] text-muted-foreground">
|
|
80
|
+
<p className="text-[11px] text-muted-foreground">
|
|
81
|
+
{t("blueprint.config.jsonFormatOk")}
|
|
82
|
+
</p>
|
|
75
83
|
)}
|
|
76
84
|
</label>
|
|
77
85
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState, type ChangeEvent } from "react";
|
|
2
2
|
import { validateLogicSourceCode } from "@arronqzy/blueprint-dsl";
|
|
3
3
|
import type { LogicNodeConfig } from "@arronqzy/blueprint-dsl";
|
|
4
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
4
5
|
|
|
5
6
|
import type { BlueprintGraphNode } from "../graph/document";
|
|
6
7
|
import { resolveNodeLogicConfig } from "../graph/document";
|
|
@@ -27,6 +28,7 @@ export function LogicNodeConfigPanel({
|
|
|
27
28
|
node,
|
|
28
29
|
onUpdateNode,
|
|
29
30
|
}: LogicNodeConfigPanelProps) {
|
|
31
|
+
const { t } = useI18n();
|
|
30
32
|
const logicConfig = resolveNodeLogicConfig(node);
|
|
31
33
|
const [draftError, setDraftError] = useState<string | null>(null);
|
|
32
34
|
|
|
@@ -50,15 +52,17 @@ export function LogicNodeConfigPanel({
|
|
|
50
52
|
|
|
51
53
|
return (
|
|
52
54
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
53
|
-
<div className="font-medium text-foreground"
|
|
55
|
+
<div className="font-medium text-foreground">
|
|
56
|
+
{t("blueprint.config.logicTitle")}
|
|
57
|
+
</div>
|
|
54
58
|
<p className="text-[11px] text-muted-foreground">
|
|
55
|
-
|
|
56
|
-
函数并执行;返回值作为<strong>真信号</strong>输出。代码语法错误或运行时报错则发出
|
|
57
|
-
<strong>假信号</strong>(错误信息为输出值)。
|
|
59
|
+
{t("blueprint.config.logicHint")}
|
|
58
60
|
</p>
|
|
59
61
|
|
|
60
62
|
<label className="block space-y-1">
|
|
61
|
-
<span className="text-muted-foreground">
|
|
63
|
+
<span className="text-muted-foreground">
|
|
64
|
+
{t("blueprint.config.jsCode")}
|
|
65
|
+
</span>
|
|
62
66
|
<textarea
|
|
63
67
|
value={logicConfig.sourceCode}
|
|
64
68
|
onChange={handleChange}
|
|
@@ -69,10 +73,12 @@ export function LogicNodeConfigPanel({
|
|
|
69
73
|
/>
|
|
70
74
|
{parseError ? (
|
|
71
75
|
<p className="text-[11px] text-destructive">
|
|
72
|
-
|
|
76
|
+
{t("blueprint.config.jsError", { error: parseError })}
|
|
73
77
|
</p>
|
|
74
78
|
) : (
|
|
75
|
-
<p className="text-[11px] text-muted-foreground">
|
|
79
|
+
<p className="text-[11px] text-muted-foreground">
|
|
80
|
+
{t("blueprint.config.jsSyntaxOk")}
|
|
81
|
+
</p>
|
|
76
82
|
)}
|
|
77
83
|
</label>
|
|
78
84
|
</div>
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
PopoverContent,
|
|
13
13
|
PopoverTrigger,
|
|
14
14
|
} from "@arronqzy/ui";
|
|
15
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
15
16
|
|
|
16
17
|
export type ViewElementMultiSelectOption = {
|
|
17
18
|
id: string;
|
|
@@ -45,35 +46,38 @@ function ChevronDownIcon({ className }: { className?: string }) {
|
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function resolveTriggerText(
|
|
49
|
-
value: string[],
|
|
50
|
-
labelById: Map<string, string>,
|
|
51
|
-
placeholder: string
|
|
52
|
-
): string {
|
|
53
|
-
if (value.length === 0) return placeholder;
|
|
54
|
-
const labels = value.map((id) => labelById.get(id) ?? id);
|
|
55
|
-
if (labels.length === 1) return labels[0]!;
|
|
56
|
-
const joined = labels.join("、");
|
|
57
|
-
if (joined.length <= 26) return joined;
|
|
58
|
-
return `已选 ${value.length} 项`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
49
|
export function ViewElementMultiSelect({
|
|
62
50
|
options,
|
|
63
51
|
value,
|
|
64
52
|
onChange,
|
|
65
|
-
placeholder
|
|
53
|
+
placeholder,
|
|
66
54
|
disabled = false,
|
|
67
55
|
}: ViewElementMultiSelectProps) {
|
|
56
|
+
const { t } = useI18n();
|
|
68
57
|
const [open, setOpen] = useState(false);
|
|
58
|
+
const resolvedPlaceholder =
|
|
59
|
+
placeholder ?? t("blueprint.config.selectViewNode");
|
|
69
60
|
const labelById = useMemo(
|
|
70
61
|
() => new Map(options.map((opt) => [opt.id, opt.label])),
|
|
71
62
|
[options]
|
|
72
63
|
);
|
|
73
64
|
const selectedSet = useMemo(() => new Set(value), [value]);
|
|
74
|
-
const triggerText = resolveTriggerText(value, labelById, placeholder);
|
|
75
65
|
const emptyOptions = options.length === 0;
|
|
76
66
|
|
|
67
|
+
let triggerText = resolvedPlaceholder;
|
|
68
|
+
if (value.length > 0) {
|
|
69
|
+
const labels = value.map((id) => labelById.get(id) ?? id);
|
|
70
|
+
if (labels.length === 1) {
|
|
71
|
+
triggerText = labels[0]!;
|
|
72
|
+
} else {
|
|
73
|
+
const joined = labels.join("、");
|
|
74
|
+
triggerText =
|
|
75
|
+
joined.length <= 26
|
|
76
|
+
? joined
|
|
77
|
+
: t("blueprint.config.selectedCount", { count: value.length });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
const toggle = (id: string) => {
|
|
78
82
|
onChange(
|
|
79
83
|
selectedSet.has(id)
|
|
@@ -97,7 +101,7 @@ export function ViewElementMultiSelect({
|
|
|
97
101
|
)}
|
|
98
102
|
>
|
|
99
103
|
<span className="min-w-0 flex-1 truncate text-left">
|
|
100
|
-
{emptyOptions ? "
|
|
104
|
+
{emptyOptions ? t("blueprint.config.noViewOnCanvas") : triggerText}
|
|
101
105
|
</span>
|
|
102
106
|
<ChevronDownIcon className="h-4 w-4 shrink-0 opacity-50" />
|
|
103
107
|
</Button>
|
|
@@ -107,9 +111,12 @@ export function ViewElementMultiSelect({
|
|
|
107
111
|
align="start"
|
|
108
112
|
>
|
|
109
113
|
<Command>
|
|
110
|
-
<CommandInput
|
|
114
|
+
<CommandInput
|
|
115
|
+
placeholder={t("blueprint.config.searchViewNodes")}
|
|
116
|
+
className="h-9 text-xs"
|
|
117
|
+
/>
|
|
111
118
|
<CommandList>
|
|
112
|
-
<CommandEmpty
|
|
119
|
+
<CommandEmpty>{t("blueprint.config.noMatchingNodes")}</CommandEmpty>
|
|
113
120
|
<CommandGroup>
|
|
114
121
|
{options.map((opt) => {
|
|
115
122
|
const selected = selectedSet.has(opt.id);
|
package/src/dev-seed.ts
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
/** DevApp 初始图:两个节点 + 一条连线,便于直接验证边是否渲染 */
|
|
12
12
|
export function createDevBlueprintGraph(): BlueprintGraph {
|
|
13
13
|
let graph = BlueprintGraph.empty();
|
|
14
|
-
graph = graph.addBlueprintNode({ x: 40, y: 80 }, "
|
|
14
|
+
graph = graph.addBlueprintNode({ x: 40, y: 80 }, "Node A");
|
|
15
15
|
const nodeA = graph.document.nodes[0]!;
|
|
16
|
-
graph = graph.addBlueprintNode({ x: 300, y: 80 }, "
|
|
16
|
+
graph = graph.addBlueprintNode({ x: 300, y: 80 }, "Node B");
|
|
17
17
|
const nodeB = graph.document.nodes[1]!;
|
|
18
18
|
|
|
19
19
|
const withEdge = addEdge(
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ParsedSwaggerDocument,
|
|
7
7
|
} from "@arronqzy/blueprint-dsl";
|
|
8
8
|
import { executeFetch } from "@arronqzy/blueprint-dsl";
|
|
9
|
+
import { resolveLocale, tForLocale } from "@arronqzy/i18n";
|
|
9
10
|
|
|
10
11
|
import { loadSwaggerDocument } from "./library/swagger-docs";
|
|
11
12
|
|
|
@@ -143,7 +144,9 @@ export function startSwaggerLoadTask(params: {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
const message =
|
|
146
|
-
error instanceof Error
|
|
147
|
+
error instanceof Error
|
|
148
|
+
? error.message
|
|
149
|
+
: tForLocale(resolveLocale())("blueprint.config.swaggerParseFailed");
|
|
147
150
|
swaggerTasks.set(params.nodeId, {
|
|
148
151
|
record: { status: "error", error: message },
|
|
149
152
|
abortController,
|
|
@@ -186,7 +189,10 @@ export function startFetchDebugTask(params: {
|
|
|
186
189
|
return;
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
const message =
|
|
192
|
+
const message =
|
|
193
|
+
error instanceof Error
|
|
194
|
+
? error.message
|
|
195
|
+
: tForLocale(resolveLocale())("blueprint.config.requestFailed");
|
|
190
196
|
fetchDebugTasks.set(params.nodeId, {
|
|
191
197
|
record: { status: "error", error: message },
|
|
192
198
|
abortController,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveNodeFetchConfig,
|
|
9
9
|
resolveNodeJsonConfig,
|
|
10
10
|
resolveNodeLogicConfig,
|
|
11
|
+
resolveRunnableNodeType,
|
|
11
12
|
sanitizeBlueprintDocument,
|
|
12
13
|
type BlueprintDocument,
|
|
13
14
|
type BlueprintGraphEdge,
|
|
@@ -250,7 +251,10 @@ export class BlueprintGraph {
|
|
|
250
251
|
...patch.clockConfig,
|
|
251
252
|
};
|
|
252
253
|
}
|
|
253
|
-
return
|
|
254
|
+
return {
|
|
255
|
+
...nextNode,
|
|
256
|
+
nodeType: resolveRunnableNodeType(nextNode),
|
|
257
|
+
};
|
|
254
258
|
}),
|
|
255
259
|
});
|
|
256
260
|
}
|
package/src/graph/document.ts
CHANGED
|
@@ -89,48 +89,116 @@ export function pruneViewElementIds(
|
|
|
89
89
|
export function resolveBlueprintConfigSource(
|
|
90
90
|
node: Pick<
|
|
91
91
|
BlueprintGraphNode,
|
|
92
|
-
"role" | "configSource" | "viewElementId" | "viewElementIds"
|
|
92
|
+
"role" | "nodeType" | "configSource" | "viewElementId" | "viewElementIds"
|
|
93
93
|
>
|
|
94
94
|
): BlueprintConfigSource {
|
|
95
95
|
if (node.configSource) return node.configSource;
|
|
96
|
-
if (resolveViewElementIds(node).length > 0) return "view";
|
|
97
96
|
if (node.role === "lifecycle") return "lifecycle";
|
|
98
97
|
if (node.role === "clock") return "clock";
|
|
99
98
|
if (node.role === "and") return "and";
|
|
100
99
|
if (node.role === "fetch") return "fetch";
|
|
101
100
|
if (node.role === "json") return "json";
|
|
101
|
+
if (node.nodeType === FETCH_NODE_TYPE) return "fetch";
|
|
102
|
+
if (node.nodeType === JSON_NODE_TYPE) return "json";
|
|
103
|
+
if (node.nodeType === CLOCK_NODE_TYPE) return "clock";
|
|
104
|
+
if (node.nodeType === VIEW_NODE_TYPE) return "view";
|
|
105
|
+
if (resolveViewElementIds(node).length > 0) return "view";
|
|
102
106
|
return node.role === "logic" ? "logic" : "blueprint";
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
/**
|
|
106
|
-
export
|
|
109
|
+
/** 可执行图节点类型:与配置类型对齐,避免文档里残留旧 nodeType */
|
|
110
|
+
export function resolveRunnableNodeType(
|
|
111
|
+
node: Pick<
|
|
112
|
+
BlueprintGraphNode,
|
|
113
|
+
"role" | "nodeType" | "configSource" | "viewElementId" | "viewElementIds"
|
|
114
|
+
>
|
|
115
|
+
): string {
|
|
116
|
+
switch (resolveBlueprintConfigSource(node)) {
|
|
117
|
+
case "view":
|
|
118
|
+
return VIEW_NODE_TYPE;
|
|
119
|
+
case "lifecycle":
|
|
120
|
+
return LIFECYCLE_NODE_TYPE;
|
|
121
|
+
case "clock":
|
|
122
|
+
return CLOCK_NODE_TYPE;
|
|
123
|
+
case "and":
|
|
124
|
+
return AND_NODE_TYPE;
|
|
125
|
+
case "fetch":
|
|
126
|
+
return FETCH_NODE_TYPE;
|
|
127
|
+
case "json":
|
|
128
|
+
return JSON_NODE_TYPE;
|
|
129
|
+
case "logic":
|
|
130
|
+
return DEFAULT_LOGIC_NODE_TYPE;
|
|
131
|
+
default:
|
|
132
|
+
return BLUEPRINT_NODE_TYPE;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
import type { TranslateFn } from "@arronqzy/i18n";
|
|
137
|
+
import { resolveLocale, tForLocale } from "@arronqzy/i18n";
|
|
138
|
+
|
|
139
|
+
/** 画布节点顶部(拖拽区)配置类型对应的 i18n key */
|
|
140
|
+
export const BLUEPRINT_CONFIG_TYPE_LABEL_KEYS: Record<
|
|
107
141
|
BlueprintConfigSource,
|
|
108
142
|
string
|
|
109
143
|
> = {
|
|
110
|
-
blueprint: "
|
|
111
|
-
logic: "
|
|
112
|
-
and: "
|
|
113
|
-
lifecycle: "
|
|
114
|
-
view: "
|
|
115
|
-
fetch: "
|
|
116
|
-
json: "
|
|
117
|
-
clock: "
|
|
144
|
+
blueprint: "blueprint.node.typeBlueprint",
|
|
145
|
+
logic: "blueprint.node.typeLogic",
|
|
146
|
+
and: "blueprint.node.typeAnd",
|
|
147
|
+
lifecycle: "blueprint.node.typeLifecycle",
|
|
148
|
+
view: "blueprint.node.typeView",
|
|
149
|
+
fetch: "blueprint.node.typeFetch",
|
|
150
|
+
json: "blueprint.node.typeJson",
|
|
151
|
+
clock: "blueprint.node.typeClock",
|
|
118
152
|
};
|
|
119
153
|
|
|
154
|
+
export const BLUEPRINT_LIFECYCLE_PHASE_KEYS: Record<PageLifecyclePhase, string> =
|
|
155
|
+
{
|
|
156
|
+
created: "blueprint.node.phaseCreated",
|
|
157
|
+
beforeMount: "blueprint.node.phaseBeforeMount",
|
|
158
|
+
mounted: "blueprint.node.phaseMounted",
|
|
159
|
+
updated: "blueprint.node.phaseUpdated",
|
|
160
|
+
beforeDestroy: "blueprint.node.phaseBeforeDestroy",
|
|
161
|
+
destroy: "blueprint.node.phaseDestroy",
|
|
162
|
+
activated: "blueprint.node.phaseActivated",
|
|
163
|
+
deactivated: "blueprint.node.phaseDeactivated",
|
|
164
|
+
blueprintActivated: "blueprint.node.phaseBlueprintActivated",
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function defaultTranslate(): TranslateFn {
|
|
168
|
+
return tForLocale(resolveLocale());
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getBlueprintNodeTypeLabel(
|
|
172
|
+
t: TranslateFn,
|
|
173
|
+
type: BlueprintConfigSource
|
|
174
|
+
): string {
|
|
175
|
+
return t(BLUEPRINT_CONFIG_TYPE_LABEL_KEYS[type]);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getLifecyclePhaseLabel(
|
|
179
|
+
t: TranslateFn,
|
|
180
|
+
phase: PageLifecyclePhase
|
|
181
|
+
): string {
|
|
182
|
+
const key = BLUEPRINT_LIFECYCLE_PHASE_KEYS[phase];
|
|
183
|
+
return key ? t(key) : phase;
|
|
184
|
+
}
|
|
185
|
+
|
|
120
186
|
export function resolveBlueprintNodeTypeLabel(
|
|
121
187
|
node: Pick<
|
|
122
188
|
BlueprintGraphNode,
|
|
123
|
-
"role" | "configSource" | "viewElementId" | "viewElementIds"
|
|
124
|
-
|
|
189
|
+
"role" | "nodeType" | "configSource" | "viewElementId" | "viewElementIds"
|
|
190
|
+
>,
|
|
191
|
+
t: TranslateFn = defaultTranslate()
|
|
125
192
|
): string {
|
|
126
|
-
return
|
|
193
|
+
return getBlueprintNodeTypeLabel(t, resolveBlueprintConfigSource(node));
|
|
127
194
|
}
|
|
128
195
|
|
|
129
196
|
/** 新建节点时的默认显示名称(与类型文案一致) */
|
|
130
197
|
export function defaultBlueprintNodeLabel(
|
|
131
|
-
configSource: BlueprintConfigSource
|
|
198
|
+
configSource: BlueprintConfigSource,
|
|
199
|
+
t: TranslateFn = defaultTranslate()
|
|
132
200
|
): string {
|
|
133
|
-
return
|
|
201
|
+
return getBlueprintNodeTypeLabel(t, configSource);
|
|
134
202
|
}
|
|
135
203
|
|
|
136
204
|
export function resolveNodeFetchConfig(
|
|
@@ -221,11 +289,9 @@ export function sanitizeBlueprintDocument(
|
|
|
221
289
|
viewElementId: undefined,
|
|
222
290
|
};
|
|
223
291
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
next
|
|
227
|
-
) {
|
|
228
|
-
next = { ...next, nodeType: VIEW_NODE_TYPE };
|
|
292
|
+
const runnableNodeType = resolveRunnableNodeType(next);
|
|
293
|
+
if (next.nodeType !== runnableNodeType) {
|
|
294
|
+
next = { ...next, nodeType: runnableNodeType };
|
|
229
295
|
}
|
|
230
296
|
if (next.role === "lifecycle") {
|
|
231
297
|
return { ...next, configSource: "lifecycle" as const };
|
|
@@ -233,6 +299,15 @@ export function sanitizeBlueprintDocument(
|
|
|
233
299
|
if (next.role === "clock") {
|
|
234
300
|
return { ...next, configSource: "clock" as const };
|
|
235
301
|
}
|
|
302
|
+
if (next.role === "fetch") {
|
|
303
|
+
return { ...next, configSource: "fetch" as const };
|
|
304
|
+
}
|
|
305
|
+
if (next.role === "json") {
|
|
306
|
+
return { ...next, configSource: "json" as const };
|
|
307
|
+
}
|
|
308
|
+
if (next.role === "and") {
|
|
309
|
+
return { ...next, configSource: "and" as const };
|
|
310
|
+
}
|
|
236
311
|
if (next.configSource === "lifecycle" || next.configSource === "clock") {
|
|
237
312
|
return { ...next, configSource: undefined };
|
|
238
313
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "@arronqzy/blueprint-dsl";
|
|
1
|
+
import { resolveFetchRequestUrl } from "@arronqzy/blueprint-dsl";
|
|
2
|
+
import type { TranslateFn } from "@arronqzy/i18n";
|
|
3
|
+
import { resolveLocale, tForLocale } from "@arronqzy/i18n";
|
|
5
4
|
|
|
6
5
|
import {
|
|
6
|
+
getLifecyclePhaseLabel,
|
|
7
7
|
resolveBlueprintConfigSource,
|
|
8
8
|
resolveNodeClockConfig,
|
|
9
9
|
resolveNodeFetchConfig,
|
|
@@ -19,21 +19,26 @@ function truncate(text: string, max: number): string {
|
|
|
19
19
|
return `${trimmed.slice(0, Math.max(0, max - 1))}…`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function defaultTranslate(): TranslateFn {
|
|
23
|
+
return tForLocale(resolveLocale());
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
/** 画布节点正文区展示的配置摘要 */
|
|
23
27
|
export function resolveBlueprintNodeSummary(
|
|
24
|
-
data: BlueprintFlowNodeData
|
|
28
|
+
data: BlueprintFlowNodeData,
|
|
29
|
+
t: TranslateFn = defaultTranslate()
|
|
25
30
|
): string | undefined {
|
|
26
31
|
const configSource = resolveBlueprintConfigSource(data);
|
|
27
32
|
|
|
28
33
|
switch (configSource) {
|
|
29
34
|
case "fetch": {
|
|
30
35
|
const config = resolveNodeFetchConfig(data);
|
|
31
|
-
if (!config.url?.trim()) return "
|
|
36
|
+
if (!config.url?.trim()) return t("blueprint.node.summaryNoUrl");
|
|
32
37
|
let url = config.url.trim();
|
|
33
38
|
try {
|
|
34
39
|
url = resolveFetchRequestUrl(config);
|
|
35
40
|
} catch {
|
|
36
|
-
/*
|
|
41
|
+
/* keep raw input */
|
|
37
42
|
}
|
|
38
43
|
const method = config.method ?? "GET";
|
|
39
44
|
return truncate(`${method} ${url}`, 44);
|
|
@@ -41,20 +46,26 @@ export function resolveBlueprintNodeSummary(
|
|
|
41
46
|
case "clock": {
|
|
42
47
|
const clock = resolveNodeClockConfig(data);
|
|
43
48
|
const parts = [
|
|
44
|
-
|
|
45
|
-
clock.intervalSeconds > 0
|
|
46
|
-
|
|
49
|
+
t("blueprint.node.summaryOutputCount", { count: clock.outputCount }),
|
|
50
|
+
clock.intervalSeconds > 0
|
|
51
|
+
? t("blueprint.node.summaryInterval", {
|
|
52
|
+
seconds: clock.intervalSeconds,
|
|
53
|
+
})
|
|
54
|
+
: null,
|
|
55
|
+
clock.emitImmediately
|
|
56
|
+
? t("blueprint.node.summaryEmitImmediate")
|
|
57
|
+
: t("blueprint.node.summaryEmitDelayed"),
|
|
47
58
|
].filter(Boolean);
|
|
48
59
|
return parts.join(" · ");
|
|
49
60
|
}
|
|
50
61
|
case "json": {
|
|
51
62
|
const json = resolveNodeJsonConfig(data).jsonString?.trim();
|
|
52
|
-
if (!json) return "
|
|
63
|
+
if (!json) return t("blueprint.node.summaryEmptyJson");
|
|
53
64
|
return truncate(json.replace(/\s+/g, " "), 44);
|
|
54
65
|
}
|
|
55
66
|
case "logic": {
|
|
56
67
|
const code = resolveNodeLogicConfig(data).sourceCode?.trim();
|
|
57
|
-
if (!code) return "
|
|
68
|
+
if (!code) return t("blueprint.node.summaryNoScript");
|
|
58
69
|
const firstLine =
|
|
59
70
|
code.split("\n").find((line) => line.trim())?.trim() ?? code;
|
|
60
71
|
return truncate(firstLine, 44);
|
|
@@ -66,22 +77,25 @@ export function resolveBlueprintNodeSummary(
|
|
|
66
77
|
if (data.libraryBlueprintId) {
|
|
67
78
|
return truncate(data.libraryBlueprintId, 44);
|
|
68
79
|
}
|
|
69
|
-
return "
|
|
80
|
+
return t("blueprint.node.summaryNoBlueprint");
|
|
70
81
|
}
|
|
71
82
|
case "lifecycle": {
|
|
72
83
|
const phase = data.lifecyclePhase ?? "mounted";
|
|
73
|
-
return
|
|
84
|
+
return getLifecyclePhaseLabel(t, phase);
|
|
74
85
|
}
|
|
75
86
|
case "view": {
|
|
76
87
|
const ids = resolveViewElementIds(data);
|
|
77
|
-
if (ids.length === 0) return "
|
|
88
|
+
if (ids.length === 0) return t("blueprint.node.summaryNoView");
|
|
78
89
|
if (ids.length === 1) {
|
|
79
|
-
return truncate(
|
|
90
|
+
return truncate(
|
|
91
|
+
t("blueprint.node.summaryViewOne", { id: ids[0] }),
|
|
92
|
+
44
|
|
93
|
+
);
|
|
80
94
|
}
|
|
81
|
-
return
|
|
95
|
+
return t("blueprint.node.summaryViewMany", { count: ids.length });
|
|
82
96
|
}
|
|
83
97
|
case "and":
|
|
84
|
-
return "
|
|
98
|
+
return t("blueprint.node.summaryAnd");
|
|
85
99
|
default:
|
|
86
100
|
return undefined;
|
|
87
101
|
}
|
package/src/graph/sync-nodes.ts
CHANGED
|
@@ -42,6 +42,8 @@ export function nodeStructureSignature(
|
|
|
42
42
|
lifecyclePhase?: string;
|
|
43
43
|
libraryBlueprintId?: string;
|
|
44
44
|
nodeType?: string;
|
|
45
|
+
viewElementId?: string;
|
|
46
|
+
viewElementIds?: string[];
|
|
45
47
|
fetchConfig?: { url?: string; method?: string; apiBaseUrl?: string; swaggerDocsUrl?: string };
|
|
46
48
|
jsonConfig?: { jsonString?: string };
|
|
47
49
|
logicConfig?: { sourceCode?: string };
|
|
@@ -51,7 +53,7 @@ export function nodeStructureSignature(
|
|
|
51
53
|
return nodes
|
|
52
54
|
.map(
|
|
53
55
|
(n) =>
|
|
54
|
-
`${n.id}:${n.role}:${n.label}:${n.configSource ?? ""}:${n.lifecyclePhase ?? ""}:${n.libraryBlueprintId ?? ""}:${n.nodeType ?? ""}:${n.fetchConfig?.url ?? ""}:${n.fetchConfig?.method ?? ""}:${n.fetchConfig?.apiBaseUrl ?? ""}:${n.fetchConfig?.swaggerDocsUrl ?? ""}:${n.jsonConfig?.jsonString ?? ""}:${n.logicConfig?.sourceCode ?? ""}:${n.clockConfig?.intervalSeconds ?? ""}:${n.clockConfig?.timeFormat ?? ""}:${n.clockConfig?.outputCount ?? ""}:${n.clockConfig?.emitImmediately ?? ""}`
|
|
56
|
+
`${n.id}:${n.role}:${n.label}:${n.configSource ?? ""}:${n.lifecyclePhase ?? ""}:${n.libraryBlueprintId ?? ""}:${n.nodeType ?? ""}:${(n.viewElementIds ?? (n.viewElementId ? [n.viewElementId] : [])).join(",")}:${n.fetchConfig?.url ?? ""}:${n.fetchConfig?.method ?? ""}:${n.fetchConfig?.apiBaseUrl ?? ""}:${n.fetchConfig?.swaggerDocsUrl ?? ""}:${n.jsonConfig?.jsonString ?? ""}:${n.logicConfig?.sourceCode ?? ""}:${n.clockConfig?.intervalSeconds ?? ""}:${n.clockConfig?.timeFormat ?? ""}:${n.clockConfig?.outputCount ?? ""}:${n.clockConfig?.emitImmediately ?? ""}`
|
|
55
57
|
)
|
|
56
58
|
.sort()
|
|
57
59
|
.join("|");
|
|
@@ -112,16 +112,22 @@ export function useBlueprintDebugSession({
|
|
|
112
112
|
[graph.document, libraryNameById]
|
|
113
113
|
);
|
|
114
114
|
|
|
115
|
-
const
|
|
115
|
+
const syncRunnerGraphConfig = useCallback(() => {
|
|
116
116
|
const runner = runnerRef.current;
|
|
117
117
|
if (!runner?.isDebugSessionActive()) return;
|
|
118
118
|
runner.updateGraph(buildRunnableGraph());
|
|
119
119
|
if (selectedLifecycleNodeId) {
|
|
120
120
|
runner.refreshDebugLifecycleScope(selectedLifecycleNodeId);
|
|
121
121
|
}
|
|
122
|
-
runner.refreshPendingQueueFromLatestGraph();
|
|
123
122
|
}, [buildRunnableGraph, selectedLifecycleNodeId]);
|
|
124
123
|
|
|
124
|
+
const syncRunnerWithLatestGraph = useCallback(() => {
|
|
125
|
+
syncRunnerGraphConfig();
|
|
126
|
+
const runner = runnerRef.current;
|
|
127
|
+
if (!runner?.isDebugSessionActive()) return;
|
|
128
|
+
runner.refreshPendingQueueFromLatestGraph();
|
|
129
|
+
}, [syncRunnerGraphConfig]);
|
|
130
|
+
|
|
125
131
|
const resolveBlueprintName = useCallback(
|
|
126
132
|
(id: string) => libraryNameById?.get(id) ?? id,
|
|
127
133
|
[libraryNameById]
|
|
@@ -242,12 +248,9 @@ export function useBlueprintDebugSession({
|
|
|
242
248
|
useEffect(() => {
|
|
243
249
|
const runner = runnerRef.current;
|
|
244
250
|
if (!runner?.isDebugSessionActive()) return;
|
|
245
|
-
|
|
246
|
-
if (selectedLifecycleNodeId) {
|
|
247
|
-
runner.refreshDebugLifecycleScope(selectedLifecycleNodeId);
|
|
248
|
-
}
|
|
251
|
+
syncRunnerGraphConfig();
|
|
249
252
|
runner.refreshPendingQueueFromLatestGraph();
|
|
250
|
-
}, [
|
|
253
|
+
}, [graphDebugSig, syncRunnerGraphConfig]);
|
|
251
254
|
|
|
252
255
|
useEffect(() => {
|
|
253
256
|
if (running) return;
|
|
@@ -277,24 +280,14 @@ export function useBlueprintDebugSession({
|
|
|
277
280
|
try {
|
|
278
281
|
const runner = beginRun();
|
|
279
282
|
if (!runner) return;
|
|
283
|
+
runner.updateGraph(buildRunnableGraph());
|
|
284
|
+
runner.refreshDebugLifecycleScope(selectedLifecycleNodeId);
|
|
280
285
|
const labels = buildNodeLabelMap(graph);
|
|
281
286
|
const onProgress = (trace: ExecutionTraceEntry[]) => {
|
|
282
287
|
setEntries([...trace]);
|
|
283
288
|
};
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
await runner.debugStep(labels, onProgress);
|
|
287
|
-
const trace = runner.getTraceEntries();
|
|
288
|
-
setEntries(trace);
|
|
289
|
-
if (shouldHaltDebugOnFalseSignal(trace, allowFalseSignalPropagation)) {
|
|
290
|
-
setChainComplete(true);
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
|
-
await new Promise<void>((resolve) => {
|
|
294
|
-
requestAnimationFrame(() => resolve());
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
const result = runner.getTraceEntries();
|
|
289
|
+
const result = await runner.debugRunAll(labels, onProgress);
|
|
290
|
+
setEntries(result);
|
|
298
291
|
setChainComplete(
|
|
299
292
|
shouldHaltDebugOnFalseSignal(result, allowFalseSignalPropagation) ||
|
|
300
293
|
(runner.isDebugQueueEmpty() && result.length > 0)
|
|
@@ -304,7 +297,16 @@ export function useBlueprintDebugSession({
|
|
|
304
297
|
} finally {
|
|
305
298
|
setRunning(false);
|
|
306
299
|
}
|
|
307
|
-
}, [
|
|
300
|
+
}, [
|
|
301
|
+
allowFalseSignalPropagation,
|
|
302
|
+
beginRun,
|
|
303
|
+
buildRunRecord,
|
|
304
|
+
buildRunnableGraph,
|
|
305
|
+
graph,
|
|
306
|
+
persistRunIfNeeded,
|
|
307
|
+
selectedLifecycleNodeId,
|
|
308
|
+
validateBlueprintGraph,
|
|
309
|
+
]);
|
|
308
310
|
|
|
309
311
|
const stepNext = useCallback(async () => {
|
|
310
312
|
if (
|