@arronqzy/react-blueprint 1.0.10 → 1.0.12

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.
@@ -0,0 +1,327 @@
1
+ /* 画布:与 panel / next-themes 主题一致 */
2
+ .bp-canvas,
3
+ [data-workspace-region="blueprint"] .bp-canvas {
4
+ background-color: hsl(var(--background));
5
+ color: hsl(var(--foreground));
6
+ }
7
+
8
+ .bp-canvas .react-flow__pane,
9
+ .bp-flow.react-flow__pane {
10
+ cursor: default;
11
+ }
12
+
13
+ .bp-canvas .react-flow,
14
+ .bp-flow.react-flow,
15
+ [data-workspace-region="blueprint"] .react-flow {
16
+ --xy-background-color: transparent;
17
+ --xy-edge-stroke: #2563eb;
18
+ --xy-edge-stroke-width: 2.5;
19
+ --xy-edge-stroke-selected: #1d4ed8;
20
+ --xy-connectionline-stroke: #2563eb;
21
+ --xy-connectionline-stroke-width: 2.5;
22
+ --xy-handle-background-color: hsl(var(--primary));
23
+ --xy-handle-border-color: hsl(var(--background));
24
+ background-color: transparent;
25
+ }
26
+
27
+ /*
28
+ * 全局 reset(apps/web style.css、Tailwind preflight)会给 svg 设 max-width:100%,
29
+ * 导致 React Flow 边线 SVG 坐标系被压扁;连线预览单独有 z-index:1001 所以能看见。
30
+ */
31
+ .bp-canvas svg.react-flow__connectionline,
32
+ .bp-canvas .react-flow__edges svg,
33
+ .bp-flow svg.react-flow__connectionline,
34
+ .bp-flow .react-flow__edges svg,
35
+ [data-workspace-region="blueprint"] svg.react-flow__connectionline,
36
+ [data-workspace-region="blueprint"] .react-flow__edges svg {
37
+ display: block;
38
+ max-width: none !important;
39
+ max-height: none !important;
40
+ width: auto !important;
41
+ height: auto !important;
42
+ overflow: visible !important;
43
+ }
44
+
45
+ /*
46
+ * 边层必须在节点之上:.react-flow__edges 与 .react-flow__nodes 为兄弟节点,
47
+ * 默认 DOM 顺序下节点后渲染会盖住边;连接线因有 z-index:1001 不受影响。
48
+ */
49
+ .bp-canvas .react-flow__edges,
50
+ .bp-flow .react-flow__edges,
51
+ [data-workspace-region="blueprint"] .react-flow__edges {
52
+ z-index: 1000 !important;
53
+ pointer-events: none;
54
+ }
55
+
56
+ .bp-canvas .react-flow__nodes,
57
+ .bp-flow .react-flow__nodes,
58
+ [data-workspace-region="blueprint"] .react-flow__nodes {
59
+ z-index: 0 !important;
60
+ }
61
+
62
+ .bp-canvas .react-flow__edge,
63
+ .bp-flow .react-flow__edge,
64
+ [data-workspace-region="blueprint"] .react-flow__edge {
65
+ pointer-events: visibleStroke;
66
+ }
67
+
68
+ /* 持久边 path:强制描边(覆盖 currentColor / 主题色) */
69
+ .bp-canvas .react-flow__edges path.react-flow__edge-path,
70
+ .bp-canvas .react-flow__edges path.bp-edge-visible,
71
+ .bp-flow .react-flow__edges path.react-flow__edge-path,
72
+ .bp-flow .react-flow__edges path.bp-edge-visible,
73
+ [data-workspace-region="blueprint"] .react-flow__edges path.react-flow__edge-path,
74
+ [data-workspace-region="blueprint"] .react-flow__edges path.bp-edge-visible {
75
+ fill: none !important;
76
+ stroke: #2563eb !important;
77
+ stroke-width: 2.5px !important;
78
+ opacity: 1 !important;
79
+ }
80
+
81
+ .bp-canvas .react-flow__edge.selected path.react-flow__edge-path,
82
+ .bp-flow .react-flow__edge.selected path.react-flow__edge-path,
83
+ [data-workspace-region="blueprint"] .react-flow__edge.selected path.react-flow__edge-path {
84
+ stroke: #1d4ed8 !important;
85
+ stroke-width: 3px !important;
86
+ }
87
+
88
+ .bp-canvas .react-flow__edge.bp-edge--signal-true path.react-flow__edge-path,
89
+ .bp-flow .react-flow__edge.bp-edge--signal-true path.react-flow__edge-path,
90
+ [data-workspace-region="blueprint"] .react-flow__edge.bp-edge--signal-true path.react-flow__edge-path {
91
+ stroke: #16a34a !important;
92
+ stroke-width: 3px !important;
93
+ }
94
+
95
+ .bp-canvas .react-flow__edge.bp-edge--signal-false path.react-flow__edge-path,
96
+ .bp-flow .react-flow__edge.bp-edge--signal-false path.react-flow__edge-path,
97
+ [data-workspace-region="blueprint"] .react-flow__edge.bp-edge--signal-false path.react-flow__edge-path {
98
+ stroke: #dc2626 !important;
99
+ stroke-width: 3px !important;
100
+ }
101
+
102
+ .bp-canvas .react-flow__edge.bp-edge--signal-true .react-flow__arrowhead polyline,
103
+ .bp-flow .react-flow__edge.bp-edge--signal-true .react-flow__arrowhead polyline,
104
+ [data-workspace-region="blueprint"] .react-flow__edge.bp-edge--signal-true .react-flow__arrowhead polyline {
105
+ stroke: #16a34a;
106
+ fill: #16a34a;
107
+ }
108
+
109
+ .bp-canvas .react-flow__edge.bp-edge--signal-false .react-flow__arrowhead polyline,
110
+ .bp-flow .react-flow__edge.bp-edge--signal-false .react-flow__arrowhead polyline,
111
+ [data-workspace-region="blueprint"] .react-flow__edge.bp-edge--signal-false .react-flow__arrowhead polyline {
112
+ stroke: #dc2626;
113
+ fill: #dc2626;
114
+ }
115
+
116
+ .bp-canvas .react-flow__edge.bp-edge--signal-clock-blue path.react-flow__edge-path,
117
+ .bp-flow .react-flow__edge.bp-edge--signal-clock-blue path.react-flow__edge-path,
118
+ [data-workspace-region="blueprint"] .react-flow__edge.bp-edge--signal-clock-blue path.react-flow__edge-path {
119
+ stroke: #2563eb !important;
120
+ stroke-width: 3px !important;
121
+ }
122
+
123
+ .bp-canvas .react-flow__edge.bp-edge--signal-clock-blue .react-flow__arrowhead polyline,
124
+ .bp-flow .react-flow__edge.bp-edge--signal-clock-blue .react-flow__arrowhead polyline,
125
+ [data-workspace-region="blueprint"] .react-flow__edge.bp-edge--signal-clock-blue .react-flow__arrowhead polyline {
126
+ stroke: #2563eb;
127
+ fill: #2563eb;
128
+ }
129
+
130
+ .bp-canvas .react-flow__arrowhead polyline,
131
+ .bp-flow .react-flow__arrowhead polyline,
132
+ [data-workspace-region="blueprint"] .react-flow__arrowhead polyline {
133
+ stroke: #2563eb;
134
+ fill: #2563eb;
135
+ }
136
+
137
+ .bp-canvas .react-flow__connection-path,
138
+ .bp-flow .react-flow__connection-path,
139
+ [data-workspace-region="blueprint"] .react-flow__connection-path {
140
+ fill: none !important;
141
+ stroke: #2563eb !important;
142
+ stroke-width: 2.5px !important;
143
+ }
144
+
145
+ .bp-canvas .react-flow__edge-interaction,
146
+ .bp-flow .react-flow__edge-interaction,
147
+ [data-workspace-region="blueprint"] .react-flow__edge-interaction {
148
+ pointer-events: stroke;
149
+ stroke: transparent !important;
150
+ stroke-opacity: 0 !important;
151
+ stroke-width: 18px !important;
152
+ }
153
+
154
+ .bp-canvas .react-flow__edge.selected .react-flow__edge-interaction,
155
+ .bp-flow .react-flow__edge.selected .react-flow__edge-interaction,
156
+ [data-workspace-region="blueprint"] .react-flow__edge.selected .react-flow__edge-interaction {
157
+ pointer-events: stroke;
158
+ }
159
+
160
+ /* 节点容器:去掉默认包裹样式 */
161
+ .bp-canvas .react-flow__node,
162
+ .bp-flow .react-flow__node,
163
+ [data-workspace-region="blueprint"] .react-flow__node {
164
+ border: none;
165
+ background: transparent;
166
+ box-shadow: none;
167
+ padding: 0;
168
+ }
169
+
170
+ /* 连接点:禁止 transform,避免覆盖 React Flow 的 translate(-50%, -50%) 定位 */
171
+ .bp-canvas .react-flow__handle.bp-flow-handle,
172
+ .bp-flow .react-flow__handle.bp-flow-handle,
173
+ [data-workspace-region="blueprint"] .react-flow__handle.bp-flow-handle {
174
+ z-index: 3;
175
+ width: 10px;
176
+ height: 10px;
177
+ min-width: 10px;
178
+ min-height: 10px;
179
+ border: 2px solid hsl(var(--background)) !important;
180
+ border-radius: 9999px;
181
+ background-color: hsl(var(--foreground) / 0.42) !important;
182
+ transition:
183
+ background-color 0.15s ease,
184
+ border-color 0.15s ease,
185
+ box-shadow 0.15s ease;
186
+ }
187
+
188
+ .bp-canvas .react-flow__handle.bp-flow-handle:hover,
189
+ .bp-flow .react-flow__handle.bp-flow-handle:hover,
190
+ [data-workspace-region="blueprint"] .react-flow__handle.bp-flow-handle:hover {
191
+ background-color: hsl(var(--primary)) !important;
192
+ border-color: hsl(var(--background)) !important;
193
+ box-shadow: 0 0 0 3px hsl(var(--primary) / 0.28);
194
+ }
195
+
196
+ .bp-canvas .react-flow__handle.bp-flow-handle--target,
197
+ .bp-flow .react-flow__handle.bp-flow-handle--target,
198
+ [data-workspace-region="blueprint"] .react-flow__handle.bp-flow-handle--target {
199
+ background-color: hsl(var(--foreground) / 0.48) !important;
200
+ }
201
+
202
+ .bp-canvas .react-flow__handle.bp-flow-handle--source,
203
+ .bp-flow .react-flow__handle.bp-flow-handle--source,
204
+ [data-workspace-region="blueprint"] .react-flow__handle.bp-flow-handle--source {
205
+ background-color: hsl(var(--primary) / 0.72) !important;
206
+ }
207
+
208
+ .bp-node--selected .bp-flow-handle {
209
+ border-color: hsl(var(--card)) !important;
210
+ box-shadow: 0 0 0 2px hsl(var(--primary) / 0.35);
211
+ }
212
+
213
+ .bp-node--selected .bp-flow-handle--target {
214
+ background-color: hsl(var(--foreground) / 0.55) !important;
215
+ }
216
+
217
+ .bp-node--selected .bp-flow-handle--source {
218
+ background-color: hsl(var(--primary)) !important;
219
+ }
220
+
221
+ .bp-node--execution-true .bp-node-card {
222
+ border-color: rgb(22 163 74 / 0.65);
223
+ box-shadow:
224
+ 0 0 0 1px rgb(22 163 74 / 0.35),
225
+ 0 4px 14px rgb(22 163 74 / 0.18);
226
+ }
227
+
228
+ .bp-node--execution-true .bp-flow-handle {
229
+ border-color: hsl(var(--card));
230
+ box-shadow: 0 0 0 2px rgb(22 163 74 / 0.45);
231
+ }
232
+
233
+ .bp-node--execution-true .bp-flow-handle--source {
234
+ background-color: #16a34a !important;
235
+ }
236
+
237
+ .bp-node--execution-true .bp-flow-handle--target {
238
+ background-color: rgb(22 163 74 / 0.55) !important;
239
+ }
240
+
241
+ .bp-node--execution-false .bp-node-card {
242
+ border-color: hsl(var(--destructive) / 0.65);
243
+ box-shadow:
244
+ 0 0 0 1px hsl(var(--destructive) / 0.4),
245
+ 0 4px 14px hsl(var(--destructive) / 0.16);
246
+ }
247
+
248
+ .bp-node--execution-false .bp-flow-handle {
249
+ border-color: hsl(var(--card));
250
+ box-shadow: 0 0 0 2px hsl(var(--destructive) / 0.4);
251
+ }
252
+
253
+ .bp-node--execution-false .bp-flow-handle--source {
254
+ background-color: hsl(var(--destructive)) !important;
255
+ }
256
+
257
+ .bp-node--execution-false .bp-flow-handle--target {
258
+ background-color: hsl(var(--destructive) / 0.55) !important;
259
+ }
260
+
261
+ /* 生命周期节点无输入口:隐藏任何左侧/目标连接点(含旧数据产生的幽灵 handle) */
262
+ .bp-node--lifecycle .react-flow__handle.bp-flow-handle--target,
263
+ .bp-node--lifecycle .react-flow__handle-left,
264
+ .bp-node--lifecycle .react-flow__handle[data-handlepos="left"] {
265
+ display: none !important;
266
+ visibility: hidden !important;
267
+ pointer-events: none !important;
268
+ width: 0 !important;
269
+ height: 0 !important;
270
+ opacity: 0 !important;
271
+ }
272
+
273
+ .bp-canvas .react-flow__handle.bp-flow-handle--signal,
274
+ .bp-flow .react-flow__handle.bp-flow-handle--signal,
275
+ [data-workspace-region="blueprint"] .react-flow__handle.bp-flow-handle--signal {
276
+ background-color: hsl(38 92% 50% / 0.75) !important;
277
+ width: 8px;
278
+ height: 8px;
279
+ }
280
+
281
+ .bp-node--selected .bp-flow-handle--signal {
282
+ background-color: hsl(38 92% 50%) !important;
283
+ }
284
+
285
+ /* 右键菜单 */
286
+ .bp-context-menu {
287
+ position: fixed;
288
+ z-index: 10050;
289
+ min-width: 148px;
290
+ padding: 4px;
291
+ border-radius: 8px;
292
+ border: 1px solid hsl(var(--border));
293
+ background: hsl(var(--card));
294
+ color: hsl(var(--card-foreground));
295
+ box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
296
+ }
297
+
298
+ .bp-context-menu__item {
299
+ display: block;
300
+ width: 100%;
301
+ padding: 6px 10px;
302
+ border: none;
303
+ border-radius: 6px;
304
+ background: transparent;
305
+ text-align: left;
306
+ font-size: 12px;
307
+ color: inherit;
308
+ cursor: pointer;
309
+ }
310
+
311
+ .bp-context-menu__item:hover {
312
+ background: hsl(var(--accent));
313
+ }
314
+
315
+ .bp-context-menu__item--danger {
316
+ color: hsl(var(--destructive));
317
+ }
318
+
319
+ .bp-context-menu__item--danger:hover {
320
+ background: hsl(var(--destructive) / 0.1);
321
+ }
322
+
323
+ .bp-context-menu__separator {
324
+ height: 1px;
325
+ margin: 4px 0;
326
+ background: hsl(var(--border));
327
+ }
package/package.json CHANGED
@@ -1,21 +1,27 @@
1
1
  {
2
2
  "name": "@arronqzy/react-blueprint",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "",
5
+ "files": [
6
+ "index.ts",
7
+ "src",
8
+ "dist",
9
+ "README.md"
10
+ ],
5
11
  "main": "index.ts",
6
12
  "exports": {
7
13
  ".": "./index.ts",
8
- "./blueprint.css": "./src/blueprint.css"
14
+ "./blueprint.css": "./dist/blueprint.css"
9
15
  },
10
16
  "dependencies": {
11
17
  "react": "^19",
12
18
  "dayjs": "^1.11.13",
13
19
  "react-dom": "^19",
14
20
  "@xyflow/react": "^12.9.3",
15
- "@arronqzy/i18n": "0.1.7",
16
- "@arronqzy/blueprint-dsl": "1.0.9",
21
+ "@arronqzy/blueprint-dsl": "1.0.10",
17
22
  "@arronqzy/react-rx-store": "1.1.5",
18
- "@arronqzy/ui": "1.0.6"
23
+ "@arronqzy/ui": "1.0.6",
24
+ "@arronqzy/i18n": "0.1.8"
19
25
  },
20
26
  "devDependencies": {
21
27
  "@types/react": "^19.0.1",
@@ -29,8 +35,8 @@
29
35
  "typescript": "^5.5.4",
30
36
  "vite": "^5.1.4",
31
37
  "@arronqzy/tailwind": "1.0.2",
32
- "@arronqzy/typescript-config": "1.0.0",
33
- "@arronqzy/eslint-config": "0.0.0"
38
+ "@arronqzy/eslint-config": "0.0.0",
39
+ "@arronqzy/typescript-config": "1.0.0"
34
40
  },
35
41
  "keywords": [],
36
42
  "author": "",
@@ -38,7 +44,8 @@
38
44
  "scripts": {
39
45
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
40
46
  "typecheck": "tsc -p tsconfig.json --noEmit",
41
- "build": "pnpm run lint && pnpm run typecheck && vite build",
47
+ "build:css": "mkdir -p dist && cp src/blueprint.css dist/blueprint.css",
48
+ "build": "pnpm run lint && pnpm run typecheck && pnpm run build:css",
42
49
  "dev": "vite"
43
50
  }
44
51
  }
@@ -12,7 +12,7 @@ export type BlueprintContextMenuState =
12
12
  clientX: number;
13
13
  clientY: number;
14
14
  nodeId: string;
15
- role: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "clock";
15
+ role: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "storage" | "clock";
16
16
  }
17
17
  | {
18
18
  kind: "edge";
@@ -21,6 +21,7 @@ import { useI18n } from "@arronqzy/i18n/react";
21
21
  import { FetchNodeConfigPanel } from "./components/FetchNodeConfigPanel";
22
22
  import { ClockNodeConfigPanel } from "./components/ClockNodeConfigPanel";
23
23
  import { JsonNodeConfigPanel } from "./components/JsonNodeConfigPanel";
24
+ import { StorageNodeConfigPanel } from "./components/StorageNodeConfigPanel";
24
25
  import { LogicNodeConfigPanel } from "./components/LogicNodeConfigPanel";
25
26
  import { ViewElementMultiSelect } from "./components/ViewElementMultiSelect";
26
27
  import { ConfigFieldLabel, ConfigHintIcon, ConfigSectionTitle } from "./components/ConfigHintIcon";
@@ -74,6 +75,7 @@ export type BlueprintNodeConfigSidebarProps = {
74
75
  | "lifecyclePhase"
75
76
  | "fetchConfig"
76
77
  | "jsonConfig"
78
+ | "storageConfig"
77
79
  | "logicConfig"
78
80
  | "clockConfig"
79
81
  | "eventConfig"
@@ -88,6 +90,7 @@ const ROLE_LABEL_KEYS: Record<BlueprintNodeRole, string> = {
88
90
  and: "blueprint.config.roleAnd",
89
91
  fetch: "blueprint.config.roleFetch",
90
92
  json: "blueprint.config.roleJson",
93
+ storage: "blueprint.config.roleStorage",
91
94
  logic: "blueprint.config.roleLogic",
92
95
  clock: "blueprint.config.roleClock",
93
96
  event: "blueprint.config.roleEvent",
@@ -195,6 +198,7 @@ export function BlueprintNodeConfigSidebar({
195
198
  <SelectItem value="lifecycle">{t("blueprint.config.configLifecycle")}</SelectItem>
196
199
  <SelectItem value="fetch">{t("blueprint.config.configFetch")}</SelectItem>
197
200
  <SelectItem value="json">{t("blueprint.config.configJson")}</SelectItem>
201
+ <SelectItem value="storage">{t("blueprint.config.configStorage")}</SelectItem>
198
202
  <SelectItem value="clock">{t("blueprint.config.configClock")}</SelectItem>
199
203
  <SelectItem value="event">{t("blueprint.config.configEvent")}</SelectItem>
200
204
  <SelectItem value="view">{t("blueprint.config.configView")}</SelectItem>
@@ -399,6 +403,16 @@ export function BlueprintNodeConfigSidebar({
399
403
  <JsonNodeConfigPanel node={node} onUpdateNode={onUpdateNode} />
400
404
  ) : null}
401
405
 
406
+ {configSource === "storage" ? (
407
+ <StorageNodeConfigPanel
408
+ node={node}
409
+ graphNodes={graphNodes}
410
+ graphEdges={graphEdges}
411
+ traceEntries={traceEntries}
412
+ onUpdateNode={onUpdateNode}
413
+ />
414
+ ) : null}
415
+
402
416
  {configSource === "clock" ? (
403
417
  <ClockNodeConfigPanel node={node} onUpdateNode={onUpdateNode} />
404
418
  ) : null}
@@ -5,6 +5,7 @@ import { AndFlowNode } from "./nodes/AndFlowNode";
5
5
  import { ClockFlowNode } from "./nodes/ClockFlowNode";
6
6
  import { FetchFlowNode } from "./nodes/FetchFlowNode";
7
7
  import { JsonFlowNode } from "./nodes/JsonFlowNode";
8
+ import { StorageFlowNode } from "./nodes/StorageFlowNode";
8
9
  import { LifecycleFlowNode } from "./nodes/LifecycleFlowNode";
9
10
  import { EventFlowNode } from "./nodes/EventFlowNode";
10
11
  import { LogicFlowNode } from "./nodes/LogicFlowNode";
@@ -16,6 +17,7 @@ export const blueprintNodeTypes = {
16
17
  clock: ClockFlowNode,
17
18
  fetch: FetchFlowNode,
18
19
  json: JsonFlowNode,
20
+ storage: StorageFlowNode,
19
21
  logic: LogicFlowNode,
20
22
  lifecycle: LifecycleFlowNode,
21
23
  event: EventFlowNode,
@@ -0,0 +1,240 @@
1
+ import { useCallback, useMemo, useState, type ChangeEvent } from "react";
2
+ import {
3
+ Checkbox,
4
+ Input,
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@arronqzy/ui";
11
+ import type {
12
+ ExecutionTraceEntry,
13
+ StorageKind,
14
+ StorageReadConfig,
15
+ StorageSetConfig,
16
+ } from "@arronqzy/blueprint-dsl";
17
+ import {
18
+ latestTraceOutputsByNode,
19
+ resolveFetchIncomingScope,
20
+ resolveFetchScopeAutocompleteRoot,
21
+ uniqueStorageKinds,
22
+ } from "@arronqzy/blueprint-dsl";
23
+ import { useI18n } from "@arronqzy/i18n/react";
24
+
25
+ import type { BlueprintGraphEdge, BlueprintGraphNode } from "../graph/document";
26
+ import { resolveNodeStorageConfig } from "../graph/document";
27
+ import { ConfigFieldLabel, ConfigSectionTitle } from "./ConfigHintIcon";
28
+ import { ScopeTemplateAutocompleteHost } from "./ScopeTemplateAutocompleteHost";
29
+
30
+ export type StorageNodeConfigPanelProps = {
31
+ node: BlueprintGraphNode;
32
+ graphNodes?: BlueprintGraphNode[];
33
+ graphEdges?: BlueprintGraphEdge[];
34
+ traceEntries?: ExecutionTraceEntry[];
35
+ onUpdateNode: (
36
+ nodeId: string,
37
+ patch: Partial<Pick<BlueprintGraphNode, "storageConfig" | "configSource">>
38
+ ) => void;
39
+ };
40
+
41
+ function patchStorageConfig(
42
+ node: BlueprintGraphNode,
43
+ patch: {
44
+ read?: Partial<StorageReadConfig>;
45
+ set?: Partial<StorageSetConfig>;
46
+ }
47
+ ) {
48
+ const current = resolveNodeStorageConfig(node);
49
+ return {
50
+ storageConfig: {
51
+ read: { ...current.read, ...patch.read },
52
+ set: { ...current.set, ...patch.set },
53
+ },
54
+ configSource: "storage" as const,
55
+ };
56
+ }
57
+
58
+ export function StorageNodeConfigPanel({
59
+ node,
60
+ graphNodes = [],
61
+ graphEdges = [],
62
+ traceEntries = [],
63
+ onUpdateNode,
64
+ }: StorageNodeConfigPanelProps) {
65
+ const { t } = useI18n();
66
+ const [formEl, setFormEl] = useState<HTMLDivElement | null>(null);
67
+ const storageConfig = resolveNodeStorageConfig(node);
68
+
69
+ const incomingScope = useMemo(() => {
70
+ const outputs = latestTraceOutputsByNode(traceEntries);
71
+ return resolveFetchIncomingScope({
72
+ fetchNodeId: node.id,
73
+ nodes: graphNodes,
74
+ edges: graphEdges,
75
+ getOutput: (sourceId, port) => outputs[sourceId]?.[port],
76
+ });
77
+ }, [graphEdges, graphNodes, node.id, traceEntries]);
78
+ const autocompleteScope = resolveFetchScopeAutocompleteRoot(incomingScope);
79
+
80
+ const handleReadStorageChange = useCallback(
81
+ (value: string) => {
82
+ onUpdateNode(
83
+ node.id,
84
+ patchStorageConfig(node, {
85
+ read: { storage: value === "session" ? "session" : "local" },
86
+ })
87
+ );
88
+ },
89
+ [node, onUpdateNode]
90
+ );
91
+
92
+ const handleReadKeyChange = useCallback(
93
+ (e: ChangeEvent<HTMLInputElement>) => {
94
+ onUpdateNode(
95
+ node.id,
96
+ patchStorageConfig(node, { read: { key: e.target.value } })
97
+ );
98
+ },
99
+ [node, onUpdateNode]
100
+ );
101
+
102
+ const handleSetStorageToggle = useCallback(
103
+ (kind: StorageKind, checked: boolean) => {
104
+ const next = new Set(storageConfig.set.storages);
105
+ if (checked) next.add(kind);
106
+ else next.delete(kind);
107
+ onUpdateNode(
108
+ node.id,
109
+ patchStorageConfig(node, {
110
+ set: { storages: uniqueStorageKinds([...next]) },
111
+ })
112
+ );
113
+ },
114
+ [node, onUpdateNode, storageConfig.set.storages]
115
+ );
116
+
117
+ const handleSetKeyChange = useCallback(
118
+ (e: ChangeEvent<HTMLInputElement>) => {
119
+ onUpdateNode(
120
+ node.id,
121
+ patchStorageConfig(node, { set: { key: e.target.value } })
122
+ );
123
+ },
124
+ [node, onUpdateNode]
125
+ );
126
+
127
+ const handleSetValueChange = useCallback(
128
+ (e: ChangeEvent<HTMLInputElement>) => {
129
+ onUpdateNode(
130
+ node.id,
131
+ patchStorageConfig(node, { set: { value: e.target.value } })
132
+ );
133
+ },
134
+ [node, onUpdateNode]
135
+ );
136
+
137
+ return (
138
+ <div
139
+ ref={setFormEl}
140
+ className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5"
141
+ >
142
+ <ScopeTemplateAutocompleteHost
143
+ scope={autocompleteScope}
144
+ container={formEl}
145
+ />
146
+ <ConfigSectionTitle
147
+ title={t("blueprint.config.storageTitle")}
148
+ hint={t("blueprint.config.storageHint")}
149
+ />
150
+
151
+ <div className="space-y-2 rounded-md border border-border/60 bg-background/70 p-2">
152
+ <ConfigSectionTitle
153
+ title={t("blueprint.config.storageReadTitle")}
154
+ hint={t("blueprint.config.storageReadHint")}
155
+ />
156
+ <label className="block space-y-1">
157
+ <ConfigFieldLabel label={t("blueprint.config.storageTarget")} />
158
+ <Select
159
+ value={storageConfig.read.storage}
160
+ onValueChange={handleReadStorageChange}
161
+ >
162
+ <SelectTrigger className="h-8">
163
+ <SelectValue />
164
+ </SelectTrigger>
165
+ <SelectContent>
166
+ <SelectItem value="session">
167
+ {t("blueprint.config.storageSession")}
168
+ </SelectItem>
169
+ <SelectItem value="local">
170
+ {t("blueprint.config.storageLocal")}
171
+ </SelectItem>
172
+ </SelectContent>
173
+ </Select>
174
+ </label>
175
+ <label className="block space-y-1">
176
+ <ConfigFieldLabel label={t("blueprint.config.storageKey")} />
177
+ <Input
178
+ value={storageConfig.read.key}
179
+ onChange={handleReadKeyChange}
180
+ spellCheck={false}
181
+ className="h-8 font-mono text-[11px]"
182
+ placeholder="key"
183
+ />
184
+ </label>
185
+ </div>
186
+
187
+ <div className="space-y-2 rounded-md border border-border/60 bg-background/70 p-2">
188
+ <ConfigSectionTitle
189
+ title={t("blueprint.config.storageSetTitle")}
190
+ hint={t("blueprint.config.storageSetHint")}
191
+ />
192
+ <div className="space-y-1.5">
193
+ <ConfigFieldLabel label={t("blueprint.config.storageTarget")} />
194
+ {(["session", "local"] as const).map((kind) => {
195
+ const checked = storageConfig.set.storages.includes(kind);
196
+ return (
197
+ <label key={kind} className="flex items-center gap-2">
198
+ <Checkbox
199
+ checked={checked}
200
+ className="h-3.5 w-3.5 border-2 border-foreground/80 bg-background ring-1 ring-foreground/40 data-[state=checked]:border-primary data-[state=checked]:ring-primary/40"
201
+ onCheckedChange={(nextChecked: boolean | "indeterminate") => {
202
+ handleSetStorageToggle(kind, nextChecked === true);
203
+ }}
204
+ />
205
+ <span>
206
+ {kind === "session"
207
+ ? t("blueprint.config.storageSession")
208
+ : t("blueprint.config.storageLocal")}
209
+ </span>
210
+ </label>
211
+ );
212
+ })}
213
+ </div>
214
+ <label className="block space-y-1">
215
+ <ConfigFieldLabel label={t("blueprint.config.storageKey")} />
216
+ <Input
217
+ value={storageConfig.set.key}
218
+ onChange={handleSetKeyChange}
219
+ spellCheck={false}
220
+ className="h-8 font-mono text-[11px]"
221
+ placeholder="key"
222
+ />
223
+ </label>
224
+ <label className="block space-y-1">
225
+ <ConfigFieldLabel
226
+ label={t("blueprint.config.storageValue")}
227
+ hint={t("blueprint.config.storageValueHint")}
228
+ />
229
+ <Input
230
+ value={storageConfig.set.value}
231
+ onChange={handleSetValueChange}
232
+ spellCheck={false}
233
+ className="h-8 font-mono text-[11px]"
234
+ placeholder="{scope.value}"
235
+ />
236
+ </label>
237
+ </div>
238
+ </div>
239
+ );
240
+ }
@@ -8,6 +8,7 @@ import {
8
8
  resolveNodeEventConfig,
9
9
  resolveNodeFetchConfig,
10
10
  resolveNodeJsonConfig,
11
+ resolveNodeStorageConfig,
11
12
  resolveNodeLogicConfig,
12
13
  resolveRunnableNodeType,
13
14
  sanitizeBlueprintDocument,
@@ -197,6 +198,7 @@ export class BlueprintGraph {
197
198
  | "lifecyclePhase"
198
199
  | "fetchConfig"
199
200
  | "jsonConfig"
201
+ | "storageConfig"
200
202
  | "logicConfig"
201
203
  | "clockConfig"
202
204
  | "eventConfig"
@@ -241,6 +243,20 @@ export class BlueprintGraph {
241
243
  ...patch.jsonConfig,
242
244
  };
243
245
  }
246
+ if (patch.storageConfig) {
247
+ nextNode.storageConfig = {
248
+ ...resolveNodeStorageConfig(n),
249
+ ...patch.storageConfig,
250
+ read: {
251
+ ...resolveNodeStorageConfig(n).read,
252
+ ...patch.storageConfig.read,
253
+ },
254
+ set: {
255
+ ...resolveNodeStorageConfig(n).set,
256
+ ...patch.storageConfig.set,
257
+ },
258
+ };
259
+ }
244
260
  if (patch.logicConfig) {
245
261
  nextNode.logicConfig = {
246
262
  ...resolveNodeLogicConfig(n),
@@ -4,6 +4,7 @@ import type {
4
4
  JsonNodeConfig,
5
5
  LogicNodeConfig,
6
6
  PageLifecyclePhase,
7
+ StorageNodeConfig,
7
8
  ViewEventNodeConfig,
8
9
  ViewEventType,
9
10
  } from "@arronqzy/blueprint-dsl";
@@ -16,13 +17,16 @@ import {
16
17
  DEFAULT_JSON_NODE_CONFIG,
17
18
  DEFAULT_LOGIC_NODE_CONFIG,
18
19
  DEFAULT_LOGIC_NODE_TYPE,
20
+ DEFAULT_STORAGE_NODE_CONFIG,
19
21
  DEFAULT_VIEW_EVENT_NODE_CONFIG,
20
22
  EVENT_NODE_TYPE,
21
23
  FETCH_NODE_TYPE,
22
24
  JSON_NODE_TYPE,
23
25
  LIFECYCLE_NODE_TYPE,
24
26
  normalizeClockConfig,
27
+ normalizeStorageConfig,
25
28
  normalizeViewEventConfig,
29
+ STORAGE_NODE_TYPE,
26
30
  VIEW_NODE_TYPE,
27
31
  } from "@arronqzy/blueprint-dsl";
28
32
 
@@ -31,8 +35,9 @@ export type { JsonNodeConfig as BlueprintJsonConfig } from "@arronqzy/blueprint-
31
35
  export type { LogicNodeConfig as BlueprintLogicConfig } from "@arronqzy/blueprint-dsl";
32
36
  export type { ClockNodeConfig as BlueprintClockConfig } from "@arronqzy/blueprint-dsl";
33
37
  export type { ViewEventNodeConfig as BlueprintEventConfig } from "@arronqzy/blueprint-dsl";
38
+ export type { StorageNodeConfig as BlueprintStorageConfig } from "@arronqzy/blueprint-dsl";
34
39
 
35
- export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock" | "event";
40
+ export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "storage" | "clock" | "event";
36
41
 
37
42
  /** 决定右侧配置面板展示哪类配置 */
38
43
  export type BlueprintConfigSource =
@@ -43,6 +48,7 @@ export type BlueprintConfigSource =
43
48
  | "view"
44
49
  | "fetch"
45
50
  | "json"
51
+ | "storage"
46
52
  | "clock"
47
53
  | "event";
48
54
 
@@ -71,6 +77,8 @@ export type BlueprintGraphNode = {
71
77
  fetchConfig?: FetchRequestConfig;
72
78
  /** JSON 节点的 JSON 字符串配置 */
73
79
  jsonConfig?: JsonNodeConfig;
80
+ /** 存储节点的读写配置 */
81
+ storageConfig?: StorageNodeConfig;
74
82
  /** 逻辑节点的 JavaScript 脚本配置 */
75
83
  logicConfig?: LogicNodeConfig;
76
84
  /** 时钟节点的间隔与时间格式配置 */
@@ -108,8 +116,10 @@ export function resolveBlueprintConfigSource(
108
116
  if (node.role === "and") return "and";
109
117
  if (node.role === "fetch") return "fetch";
110
118
  if (node.role === "json") return "json";
119
+ if (node.role === "storage") return "storage";
111
120
  if (node.nodeType === FETCH_NODE_TYPE) return "fetch";
112
121
  if (node.nodeType === JSON_NODE_TYPE) return "json";
122
+ if (node.nodeType === STORAGE_NODE_TYPE) return "storage";
113
123
  if (node.nodeType === CLOCK_NODE_TYPE) return "clock";
114
124
  if (node.nodeType === EVENT_NODE_TYPE) return "event";
115
125
  if (node.nodeType === VIEW_NODE_TYPE) return "view";
@@ -187,6 +197,8 @@ export function resolveRunnableNodeType(
187
197
  return FETCH_NODE_TYPE;
188
198
  case "json":
189
199
  return JSON_NODE_TYPE;
200
+ case "storage":
201
+ return STORAGE_NODE_TYPE;
190
202
  case "logic":
191
203
  return DEFAULT_LOGIC_NODE_TYPE;
192
204
  default:
@@ -210,6 +222,7 @@ export const BLUEPRINT_CONFIG_TYPE_LABEL_KEYS: Record<
210
222
  event: "blueprint.node.typeEvent",
211
223
  fetch: "blueprint.node.typeFetch",
212
224
  json: "blueprint.node.typeJson",
225
+ storage: "blueprint.node.typeStorage",
213
226
  clock: "blueprint.node.typeClock",
214
227
  };
215
228
 
@@ -300,6 +313,15 @@ export function resolveNodeJsonConfig(
300
313
  };
301
314
  }
302
315
 
316
+ export function resolveNodeStorageConfig(
317
+ node: Pick<BlueprintGraphNode, "storageConfig">
318
+ ): StorageNodeConfig {
319
+ return normalizeStorageConfig({
320
+ ...DEFAULT_STORAGE_NODE_CONFIG,
321
+ ...node.storageConfig,
322
+ });
323
+ }
324
+
303
325
  export function resolveNodeLogicConfig(
304
326
  node: Pick<BlueprintGraphNode, "logicConfig">
305
327
  ): LogicNodeConfig {
@@ -399,6 +421,9 @@ export function sanitizeBlueprintDocument(
399
421
  if (next.role === "json") {
400
422
  return { ...next, configSource: "json" as const };
401
423
  }
424
+ if (next.role === "storage") {
425
+ return { ...next, configSource: "storage" as const };
426
+ }
402
427
  if (next.role === "and") {
403
428
  return { ...next, configSource: "and" as const };
404
429
  }
@@ -452,6 +477,7 @@ export function patchNodeConfigSource(
452
477
  logicConfig: undefined,
453
478
  clockConfig: undefined,
454
479
  eventConfig: undefined,
480
+ storageConfig: undefined,
455
481
  };
456
482
  }
457
483
 
@@ -469,6 +495,7 @@ export function patchNodeConfigSource(
469
495
  jsonConfig: undefined,
470
496
  logicConfig: undefined,
471
497
  eventConfig: undefined,
498
+ storageConfig: undefined,
472
499
  };
473
500
  }
474
501
 
@@ -486,6 +513,7 @@ export function patchNodeConfigSource(
486
513
  logicConfig: undefined,
487
514
  clockConfig: undefined,
488
515
  eventConfig: undefined,
516
+ storageConfig: undefined,
489
517
  };
490
518
  }
491
519
 
@@ -503,6 +531,25 @@ export function patchNodeConfigSource(
503
531
  logicConfig: undefined,
504
532
  clockConfig: undefined,
505
533
  eventConfig: undefined,
534
+ storageConfig: undefined,
535
+ };
536
+ }
537
+
538
+ if (configSource === "storage") {
539
+ return {
540
+ role: "storage",
541
+ nodeType: STORAGE_NODE_TYPE,
542
+ configSource: "storage",
543
+ storageConfig: resolveNodeStorageConfig(node),
544
+ lifecyclePhase: undefined,
545
+ viewElementId: undefined,
546
+ viewElementIds: undefined,
547
+ libraryBlueprintId: undefined,
548
+ fetchConfig: undefined,
549
+ jsonConfig: undefined,
550
+ logicConfig: undefined,
551
+ clockConfig: undefined,
552
+ eventConfig: undefined,
506
553
  };
507
554
  }
508
555
 
@@ -520,6 +567,7 @@ export function patchNodeConfigSource(
520
567
  logicConfig: undefined,
521
568
  clockConfig: undefined,
522
569
  eventConfig: undefined,
570
+ storageConfig: undefined,
523
571
  };
524
572
  }
525
573
 
@@ -537,6 +585,7 @@ export function patchNodeConfigSource(
537
585
  logicConfig: resolveNodeLogicConfig(node),
538
586
  clockConfig: undefined,
539
587
  eventConfig: undefined,
588
+ storageConfig: undefined,
540
589
  };
541
590
  }
542
591
 
@@ -554,6 +603,7 @@ export function patchNodeConfigSource(
554
603
  logicConfig: undefined,
555
604
  clockConfig: undefined,
556
605
  eventConfig: undefined,
606
+ storageConfig: undefined,
557
607
  };
558
608
  }
559
609
 
@@ -569,6 +619,7 @@ export function patchNodeConfigSource(
569
619
  jsonConfig: undefined,
570
620
  logicConfig: undefined,
571
621
  clockConfig: undefined,
622
+ storageConfig: undefined,
572
623
  };
573
624
  }
574
625
 
@@ -583,5 +634,6 @@ export function patchNodeConfigSource(
583
634
  logicConfig: undefined,
584
635
  clockConfig: undefined,
585
636
  eventConfig: undefined,
637
+ storageConfig: undefined,
586
638
  };
587
639
  }
@@ -9,6 +9,7 @@ import {
9
9
  resolveNodeFetchConfig,
10
10
  resolveNodeJsonConfig,
11
11
  resolveNodeLogicConfig,
12
+ resolveNodeStorageConfig,
12
13
  resolveViewElementIds,
13
14
  resolveNodeEventConfig,
14
15
  getViewEventTypeLabel,
@@ -65,6 +66,29 @@ export function resolveBlueprintNodeSummary(
65
66
  if (!json) return t("blueprint.node.summaryEmptyJson");
66
67
  return truncate(json.replace(/\s+/g, " "), 44);
67
68
  }
69
+ case "storage": {
70
+ const storage = resolveNodeStorageConfig(data);
71
+ const setKey = storage.set.key.trim();
72
+ const readKey = storage.read.key.trim();
73
+ const hasSet = Boolean(setKey) && storage.set.storages.length > 0;
74
+ const hasRead = Boolean(readKey);
75
+ if (hasSet && hasRead) {
76
+ return truncate(
77
+ t("blueprint.node.summaryStorageSetRead", {
78
+ setKey,
79
+ readKey,
80
+ }),
81
+ 44
82
+ );
83
+ }
84
+ if (hasSet) {
85
+ return truncate(t("blueprint.node.summaryStorageSet", { key: setKey }), 44);
86
+ }
87
+ if (hasRead) {
88
+ return truncate(t("blueprint.node.summaryStorageRead", { key: readKey }), 44);
89
+ }
90
+ return t("blueprint.node.summaryStorageUnset");
91
+ }
68
92
  case "logic": {
69
93
  const code = resolveNodeLogicConfig(data).sourceCode?.trim();
70
94
  if (!code) return t("blueprint.node.summaryNoScript");
@@ -4,6 +4,7 @@ export {
4
4
  DEFAULT_LOGIC_NODE_TYPE,
5
5
  LIFECYCLE_NODE_TYPE,
6
6
  JSON_NODE_TYPE,
7
+ STORAGE_NODE_TYPE,
7
8
  CLOCK_NODE_TYPE,
8
9
  AND_NODE_TYPE,
9
10
  EVENT_NODE_TYPE,
@@ -19,6 +19,7 @@ export type BlueprintFlowNodeData = {
19
19
  lifecyclePhase?: BlueprintGraphNode["lifecyclePhase"];
20
20
  fetchConfig?: BlueprintGraphNode["fetchConfig"];
21
21
  jsonConfig?: BlueprintGraphNode["jsonConfig"];
22
+ storageConfig?: BlueprintGraphNode["storageConfig"];
22
23
  clockConfig?: BlueprintGraphNode["clockConfig"];
23
24
  eventConfig?: BlueprintGraphNode["eventConfig"];
24
25
  logicConfig?: BlueprintGraphNode["logicConfig"];
@@ -71,6 +72,8 @@ export function toReactFlowNodes(document: BlueprintDocument): ReactFlowNodeView
71
72
  ? "fetch"
72
73
  : n.role === "json"
73
74
  ? "json"
75
+ : n.role === "storage"
76
+ ? "storage"
74
77
  : n.role === "event"
75
78
  ? "event"
76
79
  : "logic",
@@ -93,6 +96,7 @@ export function toReactFlowNodes(document: BlueprintDocument): ReactFlowNodeView
93
96
  lifecyclePhase: n.lifecyclePhase,
94
97
  fetchConfig: n.fetchConfig,
95
98
  jsonConfig: n.jsonConfig,
99
+ storageConfig: n.storageConfig,
96
100
  clockConfig: n.clockConfig,
97
101
  eventConfig: n.eventConfig,
98
102
  logicConfig: n.logicConfig,
@@ -46,6 +46,10 @@ export function nodeStructureSignature(
46
46
  viewElementIds?: string[];
47
47
  fetchConfig?: { url?: string; method?: string; apiBaseUrl?: string; swaggerDocsUrl?: string };
48
48
  jsonConfig?: { jsonString?: string };
49
+ storageConfig?: {
50
+ read?: { storage?: string; key?: string };
51
+ set?: { storages?: string[]; key?: string; value?: string };
52
+ };
49
53
  logicConfig?: { sourceCode?: string };
50
54
  clockConfig?: { intervalSeconds?: number; timeFormat?: string; outputCount?: number; emitImmediately?: boolean };
51
55
  eventConfig?: { eventTypes?: string[] };
@@ -54,7 +58,7 @@ export function nodeStructureSignature(
54
58
  return nodes
55
59
  .map(
56
60
  (n) =>
57
- `${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 ?? ""}:${(n.eventConfig?.eventTypes ?? []).join(",")}`
61
+ `${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.storageConfig?.read?.storage ?? ""}:${n.storageConfig?.read?.key ?? ""}:${(n.storageConfig?.set?.storages ?? []).join(",")}:${n.storageConfig?.set?.key ?? ""}:${n.storageConfig?.set?.value ?? ""}:${n.logicConfig?.sourceCode ?? ""}:${n.clockConfig?.intervalSeconds ?? ""}:${n.clockConfig?.timeFormat ?? ""}:${n.clockConfig?.outputCount ?? ""}:${n.clockConfig?.emitImmediately ?? ""}:${(n.eventConfig?.eventTypes ?? []).join(",")}`
58
62
  )
59
63
  .sort()
60
64
  .join("|");
@@ -9,7 +9,7 @@ export type BlueprintNodeCardProps = {
9
9
  subtitle?: string;
10
10
  /** 调试执行进度(如时钟 2/3) */
11
11
  progressLabel?: string;
12
- variant?: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "clock";
12
+ variant?: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "storage" | "clock";
13
13
  selected?: boolean;
14
14
  hideLeadingDot?: boolean;
15
15
  onSelect?: (nodeId: string) => void;
@@ -51,6 +51,11 @@ const variantStyle = {
51
51
  badge: "bg-teal-500/10 text-teal-700 dark:text-teal-300",
52
52
  dot: "bg-teal-500",
53
53
  },
54
+ storage: {
55
+ accent: "border-l-emerald-500 dark:border-l-emerald-400",
56
+ badge: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
57
+ dot: "bg-emerald-500",
58
+ },
54
59
  clock: {
55
60
  accent: "border-l-rose-500 dark:border-l-rose-400",
56
61
  badge: "bg-rose-500/10 text-rose-700 dark:text-rose-300",
@@ -0,0 +1,28 @@
1
+ import { useI18n } from "@arronqzy/i18n/react";
2
+ import type { NodeProps } from "@xyflow/react";
3
+
4
+ import { resolveBlueprintNodeTypeLabel } from "../graph/document";
5
+ import { resolveBlueprintNodeSummary } from "../graph/node-summary";
6
+ import { useBlueprintNodeSelect } from "../BlueprintCanvasContext";
7
+ import { resolveBlueprintNodeExecutionTone } from "../runtime/execution-overlay";
8
+ import type { BlueprintFlowNodeData } from "../types";
9
+ import { BlueprintNodeShell } from "./BlueprintNodeShell";
10
+
11
+ export function StorageFlowNode({ id, data }: NodeProps) {
12
+ const { t } = useI18n();
13
+ const nodeData = data as BlueprintFlowNodeData;
14
+ const onSelect = useBlueprintNodeSelect();
15
+
16
+ return (
17
+ <BlueprintNodeShell
18
+ nodeId={id}
19
+ label={nodeData.label}
20
+ meta={resolveBlueprintNodeTypeLabel(nodeData, t)}
21
+ subtitle={resolveBlueprintNodeSummary(nodeData, t)}
22
+ variant="storage"
23
+ selected={Boolean(nodeData.isSelected)}
24
+ executionTone={resolveBlueprintNodeExecutionTone(nodeData)}
25
+ onSelect={onSelect}
26
+ />
27
+ );
28
+ }
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  FETCH_NODE_TYPE,
3
+ STORAGE_NODE_TYPE,
3
4
  type RunnableGraph,
4
5
  } from "@arronqzy/blueprint-dsl";
5
6
 
6
7
  import {
7
8
  resolveNodeFetchConfig,
9
+ resolveNodeStorageConfig,
8
10
  resolveRunnableNodeType,
9
11
  resolveViewElementIds,
10
12
  type BlueprintDocument,
@@ -34,6 +36,10 @@ export function documentToRunnableGraph(
34
36
  ? resolveNodeFetchConfig(node)
35
37
  : node.fetchConfig,
36
38
  jsonConfig: node.jsonConfig,
39
+ storageConfig:
40
+ nodeType === STORAGE_NODE_TYPE
41
+ ? resolveNodeStorageConfig(node)
42
+ : node.storageConfig,
37
43
  logicConfig: node.logicConfig,
38
44
  clockConfig: node.clockConfig,
39
45
  eventConfig: node.eventConfig,
package/.eslintrc.cjs DELETED
@@ -1,5 +0,0 @@
1
- /** @type {import("eslint").Linter.Config} */
2
- module.exports = {
3
- root: true,
4
- extends: ["@arronqzy/eslint-config/index.js"],
5
- };
package/index.html DELETED
@@ -1,13 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>blue-print</title>
8
- </head>
9
- <body>
10
- <div id="app"></div>
11
- <script type="module" src="/src/main.tsx"></script>
12
- </body>
13
- </html>
@@ -1,6 +0,0 @@
1
- module.exports = {
2
- plugins: {
3
- tailwindcss: {},
4
- autoprefixer: {},
5
- },
6
- };
@@ -1,6 +0,0 @@
1
- import preset from "../tailwind/src/preset";
2
-
3
- export default {
4
- presets: [preset],
5
- content: ["./index.html", "./src/**/*.{ts,tsx}"],
6
- };
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "@arronqzy/typescript-config/react-library.json",
3
- "compilerOptions": {
4
- "baseUrl": ".",
5
- "paths": {
6
- "@arronqzy/blueprint-dsl": ["../blueprint-dsl/src/index.ts"],
7
- "@arronqzy/ui": ["../ui/index.ts"]
8
- }
9
- },
10
- "include": ["."],
11
- "exclude": ["node_modules"]
12
- }
package/vite.config.ts DELETED
@@ -1,16 +0,0 @@
1
- import path from "node:path";
2
-
3
- import react from "@vitejs/plugin-react";
4
- import { defineConfig } from "vite";
5
-
6
- export default defineConfig({
7
- plugins: [react()],
8
- resolve: {
9
- alias: {
10
- "@arronqzy/ui": path.resolve(process.cwd(), "../ui/index.ts"),
11
- },
12
- },
13
- server: {
14
- port: 31012,
15
- },
16
- });