@tari-project/tari-extension-query-builder 0.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.
Files changed (175) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +1 -0
  3. package/components.json +21 -0
  4. package/dist/App.d.ts +3 -0
  5. package/dist/App.d.ts.map +1 -0
  6. package/dist/codegen/BuilderCodegen.d.ts +20 -0
  7. package/dist/codegen/BuilderCodegen.d.ts.map +1 -0
  8. package/dist/codegen/sample.d.ts +3 -0
  9. package/dist/codegen/sample.d.ts.map +1 -0
  10. package/dist/components/query-builder/edges/button-edge.d.ts +3 -0
  11. package/dist/components/query-builder/edges/button-edge.d.ts.map +1 -0
  12. package/dist/components/query-builder/input/call-input-checkbox.d.ts +11 -0
  13. package/dist/components/query-builder/input/call-input-checkbox.d.ts.map +1 -0
  14. package/dist/components/query-builder/input/call-input-select.d.ts +14 -0
  15. package/dist/components/query-builder/input/call-input-select.d.ts.map +1 -0
  16. package/dist/components/query-builder/input/call-input-text.d.ts +18 -0
  17. package/dist/components/query-builder/input/call-input-text.d.ts.map +1 -0
  18. package/dist/components/query-builder/input/call-input.d.ts +13 -0
  19. package/dist/components/query-builder/input/call-input.d.ts.map +1 -0
  20. package/dist/components/query-builder/nodes/call-node.types.d.ts +4 -0
  21. package/dist/components/query-builder/nodes/call-node.types.d.ts.map +1 -0
  22. package/dist/components/query-builder/nodes/constants.d.ts +5 -0
  23. package/dist/components/query-builder/nodes/constants.d.ts.map +1 -0
  24. package/dist/components/query-builder/nodes/enter-connection.d.ts +3 -0
  25. package/dist/components/query-builder/nodes/enter-connection.d.ts.map +1 -0
  26. package/dist/components/query-builder/nodes/exit-connection.d.ts +3 -0
  27. package/dist/components/query-builder/nodes/exit-connection.d.ts.map +1 -0
  28. package/dist/components/query-builder/nodes/generic/generic-node.d.ts +5 -0
  29. package/dist/components/query-builder/nodes/generic/generic-node.d.ts.map +1 -0
  30. package/dist/components/query-builder/nodes/generic/node-icon.d.ts +8 -0
  31. package/dist/components/query-builder/nodes/generic/node-icon.d.ts.map +1 -0
  32. package/dist/components/query-builder/nodes/generic-node.types.d.ts +6 -0
  33. package/dist/components/query-builder/nodes/generic-node.types.d.ts.map +1 -0
  34. package/dist/components/query-builder/nodes/input/constants.d.ts +2 -0
  35. package/dist/components/query-builder/nodes/input/constants.d.ts.map +1 -0
  36. package/dist/components/query-builder/nodes/input/editable-label.d.ts +9 -0
  37. package/dist/components/query-builder/nodes/input/editable-label.d.ts.map +1 -0
  38. package/dist/components/query-builder/nodes/input/input-params-node.d.ts +5 -0
  39. package/dist/components/query-builder/nodes/input/input-params-node.d.ts.map +1 -0
  40. package/dist/components/query-builder/query-builder.d.ts +13 -0
  41. package/dist/components/query-builder/query-builder.d.ts.map +1 -0
  42. package/dist/components/ui/alert-dialog.d.ts +15 -0
  43. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  44. package/dist/components/ui/badge.d.ts +10 -0
  45. package/dist/components/ui/badge.d.ts.map +1 -0
  46. package/dist/components/ui/button.d.ts +11 -0
  47. package/dist/components/ui/button.d.ts.map +1 -0
  48. package/dist/components/ui/checkbox.d.ts +5 -0
  49. package/dist/components/ui/checkbox.d.ts.map +1 -0
  50. package/dist/components/ui/dropdown-menu.d.ts +26 -0
  51. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  52. package/dist/components/ui/input.d.ts +4 -0
  53. package/dist/components/ui/input.d.ts.map +1 -0
  54. package/dist/components/ui/label.d.ts +5 -0
  55. package/dist/components/ui/label.d.ts.map +1 -0
  56. package/dist/components/ui/loading-spinner.d.ts +7 -0
  57. package/dist/components/ui/loading-spinner.d.ts.map +1 -0
  58. package/dist/components/ui/popover.d.ts +8 -0
  59. package/dist/components/ui/popover.d.ts.map +1 -0
  60. package/dist/components/ui/select.d.ts +16 -0
  61. package/dist/components/ui/select.d.ts.map +1 -0
  62. package/dist/components/ui/separator.d.ts +5 -0
  63. package/dist/components/ui/separator.d.ts.map +1 -0
  64. package/dist/components/ui/sonner.d.ts +4 -0
  65. package/dist/components/ui/sonner.d.ts.map +1 -0
  66. package/dist/components/ui/textarea.d.ts +4 -0
  67. package/dist/components/ui/textarea.d.ts.map +1 -0
  68. package/dist/components/ui/tooltip.d.ts +8 -0
  69. package/dist/components/ui/tooltip.d.ts.map +1 -0
  70. package/dist/execute/AmbiguousOrderError.d.ts +6 -0
  71. package/dist/execute/AmbiguousOrderError.d.ts.map +1 -0
  72. package/dist/execute/CycleDetectedError.d.ts +4 -0
  73. package/dist/execute/CycleDetectedError.d.ts.map +1 -0
  74. package/dist/execute/ExecutionPlanner.d.ts +30 -0
  75. package/dist/execute/ExecutionPlanner.d.ts.map +1 -0
  76. package/dist/execute/ExecutionPlanner.spec.d.ts +2 -0
  77. package/dist/execute/ExecutionPlanner.spec.d.ts.map +1 -0
  78. package/dist/execute/MissingDataError.d.ts +5 -0
  79. package/dist/execute/MissingDataError.d.ts.map +1 -0
  80. package/dist/execute/types.d.ts +65 -0
  81. package/dist/execute/types.d.ts.map +1 -0
  82. package/dist/index.d.ts +6 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/lib/get-next-available.d.ts +2 -0
  85. package/dist/lib/get-next-available.d.ts.map +1 -0
  86. package/dist/lib/utils.d.ts +3 -0
  87. package/dist/lib/utils.d.ts.map +1 -0
  88. package/dist/main.d.ts +2 -0
  89. package/dist/main.d.ts.map +1 -0
  90. package/dist/query-builder/tari-type.d.ts +29 -0
  91. package/dist/query-builder/tari-type.d.ts.map +1 -0
  92. package/dist/query-builder/template-reader.d.ts +13 -0
  93. package/dist/query-builder/template-reader.d.ts.map +1 -0
  94. package/dist/store/persistence/handlers.d.ts +12 -0
  95. package/dist/store/persistence/handlers.d.ts.map +1 -0
  96. package/dist/store/persistence/types.d.ts +18 -0
  97. package/dist/store/persistence/types.d.ts.map +1 -0
  98. package/dist/store/persistence/v1_0.d.ts +5 -0
  99. package/dist/store/persistence/v1_0.d.ts.map +1 -0
  100. package/dist/store/store.d.ts +4 -0
  101. package/dist/store/store.d.ts.map +1 -0
  102. package/dist/store/types.d.ts +97 -0
  103. package/dist/store/types.d.ts.map +1 -0
  104. package/dist/tari-extension-query-builder.css +1 -0
  105. package/dist/tari-extension-query-builder.es.js +183368 -0
  106. package/dist/tari-extension-query-builder.umd.js +620 -0
  107. package/dist/types.d.ts +7 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/eslint.config.js +34 -0
  110. package/index.html +12 -0
  111. package/moon.yml +43 -0
  112. package/package.json +84 -0
  113. package/src/App.tsx +114 -0
  114. package/src/assets/react.svg +1 -0
  115. package/src/codegen/BuilderCodegen.ts +516 -0
  116. package/src/codegen/sample.ts +58 -0
  117. package/src/components/query-builder/edges/button-edge.tsx +50 -0
  118. package/src/components/query-builder/input/call-input-checkbox.tsx +54 -0
  119. package/src/components/query-builder/input/call-input-select.tsx +87 -0
  120. package/src/components/query-builder/input/call-input-text.tsx +98 -0
  121. package/src/components/query-builder/input/call-input.tsx +51 -0
  122. package/src/components/query-builder/nodes/call-node.types.ts +3 -0
  123. package/src/components/query-builder/nodes/constants.ts +4 -0
  124. package/src/components/query-builder/nodes/enter-connection.tsx +16 -0
  125. package/src/components/query-builder/nodes/exit-connection.tsx +16 -0
  126. package/src/components/query-builder/nodes/generic/generic-node.tsx +252 -0
  127. package/src/components/query-builder/nodes/generic/node-icon.tsx +37 -0
  128. package/src/components/query-builder/nodes/generic-node.types.ts +5 -0
  129. package/src/components/query-builder/nodes/input/constants.ts +1 -0
  130. package/src/components/query-builder/nodes/input/editable-label.tsx +142 -0
  131. package/src/components/query-builder/nodes/input/input-params-node.tsx +190 -0
  132. package/src/components/query-builder/query-builder.tsx +577 -0
  133. package/src/components/ui/alert-dialog.tsx +111 -0
  134. package/src/components/ui/badge.tsx +37 -0
  135. package/src/components/ui/button.tsx +50 -0
  136. package/src/components/ui/checkbox.tsx +27 -0
  137. package/src/components/ui/dropdown-menu.tsx +217 -0
  138. package/src/components/ui/input.tsx +21 -0
  139. package/src/components/ui/label.tsx +19 -0
  140. package/src/components/ui/loading-spinner.tsx +46 -0
  141. package/src/components/ui/popover.tsx +40 -0
  142. package/src/components/ui/select.tsx +158 -0
  143. package/src/components/ui/separator.tsx +26 -0
  144. package/src/components/ui/sonner.tsx +23 -0
  145. package/src/components/ui/textarea.tsx +18 -0
  146. package/src/components/ui/tooltip.tsx +46 -0
  147. package/src/execute/AmbiguousOrderError.ts +13 -0
  148. package/src/execute/CycleDetectedError.ts +7 -0
  149. package/src/execute/ExecutionPlanner.spec.ts +174 -0
  150. package/src/execute/ExecutionPlanner.ts +445 -0
  151. package/src/execute/MissingDataError.ts +10 -0
  152. package/src/execute/types.ts +87 -0
  153. package/src/index.css +124 -0
  154. package/src/index.ts +5 -0
  155. package/src/lib/get-next-available.ts +12 -0
  156. package/src/lib/utils.ts +6 -0
  157. package/src/main.tsx +13 -0
  158. package/src/query-builder/tari-type.ts +185 -0
  159. package/src/query-builder/template-reader.ts +69 -0
  160. package/src/root.css +4 -0
  161. package/src/store/persistence/handlers.ts +16 -0
  162. package/src/store/persistence/types.ts +14 -0
  163. package/src/store/persistence/v1_0.ts +35 -0
  164. package/src/store/store.ts +396 -0
  165. package/src/store/types.ts +115 -0
  166. package/src/stories/data/tari-swap-pool.json +317 -0
  167. package/src/stories/data/wallet-functions.json +580 -0
  168. package/src/types.ts +7 -0
  169. package/src/vite-env.d.ts +1 -0
  170. package/src/xy-theme.css +144 -0
  171. package/tsconfig.app.json +31 -0
  172. package/tsconfig.json +15 -0
  173. package/tsconfig.lib.json +13 -0
  174. package/tsconfig.node.json +24 -0
  175. package/vite.config.ts +35 -0
@@ -0,0 +1,577 @@
1
+ import {
2
+ ReactFlow,
3
+ Background,
4
+ Controls,
5
+ useViewport,
6
+ ReactFlowProvider,
7
+ Viewport,
8
+ Panel,
9
+ MiniMap,
10
+ } from "@xyflow/react";
11
+ import { CALL_NODE_DRAG_DROP_TYPE, GeneratedCodeType, TransactionProps } from "@tari-project/tari-extension-common";
12
+ import useStore from "../../store/store";
13
+ import { useShallow } from "zustand/shallow";
14
+ import { InputConnectionType, GenericNodeType, NodeType, QueryBuilderState } from "@/store/types";
15
+ import { useCallback, useEffect, useRef, useState } from "react";
16
+ import ButtonEdge from "./edges/button-edge";
17
+ import { TariFlowNodeDetails } from "@/types";
18
+ import { TemplateReader } from "@/query-builder/template-reader";
19
+ import { Toaster } from "@/components/ui/sonner";
20
+
21
+ import "../../index.css";
22
+ import "@xyflow/react/dist/style.css";
23
+ import "@/xy-theme.css";
24
+ import {
25
+ DropdownMenu,
26
+ DropdownMenuContent,
27
+ DropdownMenuItem,
28
+ DropdownMenuPortal,
29
+ DropdownMenuSeparator,
30
+ DropdownMenuSub,
31
+ DropdownMenuSubContent,
32
+ DropdownMenuSubTrigger,
33
+ DropdownMenuTrigger,
34
+ } from "../ui/dropdown-menu";
35
+ import { Button } from "../ui/button";
36
+ import {
37
+ ArchiveIcon,
38
+ CheckCircledIcon,
39
+ Component1Icon,
40
+ EnterIcon,
41
+ InputIcon,
42
+ LayersIcon,
43
+ PlayIcon,
44
+ RocketIcon,
45
+ } from "@radix-ui/react-icons";
46
+ import GenericNode from "./nodes/generic/generic-node";
47
+ import InputParamsNode from "./nodes/input/input-params-node";
48
+ import { ExecutionPlanner } from "@/execute/ExecutionPlanner";
49
+ import { AmbiguousOrderError } from "@/execute/AmbiguousOrderError";
50
+ import { CycleDetectedError } from "@/execute/CycleDetectedError";
51
+ import {
52
+ AlertDialog,
53
+ AlertDialogCancel,
54
+ AlertDialogContent,
55
+ AlertDialogDescription,
56
+ AlertDialogFooter,
57
+ AlertDialogHeader,
58
+ AlertDialogTitle,
59
+ } from "../ui/alert-dialog";
60
+ import { Amount, Transaction } from "@tari-project/tarijs-all";
61
+ import { MissingDataError } from "@/execute/MissingDataError";
62
+ import { toast } from "sonner";
63
+ import { LoadingSpinner } from "../ui/loading-spinner";
64
+ import { BuilderCodegen } from "@/codegen/BuilderCodegen";
65
+ import { getNextAvailable } from "@/lib/get-next-available";
66
+ import { ALLOCATE_COMPONENT_ADDRESS_RESULT, ALLOCATE_RESOURCE_ADDRESS_RESULT } from "./nodes/generic-node.types";
67
+
68
+ export type Theme = "dark" | "light";
69
+
70
+ const selector = (state: QueryBuilderState) => ({
71
+ nodes: state.nodes,
72
+ edges: state.edges,
73
+ setReadOnly: state.setReadOnly,
74
+ onNodesChange: state.onNodesChange,
75
+ onEdgesChange: state.onEdgesChange,
76
+ onConnect: state.onConnect,
77
+ addNode: state.addNode,
78
+ isValidConnection: state.isValidConnection,
79
+ updateCenter: state.updateCenter,
80
+ addNodeAt: state.addNodeAt,
81
+ getNodeById: state.getNodeById,
82
+ isValidInputParamsTitle: state.isValidInputParamsTitle,
83
+ });
84
+
85
+ export interface QueryBuilderProps {
86
+ theme: Theme;
87
+ readOnly?: boolean;
88
+ getTransactionProps?: () => Promise<TransactionProps>;
89
+ executeTransaction?: (transaction: Transaction, dryRun: boolean) => Promise<void>;
90
+ showGeneratedCode?: (code: string, type: GeneratedCodeType) => Promise<void>;
91
+ }
92
+
93
+ const nodeTypes = {
94
+ [NodeType.GenericNode]: GenericNode,
95
+ [NodeType.InputParamsNode]: InputParamsNode,
96
+ };
97
+
98
+ const edgeTypes = {
99
+ buttonEdge: ButtonEdge,
100
+ };
101
+
102
+ function Flow({
103
+ theme,
104
+ readOnly = false,
105
+ getTransactionProps,
106
+ executeTransaction,
107
+ showGeneratedCode,
108
+ }: QueryBuilderProps) {
109
+ const {
110
+ nodes,
111
+ edges,
112
+ setReadOnly,
113
+ onNodesChange,
114
+ onEdgesChange,
115
+ onConnect,
116
+ isValidConnection,
117
+ updateCenter,
118
+ addNodeAt,
119
+ getNodeById,
120
+ isValidInputParamsTitle,
121
+ } = useStore(useShallow(selector));
122
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
123
+ const [viewport, setViewport] = useState(useViewport());
124
+ const [loading, setLoading] = useState(false);
125
+ const [isErrorDialogOpen, setIsErrorDialogOpen] = useState(false);
126
+ const [errorMessage, setErrorMessage] = useState("");
127
+ const reactflowRef = useRef<HTMLDivElement>(null);
128
+
129
+ const executeEnabled = !!getTransactionProps && !!executeTransaction;
130
+ const generateCodeEnabled = !!getTransactionProps && !!showGeneratedCode;
131
+
132
+ const onMove = useCallback(
133
+ (_event: unknown, viewport: Viewport) => {
134
+ setViewport(viewport);
135
+ },
136
+ [setViewport],
137
+ );
138
+
139
+ const onDragOver = useCallback((event: React.DragEvent<HTMLElement>) => {
140
+ event.preventDefault();
141
+ event.dataTransfer.dropEffect = "move";
142
+ }, []);
143
+
144
+ const onDrop = useCallback(
145
+ (event: React.DragEvent<HTMLElement>) => {
146
+ event.preventDefault();
147
+
148
+ const data = event.dataTransfer.getData(CALL_NODE_DRAG_DROP_TYPE);
149
+ if (data) {
150
+ const json = JSON.parse(data) as TariFlowNodeDetails;
151
+ const reader = new TemplateReader(json.template, json.templateAddress);
152
+
153
+ const flowX = (event.clientX - viewport.x) / viewport.zoom;
154
+ const flowY = (event.clientY - viewport.y) / viewport.zoom;
155
+
156
+ const nodeData = reader.getGenericNode(json.functionName);
157
+ if (nodeData) {
158
+ addNodeAt(nodeData, { x: flowX, y: flowY });
159
+ }
160
+ }
161
+ },
162
+ [addNodeAt, viewport],
163
+ );
164
+
165
+ const buildTransactionDescriptions = useCallback(
166
+ async (getTransactionProps: () => Promise<TransactionProps>) => {
167
+ const planner = new ExecutionPlanner(nodes, edges);
168
+ try {
169
+ const executionOrder = planner.getExecutionOrder();
170
+ const { accountAddress, fee } = await getTransactionProps();
171
+ const details = planner.buildTransactionDescription(executionOrder, accountAddress, new Amount(fee));
172
+ return { planner, details };
173
+ } catch (e) {
174
+ let errorMessage = "Failed to determine execution order";
175
+ if (e instanceof AmbiguousOrderError) {
176
+ const getNodeName = (id: string) => {
177
+ const node = getNodeById(id);
178
+ if (!node || node.type !== NodeType.GenericNode) {
179
+ return id;
180
+ }
181
+ if (node.data.type === GenericNodeType.StartNode) {
182
+ return "Start node";
183
+ }
184
+ return node.data.title ?? id;
185
+ };
186
+ const nodeA = getNodeName(e.nodeA);
187
+ const nodeB = getNodeName(e.nodeB);
188
+ if (nodeA && nodeB) {
189
+ errorMessage = `Ambiguous order between "${nodeA}" and "${nodeB}" operations. Please, add explicit connections.`;
190
+ }
191
+ } else if (e instanceof CycleDetectedError) {
192
+ errorMessage = "Cycle detected! Make sure to eliminate it.";
193
+ } else if (e instanceof MissingDataError) {
194
+ const node = e.nodes[0];
195
+ errorMessage =
196
+ e.nodes.length > 1 ? `Missing data in node "${node}" and other nodes."` : `Missing data in node "${node}"`;
197
+ } else if (e instanceof Error) {
198
+ errorMessage = e.message;
199
+ } else {
200
+ console.log(e);
201
+ }
202
+ throw new Error(errorMessage);
203
+ }
204
+ },
205
+ [nodes, edges, getNodeById],
206
+ );
207
+
208
+ const handleExecute = useCallback(
209
+ async (dryRun: boolean) => {
210
+ if (!getTransactionProps || !executeTransaction) {
211
+ return;
212
+ }
213
+
214
+ setLoading(true);
215
+ try {
216
+ const { planner, details } = await buildTransactionDescriptions(getTransactionProps);
217
+ const transaction = planner.buildTransaction(details);
218
+ await executeTransaction(transaction, dryRun);
219
+ toast.success("Transaction executed");
220
+ } catch (e) {
221
+ if (e instanceof Error) {
222
+ setErrorMessage(e.message);
223
+ setIsErrorDialogOpen(true);
224
+ }
225
+ }
226
+ setLoading(false);
227
+ },
228
+ [getTransactionProps, executeTransaction, buildTransactionDescriptions],
229
+ );
230
+
231
+ const handleGenerateCode = useCallback(
232
+ async (typescript: boolean) => {
233
+ if (!getTransactionProps || !showGeneratedCode) {
234
+ return;
235
+ }
236
+
237
+ setLoading(true);
238
+ try {
239
+ const { details } = await buildTransactionDescriptions(getTransactionProps);
240
+ const codegen = new BuilderCodegen(details);
241
+ const code = typescript ? codegen.generateTypescriptCode() : codegen.generateJavascriptCode();
242
+ await showGeneratedCode(code, typescript ? GeneratedCodeType.Typescript : GeneratedCodeType.Javascript);
243
+ } catch (e) {
244
+ if (e instanceof Error) {
245
+ setErrorMessage(e.message);
246
+ setIsErrorDialogOpen(true);
247
+ }
248
+ }
249
+ setLoading(false);
250
+ },
251
+ [getTransactionProps, showGeneratedCode, buildTransactionDescriptions],
252
+ );
253
+
254
+ const handleAddStartNode = useCallback(() => {
255
+ addNodeAt({
256
+ type: NodeType.GenericNode,
257
+ data: {
258
+ type: GenericNodeType.StartNode,
259
+ hasExitConnection: true,
260
+ icon: "enter",
261
+ largeCaption: "START",
262
+ },
263
+ });
264
+ }, [addNodeAt]);
265
+
266
+ const handleAddInputParamsNode = useCallback(() => {
267
+ const title = getNextAvailable("input", (title) => isValidInputParamsTitle("", title));
268
+ addNodeAt({
269
+ type: NodeType.InputParamsNode,
270
+ data: {
271
+ title,
272
+ values: {},
273
+ inputs: [],
274
+ },
275
+ });
276
+ }, [addNodeAt, isValidInputParamsTitle]);
277
+
278
+ const handleAddEmitLogNode = useCallback(() => {
279
+ addNodeAt({
280
+ type: NodeType.GenericNode,
281
+ data: {
282
+ type: GenericNodeType.EmitLogNode,
283
+ hasEnterConnection: true,
284
+ hasExitConnection: true,
285
+ icon: "rocket",
286
+ title: "Emit Log",
287
+ inputs: [
288
+ {
289
+ inputConnectionType: InputConnectionType.None,
290
+ name: "log_level",
291
+ label: "Log Level",
292
+ type: "String",
293
+ validValues: ["Error", "Warn", "Info", "Debug"],
294
+ },
295
+ {
296
+ inputConnectionType: InputConnectionType.None,
297
+ name: "message",
298
+ label: "Message",
299
+ type: "String",
300
+ },
301
+ ],
302
+ },
303
+ });
304
+ }, [addNodeAt]);
305
+
306
+ const handleAddAssertBucketContainsNode = useCallback(() => {
307
+ addNodeAt({
308
+ type: NodeType.GenericNode,
309
+ data: {
310
+ type: GenericNodeType.AssertBucketContains,
311
+ hasEnterConnection: true,
312
+ hasExitConnection: true,
313
+ icon: "check-circled",
314
+ title: "Assert Bucket Contains",
315
+ inputs: [
316
+ {
317
+ inputConnectionType: InputConnectionType.None,
318
+ name: "key",
319
+ label: "Key",
320
+ type: { Vec: "U8" },
321
+ },
322
+ {
323
+ inputConnectionType: InputConnectionType.None,
324
+ name: "resource_address",
325
+ label: "Resource Address",
326
+ type: { Other: { name: "ResourceAddress" } },
327
+ },
328
+ {
329
+ inputConnectionType: InputConnectionType.None,
330
+ name: "min_amount",
331
+ label: "Minimum Amount",
332
+ type: { Other: { name: "Amount" } },
333
+ },
334
+ ],
335
+ },
336
+ });
337
+ }, [addNodeAt]);
338
+
339
+ const handleAddAllocateComponentAddressNode = useCallback(() => {
340
+ addNodeAt({
341
+ type: NodeType.GenericNode,
342
+ data: {
343
+ type: GenericNodeType.AllocateComponentAddress,
344
+ hasEnterConnection: true,
345
+ hasExitConnection: true,
346
+ icon: "component",
347
+ title: "Allocate Component Address",
348
+ inputs: [
349
+ {
350
+ inputConnectionType: InputConnectionType.None,
351
+ name: "component_name",
352
+ label: "Component Name",
353
+ type: "String",
354
+ },
355
+ ],
356
+ output: {
357
+ type: { Other: { name: "ComponentAddressAllocation" } },
358
+ name: ALLOCATE_COMPONENT_ADDRESS_RESULT,
359
+ label: "ComponentAddressAllocation",
360
+ },
361
+ },
362
+ });
363
+ }, [addNodeAt]);
364
+
365
+ const handleAddAllocateResourceAddressNode = useCallback(() => {
366
+ addNodeAt({
367
+ type: NodeType.GenericNode,
368
+ data: {
369
+ type: GenericNodeType.AllocateResourceAddress,
370
+ hasEnterConnection: true,
371
+ hasExitConnection: true,
372
+ icon: "archive",
373
+ title: "Allocate Resource Address",
374
+ inputs: [
375
+ {
376
+ inputConnectionType: InputConnectionType.None,
377
+ name: "resource_name",
378
+ label: "Resource Name",
379
+ type: "String",
380
+ },
381
+ ],
382
+ output: {
383
+ type: { Other: { name: "ResourceAddressAllocation" } },
384
+ name: ALLOCATE_RESOURCE_ADDRESS_RESULT,
385
+ label: "ResourceAddressAllocation",
386
+ },
387
+ },
388
+ });
389
+ }, [addNodeAt]);
390
+
391
+ useEffect(() => {
392
+ const root = window.document.documentElement;
393
+ root.classList.remove("light", "dark");
394
+ root.classList.add(theme);
395
+ }, [theme]);
396
+
397
+ useEffect(() => {
398
+ setReadOnly(readOnly);
399
+ }, [setReadOnly, readOnly]);
400
+
401
+ useEffect(() => {
402
+ const updateDimensions = () => {
403
+ if (reactflowRef.current) {
404
+ setDimensions({
405
+ width: reactflowRef.current.clientWidth,
406
+ height: reactflowRef.current.clientHeight,
407
+ });
408
+ }
409
+ };
410
+
411
+ updateDimensions();
412
+ window.addEventListener("resize", updateDimensions);
413
+
414
+ return () => {
415
+ window.removeEventListener("resize", updateDimensions);
416
+ };
417
+ }, []);
418
+
419
+ useEffect(() => {
420
+ if (!reactflowRef.current) return;
421
+
422
+ const { width, height } = dimensions;
423
+
424
+ const centerX = (width / 2 - viewport.x) / viewport.zoom;
425
+ const centerY = (height / 2 - viewport.y) / viewport.zoom;
426
+
427
+ updateCenter(centerX, centerY);
428
+ }, [dimensions, viewport, updateCenter]);
429
+
430
+ return (
431
+ <>
432
+ <ReactFlowProvider>
433
+ <ReactFlow
434
+ ref={reactflowRef}
435
+ nodesConnectable={!readOnly}
436
+ nodesDraggable={!readOnly}
437
+ nodesFocusable={!readOnly}
438
+ edgesFocusable={!readOnly}
439
+ edgesReconnectable={!readOnly}
440
+ nodeTypes={nodeTypes}
441
+ edgeTypes={edgeTypes}
442
+ nodes={nodes}
443
+ edges={edges}
444
+ onNodesChange={onNodesChange}
445
+ onEdgesChange={onEdgesChange}
446
+ onConnect={onConnect}
447
+ onMove={onMove}
448
+ onDragOver={onDragOver}
449
+ onDrop={onDrop}
450
+ colorMode={theme}
451
+ fitView
452
+ minZoom={0.05}
453
+ proOptions={{ hideAttribution: true }}
454
+ defaultEdgeOptions={{
455
+ type: "buttonEdge",
456
+ }}
457
+ isValidConnection={isValidConnection}
458
+ >
459
+ <Panel position="top-right" style={{ right: "15px" }}>
460
+ <DropdownMenu modal={false}>
461
+ <DropdownMenuTrigger asChild>
462
+ <Button variant="outline">
463
+ {loading ? <LoadingSpinner type="short" className="h-4 w-4 animate-spin" /> : "..."}
464
+ </Button>
465
+ </DropdownMenuTrigger>
466
+ <DropdownMenuContent className="w-56">
467
+ <DropdownMenuItem
468
+ onSelect={() => {
469
+ handleExecute(false).catch(console.log);
470
+ }}
471
+ disabled={!executeEnabled}
472
+ >
473
+ <PlayIcon /> Execute
474
+ </DropdownMenuItem>
475
+ <DropdownMenuItem
476
+ onSelect={() => {
477
+ handleExecute(true).catch(console.log);
478
+ }}
479
+ disabled={!executeEnabled}
480
+ >
481
+ <LayersIcon /> Execute - Dry Run
482
+ </DropdownMenuItem>
483
+ <DropdownMenuSeparator />
484
+ <DropdownMenuSub>
485
+ <DropdownMenuSubTrigger hidden={!generateCodeEnabled}>Generate Code</DropdownMenuSubTrigger>
486
+ <DropdownMenuPortal>
487
+ <DropdownMenuSubContent>
488
+ <DropdownMenuItem
489
+ onSelect={() => {
490
+ handleGenerateCode(true).catch(console.log);
491
+ }}
492
+ >
493
+ TypeScript
494
+ </DropdownMenuItem>
495
+ <DropdownMenuItem
496
+ onSelect={() => {
497
+ handleGenerateCode(false).catch(console.log);
498
+ }}
499
+ >
500
+ JavaScript
501
+ </DropdownMenuItem>
502
+ </DropdownMenuSubContent>
503
+ </DropdownMenuPortal>
504
+ </DropdownMenuSub>
505
+ <DropdownMenuSub>
506
+ <DropdownMenuSubTrigger>Add Instruction</DropdownMenuSubTrigger>
507
+ <DropdownMenuPortal>
508
+ <DropdownMenuSubContent>
509
+ <DropdownMenuItem onSelect={handleAddInputParamsNode}>
510
+ <InputIcon /> Input Parameters Node
511
+ </DropdownMenuItem>
512
+ <DropdownMenuItem onSelect={handleAddStartNode}>
513
+ <EnterIcon /> Start Node
514
+ </DropdownMenuItem>
515
+ <DropdownMenuItem onSelect={handleAddEmitLogNode}>
516
+ <RocketIcon /> Emit Log
517
+ </DropdownMenuItem>
518
+ <DropdownMenuItem onSelect={handleAddAssertBucketContainsNode}>
519
+ <CheckCircledIcon />
520
+ Assert Bucket Contains
521
+ </DropdownMenuItem>
522
+ <DropdownMenuItem onSelect={handleAddAllocateComponentAddressNode}>
523
+ <Component1Icon />
524
+ Allocate Component Address
525
+ </DropdownMenuItem>
526
+ <DropdownMenuItem onSelect={handleAddAllocateResourceAddressNode}>
527
+ <ArchiveIcon />
528
+ Allocate Resource Address
529
+ </DropdownMenuItem>
530
+ </DropdownMenuSubContent>
531
+ </DropdownMenuPortal>
532
+ </DropdownMenuSub>
533
+ </DropdownMenuContent>
534
+ </DropdownMenu>
535
+ </Panel>
536
+ <Background />
537
+ <Controls />
538
+ <MiniMap nodeStrokeWidth={3} />
539
+ </ReactFlow>
540
+ </ReactFlowProvider>
541
+ <AlertDialog open={isErrorDialogOpen} onOpenChange={setIsErrorDialogOpen}>
542
+ <AlertDialogContent className="border-[var(--foreground)]">
543
+ <AlertDialogHeader>
544
+ <AlertDialogTitle>Execution failed</AlertDialogTitle>
545
+ <AlertDialogDescription>{errorMessage}</AlertDialogDescription>
546
+ </AlertDialogHeader>
547
+ <AlertDialogFooter>
548
+ <AlertDialogCancel>Dismiss</AlertDialogCancel>
549
+ </AlertDialogFooter>
550
+ </AlertDialogContent>
551
+ </AlertDialog>
552
+ </>
553
+ );
554
+ }
555
+
556
+ function QueryBuilder({
557
+ theme,
558
+ readOnly = false,
559
+ getTransactionProps,
560
+ executeTransaction,
561
+ showGeneratedCode,
562
+ }: QueryBuilderProps) {
563
+ return (
564
+ <ReactFlowProvider>
565
+ <Flow
566
+ theme={theme}
567
+ readOnly={readOnly}
568
+ getTransactionProps={getTransactionProps}
569
+ executeTransaction={executeTransaction}
570
+ showGeneratedCode={showGeneratedCode}
571
+ />
572
+ <Toaster />
573
+ </ReactFlowProvider>
574
+ );
575
+ }
576
+
577
+ export default QueryBuilder;
@@ -0,0 +1,111 @@
1
+ import * as React from "react";
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
3
+
4
+ import { cn } from "@/lib/utils";
5
+ import { buttonVariants } from "@/components/ui/button";
6
+
7
+ function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
8
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
9
+ }
10
+
11
+ function AlertDialogTrigger({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
12
+ return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
13
+ }
14
+
15
+ function AlertDialogPortal({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
16
+ return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
17
+ }
18
+
19
+ function AlertDialogOverlay({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
20
+ return (
21
+ <AlertDialogPrimitive.Overlay
22
+ data-slot="alert-dialog-overlay"
23
+ className={cn(
24
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ function AlertDialogContent({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
33
+ return (
34
+ <AlertDialogPortal>
35
+ <AlertDialogOverlay />
36
+ <AlertDialogPrimitive.Content
37
+ data-slot="alert-dialog-content"
38
+ className={cn(
39
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ </AlertDialogPortal>
45
+ );
46
+ }
47
+
48
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
49
+ return (
50
+ <div
51
+ data-slot="alert-dialog-header"
52
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
59
+ return (
60
+ <div
61
+ data-slot="alert-dialog-footer"
62
+ className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function AlertDialogTitle({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
69
+ return (
70
+ <AlertDialogPrimitive.Title
71
+ data-slot="alert-dialog-title"
72
+ className={cn("text-lg font-semibold", className)}
73
+ {...props}
74
+ />
75
+ );
76
+ }
77
+
78
+ function AlertDialogDescription({
79
+ className,
80
+ ...props
81
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
82
+ return (
83
+ <AlertDialogPrimitive.Description
84
+ data-slot="alert-dialog-description"
85
+ className={cn("text-muted-foreground text-sm", className)}
86
+ {...props}
87
+ />
88
+ );
89
+ }
90
+
91
+ function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
92
+ return <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />;
93
+ }
94
+
95
+ function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
96
+ return <AlertDialogPrimitive.Cancel className={cn(buttonVariants({ variant: "outline" }), className)} {...props} />;
97
+ }
98
+
99
+ export {
100
+ AlertDialog,
101
+ AlertDialogPortal,
102
+ AlertDialogOverlay,
103
+ AlertDialogTrigger,
104
+ AlertDialogContent,
105
+ AlertDialogHeader,
106
+ AlertDialogFooter,
107
+ AlertDialogTitle,
108
+ AlertDialogDescription,
109
+ AlertDialogAction,
110
+ AlertDialogCancel,
111
+ };