From 31ddbb369037c0c920a050826249e7f4f0254169 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Fri, 16 May 2025 14:50:05 -0600 Subject: [PATCH] Add Creating a Node --- Creating-a-Node.md | 217 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 Creating-a-Node.md diff --git a/Creating-a-Node.md b/Creating-a-Node.md new file mode 100644 index 0000000..4ba45f4 --- /dev/null +++ b/Creating-a-Node.md @@ -0,0 +1,217 @@ +# πŸ“¦ How to Create a Custom Node in Borealis + +This guide explains **how to build your own node** for the Borealis workflow automation tool. It walks through the key structure, best practices, and configuration options. You’ll find example code, inline commentary, and a copy-paste template at the bottom. + +--- + +## 1. πŸ“š Node Anatomy: The Big Picture + +Every Borealis node is a **React component** paired with an exported metadata object. Nodes communicate via the **global `window.BorealisValueBus`** (shared memory), and can specify configurable fields for the sidebar via a `config` array. + +**A node must:** + +* Export an object with keys: `type`, `label`, `description`, `content`, and `component` +* Implement a React component that uses ReactFlow handles and the value bus +* (Optionally) add a `config` array for sidebar-driven configuration +* (Optionally) provide Markdown documentation in `usage_documentation` + +--- + +## 2. πŸ—οΈ General Node File Structure + +```js +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"; + +// Node Component +const MyNode = ({ id, data }) => { + // ...state and logic... + return ( +
+ + {/* ...UI... */} + +
+ ); +}; + +// Export node metadata object +export default { + type: "MyNodeType", + label: "My Node", + description: "What this node does...", + content: "Short content summary...", + component: MyNode, + // Optional for Node Configuration Sidebar: + config: [ /* ... */ ], + usage_documentation: `...` +}; +``` + +--- + +## 3. βš™οΈ Node Configuration Sidebar (`config`) + +To enable sidebar configuration, **add a `config` array** to your export object: + +```js +config: [ + { key: "value", label: "Value", type: "text" }, + { + key: "operator", + label: "Operator", + type: "select", + options: ["Add", "Subtract", "Multiply", "Divide", "Average"] + } +] +``` + +**Supported field types:** + +* `text` – simple input +* `select` – dropdown (must specify `options`) +* More can be added as needed + +**Each config field is automatically rendered in the sidebar** when a node is selected. + +--- + +## 4. πŸ§‘β€πŸŽ¨ Tips for Sidebar-Configurable Nodes + +* **Use `config` for all user-tunable parameters.** +* **Keys in `config` must match data keys in your node’s `data` prop.** +* **Sidebar will auto-update and write changes to `window.BorealisValueBus` and node state.** +* **Add usage documentation in `usage_documentation` for help inside the editor.** + +--- + +## 5. 🧩 Copy-Paste Node Template + +```js +import React, { useEffect, useState, useRef } from "react"; +import { Handle, Position, useReactFlow, useStore } from "reactflow"; + +/** + * TemplateNode Component + */ +const TemplateNode = ({ id, data }) => { + const { setNodes } = useReactFlow(); + const edges = useStore((state) => state.edges); + const [renderValue, setRenderValue] = useState(data?.value || ""); + const valueRef = useRef(renderValue); + + // Manual input handler (when no upstream) + 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 + ) + ); + }; + + useEffect(() => { + let currentRate = window.BorealisUpdateRate; + let intervalId; + const runNodeLogic = () => { + const inputEdge = edges.find((e) => e.target === id); + const hasInput = Boolean(inputEdge && inputEdge.source); + 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; + } + }; + 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, setNodes]); + + const inputEdge = edges.find((e) => e.target === id); + const hasInput = Boolean(inputEdge && inputEdge.source); + + return ( +
+ +
+ {data?.label || "Node Template"} +
+
+
+ {data?.content || "Template acting as a design scaffold for designing nodes for Borealis."} +
+ + +
+ +
+ ); +}; + +export default { + type: "Node_Template", + label: "Node Template", + description: `Node structure template to be used as a scaffold when building new nodes for Borealis.`, + content: "Template acting as a design scaffold for designing nodes for Borealis.", + component: TemplateNode, + config: [ + { key: "value", label: "Value", type: "text" } + ], + usage_documentation: ` +### Node Template + +A reference implementation of a simple, single-value node for Borealis. + +- Accepts manual or upstream input +- Configurable via sidebar +- Uses the value bus for live updates +`.trim() +}; +``` + +--- + +**Happy Node Building!** + +If you need help, check out the sample nodes and open a PR or issue with your questions.