sample-ui-component-library 0.0.40-dev → 0.0.43-dev
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/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.js.map +1 -1
- package/package.json +4 -2
- package/src/components/BehavioralGraphBuilder/BehavioralGraphBuilder.jsx +101 -26
- package/src/components/BehavioralGraphBuilder/BehavioralGraphBuilder.scss +12 -0
- package/src/components/BehavioralGraphBuilder/helper.js +18 -14
- package/src/stories/{BehavioralGraphBuilderStories.scss → BehavioralGraphBuilder.scss} +4 -1
- package/src/stories/BehavioralGraphBuilder.stories.js +86 -37
- package/src/stories/components/ToolBar/ToolBar.js +9 -25
- package/src/stories/data/Designs/simple_designs_temp.json +1 -0
- package/src/components/BehavioralGraphBuilder/DagreLayout.js +0 -33
- package/src/components/BehavioralGraphBuilder/Flow.jsx +0 -164
- package/src/stories/data/Designs/simple_design_temp.json +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sample-ui-component-library",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.43-dev",
|
|
4
4
|
"description": "A library which contains sample UI elements that can be used for populating layouts.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"@dagrejs/dagre": "^1.1.4",
|
|
69
69
|
"@monaco-editor/react": "^4.7.0",
|
|
70
70
|
"@xyflow/react": "^12.6.0",
|
|
71
|
-
"
|
|
71
|
+
"dal-engine-core-js-lib-dev": "^0.0.4",
|
|
72
|
+
"react-bootstrap-icons": "^1.11.5",
|
|
73
|
+
"reaflow": "^5.4.1"
|
|
72
74
|
}
|
|
73
75
|
}
|
|
@@ -1,32 +1,107 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { designToReactFlowElements } from "./helper";
|
|
5
|
-
|
|
6
|
-
export const BehavioralGraphBuilder = ({
|
|
7
|
-
activeTool,
|
|
8
|
-
onBehaviorSelect,
|
|
9
|
-
onAddBehavior,
|
|
10
|
-
onAddEdge,
|
|
11
|
-
design,
|
|
12
|
-
}) => {
|
|
13
|
-
const [initialElements, setInitialElements] = useState(null);
|
|
1
|
+
import { useEffect, useState, useRef, useImperativeHandle, forwardRef, useMemo, useCallback } from "react";
|
|
2
|
+
import { Canvas, Node, Edge, removeAndUpsertNodes } from 'reaflow';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
14
4
|
|
|
5
|
+
import { designToNodes } from "./helper";
|
|
6
|
+
|
|
7
|
+
import "./BehavioralGraphBuilder.scss";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders a behavioral graph editor component using reaflow.
|
|
11
|
+
*
|
|
12
|
+
* @return {JSX}
|
|
13
|
+
*/
|
|
14
|
+
export const BehavioralGraphBuilder = forwardRef(({connectBehaviors, deleteTransition, deleteBehavior}, ref) => {
|
|
15
|
+
const canvasRef = useRef();
|
|
16
|
+
|
|
17
|
+
// Resizer to set canvas size to match container
|
|
18
|
+
// TODO: Issues with zooming and resizing, need to explore.
|
|
19
|
+
const containerRef = useRef();
|
|
20
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
15
21
|
useEffect(() => {
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
23
|
+
const { width, height } = entry.contentRect;
|
|
24
|
+
setSize({ width, height });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (containerRef.current) observer.observe(containerRef.current);
|
|
28
|
+
return () => observer.disconnect();
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const [edges, setEdges] = useState([]);
|
|
33
|
+
const [nodes, setNodes] = useState([]);
|
|
34
|
+
const [selections, setSelections] = useState([]);
|
|
35
|
+
'1'
|
|
36
|
+
const onNodeLink =(_event, from, to) => {
|
|
37
|
+
connectBehaviors(from, to);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const updateEngine = useCallback((engine) => {
|
|
41
|
+
if (engine) {
|
|
42
|
+
const { nodes, edges } = designToNodes(engine);
|
|
43
|
+
setNodes(nodes);
|
|
44
|
+
setEdges(edges);
|
|
18
45
|
}
|
|
19
|
-
}, [
|
|
46
|
+
}, [setNodes, setEdges]);
|
|
47
|
+
|
|
48
|
+
const api = useMemo(() => {
|
|
49
|
+
return {
|
|
50
|
+
updateEngine
|
|
51
|
+
};
|
|
52
|
+
}, [updateEngine]);
|
|
53
|
+
|
|
54
|
+
useImperativeHandle(ref, () => api, [api]);
|
|
55
|
+
|
|
56
|
+
const handleWheel = useCallback((e) => {
|
|
57
|
+
if (e.deltaY < 0) {
|
|
58
|
+
canvasRef.current.zoomIn()
|
|
59
|
+
} else {
|
|
60
|
+
canvasRef.current.zoomOut();
|
|
61
|
+
}
|
|
62
|
+
}, [canvasRef]);
|
|
20
63
|
|
|
21
64
|
return (
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
65
|
+
<div onWheel={handleWheel} ref={containerRef} className="canvas-wrapper">
|
|
66
|
+
<Canvas
|
|
67
|
+
ref={canvasRef}
|
|
68
|
+
nodes={nodes}
|
|
69
|
+
edges={edges}
|
|
70
|
+
width={size.width}
|
|
71
|
+
height={size.height}
|
|
72
|
+
onNodeLink={onNodeLink}
|
|
73
|
+
panType="drag"
|
|
74
|
+
selections={selections}node={
|
|
75
|
+
<Node
|
|
76
|
+
onClick={(event, node) => {
|
|
77
|
+
setSelections([node.id]);
|
|
78
|
+
}}
|
|
79
|
+
onRemove={(event, node) => {
|
|
80
|
+
deleteBehavior(node);
|
|
81
|
+
setSelections([]);
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
}
|
|
85
|
+
edge={
|
|
86
|
+
<Edge
|
|
87
|
+
onClick={(event, edge) => {
|
|
88
|
+
setSelections([edge.id]);
|
|
89
|
+
}}
|
|
90
|
+
onRemove={(event, edge) => {
|
|
91
|
+
deleteTransition(edge);
|
|
92
|
+
setSelections([]);
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
}
|
|
96
|
+
onCanvasClick={(event) => {setSelections([]);}}
|
|
97
|
+
fit
|
|
98
|
+
center
|
|
29
99
|
/>
|
|
30
|
-
</
|
|
31
|
-
)
|
|
32
|
-
};
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
BehavioralGraphBuilder.propTypes = {
|
|
105
|
+
connectBehaviors: PropTypes.func.isRequired,
|
|
106
|
+
deleteBehavior: PropTypes.func.isRequired
|
|
107
|
+
}
|
|
@@ -4,32 +4,36 @@
|
|
|
4
4
|
* @param {Object} design
|
|
5
5
|
* @returns {Object} An object containing nodes and edges for React Flow
|
|
6
6
|
*/
|
|
7
|
-
export const
|
|
7
|
+
export const designToNodes = (engine) => {
|
|
8
8
|
|
|
9
9
|
let edges = [];
|
|
10
10
|
let nodes = [];
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < engine.graph.nodes.length; i++) {
|
|
13
|
+
const node = engine.graph.nodes[i];
|
|
12
14
|
nodes.push({
|
|
13
|
-
id: node.
|
|
14
|
-
|
|
15
|
-
data: { label: node.behavior.name },
|
|
16
|
-
position: { x: 10, y: 10 },
|
|
15
|
+
id: node.getBehavior().name,
|
|
16
|
+
text: node.getBehavior().name,
|
|
17
17
|
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < engine.graph.nodes.length; i++) {
|
|
21
|
+
const node = engine.graph.nodes[i];
|
|
22
|
+
if (!node?.getGoToBehaviors()) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
18
25
|
|
|
19
|
-
node.
|
|
26
|
+
node.getGoToBehaviors().forEach((goTo) => {
|
|
20
27
|
edges.push({
|
|
21
|
-
id: `${node.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
target: goTo.uid,
|
|
25
|
-
animated: true
|
|
28
|
+
id: `${node.getBehavior().name}->${goTo}`,
|
|
29
|
+
from: node.getBehavior().name,
|
|
30
|
+
to: goTo,
|
|
26
31
|
});
|
|
27
32
|
});
|
|
28
|
-
}
|
|
33
|
+
}
|
|
29
34
|
|
|
30
35
|
return {
|
|
31
36
|
nodes: nodes,
|
|
32
37
|
edges: edges
|
|
33
38
|
};
|
|
34
|
-
|
|
35
39
|
}
|
|
@@ -1,66 +1,115 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BehavioralGraphBuilder } from "../components/BehavioralGraphBuilder";
|
|
3
|
-
import { ToolBar } from "./components/ToolBar/ToolBar";
|
|
1
|
+
import { useEffect, useState, useCallback, useRef } from "react";
|
|
4
2
|
import { useArgs } from "@storybook/preview-api";
|
|
5
3
|
import { action } from "@storybook/addon-actions";
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
import "./
|
|
5
|
+
import { BehavioralGraphBuilder } from "../components/BehavioralGraphBuilder/BehavioralGraphBuilder";
|
|
6
|
+
import { ToolBar } from "./components/ToolBar/ToolBar";
|
|
7
|
+
|
|
8
|
+
import { DALEngine } from "dal-engine-core-js-lib-dev";
|
|
9
|
+
|
|
10
|
+
import design from "./data/Designs/simple_designs_temp.json";
|
|
11
|
+
|
|
12
|
+
import "./BehavioralGraphBuilder.scss";
|
|
9
13
|
|
|
10
14
|
export default {
|
|
11
|
-
title:
|
|
15
|
+
title: "BehavioralGraphBuilder",
|
|
12
16
|
component: BehavioralGraphBuilder,
|
|
13
|
-
argTypes: {
|
|
14
|
-
}
|
|
17
|
+
argTypes: {},
|
|
15
18
|
};
|
|
16
19
|
|
|
20
|
+
let count = 0;
|
|
21
|
+
|
|
17
22
|
const Template = (args) => {
|
|
18
23
|
const [, updateArgs] = useArgs();
|
|
19
24
|
|
|
25
|
+
const editorRef = useRef();
|
|
26
|
+
|
|
27
|
+
const [engine, setEngine] = useState();
|
|
28
|
+
|
|
20
29
|
const [activeTool, setActiveTool] = useState("select");
|
|
21
30
|
|
|
22
|
-
const selectTool = useCallback(
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
const selectTool = useCallback(
|
|
32
|
+
(tool) => {
|
|
33
|
+
updateArgs({ activeTool: tool });
|
|
34
|
+
setActiveTool(tool);
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
if (tool === "add-node") {
|
|
37
|
+
engine.addNode("node-" + count++, []);
|
|
38
|
+
editorRef.current.updateEngine(engine);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[activeTool, setActiveTool, engine],
|
|
42
|
+
);
|
|
29
43
|
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
updateArgs({
|
|
46
|
+
activeTool: activeTool,
|
|
47
|
+
design: design,
|
|
48
|
+
});
|
|
32
49
|
}, []);
|
|
33
50
|
|
|
34
|
-
const onAddEdge = useCallback((edge) => {
|
|
35
|
-
action('Added Edge:')(edge);
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
51
|
useEffect(() => {
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
if (editorRef.current) {
|
|
53
|
+
const engine = new DALEngine({ name: "testEngine" });
|
|
54
|
+
// engine.deserialize(JSON.stringify(design));
|
|
55
|
+
setEngine(engine);
|
|
56
|
+
editorRef.current.updateEngine(engine);
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
engine.addNode("testBehavior", []);
|
|
59
|
+
engine.addNode("testBehavior2", []);
|
|
60
|
+
editorRef.current.updateEngine(engine);
|
|
61
|
+
}, 4000);
|
|
62
|
+
}
|
|
63
|
+
}, [design, editorRef]);
|
|
49
64
|
|
|
65
|
+
const connectBehaviors = useCallback(
|
|
66
|
+
(from, to) => {
|
|
67
|
+
if (!to) return;
|
|
68
|
+
action("Connect Behaviors")(from, to);
|
|
69
|
+
engine.getNode(from.id).addGoToBehavior(to.id);
|
|
70
|
+
editorRef.current.updateEngine(engine);
|
|
71
|
+
},
|
|
72
|
+
[editorRef, engine],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const deleteBehavior = useCallback(
|
|
76
|
+
(node) => {
|
|
77
|
+
action("Delete Behavior")(node);
|
|
78
|
+
engine.removeNode(node.id);
|
|
79
|
+
editorRef.current.updateEngine(engine);
|
|
80
|
+
},
|
|
81
|
+
[engine, editorRef],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const deleteTransition = useCallback(
|
|
85
|
+
(edge) => {
|
|
86
|
+
action("Delete Transition")(edge);
|
|
87
|
+
const fromNode = engine.getNode(edge.from);
|
|
88
|
+
fromNode.removeGoToBehavior(edge.to);
|
|
89
|
+
editorRef.current.updateEngine(engine);
|
|
90
|
+
},
|
|
91
|
+
[engine, editorRef],
|
|
92
|
+
);
|
|
50
93
|
return (
|
|
51
94
|
<div className="graphBuilderRootContainer">
|
|
52
95
|
<div className="toolbar">
|
|
53
|
-
<ToolBar onSelectTool={selectTool}/>
|
|
96
|
+
<ToolBar onSelectTool={selectTool} />
|
|
54
97
|
</div>
|
|
55
98
|
<div className="flow">
|
|
56
|
-
<BehavioralGraphBuilder
|
|
99
|
+
<BehavioralGraphBuilder
|
|
100
|
+
ref={editorRef}
|
|
101
|
+
{...args}
|
|
102
|
+
connectBehaviors={connectBehaviors}
|
|
103
|
+
deleteTransition={deleteTransition}
|
|
104
|
+
deleteBehavior={deleteBehavior}
|
|
105
|
+
/>
|
|
57
106
|
</div>
|
|
58
107
|
</div>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
108
|
+
);
|
|
109
|
+
};
|
|
61
110
|
|
|
62
|
-
export const Default = Template.bind({})
|
|
111
|
+
export const Default = Template.bind({});
|
|
63
112
|
|
|
64
113
|
Default.args = {
|
|
65
|
-
design: design
|
|
66
|
-
}
|
|
114
|
+
design: design,
|
|
115
|
+
};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
NodePlus,
|
|
7
|
-
Trash
|
|
4
|
+
PlusSquare,
|
|
5
|
+
Floppy
|
|
8
6
|
} from "react-bootstrap-icons";
|
|
9
7
|
|
|
10
8
|
import "./ToolBar.scss";
|
|
@@ -26,32 +24,18 @@ export function ToolBar({ onSelectTool }) {
|
|
|
26
24
|
return (
|
|
27
25
|
<div className="toolbarWrapper">
|
|
28
26
|
<div className="toolbarContainer">
|
|
29
|
-
<
|
|
30
|
-
onClick={(e) => selectTool("
|
|
31
|
-
style={{color: selectedTool === "select" ? "white": "grey"}}
|
|
32
|
-
title="Select"
|
|
33
|
-
className="icon"
|
|
34
|
-
/>
|
|
35
|
-
<Square
|
|
36
|
-
onClick={(e) => selectTool("drop")}
|
|
37
|
-
style={{color: selectedTool === "drop" ? "white": "grey"}}
|
|
27
|
+
<PlusSquare
|
|
28
|
+
onClick={(e) => selectTool("add-node")}
|
|
38
29
|
title="Add Node"
|
|
39
30
|
className="icon"
|
|
40
31
|
/>
|
|
41
|
-
<NodePlus
|
|
42
|
-
onClick={(e) => selectTool("connect")}
|
|
43
|
-
style={{color: selectedTool === "connect" ? "white": "grey"}}
|
|
44
|
-
title="Connect Node"
|
|
45
|
-
className="icon"
|
|
46
|
-
/>
|
|
47
|
-
<Trash
|
|
48
|
-
onClick={(e) => selectTool("delete")}
|
|
49
|
-
style={{color: selectedTool === "delete" ? "white": "grey"}}
|
|
50
|
-
title="Delete Node"
|
|
51
|
-
className="icon"
|
|
52
|
-
/>
|
|
53
32
|
</div>
|
|
54
33
|
<div className="toolbarContainer bottom"></div>
|
|
34
|
+
<Floppy
|
|
35
|
+
onClick={(e) => selectTool("save")}
|
|
36
|
+
title="Save Design"
|
|
37
|
+
className="icon"
|
|
38
|
+
/>
|
|
55
39
|
</div>
|
|
56
40
|
);
|
|
57
41
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"uid":"3b9b65ab-5e3e-48a7-871c-c76de3e20187","type":5,"nodes":[{"uid":"2038c653-587d-4c33-9103-43b59d85c5be","type":6,"_behavior":{"uid":"19ffb9d9-772b-4aca-89b5-6278e0cbee89","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToAddBookToBasket"},"_goToBehaviorIds":["AcceptBookFromUser"]},{"uid":"98d6a9b8-3867-49c8-a93a-f0e11f70af3b","type":6,"_behavior":{"uid":"d1157a53-5938-42ca-92ad-d2d34d99c704","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptBookFromUser"},"_goToBehaviorIds":["AddBookToBasket"]},{"uid":"2aeb336f-37cc-4f28-8301-f3464c387603","type":6,"_behavior":{"uid":"0dc90e9c-6342-4c3d-90f7-5e5e89b48b76","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToBasket"},"_goToBehaviorIds":[]},{"uid":"763e440b-8c09-4893-8e21-b7af11722d93","type":6,"_behavior":{"uid":"555f261d-00b8-4f2d-ad1d-7d18fb68a035","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToAuditLibrary"},"_goToBehaviorIds":["GenerateAuditReport"]},{"uid":"92ba2c22-3beb-442c-a198-562779d7936c","type":6,"_behavior":{"uid":"d44d5d9c-f0f5-4542-9b7d-21793ea1995c","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GenerateAuditReport"},"_goToBehaviorIds":["HandAuditToUser"]},{"uid":"f2e7e018-2596-4aeb-abeb-55948a17157c","type":6,"_behavior":{"uid":"17ac5068-a521-4fbd-9dcb-783d365e01fd","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"HandAuditToUser"},"_goToBehaviorIds":[]},{"uid":"e89cefaa-cdda-450e-808e-ffee9250db01","type":6,"_behavior":{"uid":"4db0dab8-3d4c-4668-88de-e73f38d8c28e","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToPlaceBooksOnShelf"},"_goToBehaviorIds":[]},{"uid":"4d1de6bd-cd98-48de-81ed-9754b40c3a36","type":6,"_behavior":{"uid":"0ddc2e33-2b01-411b-a3ae-9f0198fafaf8","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetFirstLetterOfBookName"},"_goToBehaviorIds":["CreateSlotOnBookShelf","AddBookToShelf"]},{"uid":"e8e5ffe7-ba16-4c3e-a87f-2083c2502812","type":6,"_behavior":{"uid":"eefbf993-c76b-4c80-bb8d-e3093aaa17cb","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"CreateSlotOnBookShelf"},"_goToBehaviorIds":["AddBookToShelf"]},{"uid":"2fb19dc7-7647-44c1-afd1-4a157ab3d833","type":6,"_behavior":{"uid":"e4092d6e-49bb-4c54-9573-0379466c4ae6","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToShelf"},"_goToBehaviorIds":[]}]}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import Dagre from '@dagrejs/dagre';
|
|
3
|
-
|
|
4
|
-
export const getLayoutedElements = (nodes, edges, options) => {
|
|
5
|
-
// Reference: https://reactflow.dev/learn/layouting/layouting
|
|
6
|
-
|
|
7
|
-
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
|
8
|
-
g.setGraph({ rankdir: options.direction, ranksep: 100, nodesep: 170});
|
|
9
|
-
|
|
10
|
-
edges.forEach((edge) => g.setEdge(edge.source, edge.target));
|
|
11
|
-
nodes.forEach((node) =>
|
|
12
|
-
g.setNode(node.id, {
|
|
13
|
-
...node,
|
|
14
|
-
width: node.measured?.width ?? 0,
|
|
15
|
-
height: node.measured?.height ?? 0,
|
|
16
|
-
}),
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
Dagre.layout(g);
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
nodes: nodes.map((node) => {
|
|
23
|
-
const position = g.node(node.id);
|
|
24
|
-
// We are shifting the dagre node position (anchor=center center) to the top left
|
|
25
|
-
// so it matches the React Flow node anchor point (top left).
|
|
26
|
-
const x = position.x - (node.measured?.width ?? 0) / 2;
|
|
27
|
-
const y = position.y - (node.measured?.height ?? 0) / 2;
|
|
28
|
-
|
|
29
|
-
return { ...node, position: { x, y } };
|
|
30
|
-
}),
|
|
31
|
-
edges,
|
|
32
|
-
};
|
|
33
|
-
};
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
ReactFlow,
|
|
4
|
-
ReactFlowProvider,
|
|
5
|
-
useNodesState,
|
|
6
|
-
useEdgesState,
|
|
7
|
-
Background,
|
|
8
|
-
Controls,
|
|
9
|
-
useReactFlow,
|
|
10
|
-
addEdge,
|
|
11
|
-
BezierEdge ,
|
|
12
|
-
applyNodeChanges,
|
|
13
|
-
applyEdgeChanges
|
|
14
|
-
} from "@xyflow/react";
|
|
15
|
-
import "@xyflow/react/dist/style.css";
|
|
16
|
-
|
|
17
|
-
import { getLayoutedElements } from "./DagreLayout.js";
|
|
18
|
-
|
|
19
|
-
const edgeTypes = {
|
|
20
|
-
bezier: BezierEdge,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const NODE_WIDTH = 150;
|
|
24
|
-
const NODE_HEIGHT = 40;
|
|
25
|
-
export const Flow = ({ activeTool, onBehaviorSelect, onAddBehavior, onAddEdge, initialElements }) => {
|
|
26
|
-
const { screenToFlowPosition } = useReactFlow();
|
|
27
|
-
const [nodes, setNodes] = useState([]);
|
|
28
|
-
const [edges, setEdges] = useState([]);
|
|
29
|
-
const [ghostNode, setGhostNode] = useState(null);
|
|
30
|
-
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (!initialElements) return;
|
|
33
|
-
const layouted = getLayoutedElements(
|
|
34
|
-
initialElements.nodes,
|
|
35
|
-
initialElements.edges,
|
|
36
|
-
{direction:"TB"}
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
setNodes([...layouted.nodes]);
|
|
40
|
-
setEdges([...layouted.edges]);
|
|
41
|
-
}, [initialElements]);
|
|
42
|
-
|
|
43
|
-
// Callbacks to apply node changes
|
|
44
|
-
const onNodesChange = useCallback((changes) => {
|
|
45
|
-
setNodes((nds) => applyNodeChanges(changes, nds));
|
|
46
|
-
}, []);
|
|
47
|
-
const onEdgesChange = useCallback((changes) => {
|
|
48
|
-
setEdges((eds) => applyEdgeChanges(changes, eds));
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
// Console messages for debugging
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
console.log("Active Tool:", activeTool);
|
|
54
|
-
}, [activeTool]);
|
|
55
|
-
|
|
56
|
-
// Callback for mouse pane move
|
|
57
|
-
const onPaneMouseMove = useCallback( (event) => {
|
|
58
|
-
const pos = screenToFlowPosition({
|
|
59
|
-
x: event.clientX,
|
|
60
|
-
y: event.clientY,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (activeTool === "drop") {
|
|
64
|
-
setGhostNode({
|
|
65
|
-
id: "ghost",
|
|
66
|
-
position: {
|
|
67
|
-
x: pos.x - NODE_WIDTH / 2,
|
|
68
|
-
y: pos.y - NODE_HEIGHT / 2,
|
|
69
|
-
},
|
|
70
|
-
data: { label: "Behavior" },
|
|
71
|
-
draggable: false,
|
|
72
|
-
selectable: false,
|
|
73
|
-
style: {
|
|
74
|
-
opacity: 0.5,
|
|
75
|
-
pointerEvents: "none",
|
|
76
|
-
width: NODE_WIDTH,
|
|
77
|
-
height: NODE_HEIGHT,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}, [activeTool]);
|
|
82
|
-
|
|
83
|
-
// Callback for when mouse leaves pane
|
|
84
|
-
const onPaneMouseLeave = useCallback(() => {
|
|
85
|
-
setGhostNode(null);
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
// Callback for pane click
|
|
89
|
-
const onPaneClick = useCallback((event, node) => {
|
|
90
|
-
if (activeTool === "drop") {
|
|
91
|
-
if (!ghostNode) return;
|
|
92
|
-
setNodes((nds) => [
|
|
93
|
-
...nds,
|
|
94
|
-
{
|
|
95
|
-
...ghostNode,
|
|
96
|
-
draggable: true,
|
|
97
|
-
selectable: true,
|
|
98
|
-
id: crypto.randomUUID(),
|
|
99
|
-
style: {},
|
|
100
|
-
},
|
|
101
|
-
]);
|
|
102
|
-
onAddBehavior(ghostNode);
|
|
103
|
-
setGhostNode(null);
|
|
104
|
-
}
|
|
105
|
-
}, [ghostNode, activeTool, onAddBehavior]);
|
|
106
|
-
|
|
107
|
-
// Callback for node click to delete it (if tool is active)
|
|
108
|
-
const onNodeClick = useCallback((event, node) => {
|
|
109
|
-
if (activeTool === "delete") {
|
|
110
|
-
setNodes((nds) => nds.filter((n) => n.id !== node.id));
|
|
111
|
-
setEdges((eds) =>
|
|
112
|
-
eds.filter((e) => e.source !== node.id && e.target !== node.id)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}, [activeTool, onBehaviorSelect]);
|
|
116
|
-
|
|
117
|
-
// Callback for edge click to delete it (if tool is active)
|
|
118
|
-
const onEdgeClick = useCallback((event, edge) => {
|
|
119
|
-
if (activeTool === "delete") {
|
|
120
|
-
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
|
|
121
|
-
}
|
|
122
|
-
}, [activeTool]);
|
|
123
|
-
|
|
124
|
-
const onSelectionChange = useCallback((selection) => {
|
|
125
|
-
if (selection.nodes.length > 0) {
|
|
126
|
-
onBehaviorSelect(selection.nodes[0]);
|
|
127
|
-
}
|
|
128
|
-
}, [ onBehaviorSelect]);
|
|
129
|
-
|
|
130
|
-
// Callback for when edge is connected, nodesConnectable={activeTool === "connect"}
|
|
131
|
-
// is used to determine connectability
|
|
132
|
-
const onConnect = useCallback((connection) => {
|
|
133
|
-
const edge = {
|
|
134
|
-
...connection,
|
|
135
|
-
id: crypto.randomUUID(),
|
|
136
|
-
type: "smoothstep",
|
|
137
|
-
animated: true,
|
|
138
|
-
};
|
|
139
|
-
setEdges((eds) => [...eds, edge]);
|
|
140
|
-
onAddEdge(edge);
|
|
141
|
-
}, [activeTool, onAddEdge]);
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<ReactFlow
|
|
145
|
-
nodes={ghostNode ? [...nodes, ghostNode] : nodes}
|
|
146
|
-
edges={edges}
|
|
147
|
-
onPaneMouseMove={onPaneMouseMove}
|
|
148
|
-
onPaneMouseLeave={onPaneMouseLeave}
|
|
149
|
-
onPaneClick={onPaneClick}
|
|
150
|
-
onConnect={onConnect}
|
|
151
|
-
onNodesChange={onNodesChange}
|
|
152
|
-
onEdgesChange={onEdgesChange}
|
|
153
|
-
onNodeClick={onNodeClick}
|
|
154
|
-
onEdgeClick={onEdgeClick}
|
|
155
|
-
colorMode={"dark"}
|
|
156
|
-
edgeTypes={edgeTypes}
|
|
157
|
-
onSelectionChange={onSelectionChange}
|
|
158
|
-
fitView
|
|
159
|
-
>
|
|
160
|
-
<Background />
|
|
161
|
-
<Controls />
|
|
162
|
-
</ReactFlow>
|
|
163
|
-
);
|
|
164
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"uid":"60e79b4f-fc4b-4237-989f-e1b1559c2ee9","type":5,"nodes":[{"uid":"08a4e18c-f293-4ba1-be0a-c8451b50e819","type":6,"goToBehaviors":[{"uid":"35a2759b-e0df-47db-93b7-1f18c216922d","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptBookFromUser"}],"behavior":{"uid":"a873c4ba-3a63-4097-80df-46ed361f2e26","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToAddBookToBasket"}},{"uid":"db74773a-2f47-4873-b66f-08da9aaf196f","type":6,"goToBehaviors":[{"uid":"ac522716-13d2-48e5-b241-f2366b37efdb","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToBasket"}],"behavior":{"uid":"35a2759b-e0df-47db-93b7-1f18c216922d","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptBookFromUser"}},{"uid":"31bba9d3-9af2-41bc-bd70-d757f62b40c6","type":6,"goToBehaviors":[{"uid":"d4ec5fe5-acd2-4a8b-8e09-9ea3d5212a1f","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GenerateAuditReport"}],"behavior":{"uid":"53cebd65-5c19-4dde-8e50-4acc581dc166","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToAuditLibrary"}},{"uid":"836fdec5-db3e-4770-95fe-70c2d2c1261c","type":6,"goToBehaviors":[{"uid":"3e0b400c-f92a-48b5-b67b-e65d69f18a21","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"HandAuditToUser"}],"behavior":{"uid":"d4ec5fe5-acd2-4a8b-8e09-9ea3d5212a1f","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GenerateAuditReport"}},{"uid":"f79c407e-66fc-4d64-bcce-4a1c4841d098","type":6,"goToBehaviors":[{"uid":"1c75d75c-8970-4dd1-b91e-c79590354bd9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetBookFromBasket"}],"behavior":{"uid":"b39dfb15-d121-4ded-aa29-d317149308e9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToPlaceBooksOnShelf"}},{"uid":"0e266610-d498-4857-9249-7e6c98a1077f","type":6,"goToBehaviors":[{"uid":"7e80caf3-7fa9-4684-af89-fe2556addb87","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetFirstLetterOfBookName"}],"behavior":{"uid":"1c75d75c-8970-4dd1-b91e-c79590354bd9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetBookFromBasket"}},{"uid":"b009ee09-e037-4864-99a2-15f80187623f","type":6,"goToBehaviors":[{"uid":"457332a3-3c79-4c04-83c7-2b5b0539c0da","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"CreateSlotOnBookShelf"}],"behavior":{"uid":"7e80caf3-7fa9-4684-af89-fe2556addb87","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetFirstLetterOfBookName"}},{"uid":"b2acd6d3-d6b8-41de-b8a1-f22b4db4fcbe","type":6,"goToBehaviors":[{"uid":"a666f210-c00c-48bc-a13f-aea72f068e37","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToShelf"}],"behavior":{"uid":"7e80caf3-7fa9-4684-af89-fe2556addb87","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetFirstLetterOfBookName"}},{"uid":"3baca40a-9584-4ba7-be1b-235a660ca472","type":6,"goToBehaviors":[{"uid":"a666f210-c00c-48bc-a13f-aea72f068e37","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToShelf"}],"behavior":{"uid":"457332a3-3c79-4c04-83c7-2b5b0539c0da","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"CreateSlotOnBookShelf"}},{"uid":"14acf45d-ca77-48f4-9ff0-5692133a6258","type":6,"goToBehaviors":[{"uid":"1c75d75c-8970-4dd1-b91e-c79590354bd9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetBookFromBasket"}],"behavior":{"uid":"a666f210-c00c-48bc-a13f-aea72f068e37","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToShelf"}}]}
|