@bemedev/mind-flow 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/lib/helpers/createContext.d.ts +7 -2
- package/lib/helpers/createContext.d.ts.map +1 -1
- package/lib/index.cjs.js +1 -1
- package/lib/index.es.js +556 -510
- package/lib/output.css +1 -1
- package/lib/server.cjs.js +645 -418
- package/lib/server.es.js +649 -422
- package/lib/services/main.machine.d.ts +4857 -218
- package/lib/services/main.machine.d.ts.map +1 -1
- package/lib/{ui/components/FlowChart.data.d.ts → services/main.machine.data.d.ts} +4 -2
- package/lib/services/main.machine.data.d.ts.map +1 -0
- package/lib/services/main.machine.helpers.d.ts +32 -0
- package/lib/services/main.machine.helpers.d.ts.map +1 -0
- package/lib/services/main.machine.typings.d.ts +126 -0
- package/lib/services/main.machine.typings.d.ts.map +1 -0
- package/lib/ui/Flow.d.ts +2 -0
- package/lib/ui/Flow.d.ts.map +1 -1
- package/lib/ui/components/Bounds.d.ts +2 -0
- package/lib/ui/components/Bounds.d.ts.map +1 -1
- package/lib/ui/components/EdgeComponent.d.ts +3 -2
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
- package/lib/ui/components/EdgesBoard.d.ts +2 -0
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.context.d.ts +5608 -996
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.d.ts +3 -1
- package/lib/ui/components/FlowChart.d.ts.map +1 -1
- package/lib/ui/components/NodeComponent.d.ts +2 -10
- package/lib/ui/components/NodeComponent.d.ts.map +1 -1
- package/lib/ui/components/NodesBoard.d.ts +2 -0
- package/lib/ui/components/NodesBoard.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/helpers/createContext.ts +7 -2
- package/src/{ui/components/FlowChart.data.ts → services/main.machine.data.ts} +3 -1
- package/src/services/main.machine.helpers.ts +55 -0
- package/src/services/main.machine.ts +288 -70
- package/src/services/main.machine.typings.ts +98 -0
- package/src/ui/Flow.tsx +2 -0
- package/src/ui/components/Bounds.tsx +13 -8
- package/src/ui/components/EdgeComponent.tsx +78 -56
- package/src/ui/components/EdgesBoard.tsx +13 -36
- package/src/ui/components/FlowChart.context.ts +204 -310
- package/src/ui/components/FlowChart.tsx +16 -16
- package/src/ui/components/NodeComponent.tsx +60 -104
- package/src/ui/components/NodesBoard.tsx +74 -47
- package/src/ui/components/classes.ts +1 -1
- package/lib/services/main.typings.d.ts +0 -68
- package/lib/services/main.typings.d.ts.map +0 -1
- package/lib/ui/components/FlowChart.data.d.ts.map +0 -1
- package/src/services/main.typings.ts +0 -50
|
@@ -1,366 +1,260 @@
|
|
|
1
1
|
import { interpret } from '@bemedev/app';
|
|
2
|
-
import { dequal } from 'dequal/lite';
|
|
3
|
-
import { createSignal } from 'solid-js';
|
|
4
|
-
import { produce } from 'solid-js/store';
|
|
5
2
|
|
|
6
|
-
import { createContext } from '
|
|
7
|
-
import { machine } from '
|
|
8
|
-
import type { Point, Vector } from '../../services/main.typings';
|
|
3
|
+
import { createContext } from '#helpers/createContext';
|
|
4
|
+
import { machine } from '#main-machine';
|
|
9
5
|
import {
|
|
10
6
|
BOUNDS_CONSTRAINTS,
|
|
11
7
|
DEFAULT_INPUT_OFFSET,
|
|
12
8
|
getDefaultOutputOffset,
|
|
13
9
|
PARENT_CHILD_GAP_WIDTH,
|
|
14
|
-
} from '
|
|
15
|
-
|
|
16
|
-
/** Layout dimensions and handle offset coordinates for a flowchart node. */
|
|
17
|
-
type Dimensions = {
|
|
18
|
-
/** Node width in pixels. */
|
|
19
|
-
width: number;
|
|
20
|
-
/** Node height in pixels. */
|
|
21
|
-
height: number;
|
|
22
|
-
/** Output handle point coordinates of type {@linkcode Point}. */
|
|
23
|
-
output: Point;
|
|
24
|
-
/** Input handle point coordinates of type {@linkcode Point}. */
|
|
25
|
-
input?: Point;
|
|
26
|
-
/** Output handle relative offset point of type {@linkcode Point}. */
|
|
27
|
-
outputOffset?: Point;
|
|
28
|
-
/** Input handle relative offset point of type {@linkcode Point}. */
|
|
29
|
-
inputOffset?: Point;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/** Connection edge coordinate representation between two endpoints. */
|
|
33
|
-
type Edge = {
|
|
34
|
-
/** Source node identifier. */
|
|
35
|
-
from: string;
|
|
36
|
-
} & Vector;
|
|
10
|
+
} from '#services/main.machine.data';
|
|
11
|
+
import type { Dimension, Point } from '#services/main.machine.typings';
|
|
37
12
|
|
|
38
13
|
/**
|
|
39
14
|
* Solid Context Provider component and hook for accessing flowchart board state,
|
|
40
15
|
* services, and zoom.
|
|
16
|
+
*
|
|
17
|
+
* @see {@linkcode machine}, {@linkcode createContext}
|
|
41
18
|
*/
|
|
42
19
|
export const [Provider, useFlow] = createContext(
|
|
43
20
|
() => {
|
|
44
|
-
/**
|
|
45
|
-
* Shared state machine interpreter service instance for flowchart state
|
|
46
|
-
* management.
|
|
47
|
-
*/
|
|
21
|
+
/** Shared service for flowchart state management. */
|
|
48
22
|
const service = interpret(machine, {
|
|
49
|
-
context: {},
|
|
50
|
-
pContext: {
|
|
23
|
+
context: { zoom: 1, edgesPositions: {}, bounds: { x: 0, y: 0 } },
|
|
24
|
+
pContext: {
|
|
25
|
+
generatedId: null,
|
|
26
|
+
dimensions: {},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Converts viewport screen coordinates to unscaled board coordinates.
|
|
30
|
+
*
|
|
31
|
+
* @param clientX - Client X coordinate in pixels.
|
|
32
|
+
* @param clientY - Client Y coordinate in pixels.
|
|
33
|
+
* @param el - Board layout details of type {@linkcode Board}.
|
|
34
|
+
*
|
|
35
|
+
* @returns Calculated 2D coordinate of type {@linkcode Point}.
|
|
36
|
+
*/
|
|
37
|
+
getBoardPosition(clientX, clientY, el): Point {
|
|
38
|
+
if (!el) return { x: clientX, y: clientY };
|
|
39
|
+
const currentZoom = service.state.context.zoom;
|
|
40
|
+
const x = (clientX - el.self.left) / currentZoom;
|
|
41
|
+
const y = (clientY - el.self.top) / currentZoom;
|
|
42
|
+
return { x, y };
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Clamps given coordinates within board boundaries.
|
|
47
|
+
*
|
|
48
|
+
* @param board - Board layout and container details of type
|
|
49
|
+
* {@linkcode Board}.
|
|
50
|
+
* @param x - Desired X position in pixels.
|
|
51
|
+
* @param y - Desired Y position in pixels.
|
|
52
|
+
* @param nodeWidth - Node width in pixels, defaults to `192`.
|
|
53
|
+
* @param nodeHeight - Node height in pixels, defaults to `50`.
|
|
54
|
+
*
|
|
55
|
+
* @returns Clamped coordinate of type {@linkcode Point}.
|
|
56
|
+
*
|
|
57
|
+
* @see {@linkcode BOUNDS_CONSTRAINTS}
|
|
58
|
+
*/
|
|
59
|
+
clampPosition(board, x, y, nodeWidth = 192, nodeHeight = 50): Point {
|
|
60
|
+
const container = board?.parent;
|
|
61
|
+
if (!container) return { x, y };
|
|
62
|
+
|
|
63
|
+
const currentZoom = service.state.context.zoom ?? 1;
|
|
64
|
+
|
|
65
|
+
const minX =
|
|
66
|
+
container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
|
|
67
|
+
|
|
68
|
+
const maxX = Math.max(
|
|
69
|
+
minX,
|
|
70
|
+
(container.scrollLeft + container.width) / currentZoom -
|
|
71
|
+
BOUNDS_CONSTRAINTS.horizontal -
|
|
72
|
+
nodeWidth,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const minY =
|
|
76
|
+
container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
|
|
77
|
+
|
|
78
|
+
const maxY = Math.max(
|
|
79
|
+
minY,
|
|
80
|
+
(container.scrollTop + container.height) / currentZoom -
|
|
81
|
+
BOUNDS_CONSTRAINTS.vertical -
|
|
82
|
+
nodeHeight,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
x: Math.min(Math.max(x, minX), maxX),
|
|
87
|
+
y: Math.min(Math.max(y, minY), maxY),
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Computes node dimension, connection points, and handle offsets.
|
|
93
|
+
*
|
|
94
|
+
* @param position - Node position of type {@linkcode Point}.
|
|
95
|
+
* @param parentDimension - Optional sizing and offsets to inherit.
|
|
96
|
+
*
|
|
97
|
+
* @returns Calculated dimension object of type {@linkcode Dimension}.
|
|
98
|
+
*
|
|
99
|
+
* @see {@linkcode DEFAULT_INPUT_OFFSET}, {@linkcode getDefaultOutputOffset}
|
|
100
|
+
*/
|
|
101
|
+
calculateDimensions: (
|
|
102
|
+
position: Point,
|
|
103
|
+
parentDimension: Pick<
|
|
104
|
+
Dimension,
|
|
105
|
+
'width' | 'height' | 'outputOffset' | 'inputOffset'
|
|
106
|
+
> = { width: 192, height: 50, inputOffset: DEFAULT_INPUT_OFFSET },
|
|
107
|
+
): Dimension => {
|
|
108
|
+
const width = parentDimension.width;
|
|
109
|
+
const height = parentDimension.height;
|
|
110
|
+
const outputOffset =
|
|
111
|
+
parentDimension.outputOffset ?? getDefaultOutputOffset(width);
|
|
112
|
+
const inputOffset = parentDimension.inputOffset!;
|
|
113
|
+
|
|
114
|
+
const output = {
|
|
115
|
+
x: position.x + outputOffset.x,
|
|
116
|
+
y: position.y + outputOffset.y,
|
|
117
|
+
};
|
|
118
|
+
const input = {
|
|
119
|
+
x: position.x + inputOffset.x,
|
|
120
|
+
y: position.y + inputOffset.y,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return { width, height, output, input, outputOffset, inputOffset };
|
|
124
|
+
},
|
|
125
|
+
},
|
|
51
126
|
});
|
|
52
127
|
|
|
53
|
-
|
|
54
|
-
const newEdge = createSignal<Edge>();
|
|
55
|
-
const [boardRef, setBoardRef] = createSignal<HTMLDivElement>();
|
|
56
|
-
|
|
57
|
-
const [dimensions, setDimensions] = createSignal<Record<string, Dimensions>>(
|
|
58
|
-
{},
|
|
59
|
-
{ equals: dequal },
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Converts client viewport coordinates to board canvas coordinates adjusted for
|
|
64
|
-
* zoom and scroll.
|
|
65
|
-
*
|
|
66
|
-
* @param clientX - Viewport horizontal coordinate.
|
|
67
|
-
* @param clientY - Viewport vertical coordinate.
|
|
68
|
-
*
|
|
69
|
-
* @returns Board coordinate point of type {@linkcode Point}.
|
|
70
|
-
*/
|
|
71
|
-
const getBoardPoint = (clientX: number, clientY: number): Point => {
|
|
72
|
-
const el = boardRef();
|
|
73
|
-
if (!el) return { x: clientX, y: clientY };
|
|
74
|
-
|
|
75
|
-
const rect = el.getBoundingClientRect();
|
|
76
|
-
const currentZoom = zoom[0]();
|
|
77
|
-
const x = clientX - rect.left / currentZoom;
|
|
78
|
-
const y = clientY - rect.top / currentZoom;
|
|
79
|
-
return { x, y };
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const [edgesPositions, setEdgesPositions] = createSignal<Record<string, Vector>>(
|
|
83
|
-
{},
|
|
84
|
-
{ equals: false },
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Clamps node coordinates within the visible container boundaries.
|
|
89
|
-
*
|
|
90
|
-
* @param x - Desired horizontal X coordinate.
|
|
91
|
-
* @param y - Desired vertical Y coordinate.
|
|
92
|
-
* @param nodeWidth - Width of the node element in pixels, defaults to `192`.
|
|
93
|
-
* @param nodeHeight - Height of the node element in pixels, defaults to `50`.
|
|
94
|
-
*
|
|
95
|
-
* @returns Clamped coordinate point of type {@linkcode Point}.
|
|
96
|
-
*/
|
|
97
|
-
const clampPosition = (
|
|
98
|
-
x: number,
|
|
99
|
-
y: number,
|
|
100
|
-
nodeWidth = 192,
|
|
101
|
-
nodeHeight = 50,
|
|
102
|
-
): Point => {
|
|
103
|
-
const container = boardRef()?.parentElement;
|
|
104
|
-
if (!container) return { x, y };
|
|
105
|
-
|
|
106
|
-
const currentZoom = zoom[0]();
|
|
107
|
-
|
|
108
|
-
const minX =
|
|
109
|
-
container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
|
|
110
|
-
|
|
111
|
-
const maxX = Math.max(
|
|
112
|
-
minX,
|
|
113
|
-
(container.scrollLeft + container.clientWidth) / currentZoom -
|
|
114
|
-
BOUNDS_CONSTRAINTS.horizontal -
|
|
115
|
-
nodeWidth,
|
|
116
|
-
);
|
|
128
|
+
// #endregion
|
|
117
129
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const maxY = Math.max(
|
|
121
|
-
minY,
|
|
122
|
-
(container.scrollTop + container.clientHeight) / currentZoom -
|
|
123
|
-
BOUNDS_CONSTRAINTS.vertical -
|
|
124
|
-
nodeHeight,
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
x: Math.min(Math.max(x, minX), maxX),
|
|
129
|
-
y: Math.min(Math.max(y, minY), maxY),
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
service.addOptions(({ voidAction, batch, assign }) => ({
|
|
130
|
+
service.addOptions(({ assign }) => ({
|
|
134
131
|
actions: {
|
|
135
|
-
placeChild: assign('
|
|
136
|
-
ADD_CHILD: ({ payload, context: { data }, pContext
|
|
137
|
-
|
|
132
|
+
placeChild: assign('data.nodes', {
|
|
133
|
+
ADD_CHILD: ({ payload, context: { data, board }, pContext }) => {
|
|
134
|
+
if (!board) return data?.nodes;
|
|
135
|
+
const nodes = data?.nodes;
|
|
138
136
|
if (!payload) return nodes;
|
|
139
137
|
|
|
140
|
-
const parentNode = nodes
|
|
138
|
+
const parentNode = nodes?.find(node => node.id === payload);
|
|
141
139
|
if (!parentNode) return nodes;
|
|
142
140
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const
|
|
141
|
+
const parentDimension = pContext.dimensions[payload];
|
|
142
|
+
|
|
143
|
+
const id = `node-${pContext.generatedId}`;
|
|
144
|
+
const width = parentDimension?.width ?? 192;
|
|
145
|
+
const height = parentDimension?.height ?? 50;
|
|
146
146
|
const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
147
147
|
const initialY = parentNode.position.y;
|
|
148
|
-
const position = clampPosition(
|
|
148
|
+
const position = pContext.clampPosition(
|
|
149
|
+
board,
|
|
150
|
+
initialX,
|
|
151
|
+
initialY,
|
|
152
|
+
width,
|
|
153
|
+
height,
|
|
154
|
+
);
|
|
155
|
+
const dimension = pContext.calculateDimensions(
|
|
156
|
+
position,
|
|
157
|
+
parentDimension,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
nodes?.push({
|
|
161
|
+
id,
|
|
162
|
+
data: { content: '<Nouveau nœud>' },
|
|
163
|
+
input: true,
|
|
164
|
+
position,
|
|
165
|
+
});
|
|
166
|
+
pContext.dimensions[id] = dimension;
|
|
149
167
|
|
|
150
|
-
return
|
|
151
|
-
...nodes,
|
|
152
|
-
{ id, data: { content: '<Nouveau nœud>' }, input: true, position },
|
|
153
|
-
];
|
|
168
|
+
return nodes;
|
|
154
169
|
},
|
|
155
170
|
}),
|
|
156
171
|
|
|
157
|
-
placeParent: assign('
|
|
158
|
-
ADD_PARENT: ({ context: { data
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
const
|
|
172
|
+
placeParent: assign('data.nodes', {
|
|
173
|
+
ADD_PARENT: ({ context: { data, zoom = 1, board }, pContext }) => {
|
|
174
|
+
if (!board) return data?.nodes;
|
|
175
|
+
const nodes = data?.nodes;
|
|
176
|
+
const id = `node-${pContext.generatedId}`;
|
|
177
|
+
const container = board.parent;
|
|
162
178
|
const scrollLeft = container?.scrollLeft ?? 0;
|
|
163
179
|
const scrollTop = container?.scrollTop ?? 0;
|
|
164
|
-
const width = container?.
|
|
165
|
-
const height = container?.
|
|
166
|
-
const currentZoom = zoom
|
|
180
|
+
const width = container?.width ?? 0;
|
|
181
|
+
const height = container?.height ?? 0;
|
|
182
|
+
const currentZoom = zoom;
|
|
167
183
|
const x = (scrollLeft + width / 2) / currentZoom;
|
|
168
184
|
const y = (scrollTop + height / 2) / currentZoom;
|
|
185
|
+
const position = pContext.clampPosition(board, x, y);
|
|
186
|
+
const dimension = pContext.calculateDimensions(position);
|
|
187
|
+
|
|
188
|
+
nodes?.push({
|
|
189
|
+
id,
|
|
190
|
+
data: { content: '<Nouveau nœud>' },
|
|
191
|
+
input: false,
|
|
192
|
+
position,
|
|
193
|
+
});
|
|
169
194
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
{
|
|
173
|
-
id,
|
|
174
|
-
data: { content: '<Nouveau nœud>' },
|
|
175
|
-
input: false,
|
|
176
|
-
position: { x, y },
|
|
177
|
-
},
|
|
178
|
-
];
|
|
195
|
+
pContext.dimensions[id] = dimension;
|
|
196
|
+
return nodes;
|
|
179
197
|
},
|
|
180
198
|
}),
|
|
181
199
|
|
|
182
|
-
placeSibling: assign('
|
|
183
|
-
ADD_SIBLING: ({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const edges = data?.edges ?? [];
|
|
189
|
-
const nodes = data?.nodes ?? [];
|
|
190
|
-
const parentID = edges.find(edge => edge.to === payload)?.from;
|
|
200
|
+
placeSibling: assign('data.nodes', {
|
|
201
|
+
ADD_SIBLING: ({ payload, context: { data, board }, pContext }) => {
|
|
202
|
+
if (!board) return data?.nodes;
|
|
203
|
+
const edges = data?.edges;
|
|
204
|
+
const nodes = data?.nodes;
|
|
205
|
+
const parentID = edges?.find(edge => edge.to === payload)?.from;
|
|
191
206
|
if (!parentID) return nodes;
|
|
192
207
|
|
|
193
|
-
const parentNode = nodes
|
|
208
|
+
const parentNode = nodes?.find(node => node.id === parentID);
|
|
194
209
|
if (!parentNode) return nodes;
|
|
195
210
|
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
const
|
|
211
|
+
const parentDimension = pContext.dimensions[parentID];
|
|
212
|
+
const id = `node-${pContext.generatedId}`;
|
|
213
|
+
const width = parentDimension?.width ?? 192;
|
|
214
|
+
const height = parentDimension?.height ?? 50;
|
|
199
215
|
const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
200
|
-
const initialY = parentNode.position.y +
|
|
201
|
-
const position = clampPosition(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const edges = data?.edges ?? [];
|
|
213
|
-
setEdgesPositions(data => {
|
|
214
|
-
const array = Object.entries({ ...data }).filter(([id]) =>
|
|
215
|
-
edges.some(edge => edge.id === id),
|
|
216
|
-
);
|
|
216
|
+
const initialY = parentNode.position.y + PARENT_CHILD_GAP_WIDTH;
|
|
217
|
+
const position = pContext.clampPosition(
|
|
218
|
+
board,
|
|
219
|
+
initialX,
|
|
220
|
+
initialY,
|
|
221
|
+
width,
|
|
222
|
+
height,
|
|
223
|
+
);
|
|
224
|
+
const dimension = pContext.calculateDimensions(
|
|
225
|
+
position,
|
|
226
|
+
parentDimension,
|
|
227
|
+
);
|
|
217
228
|
|
|
218
|
-
|
|
229
|
+
nodes?.push({
|
|
230
|
+
id,
|
|
231
|
+
data: { content: '<Nouveau nœud>' },
|
|
232
|
+
input: true,
|
|
233
|
+
position,
|
|
219
234
|
});
|
|
220
|
-
}),
|
|
221
|
-
voidAction({
|
|
222
|
-
else: ({ context: { data } }) => {
|
|
223
|
-
const edges = data?.edges ?? [];
|
|
224
|
-
setEdgesPositions(
|
|
225
|
-
produce(next => {
|
|
226
|
-
edges?.forEach(({ from, id, to }) => {
|
|
227
|
-
const output = dimensions()[from]?.output;
|
|
228
|
-
const input = dimensions()[to]?.input;
|
|
229
|
-
|
|
230
|
-
if (output && input) {
|
|
231
|
-
next[id] = {
|
|
232
|
-
x0: output.x,
|
|
233
|
-
y0: output.y,
|
|
234
|
-
x1: input.x,
|
|
235
|
-
y1: input.y,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
}),
|
|
240
|
-
);
|
|
241
|
-
},
|
|
242
|
-
MOVE: ({ context: { data }, payload }) => {
|
|
243
|
-
const edges = data?.edges ?? [];
|
|
244
|
-
|
|
245
|
-
setEdgesPositions(
|
|
246
|
-
produce(next => {
|
|
247
|
-
edges?.forEach(({ from, to, id }) => {
|
|
248
|
-
if (from === payload.id) {
|
|
249
|
-
const offset =
|
|
250
|
-
dimensions()[payload.id]?.outputOffset ??
|
|
251
|
-
getDefaultOutputOffset(dimensions()[payload.id]?.width);
|
|
252
|
-
|
|
253
|
-
const x0 = payload.x + offset.x;
|
|
254
|
-
const y0 = payload.y + offset.y;
|
|
255
|
-
next[id] = { ...next[id], x0, y0 };
|
|
256
|
-
|
|
257
|
-
setDimensions(
|
|
258
|
-
produce(data => {
|
|
259
|
-
if (data[payload.id]) {
|
|
260
|
-
data[payload.id] = {
|
|
261
|
-
...data[payload.id],
|
|
262
|
-
output: { x: x0, y: y0 },
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
}),
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
if (to === payload.id) {
|
|
269
|
-
const offset =
|
|
270
|
-
dimensions()[payload.id]?.inputOffset ??
|
|
271
|
-
DEFAULT_INPUT_OFFSET;
|
|
272
|
-
|
|
273
|
-
const x1 = payload.x + offset.x;
|
|
274
|
-
const y1 = payload.y + offset.y;
|
|
275
|
-
next[id] = { ...next[id], x1, y1 };
|
|
276
235
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
...data[payload.id],
|
|
282
|
-
input: { x: x1, y: y1 },
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
}),
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
}),
|
|
290
|
-
);
|
|
291
|
-
},
|
|
292
|
-
}),
|
|
293
|
-
|
|
294
|
-
assign('context.updatingUI', () => true),
|
|
295
|
-
),
|
|
296
|
-
|
|
297
|
-
buildImmediateUI: voidAction({
|
|
298
|
-
MOVE_IMMEDIATE: ({ context: { data }, payload }) => {
|
|
299
|
-
const edges = data?.edges ?? [];
|
|
300
|
-
|
|
301
|
-
setEdgesPositions(
|
|
302
|
-
produce(next => {
|
|
303
|
-
edges?.forEach(({ from, to, id }) => {
|
|
304
|
-
if (from === payload.id) {
|
|
305
|
-
const offset =
|
|
306
|
-
dimensions()[payload.id]?.outputOffset ??
|
|
307
|
-
getDefaultOutputOffset(dimensions()[payload.id]?.width);
|
|
308
|
-
|
|
309
|
-
const x0 = payload.x + offset.x;
|
|
310
|
-
const y0 = payload.y + offset.y;
|
|
311
|
-
next[id] = { ...next[id], x0, y0 };
|
|
312
|
-
|
|
313
|
-
setDimensions(
|
|
314
|
-
produce(data => {
|
|
315
|
-
if (data[payload.id]) {
|
|
316
|
-
data[payload.id] = {
|
|
317
|
-
...data[payload.id],
|
|
318
|
-
output: { x: x0, y: y0 },
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
}),
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (to === payload.id) {
|
|
326
|
-
const offset =
|
|
327
|
-
dimensions()[payload.id]?.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
328
|
-
|
|
329
|
-
const x1 = payload.x + offset.x;
|
|
330
|
-
const y1 = payload.y + offset.y;
|
|
331
|
-
next[id] = { ...next[id], x1, y1 };
|
|
236
|
+
pContext.dimensions[id] = dimension;
|
|
237
|
+
return nodes;
|
|
238
|
+
},
|
|
239
|
+
}),
|
|
332
240
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}),
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
}),
|
|
241
|
+
moveNewEdge: assign('newEdge', {
|
|
242
|
+
MOVE_NEW_EDGE: ({ context: { newEdge, board }, payload, pContext }) => {
|
|
243
|
+
if (!board) return undefined;
|
|
244
|
+
if (!newEdge) return undefined;
|
|
245
|
+
const { x: x1, y: y1 } = pContext.getBoardPosition(
|
|
246
|
+
payload.x,
|
|
247
|
+
payload.y,
|
|
248
|
+
board,
|
|
346
249
|
);
|
|
250
|
+
return { ...newEdge, x1, y1 };
|
|
347
251
|
},
|
|
348
252
|
}),
|
|
349
253
|
},
|
|
350
254
|
}));
|
|
351
255
|
|
|
352
256
|
service.start();
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
dimensions: [dimensions, setDimensions] as const,
|
|
356
|
-
newEdge,
|
|
357
|
-
board: [boardRef, setBoardRef] as const,
|
|
358
|
-
getBoardPoint,
|
|
359
|
-
edgesPositions: [edgesPositions, setEdgesPositions] as const,
|
|
360
|
-
service,
|
|
361
|
-
zoom,
|
|
362
|
-
clampPosition,
|
|
363
|
-
};
|
|
257
|
+
return service;
|
|
364
258
|
},
|
|
365
259
|
{ name: 'FlowContext' },
|
|
366
260
|
);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { createState } from '@bemedev/app-solidjs';
|
|
1
2
|
import type { inferT } from '@bemedev/app/typings';
|
|
2
3
|
import { Component, onCleanup, onMount } from 'solid-js';
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
+
import { DEFAULT_NODES } from '../../services/main.machine.data';
|
|
6
|
+
import type { edgeJSON, nodeJSON } from '../../services/main.machine.typings';
|
|
5
7
|
import { useFlow } from './FlowChart.context';
|
|
6
|
-
import { DEFAULT_NODES } from './FlowChart.data';
|
|
7
8
|
import { NodesBoard } from './NodesBoard';
|
|
8
9
|
|
|
9
10
|
/** Serialized node properties type inferred from schema {@linkcode nodeJSON}. */
|
|
@@ -58,44 +59,43 @@ export type FlowProps = {
|
|
|
58
59
|
* {@linkcode FlowProps}.
|
|
59
60
|
*
|
|
60
61
|
* @returns The rendered Solid component.
|
|
62
|
+
*
|
|
63
|
+
* @see {@linkcode NodesBoard}, {@linkcode useFlow}, {@linkcode DEFAULT_NODES}
|
|
61
64
|
*/
|
|
62
65
|
export const FlowChart: Component<FlowProps> = props => {
|
|
63
66
|
const primaryNodes = props.config?.nodes ?? DEFAULT_NODES;
|
|
64
67
|
const primaryEdges = props.config?.edges;
|
|
65
68
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
getBoardPoint,
|
|
70
|
-
} = useFlow();
|
|
69
|
+
const service = useFlow();
|
|
70
|
+
|
|
71
|
+
const hasNewEdge = createState(service, { selector: s => !!s.context.newEdge });
|
|
71
72
|
|
|
72
73
|
onMount(() => {
|
|
73
|
-
service.
|
|
74
|
+
service.resume();
|
|
74
75
|
service.send({
|
|
75
76
|
type: 'CONFIGURE',
|
|
76
77
|
payload: { nodes: primaryNodes, edges: primaryEdges ?? [] },
|
|
77
78
|
});
|
|
78
79
|
});
|
|
79
80
|
|
|
80
|
-
onCleanup(service.
|
|
81
|
+
onCleanup(service.pause);
|
|
81
82
|
|
|
82
83
|
return (
|
|
83
84
|
<div
|
|
84
85
|
class='relative h-full w-full'
|
|
85
|
-
onMouseUp={() =>
|
|
86
|
+
onMouseUp={() => service.send('CLEAR_NEW_EDGE')}
|
|
86
87
|
onMouseMove={event => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
88
|
+
service.send({
|
|
89
|
+
type: 'MOVE_NEW_EDGE',
|
|
90
|
+
payload: { x: event.clientX, y: event.clientY },
|
|
91
|
+
});
|
|
92
92
|
}}
|
|
93
93
|
style={{}}
|
|
94
94
|
>
|
|
95
95
|
<div
|
|
96
96
|
class='relative h-full w-full bg-white bg-size-[30px_30px]'
|
|
97
97
|
style={{
|
|
98
|
-
cursor:
|
|
98
|
+
cursor: hasNewEdge() ? 'inherit' : 'crosshair',
|
|
99
99
|
'background-image':
|
|
100
100
|
'radial-gradient(circle, #b8b8b8bf 1px, rgba(0, 0, 0, 0) 1px)',
|
|
101
101
|
}}
|