Add Creating a Node

2025-05-16 14:50:05 -06:00
parent ed188d2aef
commit 31ddbb3690

217
Creating-a-Node.md Normal file

@ -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. Youll 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 (
<div className="borealis-node">
<Handle type="target" position={Position.Left} className="borealis-handle" />
{/* ...UI... */}
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
// 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 nodes `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 (
<div className="borealis-node">
<Handle type="target" position={Position.Left} className="borealis-handle" />
<div className="borealis-node-header">
{data?.label || "Node Template"}
</div>
<div className="borealis-node-content">
<div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}>
{data?.content || "Template acting as a design scaffold for designing nodes for Borealis."}
</div>
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
Template Location:
</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: "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.