mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-07-27 03:28:28 -06:00
Overhauled Data_Node to be functional
Implemented dynamically-configurable global timer Revised Data_Node to be foundational for future nodes.
This commit is contained in:
@ -10,34 +10,12 @@
|
|||||||
content="Workflow Automation Tool"
|
content="Workflow Automation Tool"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<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" />
|
<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>
|
<title>Borealis</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -54,6 +54,11 @@ import ReactFlow, {
|
|||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
import "./Borealis.css";
|
import "./Borealis.css";
|
||||||
|
|
||||||
|
// Global Node Update Timer Variable
|
||||||
|
if (!window.BorealisUpdateRate) {
|
||||||
|
window.BorealisUpdateRate = 100; // Default Update Rate: 100ms
|
||||||
|
}
|
||||||
|
|
||||||
const nodeContext = require.context("./nodes", true, /\.jsx$/);
|
const nodeContext = require.context("./nodes", true, /\.jsx$/);
|
||||||
const nodeTypes = {};
|
const nodeTypes = {};
|
||||||
const categorizedNodes = {};
|
const categorizedNodes = {};
|
||||||
@ -413,8 +418,42 @@ export default function App() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box component="footer" sx={{ bgcolor: "#1e1e1e", color: "white", px: 2, py: 1, textAlign: "left" }}>
|
<Box component="footer" sx={{ bgcolor: "#1e1e1e", color: "white", px: 2, py: 1, display: "flex", alignItems: "center", gap: 2 }}>
|
||||||
<b>Nodes</b>: <span id="nodeCount">0</span> | <b>Update Rate</b>: 500ms
|
<b>Nodes</b>: <span id="nodeCount">0</span>
|
||||||
|
<Divider orientation="vertical" flexItem sx={{ borderColor: "#444" }} />
|
||||||
|
<b>Update Rate (ms):</b>
|
||||||
|
<input
|
||||||
|
id="updateRateInput"
|
||||||
|
type="number"
|
||||||
|
min="50"
|
||||||
|
step="50"
|
||||||
|
defaultValue={window.BorealisUpdateRate}
|
||||||
|
style={{
|
||||||
|
width: "80px",
|
||||||
|
background: "#121212",
|
||||||
|
color: "#fff",
|
||||||
|
border: "1px solid #444",
|
||||||
|
borderRadius: "3px",
|
||||||
|
padding: "3px",
|
||||||
|
fontSize: "0.8rem"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
const val = parseInt(document.getElementById("updateRateInput")?.value);
|
||||||
|
if (!isNaN(val) && val >= 50) {
|
||||||
|
window.BorealisUpdateRate = val;
|
||||||
|
console.log("Global update rate set to", val + "ms");
|
||||||
|
} else {
|
||||||
|
alert("Please enter a valid number (minimum 50)");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{ color: "#58a6ff", borderColor: "#58a6ff", fontSize: "0.75rem", textTransform: "none", px: 1.5 }}
|
||||||
|
>
|
||||||
|
Update Rate
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
@ -1,21 +1,190 @@
|
|||||||
import React from "react";
|
/**
|
||||||
import { Handle, Position } from "reactflow";
|
* ============================================
|
||||||
|
* Borealis - Standard Live Data Node Template
|
||||||
|
* ============================================
|
||||||
|
*
|
||||||
|
* COMPONENT ROLE:
|
||||||
|
* This component defines a "data conduit" node that can accept input,
|
||||||
|
* process/override it with local logic, and forward the output on a timed basis.
|
||||||
|
*
|
||||||
|
* It serves as the core behavior model for other nodes that rely on live propagation.
|
||||||
|
* Clone and extend this file to create nodes with specialized logic.
|
||||||
|
*
|
||||||
|
* CORE CONCEPTS:
|
||||||
|
* - Uses a centralized shared memory (window.BorealisValueBus) for value sharing
|
||||||
|
* - Synchronizes with upstream nodes based on ReactFlow edges
|
||||||
|
* - Emits to downstream nodes by updating its own BorealisValueBus[id] value
|
||||||
|
* - Controlled by a global polling timer (window.BorealisUpdateRate)
|
||||||
|
*
|
||||||
|
* LIFECYCLE SUMMARY:
|
||||||
|
* - onMount: initializes logic loop and sync monitor
|
||||||
|
* - onUpdate: watches edges and global rate, reconfigures as needed
|
||||||
|
* - onUnmount: cleans up all timers
|
||||||
|
*
|
||||||
|
* DATA FLOW OVERVIEW:
|
||||||
|
* - INPUT: if a left-edge (target) is connected, disables manual editing
|
||||||
|
* - OUTPUT: propagates renderValue to downstream nodes via right-edge (source)
|
||||||
|
*
|
||||||
|
* STRUCTURE:
|
||||||
|
* - Node UI includes:
|
||||||
|
* * Label (from data.label)
|
||||||
|
* * Body description (from data.content)
|
||||||
|
* * Input textbox (disabled if input is connected)
|
||||||
|
*
|
||||||
|
* HOW TO EXTEND:
|
||||||
|
* - For transformations, insert logic into runNodeLogic()
|
||||||
|
* - To validate or restrict input types, modify handleManualInput()
|
||||||
|
* - For side-effects or external API calls, add hooks inside runNodeLogic()
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Handle, Position, useReactFlow, useStore } from "reactflow";
|
||||||
|
|
||||||
|
// Global Shared Bus for Node Data Propagation
|
||||||
|
if (!window.BorealisValueBus) {
|
||||||
|
window.BorealisValueBus = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global Update Rate (ms) for All Data Nodes
|
||||||
|
if (!window.BorealisUpdateRate) {
|
||||||
|
window.BorealisUpdateRate = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DataNode = ({ id, data }) => {
|
||||||
|
const { setNodes } = useReactFlow();
|
||||||
|
const edges = useStore(state => state.edges);
|
||||||
|
|
||||||
|
const [renderValue, setRenderValue] = useState(data?.value || "");
|
||||||
|
const valueRef = useRef(renderValue);
|
||||||
|
|
||||||
|
// Manual input handler (disabled if connected to input)
|
||||||
|
const handleManualInput = (e) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
|
||||||
|
// TODO: Add input validation/sanitization here if needed
|
||||||
|
valueRef.current = newValue;
|
||||||
|
setRenderValue(newValue);
|
||||||
|
|
||||||
|
window.BorealisValueBus[id] = newValue;
|
||||||
|
|
||||||
|
setNodes(nds =>
|
||||||
|
nds.map(n =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, value: newValue } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let currentRate = window.BorealisUpdateRate || 100;
|
||||||
|
let intervalId = null;
|
||||||
|
|
||||||
|
const runNodeLogic = () => {
|
||||||
|
const inputEdge = edges.find(e => e?.target === id);
|
||||||
|
const hasInput = Boolean(inputEdge);
|
||||||
|
|
||||||
|
if (hasInput && inputEdge.source) {
|
||||||
|
const upstreamValue = window.BorealisValueBus[inputEdge.source] ?? "";
|
||||||
|
|
||||||
|
// TODO: Insert custom transform logic here (e.g., parseInt, apply formula)
|
||||||
|
|
||||||
|
if (upstreamValue !== valueRef.current) {
|
||||||
|
valueRef.current = upstreamValue;
|
||||||
|
setRenderValue(upstreamValue);
|
||||||
|
window.BorealisValueBus[id] = upstreamValue;
|
||||||
|
|
||||||
|
setNodes(nds =>
|
||||||
|
nds.map(n =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, value: upstreamValue } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// OUTPUT BROADCAST: emits to downstream via shared memory
|
||||||
|
window.BorealisValueBus[id] = valueRef.current;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startInterval = () => {
|
||||||
|
intervalId = setInterval(runNodeLogic, currentRate);
|
||||||
|
};
|
||||||
|
|
||||||
|
startInterval();
|
||||||
|
|
||||||
|
// Monitor for global update rate changes
|
||||||
|
const monitor = setInterval(() => {
|
||||||
|
const newRate = window.BorealisUpdateRate || 100;
|
||||||
|
if (newRate !== currentRate) {
|
||||||
|
currentRate = newRate;
|
||||||
|
clearInterval(intervalId);
|
||||||
|
startInterval();
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
clearInterval(monitor);
|
||||||
|
};
|
||||||
|
}, [id, setNodes, edges]);
|
||||||
|
|
||||||
|
const inputEdge = edges.find(e => e?.target === id);
|
||||||
|
const hasInput = Boolean(inputEdge);
|
||||||
|
const upstreamId = inputEdge?.source || "";
|
||||||
|
const upstreamValue = window.BorealisValueBus[upstreamId] || "";
|
||||||
|
|
||||||
const dataNode = ({ data }) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="borealis-node">
|
<div className="borealis-node">
|
||||||
<Handle type="target" position={Position.Left} className="borealis-handle" />
|
<Handle type="target" position={Position.Left} className="borealis-handle" />
|
||||||
|
|
||||||
|
<div className="borealis-node-header">
|
||||||
|
{data?.label || "Data Node"}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="borealis-node-content">
|
||||||
|
{/* Description visible in node body */}
|
||||||
|
<div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}>
|
||||||
|
{data?.content || "Foundational node for live value propagation."}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
|
||||||
|
Value:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={renderValue}
|
||||||
|
onChange={handleManualInput}
|
||||||
|
disabled={hasInput}
|
||||||
|
style={{
|
||||||
|
fontSize: "9px",
|
||||||
|
padding: "4px",
|
||||||
|
background: hasInput ? "#2a2a2a" : "#1e1e1e",
|
||||||
|
color: "#ccc",
|
||||||
|
border: "1px solid #444",
|
||||||
|
borderRadius: "2px",
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||||
<div className="borealis-node-header">{data.label || "Data Node"}</div>
|
|
||||||
<div className="borealis-node-content">{data.content || "Placeholder Data Content"}</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
type: "dataNode",
|
type: "DataNode", // REQUIRED: unique identifier for the node type
|
||||||
label: "Data Node",
|
label: "Data Node",
|
||||||
description: "This node acts as a baseline foundational example for all nodes to follow.",
|
description: `
|
||||||
defaultContent: "Placeholder Node",
|
Foundational Data Node
|
||||||
component: dataNode
|
|
||||||
|
- Accepts input from another node
|
||||||
|
- If no input is connected, allows user-defined value
|
||||||
|
- Pushes value to downstream nodes every X ms
|
||||||
|
- Uses BorealisValueBus to communicate with other nodes
|
||||||
|
`.trim(),
|
||||||
|
content: "Store Strings, Ints, and Floats",
|
||||||
|
component: DataNode
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user