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:
Nicole Rappe 2025-04-01 20:53:16 -06:00
parent feed26da98
commit d46c6ecc3c
3 changed files with 219 additions and 33 deletions

View File

@ -10,34 +10,12 @@
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

@ -54,6 +54,11 @@ import ReactFlow, {
import "reactflow/dist/style.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 nodeTypes = {};
const categorizedNodes = {};
@ -413,8 +418,42 @@ export default function App() {
</Box>
</Box>
<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
<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>
<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>

View File

@ -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 (
<div className="borealis-node">
<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" />
<div className="borealis-node-header">{data.label || "Data Node"}</div>
<div className="borealis-node-content">{data.content || "Placeholder Data Content"}</div>
</div>
);
};
export default {
type: "dataNode",
type: "DataNode", // REQUIRED: unique identifier for the node type
label: "Data Node",
description: "This node acts as a baseline foundational example for all nodes to follow.",
defaultContent: "Placeholder Node",
component: dataNode
description: `
Foundational Data Node
- 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
};