From de4e4ed95360a8731f9b3f1ac9cfc4f04901a265 Mon Sep 17 00:00:00 2001
From: Nicole Rappe <nicole.rappe@bunny-lab.io>
Date: Mon, 14 Apr 2025 19:08:45 -0600
Subject: [PATCH] Fixed BW Threshold Node Import Behavior

---
 Data/WebUI/src/Borealis.css                   |  44 +++
 .../Image Processing/Node_BW_Threshold.jsx    |  39 ++-
 .../Macro Automation/Node_Macro_KeyPress.jsx  | 295 ++++++++++++++++++
 Launch-Borealis.ps1                           |   4 +-
 4 files changed, 375 insertions(+), 7 deletions(-)
 create mode 100644 Data/WebUI/src/nodes/Macro Automation/Node_Macro_KeyPress.jsx

diff --git a/Data/WebUI/src/Borealis.css b/Data/WebUI/src/Borealis.css
index 45b4a8a..abe4fde 100644
--- a/Data/WebUI/src/Borealis.css
+++ b/Data/WebUI/src/Borealis.css
@@ -93,3 +93,47 @@ label {
 }
 
 /* We rely on the TabIndicatorProps to show the underline highlight for active tabs. */
+
+/* keyboard-dark-theme.css */
+
+/* react-simple-keyboard dark theming */
+.hg-theme-dark {
+    background-color: #1e1e1e;
+    border: 1px solid #444;
+}
+
+.hg-button {
+    background: #2c2c2c;
+    color: #ccc;
+    border: 1px solid #444;
+    font-size: 11px;
+}
+
+.hg-button:hover {
+    background: #58a6ff;
+    color: #000;
+    border-color: #58a6ff;
+}
+
+.borealis-keyboard .hg-button.hg-standardBtn {
+    border-radius: 3px;
+}
+
+/* Force rows to appear horizontally */
+.simple-keyboard-main .hg-row {
+    display: flex !important;
+    flex-flow: row wrap;
+    justify-content: center;
+  }
+
+/* Slight spacing around each key (optional) */
+.simple-keyboard-main .hg-row .hg-button {
+    margin: 3px !important;
+  }
+  
+  /* Keep the entire keyboard from shrinking or going vertical */
+  .simple-keyboard-main {
+    display: inline-block !important;
+    width: auto !important;
+    max-width: 1000px; /* or whatever max width you like */
+  }
\ No newline at end of file
diff --git a/Data/WebUI/src/nodes/Image Processing/Node_BW_Threshold.jsx b/Data/WebUI/src/nodes/Image Processing/Node_BW_Threshold.jsx
index a09d0f9..b2f983d 100644
--- a/Data/WebUI/src/nodes/Image Processing/Node_BW_Threshold.jsx	
+++ b/Data/WebUI/src/nodes/Image Processing/Node_BW_Threshold.jsx	
@@ -12,22 +12,45 @@ if (!window.BorealisUpdateRate) {
 
 const BWThresholdNode = ({ id, data }) => {
     const edges = useStore((state) => state.edges);
-    const [threshold, setThreshold] = useState(() => parseInt(data?.value, 10) || 128);
+
+    // Attempt to parse threshold from data.value (if present),
+    // otherwise default to 128.
+    const initial = parseInt(data?.value, 10);
+    const [threshold, setThreshold] = useState(
+        isNaN(initial) ? 128 : initial
+    );
+
     const [renderValue, setRenderValue] = useState("");
     const valueRef = useRef("");
     const lastUpstreamRef = useRef("");
 
+    // If the node is reimported and data.value changes externally,
+    // update the threshold accordingly.
+    useEffect(() => {
+        const newVal = parseInt(data?.value, 10);
+        if (!isNaN(newVal)) {
+            setThreshold(newVal);
+        }
+    }, [data?.value]);
+
     const handleThresholdInput = (e) => {
         let val = parseInt(e.target.value, 10);
-        if (isNaN(val)) val = 128;
+        if (isNaN(val)) {
+            val = 128;
+        }
         val = Math.max(0, Math.min(255, val));
 
+        // Keep the Node's data.value updated
+        data.value = val;
+
         setThreshold(val);
         window.BorealisValueBus[id] = val;
     };
 
     const applyThreshold = async (base64Data, cutoff) => {
-        if (!base64Data || typeof base64Data !== "string") return "";
+        if (!base64Data || typeof base64Data !== "string") {
+            return "";
+        }
 
         return new Promise((resolve) => {
             const img = new Image();
@@ -56,7 +79,7 @@ const BWThresholdNode = ({ id, data }) => {
             };
 
             img.onerror = () => resolve(base64Data);
-            img.src = `data:image/png;base64,${base64Data}`;
+            img.src = "data:image/png;base64," + base64Data;
         });
     };
 
@@ -105,10 +128,14 @@ const BWThresholdNode = ({ id, data }) => {
     // Reapply when threshold changes (even if image didn't)
     useEffect(() => {
         const inputEdge = edges.find(e => e.target === id);
-        if (!inputEdge?.source) return;
+        if (!inputEdge?.source) {
+            return;
+        }
 
         const upstreamValue = window.BorealisValueBus[inputEdge.source] ?? "";
-        if (!upstreamValue) return;
+        if (!upstreamValue) {
+            return;
+        }
 
         applyThreshold(upstreamValue, threshold).then((result) => {
             valueRef.current = result;
diff --git a/Data/WebUI/src/nodes/Macro Automation/Node_Macro_KeyPress.jsx b/Data/WebUI/src/nodes/Macro Automation/Node_Macro_KeyPress.jsx
new file mode 100644
index 0000000..00a958a
--- /dev/null
+++ b/Data/WebUI/src/nodes/Macro Automation/Node_Macro_KeyPress.jsx	
@@ -0,0 +1,295 @@
+import React, { useState, useRef } from "react";
+import { Handle, Position } from "reactflow";
+import Keyboard from "react-simple-keyboard";
+import "react-simple-keyboard/build/css/index.css";
+import "../../Borealis.css";
+
+if (!window.BorealisValueBus) window.BorealisValueBus = {};
+if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
+
+/**
+ * KeyPressNode:
+ * - Full keyboard with SHIFT toggling
+ * - Press F-keys, digits, letters, or symbols
+ * - Single key stored, overlay closes
+ * - SHIFT or CAPS toggles "default" <-> "shift"
+ */
+
+const KeyPressNode = ({ id, data }) => {
+  const [selectedWindow, setSelectedWindow] = useState(data?.selectedWindow || "");
+  const [keyPressed, setKeyPressed] = useState(data?.keyPressed || "");
+  const [intervalMs, setIntervalMs] = useState(data?.intervalMs || 1000);
+  const [randomRangeEnabled, setRandomRangeEnabled] = useState(false);
+  const [randomMin, setRandomMin] = useState(750);
+  const [randomMax, setRandomMax] = useState(950);
+
+  // Keyboard overlay
+  const [showKeyboard, setShowKeyboard] = useState(false);
+  const [layoutName, setLayoutName] = useState("default");
+
+  // A simple set of Windows for demonstration
+  const fakeWindows = ["Notepad", "Chrome", "Discord", "Visual Studio Code"];
+
+  // This function is triggered whenever the user taps a key on the virtual keyboard
+  const onKeyPress = (button) => {
+    // SHIFT or CAPS toggling:
+    if (button === "{shift}" || button === "{lock}") {
+      handleShift();
+      return;
+    }
+
+    // Example skip list: these won't be stored as final single key
+    const skipKeys = [
+      "{bksp}", "{space}", "{tab}", "{enter}", "{escape}",
+      "{f1}", "{f2}", "{f3}", "{f4}", "{f5}", "{f6}",
+      "{f7}", "{f8}", "{f9}", "{f10}", "{f11}", "{f12}",
+      "{shift}", "{lock}"
+    ];
+
+    // If the pressed button is not in skipKeys, let's store it and close
+    if (!skipKeys.includes(button)) {
+      setKeyPressed(button);
+      setShowKeyboard(false);
+    }
+  };
+
+  // Toggle between "default" layout and "shift" layout
+  const handleShift = () => {
+    setLayoutName((prev) => (prev === "default" ? "shift" : "default"));
+  };
+
+  return (
+    <div className="borealis-node" style={{ minWidth: 240, position: "relative" }}>
+      {/* React Flow Handles */}
+      <Handle type="target" position={Position.Left} className="borealis-handle" />
+      <Handle type="source" position={Position.Right} className="borealis-handle" />
+
+      {/* Node Header */}
+      <div className="borealis-node-header" style={{ position: "relative" }}>
+        Key Press
+        <div
+          style={{
+            position: "absolute",
+            top: "12px",
+            right: "6px",
+            width: "10px",
+            height: "10px",
+            borderRadius: "50%",
+            backgroundColor: "#333",
+            border: "1px solid #222"
+          }}
+        />
+      </div>
+
+      {/* Node Content */}
+      <div className="borealis-node-content">
+        <div style={{ fontSize: "9px", color: "#ccc", marginBottom: "8px" }}>
+          Sends keypress to selected window on trigger.
+        </div>
+
+        {/* Window Selector */}
+        <label>Window:</label>
+        <select
+          value={selectedWindow}
+          onChange={(e) => setSelectedWindow(e.target.value)}
+          style={inputStyle}
+        >
+          <option value="">-- Choose --</option>
+          {fakeWindows.map((win) => (
+            <option key={win} value={win}>
+              {win}
+            </option>
+          ))}
+        </select>
+
+        {/* Key: "Select Key" button & readOnly input */}
+        <label style={{ marginTop: "6px" }}>Key:</label>
+        <div style={{ display: "flex", gap: "6px", alignItems: "center", marginBottom: "6px" }}>
+          <button onClick={() => setShowKeyboard(true)} style={buttonStyle}>
+            Select Key
+          </button>
+          <input
+            type="text"
+            value={keyPressed}
+            disabled
+            readOnly
+            style={{
+              ...inputStyle,
+              width: "60px",
+              backgroundColor: "#2a2a2a",
+              color: "#aaa",
+              cursor: "default"
+            }}
+          />
+        </div>
+
+        {/* Interval Configuration */}
+        <label>Fixed Interval (ms):</label>
+        <input
+          type="number"
+          min="100"
+          step="50"
+          value={intervalMs}
+          onChange={(e) => setIntervalMs(Number(e.target.value))}
+          disabled={randomRangeEnabled}
+          style={{
+            ...inputStyle,
+            backgroundColor: randomRangeEnabled ? "#2a2a2a" : "#1e1e1e"
+          }}
+        />
+
+        {/* Random Interval */}
+        <label style={{ marginTop: "6px" }}>
+          <input
+            type="checkbox"
+            checked={randomRangeEnabled}
+            onChange={(e) => setRandomRangeEnabled(e.target.checked)}
+            style={{ marginRight: "6px" }}
+          />
+          Randomize Interval (ms):
+        </label>
+
+        {randomRangeEnabled && (
+          <div style={{ display: "flex", gap: "4px", marginTop: "4px" }}>
+            <input
+              type="number"
+              min="100"
+              value={randomMin}
+              onChange={(e) => setRandomMin(Number(e.target.value))}
+              style={{ ...inputStyle, flex: 1 }}
+            />
+            <input
+              type="number"
+              min="100"
+              value={randomMax}
+              onChange={(e) => setRandomMax(Number(e.target.value))}
+              style={{ ...inputStyle, flex: 1 }}
+            />
+          </div>
+        )}
+      </div>
+
+      {/* Keyboard Overlay */}
+      {showKeyboard && (
+        <div style={keyboardOverlay}>
+          <div style={keyboardContainer}>
+            <div
+              style={{
+                fontSize: "11px",
+                color: "#ccc",
+                marginBottom: "6px",
+                textAlign: "center"
+              }}
+            >
+              Full Keyboard
+            </div>
+            <Keyboard
+              onKeyPress={onKeyPress}
+              layoutName={layoutName}
+              theme="hg-theme-dark hg-layout-default"
+              layout={{
+                default: [
+                  "{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}",
+                  "` 1 2 3 4 5 6 7 8 9 0 - = {bksp}",
+                  "{tab} q w e r t y u i o p [ ] \\",
+                  "{lock} a s d f g h j k l ; ' {enter}",
+                  "{shift} z x c v b n m , . / {shift}",
+                  "{space}"
+                ],
+                shift: [
+                  "{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}",
+                  "~ ! @ # $ % ^ & * ( ) _ + {bksp}",
+                  "{tab} Q W E R T Y U I O P { } |",
+                  "{lock} A S D F G H J K L : \" {enter}",
+                  "{shift} Z X C V B N M < > ? {shift}",
+                  "{space}"
+                ]
+              }}
+              display={{
+                "{bksp}": "⌫",
+                "{escape}": "esc",
+                "{tab}": "tab",
+                "{lock}": "caps",
+                "{enter}": "enter",
+                "{shift}": "shift",
+                "{space}": "space",
+                "{f1}": "F1",
+                "{f2}": "F2",
+                "{f3}": "F3",
+                "{f4}": "F4",
+                "{f5}": "F5",
+                "{f6}": "F6",
+                "{f7}": "F7",
+                "{f8}": "F8",
+                "{f9}": "F9",
+                "{f10}": "F10",
+                "{f11}": "F11",
+                "{f12}": "F12"
+              }}
+            />
+            <div style={{ display: "flex", justifyContent: "center", marginTop: "8px" }}>
+              <button onClick={() => setShowKeyboard(false)} style={buttonStyle}>
+                Close
+              </button>
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+
+/* Basic styling objects */
+const inputStyle = {
+  width: "100%",
+  fontSize: "9px",
+  padding: "4px",
+  color: "#ccc",
+  border: "1px solid #444",
+  borderRadius: "2px",
+  marginBottom: "6px"
+};
+
+const buttonStyle = {
+  fontSize: "9px",
+  padding: "4px 8px",
+  backgroundColor: "#1e1e1e",
+  color: "#ccc",
+  border: "1px solid #444",
+  borderRadius: "2px",
+  cursor: "pointer"
+};
+
+const keyboardOverlay = {
+  position: "fixed",
+  top: 0,
+  left: 0,
+  width: "100vw",
+  height: "100vh",
+  zIndex: 1000,
+  backgroundColor: "rgba(0, 0, 0, 0.8)",
+  display: "flex",
+  justifyContent: "center",
+  alignItems: "center"
+};
+
+const keyboardContainer = {
+  backgroundColor: "#1e1e1e",
+  padding: "16px",
+  borderRadius: "6px",
+  border: "1px solid #444",
+  zIndex: 1001,
+  maxWidth: "650px"
+};
+
+export default {
+  type: "Macro_KeyPress",
+  label: "Key Press (GUI-ONLY)",
+  description: `
+Press a single character or function key on a full keyboard overlay.
+Shift/caps toggles uppercase/symbols. 
+F-keys are included, but pressing them won't store that value unless you remove them from "skip" logic, if desired.
+`,
+  content: "Send Key Press to Foreground Window via Borealis Agent",
+  component: KeyPressNode
+};
diff --git a/Launch-Borealis.ps1 b/Launch-Borealis.ps1
index f950e51..32134bf 100644
--- a/Launch-Borealis.ps1
+++ b/Launch-Borealis.ps1
@@ -137,7 +137,9 @@ switch ($choice) {
                 npm install --silent react-resizable --no-fund --audit=false | Out-Null
                 npm install --silent reactflow --no-fund --audit=false | Out-Null
                 npm install --silent @mui/material @mui/icons-material @emotion/react @emotion/styled --no-fund --audit=false 2>&1 | Out-Null
-                npm install  --silent socket.io-client --no-fund --audit=false | Out-Null
+                npm install --silent socket.io-client --no-fund --audit=false | Out-Null
+                npm install --silent react-simple-keyboard --no-fund --audit=false | Out-Null
+
                 Pop-Location
             }
         }