sample-ui-component-library 0.0.39-dev → 0.0.40-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sample-ui-component-library",
3
- "version": "0.0.39-dev",
3
+ "version": "0.0.40-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",
@@ -1,159 +1,32 @@
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
- applyNodeChanges,
12
- applyEdgeChanges
13
- } from "@xyflow/react";
14
- import "@xyflow/react/dist/style.css";
1
+ import { Flow } from "./Flow";
2
+ import { ReactFlowProvider } from "@xyflow/react";
3
+ import { useEffect, useState } from "react";
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);
15
14
 
16
- const initialNodes = [
17
- {
18
- id: "1",
19
- position: { x: 250, y: 150 },
20
- data: { label: "Behavior 1" },
21
- draggable: true,
22
- selectable: true,
23
- },
24
- {
25
- id: "2",
26
- position: { x: 250, y: 250 },
27
- data: { label: "Behavior 2" },
28
- draggable: true,
29
- selectable: true,
30
- },
31
- ];
32
-
33
- const NODE_WIDTH = 150;
34
- const NODE_HEIGHT = 40;
35
- const Flow = ({ activeTool }) => {
36
- const { screenToFlowPosition } = useReactFlow();
37
- const [nodes, setNodes] = useState(initialNodes);
38
- const [edges, setEdges] = useState([]);
39
- const [ghostNode, setGhostNode] = useState(null);
40
-
41
- // Callbacks to apply node changes
42
- const onNodesChange = useCallback((changes) => {
43
- setNodes((nds) => applyNodeChanges(changes, nds));
44
- }, []);
45
- const onEdgesChange = useCallback((changes) => {
46
- setEdges((eds) => applyEdgeChanges(changes, eds));
47
- }, []);
48
-
49
- // Console messages for debugging
50
15
  useEffect(() => {
51
- console.log("Active Tool:", activeTool);
52
- }, [activeTool]);
53
-
54
- // Callback for mouse pane move
55
- const onPaneMouseMove = useCallback( (event) => {
56
- const pos = screenToFlowPosition({
57
- x: event.clientX,
58
- y: event.clientY,
59
- });
60
-
61
- if (activeTool === "drop") {
62
- setGhostNode({
63
- id: "ghost",
64
- position: {
65
- x: pos.x - NODE_WIDTH / 2,
66
- y: pos.y - NODE_HEIGHT / 2,
67
- },
68
- data: { label: "Behavior" },
69
- draggable: false,
70
- selectable: false,
71
- style: {
72
- opacity: 0.5,
73
- pointerEvents: "none",
74
- width: NODE_WIDTH,
75
- height: NODE_HEIGHT,
76
- },
77
- });
78
- }
79
- }, [activeTool]);
80
-
81
- // Callback for when mouse leaves pane
82
- const onPaneMouseLeave = useCallback(() => {
83
- setGhostNode(null);
84
- }, []);
85
-
86
- // Callback for pane click
87
- const onPaneClick = useCallback((event, node) => {
88
- if (activeTool === "drop") {
89
- if (!ghostNode) return;
90
- setNodes((nds) => [
91
- ...nds,
92
- {
93
- ...ghostNode,
94
- draggable: true,
95
- selectable: true,
96
- id: crypto.randomUUID(),
97
- style: {},
98
- },
99
- ]);
100
- }
101
- }, [ghostNode, activeTool]);
102
-
103
- // Callback for node click to delete it (if tool is active)
104
- const onNodeClick = useCallback((event, node) => {
105
- if (activeTool === "delete") {
106
- setNodes((nds) => nds.filter((n) => n.id !== node.id));
107
- setEdges((eds) =>
108
- eds.filter((e) => e.source !== node.id && e.target !== node.id)
109
- );
16
+ if (design) {
17
+ setInitialElements(designToReactFlowElements(design));
110
18
  }
111
- }, [activeTool]);
112
-
113
- // Callback for edge click to delete it (if tool is active)
114
- const onEdgeClick = useCallback((event, edge) => {
115
- if (activeTool === "delete") {
116
- setEdges((eds) => eds.filter((e) => e.id !== edge.id));
117
- }
118
- }, [activeTool]);
119
-
120
- // Callback for when edge is connected, nodesConnectable={activeTool === "connect"}
121
- // is used to determine connectability
122
- const onConnect = useCallback((connection) => {
123
- const edge = {
124
- ...connection,
125
- id: crypto.randomUUID(),
126
- type: "smoothstep",
127
- animated: true,
128
- };
129
- setEdges((eds) => [...eds, edge]);
130
- }, [activeTool]);
131
-
132
- return (
133
- <ReactFlow
134
- nodes={ghostNode ? [...nodes, ghostNode] : nodes}
135
- edges={edges}
136
- onPaneMouseMove={onPaneMouseMove}
137
- onPaneMouseLeave={onPaneMouseLeave}
138
- onPaneClick={onPaneClick}
139
- onConnect={onConnect}
140
- onNodesChange={onNodesChange}
141
- onEdgesChange={onEdgesChange}
142
- onNodeClick={onNodeClick}
143
- onEdgeClick={onEdgeClick}
144
- colorMode={"dark"}
145
- fitView
146
- >
147
- <Background />
148
- <Controls />
149
- </ReactFlow>
150
- );
151
- };
19
+ }, [design]);
152
20
 
153
- export const BehavioralGraphBuilder = ({ activeTool }) => {
154
21
  return (
155
22
  <ReactFlowProvider>
156
- <Flow activeTool={activeTool} />
23
+ <Flow
24
+ initialElements={initialElements}
25
+ activeTool={activeTool}
26
+ onBehaviorSelect={onBehaviorSelect}
27
+ onAddBehavior={onAddBehavior}
28
+ onAddEdge={onAddEdge}
29
+ />
157
30
  </ReactFlowProvider>
158
31
  );
159
32
  };
@@ -0,0 +1,33 @@
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
+ };
@@ -0,0 +1,164 @@
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
+ };
@@ -0,0 +1,35 @@
1
+
2
+ /**
3
+ * Converts a DAL design specification object into React Flow elements (nodes and edges).
4
+ * @param {Object} design
5
+ * @returns {Object} An object containing nodes and edges for React Flow
6
+ */
7
+ export const designToReactFlowElements = (design) => {
8
+
9
+ let edges = [];
10
+ let nodes = [];
11
+ design.nodes.forEach((node) => {
12
+ nodes.push({
13
+ id: node.behavior.uid,
14
+ type: 'default',
15
+ data: { label: node.behavior.name },
16
+ position: { x: 10, y: 10 },
17
+ });
18
+
19
+ node.goToBehaviors.forEach((goTo) => {
20
+ edges.push({
21
+ id: `${node.behavior.uid}->${goTo.uid}`,
22
+ type: "bezier",
23
+ source: node.behavior.uid,
24
+ target: goTo.uid,
25
+ animated: true
26
+ });
27
+ });
28
+ });
29
+
30
+ return {
31
+ nodes: nodes,
32
+ edges: edges
33
+ };
34
+
35
+ }
@@ -1,8 +1,11 @@
1
1
  import { useCallback, useState, useEffect } from "react";
2
2
  import { BehavioralGraphBuilder } from "../components/BehavioralGraphBuilder";
3
- import "./BehavioralGraphBuilderStories.scss"
4
3
  import { ToolBar } from "./components/ToolBar/ToolBar";
5
4
  import { useArgs } from "@storybook/preview-api";
5
+ import { action } from "@storybook/addon-actions";
6
+
7
+ import design from "./data/Designs/simple_design_temp.json";
8
+ import "./BehavioralGraphBuilderStories.scss"
6
9
 
7
10
  export default {
8
11
  title: 'BehavioralGraphBuilder',
@@ -19,9 +22,29 @@ const Template = (args) => {
19
22
  const selectTool = useCallback((tool) => {
20
23
  updateArgs({activeTool : tool});
21
24
  }, [activeTool, setActiveTool]);
25
+
26
+ const onBehaviorSelect = useCallback((behavior) => {
27
+ action('Selected Behavior:')(behavior);
28
+ }, []);
29
+
30
+ const onAddBehavior = useCallback((behavior) => {
31
+ action('Added Behavior:')(behavior);
32
+ }, []);
33
+
34
+ const onAddEdge = useCallback((edge) => {
35
+ action('Added Edge:')(edge);
36
+ }, []);
22
37
 
23
38
  useEffect(() => {
24
- updateArgs({activeTool : activeTool});
39
+ updateArgs(
40
+ {
41
+ activeTool: activeTool,
42
+ design: design,
43
+ onBehaviorSelect: onBehaviorSelect,
44
+ onAddBehavior: onAddBehavior,
45
+ onAddEdge: onAddEdge
46
+ }
47
+ );
25
48
  }, []);
26
49
 
27
50
  return (
@@ -39,4 +62,5 @@ const Template = (args) => {
39
62
  export const Default = Template.bind({})
40
63
 
41
64
  Default.args = {
65
+ design: design
42
66
  }
@@ -0,0 +1 @@
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"}}]}