@bemedev/mind-flow 0.0.1
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/lib/helpers/createContext.d.ts +21 -0
- package/lib/helpers/createContext.d.ts.map +1 -0
- package/lib/index.cjs.js +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.es.js +859 -0
- package/lib/output.css +2 -0
- package/lib/server.cjs.js +1058 -0
- package/lib/server.es.js +1056 -0
- package/lib/services/main.machine.d.ts +1198 -0
- package/lib/services/main.machine.d.ts.map +1 -0
- package/lib/services/main.typings.d.ts +68 -0
- package/lib/services/main.typings.d.ts.map +1 -0
- package/lib/ui/Flow.d.ts +13 -0
- package/lib/ui/Flow.d.ts.map +1 -0
- package/lib/ui/components/Bounds.d.ts +10 -0
- package/lib/ui/components/Bounds.d.ts.map +1 -0
- package/lib/ui/components/EdgeComponent.d.ts +20 -0
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -0
- package/lib/ui/components/EdgesBoard.d.ts +9 -0
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -0
- package/lib/ui/components/FlowChart.context.d.ts +1228 -0
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -0
- package/lib/ui/components/FlowChart.d.ts +63 -0
- package/lib/ui/components/FlowChart.d.ts.map +1 -0
- package/lib/ui/components/FlowChart.data.d.ts +67 -0
- package/lib/ui/components/FlowChart.data.d.ts.map +1 -0
- package/lib/ui/components/NodeComponent.d.ts +34 -0
- package/lib/ui/components/NodeComponent.d.ts.map +1 -0
- package/lib/ui/components/NodesBoard.d.ts +9 -0
- package/lib/ui/components/NodesBoard.d.ts.map +1 -0
- package/lib/ui/components/classes.d.ts +6 -0
- package/lib/ui/components/classes.d.ts.map +1 -0
- package/package.json +146 -0
- package/src/README.md +36 -0
- package/src/helpers/createContext.ts +37 -0
- package/src/index.ts +3 -0
- package/src/input.css +15 -0
- package/src/services/main.machine.ts +247 -0
- package/src/services/main.typings.ts +50 -0
- package/src/ui/Flow.tsx +18 -0
- package/src/ui/components/Bounds.tsx +87 -0
- package/src/ui/components/EdgeComponent.tsx +106 -0
- package/src/ui/components/EdgesBoard.tsx +56 -0
- package/src/ui/components/FlowChart.context.ts +373 -0
- package/src/ui/components/FlowChart.data.ts +89 -0
- package/src/ui/components/FlowChart.tsx +112 -0
- package/src/ui/components/NodeComponent.tsx +288 -0
- package/src/ui/components/NodesBoard.tsx +298 -0
- package/src/ui/components/classes.ts +85 -0
package/lib/server.es.js
ADDED
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
import { createComponent, escape, ssr, ssrAttribute, ssrHydrationKey, ssrStyleProperty } from "solid-js/web";
|
|
2
|
+
import { For, Show, createContext, createEffect, createMemo, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
|
|
3
|
+
import { createMachine, interpret } from "@bemedev/app";
|
|
4
|
+
import { dequal } from "dequal/lite";
|
|
5
|
+
import { produce } from "solid-js/store";
|
|
6
|
+
import { type } from "@bemedev/app/bemedev";
|
|
7
|
+
import { nanoid } from "nanoid";
|
|
8
|
+
import { useState } from "@bemedev/app-solidjs";
|
|
9
|
+
import { DragDropProvider, DragDropSensors, DragOverlay, createDraggable, useDragDropContext } from "@thisbeyond/solid-dnd";
|
|
10
|
+
import { dequal as dequal$1 } from "dequal";
|
|
11
|
+
//#region src/ui/components/classes.ts
|
|
12
|
+
/**
|
|
13
|
+
* List of Tailwind CSS class names used across flowchart UI components to
|
|
14
|
+
* ensure compilation safelist.
|
|
15
|
+
*/
|
|
16
|
+
var CLASSES = [
|
|
17
|
+
"fill-transparent",
|
|
18
|
+
"cursor-pointer",
|
|
19
|
+
"relative",
|
|
20
|
+
"stroke-[rgba(168,168,168,0.4)]",
|
|
21
|
+
"stroke-3",
|
|
22
|
+
"stroke-[rgba(168,168,168,1)]",
|
|
23
|
+
"stroke-4",
|
|
24
|
+
"z-100",
|
|
25
|
+
"stroke-[rgba(168,168,168,0.8)]",
|
|
26
|
+
"pointer-events-all",
|
|
27
|
+
"w-[100px]",
|
|
28
|
+
"h-[100px]",
|
|
29
|
+
"bg-white",
|
|
30
|
+
"fill-white",
|
|
31
|
+
"pointer-events-none",
|
|
32
|
+
"absolute",
|
|
33
|
+
"top-0",
|
|
34
|
+
"w-full",
|
|
35
|
+
"h-full",
|
|
36
|
+
"overflow-hidden",
|
|
37
|
+
"overflow-scroll",
|
|
38
|
+
"h-[150vh]",
|
|
39
|
+
"w-[2160px]",
|
|
40
|
+
"bg-size-[30px_30px]",
|
|
41
|
+
"flex",
|
|
42
|
+
"flex-col",
|
|
43
|
+
"cursor-grab",
|
|
44
|
+
"rounded-md",
|
|
45
|
+
"shadow-[1px_1px_11px_-6px_rgba(0,0,0,0.75)]",
|
|
46
|
+
"select-none",
|
|
47
|
+
"transition-[border,box-shadow]",
|
|
48
|
+
"duration-200",
|
|
49
|
+
"ease-in-out",
|
|
50
|
+
"hover:shadow-[2px_2px_12px_-6px_rgba(0,0,0,0.75)]",
|
|
51
|
+
"draggable",
|
|
52
|
+
"border",
|
|
53
|
+
"border-[#e38c29]",
|
|
54
|
+
"z-[100]",
|
|
55
|
+
"border-[#e6d4be]",
|
|
56
|
+
"z-[1]",
|
|
57
|
+
"items-center",
|
|
58
|
+
"justify-end",
|
|
59
|
+
"-top-[30px]",
|
|
60
|
+
"right-0",
|
|
61
|
+
"transition-all",
|
|
62
|
+
"space-x-2",
|
|
63
|
+
"opacity-100",
|
|
64
|
+
"w-0",
|
|
65
|
+
"-right-3",
|
|
66
|
+
"opacity-0",
|
|
67
|
+
"w-6",
|
|
68
|
+
"h-6",
|
|
69
|
+
"fill-[#a11111]",
|
|
70
|
+
"rounded-full",
|
|
71
|
+
"size-6",
|
|
72
|
+
"bg-green-500",
|
|
73
|
+
"p-0.5",
|
|
74
|
+
"hover:bg-green-600",
|
|
75
|
+
"font-bold",
|
|
76
|
+
"text-center",
|
|
77
|
+
"overflow-visible",
|
|
78
|
+
"bg-blue-500",
|
|
79
|
+
"text-white",
|
|
80
|
+
"rounded-lg",
|
|
81
|
+
"hover:bg-blue-600",
|
|
82
|
+
"justify-center",
|
|
83
|
+
"p-3",
|
|
84
|
+
"border-b",
|
|
85
|
+
"border-[#f0f0f0]",
|
|
86
|
+
"cursor-default",
|
|
87
|
+
"z-[-3]",
|
|
88
|
+
"-left-[18px]",
|
|
89
|
+
"bg-[#e38b29]",
|
|
90
|
+
"w-3",
|
|
91
|
+
"h-3",
|
|
92
|
+
"my-3",
|
|
93
|
+
"-right-[18px]",
|
|
94
|
+
"cursor-crosshair",
|
|
95
|
+
"mt-3"
|
|
96
|
+
];
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/helpers/createContext.ts
|
|
99
|
+
/**
|
|
100
|
+
* Creates a Solid context provider component and a custom hook to consume
|
|
101
|
+
* it.
|
|
102
|
+
*
|
|
103
|
+
* @template T - The type of the context value.
|
|
104
|
+
*
|
|
105
|
+
* @param context - Factory function returning a non-nullable context value
|
|
106
|
+
* of type `NonNullable<T>`.
|
|
107
|
+
* @param options - Optional context configuration of type
|
|
108
|
+
* {@linkcode Options}.
|
|
109
|
+
*
|
|
110
|
+
* @returns A tuple containing the Provider component of type
|
|
111
|
+
* {@linkcode ParentComponent}, the accessor hook, and the Solid context
|
|
112
|
+
* object.
|
|
113
|
+
*/
|
|
114
|
+
var createContext$1 = (context, options) => {
|
|
115
|
+
const _context = createContext(context(), options);
|
|
116
|
+
const Provider = ({ children }) => _context.Provider({
|
|
117
|
+
value: _context.defaultValue,
|
|
118
|
+
children
|
|
119
|
+
});
|
|
120
|
+
const _useContext = () => useContext(_context);
|
|
121
|
+
return [
|
|
122
|
+
Provider,
|
|
123
|
+
_useContext,
|
|
124
|
+
_context
|
|
125
|
+
];
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/services/main.typings.ts
|
|
129
|
+
/** Schema definition for 2D coordinates `(x, y)`. */
|
|
130
|
+
var point = type({
|
|
131
|
+
x: "number",
|
|
132
|
+
y: "number"
|
|
133
|
+
});
|
|
134
|
+
type(({ optional, use }) => ({
|
|
135
|
+
input: optional(use(point)),
|
|
136
|
+
output: use(point)
|
|
137
|
+
}));
|
|
138
|
+
/** Schema definition for edge extremities */
|
|
139
|
+
var extremities = type({
|
|
140
|
+
from: "string",
|
|
141
|
+
to: "string"
|
|
142
|
+
});
|
|
143
|
+
/** Schema definition for a serialized flowchart node entity. */
|
|
144
|
+
var nodeJSON = type(({ optional, use }) => ({
|
|
145
|
+
position: use(point),
|
|
146
|
+
data: {
|
|
147
|
+
label: optional("string"),
|
|
148
|
+
content: "string"
|
|
149
|
+
},
|
|
150
|
+
input: "boolean"
|
|
151
|
+
}));
|
|
152
|
+
/** Schema definition for a serialized flowchart edge entity. */
|
|
153
|
+
var edgeJSON = extremities;
|
|
154
|
+
type(({ optional, use }) => ({
|
|
155
|
+
width: "number",
|
|
156
|
+
height: "number",
|
|
157
|
+
id: "string",
|
|
158
|
+
output: use(point),
|
|
159
|
+
input: optional(use(point))
|
|
160
|
+
}));
|
|
161
|
+
type({
|
|
162
|
+
x0: "number",
|
|
163
|
+
y0: "number",
|
|
164
|
+
x1: "number",
|
|
165
|
+
y1: "number"
|
|
166
|
+
});
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/services/main.machine.ts
|
|
169
|
+
/**
|
|
170
|
+
* Constructs a unique edge identifier string from source and destination
|
|
171
|
+
* node IDs.
|
|
172
|
+
*
|
|
173
|
+
* @param out - The source node ID string.
|
|
174
|
+
* @param _in - The destination node ID string.
|
|
175
|
+
*
|
|
176
|
+
* @returns Formatted edge identifier string.
|
|
177
|
+
*/
|
|
178
|
+
var buildEdgeId = (out, _in) => {
|
|
179
|
+
return `edge = ${out} => ${_in}`;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Constructs a formatted node identifier string from a generated ID.
|
|
183
|
+
*
|
|
184
|
+
* @param generated - The generated unique ID string or `null`/`undefined`.
|
|
185
|
+
*
|
|
186
|
+
* @returns Formatted node identifier string.
|
|
187
|
+
*/
|
|
188
|
+
var buildNodeID = (generated) => {
|
|
189
|
+
return `node-${generated}`;
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* State machine managing flowchart state transitions, nodes, edges,
|
|
193
|
+
* selection, and layout actions.
|
|
194
|
+
*/
|
|
195
|
+
var machine = createMachine({
|
|
196
|
+
initial: "idle",
|
|
197
|
+
states: {
|
|
198
|
+
idle: { on: {
|
|
199
|
+
CONFIGURE: {
|
|
200
|
+
actions: ["configure"],
|
|
201
|
+
target: "/working"
|
|
202
|
+
},
|
|
203
|
+
CONFIGURE_EMPTY: "/working"
|
|
204
|
+
} },
|
|
205
|
+
construction: { always: {
|
|
206
|
+
actions: ["buildUI"],
|
|
207
|
+
target: "/working"
|
|
208
|
+
} },
|
|
209
|
+
working: { on: {
|
|
210
|
+
MOVE: {
|
|
211
|
+
actions: ["moveNode", "buildUI"],
|
|
212
|
+
target: "/construction"
|
|
213
|
+
},
|
|
214
|
+
MOVE_IMMEDIATE: { actions: [{
|
|
215
|
+
name: "buildImmediateUI",
|
|
216
|
+
description: "Must be in the ui"
|
|
217
|
+
}] },
|
|
218
|
+
UPDATE_UI: "/construction",
|
|
219
|
+
ADD_CHILD: {
|
|
220
|
+
actions: [
|
|
221
|
+
"generateID",
|
|
222
|
+
{
|
|
223
|
+
name: "placeChild",
|
|
224
|
+
description: "Must be in the ui"
|
|
225
|
+
},
|
|
226
|
+
"linkChild"
|
|
227
|
+
],
|
|
228
|
+
target: "/construction"
|
|
229
|
+
},
|
|
230
|
+
ADD_PARENT: {
|
|
231
|
+
actions: [
|
|
232
|
+
"generateID",
|
|
233
|
+
{
|
|
234
|
+
name: "placeParent",
|
|
235
|
+
description: "Must be in the ui"
|
|
236
|
+
},
|
|
237
|
+
"selectParent"
|
|
238
|
+
],
|
|
239
|
+
target: "/construction"
|
|
240
|
+
},
|
|
241
|
+
ADD_SIBLING: {
|
|
242
|
+
actions: [
|
|
243
|
+
"generateID",
|
|
244
|
+
{
|
|
245
|
+
name: "placeSibling",
|
|
246
|
+
description: "Must be in the ui"
|
|
247
|
+
},
|
|
248
|
+
"linkSibling"
|
|
249
|
+
],
|
|
250
|
+
target: "/construction"
|
|
251
|
+
},
|
|
252
|
+
ADD_EDGE: {
|
|
253
|
+
actions: ["addEdge"],
|
|
254
|
+
target: "/construction"
|
|
255
|
+
},
|
|
256
|
+
DELETE: {
|
|
257
|
+
actions: ["delete"],
|
|
258
|
+
target: "/construction"
|
|
259
|
+
},
|
|
260
|
+
SELECT: { actions: ["select"] },
|
|
261
|
+
DESELECT: { actions: ["deselect"] }
|
|
262
|
+
} }
|
|
263
|
+
}
|
|
264
|
+
}, {
|
|
265
|
+
eventsMap: type(({ intersection, use, array }) => ({
|
|
266
|
+
CONFIGURE: {
|
|
267
|
+
nodes: array(intersection(use(nodeJSON), { id: "string" })),
|
|
268
|
+
edges: array(intersection(use(edgeJSON), { id: "string" }))
|
|
269
|
+
},
|
|
270
|
+
CONFIGURE_EMPTY: "never",
|
|
271
|
+
MOVE: {
|
|
272
|
+
id: "string",
|
|
273
|
+
x: "number",
|
|
274
|
+
y: "number"
|
|
275
|
+
},
|
|
276
|
+
MOVE_IMMEDIATE: {
|
|
277
|
+
id: "string",
|
|
278
|
+
x: "number",
|
|
279
|
+
y: "number"
|
|
280
|
+
},
|
|
281
|
+
ADD_CHILD: "string",
|
|
282
|
+
ADD_PARENT: "never",
|
|
283
|
+
ADD_SIBLING: "string",
|
|
284
|
+
DELETE: "string",
|
|
285
|
+
SELECT: "string",
|
|
286
|
+
DESELECT: "never",
|
|
287
|
+
ADD_EDGE: use(extremities)
|
|
288
|
+
})),
|
|
289
|
+
pContext: type(({ union }) => ({ generatedId: union("string", "null") })),
|
|
290
|
+
context: type(({ optional, use, array }) => ({
|
|
291
|
+
data: optional({
|
|
292
|
+
nodes: array({
|
|
293
|
+
...use(nodeJSON),
|
|
294
|
+
id: "string"
|
|
295
|
+
}),
|
|
296
|
+
edges: array({
|
|
297
|
+
...use(edgeJSON),
|
|
298
|
+
id: "string"
|
|
299
|
+
})
|
|
300
|
+
}),
|
|
301
|
+
selected: optional("string"),
|
|
302
|
+
updatingUI: optional("boolean")
|
|
303
|
+
})),
|
|
304
|
+
sync: true
|
|
305
|
+
}).provideOptions(({ assign, batch, erase }) => ({ actions: {
|
|
306
|
+
configure: batch(assign("context.data", () => ({
|
|
307
|
+
nodes: [],
|
|
308
|
+
edges: []
|
|
309
|
+
})), assign("context.data.nodes", { CONFIGURE: ({ payload: { nodes } }) => nodes }), assign("context.data.edges", { CONFIGURE: ({ payload: { edges } }) => edges }), assign("context.updatingUI", () => false), assign("pContext.generatedId", () => null)),
|
|
310
|
+
generateID: assign("pContext.generatedId", () => nanoid()),
|
|
311
|
+
linkChild: batch(assign("context.data.edges", { ADD_CHILD: ({ context, pContext, payload }) => {
|
|
312
|
+
const data = context.data;
|
|
313
|
+
const from = payload;
|
|
314
|
+
const generatedId = pContext?.generatedId;
|
|
315
|
+
const to = buildNodeID(generatedId);
|
|
316
|
+
const id = buildEdgeId(from, to);
|
|
317
|
+
return [...data?.edges ?? [], {
|
|
318
|
+
id,
|
|
319
|
+
from,
|
|
320
|
+
to
|
|
321
|
+
}];
|
|
322
|
+
} }), assign("context.selected", ({ pContext: { generatedId } }) => buildNodeID(generatedId))),
|
|
323
|
+
linkSibling: batch(assign("context.data.edges", { ADD_SIBLING: ({ pContext, payload, context }) => {
|
|
324
|
+
const edges = context.data?.edges;
|
|
325
|
+
const generatedId = pContext?.generatedId;
|
|
326
|
+
const out = [...edges ?? []];
|
|
327
|
+
const from = edges?.find(({ to }) => to === payload)?.from;
|
|
328
|
+
if (!from) return out;
|
|
329
|
+
const to = buildNodeID(generatedId);
|
|
330
|
+
const id = buildEdgeId(from, to);
|
|
331
|
+
out.push({
|
|
332
|
+
from,
|
|
333
|
+
to,
|
|
334
|
+
id
|
|
335
|
+
});
|
|
336
|
+
return out;
|
|
337
|
+
} }), assign("context.selected", ({ pContext: { generatedId } }) => buildNodeID(generatedId))),
|
|
338
|
+
selectParent: assign("context.selected", ({ pContext: { generatedId } }) => buildNodeID(generatedId)),
|
|
339
|
+
moveNode: assign("context.data.nodes", { MOVE: ({ context: { data }, payload }) => {
|
|
340
|
+
const { id, x, y } = payload;
|
|
341
|
+
if (!id) return data?.nodes ?? [];
|
|
342
|
+
return data?.nodes?.map((d) => {
|
|
343
|
+
if (d.id === id) return {
|
|
344
|
+
...d,
|
|
345
|
+
position: {
|
|
346
|
+
x,
|
|
347
|
+
y
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
return d;
|
|
351
|
+
}) ?? [];
|
|
352
|
+
} }),
|
|
353
|
+
select: assign("context.selected", { SELECT: ({ payload }) => payload }),
|
|
354
|
+
delete: assign(["context.data.nodes", "context.data.edges"], { DELETE: ({ context: { data }, payload }) => {
|
|
355
|
+
return [data?.nodes?.filter(({ id }) => id !== payload), data?.edges?.filter(({ id, from, to }) => id !== payload && from !== payload && to !== payload)];
|
|
356
|
+
} }),
|
|
357
|
+
addEdge: assign("context.data.edges", { ADD_EDGE: ({ context, payload: { from, to } }) => {
|
|
358
|
+
const edges = context.data?.edges ?? [];
|
|
359
|
+
const id = buildEdgeId(from, to);
|
|
360
|
+
if (edges.some((e) => e.id === id)) return edges;
|
|
361
|
+
return [...edges, {
|
|
362
|
+
id,
|
|
363
|
+
from,
|
|
364
|
+
to
|
|
365
|
+
}];
|
|
366
|
+
} }),
|
|
367
|
+
deselect: erase("context.selected")
|
|
368
|
+
} }));
|
|
369
|
+
/** X-axis center offset in pixels from the node container edge. */
|
|
370
|
+
var HANDLE_CENTER_X_OFFSET = 10.5;
|
|
371
|
+
/** Default 2D offset coordinates for input connection handles. */
|
|
372
|
+
var DEFAULT_INPUT_OFFSET = {
|
|
373
|
+
x: -10.5,
|
|
374
|
+
y: 18
|
|
375
|
+
};
|
|
376
|
+
/**
|
|
377
|
+
* Computes default output handle offset coordinates given a node width.
|
|
378
|
+
*
|
|
379
|
+
* @param width - Node width in pixels, defaults to `0`.
|
|
380
|
+
*
|
|
381
|
+
* @returns 2D offset coordinates for the output handle.
|
|
382
|
+
*/
|
|
383
|
+
var getDefaultOutputOffset = (width = 0) => ({
|
|
384
|
+
x: width + HANDLE_CENTER_X_OFFSET,
|
|
385
|
+
y: 18
|
|
386
|
+
});
|
|
387
|
+
/**
|
|
388
|
+
* Boundary padding constraints for node dragging and positioning within
|
|
389
|
+
* the viewport.
|
|
390
|
+
*/
|
|
391
|
+
var BOUNDS_CONSTRAINTS = {
|
|
392
|
+
horizontal: 20,
|
|
393
|
+
vertical: 35
|
|
394
|
+
};
|
|
395
|
+
/** Default node items provided when no initial configuration is given. */
|
|
396
|
+
var DEFAULT_NODES = [{
|
|
397
|
+
id: "node-0",
|
|
398
|
+
data: {
|
|
399
|
+
content: "Some text",
|
|
400
|
+
label: "Root node"
|
|
401
|
+
},
|
|
402
|
+
input: false,
|
|
403
|
+
position: {
|
|
404
|
+
x: 350,
|
|
405
|
+
y: 100
|
|
406
|
+
}
|
|
407
|
+
}];
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/ui/components/FlowChart.context.ts
|
|
410
|
+
/**
|
|
411
|
+
* Shared state machine interpreter service instance for flowchart state
|
|
412
|
+
* management.
|
|
413
|
+
*/
|
|
414
|
+
var service = interpret(machine, {
|
|
415
|
+
context: {},
|
|
416
|
+
pContext: { generatedId: null }
|
|
417
|
+
});
|
|
418
|
+
/**
|
|
419
|
+
* Solid Context Provider component and hook for accessing flowchart board
|
|
420
|
+
* state, services, and zoom.
|
|
421
|
+
*/
|
|
422
|
+
var [Provider, useFlow] = createContext$1(() => {
|
|
423
|
+
const zoom = createSignal(1);
|
|
424
|
+
const newEdge = createSignal();
|
|
425
|
+
const [boardRef, setBoardRef] = createSignal();
|
|
426
|
+
const [dimensions, setDimensions] = createSignal({}, { equals: dequal });
|
|
427
|
+
const getBoardPoint = (clientX, clientY) => {
|
|
428
|
+
const el = boardRef();
|
|
429
|
+
if (!el) return {
|
|
430
|
+
x: clientX,
|
|
431
|
+
y: clientY
|
|
432
|
+
};
|
|
433
|
+
const rect = el.getBoundingClientRect();
|
|
434
|
+
const currentZoom = zoom[0]();
|
|
435
|
+
return {
|
|
436
|
+
x: (clientX - rect.left) / currentZoom,
|
|
437
|
+
y: (clientY - rect.top) / currentZoom
|
|
438
|
+
};
|
|
439
|
+
};
|
|
440
|
+
const [edgesPositions, setEdgesPositions] = createSignal({}, { equals: false });
|
|
441
|
+
const clampPosition = (x, y, nodeWidth = 192, nodeHeight = 50) => {
|
|
442
|
+
const container = boardRef()?.parentElement;
|
|
443
|
+
if (!container) return {
|
|
444
|
+
x,
|
|
445
|
+
y
|
|
446
|
+
};
|
|
447
|
+
const currentZoom = zoom[0]();
|
|
448
|
+
const minX = container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
|
|
449
|
+
const maxX = Math.max(minX, (container.scrollLeft + container.clientWidth) / currentZoom - BOUNDS_CONSTRAINTS.horizontal - nodeWidth);
|
|
450
|
+
const minY = container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
|
|
451
|
+
const maxY = Math.max(minY, (container.scrollTop + container.clientHeight) / currentZoom - BOUNDS_CONSTRAINTS.vertical - nodeHeight);
|
|
452
|
+
return {
|
|
453
|
+
x: Math.min(Math.max(x, minX), maxX),
|
|
454
|
+
y: Math.min(Math.max(y, minY), maxY)
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
service.addOptions(({ voidAction, batch, assign }) => ({ actions: {
|
|
458
|
+
placeChild: assign("context.data.nodes", { ADD_CHILD: ({ payload, context: { data }, pContext: { generatedId } }) => {
|
|
459
|
+
const nodes = data?.nodes ?? [];
|
|
460
|
+
if (!payload) return nodes;
|
|
461
|
+
const parentNode = nodes.find((node) => node.id === payload);
|
|
462
|
+
if (!parentNode) return nodes;
|
|
463
|
+
const id = `node-${generatedId}`;
|
|
464
|
+
const width = dimensions()[payload]?.width ?? 0;
|
|
465
|
+
const height = dimensions()[payload]?.height ?? 0;
|
|
466
|
+
const initialX = parentNode.position.x + width + 100;
|
|
467
|
+
const initialY = parentNode.position.y;
|
|
468
|
+
const position = clampPosition(initialX, initialY, width, height);
|
|
469
|
+
return [...nodes, {
|
|
470
|
+
id,
|
|
471
|
+
data: { content: "<Nouveau nœud>" },
|
|
472
|
+
input: true,
|
|
473
|
+
position
|
|
474
|
+
}];
|
|
475
|
+
} }),
|
|
476
|
+
placeParent: assign("context.data.nodes", { ADD_PARENT: ({ context: { data }, pContext: { generatedId } }) => {
|
|
477
|
+
const nodes = data?.nodes ?? [];
|
|
478
|
+
const id = `node-${generatedId}`;
|
|
479
|
+
const container = boardRef()?.parentElement;
|
|
480
|
+
const scrollLeft = container?.scrollLeft ?? 0;
|
|
481
|
+
const scrollTop = container?.scrollTop ?? 0;
|
|
482
|
+
const width = container?.clientWidth ?? 0;
|
|
483
|
+
const height = container?.clientHeight ?? 0;
|
|
484
|
+
const currentZoom = zoom[0]();
|
|
485
|
+
const x = (scrollLeft + width / 2) / currentZoom;
|
|
486
|
+
const y = (scrollTop + height / 2) / currentZoom;
|
|
487
|
+
return [...nodes, {
|
|
488
|
+
id,
|
|
489
|
+
data: { content: "<Nouveau nœud>" },
|
|
490
|
+
input: false,
|
|
491
|
+
position: {
|
|
492
|
+
x,
|
|
493
|
+
y
|
|
494
|
+
}
|
|
495
|
+
}];
|
|
496
|
+
} }),
|
|
497
|
+
placeSibling: assign("context.data.nodes", { ADD_SIBLING: ({ payload, context: { data }, pContext: { generatedId } }) => {
|
|
498
|
+
const edges = data?.edges ?? [];
|
|
499
|
+
const nodes = data?.nodes ?? [];
|
|
500
|
+
const parentID = edges.find((edge) => edge.to === payload)?.from;
|
|
501
|
+
if (!parentID) return nodes;
|
|
502
|
+
const parentNode = nodes.find((node) => node.id === parentID);
|
|
503
|
+
if (!parentNode) return nodes;
|
|
504
|
+
const id = `node-${generatedId}`;
|
|
505
|
+
const width = dimensions()[parentID]?.width ?? 0;
|
|
506
|
+
const height = dimensions()[parentID]?.height ?? 0;
|
|
507
|
+
const initialX = parentNode.position.x + width + 100;
|
|
508
|
+
const initialY = parentNode.position.y + 100;
|
|
509
|
+
const position = clampPosition(initialX, initialY, width, height);
|
|
510
|
+
return [...nodes, {
|
|
511
|
+
id,
|
|
512
|
+
data: { content: "<Nouveau nœud>" },
|
|
513
|
+
input: true,
|
|
514
|
+
position
|
|
515
|
+
}];
|
|
516
|
+
} }),
|
|
517
|
+
buildUI: batch(voidAction(({ context: { data } }) => {
|
|
518
|
+
const edges = data?.edges ?? [];
|
|
519
|
+
setEdgesPositions((data) => {
|
|
520
|
+
const array = Object.entries({ ...data }).filter(([id]) => {
|
|
521
|
+
return edges?.some((edge) => edge.id === id);
|
|
522
|
+
});
|
|
523
|
+
return Object.fromEntries(array);
|
|
524
|
+
});
|
|
525
|
+
}), voidAction({
|
|
526
|
+
else: ({ context: { data } }) => {
|
|
527
|
+
const edges = data?.edges ?? [];
|
|
528
|
+
setEdgesPositions(produce((next) => {
|
|
529
|
+
edges?.forEach(({ from, id, to }) => {
|
|
530
|
+
const output = dimensions()[from]?.output;
|
|
531
|
+
const input = dimensions()[to]?.input;
|
|
532
|
+
if (output && input) next[id] = {
|
|
533
|
+
x0: output.x,
|
|
534
|
+
y0: output.y,
|
|
535
|
+
x1: input.x,
|
|
536
|
+
y1: input.y
|
|
537
|
+
};
|
|
538
|
+
});
|
|
539
|
+
}));
|
|
540
|
+
},
|
|
541
|
+
MOVE: ({ context: { data }, payload }) => {
|
|
542
|
+
const edges = data?.edges ?? [];
|
|
543
|
+
setEdgesPositions(produce((next) => {
|
|
544
|
+
edges?.forEach(({ from, to, id }) => {
|
|
545
|
+
if (from === payload.id) {
|
|
546
|
+
const offset = dimensions()[payload.id]?.outputOffset ?? getDefaultOutputOffset(dimensions()[payload.id]?.width);
|
|
547
|
+
const x0 = payload.x + offset.x;
|
|
548
|
+
const y0 = payload.y + offset.y;
|
|
549
|
+
next[id] = {
|
|
550
|
+
...next[id],
|
|
551
|
+
x0,
|
|
552
|
+
y0
|
|
553
|
+
};
|
|
554
|
+
setDimensions(produce((data) => {
|
|
555
|
+
if (data[payload.id]) data[payload.id] = {
|
|
556
|
+
...data[payload.id],
|
|
557
|
+
output: {
|
|
558
|
+
x: x0,
|
|
559
|
+
y: y0
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
564
|
+
if (to === payload.id) {
|
|
565
|
+
const offset = dimensions()[payload.id]?.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
566
|
+
const x1 = payload.x + offset.x;
|
|
567
|
+
const y1 = payload.y + offset.y;
|
|
568
|
+
next[id] = {
|
|
569
|
+
...next[id],
|
|
570
|
+
x1,
|
|
571
|
+
y1
|
|
572
|
+
};
|
|
573
|
+
setDimensions(produce((data) => {
|
|
574
|
+
if (data[payload.id]) data[payload.id] = {
|
|
575
|
+
...data[payload.id],
|
|
576
|
+
input: {
|
|
577
|
+
x: x1,
|
|
578
|
+
y: y1
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}));
|
|
585
|
+
}
|
|
586
|
+
}), assign("context.updatingUI", () => true)),
|
|
587
|
+
buildImmediateUI: voidAction({ MOVE_IMMEDIATE: ({ context: { data }, payload }) => {
|
|
588
|
+
const edges = data?.edges ?? [];
|
|
589
|
+
setEdgesPositions(produce((next) => {
|
|
590
|
+
edges?.forEach(({ from, to, id }) => {
|
|
591
|
+
if (from === payload.id) {
|
|
592
|
+
const offset = dimensions()[payload.id]?.outputOffset ?? getDefaultOutputOffset(dimensions()[payload.id]?.width);
|
|
593
|
+
const x0 = payload.x + offset.x;
|
|
594
|
+
const y0 = payload.y + offset.y;
|
|
595
|
+
next[id] = {
|
|
596
|
+
...next[id],
|
|
597
|
+
x0,
|
|
598
|
+
y0
|
|
599
|
+
};
|
|
600
|
+
setDimensions(produce((data) => {
|
|
601
|
+
if (data[payload.id]) data[payload.id] = {
|
|
602
|
+
...data[payload.id],
|
|
603
|
+
output: {
|
|
604
|
+
x: x0,
|
|
605
|
+
y: y0
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}));
|
|
609
|
+
}
|
|
610
|
+
if (to === payload.id) {
|
|
611
|
+
const offset = dimensions()[payload.id]?.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
612
|
+
const x1 = payload.x + offset.x;
|
|
613
|
+
const y1 = payload.y + offset.y;
|
|
614
|
+
next[id] = {
|
|
615
|
+
...next[id],
|
|
616
|
+
x1,
|
|
617
|
+
y1
|
|
618
|
+
};
|
|
619
|
+
setDimensions(produce((data) => {
|
|
620
|
+
if (data[payload.id]) data[payload.id] = {
|
|
621
|
+
...data[payload.id],
|
|
622
|
+
input: {
|
|
623
|
+
x: x1,
|
|
624
|
+
y: y1
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}));
|
|
631
|
+
} })
|
|
632
|
+
} }));
|
|
633
|
+
service.start();
|
|
634
|
+
return {
|
|
635
|
+
dimensions: [dimensions, setDimensions],
|
|
636
|
+
newEdge,
|
|
637
|
+
board: [boardRef, setBoardRef],
|
|
638
|
+
getBoardPoint,
|
|
639
|
+
edgesPositions: [edgesPositions, setEdgesPositions],
|
|
640
|
+
service,
|
|
641
|
+
zoom
|
|
642
|
+
};
|
|
643
|
+
}, { name: "FlowContext" });
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/ui/components/Bounds.tsx
|
|
646
|
+
/**
|
|
647
|
+
* Drag boundary transformer component that clamps draggable nodes within
|
|
648
|
+
* container scroll bounds.
|
|
649
|
+
*
|
|
650
|
+
* @returns `null` as this component performs side-effect transformer
|
|
651
|
+
* registrations only.
|
|
652
|
+
*/
|
|
653
|
+
var DragBounds = () => {
|
|
654
|
+
const { board: [ref], zoom: [zoom] } = useFlow();
|
|
655
|
+
const [state, { addTransformer, removeTransformer, onDragStart, onDragEnd }] = useDragDropContext();
|
|
656
|
+
const transformer = {
|
|
657
|
+
id: "clamp-to-container",
|
|
658
|
+
order: 100,
|
|
659
|
+
callback: (transform) => {
|
|
660
|
+
const container = ref()?.parentElement;
|
|
661
|
+
const activeDraggable = state.active.draggable;
|
|
662
|
+
if (!container || !activeDraggable) return transform;
|
|
663
|
+
const draggableLayout = activeDraggable.layout;
|
|
664
|
+
const containerRect = container.getBoundingClientRect();
|
|
665
|
+
const innerLeft = containerRect.left + container.clientLeft;
|
|
666
|
+
const innerTop = containerRect.top + container.clientTop;
|
|
667
|
+
const innerRight = innerLeft + container.clientWidth;
|
|
668
|
+
const innerBottom = innerTop + container.clientHeight;
|
|
669
|
+
const currentZoom = zoom();
|
|
670
|
+
const minX = innerLeft - draggableLayout.left + BOUNDS_CONSTRAINTS.horizontal * currentZoom;
|
|
671
|
+
const maxX = Math.max(minX, innerRight - draggableLayout.right - BOUNDS_CONSTRAINTS.horizontal * currentZoom);
|
|
672
|
+
const minY = innerTop - draggableLayout.top + BOUNDS_CONSTRAINTS.vertical * currentZoom;
|
|
673
|
+
const maxY = Math.max(minY, innerBottom - draggableLayout.bottom - BOUNDS_CONSTRAINTS.vertical * currentZoom);
|
|
674
|
+
return {
|
|
675
|
+
x: Math.min(Math.max(transform.x, minX), maxX),
|
|
676
|
+
y: Math.min(Math.max(transform.y, minY), maxY)
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
onDragStart(({ draggable }) => {
|
|
681
|
+
addTransformer("draggables", draggable.id, transformer);
|
|
682
|
+
});
|
|
683
|
+
onDragEnd(({ draggable }) => {
|
|
684
|
+
removeTransformer("draggables", draggable.id, transformer.id);
|
|
685
|
+
});
|
|
686
|
+
return null;
|
|
687
|
+
};
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/ui/components/EdgeComponent.tsx
|
|
690
|
+
var _tmpl$$4 = [
|
|
691
|
+
"<path",
|
|
692
|
+
" class=\"",
|
|
693
|
+
"\" style=\"",
|
|
694
|
+
"\"",
|
|
695
|
+
"></path>"
|
|
696
|
+
];
|
|
697
|
+
var _tmpl$2$1 = [
|
|
698
|
+
"<g",
|
|
699
|
+
" cursor=\"pointer\" transform=\"",
|
|
700
|
+
"\" style=\"",
|
|
701
|
+
"\"><circle cx=\"0\" cy=\"0\" r=\"12\" fill=\"rgba(168, 168, 168, 1)\"></circle><svg fill=\"currentColor\" stroke-width=\"0\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-25 w-25 bg-white fill-white\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" color=\"white\" x=\"-10\" y=\"-10\"><path d=\"M10.185,1.417c-4.741,0-8.583,3.842-8.583,8.583c0,4.74,3.842,8.582,8.583,8.582S18.768,14.74,18.768,10C18.768,5.259,14.926,1.417,10.185,1.417 M10.185,17.68c-4.235,0-7.679-3.445-7.679-7.68c0-4.235,3.444-7.679,7.679-7.679S17.864,5.765,17.864,10C17.864,14.234,14.42,17.68,10.185,17.68 M10.824,10l2.842-2.844c0.178-0.176,0.178-0.46,0-0.637c-0.177-0.178-0.461-0.178-0.637,0l-2.844,2.841L7.341,6.52c-0.176-0.178-0.46-0.178-0.637,0c-0.178,0.176-0.178,0.461,0,0.637L9.546,10l-2.841,2.844c-0.178,0.176-0.178,0.461,0,0.637c0.178,0.178,0.459,0.178,0.637,0l2.844-2.841l2.844,2.841c0.178,0.178,0.459,0.178,0.637,0c0.178-0.176,0.178-0.461,0-0.637L10.824,10z\"></path></svg></g>"
|
|
702
|
+
];
|
|
703
|
+
/**
|
|
704
|
+
* Computes bezier curve control point offset based on horizontal distance.
|
|
705
|
+
*
|
|
706
|
+
* @param value - Absolute horizontal delta between start and end points.
|
|
707
|
+
*
|
|
708
|
+
* @returns Offset distance in pixels for bezier curvature.
|
|
709
|
+
*/
|
|
710
|
+
var calculateOffset = (value) => value * 100 / 200;
|
|
711
|
+
/**
|
|
712
|
+
* Generates an SVG cubic bezier path `d` string between coordinates `(x0,
|
|
713
|
+
* y0)` and `(x1, y1)`.
|
|
714
|
+
*
|
|
715
|
+
* @param vector - Vector coordinates of type {@linkcode Vector}.
|
|
716
|
+
*
|
|
717
|
+
* @returns SVG cubic bezier path string.
|
|
718
|
+
*/
|
|
719
|
+
var draw = ({ x0, y0, x1, y1 }) => {
|
|
720
|
+
return `M ${x0} ${y0} C ${x0 + calculateOffset(Math.abs(x1 - x0))} ${y0}, ${x1 - calculateOffset(Math.abs(x1 - x0))} ${y1}, ${x1} ${y1}`;
|
|
721
|
+
};
|
|
722
|
+
/**
|
|
723
|
+
* SVG edge component rendering bezier curves and delete interaction
|
|
724
|
+
* handles for node connections.
|
|
725
|
+
*
|
|
726
|
+
* @param props - Edge properties of type {@linkcode Props}.
|
|
727
|
+
*
|
|
728
|
+
* @returns The rendered SVG JSX elements.
|
|
729
|
+
*/
|
|
730
|
+
var EdgeComponent = (props) => {
|
|
731
|
+
const [middlePoint, setMiddlePoint] = createSignal({
|
|
732
|
+
x: props.x0 + (props.x1 - props.x0) / 2,
|
|
733
|
+
y: props.y0 + (props.y1 - props.y0) / 2
|
|
734
|
+
});
|
|
735
|
+
const { service } = useFlow();
|
|
736
|
+
createEffect(() => {
|
|
737
|
+
const middleX = props.x0 + (props.x1 - props.x0) / 2;
|
|
738
|
+
const middleY = props.y0 + (props.y1 - props.y0) / 2;
|
|
739
|
+
setMiddlePoint({
|
|
740
|
+
x: middleX,
|
|
741
|
+
y: middleY
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
const selected = useState(service, { selector: (s) => s.context.selected === props.id });
|
|
745
|
+
return [ssr(_tmpl$$4, ssrHydrationKey(), `relative cursor-pointer fill-transparent ${!!props.isNew ? "stroke-[rgba(168,168,168,0.4)] stroke-3 z-200" : ""} ${selected() && !props.isNew ? "stroke-[rgba(168,168,168,1)] stroke-4 z-100" : ""} ${!selected() && !props.isNew ? "stroke-[rgba(168,168,168,0.8)] stroke-3" : ""}`, ssrStyleProperty("pointer-events:", props.isNew ? "none" : "all"), ssrAttribute("d", escape(draw(props), true), false)), createComponent(Show, {
|
|
746
|
+
get when() {
|
|
747
|
+
return selected();
|
|
748
|
+
},
|
|
749
|
+
get children() {
|
|
750
|
+
return ssr(_tmpl$2$1, ssrHydrationKey(), `translate(${escape(middlePoint().x, true)}, ${escape(middlePoint().y, true)})`, ssrStyleProperty("pointer-events:", "all") + ssrStyleProperty(";z-index:", "30"));
|
|
751
|
+
}
|
|
752
|
+
})];
|
|
753
|
+
};
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region src/ui/components/EdgesBoard.tsx
|
|
756
|
+
var _tmpl$$3 = [
|
|
757
|
+
"<svg",
|
|
758
|
+
" class=\"pointer-events-none h-full w-full overflow-visible\"><!--$-->",
|
|
759
|
+
"<!--/--><!--$-->",
|
|
760
|
+
"<!--/--></svg>"
|
|
761
|
+
];
|
|
762
|
+
/**
|
|
763
|
+
* SVG board overlay component that renders all active connecting edges and
|
|
764
|
+
* ongoing edge creation previews.
|
|
765
|
+
*
|
|
766
|
+
* @returns The rendered SVG JSX element.
|
|
767
|
+
*/
|
|
768
|
+
var EdgesBoard = () => {
|
|
769
|
+
const [selected, setSelected] = createSignal();
|
|
770
|
+
const { newEdge: [newEdge], edgesPositions: [edgesPositions] } = useFlow();
|
|
771
|
+
const datas = createMemo(() => {
|
|
772
|
+
return Object.entries(edgesPositions()).map(([id, vector]) => ({
|
|
773
|
+
id,
|
|
774
|
+
...vector
|
|
775
|
+
}));
|
|
776
|
+
});
|
|
777
|
+
createEffect(() => {
|
|
778
|
+
if (selected() && newEdge()) setSelected();
|
|
779
|
+
});
|
|
780
|
+
return ssr(_tmpl$$3, ssrHydrationKey(), escape(createComponent(Show, {
|
|
781
|
+
get when() {
|
|
782
|
+
return newEdge();
|
|
783
|
+
},
|
|
784
|
+
children: (value) => createComponent(EdgeComponent, {
|
|
785
|
+
id: "__#new-edge#__TEMP",
|
|
786
|
+
isNew: true,
|
|
787
|
+
get x0() {
|
|
788
|
+
return value().x0;
|
|
789
|
+
},
|
|
790
|
+
get y0() {
|
|
791
|
+
return value().y0;
|
|
792
|
+
},
|
|
793
|
+
get x1() {
|
|
794
|
+
return value().x1;
|
|
795
|
+
},
|
|
796
|
+
get y1() {
|
|
797
|
+
return value().y1;
|
|
798
|
+
}
|
|
799
|
+
})
|
|
800
|
+
})), escape(createComponent(For, {
|
|
801
|
+
get each() {
|
|
802
|
+
return datas();
|
|
803
|
+
},
|
|
804
|
+
children: EdgeComponent
|
|
805
|
+
})));
|
|
806
|
+
};
|
|
807
|
+
//#endregion
|
|
808
|
+
//#region src/ui/components/NodeComponent.tsx
|
|
809
|
+
var _tmpl$$2 = [
|
|
810
|
+
"<svg",
|
|
811
|
+
" class=\"cursor-pointer overflow-visible rounded-full bg-green-500 p-0.5 text-center font-bold hover:bg-green-600\" style=\"",
|
|
812
|
+
"\" viewBox=\"0 0 1024 1024\" preserveAspectRatio=\"xMaxYMax\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"white\"><g id=\"background\"><path d=\"M467.40667,277.66696c-0.05948,-14.53055 5.75527,-22.95613 -8.62044,-20.90487c-112.55699,16.0607 -222.1609,112.14558 -245.06161,239.85765c-46.52056,259.43466 231.33083,443.06705 449.51209,316.97506c117.31668,-67.80002 160.95215,-190.43324 151.34416,-288.29849c-5.92276,-60.32819 -27.80273,-107.95668 -53.44246,-144.25469l59.39269,-42.05363c111.72214,156.309 73.11535,351.55635 -25.06953,459.45565c-184.18877,202.4124 -470.46624,145.52064 -592.95027,-32.92123c-156.18269,-227.53604 -27.15324,-543.64371 261.18883,-582.44416c5.0579,-0.68061 3.56556,-7.04079 3.56442,-8.58985c-0.05594,-76.3354 -0.11021,-76.7687 1.10909,-77.24589c2.06886,-0.80969 151.41433,118.4561 151.92482,118.95524c4.65592,4.55233 -0.99548,7.829 -29.07828,30.50907c-120.49369,97.31245 -120.4977,98.55675 -123.0691,97.87586c-0.43639,-0.11555 -0.80698,-0.31322 -0.74442,-66.91571Z\"></path><path d=\"M316.61562,611.03414c0.11517,-90.16257 -0.25516,-99.92912 0.64739,-101.78856c1.56486,-3.22393 131.99102,0.91032 134.6959,-1.89763c2.21336,-2.2977 -0.59362,-129.97807 1.1376,-132.37034c0.7253,-1.00225 11.10552,-0.99175 12.07474,-0.99077c104.50336,0.10564 104.90098,-0.37811 105.967,0.85765c1.56461,1.81373 0.25596,114.18436 0.67852,129.81412c0.1322,4.88975 3.22386,3.28152 99.72028,3.4563c33.0841,0.05992 36.14259,-1.40368 36.21852,4.50462c0.11713,9.11348 1.41954,110.45369 -0.40274,113.92715c-1.81106,3.45208 -130.39967,-0.42618 -134.48289,1.62075c-2.29035,1.14816 -0.36989,101.12392 -1.11542,130.51904c-0.09548,3.76459 -2.13506,3.20617 -47.84854,3.17365c-69.30253,-0.0493 -69.31099,-0.25627 -69.65762,-0.42191c-3.77927,-1.80595 0.16287,-129.33555 -2.24266,-132.58994c-1.79931,-2.43424 -124.06108,-0.29118 -132.80252,-0.97392c-3.81102,-0.29766 -2.58229,-14.8847 -2.58758,-16.8402Z\"></path></g></svg>"
|
|
813
|
+
];
|
|
814
|
+
var _tmpl$2 = [
|
|
815
|
+
"<div",
|
|
816
|
+
" id=\"outputs\" class=\"pointer-events-none absolute top-0 z-10 flex cursor-default flex-col\" style=\"",
|
|
817
|
+
"\"><div class=\"cursor-default rounded-full bg-[#e38b29] shadow-md\" style=\"",
|
|
818
|
+
"\"></div></div>"
|
|
819
|
+
];
|
|
820
|
+
var _tmpl$3 = [
|
|
821
|
+
"<div",
|
|
822
|
+
" class=\"",
|
|
823
|
+
"\" style=\"",
|
|
824
|
+
"\"><div class=\"",
|
|
825
|
+
"\"><svg class=\"cursor-pointer rounded-full fill-[#a11111] opacity-100 transition-all duration-200 ease-in-out\" fill=\"currentColor\" stroke-width=\"2\" viewBox=\"4 4 16 16\" style=\"",
|
|
826
|
+
"\"><path d=\"M12 4c-4.419 0-8 3.582-8 8s3.581 8 8 8 8-3.582 8-8-3.581-8-8-8zm3.707 10.293a.999.999 0 11-1.414 1.414L12 13.414l-2.293 2.293a.997.997 0 01-1.414 0 .999.999 0 010-1.414L10.586 12 8.293 9.707a.999.999 0 111.414-1.414L12 10.586l2.293-2.293a.999.999 0 111.414 1.414L13.414 12l2.293 2.293z\"></path></svg><!--$-->",
|
|
827
|
+
"<!--/--><svg class=\"flex cursor-pointer items-center justify-center rounded-lg bg-blue-500 p-0.5 text-center font-bold text-white hover:bg-blue-600\" style=\"",
|
|
828
|
+
"\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"></path></svg></div><!--$-->",
|
|
829
|
+
"<!--/--><!--$-->",
|
|
830
|
+
"<!--/--><!--$-->",
|
|
831
|
+
"<!--/--><div id=\"inputs\" class=\"pointer-events-none absolute top-0 z-10 flex flex-col\" style=\"",
|
|
832
|
+
"\"><div class=\"cursor-crosshair rounded-full bg-[#e38b29] shadow-md\" style=\"",
|
|
833
|
+
"\"></div></div></div>"
|
|
834
|
+
];
|
|
835
|
+
var _tmpl$4 = [
|
|
836
|
+
"<span",
|
|
837
|
+
" class=\"min-w-max border-b border-[#f0f0f0] p-3 whitespace-nowrap text-red-600 select-none\">",
|
|
838
|
+
"</span>"
|
|
839
|
+
];
|
|
840
|
+
var _tmpl$5 = [
|
|
841
|
+
"<div",
|
|
842
|
+
" class=\"p-3 select-none\">",
|
|
843
|
+
"</div>"
|
|
844
|
+
];
|
|
845
|
+
/**
|
|
846
|
+
* Interactive flowchart node component supporting dragging, selection,
|
|
847
|
+
* handle connections, and child/sibling creation.
|
|
848
|
+
*
|
|
849
|
+
* @param props - Node rendering properties of type {@linkcode Props}.
|
|
850
|
+
*
|
|
851
|
+
* @returns The rendered Solid component.
|
|
852
|
+
*/
|
|
853
|
+
var NodeComponent = (props) => {
|
|
854
|
+
const [ref, setRef] = createSignal();
|
|
855
|
+
const { dimensions: [dimensions, setDimensions], newEdge: [newEdge, setNewEdge], getBoardPoint, service, zoom: [zoom] } = useFlow();
|
|
856
|
+
const selected = useState(service, { selector: (s) => s.context.selected === props.id });
|
|
857
|
+
createEffect(() => {
|
|
858
|
+
ref();
|
|
859
|
+
zoom();
|
|
860
|
+
});
|
|
861
|
+
const hasParent = useState(service, { selector: ({ context: { data } }) => {
|
|
862
|
+
const edges = data?.edges;
|
|
863
|
+
if (!edges) return false;
|
|
864
|
+
return Object.values(edges).some((edge) => edge.to === props.id);
|
|
865
|
+
} });
|
|
866
|
+
createDraggable(props.id);
|
|
867
|
+
return ssr(_tmpl$3, ssrHydrationKey() + ssrAttribute("id", escape(props.id, true), false), `flex flex-col absolute cursor-grab bg-white rounded-md shadow-md select-none transition-[border,box-shadow] duration-200 ease-in-out hover:shadow-lg draggable ${selected() ? "border border-[#e38c29] z-[100]" : ""} ${!selected() ? "border border-[#e6d4be] z-[1]" : ""} min-w-48`, ssrStyleProperty("top:", `${escape(props.y, true)}px`) + ssrStyleProperty(";left:", `${escape(props.x, true)}px`), `pointer-events-none absolute flex items-center justify-end -top-7.5 right-0 transition-all duration-200 ease-in-out space-x-2 ${selected() ? "w-full opacity-100" : ""} ${!selected() ? "w-0 -right-3 opacity-0 overflow-hidden" : ""}`, ssrStyleProperty("overflow:", "visible") + ssrStyleProperty(";pointer-events:", "all") + ssrStyleProperty(";width:", `${escape(12, true) * 2}px`) + ssrStyleProperty(";height:", `${escape(12, true) * 2}px`), escape(createComponent(Show, {
|
|
868
|
+
get when() {
|
|
869
|
+
return hasParent();
|
|
870
|
+
},
|
|
871
|
+
get children() {
|
|
872
|
+
return ssr(_tmpl$$2, ssrHydrationKey(), ssrStyleProperty("pointer-events:", "all") + ssrStyleProperty(";fill-rule:", "evenodd") + ssrStyleProperty(";clip-rule:", "evenodd") + ssrStyleProperty(";stroke-linejoin:", "round") + ssrStyleProperty(";stroke-miterlimit:", "2") + ssrStyleProperty(";width:", `${escape(12, true) * 2}px`) + ssrStyleProperty(";height:", `${escape(12, true) * 2}px`));
|
|
873
|
+
}
|
|
874
|
+
})), ssrStyleProperty("pointer-events:", "all") + ssrStyleProperty(";width:", `${escape(12, true) * 2}px`) + ssrStyleProperty(";height:", `${escape(12, true) * 2}px`), escape(createComponent(Show, {
|
|
875
|
+
get when() {
|
|
876
|
+
return props.label;
|
|
877
|
+
},
|
|
878
|
+
keyed: true,
|
|
879
|
+
children: (label) => ssr(_tmpl$4, ssrHydrationKey(), escape(label))
|
|
880
|
+
})), escape(createComponent(Show, {
|
|
881
|
+
get when() {
|
|
882
|
+
return props.content;
|
|
883
|
+
},
|
|
884
|
+
keyed: true,
|
|
885
|
+
children: (content) => ssr(_tmpl$5, ssrHydrationKey(), escape(content))
|
|
886
|
+
})), escape(createComponent(Show, {
|
|
887
|
+
get when() {
|
|
888
|
+
return props.input || hasParent();
|
|
889
|
+
},
|
|
890
|
+
get children() {
|
|
891
|
+
return ssr(_tmpl$2, ssrHydrationKey(), ssrStyleProperty("left:", `-${escape(18, true)}px`), ssrStyleProperty("width:", `${escape(12, true)}px`) + ssrStyleProperty(";height:", `${escape(12, true)}px`) + ssrStyleProperty(";margin-top:", `${escape(12, true)}px`) + ssrStyleProperty(";pointer-events:", "all"));
|
|
892
|
+
}
|
|
893
|
+
})), ssrStyleProperty("right:", `-${escape(18, true)}px`), ssrStyleProperty("width:", `${escape(12, true)}px`) + ssrStyleProperty(";height:", `${escape(12, true)}px`) + ssrStyleProperty(";margin-top:", `${escape(12, true)}px`) + ssrStyleProperty(";pointer-events:", "all"));
|
|
894
|
+
};
|
|
895
|
+
//#endregion
|
|
896
|
+
//#region src/ui/components/NodesBoard.tsx
|
|
897
|
+
var _tmpl$$1 = [
|
|
898
|
+
"<div",
|
|
899
|
+
" class=\"relative mx-auto h-[calc(100vh-64px)] w-[calc(100vw-32px)]\"><div class=\"relative h-full w-full overflow-scroll rounded-lg border-2 border-gray-600\"><!--$-->",
|
|
900
|
+
"<!--/--><!--$-->",
|
|
901
|
+
"<!--/--><div class=\"",
|
|
902
|
+
"\" style=\"",
|
|
903
|
+
"\"><!--$-->",
|
|
904
|
+
"<!--/--><!--$-->",
|
|
905
|
+
"<!--/--></div></div><div class=\"absolute right-4 bottom-4 z-50 flex items-center gap-2 rounded-xl border border-gray-200 bg-white/90 p-2 shadow-lg backdrop-blur-md\"><button type=\"button\" class=\"flex size-9 cursor-pointer items-center justify-center rounded-lg bg-gray-100 text-lg font-bold text-gray-700 shadow-sm transition-all duration-150 hover:bg-gray-200 active:scale-95\" title=\"Zoom out\" aria-label=\"Zoom out\">-</button><button type=\"button\" class=\"h-9 cursor-pointer rounded-lg px-2 text-xs font-semibold text-gray-700 transition-colors hover:bg-gray-100\" title=\"Reset zoom\" aria-label=\"Reset zoom\"><!--$-->",
|
|
906
|
+
"<!--/-->%</button><button type=\"button\" class=\"flex size-9 cursor-pointer items-center justify-center rounded-lg bg-gray-100 text-lg font-bold text-gray-700 shadow-sm transition-all duration-150 hover:bg-gray-200 active:scale-95\" title=\"Zoom in\" aria-label=\"Zoom in\">+</button><div class=\"h-5 w-px bg-gray-300\"></div><button type=\"button\" class=\"flex size-9 cursor-pointer items-center justify-center rounded-lg bg-blue-600 text-white shadow transition-all duration-150 hover:bg-blue-700 active:scale-95\" title=\"Add parent node\" aria-label=\"Add parent node\"><svg class=\"size-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"></path></svg></button></div></div>"
|
|
907
|
+
];
|
|
908
|
+
/**
|
|
909
|
+
* Interactive board component containing the drag-drop viewport, zoom
|
|
910
|
+
* controls, panning gestures, and rendered nodes/edges.
|
|
911
|
+
*
|
|
912
|
+
* @returns The rendered Solid component.
|
|
913
|
+
*/
|
|
914
|
+
var NodesBoard = () => {
|
|
915
|
+
const [isPanning, setIsPanning] = createSignal(false);
|
|
916
|
+
const [transform, setTransform] = createSignal({
|
|
917
|
+
x: 0,
|
|
918
|
+
y: 0
|
|
919
|
+
});
|
|
920
|
+
const [id, setId] = createSignal("");
|
|
921
|
+
const [previousZoom, setPreviousZoom] = createSignal();
|
|
922
|
+
let cleanupPanning = () => {};
|
|
923
|
+
onCleanup(cleanupPanning);
|
|
924
|
+
const { board: [, setRef], service, newEdge: [newEdge], zoom: [zoom, setZoom] } = useFlow();
|
|
925
|
+
createEffect(on(zoom, () => {}, { defer: true }));
|
|
926
|
+
const selectedId = useState(service, { selector: (s) => s.context?.selected });
|
|
927
|
+
const selected = (id) => selectedId() === id;
|
|
928
|
+
const nodes = useState(service, {
|
|
929
|
+
selector: (s) => {
|
|
930
|
+
return (s.context.data?.nodes ?? []).map((item) => ({
|
|
931
|
+
id: item.id,
|
|
932
|
+
x: item.position.x,
|
|
933
|
+
y: item.position.y,
|
|
934
|
+
label: item.data.label,
|
|
935
|
+
content: item.data.content ?? "",
|
|
936
|
+
input: item.input
|
|
937
|
+
}));
|
|
938
|
+
},
|
|
939
|
+
equals: dequal$1
|
|
940
|
+
});
|
|
941
|
+
const cDim = () => {
|
|
942
|
+
const _zoom = zoom();
|
|
943
|
+
if (_zoom < 1) return 500 * _zoom;
|
|
944
|
+
return 500;
|
|
945
|
+
};
|
|
946
|
+
return createComponent(DragDropProvider, {
|
|
947
|
+
onDragMove: ({ draggable: { transform: _transform, node, id }, overlay }) => {
|
|
948
|
+
setId(id);
|
|
949
|
+
if (selected(id)) {
|
|
950
|
+
const x = node.offsetLeft + _transform.x / zoom();
|
|
951
|
+
const y = node.offsetTop + _transform.y / zoom();
|
|
952
|
+
overlay?.node.style.setProperty("top", y * 2 + "px");
|
|
953
|
+
overlay?.node.style.setProperty("left", x * 2 + "px");
|
|
954
|
+
const deltaX = _transform.x / zoom();
|
|
955
|
+
const deltaY = _transform.y / zoom();
|
|
956
|
+
node.style.setProperty("transform", `translate3d(${deltaX}px, ${deltaY}px, 0)`);
|
|
957
|
+
service.send({
|
|
958
|
+
type: "MOVE_IMMEDIATE",
|
|
959
|
+
payload: {
|
|
960
|
+
id: `${id}`,
|
|
961
|
+
x,
|
|
962
|
+
y
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
setTransform({ ..._transform });
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
onDragEnd: ({ draggable: { node, id } }) => {
|
|
969
|
+
if (!selected(id)) return;
|
|
970
|
+
const X = node.offsetLeft + transform().x / zoom();
|
|
971
|
+
const Y = node.offsetTop + transform().y / zoom();
|
|
972
|
+
node.style.setProperty("top", Y + "px");
|
|
973
|
+
node.style.setProperty("left", X + "px");
|
|
974
|
+
node.style.removeProperty("transform");
|
|
975
|
+
setTimeout(() => {
|
|
976
|
+
service.send({
|
|
977
|
+
type: "MOVE",
|
|
978
|
+
payload: {
|
|
979
|
+
id: `${id}`,
|
|
980
|
+
x: X,
|
|
981
|
+
y: Y
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
setTransform({
|
|
985
|
+
x: 0,
|
|
986
|
+
y: 0
|
|
987
|
+
});
|
|
988
|
+
}, 0);
|
|
989
|
+
},
|
|
990
|
+
get children() {
|
|
991
|
+
return [ssr(_tmpl$$1, ssrHydrationKey(), escape(createComponent(DragDropSensors, {})), escape(createComponent(DragBounds, {})), `relative cursor-crosshair ${isPanning() ? "cursor-grabbing" : ""}`, ssrStyleProperty("height:", `calc(${escape(cDim(), true)}vh - 85px)`) + ssrStyleProperty(";width:", `calc(${escape(cDim(), true)}vw - 53px)`) + ssrStyleProperty(";scale:", escape(zoom(), true)) + ssrStyleProperty(";transform-origin:", "top left"), escape(createComponent(EdgesBoard, {})), escape(createComponent(For, {
|
|
992
|
+
get each() {
|
|
993
|
+
return nodes();
|
|
994
|
+
},
|
|
995
|
+
children: NodeComponent
|
|
996
|
+
})), escape(Math.round(zoom() * 100))), createComponent(Show, {
|
|
997
|
+
get when() {
|
|
998
|
+
return !id() || !selected(id());
|
|
999
|
+
},
|
|
1000
|
+
get children() {
|
|
1001
|
+
return createComponent(DragOverlay, { children: "" });
|
|
1002
|
+
}
|
|
1003
|
+
})];
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
};
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/ui/components/FlowChart.tsx
|
|
1009
|
+
var _tmpl$ = [
|
|
1010
|
+
"<div",
|
|
1011
|
+
" class=\"relative h-full w-full\"><div class=\"relative h-full w-full bg-white bg-size-[30px_30px]\" style=\"",
|
|
1012
|
+
"\">",
|
|
1013
|
+
"</div></div>"
|
|
1014
|
+
];
|
|
1015
|
+
/**
|
|
1016
|
+
* Flowchart board canvas component that renders interactive nodes, edges,
|
|
1017
|
+
* pan/zoom, and toolbar controls.
|
|
1018
|
+
*
|
|
1019
|
+
* @param props - Flowchart configuration and event handlers of type
|
|
1020
|
+
* {@linkcode FlowProps}.
|
|
1021
|
+
*
|
|
1022
|
+
* @returns The rendered Solid component.
|
|
1023
|
+
*/
|
|
1024
|
+
var FlowChart = (props) => {
|
|
1025
|
+
const primaryNodes = props.config?.nodes ?? DEFAULT_NODES;
|
|
1026
|
+
const primaryEdges = props.config?.edges;
|
|
1027
|
+
const { service, newEdge: [newEdge, setNewEdge], getBoardPoint } = useFlow();
|
|
1028
|
+
onMount(() => {
|
|
1029
|
+
service.start();
|
|
1030
|
+
service.send({
|
|
1031
|
+
type: "CONFIGURE",
|
|
1032
|
+
payload: {
|
|
1033
|
+
nodes: primaryNodes,
|
|
1034
|
+
edges: primaryEdges ?? []
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
onCleanup(service.stop);
|
|
1039
|
+
return ssr(_tmpl$, ssrHydrationKey(), ssrStyleProperty("cursor:", newEdge() ? "inherit" : "crosshair") + ssrStyleProperty(";background-image:", "radial-gradient(circle, #b8b8b8bf 1px, rgba(0, 0, 0, 0) 1px)"), escape(createComponent(NodesBoard, {})));
|
|
1040
|
+
};
|
|
1041
|
+
//#endregion
|
|
1042
|
+
//#region src/ui/Flow.tsx
|
|
1043
|
+
/**
|
|
1044
|
+
* Root Flow component wrapping the {@linkcode FlowChart} inside the flow
|
|
1045
|
+
* context provider.
|
|
1046
|
+
*
|
|
1047
|
+
* @param props - Flow chart configuration and callbacks of type
|
|
1048
|
+
* {@linkcode FlowProps}.
|
|
1049
|
+
*
|
|
1050
|
+
* @returns The rendered Solid component.
|
|
1051
|
+
*/
|
|
1052
|
+
var Flow = (props) => createComponent(Provider, { get children() {
|
|
1053
|
+
return createComponent(FlowChart, props);
|
|
1054
|
+
} });
|
|
1055
|
+
//#endregion
|
|
1056
|
+
export { CLASSES, Flow };
|