From cfd64ddd02276dc1d5e6b6c38fdd1acdef321c3e Mon Sep 17 00:00:00 2001 From: Jeremy Hayes Date: Fri, 5 Jun 2026 11:13:33 -0500 Subject: [PATCH] Inital commit --- Readme.md | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ app.js | 30 +++++++++ 2 files changed, 221 insertions(+) create mode 100644 Readme.md create mode 100644 app.js diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..9f0c0dc --- /dev/null +++ b/Readme.md @@ -0,0 +1,191 @@ +# NodeEditor.js API Documentation + +`NodeEditor.js` is a lightweight, framework-agnostic, zero-dependency node editor library written in pure ES6 JavaScript. It leverages standard DOM elements for high-performance node manipulation and an SVG overlay plane for drawing scalable Bezier connections. + +--- + +## 🚀 Quick Start + +### 1. HTML Setup +Create a dedicated target container for the editor. Ensure the container has explicit dimensional bounds (`width` and `height`). + +```html + + + + + Node Editor Instance + + + + +
+ + + + +``` + +### 2. JavaScript Initialization (`app.js`) +Import the `NodeEditor` module, hook it to your DOM node, and instantiate your first pipeline. + +```javascript +import { NodeEditor } from './NodeEditor.js'; + +// 1. Initialize the UI Engine +const container = document.getElementById('editor-container'); +const editor = new NodeEditor(container); + +// 2. Extract the State Manager +const graph = editor.getGraphManager(); + +// 3. Spawning Nodes +const nodeA = graph.addNode({ + type: 'source_node', + title: 'Data Stream', + x: 100, + y: 150, + inputs: [], + outputs: [{ id: 'out_raw', name: 'Raw Buffer' }] +}); + +const nodeB = graph.addNode({ + type: 'processing_node', + title: 'Transform Matrix', + x: 450, + y: 250, + inputs: [{ id: 'in_data', name: 'Payload' }], + outputs: [{ id: 'out_success', name: 'Success' }] +}); + +// 4. Create a programmatically driven wire connection +graph.connect(nodeA.id, 'out_raw', nodeB.id, 'in_data'); +``` + +--- + +## 🛠️ Architecture Overview + +The library enforces a strict separation of concerns via an asynchronous, decoupled state cycle: + +``` +[User Action / UI Pointer Interaction] + │ + ▼ + [GraphManager State Engine] ◄─── (Single Source of Truth) + │ + ▼ (Dispatches Event Hub Actions) + [NodeEditor Render Architecture] + │ + ┌──────────┴──────────┐ + ▼ ▼ +[DOM Element Nodes] [SVG Vector Overlay Patches] +``` + +--- + +## 📚 API Reference + +### `NodeEditor` (UI Controller) + +The wrapper orchestration manager that structures layout elements, listens to user viewport transforms, and monitors state bindings. + +* **`new NodeEditor(containerElement)`** + Instantiates a fresh node pipeline map inside a target DOM container. +* **`editor.getGraphManager()`** + Returns the underlying data `GraphManager` engine operating behind the target instance. +* **`editor.updateAllConnections()`** + Forces an absolute layout recalculation of all connection nodes in view. Useful if you change node positions outside the lifecycle API or resize your external container dynamically. + +--- + +### `GraphManager` (State Controller) + +The isolated data engine that processes, updates, validates, and serializes graph states. + +#### Node Management APIs +* **`addNode(config)`** + Registers a new data node. Returns the instantiated `NodeSchema` object. + * `config.title`: (String) Header title text. + * `config.x` / `config.y`: (Numbers) Absolute starting canvas positioning. + * `config.inputs` / `config.outputs`: Array of port objects (`{ id: 'unique_port_id', name: 'Display Name' }`). + * `config.id`: *(Optional)* Provide a deterministic string ID; otherwise, an internal generic system identifier will be automatically assigned. +* **`deleteNode(nodeId)`** + Destroys the node element matching the identifier and automatically sweeps/prunes any associated broken dependency lines. +* **`updateNodePosition(nodeId, x, y)`** + Mutates raw positioning values inside the memory reference model and triggers view updating layers. + +#### Connection Management APIs +* **`connect(fromNodeId, fromSocketId, toNodeId, toSocketId)`** + Wires an active logical curve bridge between an output port and an input port. Returns `ConnectionSchema` object or `null` if the connection is illegal (e.g. self-looping or double mapping onto an input). +* **`disconnect(connectionId)`** + Severs a trace path across nodes using its direct line identifier. + +#### Serialization & Hydration +* **`getState()`** + Returns a deep, JSON-serializable clone of the entire active workspace graph network topology. +* **`loadState(graphState)`** + Clears all active rendered interface objects and loads a freshly mapped `GraphState` configuration layer. + +```javascript +// Saving state to local storage +const currentSnapshot = graph.getState(); +localStorage.setItem('my_graph', JSON.stringify(currentSnapshot)); + +// Restoring state later +const cachedSnapshot = JSON.parse(localStorage.getItem('my_graph')); +if (cachedSnapshot) { + graph.loadState(cachedSnapshot); +} +``` + +--- + +## 🎛️ Handling Graph Events + +The `GraphManager` implements a lightweight publisher/subscriber layout. You can listen to lifecycle changes across your pipeline engine to trigger custom code: + +```javascript +// Capture changes to map properties to external reactive modules +graph.on('stateChanged', (latestState) => { + console.log("Global modifications registered:", latestState); +}); + +// Trace isolated edge terminations +graph.on('connectionRemoved', (connId) => { + console.warn(`Connection item vanished from runtime: ${connId}`); +}); + +// Trace node structural changes +graph.on('nodeAdded', (node) => { + console.log(`New node generated: ${node.title}`); +}); +``` + +### Available Event Signatures + +| Event Key | Callback Arguments | Description | +| :--- | :--- | :--- | +| `'nodeAdded'` | `(nodeSchema)` | Dispatched when a node object joins the topology. | +| `'nodeRemoved'` | `(nodeId)` | Dispatched when a node is removed from the canvas. | +| `'nodeMoved'` | `(nodeSchema)` | Dispatched repeatedly when a node object moves coordinates. | +| `'connectionAdded'`| `(connectionSchema)` | Dispatched when two nodes are connected. | +| `'connectionRemoved'`| `(connectionId)` | Dispatched when an edge bridge is broken or deleted. | +| `'stateChanged'` | `(graphState)` | Global catch-all event executed immediately after any graph mutation. | + +--- + +## ⌨️ User Interface Interactions + +* **Pan Canvas:** Click and drag anywhere on the empty background or the SVG connection space. +* **Zoom Viewport:** Scroll your mouse wheel or trackpad up and down. The transformation framework calculates and pins vector values centered **directly on your mouse cursor location**. +* **Drag Nodes:** Click and hold the node header bar container element. Movement velocities match your active zoom ratio precisely. +* **Draw Wires:** Click a target circular socket connector on an output node and drag your mouse. Drop the temporary dashed wire onto a target input node socket to complete a bridge connection. +* **Destroy Wires:** Click directly on an active solid colored connection bridge path vector line to delete it from the running memory structure. +* **Destroy Nodes:** Click the small `✕` button positioned inside the upper-right corner of the node header frame. \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..13cb369 --- /dev/null +++ b/app.js @@ -0,0 +1,30 @@ +import { NodeEditor } from './NodeEditor.js'; + +// 1. Initialize the UI Engine +const container = document.getElementById('editor-container'); +const editor = new NodeEditor(container); + +// 2. Extract the State Manager +const graph = editor.getGraphManager(); + +// 3. Spawning Nodes +const nodeA = graph.addNode({ + type: 'source_node', + title: 'Data Stream', + x: 100, + y: 150, + inputs: [], + outputs: [{ id: 'out_raw', name: 'Raw Buffer' }] +}); + +const nodeB = graph.addNode({ + type: 'processing_node', + title: 'Transform Matrix', + x: 450, + y: 250, + inputs: [{ id: 'in_data', name: 'Payload' }], + outputs: [{ id: 'out_success', name: 'Success' }] +}); + +// 4. Create a programmatically driven wire connection +graph.connect(nodeA.id, 'out_raw', nodeB.id, 'in_data'); \ No newline at end of file