diff --git a/Data/Server/WebUI/src/Flow_Editor.jsx b/Data/Server/WebUI/src/Flow_Editor.jsx index 9e94f34..21a0225 100644 --- a/Data/Server/WebUI/src/Flow_Editor.jsx +++ b/Data/Server/WebUI/src/Flow_Editor.jsx @@ -53,8 +53,7 @@ export default function FlowEditor({ return () => { delete window.BorealisOpenDrawer; }; - }, []); - + }, [nodes]); const wrapperRef = useRef(null); const { project } = useReactFlow(); @@ -353,17 +352,25 @@ export default function FlowEditor({ if (nodeCountEl) nodeCountEl.innerText = nodes.length; }, [nodes]); + const selectedNode = nodes.find((n) => n.data?.label === selectedNodeLabel); + const nodeTypeMeta = selectedNode + ? Object.values(categorizedNodes).flat().find((def) => def.type === selectedNode.type) + : null; + return (
- + + + /Data/WebUI/src/Node_Configuration_Sidebar.jsx -import { Box, Typography } from "@mui/material"; +import { Box, Typography, Tabs, Tab, TextField } from "@mui/material"; +import React, { useState } from "react"; -export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, title }) { - return ( - <> - {/* Dim overlay when drawer is open */} - {drawerOpen && ( - setDrawerOpen(false)} - sx={{ - position: "absolute", - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: "rgba(0,0,0,0.4)", - zIndex: 10 +export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, title, nodeData }) { + const [activeTab, setActiveTab] = useState(0); + const handleTabChange = (_, newValue) => setActiveTab(newValue); + + const renderConfigFields = () => { + const config = nodeData?.config || []; + return config.map((field, index) => ( + + + {field.label || field.key} + + - )} + + )); + }; - {/* Right-Side Node Configuration Panel */} + return ( + <> setDrawerOpen(false)} sx={{ @@ -37,7 +49,6 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti }} /> - {/* Animated right drawer panel */} e.stopPropagation()} > - {/* Title bar section */} - - - {title || "Node Configuration Panel"} - + + + + {"Edit " + (title || "Node")} + + + + + + - {/* Content */} -

- This sidebar will be used to configure nodes in the future. -

-

- The idea is that this area will allow for more node configuration controls to be dynamically-populated by the nodes to allow more complex node documentation and configuration. -

+ {activeTab === 0 && renderConfigFields()} + {activeTab === 1 && ( + + {nodeData?.usage_documentation || "No documentation provided for this node."} + + )}
diff --git a/Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx b/Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx index 5f0323a..77156b3 100644 --- a/Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx +++ b/Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx @@ -1,213 +1,138 @@ ////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: /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"; import { IconButton } from "@mui/material"; import { Settings as SettingsIcon } from "@mui/icons-material"; -// 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; -} +if (!window.BorealisValueBus) window.BorealisValueBus = {}; +if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100; const DataNode = ({ id, data }) => { - const { setNodes } = useReactFlow(); - const edges = useStore(state => state.edges); + const { setNodes } = useReactFlow(); + const edges = useStore((state) => state.edges); + const [renderValue, setRenderValue] = useState(data?.value || ""); + const valueRef = useRef(renderValue); - const [renderValue, setRenderValue] = useState(data?.value || ""); - const valueRef = useRef(renderValue); + const handleManualInput = (e) => { + const newValue = e.target.value; + valueRef.current = newValue; + setRenderValue(newValue); + window.BorealisValueBus[id] = newValue; + setNodes((nds) => + nds.map((n) => + n.id === id ? { ...n, data: { ...n.data, value: newValue } } : n + ) + ); + }; - // Manual input handler (disabled if connected to input) - const handleManualInput = (e) => { - const newValue = e.target.value; + useEffect(() => { + let currentRate = window.BorealisUpdateRate || 100; + let intervalId = null; - // TODO: Add input validation/sanitization here if needed - valueRef.current = newValue; - setRenderValue(newValue); + const runNodeLogic = () => { + const inputEdge = edges.find((e) => e?.target === id); + const hasInput = Boolean(inputEdge?.source); - window.BorealisValueBus[id] = newValue; - - setNodes(nds => - nds.map(n => - n.id === id - ? { ...n, data: { ...n.data, value: newValue } } - : n + if (hasInput) { + const upstreamValue = window.BorealisValueBus[inputEdge.source] ?? ""; + 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 { + window.BorealisValueBus[id] = valueRef.current; + } }; - useEffect(() => { - let currentRate = window.BorealisUpdateRate || 100; - let intervalId = null; + intervalId = setInterval(runNodeLogic, currentRate); + const monitor = setInterval(() => { + const newRate = window.BorealisUpdateRate || 100; + if (newRate !== currentRate) { + clearInterval(intervalId); + currentRate = newRate; + intervalId = setInterval(runNodeLogic, currentRate); + } + }, 250); - const runNodeLogic = () => { - const inputEdge = edges.find(e => e?.target === id); - const hasInput = Boolean(inputEdge); + return () => { + clearInterval(intervalId); + clearInterval(monitor); + }; + }, [id, setNodes, edges]); - if (hasInput && inputEdge.source) { - const upstreamValue = window.BorealisValueBus[inputEdge.source] ?? ""; + const inputEdge = edges.find((e) => e?.target === id); + const hasInput = Boolean(inputEdge); - // 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 ( -
- - -
- {data?.label || "Data Node"} - - window.BorealisOpenDrawer && - window.BorealisOpenDrawer(data?.label || "Unknown Node") - } - sx={{ - padding: "0px", - marginRight: "-3px", - color: "#58a6ff", - fontSize: "14px", // affects inner icon when no size prop - width: "20px", - height: "20px", - minWidth: "20px" - }} - > - - -
- - -
- {/* Description visible in node body */} -
- {data?.content || "Foundational node for live value propagation."} -
- - - -
- - -
- ); + return ( +
+ +
+ {data?.label || "Data Node"} + + window.BorealisOpenDrawer && + window.BorealisOpenDrawer(data?.label || "Unknown Node", data) + } + sx={{ padding: 0, marginRight: "-3px", color: "#58a6ff", width: "20px", height: "20px" }} + > + + +
+
+
{data?.content}
+ + +
+ +
+ ); }; export default { - type: "DataNode", // REQUIRED: unique identifier for the node type - label: "String / Number", - description: ` -Foundational Data Node + type: "DataNode", + label: "String / Number", + description: "Foundational node for live value propagation.\n\n- Accepts input or manual value\n- Pushes downstream\n- Uses shared memory", + content: "Store a String or Number", + component: DataNode, + config: [ + { key: "value", label: "Initial Value", type: "text" } + ], + usage_documentation: ` +### DataNode Usage -- 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 +This node acts as a live data emitter. When connected to an upstream source, it inherits its value. Otherwise, it accepts user-defined input. + +- **Use Cases**: + - Static constants + - Pass-through conduit + - Manual value input + +- **Behavior**: + - Automatically updates on interval + - Emits data through BorealisValueBus + +Ensure no input edge if manual input is required. + `.trim() };