sample-ui-component-library 0.0.38-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.38-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",
@@ -0,0 +1,32 @@
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);
14
+
15
+ useEffect(() => {
16
+ if (design) {
17
+ setInitialElements(designToReactFlowElements(design));
18
+ }
19
+ }, [design]);
20
+
21
+ return (
22
+ <ReactFlowProvider>
23
+ <Flow
24
+ initialElements={initialElements}
25
+ activeTool={activeTool}
26
+ onBehaviorSelect={onBehaviorSelect}
27
+ onAddBehavior={onAddBehavior}
28
+ onAddEdge={onAddEdge}
29
+ />
30
+ </ReactFlowProvider>
31
+ );
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
+ }
@@ -0,0 +1 @@
1
+ export * from "./BehavioralGraphBuilder.jsx"
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./components/StackList";
2
2
  export * from "./components/FlowDiagram";
3
3
  export * from "./components/FileBrowser";
4
- export * from "./components/Editor";
4
+ export * from "./components/Editor";
5
+ export * from "./components/BehavioralGraphBuilder";
@@ -0,0 +1,66 @@
1
+ import { useCallback, useState, useEffect } from "react";
2
+ import { BehavioralGraphBuilder } from "../components/BehavioralGraphBuilder";
3
+ import { ToolBar } from "./components/ToolBar/ToolBar";
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"
9
+
10
+ export default {
11
+ title: 'BehavioralGraphBuilder',
12
+ component: BehavioralGraphBuilder,
13
+ argTypes: {
14
+ }
15
+ };
16
+
17
+ const Template = (args) => {
18
+ const [, updateArgs] = useArgs();
19
+
20
+ const [activeTool, setActiveTool] = useState("select");
21
+
22
+ const selectTool = useCallback((tool) => {
23
+ updateArgs({activeTool : tool});
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
+ }, []);
37
+
38
+ useEffect(() => {
39
+ updateArgs(
40
+ {
41
+ activeTool: activeTool,
42
+ design: design,
43
+ onBehaviorSelect: onBehaviorSelect,
44
+ onAddBehavior: onAddBehavior,
45
+ onAddEdge: onAddEdge
46
+ }
47
+ );
48
+ }, []);
49
+
50
+ return (
51
+ <div className="graphBuilderRootContainer">
52
+ <div className="toolbar">
53
+ <ToolBar onSelectTool={selectTool}/>
54
+ </div>
55
+ <div className="flow">
56
+ <BehavioralGraphBuilder {...args}/>
57
+ </div>
58
+ </div>
59
+ )
60
+ }
61
+
62
+ export const Default = Template.bind({})
63
+
64
+ Default.args = {
65
+ design: design
66
+ }
@@ -0,0 +1,17 @@
1
+ .graphBuilderRootContainer {
2
+ position: absolute;
3
+ top:0;
4
+ bottom:0;
5
+ left:0;
6
+ right:0;
7
+ display:flex;
8
+ flex-direction: row;
9
+ }
10
+
11
+ .toolbar {
12
+ width:40px;
13
+ }
14
+
15
+ .flow {
16
+ flex-grow: 1;
17
+ }
@@ -0,0 +1,57 @@
1
+ import React, { useState } from "react";
2
+
3
+ import {
4
+ Cursor,
5
+ Square,
6
+ NodePlus,
7
+ Trash
8
+ } from "react-bootstrap-icons";
9
+
10
+ import "./ToolBar.scss";
11
+
12
+ ToolBar.propTypes = {};
13
+
14
+ /**
15
+ * Toolbar Component
16
+ * @return {JSX.Element}
17
+ */
18
+ export function ToolBar({ onSelectTool }) {
19
+ const [selectedTool, setSelectedTool] = useState("select");
20
+
21
+ const selectTool = (tool) => {
22
+ setSelectedTool(tool);
23
+ onSelectTool(tool);
24
+ };
25
+
26
+ return (
27
+ <div className="toolbarWrapper">
28
+ <div className="toolbarContainer">
29
+ <Cursor
30
+ onClick={(e) => selectTool("select")}
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"}}
38
+ title="Add Node"
39
+ className="icon"
40
+ />
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
+ </div>
54
+ <div className="toolbarContainer bottom"></div>
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,29 @@
1
+ .toolbarWrapper {
2
+ display:flex;
3
+ flex-direction: column;
4
+ justify-content:space-around;
5
+ height: 100%;
6
+ background-color: #333333;
7
+ }
8
+
9
+ .toolbarContainer {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 15px;
13
+ width:100%;
14
+ align-items: center;
15
+ padding: 10px 0;
16
+ }
17
+
18
+ .toolbarWrapper > .bottom {
19
+ margin-top: auto;
20
+ }
21
+
22
+ .toolbarContainer > .icon {
23
+ color:grey;
24
+ }
25
+
26
+ .toolbarContainer > .icon:hover {
27
+ color:white;
28
+ cursor:pointer;
29
+ }
@@ -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"}}]}