Overhauled Deployment Structure

This commit is contained in:
2025-04-19 16:01:12 -06:00
parent 2dc79a03ad
commit 72f919b971
151 changed files with 13 additions and 9 deletions

View File

@ -0,0 +1,192 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Data.jsx
/**
* ============================================
* 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] || "";
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>
);
};
export default {
type: "DataNode", // REQUIRED: unique identifier for the node type
label: "String / Number",
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 a String or Number",
component: DataNode
};

View File

@ -0,0 +1,175 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Logical_Operators.jsx
/**
* ==============================================
* Borealis - Comparison Node (Logic Evaluation)
* ==============================================
*
* COMPONENT ROLE:
* This node takes two input values and evaluates them using a selected comparison operator.
* It returns 1 (true) or 0 (false) depending on the result of the comparison.
*
* FEATURES:
* - Dropdown to select input type: "Number" or "String"
* - Dropdown to select comparison operator: ==, !=, >, <, >=, <=
* - Dynamically disables numeric-only operators for string inputs
* - Automatically resets operator to == when switching to String
* - Supports summing multiple inputs per side (A, B)
* - For "String" mode: concatenates inputs in connection order
* - Uses BorealisValueBus for input/output
* - Controlled by global update timer
*
* STRUCTURE:
* - Label and Description
* - Input A (top-left) and Input B (middle-left)
* - Output (right edge) result: 1 (true) or 0 (false)
* - Operator dropdown and Input Type dropdown
*/
import React, { useEffect, useRef, useState } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
if (!window.BorealisValueBus) window.BorealisValueBus = {};
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
const ComparisonNode = ({ id, data }) => {
const { setNodes } = useReactFlow();
const edges = useStore(state => state.edges);
const [inputType, setInputType] = useState(data?.inputType || "Number");
const [operator, setOperator] = useState(data?.operator || "Equal (==)");
const [renderValue, setRenderValue] = useState("0");
const valueRef = useRef("0");
useEffect(() => {
if (inputType === "String" && !["Equal (==)", "Not Equal (!=)"].includes(operator)) {
setOperator("Equal (==)");
}
}, [inputType]);
useEffect(() => {
let currentRate = window.BorealisUpdateRate;
let intervalId = null;
const runNodeLogic = () => {
const edgeInputsA = edges.filter(e => e?.target === id && e.targetHandle === "a");
const edgeInputsB = edges.filter(e => e?.target === id && e.targetHandle === "b");
const extractValues = (edgeList) => {
const values = edgeList.map(e => window.BorealisValueBus[e.source]).filter(v => v !== undefined);
if (inputType === "Number") {
return values.reduce((sum, v) => sum + (parseFloat(v) || 0), 0);
}
return values.join("");
};
const a = extractValues(edgeInputsA);
const b = extractValues(edgeInputsB);
const resultMap = {
"Equal (==)": a === b,
"Not Equal (!=)": a !== b,
"Greater Than (>)": a > b,
"Less Than (<)": a < b,
"Greater Than or Equal (>=)": a >= b,
"Less Than or Equal (<=)": a <= b
};
const result = resultMap[operator] ? "1" : "0";
valueRef.current = result;
setRenderValue(result);
window.BorealisValueBus[id] = result;
setNodes(nds =>
nds.map(n =>
n.id === id ? { ...n, data: { ...n.data, value: result, inputType, operator } } : n
)
);
};
intervalId = setInterval(runNodeLogic, currentRate);
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate;
if (newRate !== currentRate) {
clearInterval(intervalId);
currentRate = newRate;
intervalId = setInterval(runNodeLogic, currentRate);
}
}, 250);
return () => {
clearInterval(intervalId);
clearInterval(monitor);
};
}, [id, edges, inputType, operator, setNodes]);
return (
<div className="borealis-node">
<div style={{ position: "absolute", left: -16, top: 12, fontSize: "8px", color: "#ccc" }}>A</div>
<div style={{ position: "absolute", left: -16, top: 50, fontSize: "8px", color: "#ccc" }}>B</div>
<Handle type="target" position={Position.Left} id="a" style={{ top: 12 }} className="borealis-handle" />
<Handle type="target" position={Position.Left} id="b" style={{ top: 50 }} className="borealis-handle" />
<div className="borealis-node-header">
{data?.label || "Comparison Node"}
</div>
<div className="borealis-node-content">
<div style={{ marginBottom: "6px", fontSize: "9px", color: "#ccc" }}>
{data?.content || "Evaluates A vs B and outputs 1 (true) or 0 (false)."}
</div>
<label style={{ fontSize: "9px" }}>Input Type:</label>
<select value={inputType} onChange={(e) => setInputType(e.target.value)} style={dropdownStyle}>
<option value="Number">Number</option>
<option value="String">String</option>
</select>
<label style={{ fontSize: "9px", marginTop: "6px" }}>Operator:</label>
<select value={operator} onChange={(e) => setOperator(e.target.value)} style={dropdownStyle}>
<option>Equal (==)</option>
<option>Not Equal (!=)</option>
<option disabled={inputType === "String"}>Greater Than (&gt;)</option>
<option disabled={inputType === "String"}>Less Than (&lt;)</option>
<option disabled={inputType === "String"}>Greater Than or Equal (&gt;=)</option>
<option disabled={inputType === "String"}>Less Than or Equal (&lt;=)</option>
</select>
<div style={{ marginTop: "8px", fontSize: "9px" }}>Result: {renderValue}</div>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
const dropdownStyle = {
width: "100%",
fontSize: "9px",
padding: "4px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
marginBottom: "4px"
};
export default {
type: "ComparisonNode",
label: "Logic Comparison",
description: `
Compare Two Inputs (A vs B)
- Uses configurable operator
- Supports numeric and string comparison
- Aggregates multiple inputs by summing (Number) or joining (String in connection order)
- Only == and != are valid for String mode
- Automatically resets operator when switching to String mode
- Outputs 1 (true) or 0 (false) into BorealisValueBus
- Live-updates based on global timer
`.trim(),
content: "Compare A and B using Logic",
component: ComparisonNode
};

View File

@ -0,0 +1,179 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Math_Operations.jsx
/**
* ============================================
* Borealis - Math Operation Node (Multi-Input A/B)
* ============================================
*
* COMPONENT ROLE:
* Performs live math operations on *two grouped input sets* (A and B).
*
* FUNCTIONALITY:
* - Inputs connected to Handle A are summed
* - Inputs connected to Handle B are summed
* - Math operation is applied as: A &lt;operator&gt; B
* - Result pushed via BorealisValueBus[id]
*
* SUPPORTED OPERATORS:
* - Add, Subtract, Multiply, Divide, Average
*/
import React, { useEffect, useRef, useState } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
if (!window.BorealisValueBus) window.BorealisValueBus = {};
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
const MathNode = ({ id, data }) => {
const { setNodes } = useReactFlow();
const edges = useStore(state => state.edges);
const [operator, setOperator] = useState(data?.operator || "Add");
const [result, setResult] = useState("0");
const resultRef = useRef(0);
useEffect(() => {
let intervalId = null;
let currentRate = window.BorealisUpdateRate;
const runLogic = () => {
const inputsA = edges.filter(e => e.target === id && e.targetHandle === "a");
const inputsB = edges.filter(e => e.target === id && e.targetHandle === "b");
const sum = (list) =>
list.map(e => parseFloat(window.BorealisValueBus[e.source]) || 0).reduce((a, b) => a + b, 0);
const valA = sum(inputsA);
const valB = sum(inputsB);
let value = 0;
switch (operator) {
case "Add":
value = valA + valB;
break;
case "Subtract":
value = valA - valB;
break;
case "Multiply":
value = valA * valB;
break;
case "Divide":
value = valB !== 0 ? valA / valB : 0;
break;
case "Average":
const totalInputs = inputsA.length + inputsB.length;
const totalSum = valA + valB;
value = totalInputs > 0 ? totalSum / totalInputs : 0;
break;
}
resultRef.current = value;
setResult(value.toString());
window.BorealisValueBus[id] = value.toString();
setNodes(nds =>
nds.map(n =>
n.id === id ? { ...n, data: { ...n.data, operator, value: value.toString() } } : n
)
);
};
intervalId = setInterval(runLogic, currentRate);
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate;
if (newRate !== currentRate) {
clearInterval(intervalId);
currentRate = newRate;
intervalId = setInterval(runLogic, currentRate);
}
}, 250);
return () => {
clearInterval(intervalId);
clearInterval(monitor);
};
}, [id, operator, edges, setNodes]);
return (
<div className="borealis-node">
<div style={{ position: "absolute", left: -16, top: 12, fontSize: "8px", color: "#ccc" }}>A</div>
<div style={{ position: "absolute", left: -16, top: 50, fontSize: "8px", color: "#ccc" }}>B</div>
<Handle type="target" position={Position.Left} id="a" style={{ top: 12 }} className="borealis-handle" />
<Handle type="target" position={Position.Left} id="b" style={{ top: 50 }} className="borealis-handle" />
<div className="borealis-node-header">
{data?.label || "Math Operation"}
</div>
<div className="borealis-node-content">
<div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}>
Aggregates A and B inputs then performs operation.
</div>
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
Operator:
</label>
<select
value={operator}
onChange={(e) => setOperator(e.target.value)}
style={dropdownStyle}
>
<option value="Add">Add</option>
<option value="Subtract">Subtract</option>
<option value="Multiply">Multiply</option>
<option value="Divide">Divide</option>
<option value="Average">Average</option>
</select>
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
Result:
</label>
<input
type="text"
value={result}
disabled
style={resultBoxStyle}
/>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
const dropdownStyle = {
fontSize: "9px",
padding: "4px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
width: "100%",
marginBottom: "8px"
};
const resultBoxStyle = {
fontSize: "9px",
padding: "4px",
background: "#2a2a2a",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
width: "100%"
};
export default {
type: "MathNode",
label: "Math Operation",
description: `
Perform Math on Aggregated Inputs
- A and B groups are independently summed
- Performs: Add, Subtract, Multiply, Divide, or Average
- Result = A <op> B
- Emits result via BorealisValueBus every update tick
`.trim(),
content: "Perform Math Operations",
component: MathNode
};