From e35495c7e3e6b9a83eec189b55b468f53954b148 Mon Sep 17 00:00:00 2001
From: Nicole Rappe <nicole.rappe@bunny-lab.io>
Date: Thu, 15 May 2025 01:52:10 -0600
Subject: [PATCH] Configuration Panel Milestone 5

---
 .../WebUI/src/Node_Configuration_Sidebar.jsx  | 171 ++++++++---
 .../src/nodes/General Purpose/Node_Data.jsx   |  22 +-
 .../Node_Logical_Operators.jsx                | 278 +++++++++---------
 3 files changed, 280 insertions(+), 191 deletions(-)

diff --git a/Data/Server/WebUI/src/Node_Configuration_Sidebar.jsx b/Data/Server/WebUI/src/Node_Configuration_Sidebar.jsx
index 55a48d4..db619dd 100644
--- a/Data/Server/WebUI/src/Node_Configuration_Sidebar.jsx
+++ b/Data/Server/WebUI/src/Node_Configuration_Sidebar.jsx
@@ -1,5 +1,5 @@
 ////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/Node_Configuration_Sidebar.jsx
-import { Box, Typography, Tabs, Tab, TextField } from "@mui/material";
+import { Box, Typography, Tabs, Tab, TextField, MenuItem } from "@mui/material";
 import React, { useState } from "react";
 import { useReactFlow } from "reactflow";
 import ReactMarkdown from "react-markdown"; // Used for Node Usage Documentation
@@ -13,39 +13,117 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti
     const config = nodeData?.config || [];
     const nodeId = nodeData?.nodeId;
 
-    return config.map((field, index) => (
-      <Box key={index} sx={{ mb: 2 }}>
-        <Typography variant="body2" sx={{ color: "#ccc", mb: 0.5 }}>
-          {field.label || field.key}
-        </Typography>
-        <TextField
-          variant="outlined"
-          size="small"
-          fullWidth
-          value={nodeData?.[field.key] || ""}
-          onChange={(e) => {
-            const newValue = e.target.value;
-            if (!nodeId) return;
-            setNodes((nds) =>
-              nds.map((n) =>
-                n.id === nodeId
-                  ? { ...n, data: { ...n.data, [field.key]: newValue } }
-                  : n
-              )
-            );
-            window.BorealisValueBus[nodeId] = newValue;
-          }}
-          InputProps={{
-            sx: {
-              backgroundColor: "#1e1e1e",
-              color: "#ccc",
-              "& fieldset": { borderColor: "#444" },
-              "&:hover fieldset": { borderColor: "#666" }
-            }
-          }}
-        />
-      </Box>
-    ));
+    return config.map((field, index) => {
+      const value = nodeData?.[field.key] || "";
+
+      return (
+        <Box key={index} sx={{ mb: 2 }}>
+          <Typography variant="body2" sx={{ color: "#ccc", mb: 0.5 }}>
+            {field.label || field.key}
+          </Typography>
+
+          {field.type === "select" ? (
+            <TextField
+              select
+              fullWidth
+              size="small"
+              value={value}
+              onChange={(e) => {
+                const newValue = e.target.value;
+                if (!nodeId) return;
+                setNodes((nds) =>
+                  nds.map((n) =>
+                    n.id === nodeId
+                      ? { ...n, data: { ...n.data, [field.key]: newValue } }
+                      : n
+                  )
+                );
+                window.BorealisValueBus[nodeId] = newValue;
+              }}
+              SelectProps={{
+                MenuProps: {
+                  PaperProps: {
+                    sx: {
+                      bgcolor: "#1e1e1e",
+                      color: "#ccc",
+                      border: "1px solid #58a6ff",
+                      "& .MuiMenuItem-root": {
+                        color: "#ccc",
+                        fontSize: "0.85rem",
+                        "&:hover": {
+                          backgroundColor: "#2a2a2a"
+                        },
+                        "&.Mui-selected": {
+                          backgroundColor: "#2c2c2c !important",
+                          color: "#58a6ff"
+                        },
+                        "&.Mui-selected:hover": {
+                          backgroundColor: "#2a2a2a !important"
+                        }
+                      }
+                    }
+                  }
+                }
+              }}
+              sx={{
+                "& .MuiOutlinedInput-root": {
+                  backgroundColor: "#1e1e1e",
+                  color: "#ccc",
+                  fontSize: "0.85rem",
+                  "& fieldset": {
+                    borderColor: "#444"
+                  },
+                  "&:hover fieldset": {
+                    borderColor: "#58a6ff"
+                  },
+                  "&.Mui-focused fieldset": {
+                    borderColor: "#58a6ff"
+                  }
+                },
+                "& .MuiSelect-select": {
+                  backgroundColor: "#1e1e1e"
+                }
+              }}
+            >
+              {(field.options || []).map((opt, idx) => (
+                <MenuItem key={idx} value={opt}>
+                  {opt}
+                </MenuItem>
+              ))}
+            </TextField>
+
+          ) : (
+            <TextField
+              variant="outlined"
+              size="small"
+              fullWidth
+              value={value}
+              onChange={(e) => {
+                const newValue = e.target.value;
+                if (!nodeId) return;
+                setNodes((nds) =>
+                  nds.map((n) =>
+                    n.id === nodeId
+                      ? { ...n, data: { ...n.data, [field.key]: newValue } }
+                      : n
+                  )
+                );
+                window.BorealisValueBus[nodeId] = newValue;
+              }}
+              InputProps={{
+                sx: {
+                  backgroundColor: "#1e1e1e",
+                  color: "#ccc",
+                  "& fieldset": { borderColor: "#444" },
+                  "&:hover fieldset": { borderColor: "#666" },
+                  "&.Mui-focused fieldset": { borderColor: "#58a6ff" }
+                }
+              }}
+            />
+          )}
+        </Box>
+      );
+    });
   };
 
   return (
@@ -95,7 +173,7 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti
             onChange={handleTabChange}
             variant="fullWidth"
             textColor="inherit"
-            TabIndicatorProps={{ style: { backgroundColor: "#58a6ff" } }}
+            TabIndicatorProps={{ style: { backgroundColor: "#ccc" } }}
             sx={{
               borderTop: "1px solid #333",
               borderBottom: "1px solid #333",
@@ -103,8 +181,27 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti
               height: "36px"
             }}
           >
-            <Tab label="Config" sx={{ color: "#ccc", minHeight: "36px", height: "36px", textTransform: "none" }} />
-            <Tab label="Usage Docs" sx={{ color: "#ccc", minHeight: "36px", height: "36px", textTransform: "none" }} />
+            <Tab
+              label="Config"
+              sx={{
+                color: "#ccc",
+                "&.Mui-selected": { color: "#ccc" },
+                minHeight: "36px",
+                height: "36px",
+                textTransform: "none"
+              }}
+            />
+            <Tab
+              label="Usage Docs"
+              sx={{
+                color: "#ccc",
+                "&.Mui-selected": { color: "#ccc" },
+                minHeight: "36px",
+                height: "36px",
+                textTransform: "none"
+              }}
+            />
+
           </Tabs>
         </Box>
 
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 77da697..df2426c 100644
--- a/Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx	
+++ b/Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx	
@@ -1,5 +1,5 @@
 ////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Data.jsx
-import React, { useEffect, useRef } from "react";
+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";
@@ -10,10 +10,12 @@ if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
 const DataNode = ({ id, data }) => {
   const { setNodes } = useReactFlow();
   const edges = useStore((state) => state.edges);
-  const valueRef = useRef(data?.value || "");
+  const [renderValue, setRenderValue] = useState(data?.value || "");
+  const valueRef = useRef(renderValue);
 
   useEffect(() => {
     valueRef.current = data?.value || "";
+    setRenderValue(valueRef.current);
     window.BorealisValueBus[id] = valueRef.current;
   }, [data?.value, id]);
 
@@ -29,6 +31,7 @@ const DataNode = ({ id, data }) => {
         const upstreamValue = window.BorealisValueBus[inputEdge.source] ?? "";
         if (upstreamValue !== valueRef.current) {
           valueRef.current = upstreamValue;
+          setRenderValue(upstreamValue);
           window.BorealisValueBus[id] = upstreamValue;
           setNodes((nds) =>
             nds.map((n) =>
@@ -60,19 +63,22 @@ const DataNode = ({ id, data }) => {
   return (
     <div className="borealis-node">
       <Handle type="target" position={Position.Left} className="borealis-handle" />
+
       <div className="borealis-node-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
         <span>{data?.label || "Data Node"}</span>
         <IconButton
           size="small"
-          onClick={() =>
-            window.BorealisOpenDrawer &&
-            window.BorealisOpenDrawer(id, { ...data, nodeId: id })
-          }
-          sx={{ padding: 0, marginRight: "-3px", color: "#58a6ff", width: "20px", height: "20px" }}
+          onClick={() => window.BorealisOpenDrawer && window.BorealisOpenDrawer(id, { ...data, nodeId: id })}
+          sx={{ color: "#888", padding: 0, marginLeft: "auto" }}
         >
-          <SettingsIcon sx={{ fontSize: "16px" }} />
+          <SettingsIcon sx={{ fontSize: 16 }} />
         </IconButton>
       </div>
+
+      <div className="borealis-node-content" style={{ fontSize: "9px", color: "#ccc", marginTop: 4 }}>
+        Value: {renderValue}
+      </div>
+
       <Handle type="source" position={Position.Right} className="borealis-handle" />
     </div>
   );
diff --git a/Data/Server/WebUI/src/nodes/General Purpose/Node_Logical_Operators.jsx b/Data/Server/WebUI/src/nodes/General Purpose/Node_Logical_Operators.jsx
index d36106e..3497609 100644
--- a/Data/Server/WebUI/src/nodes/General Purpose/Node_Logical_Operators.jsx	
+++ b/Data/Server/WebUI/src/nodes/General Purpose/Node_Logical_Operators.jsx	
@@ -1,175 +1,161 @@
 ////////// 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";
+import { IconButton } from "@mui/material";
+import SettingsIcon from "@mui/icons-material/Settings";
 
 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 { setNodes } = useReactFlow();
+  const edges = useStore(state => state.edges);
+  const [renderValue, setRenderValue] = useState("0");
+  const valueRef = useRef("0");
 
-    const [inputType, setInputType] = useState(data?.inputType || "Number");
-    const [operator, setOperator] = useState(data?.operator || "Equal (==)");
-    const [renderValue, setRenderValue] = useState("0");
-    const valueRef = useRef("0");
+  useEffect(() => {
+    let currentRate = window.BorealisUpdateRate;
+    let intervalId = null;
 
-    useEffect(() => {
-        if (inputType === "String" && !["Equal (==)", "Not Equal (!=)"].includes(operator)) {
-            setOperator("Equal (==)");
+    const runNodeLogic = () => {
+      let inputType = data?.inputType || "Number";
+      let operator = data?.operator || "Equal (==)";
+
+      if (inputType === "String" && !["Equal (==)", "Not Equal (!=)"].includes(operator)) {
+        operator = "Equal (==)";
+        setNodes(nds =>
+          nds.map(n =>
+            n.id === id ? { ...n, data: { ...n.data, operator } } : n
+          )
+        );
+      }
+
+      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);
         }
-    }, [inputType]);
+        return values.join("");
+      };
 
-    useEffect(() => {
-        let currentRate = window.BorealisUpdateRate;
-        let intervalId = null;
+      const a = extractValues(edgeInputsA);
+      const b = extractValues(edgeInputsB);
 
-        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 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 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 result = resultMap[operator] ? "1" : "0";
 
-            const a = extractValues(edgeInputsA);
-            const b = extractValues(edgeInputsB);
+      valueRef.current = result;
+      setRenderValue(result);
+      window.BorealisValueBus[id] = result;
 
-            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
-            };
+      setNodes(nds =>
+        nds.map(n =>
+          n.id === id ? { ...n, data: { ...n.data, value: result } } : n
+        )
+      );
+    };
 
-            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);
 
-        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, data?.inputType, data?.operator, setNodes]);
 
-        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" />
 
-    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" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
+        <span>{data?.label || "Logic Comparison"}</span>
+        <IconButton
+          size="small"
+          onClick={() => window.BorealisOpenDrawer(id, { ...data, nodeId: id })}
+          sx={{ color: "#888", padding: 0, marginLeft: "auto" }}
+        >
+          <SettingsIcon sx={{ fontSize: 16 }} />
+        </IconButton>
+      </div>
 
-            <div className="borealis-node-header">
-                {data?.label || "Comparison Node"}
-            </div>
+      <div className="borealis-node-content" style={{ fontSize: "9px", color: "#ccc", marginTop: 4 }}>
+        Result: {renderValue}
+      </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"
+      <Handle type="source" position={Position.Right} className="borealis-handle" />
+    </div>
+  );
 };
 
 export default {
-    type: "ComparisonNode",
-    label: "Logic Comparison",
-    description: `
-Compare Two Inputs (A vs B)
+  type: "ComparisonNode",
+  label: "Logic Comparison",
+  description: "Compare A vs B using logic operators",
+  content: "Compare A and B using Logic",
+  component: ComparisonNode,
+  config: [
+    {
+      key: "inputType",
+      label: "Input Type",
+      type: "select",
+      options: ["Number", "String"]
+    },
+    {
+      key: "operator",
+      label: "Operator",
+      type: "select",
+      options: [
+        "Equal (==)",
+        "Not Equal (!=)",
+        "Greater Than (>)",
+        "Less Than (<)",
+        "Greater Than or Equal (>=)",
+        "Less Than or Equal (<=)"
+      ]
+    }
+  ],
+  usage_documentation: `
+### Logic Comparison Node
 
-- 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
+This node compares two inputs (A and B) using the selected operator.
+
+**Modes:**
+- **Number**: Sums all connected inputs and compares.
+- **String**: Concatenates all inputs for comparison.
+  - Only **Equal (==)** and **Not Equal (!=)** are valid for strings.
+
+**Output:**
+- Returns \`1\` if comparison is true.
+- Returns \`0\` if comparison is false.
+
+**Input Notes:**
+- A and B can each have multiple inputs.
+- Input order matters for strings (concatenation).
+- Input handles:
+  - **A** = Top left
+  - **B** = Middle left
+`.trim()
 };