@bemedev/mind-flow 0.0.1 → 0.1.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/README.md +95 -0
- package/lib/helpers/createContext.d.ts +8 -11
- package/lib/helpers/createContext.d.ts.map +1 -1
- package/lib/index.cjs.js +1 -1
- package/lib/index.es.js +240 -235
- package/lib/server.cjs.js +181 -121
- package/lib/server.es.js +181 -121
- package/lib/services/main.machine.d.ts +7 -9
- package/lib/services/main.machine.d.ts.map +1 -1
- package/lib/services/main.typings.d.ts +3 -3
- package/lib/services/main.typings.d.ts.map +1 -1
- package/lib/ui/Flow.d.ts +2 -2
- package/lib/ui/Flow.d.ts.map +1 -1
- package/lib/ui/components/Bounds.d.ts +4 -4
- package/lib/ui/components/Bounds.d.ts.map +1 -1
- package/lib/ui/components/EdgeComponent.d.ts +2 -2
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
- package/lib/ui/components/EdgesBoard.d.ts +2 -2
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.context.d.ts +10 -18
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.d.ts +4 -10
- package/lib/ui/components/FlowChart.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.data.d.ts +10 -11
- package/lib/ui/components/FlowChart.data.d.ts.map +1 -1
- package/lib/ui/components/NodeComponent.d.ts +2 -2
- package/lib/ui/components/NodeComponent.d.ts.map +1 -1
- package/lib/ui/components/NodesBoard.d.ts +2 -2
- package/lib/ui/components/NodesBoard.d.ts.map +1 -1
- package/lib/ui/components/classes.d.ts +2 -2
- package/package.json +3 -9
- package/src/README.md +2 -2
- package/src/helpers/createContext.ts +8 -11
- package/src/services/main.machine.ts +18 -46
- package/src/services/main.typings.ts +3 -3
- package/src/ui/Flow.tsx +3 -2
- package/src/ui/components/Bounds.tsx +35 -44
- package/src/ui/components/EdgeComponent.tsx +7 -8
- package/src/ui/components/EdgesBoard.tsx +3 -2
- package/src/ui/components/FlowChart.context.ts +75 -82
- package/src/ui/components/FlowChart.data.ts +12 -15
- package/src/ui/components/FlowChart.tsx +6 -11
- package/src/ui/components/NodeComponent.tsx +13 -25
- package/src/ui/components/NodesBoard.tsx +120 -111
- package/src/ui/components/classes.ts +2 -2
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useDragDropContext,
|
|
3
|
-
type Transformer,
|
|
4
|
-
} from '@thisbeyond/solid-dnd';
|
|
1
|
+
import { useDragDropContext, type Transformer } from '@thisbeyond/solid-dnd';
|
|
5
2
|
import { type Component } from 'solid-js';
|
|
6
|
-
|
|
3
|
+
|
|
7
4
|
import { useFlow } from './FlowChart.context';
|
|
5
|
+
import { BOUNDS_CONSTRAINTS } from './FlowChart.data';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
|
-
* Drag boundary transformer component that clamps draggable nodes within
|
|
11
|
-
*
|
|
8
|
+
* Drag boundary transformer component that clamps draggable nodes within container
|
|
9
|
+
* scroll bounds.
|
|
12
10
|
*
|
|
13
|
-
* @returns `null` as this component performs side-effect transformer
|
|
14
|
-
*
|
|
11
|
+
* @returns `null` as this component performs side-effect transformer registrations
|
|
12
|
+
* only.
|
|
15
13
|
*/
|
|
16
14
|
export const DragBounds: Component = () => {
|
|
17
15
|
const {
|
|
@@ -19,58 +17,51 @@ export const DragBounds: Component = () => {
|
|
|
19
17
|
zoom: [zoom],
|
|
20
18
|
} = useFlow();
|
|
21
19
|
|
|
22
|
-
const [
|
|
23
|
-
|
|
24
|
-
{ addTransformer, removeTransformer, onDragStart, onDragEnd },
|
|
25
|
-
] = useDragDropContext()!;
|
|
20
|
+
const [state, { addTransformer, removeTransformer, onDragStart, onDragEnd }] =
|
|
21
|
+
useDragDropContext()!;
|
|
26
22
|
|
|
27
23
|
const transformer: Transformer = {
|
|
28
24
|
id: 'clamp-to-container',
|
|
29
25
|
order: 100,
|
|
30
26
|
callback: transform => {
|
|
31
|
-
const container = ref()
|
|
27
|
+
const container = ref();
|
|
32
28
|
const activeDraggable = state.active.draggable;
|
|
33
29
|
if (!container || !activeDraggable) return transform;
|
|
34
|
-
const draggableLayout = activeDraggable.layout;
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const innerLeft = containerRect.left + container.clientLeft;
|
|
39
|
-
const innerTop = containerRect.top + container.clientTop;
|
|
40
|
-
const innerRight = innerLeft + container.clientWidth;
|
|
41
|
-
const innerBottom = innerTop + container.clientHeight;
|
|
42
|
-
// #endregion
|
|
31
|
+
const node = activeDraggable.node as HTMLElement;
|
|
32
|
+
if (!node) return transform;
|
|
43
33
|
|
|
44
|
-
// #region Convert boundaries to board coordinate space
|
|
45
34
|
const currentZoom = zoom();
|
|
46
|
-
const minX =
|
|
47
|
-
innerLeft -
|
|
48
|
-
draggableLayout.left +
|
|
49
|
-
BOUNDS_CONSTRAINTS.horizontal * currentZoom;
|
|
50
35
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
// #region Board space boundaries (unscaled CSS pixels relative to board container)
|
|
37
|
+
const minBoardX = BOUNDS_CONSTRAINTS.horizontal;
|
|
38
|
+
const maxBoardX = Math.max(
|
|
39
|
+
minBoardX,
|
|
40
|
+
container.clientWidth / currentZoom -
|
|
41
|
+
node.offsetWidth -
|
|
42
|
+
BOUNDS_CONSTRAINTS.horizontal,
|
|
56
43
|
);
|
|
57
44
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
minY,
|
|
65
|
-
innerBottom -
|
|
66
|
-
draggableLayout.bottom -
|
|
67
|
-
BOUNDS_CONSTRAINTS.vertical * currentZoom,
|
|
45
|
+
const minBoardY = BOUNDS_CONSTRAINTS.vertical;
|
|
46
|
+
const maxBoardY = Math.max(
|
|
47
|
+
minBoardY,
|
|
48
|
+
container.clientHeight / currentZoom -
|
|
49
|
+
node.offsetHeight -
|
|
50
|
+
BOUNDS_CONSTRAINTS.vertical,
|
|
68
51
|
);
|
|
69
52
|
// #endregion
|
|
70
53
|
|
|
54
|
+
// #region Convert board boundaries to transformer delta space (in screen pixels)
|
|
55
|
+
const minTransformX = (minBoardX - node.offsetLeft) * currentZoom;
|
|
56
|
+
const maxTransformX = (maxBoardX - node.offsetLeft) * currentZoom;
|
|
57
|
+
|
|
58
|
+
const minTransformY = (minBoardY - node.offsetTop) * currentZoom;
|
|
59
|
+
const maxTransformY = (maxBoardY - node.offsetTop) * currentZoom;
|
|
60
|
+
// #endregion
|
|
61
|
+
|
|
71
62
|
return {
|
|
72
|
-
x: Math.min(Math.max(transform.x,
|
|
73
|
-
y: Math.min(Math.max(transform.y,
|
|
63
|
+
x: Math.min(Math.max(transform.x, minTransformX), maxTransformX),
|
|
64
|
+
y: Math.min(Math.max(transform.y, minTransformY), maxTransformY),
|
|
74
65
|
};
|
|
75
66
|
},
|
|
76
67
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from '@bemedev/app-solidjs';
|
|
2
2
|
import { Component, createEffect, createSignal, Show } from 'solid-js';
|
|
3
|
+
|
|
3
4
|
import type { Vector } from '../../services/main.typings';
|
|
4
5
|
import { useFlow } from './FlowChart.context';
|
|
5
6
|
|
|
@@ -21,8 +22,8 @@ type Props = {
|
|
|
21
22
|
const calculateOffset = (value: number) => (value * 100) / 200;
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Generates an SVG cubic bezier path `d` string between coordinates `(x0,
|
|
25
|
-
*
|
|
25
|
+
* Generates an SVG cubic bezier path `d` string between coordinates `(x0, y0)` and
|
|
26
|
+
* `(x1, y1)`.
|
|
26
27
|
*
|
|
27
28
|
* @param vector - Vector coordinates of type {@linkcode Vector}.
|
|
28
29
|
*
|
|
@@ -33,8 +34,8 @@ const draw = ({ x0, y0, x1, y1 }: Vector) => {
|
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
|
-
* SVG edge component rendering bezier curves and delete interaction
|
|
37
|
-
*
|
|
37
|
+
* SVG edge component rendering bezier curves and delete interaction handles for node
|
|
38
|
+
* connections.
|
|
38
39
|
*
|
|
39
40
|
* @param props - Edge properties of type {@linkcode Props}.
|
|
40
41
|
*
|
|
@@ -64,10 +65,8 @@ export const EdgeComponent: Component<Props> = props => {
|
|
|
64
65
|
class='relative cursor-pointer fill-transparent'
|
|
65
66
|
classList={{
|
|
66
67
|
'stroke-[rgba(168,168,168,0.4)] stroke-3 z-200': !!props.isNew,
|
|
67
|
-
'stroke-[rgba(168,168,168,1)] stroke-4 z-100':
|
|
68
|
-
|
|
69
|
-
'stroke-[rgba(168,168,168,0.8)] stroke-3':
|
|
70
|
-
!selected() && !props.isNew,
|
|
68
|
+
'stroke-[rgba(168,168,168,1)] stroke-4 z-100': selected() && !props.isNew,
|
|
69
|
+
'stroke-[rgba(168,168,168,0.8)] stroke-3': !selected() && !props.isNew,
|
|
71
70
|
}}
|
|
72
71
|
style={{ 'pointer-events': props.isNew ? 'none' : 'all' }}
|
|
73
72
|
d={draw(props)}
|
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
For,
|
|
7
7
|
Show,
|
|
8
8
|
} from 'solid-js';
|
|
9
|
+
|
|
9
10
|
import { EdgeComponent } from './EdgeComponent';
|
|
10
11
|
import { useFlow } from './FlowChart.context';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* SVG board overlay component that renders all active connecting edges and
|
|
14
|
-
*
|
|
14
|
+
* SVG board overlay component that renders all active connecting edges and ongoing
|
|
15
|
+
* edge creation previews.
|
|
15
16
|
*
|
|
16
17
|
* @returns The rendered SVG JSX element.
|
|
17
18
|
*/
|
|
@@ -2,6 +2,7 @@ import { interpret } from '@bemedev/app';
|
|
|
2
2
|
import { dequal } from 'dequal/lite';
|
|
3
3
|
import { createSignal } from 'solid-js';
|
|
4
4
|
import { produce } from 'solid-js/store';
|
|
5
|
+
|
|
5
6
|
import { createContext } from '../../helpers/createContext';
|
|
6
7
|
import { machine } from '../../services/main.machine';
|
|
7
8
|
import type { Point, Vector } from '../../services/main.typings';
|
|
@@ -29,57 +30,70 @@ type Dimensions = {
|
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
/** Connection edge coordinate representation between two endpoints. */
|
|
32
|
-
|
|
33
|
+
type Edge = {
|
|
33
34
|
/** Source node identifier. */
|
|
34
35
|
from: string;
|
|
35
|
-
|
|
36
|
-
x0: number;
|
|
37
|
-
/** Starting Y-coordinate. */
|
|
38
|
-
y0: number;
|
|
39
|
-
/** Ending X-coordinate. */
|
|
40
|
-
x1: number;
|
|
41
|
-
/** Ending Y-coordinate. */
|
|
42
|
-
y1: number;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Shared state machine interpreter service instance for flowchart state
|
|
47
|
-
* management.
|
|
48
|
-
*/
|
|
49
|
-
const service = interpret(machine, {
|
|
50
|
-
context: {},
|
|
51
|
-
pContext: { generatedId: null },
|
|
52
|
-
});
|
|
36
|
+
} & Vector;
|
|
53
37
|
|
|
54
38
|
/**
|
|
55
|
-
* Solid Context Provider component and hook for accessing flowchart board
|
|
56
|
-
*
|
|
39
|
+
* Solid Context Provider component and hook for accessing flowchart board state,
|
|
40
|
+
* services, and zoom.
|
|
57
41
|
*/
|
|
58
42
|
export const [Provider, useFlow] = createContext(
|
|
59
43
|
() => {
|
|
44
|
+
/**
|
|
45
|
+
* Shared state machine interpreter service instance for flowchart state
|
|
46
|
+
* management.
|
|
47
|
+
*/
|
|
48
|
+
const service = interpret(machine, {
|
|
49
|
+
context: {},
|
|
50
|
+
pContext: { generatedId: null },
|
|
51
|
+
});
|
|
52
|
+
|
|
60
53
|
const zoom = createSignal(1);
|
|
61
54
|
const newEdge = createSignal<Edge>();
|
|
62
55
|
const [boardRef, setBoardRef] = createSignal<HTMLDivElement>();
|
|
63
56
|
|
|
64
|
-
const [dimensions, setDimensions] = createSignal<
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
const [dimensions, setDimensions] = createSignal<Record<string, Dimensions>>(
|
|
58
|
+
{},
|
|
59
|
+
{ equals: dequal },
|
|
60
|
+
);
|
|
67
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
|
+
*/
|
|
68
71
|
const getBoardPoint = (clientX: number, clientY: number): Point => {
|
|
69
72
|
const el = boardRef();
|
|
70
73
|
if (!el) return { x: clientX, y: clientY };
|
|
74
|
+
|
|
71
75
|
const rect = el.getBoundingClientRect();
|
|
72
76
|
const currentZoom = zoom[0]();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
77
|
+
const x = clientX - rect.left / currentZoom;
|
|
78
|
+
const y = clientY - rect.top / currentZoom;
|
|
79
|
+
return { x, y };
|
|
77
80
|
};
|
|
78
81
|
|
|
79
|
-
const [edgesPositions, setEdgesPositions] = createSignal<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const [edgesPositions, setEdgesPositions] = createSignal<Record<string, Vector>>(
|
|
83
|
+
{},
|
|
84
|
+
{ equals: false },
|
|
85
|
+
);
|
|
82
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
|
+
*/
|
|
83
97
|
const clampPosition = (
|
|
84
98
|
x: number,
|
|
85
99
|
y: number,
|
|
@@ -88,10 +102,12 @@ export const [Provider, useFlow] = createContext(
|
|
|
88
102
|
): Point => {
|
|
89
103
|
const container = boardRef()?.parentElement;
|
|
90
104
|
if (!container) return { x, y };
|
|
105
|
+
|
|
91
106
|
const currentZoom = zoom[0]();
|
|
92
107
|
|
|
93
108
|
const minX =
|
|
94
109
|
container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
|
|
110
|
+
|
|
95
111
|
const maxX = Math.max(
|
|
96
112
|
minX,
|
|
97
113
|
(container.scrollLeft + container.clientWidth) / currentZoom -
|
|
@@ -99,8 +115,8 @@ export const [Provider, useFlow] = createContext(
|
|
|
99
115
|
nodeWidth,
|
|
100
116
|
);
|
|
101
117
|
|
|
102
|
-
const minY =
|
|
103
|
-
|
|
118
|
+
const minY = container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
|
|
119
|
+
|
|
104
120
|
const maxY = Math.max(
|
|
105
121
|
minY,
|
|
106
122
|
(container.scrollTop + container.clientHeight) / currentZoom -
|
|
@@ -117,11 +133,7 @@ export const [Provider, useFlow] = createContext(
|
|
|
117
133
|
service.addOptions(({ voidAction, batch, assign }) => ({
|
|
118
134
|
actions: {
|
|
119
135
|
placeChild: assign('context.data.nodes', {
|
|
120
|
-
ADD_CHILD: ({
|
|
121
|
-
payload,
|
|
122
|
-
context: { data },
|
|
123
|
-
pContext: { generatedId },
|
|
124
|
-
}) => {
|
|
136
|
+
ADD_CHILD: ({ payload, context: { data }, pContext: { generatedId } }) => {
|
|
125
137
|
const nodes = data?.nodes ?? [];
|
|
126
138
|
if (!payload) return nodes;
|
|
127
139
|
|
|
@@ -131,33 +143,19 @@ export const [Provider, useFlow] = createContext(
|
|
|
131
143
|
const id = `node-${generatedId}`;
|
|
132
144
|
const width = dimensions()[payload]?.width ?? 0;
|
|
133
145
|
const height = dimensions()[payload]?.height ?? 0;
|
|
134
|
-
const initialX =
|
|
135
|
-
parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
146
|
+
const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
136
147
|
const initialY = parentNode.position.y;
|
|
137
|
-
const position = clampPosition(
|
|
138
|
-
initialX,
|
|
139
|
-
initialY,
|
|
140
|
-
width,
|
|
141
|
-
height,
|
|
142
|
-
);
|
|
148
|
+
const position = clampPosition(initialX, initialY, width, height);
|
|
143
149
|
|
|
144
150
|
return [
|
|
145
151
|
...nodes,
|
|
146
|
-
{
|
|
147
|
-
id,
|
|
148
|
-
data: { content: '<Nouveau nœud>' },
|
|
149
|
-
input: true,
|
|
150
|
-
position,
|
|
151
|
-
},
|
|
152
|
+
{ id, data: { content: '<Nouveau nœud>' }, input: true, position },
|
|
152
153
|
];
|
|
153
154
|
},
|
|
154
155
|
}),
|
|
155
156
|
|
|
156
157
|
placeParent: assign('context.data.nodes', {
|
|
157
|
-
ADD_PARENT: ({
|
|
158
|
-
context: { data },
|
|
159
|
-
pContext: { generatedId },
|
|
160
|
-
}) => {
|
|
158
|
+
ADD_PARENT: ({ context: { data }, pContext: { generatedId } }) => {
|
|
161
159
|
const nodes = data?.nodes ?? [];
|
|
162
160
|
const id = `node-${generatedId}`;
|
|
163
161
|
const container = boardRef()?.parentElement;
|
|
@@ -189,7 +187,6 @@ export const [Provider, useFlow] = createContext(
|
|
|
189
187
|
}) => {
|
|
190
188
|
const edges = data?.edges ?? [];
|
|
191
189
|
const nodes = data?.nodes ?? [];
|
|
192
|
-
|
|
193
190
|
const parentID = edges.find(edge => edge.to === payload)?.from;
|
|
194
191
|
if (!parentID) return nodes;
|
|
195
192
|
|
|
@@ -199,24 +196,13 @@ export const [Provider, useFlow] = createContext(
|
|
|
199
196
|
const id = `node-${generatedId}`;
|
|
200
197
|
const width = dimensions()[parentID]?.width ?? 0;
|
|
201
198
|
const height = dimensions()[parentID]?.height ?? 0;
|
|
202
|
-
const initialX =
|
|
203
|
-
parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
199
|
+
const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
204
200
|
const initialY = parentNode.position.y + 100;
|
|
205
|
-
const position = clampPosition(
|
|
206
|
-
initialX,
|
|
207
|
-
initialY,
|
|
208
|
-
width,
|
|
209
|
-
height,
|
|
210
|
-
);
|
|
201
|
+
const position = clampPosition(initialX, initialY, width, height);
|
|
211
202
|
|
|
212
203
|
return [
|
|
213
204
|
...nodes,
|
|
214
|
-
{
|
|
215
|
-
id,
|
|
216
|
-
data: { content: '<Nouveau nœud>' },
|
|
217
|
-
input: true,
|
|
218
|
-
position,
|
|
219
|
-
},
|
|
205
|
+
{ id, data: { content: '<Nouveau nœud>' }, input: true, position },
|
|
220
206
|
];
|
|
221
207
|
},
|
|
222
208
|
}),
|
|
@@ -225,9 +211,9 @@ export const [Provider, useFlow] = createContext(
|
|
|
225
211
|
voidAction(({ context: { data } }) => {
|
|
226
212
|
const edges = data?.edges ?? [];
|
|
227
213
|
setEdgesPositions(data => {
|
|
228
|
-
const array = Object.entries({ ...data }).filter(([id]) =>
|
|
229
|
-
|
|
230
|
-
|
|
214
|
+
const array = Object.entries({ ...data }).filter(([id]) =>
|
|
215
|
+
edges.some(edge => edge.id === id),
|
|
216
|
+
);
|
|
231
217
|
|
|
232
218
|
return Object.fromEntries(array);
|
|
233
219
|
});
|
|
@@ -240,6 +226,7 @@ export const [Provider, useFlow] = createContext(
|
|
|
240
226
|
edges?.forEach(({ from, id, to }) => {
|
|
241
227
|
const output = dimensions()[from]?.output;
|
|
242
228
|
const input = dimensions()[to]?.input;
|
|
229
|
+
|
|
243
230
|
if (output && input) {
|
|
244
231
|
next[id] = {
|
|
245
232
|
x0: output.x,
|
|
@@ -261,12 +248,12 @@ export const [Provider, useFlow] = createContext(
|
|
|
261
248
|
if (from === payload.id) {
|
|
262
249
|
const offset =
|
|
263
250
|
dimensions()[payload.id]?.outputOffset ??
|
|
264
|
-
getDefaultOutputOffset(
|
|
265
|
-
|
|
266
|
-
);
|
|
251
|
+
getDefaultOutputOffset(dimensions()[payload.id]?.width);
|
|
252
|
+
|
|
267
253
|
const x0 = payload.x + offset.x;
|
|
268
254
|
const y0 = payload.y + offset.y;
|
|
269
255
|
next[id] = { ...next[id], x0, y0 };
|
|
256
|
+
|
|
270
257
|
setDimensions(
|
|
271
258
|
produce(data => {
|
|
272
259
|
if (data[payload.id]) {
|
|
@@ -282,9 +269,11 @@ export const [Provider, useFlow] = createContext(
|
|
|
282
269
|
const offset =
|
|
283
270
|
dimensions()[payload.id]?.inputOffset ??
|
|
284
271
|
DEFAULT_INPUT_OFFSET;
|
|
272
|
+
|
|
285
273
|
const x1 = payload.x + offset.x;
|
|
286
274
|
const y1 = payload.y + offset.y;
|
|
287
275
|
next[id] = { ...next[id], x1, y1 };
|
|
276
|
+
|
|
288
277
|
setDimensions(
|
|
289
278
|
produce(data => {
|
|
290
279
|
if (data[payload.id]) {
|
|
@@ -301,6 +290,7 @@ export const [Provider, useFlow] = createContext(
|
|
|
301
290
|
);
|
|
302
291
|
},
|
|
303
292
|
}),
|
|
293
|
+
|
|
304
294
|
assign('context.updatingUI', () => true),
|
|
305
295
|
),
|
|
306
296
|
|
|
@@ -314,12 +304,12 @@ export const [Provider, useFlow] = createContext(
|
|
|
314
304
|
if (from === payload.id) {
|
|
315
305
|
const offset =
|
|
316
306
|
dimensions()[payload.id]?.outputOffset ??
|
|
317
|
-
getDefaultOutputOffset(
|
|
318
|
-
|
|
319
|
-
);
|
|
307
|
+
getDefaultOutputOffset(dimensions()[payload.id]?.width);
|
|
308
|
+
|
|
320
309
|
const x0 = payload.x + offset.x;
|
|
321
310
|
const y0 = payload.y + offset.y;
|
|
322
311
|
next[id] = { ...next[id], x0, y0 };
|
|
312
|
+
|
|
323
313
|
setDimensions(
|
|
324
314
|
produce(data => {
|
|
325
315
|
if (data[payload.id]) {
|
|
@@ -331,13 +321,15 @@ export const [Provider, useFlow] = createContext(
|
|
|
331
321
|
}),
|
|
332
322
|
);
|
|
333
323
|
}
|
|
324
|
+
|
|
334
325
|
if (to === payload.id) {
|
|
335
326
|
const offset =
|
|
336
|
-
dimensions()[payload.id]?.inputOffset ??
|
|
337
|
-
|
|
327
|
+
dimensions()[payload.id]?.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
328
|
+
|
|
338
329
|
const x1 = payload.x + offset.x;
|
|
339
330
|
const y1 = payload.y + offset.y;
|
|
340
331
|
next[id] = { ...next[id], x1, y1 };
|
|
332
|
+
|
|
341
333
|
setDimensions(
|
|
342
334
|
produce(data => {
|
|
343
335
|
if (data[payload.id]) {
|
|
@@ -367,6 +359,7 @@ export const [Provider, useFlow] = createContext(
|
|
|
367
359
|
edgesPositions: [edgesPositions, setEdgesPositions] as const,
|
|
368
360
|
service,
|
|
369
361
|
zoom,
|
|
362
|
+
clampPosition,
|
|
370
363
|
};
|
|
371
364
|
},
|
|
372
365
|
{ name: 'FlowContext' },
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { NodeProps } from './FlowChart';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Horizontal gap in pixels between a parent node and newly created child
|
|
5
|
-
* node.
|
|
6
|
-
*/
|
|
3
|
+
/** Horizontal gap in pixels between a parent node and newly created child node. */
|
|
7
4
|
export const PARENT_CHILD_GAP_WIDTH = 100;
|
|
8
5
|
|
|
9
6
|
// #region Node & Handle Layout Constants & Formulas
|
|
@@ -41,10 +38,7 @@ export const DEFAULT_INPUT_OFFSET = {
|
|
|
41
38
|
y: DEFAULT_INPUT_OFFSET_Y,
|
|
42
39
|
};
|
|
43
40
|
|
|
44
|
-
/**
|
|
45
|
-
* Dimensions representing width and height of the flow chart container
|
|
46
|
-
* canvas.
|
|
47
|
-
*/
|
|
41
|
+
/** Dimensions representing width and height of the flow chart container canvas. */
|
|
48
42
|
export const CONTAINER_DIMENSIONS = { WIDTH: 5000, HEIGHT: 3500 };
|
|
49
43
|
|
|
50
44
|
/**
|
|
@@ -66,19 +60,22 @@ export const TOOLBAR_TOP_OFFSET = 30;
|
|
|
66
60
|
export const TOOLBAR_BUFFER = 5;
|
|
67
61
|
|
|
68
62
|
/**
|
|
69
|
-
* Boundary padding constraints for node dragging and positioning within
|
|
70
|
-
*
|
|
63
|
+
* Boundary padding constraints for node dragging and positioning within the
|
|
64
|
+
* viewport.
|
|
71
65
|
*/
|
|
72
|
-
export const BOUNDS_CONSTRAINTS = {
|
|
73
|
-
horizontal: HANDLE_CONTAINER_OFFSET_X + 2,
|
|
74
|
-
vertical: TOOLBAR_TOP_OFFSET + TOOLBAR_BUFFER,
|
|
75
|
-
};
|
|
66
|
+
export const BOUNDS_CONSTRAINTS = { horizontal: 55, vertical: 40 };
|
|
76
67
|
|
|
77
68
|
/** Multiplier factor used to compute canvas virtual scroll dimensions. */
|
|
78
69
|
export const CANVAS_FACTOR = 5;
|
|
70
|
+
|
|
71
|
+
/** Multiplier factor used to compute canvas scroll dimensions. */
|
|
72
|
+
export const SCROLL_MULTIPLIER = 3;
|
|
79
73
|
// #endregion
|
|
80
74
|
|
|
81
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Default node items of type {@linkcode NodeProps} provided when no initial
|
|
77
|
+
* configuration is given.
|
|
78
|
+
*/
|
|
82
79
|
export const DEFAULT_NODES: (NodeProps & { id: string })[] = [
|
|
83
80
|
{
|
|
84
81
|
id: 'node-0',
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import type { inferT } from '@bemedev/app/typings';
|
|
2
2
|
import { Component, onCleanup, onMount } from 'solid-js';
|
|
3
|
+
|
|
3
4
|
import type { edgeJSON, nodeJSON } from '../../services/main.typings';
|
|
4
5
|
import { useFlow } from './FlowChart.context';
|
|
5
|
-
import { NodesBoard } from './NodesBoard';
|
|
6
6
|
import { DEFAULT_NODES } from './FlowChart.data';
|
|
7
|
+
import { NodesBoard } from './NodesBoard';
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
* Serialized node properties type inferred from schema
|
|
10
|
-
* {@linkcode nodeJSON}.
|
|
11
|
-
*/
|
|
9
|
+
/** Serialized node properties type inferred from schema {@linkcode nodeJSON}. */
|
|
12
10
|
export type NodeProps = inferT<typeof nodeJSON>;
|
|
13
11
|
|
|
14
|
-
/**
|
|
15
|
-
* Serialized edge properties type inferred from schema
|
|
16
|
-
* {@linkcode edgeJSON}.
|
|
17
|
-
*/
|
|
12
|
+
/** Serialized edge properties type inferred from schema {@linkcode edgeJSON}. */
|
|
18
13
|
export type EdgeProps = inferT<typeof edgeJSON>;
|
|
19
14
|
|
|
20
15
|
/**
|
|
@@ -56,8 +51,8 @@ export type FlowProps = {
|
|
|
56
51
|
// const PARENT_CHILD_GAP_WIDTH = 75;
|
|
57
52
|
|
|
58
53
|
/**
|
|
59
|
-
* Flowchart board canvas component that renders interactive nodes, edges,
|
|
60
|
-
*
|
|
54
|
+
* Flowchart board canvas component that renders interactive nodes, edges, pan/zoom,
|
|
55
|
+
* and toolbar controls.
|
|
61
56
|
*
|
|
62
57
|
* @param props - Flowchart configuration and event handlers of type
|
|
63
58
|
* {@linkcode FlowProps}.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-namespace */
|
|
2
2
|
import { useState } from '@bemedev/app-solidjs';
|
|
3
3
|
import { createDraggable } from '@thisbeyond/solid-dnd';
|
|
4
|
-
import { Component,
|
|
4
|
+
import { Component, createSignal, onMount, Show } from 'solid-js';
|
|
5
5
|
import { produce } from 'solid-js/store';
|
|
6
|
+
|
|
6
7
|
import { useFlow } from './FlowChart.context';
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_INPUT_OFFSET_X,
|
|
@@ -37,8 +38,8 @@ type Props = {
|
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* Interactive flowchart node component supporting dragging, selection,
|
|
41
|
-
*
|
|
41
|
+
* Interactive flowchart node component supporting dragging, selection, handle
|
|
42
|
+
* connections, and child/sibling creation.
|
|
42
43
|
*
|
|
43
44
|
* @param props - Node rendering properties of type {@linkcode Props}.
|
|
44
45
|
*
|
|
@@ -60,7 +61,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
60
61
|
selector: s => s.context.selected === props.id,
|
|
61
62
|
});
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
onMount(() => {
|
|
64
65
|
const _inputRef = inputRef;
|
|
65
66
|
const _outputRef = outputRef;
|
|
66
67
|
const _rootRef = ref();
|
|
@@ -73,15 +74,12 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
73
74
|
const inputRect = _inputRef?.getBoundingClientRect();
|
|
74
75
|
|
|
75
76
|
const outputOffsetX =
|
|
76
|
-
(outputRect.left - rootRect.left + outputRect.width / 2) /
|
|
77
|
-
currentZoom;
|
|
77
|
+
(outputRect.left - rootRect.left + outputRect.width / 2) / currentZoom;
|
|
78
78
|
const outputOffsetY =
|
|
79
|
-
(outputRect.top - rootRect.top + outputRect.height / 2) /
|
|
80
|
-
currentZoom;
|
|
79
|
+
(outputRect.top - rootRect.top + outputRect.height / 2) / currentZoom;
|
|
81
80
|
|
|
82
81
|
const inputOffsetX = inputRect
|
|
83
|
-
? (inputRect.left - rootRect.left + inputRect.width / 2) /
|
|
84
|
-
currentZoom
|
|
82
|
+
? (inputRect.left - rootRect.left + inputRect.width / 2) / currentZoom
|
|
85
83
|
: DEFAULT_INPUT_OFFSET_X;
|
|
86
84
|
const inputOffsetY = inputRect
|
|
87
85
|
? (inputRect.top - rootRect.top + inputRect.height / 2) / currentZoom
|
|
@@ -90,10 +88,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
90
88
|
const width = rootRect.width / currentZoom;
|
|
91
89
|
const height = rootRect.height / currentZoom;
|
|
92
90
|
|
|
93
|
-
const output = {
|
|
94
|
-
x: props.x + outputOffsetX,
|
|
95
|
-
y: props.y + outputOffsetY,
|
|
96
|
-
};
|
|
91
|
+
const output = { x: props.x + outputOffsetX, y: props.y + outputOffsetY };
|
|
97
92
|
|
|
98
93
|
const input = { x: props.x + inputOffsetX, y: props.y + inputOffsetY };
|
|
99
94
|
|
|
@@ -115,7 +110,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
115
110
|
selector: ({ context: { data } }) => {
|
|
116
111
|
const edges = data?.edges;
|
|
117
112
|
if (!edges) return false;
|
|
118
|
-
return
|
|
113
|
+
return edges.some(({ to }) => to === props.id);
|
|
119
114
|
},
|
|
120
115
|
});
|
|
121
116
|
|
|
@@ -182,9 +177,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
182
177
|
preserveAspectRatio='xMaxYMax'
|
|
183
178
|
xmlns='http://www.w3.org/2000/svg'
|
|
184
179
|
fill='white'
|
|
185
|
-
onClick={() =>
|
|
186
|
-
service.send({ type: 'ADD_SIBLING', payload: props.id })
|
|
187
|
-
}
|
|
180
|
+
onClick={() => service.send({ type: 'ADD_SIBLING', payload: props.id })}
|
|
188
181
|
>
|
|
189
182
|
<g id='background'>
|
|
190
183
|
<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' />
|
|
@@ -194,9 +187,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
194
187
|
</Show>
|
|
195
188
|
<svg
|
|
196
189
|
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'
|
|
197
|
-
onClick={() =>
|
|
198
|
-
service.send({ type: 'ADD_CHILD', payload: props.id })
|
|
199
|
-
}
|
|
190
|
+
onClick={() => service.send({ type: 'ADD_CHILD', payload: props.id })}
|
|
200
191
|
style={{
|
|
201
192
|
'pointer-events': 'all',
|
|
202
193
|
width: `${HANDLE_SIZE * 2}px`,
|
|
@@ -242,10 +233,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
242
233
|
const from = newEdge()?.from;
|
|
243
234
|
|
|
244
235
|
if (from) {
|
|
245
|
-
service.send({
|
|
246
|
-
type: 'ADD_EDGE',
|
|
247
|
-
payload: { from, to: props.id },
|
|
248
|
-
});
|
|
236
|
+
service.send({ type: 'ADD_EDGE', payload: { from, to: props.id } });
|
|
249
237
|
}
|
|
250
238
|
|
|
251
239
|
setNewEdge();
|