Skip to content

Commit

Permalink
feat: added Dropdown Menu for Drag-and-Drop Node Creation + creation …
Browse files Browse the repository at this point in the history
…of local mqtt server using aedes (asyncapi#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 <[email protected]>
  • Loading branch information
SumantxD and NektariosFifes authored Dec 11, 2023
1 parent a45c0ae commit 98b7e47
Show file tree
Hide file tree
Showing 17 changed files with 7,664 additions and 3,939 deletions.
581 changes: 449 additions & 132 deletions Desktop/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@
"@babel/core": "^7.16.0",
"@swc/core": "^1.2.102",
"ace-builds": "^1.4.13",
"aedes": "^0.50.0",
"classnames": "^2.3.1",
"electron-debug": "^3.2.0",
"electron-log": "^4.4.1",
Expand All @@ -241,6 +242,7 @@
"history": "4.x.x",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"mqtt": "^5.1.4",
"path": "^0.12.7",
"prism": "^4.1.2",
"prismjs": "^1.25.0",
Expand All @@ -249,9 +251,11 @@
"react-dom": "^17.0.2",
"react-icons": "^4.3.1",
"react-router-dom": "^5.3.0",
"react-transition-group": "^4.4.5",
"react-virtualized": "^9.22.3",
"reactflow": "^11.7.4",
"regenerator-runtime": "^0.13.9"
"regenerator-runtime": "^0.13.9",
"websocket-stream": "^5.5.2"
},
"devEngines": {
"node": ">=14.x",
Expand Down
25 changes: 25 additions & 0 deletions Desktop/src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import { resolveHtmlPath } from './util';
import autoSave from './tempScenarioSave';


const aedes = require('aedes')()
const httpServer = require('http').createServer()
const ws = require('websocket-stream')



export default class AppUpdater {
constructor() {
Expand Down Expand Up @@ -188,6 +193,24 @@ async function handleFileLoad () {

}

function startServer() {

const port = 1883

ws.createServer({ server: httpServer }, aedes.handle)

httpServer.listen(port, function () {
console.log('websocket server listening on port ', port)
})

}

function stopServer() {
httpServer.close(function () {
console.log('websocket server stopped')
})
}


app
.whenReady()
Expand All @@ -198,6 +221,8 @@ app
});

ipcMain.on('button-click', handleFileLoad)
ipcMain.on('start-aedes', startServer)
ipcMain.on('stop-aedes', stopServer)

})
.catch(console.log);
27 changes: 25 additions & 2 deletions Desktop/src/parser/utils/layout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { isNode, Node } from 'reactflow';
import { useEffect, FunctionComponent } from 'react';
import { isNode, Node, useReactFlow, useStore, useNodes, } from 'reactflow';


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

return newElements.nodes;

};
};

export const AutoLayout: FunctionComponent<AutoLayoutProps> = () => {
const { fitView } = useReactFlow();
const nodes = useNodes();
const setNodes = useStore(state => state.setNodes);

useEffect(() => {
if (nodes.length === 0 || !nodes[0].width) {
return;
}

const nodesWithOrginalPosition = nodes.filter(node => node.position.x === 0 && node.position.y === 0);
if (nodesWithOrginalPosition.length > 1) {
const calculatedNodes = calculateNodesForDynamicLayout(nodes);
setNodes(calculatedNodes);
fitView();
}
}, [nodes]);

return null;
};
149 changes: 149 additions & 0 deletions Desktop/src/renderer/AddNodesDnD/AddButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, { useState, useEffect, useRef } from 'react';
import './index.css'
import { CSSTransition } from 'react-transition-group'
import { BsPlusLg, BsPlayFill, BsChevronRight, BsArrowLeft, BsArrowBarUp, BsArrowBarDown, BsFillHddRackFill, BsFillDiagram2Fill } from "react-icons/bs";
import Subscribe from './NodeInput/Subscribe';
import Application from './NodeInput/Application';
import Publish from './NodeInput/Publish';


const AddButton = ({ nodes, setNodes }) => {

return (
<Navbar>

<NavItem icon={<BsPlayFill />} />

<NavItem icon={<BsPlusLg />}>
<DropdownMenu nodes={nodes} setNodes={setNodes} ></DropdownMenu>
</NavItem>

</Navbar>
);
}

function Navbar(props: { children: React.ReactNode }) {
return (
<nav className="navbar">
<ul className="navbar-nav">{props.children}</ul>
</nav>
);
}

function NavItem(props: { icon: React.ReactChild; children?: React.ReactNode }) {
const [open, setOpen] = useState(false);

return (
<li className="nav-item">
<a href="#" className="icon-button" onClick={() => setOpen(!open)}>
{props.icon}
</a>

{open && props.children}
</li>
);
}

function DropdownMenu({ nodes, setNodes }) {
const [activeMenu, setActiveMenu] = useState('main');
const [menuHeight, setMenuHeight] = useState(null);
const dropdownRef = useRef(null);

useEffect(() => {
setMenuHeight(dropdownRef.current?.firstChild.offsetHeight)
}, [])

function calcHeight(el) {
const height = el.offsetHeight;
setMenuHeight(height);
}

function DropdownItem(props) {
return (
<div className="menu-item" onClick={() => props.goToMenu && setActiveMenu(props.goToMenu)} onKeyDown={() => props.goToMenu && setActiveMenu(props.goToMenu)}>
<span className="icon-button">{props.leftIcon}</span>
{props.children}
<span className="icon-right">{props.rightIcon}</span>
</div>
);
}

return (
<div className="dropdown" style={{ height: menuHeight }} ref={dropdownRef}>

<CSSTransition
in={activeMenu === 'main'}
timeout={500}
classNames="menu-primary"
unmountOnExit
onEnter={calcHeight}>
<div className="menu">
<DropdownItem leftIcon={<BsFillDiagram2Fill/>} >Add Nodes</DropdownItem>
<DropdownItem
leftIcon={<BsFillHddRackFill/>}
rightIcon={<BsChevronRight/>}
goToMenu="application">
Application Node
</DropdownItem>
<DropdownItem
leftIcon={<BsArrowBarUp/>}
rightIcon={<BsChevronRight/>}
goToMenu="publish">
Publish Node
</DropdownItem>
<DropdownItem
leftIcon={<BsArrowBarDown/>}
rightIcon={<BsChevronRight/>}
goToMenu="subscribe">
Subscribe Node
</DropdownItem>

</div>
</CSSTransition>

<CSSTransition
in={activeMenu === 'application'}
timeout={500}
classNames="menu-secondary"
unmountOnExit
onEnter={calcHeight}>
<div className="menu">
<DropdownItem goToMenu="main" leftIcon={<BsArrowLeft/>}>
<h3>Create Application Node</h3>
</DropdownItem>
<Application nodes={nodes} setNodes={setNodes}/>
</div>
</CSSTransition>

<CSSTransition
in={activeMenu === 'publish'}
timeout={500}
classNames="menu-secondary"
unmountOnExit
onEnter={calcHeight}>
<div className="menu">
<DropdownItem goToMenu="main" leftIcon={<BsArrowLeft/>}>
<h3>Create Publish Node</h3>
</DropdownItem>
<Publish nodes={nodes} setNodes={setNodes} />
</div>
</CSSTransition>

<CSSTransition
in={activeMenu === 'subscribe'}
timeout={500}
classNames="menu-secondary"
unmountOnExit
onEnter={calcHeight}>
<div className="menu">
<DropdownItem goToMenu="main" leftIcon={<BsArrowLeft/>}>
<h3>Create Subscribe Node</h3>
</DropdownItem>
<Subscribe nodes={nodes} setNodes={setNodes}/>
</div>
</CSSTransition>
</div>
);
}

export default AddButton
93 changes: 93 additions & 0 deletions Desktop/src/renderer/AddNodesDnD/NodeInput/Application.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useState } from "react";
import "./index.css";

export default function Application({ nodes, setNodes }) {
const [formData, setFormData] = useState({
description: "",
title: "",
version: "",
license: "",
externalDocs: "",
servers: "",
defaultContentType: "",
});

const handleChange = (event) => {
const { name, value } = event.target;
setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
};

const onDragStart = (event, nodeType) => {
event.dataTransfer.setData('application/reactflow', nodeType);
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('application/json', JSON.stringify(formData));
};

return (
<div onDragStart={(event) => onDragStart(event, 'applicationNode')} draggable>
<form className="custom-form">
<label htmlFor="description">Description:</label>
<textarea
id="description"
name="description"
value={formData.description}
onChange={handleChange}
/>

<label htmlFor="title">Title:</label>
<input
type="text"
id="title"
name="title"
value={formData.title}
onChange={handleChange}
/>

<label htmlFor="version">Version:</label>
<input
type="text"
id="version"
name="version"
value={formData.version}
onChange={handleChange}
/>

<label htmlFor="license">License:</label>
<input
type="text"
id="license"
name="license"
value={formData.license}
onChange={handleChange}
/>

<label htmlFor="externalDocs">External Docs:</label>
<input
type="text"
id="externalDocs"
name="externalDocs"
value={formData.externalDocs}
onChange={handleChange}
/>

<label htmlFor="servers">Servers:</label>
<input
type="text"
id="servers"
name="servers"
value={formData.servers}
onChange={handleChange}
/>

<label htmlFor="defaultContentType">Default Content Type:</label>
<input
type="text"
id="defaultContentType"
name="defaultContentType"
value={formData.defaultContentType}
onChange={handleChange}
/>
</form>
</div>
);
}
Loading

0 comments on commit 98b7e47

Please sign in to comment.