Skip to content
This repository was archived by the owner on Aug 4, 2025. It is now read-only.

Commit 98b7e47

Browse files
feat: added Dropdown Menu for Drag-and-Drop Node Creation + creation of local mqtt server using aedes (#185)
* added react-flow and updated toolbar * file upload and parsing added * parsing and displaying of nodes added * removed commented code and fixed the file structure * before removing comments * before pushing * removed comments and fixed the drag and drop issue * integrated mqtt using aedes and websocket * fixed subscribe node unhandled exception error * fixing sonar cloud error --------- Co-authored-by: NektariosFifes <61620751+NektariosFifes@users.noreply.github.com>
1 parent a45c0ae commit 98b7e47

File tree

17 files changed

+7664
-3939
lines changed

17 files changed

+7664
-3939
lines changed

Desktop/package-lock.json

Lines changed: 449 additions & 132 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Desktop/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@
233233
"@babel/core": "^7.16.0",
234234
"@swc/core": "^1.2.102",
235235
"ace-builds": "^1.4.13",
236+
"aedes": "^0.50.0",
236237
"classnames": "^2.3.1",
237238
"electron-debug": "^3.2.0",
238239
"electron-log": "^4.4.1",
@@ -241,6 +242,7 @@
241242
"history": "4.x.x",
242243
"js-yaml": "^4.1.0",
243244
"lodash": "^4.17.21",
245+
"mqtt": "^5.1.4",
244246
"path": "^0.12.7",
245247
"prism": "^4.1.2",
246248
"prismjs": "^1.25.0",
@@ -249,9 +251,11 @@
249251
"react-dom": "^17.0.2",
250252
"react-icons": "^4.3.1",
251253
"react-router-dom": "^5.3.0",
254+
"react-transition-group": "^4.4.5",
252255
"react-virtualized": "^9.22.3",
253256
"reactflow": "^11.7.4",
254-
"regenerator-runtime": "^0.13.9"
257+
"regenerator-runtime": "^0.13.9",
258+
"websocket-stream": "^5.5.2"
255259
},
256260
"devEngines": {
257261
"node": ">=14.x",

Desktop/src/main/main.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import { resolveHtmlPath } from './util';
2121
import autoSave from './tempScenarioSave';
2222

2323

24+
const aedes = require('aedes')()
25+
const httpServer = require('http').createServer()
26+
const ws = require('websocket-stream')
27+
28+
2429

2530
export default class AppUpdater {
2631
constructor() {
@@ -188,6 +193,24 @@ async function handleFileLoad () {
188193

189194
}
190195

196+
function startServer() {
197+
198+
const port = 1883
199+
200+
ws.createServer({ server: httpServer }, aedes.handle)
201+
202+
httpServer.listen(port, function () {
203+
console.log('websocket server listening on port ', port)
204+
})
205+
206+
}
207+
208+
function stopServer() {
209+
httpServer.close(function () {
210+
console.log('websocket server stopped')
211+
})
212+
}
213+
191214

192215
app
193216
.whenReady()
@@ -198,6 +221,8 @@ app
198221
});
199222

200223
ipcMain.on('button-click', handleFileLoad)
224+
ipcMain.on('start-aedes', startServer)
225+
ipcMain.on('stop-aedes', stopServer)
201226

202227
})
203228
.catch(console.log);

Desktop/src/parser/utils/layout.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { isNode, Node } from 'reactflow';
1+
import { useEffect, FunctionComponent } from 'react';
2+
import { isNode, Node, useReactFlow, useStore, useNodes, } from 'reactflow';
3+
24

35
const groupNodesByColumn = (elements: Node[]) => {
46
return elements.reduce((elementsGrouped: any, element: Node) => {
@@ -59,4 +61,25 @@ const groupNodesByColumn = (elements: Node[]) => {
5961

6062
return newElements.nodes;
6163

62-
};
64+
};
65+
66+
export const AutoLayout: FunctionComponent<AutoLayoutProps> = () => {
67+
const { fitView } = useReactFlow();
68+
const nodes = useNodes();
69+
const setNodes = useStore(state => state.setNodes);
70+
71+
useEffect(() => {
72+
if (nodes.length === 0 || !nodes[0].width) {
73+
return;
74+
}
75+
76+
const nodesWithOrginalPosition = nodes.filter(node => node.position.x === 0 && node.position.y === 0);
77+
if (nodesWithOrginalPosition.length > 1) {
78+
const calculatedNodes = calculateNodesForDynamicLayout(nodes);
79+
setNodes(calculatedNodes);
80+
fitView();
81+
}
82+
}, [nodes]);
83+
84+
return null;
85+
};
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import './index.css'
3+
import { CSSTransition } from 'react-transition-group'
4+
import { BsPlusLg, BsPlayFill, BsChevronRight, BsArrowLeft, BsArrowBarUp, BsArrowBarDown, BsFillHddRackFill, BsFillDiagram2Fill } from "react-icons/bs";
5+
import Subscribe from './NodeInput/Subscribe';
6+
import Application from './NodeInput/Application';
7+
import Publish from './NodeInput/Publish';
8+
9+
10+
const AddButton = ({ nodes, setNodes }) => {
11+
12+
return (
13+
<Navbar>
14+
15+
<NavItem icon={<BsPlayFill />} />
16+
17+
<NavItem icon={<BsPlusLg />}>
18+
<DropdownMenu nodes={nodes} setNodes={setNodes} ></DropdownMenu>
19+
</NavItem>
20+
21+
</Navbar>
22+
);
23+
}
24+
25+
function Navbar(props: { children: React.ReactNode }) {
26+
return (
27+
<nav className="navbar">
28+
<ul className="navbar-nav">{props.children}</ul>
29+
</nav>
30+
);
31+
}
32+
33+
function NavItem(props: { icon: React.ReactChild; children?: React.ReactNode }) {
34+
const [open, setOpen] = useState(false);
35+
36+
return (
37+
<li className="nav-item">
38+
<a href="#" className="icon-button" onClick={() => setOpen(!open)}>
39+
{props.icon}
40+
</a>
41+
42+
{open && props.children}
43+
</li>
44+
);
45+
}
46+
47+
function DropdownMenu({ nodes, setNodes }) {
48+
const [activeMenu, setActiveMenu] = useState('main');
49+
const [menuHeight, setMenuHeight] = useState(null);
50+
const dropdownRef = useRef(null);
51+
52+
useEffect(() => {
53+
setMenuHeight(dropdownRef.current?.firstChild.offsetHeight)
54+
}, [])
55+
56+
function calcHeight(el) {
57+
const height = el.offsetHeight;
58+
setMenuHeight(height);
59+
}
60+
61+
function DropdownItem(props) {
62+
return (
63+
<div className="menu-item" onClick={() => props.goToMenu && setActiveMenu(props.goToMenu)} onKeyDown={() => props.goToMenu && setActiveMenu(props.goToMenu)}>
64+
<span className="icon-button">{props.leftIcon}</span>
65+
{props.children}
66+
<span className="icon-right">{props.rightIcon}</span>
67+
</div>
68+
);
69+
}
70+
71+
return (
72+
<div className="dropdown" style={{ height: menuHeight }} ref={dropdownRef}>
73+
74+
<CSSTransition
75+
in={activeMenu === 'main'}
76+
timeout={500}
77+
classNames="menu-primary"
78+
unmountOnExit
79+
onEnter={calcHeight}>
80+
<div className="menu">
81+
<DropdownItem leftIcon={<BsFillDiagram2Fill/>} >Add Nodes</DropdownItem>
82+
<DropdownItem
83+
leftIcon={<BsFillHddRackFill/>}
84+
rightIcon={<BsChevronRight/>}
85+
goToMenu="application">
86+
Application Node
87+
</DropdownItem>
88+
<DropdownItem
89+
leftIcon={<BsArrowBarUp/>}
90+
rightIcon={<BsChevronRight/>}
91+
goToMenu="publish">
92+
Publish Node
93+
</DropdownItem>
94+
<DropdownItem
95+
leftIcon={<BsArrowBarDown/>}
96+
rightIcon={<BsChevronRight/>}
97+
goToMenu="subscribe">
98+
Subscribe Node
99+
</DropdownItem>
100+
101+
</div>
102+
</CSSTransition>
103+
104+
<CSSTransition
105+
in={activeMenu === 'application'}
106+
timeout={500}
107+
classNames="menu-secondary"
108+
unmountOnExit
109+
onEnter={calcHeight}>
110+
<div className="menu">
111+
<DropdownItem goToMenu="main" leftIcon={<BsArrowLeft/>}>
112+
<h3>Create Application Node</h3>
113+
</DropdownItem>
114+
<Application nodes={nodes} setNodes={setNodes}/>
115+
</div>
116+
</CSSTransition>
117+
118+
<CSSTransition
119+
in={activeMenu === 'publish'}
120+
timeout={500}
121+
classNames="menu-secondary"
122+
unmountOnExit
123+
onEnter={calcHeight}>
124+
<div className="menu">
125+
<DropdownItem goToMenu="main" leftIcon={<BsArrowLeft/>}>
126+
<h3>Create Publish Node</h3>
127+
</DropdownItem>
128+
<Publish nodes={nodes} setNodes={setNodes} />
129+
</div>
130+
</CSSTransition>
131+
132+
<CSSTransition
133+
in={activeMenu === 'subscribe'}
134+
timeout={500}
135+
classNames="menu-secondary"
136+
unmountOnExit
137+
onEnter={calcHeight}>
138+
<div className="menu">
139+
<DropdownItem goToMenu="main" leftIcon={<BsArrowLeft/>}>
140+
<h3>Create Subscribe Node</h3>
141+
</DropdownItem>
142+
<Subscribe nodes={nodes} setNodes={setNodes}/>
143+
</div>
144+
</CSSTransition>
145+
</div>
146+
);
147+
}
148+
149+
export default AddButton
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React, { useState } from "react";
2+
import "./index.css";
3+
4+
export default function Application({ nodes, setNodes }) {
5+
const [formData, setFormData] = useState({
6+
description: "",
7+
title: "",
8+
version: "",
9+
license: "",
10+
externalDocs: "",
11+
servers: "",
12+
defaultContentType: "",
13+
});
14+
15+
const handleChange = (event) => {
16+
const { name, value } = event.target;
17+
setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
18+
};
19+
20+
const onDragStart = (event, nodeType) => {
21+
event.dataTransfer.setData('application/reactflow', nodeType);
22+
event.dataTransfer.effectAllowed = 'move';
23+
event.dataTransfer.setData('application/json', JSON.stringify(formData));
24+
};
25+
26+
return (
27+
<div onDragStart={(event) => onDragStart(event, 'applicationNode')} draggable>
28+
<form className="custom-form">
29+
<label htmlFor="description">Description:</label>
30+
<textarea
31+
id="description"
32+
name="description"
33+
value={formData.description}
34+
onChange={handleChange}
35+
/>
36+
37+
<label htmlFor="title">Title:</label>
38+
<input
39+
type="text"
40+
id="title"
41+
name="title"
42+
value={formData.title}
43+
onChange={handleChange}
44+
/>
45+
46+
<label htmlFor="version">Version:</label>
47+
<input
48+
type="text"
49+
id="version"
50+
name="version"
51+
value={formData.version}
52+
onChange={handleChange}
53+
/>
54+
55+
<label htmlFor="license">License:</label>
56+
<input
57+
type="text"
58+
id="license"
59+
name="license"
60+
value={formData.license}
61+
onChange={handleChange}
62+
/>
63+
64+
<label htmlFor="externalDocs">External Docs:</label>
65+
<input
66+
type="text"
67+
id="externalDocs"
68+
name="externalDocs"
69+
value={formData.externalDocs}
70+
onChange={handleChange}
71+
/>
72+
73+
<label htmlFor="servers">Servers:</label>
74+
<input
75+
type="text"
76+
id="servers"
77+
name="servers"
78+
value={formData.servers}
79+
onChange={handleChange}
80+
/>
81+
82+
<label htmlFor="defaultContentType">Default Content Type:</label>
83+
<input
84+
type="text"
85+
id="defaultContentType"
86+
name="defaultContentType"
87+
value={formData.defaultContentType}
88+
onChange={handleChange}
89+
/>
90+
</form>
91+
</div>
92+
);
93+
}

0 commit comments

Comments
 (0)