sample-ui-component-library 0.0.39-dev → 0.0.41-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.41-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
- "react-bootstrap-icons": "^1.11.5"
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,159 +1,107 @@
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 { useEffect, useState, useRef, useImperativeHandle, forwardRef, useMemo, useCallback } from "react";
2
+ import { Canvas, Node, Edge, removeAndUpsertNodes } from 'reaflow';
3
+ import PropTypes from 'prop-types';
15
4
 
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
- ];
5
+ import { designToNodes } from "./helper";
32
6
 
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);
7
+ import "./BehavioralGraphBuilder.scss";
40
8
 
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
- }, []);
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();
48
16
 
49
- // Console messages for debugging
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 });
50
21
  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,
22
+ const observer = new ResizeObserver(([entry]) => {
23
+ const { width, height } = entry.contentRect;
24
+ setSize({ width, height });
59
25
  });
60
26
 
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);
27
+ if (containerRef.current) observer.observe(containerRef.current);
28
+ return () => observer.disconnect();
84
29
  }, []);
85
30
 
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
31
 
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
- );
110
- }
111
- }, [activeTool]);
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
+ }
112
39
 
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));
40
+ const updateEngine = useCallback((engine) => {
41
+ if (engine) {
42
+ const { nodes, edges } = designToNodes(engine);
43
+ setNodes(nodes);
44
+ setEdges(edges);
117
45
  }
118
- }, [activeTool]);
46
+ }, [setNodes, setEdges]);
119
47
 
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,
48
+ const api = useMemo(() => {
49
+ return {
50
+ updateEngine
128
51
  };
129
- setEdges((eds) => [...eds, edge]);
130
- }, [activeTool]);
52
+ }, [updateEngine]);
53
+
54
+ useImperativeHandle(ref, () => api, [api]);
131
55
 
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
- };
56
+ const handleWheel = useCallback((e) => {
57
+ if (e.deltaY < 0) {
58
+ canvasRef.current.zoomIn()
59
+ } else {
60
+ canvasRef.current.zoomOut();
61
+ }
62
+ }, [canvasRef]);
152
63
 
153
- export const BehavioralGraphBuilder = ({ activeTool }) => {
154
64
  return (
155
- <ReactFlowProvider>
156
- <Flow activeTool={activeTool} />
157
- </ReactFlowProvider>
158
- );
159
- };
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
99
+ />
100
+ </div>
101
+ )
102
+ });
103
+
104
+ BehavioralGraphBuilder.propTypes = {
105
+ connectBehaviors: PropTypes.func.isRequired,
106
+ deleteBehavior: PropTypes.func.isRequired
107
+ }
@@ -0,0 +1,12 @@
1
+
2
+ .canvas-wrapper {
3
+ position: relative;
4
+ width: 100%;
5
+ height: 100%;
6
+ background-color: #1b1c1d;
7
+ overflow:hidden;
8
+ }
9
+
10
+ canvas {
11
+ overflow: hidden;
12
+ }
@@ -0,0 +1,39 @@
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 designToNodes = (engine) => {
8
+
9
+ let edges = [];
10
+ let nodes = [];
11
+
12
+ for (let i = 0; i < engine.graph.nodes.length; i++) {
13
+ const node = engine.graph.nodes[i];
14
+ nodes.push({
15
+ id: node.getBehavior().name,
16
+ text: node.getBehavior().name,
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
+ }
25
+
26
+ node.getGoToBehaviors().forEach((goTo) => {
27
+ edges.push({
28
+ id: `${node.getBehavior().name}->${goTo}`,
29
+ from: node.getBehavior().name,
30
+ to: goTo,
31
+ });
32
+ });
33
+ }
34
+
35
+ return {
36
+ nodes: nodes,
37
+ edges: edges
38
+ };
39
+ }
package/src/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from "./components/StackList";
2
2
  export * from "./components/FlowDiagram";
3
3
  export * from "./components/FileBrowser";
4
- export * from "./components/Editor";
5
- export * from "./components/BehavioralGraphBuilder";
4
+ export * from "./components/Editor";
@@ -13,5 +13,8 @@
13
13
  }
14
14
 
15
15
  .flow {
16
+ display: flex;
16
17
  flex-grow: 1;
17
- }
18
+ flex-direction: column;
19
+ overflow:hidden;
20
+ }
@@ -1,42 +1,115 @@
1
- import { useCallback, useState, useEffect } from "react";
2
- import { BehavioralGraphBuilder } from "../components/BehavioralGraphBuilder";
3
- import "./BehavioralGraphBuilderStories.scss"
4
- import { ToolBar } from "./components/ToolBar/ToolBar";
1
+ import { useEffect, useState, useCallback, useRef } from "react";
5
2
  import { useArgs } from "@storybook/preview-api";
3
+ import { action } from "@storybook/addon-actions";
4
+
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";
6
13
 
7
14
  export default {
8
- title: 'BehavioralGraphBuilder',
15
+ title: "BehavioralGraphBuilder",
9
16
  component: BehavioralGraphBuilder,
10
- argTypes: {
11
- }
17
+ argTypes: {},
12
18
  };
13
19
 
20
+ let count = 0;
21
+
14
22
  const Template = (args) => {
15
23
  const [, updateArgs] = useArgs();
16
24
 
25
+ const editorRef = useRef();
26
+
27
+ const [engine, setEngine] = useState();
28
+
17
29
  const [activeTool, setActiveTool] = useState("select");
18
30
 
19
- const selectTool = useCallback((tool) => {
20
- updateArgs({activeTool : tool});
21
- }, [activeTool, setActiveTool]);
22
-
31
+ const selectTool = useCallback(
32
+ (tool) => {
33
+ updateArgs({ activeTool: tool });
34
+ setActiveTool(tool);
35
+
36
+ if (tool === "add-node") {
37
+ engine.addNode("node-" + count++, []);
38
+ editorRef.current.updateEngine(engine);
39
+ }
40
+ },
41
+ [activeTool, setActiveTool, engine],
42
+ );
43
+
23
44
  useEffect(() => {
24
- updateArgs({activeTool : activeTool});
45
+ updateArgs({
46
+ activeTool: activeTool,
47
+ design: design,
48
+ });
25
49
  }, []);
26
50
 
51
+ useEffect(() => {
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]);
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
+ );
27
93
  return (
28
94
  <div className="graphBuilderRootContainer">
29
95
  <div className="toolbar">
30
- <ToolBar onSelectTool={selectTool}/>
96
+ <ToolBar onSelectTool={selectTool} />
31
97
  </div>
32
98
  <div className="flow">
33
- <BehavioralGraphBuilder {...args}/>
99
+ <BehavioralGraphBuilder
100
+ ref={editorRef}
101
+ {...args}
102
+ connectBehaviors={connectBehaviors}
103
+ deleteTransition={deleteTransition}
104
+ deleteBehavior={deleteBehavior}
105
+ />
34
106
  </div>
35
107
  </div>
36
- )
37
- }
108
+ );
109
+ };
38
110
 
39
- export const Default = Template.bind({})
111
+ export const Default = Template.bind({});
40
112
 
41
113
  Default.args = {
42
- }
114
+ design: design,
115
+ };
@@ -1,10 +1,8 @@
1
1
  import React, { useState } from "react";
2
2
 
3
3
  import {
4
- Cursor,
5
- Square,
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
- <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"}}
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":[]}]}