react-flow; lint; layouting (#799)

This commit is contained in:
Michael Quigley 2024-12-04 12:26:29 -05:00
parent 96eb246241
commit 334637b994
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
13 changed files with 340 additions and 1069 deletions

1133
ui100/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,10 +14,11 @@
"@emotion/styled": "^11.13.5",
"@mui/icons-material": "^6.1.8",
"@mui/material": "^6.1.8",
"@xyflow/react": "^12.3.5",
"d3-hierarchy": "^3.1.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^7.0.1",
"reagraph": "^4.21.0"
"react-router": "^7.0.1"
},
"devDependencies": {
"@eslint/js": "^9.13.0",

View File

@ -1,11 +1,10 @@
import {useEffect, useState} from "react";
import {Configuration, MetadataApi} from "./api";
import buildVisualizerGraph from "./model/visualizer.ts";
import {GraphCanvas} from "reagraph";
import {Box, Button} from "@mui/material";
import buildVisualizerGraph, {VisualOverview} from "./model/visualizer.ts";
import {Box} from "@mui/material";
import NavBar from "./NavBar.tsx";
import {reagraphTheme} from "./model/theme.ts";
import {User} from "./model/user.ts";
import Visualizer from "./Visualizer.tsx";
interface ApiConsoleProps {
user: User;
@ -14,8 +13,7 @@ interface ApiConsoleProps {
const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
const [version, setVersion] = useState("no version set");
const [nodes, setNodes] = useState([]);
const [edges, setEdges] = useState([]);
const [overview, setOverview] = useState(new VisualOverview());
useEffect(() => {
let api = new MetadataApi();
@ -32,16 +30,13 @@ const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
let interval = setInterval(() => {
let cfg = new Configuration({
headers: {
// ignorable token, local development environment
"X-TOKEN": user.token
}
});
let api = new MetadataApi(cfg);
api.overview()
.then(d => {
let graph = buildVisualizerGraph(d);
setNodes(graph.nodes);
setEdges(graph.edges);
setOverview(buildVisualizerGraph(d));
})
.catch(e => {
console.log(e);
@ -54,11 +49,9 @@ const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
return (
<div>
<NavBar logout={logout} version={version} />
<NavBar logout={logout} />
<Box>
<div style={{position: "relative", width: "100%", height: "500px"}}>
<GraphCanvas nodes={nodes} edges={edges} theme={reagraphTheme} />
</div>
<Visualizer overview={overview} />
</Box>
</div>
);

View File

@ -3,7 +3,7 @@ import {User} from "./model/user.ts";
import {useEffect, useState} from "react";
import {AccountApi, MetadataApi} from "./api";
import {Link} from "react-router";
import zroket from "../public/zrok-1.0.0-rocket.svg";
import zroket from "./assets/zrok-1.0.0-rocket-purple.svg";
interface LoginProps {
onLogin: (user: User) => void;

View File

@ -1,7 +1,7 @@
import {AppBar, Box, Button, Grid2, IconButton, Toolbar, Typography} from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import LogoutIcon from "@mui/icons-material/Logout";
import zroket from "../public/zrok-1.0.0-rocket-white.svg";
import zroket from "./assets/zrok-1.0.0-rocket-white.svg";
interface NavBarProps {
logout: () => void;
@ -15,7 +15,7 @@ const NavBar = ({ logout }: NavBarProps) => {
<IconButton size="large" edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}>
<MenuIcon />
</IconButton>
<Typography variant="h6" sx={{ flexGrow: 1 }} display={{ xs: "none", sm: "none", md: "block" }}>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
<Grid2 container sx={{ flexGrow: 1 }}>
<Grid2 display="flex" justifyContent="left">
<img src={zroket} height="30" />

66
ui100/src/Visualizer.tsx Normal file
View File

@ -0,0 +1,66 @@
import "@xyflow/react/dist/style.css";
import "./react-flow.css";
import {Background, Controls, ReactFlow, ReactFlowProvider, useEdgesState, useNodesState} from "@xyflow/react";
import {VisualOverview} from "./model/visualizer.ts";
import {useEffect} from "react";
import {stratify, tree} from "d3-hierarchy";
interface VisualizerProps {
overview: VisualOverview;
}
const Visualizer = ({ overview }: VisualizerProps) => {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const layout = (nodes, edges): VisualOverview => {
if(!nodes) {
return { nodes: [], edges: [] };
}
let g = tree();
if(nodes.length === 0) return { nodes, edges };
// const { width, height } = document.querySelector(`[data-id="$nodes[0].id"]`).getBoundingClientRect();
const width = 100;
const height = 40;
const hierarchy = stratify()
.id((node) => node.id)
.parentId((node) => edges.find((edge) => edge.target === node.id)?.source);
const root = hierarchy(nodes);
const layout = g.nodeSize([width * 2, height * 2])(root);
return {
nodes: layout
.descendants()
.map((node) => ({ ...node.data, position: { x: node.x, y: node.y }})),
edges,
}
}
useEffect(() => {
let layouted = layout(overview.nodes, overview.edges);
setNodes(layouted.nodes);
setEdges(layouted.edges);
}, [overview]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
>
<Background/>
<Controls />
</ReactFlow>
);
}
export default ({ overview }: VisualizerProps) => {
return (
<div style={{ height: "400px" }}>
<ReactFlowProvider>
<Visualizer overview={overview}/>
</ReactFlowProvider>
</div>
);
}

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="125.797mm"
height="166.26598mm"
viewBox="0 0 125.797 166.26598"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4 (e7c3feb, 2024-10-09)"
sodipodi:docname="zrok-1.0.0-rocket.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showguides="false"
inkscape:zoom="0.57641403"
inkscape:cx="1125.9268"
inkscape:cy="717.3663"
inkscape:window-width="1952"
inkscape:window-height="1304"
inkscape:window-x="1311"
inkscape:window-y="48"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"><inkscape:page
x="0"
y="-4.4822158e-22"
width="125.797"
height="166.26598"
id="page2"
margin="0"
bleed="0" /></sodipodi:namedview><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-41.620475,-64.027978)"><path
id="path2"
style="fill:#241774;fill-opacity:1;stroke-width:0.865487"
d="m 104.52059,64.027974 c 0,0 -12.300998,16.684095 -17.163248,24.964306 -4.8623,8.28022 -7.86765,12.84754 -10.59501,26.3339 -2.72736,13.48636 -1.27406,42.21319 -1.27406,42.21319 l -18.58752,20.04004 -15.280273,49.9615 40.422833,-6.79427 a 22.714797,11.567473 0 0 0 22.320548,9.54731 22.714797,11.567473 0 0 0 22.32712,-9.55945 l 40.72649,6.84277 -15.28027,-49.95979 -18.58916,-20.04001 c 0,0 1.45496,-28.72857 -1.2724,-42.21493 -2.72736,-13.48636 -5.73437,-18.05368 -10.59667,-26.3339 -4.8608,-8.277646 -17.14905,-24.947062 -17.15668,-24.957378 z m -0.0151,14.741718 c 0.52421,0.860497 14.76063,18.300498 18.34492,32.586238 3.65093,14.55125 3.25036,30.16797 2.29067,46.75483 -0.68395,11.82081 -5.48912,37.13724 -8.2384,50.97445 a 22.714797,11.567473 0 0 0 -12.54073,-1.9254 22.714797,11.567473 0 0 0 -12.237098,1.84921 c -2.75047,-13.84452 -7.55003,-39.12307 -8.23343,-50.93462 -0.95969,-16.58687 -1.36026,-32.20531 2.29067,-46.75656 3.57561,-14.25109 17.733538,-31.579359 18.323398,-32.548148 z m -27.999198,95.069708 6.36686,35.53319 -30.30472,9.08328 10.94985,-35.7998 z m 56.021568,0.0351 12.988,8.81667 10.9482,35.80155 -30.30311,-9.08504 z" /></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -13,11 +13,11 @@ code {
a {
font-weight: 500;
color: #646cff;
color: #241775;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
color: #9bf316;
}
h1 {
@ -25,25 +25,6 @@ h1 {
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
#root {
max-width: 1280px;
margin: 0 auto;

View File

@ -1,5 +1,4 @@
import {createTheme} from "@mui/material";
import {Theme} from "reagraph";
const componentOptions = {
MuiCard: {
@ -45,62 +44,4 @@ export const modalStyle = {
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
};
export const reagraphTheme: Theme = {
canvas: {
background: '#fff',
fog: '#fff'
},
node: {
fill: '#241775',
activeFill: '#9bf316',
opacity: 1,
selectedOpacity: 1,
inactiveOpacity: 0.2,
label: {
color: '#241775',
stroke: '#fff',
activeColor: '#9bf316'
},
subLabel: {
color: '#241775',
stroke: '#eee',
activeColor: '#9bf316'
}
},
lasso: {
border: '1px solid #55aaff',
background: 'rgba(75, 160, 255, 0.1)'
},
ring: {
fill: '#D8E6EA',
activeFill: '#1DE9AC'
},
edge: {
fill: '#D8E6EA',
activeFill: '#1DE9AC',
opacity: 1,
selectedOpacity: 1,
inactiveOpacity: 0.1,
label: {
stroke: '#fff',
color: '#2A6475',
activeColor: '#1DE9AC'
}
},
arrow: {
fill: '#D8E6EA',
activeFill: '#1DE9AC'
},
cluster: {
stroke: '#D8E6EA',
opacity: 1,
selectedOpacity: 1,
inactiveOpacity: 0.1,
label: {
stroke: '#fff',
color: '#2A6475'
}
}
};

View File

@ -1,18 +1,34 @@
import {Overview} from "../api";
import {Edge, Node} from "@xyflow/react";
export class VisualOverview {
nodes: Node[];
edges: Edge[];
}
const buildVisualizerGraph = (overview: Overview): VisualOverview => {
let out = new VisualOverview();
out.nodes = [
{ id: "1", position: { x: 0, y: 0 }, data: { label: "michael@quigley.com" } }
];
out.edges = [];
const buildVisualizerGraph = (overview: Overview) => {
let nodes = [
{ id: "1", label: "michael@quigley.com" }
]
let edges = [];
overview.environments?.forEach((env, i) => {
nodes.push({ id: (i+2).toString(), label: env.environment?.description! });
edges.push({ source: (i+2).toString(), target: "1", id: (i+2)+"-1" });
out.nodes.push({
id: (i+2).toString(),
position: { x: 0, y: 0 },
data: { label: env.environment?.description! },
});
out.edges.push({
id: "e" + (i+2) + "-1",
source: "1",
target: (i+2).toString()
});
})
return {
nodes: nodes,
edges: edges,
}
console.log(out);
return out;
}
export default buildVisualizerGraph;

8
ui100/src/react-flow.css Normal file
View File

@ -0,0 +1,8 @@
.react-flow__node {
font-family: "Poppins", sans-serif;
color: #241775;
border: 1px solid #241775;
}
.react-flow__attribution {
display: none;
}