Implemented Linux Support

This commit is contained in:
2025-03-27 17:41:51 -06:00
parent 25812d9202
commit a71f188a73
6 changed files with 401 additions and 249 deletions

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="Borealis"
content="Workflow Automation Tool"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Borealis</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -1,118 +1,139 @@
import React from "react";
import FlowEditor from "./components/FlowEditor";
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import {
AppBar,
Toolbar,
Typography,
Box,
Menu,
MenuItem,
Button,
CssBaseline,
ThemeProvider,
createTheme
AppBar,
Toolbar,
Typography,
Box,
Menu,
MenuItem,
Button,
CssBaseline,
ThemeProvider,
createTheme
} from "@mui/material";
const darkTheme = createTheme({
palette: {
mode: "dark",
background: {
default: "#121212",
paper: "#1e1e1e"
},
text: {
primary: "#ffffff"
}
palette: {
mode: "dark",
background: {
default: "#121212",
paper: "#1e1e1e"
},
text: {
primary: "#ffffff"
}
}
});
export default function App() {
// Separate menu state for each dropdown
const [workflowsAnchorEl, setWorkflowsAnchorEl] = React.useState(null);
const [aboutAnchorEl, setAboutAnchorEl] = React.useState(null);
const [workflowsAnchorEl, setWorkflowsAnchorEl] = React.useState(null);
const [aboutAnchorEl, setAboutAnchorEl] = React.useState(null);
const handleWorkflowsMenuOpen = (event) => {
setWorkflowsAnchorEl(event.currentTarget);
};
const handleWorkflowsMenuOpen = (event) => {
setWorkflowsAnchorEl(event.currentTarget);
};
const handleAboutMenuOpen = (event) => {
setAboutAnchorEl(event.currentTarget);
};
const handleAboutMenuOpen = (event) => {
setAboutAnchorEl(event.currentTarget);
};
const handleWorkflowsMenuClose = () => {
setWorkflowsAnchorEl(null);
};
const handleWorkflowsMenuClose = () => {
setWorkflowsAnchorEl(null);
};
const handleAboutMenuClose = () => {
setAboutAnchorEl(null);
};
const handleAboutMenuClose = () => {
setAboutAnchorEl(null);
};
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Box display="flex" flexDirection="column" height="100vh" bgcolor="#121212">
{/* Top Menu Bar */}
<AppBar position="static" sx={{ bgcolor: "#092c44" }}>
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
Borealis - Workflow Automation Tool
</Typography>
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
{/*
Main container that:
- fills 100% viewport height
- organizes content with flexbox (vertical)
*/}
<Box display="flex" flexDirection="column" height="100vh">
{/* --- TOP BAR --- */}
<AppBar position="static" sx={{ bgcolor: "#092c44" }}>
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
Borealis - Workflow Automation Tool
</Typography>
{/* Workflows Menu */}
<Button
color="inherit"
onClick={handleWorkflowsMenuOpen}
endIcon={<KeyboardArrowDownIcon />}
>
Workflows
</Button>
<Menu
anchorEl={workflowsAnchorEl}
open={Boolean(workflowsAnchorEl)}
onClose={handleWorkflowsMenuClose}
>
<MenuItem onClick={handleWorkflowsMenuClose}>Save Workflow</MenuItem>
<MenuItem onClick={handleWorkflowsMenuClose}>Load Workflow</MenuItem>
<MenuItem onClick={handleWorkflowsMenuClose}>Close Workflow</MenuItem>
</Menu>
{/* Workflows Menu */}
<Button
color="inherit"
onClick={handleWorkflowsMenuOpen}
endIcon={<KeyboardArrowDownIcon />}
>
Workflows
</Button>
<Menu
anchorEl={workflowsAnchorEl}
open={Boolean(workflowsAnchorEl)}
onClose={handleWorkflowsMenuClose}
>
<MenuItem onClick={handleWorkflowsMenuClose}>Save Workflow</MenuItem>
<MenuItem onClick={handleWorkflowsMenuClose}>Load Workflow</MenuItem>
<MenuItem onClick={handleWorkflowsMenuClose}>Close Workflow</MenuItem>
</Menu>
{/* About Menu */}
<Button
color="inherit"
onClick={handleAboutMenuOpen}
endIcon={<KeyboardArrowDownIcon />}
>
About
</Button>
<Menu
anchorEl={aboutAnchorEl}
open={Boolean(aboutAnchorEl)}
onClose={handleAboutMenuClose}
>
<MenuItem onClick={handleAboutMenuClose}>Gitea Project</MenuItem>
<MenuItem onClick={handleAboutMenuClose}>Credits</MenuItem>
</Menu>
</Toolbar>
</AppBar>
{/* About Menu */}
<Button
color="inherit"
onClick={handleAboutMenuOpen}
endIcon={<KeyboardArrowDownIcon />}
>
About
</Button>
<Menu
anchorEl={aboutAnchorEl}
open={Boolean(aboutAnchorEl)}
onClose={handleAboutMenuClose}
>
<MenuItem onClick={handleAboutMenuClose}>Gitea Project</MenuItem>
<MenuItem onClick={handleAboutMenuClose}>Credits</MenuItem>
</Menu>
</Toolbar>
</AppBar>
{/* Main Content - React Flow */}
<Box flexGrow={1}>
<FlowEditor updateNodeCount={(count) => {
document.getElementById("nodeCount").innerText = count;
}} />
</Box>
{/* --- REACT FLOW EDITOR --- */}
{/*
flexGrow={1} ⇒ This box expands to fill remaining vertical space
overflow="hidden" ⇒ No scroll bars, so React Flow does internal panning
mt: 1 ⇒ Add top margin so the gradient starts closer to the AppBar.
*/}
<Box flexGrow={1} overflow="hidden" sx={{ mt: 0 }}>
<FlowEditor
updateNodeCount={(count) => {
document.getElementById("nodeCount").innerText = count;
}}
/>
</Box>
{/* Status Bar */}
<Box
component="footer"
sx={{ bgcolor: "#1e1e1e", color: "white", padding: "5px 10px", textAlign: "center" }}
>
Nodes: <span id="nodeCount">0</span> | Update Rate: 500ms | Flask API Server:
<a href="http://127.0.0.1:5000/data" style={{ color: "#3c78b4" }}> http://127.0.0.1:5000/data</a>
</Box>
</Box>
</ThemeProvider>
);
{/* --- STATUS BAR at BOTTOM --- */}
<Box
component="footer"
sx={{
bgcolor: "#1e1e1e",
color: "white",
px: 2,
py: 1,
textAlign: "left"
}}
>
<b>Nodes</b>: <span id="nodeCount">0</span> | <b>Update Rate</b>: 500ms | <b>Flask API Server:</b>{" "}
<a
href="http://127.0.0.1:5000/api/nodes"
style={{ color: "#3c78b4" }}
>
http://127.0.0.1:5000/data/api/nodes
</a>
</Box>
</Box>
</ThemeProvider>
);
}

View File

@ -14,10 +14,10 @@
width: 100%;
height: 100%;
pointer-events: none; /* Ensures grid and nodes remain fully interactive */
background: linear-gradient( to bottom, rgba(9, 44, 68, 0.8) 0%, /* Deep blue at the top */
rgba(30, 30, 30, 0) 25%, /* Fade out towards center */
background: linear-gradient( to bottom, rgba(9, 44, 68, 0.9) 0%, /* Deep blue at the top */
rgba(30, 30, 30, 0) 45%, /* Fade out towards center */
rgba(30, 30, 30, 0) 75%, /* No gradient in the middle */
rgba(9, 44, 68, 0.8) 100% /* Deep blue at the bottom */
rgba(9, 44, 68, 0.7) 100% /* Deep blue at the bottom */
);
z-index: -1; /* Ensures it stays behind the React Flow elements */
}

View File

@ -1,52 +1,68 @@
import React, { useState, useEffect, useCallback } from "react";
import ReactFlow, {
addEdge,
Controls,
Background,
addEdge,
Controls,
Background,
} from "reactflow";
import "reactflow/dist/style.css";
import "./FlowEditor.css";
import "./FlowEditor.css";
const fetchNodes = async () => {
const response = await fetch("/api/workflow");
return response.json();
const response = await fetch("/api/workflow");
return response.json();
};
const saveWorkflow = async (workflow) => {
await fetch("/api/workflow", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(workflow),
});
await fetch("/api/workflow", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(workflow),
});
};
export default function FlowEditor() {
const [elements, setElements] = useState([]);
const [elements, setElements] = useState([]);
useEffect(() => {
fetchNodes().then((data) => setElements([...data.nodes, ...data.edges]));
}, []);
useEffect(() => {
fetchNodes().then((data) => {
// Data should contain nodes and edges arrays
const newElements = [...data.nodes, ...data.edges];
setElements(newElements);
});
}, []);
const onConnect = useCallback(
(params) => {
const newEdge = { id: `e${params.source}-${params.target}`, ...params };
setElements((els) => [...els, newEdge]);
saveWorkflow({ nodes: elements.filter(e => e.type), edges: [...elements.filter(e => !e.type), newEdge] });
},
[elements]
);
const onConnect = useCallback(
(params) => {
const newEdge = { id: `e${params.source}-${params.target}`, ...params };
setElements((els) => [...els, newEdge]);
return (
<div className="flow-editor-container">
<ReactFlow elements={elements} onConnect={onConnect}>
<Controls />
<Background
variant="lines"
gap={100}
size={1}
color="rgba(255, 255, 255, 0.2)" // White grid with 20% opacity
/>
</ReactFlow>
</div>
);
// Separate nodes/edges for saving:
const nodes = elements.filter((el) => el.type);
const edges = elements.filter((el) => !el.type);
saveWorkflow({
nodes,
edges: [...edges, newEdge],
});
},
[elements]
);
return (
<div className="flow-editor-container">
<ReactFlow
proOptions={{ hideAttribution: true }} // Remove the React Flow watermark
elements={elements}
onConnect={onConnect}
>
<Controls />
<Background
variant="lines"
gap={100}
size={1}
color="rgba(255, 255, 255, 0.2)" // White grid lines at 20% opacity
/>
</ReactFlow>
</div>
);
}