Integration Guide: Overlay Manager → Node Manager
Integration Guide: Overlay Manager → Node Manager
Date: 2025-09-30
Status: READY TO INTEGRATE
Gap Status: 95% CLOSED - Just needs bridge method!
Executive Summary
🎉 GREAT NEWS! Your node_manager.js is FULLY FUNCTIONAL and has everything needed to render nodes on canvas!
What You Have:
- ✅ overlay_manager.js - Menu system that generates n8n JSON
- ✅ node_manager.js - Complete node rendering, dragging, selection system
- ✅ canvas_manager.js - Canvas viewport with pan/zoom
The Missing Link:
- ❌ A bridge method in node_manager.js to accept n8n-format JSON from overlay_manager.js
Solution: Add ONE method (addNodeFromN8nJSON) and modify ONE line in overlay_manager.js
Time to Complete: 15-30 minutes
Current Architecture Analysis
Your Node Manager (node_manager.js)
Status: ✅ EXCELLENT - Fully functional!
Key Features Already Working:
class NodeManager {
// ✅ Data storage
this.nodes = new Map();
this.connections = new Map();
this.selectedNodes = new Set();
// ✅ Node creation
createNode(type, position, config = {}) { /* ... */ }
// ✅ Visual rendering
createNodeElement(nodeConfig) { /* ... */ }
// ✅ Interactions
- selectNode() - Click to select
- startNodeDrag() - Drag to move
- deleteNode() - Delete key to remove
- duplicateSelectedNodes() - Copy/paste
// ✅ State management
getNodesState() { /* ... */ }
loadNodesState(state) { /* ... */ }
}
Analysis:
- Has createNode() method that works perfectly
- Renders nodes with proper styling, headers, icons
- Has node ports for connections (already visible!)
- Full drag-and-drop support
- Multi-select support
- Keyboard shortcuts
The Issue:
- createNode() expects parameters in this format:
javascript
createNode('action', { x: 100, y: 100 }, {
name: 'My Node',
description: 'Does something',
parameters: {}
})
- But
overlay_manager.jsgenerates n8n format:
javascript { id: "uuid", name: "Send Email", type: "n8n-nodes-base.gmail", typeVersion: 1, position: [100, 100], parameters: { resource: 'message', operation: 'send' } }
Solution: Add a bridge method!
The Missing Piece: Bridge Method
Add to node_manager.js
Location: After line 306 (right after createNode() method)
New Method:
/**
* Add node from n8n JSON format (from overlay manager)
* Converts n8n node format to internal NodeManager format
*/
addNodeFromN8nJSON(n8nNodeData) {
console.log('➕ Adding node from n8n JSON:', n8nNodeData);
// Extract n8n data
const n8nType = n8nNodeData.type || 'n8n-nodes-base.unknown';
const n8nName = n8nNodeData.name || 'Untitled Node';
const n8nPosition = n8nNodeData.position || [100, 100];
const n8nParameters = n8nNodeData.parameters || {};
// Determine node type (trigger vs action)
// Check if this is a trigger node based on n8n type
let nodeType = 'action'; // Default
if (n8nType.toLowerCase().includes('trigger') ||
n8nType.toLowerCase().includes('webhook') ||
n8nType.toLowerCase().includes('schedule')) {
nodeType = 'trigger';
}
// Convert n8n position format [x, y] to { x, y }
const position = {
x: Array.isArray(n8nPosition) ? n8nPosition[0] : (n8nPosition.x || 100),
y: Array.isArray(n8nPosition) ? n8nPosition[1] : (n8nPosition.y || 100)
};
// Build config for internal createNode method
const config = {
name: n8nName,
description: this.buildNodeDescription(n8nParameters),
parameters: n8nParameters,
n8nType: n8nType, // Store original n8n type for reference
n8nId: n8nNodeData.id, // Store original n8n ID if provided
typeVersion: n8nNodeData.typeVersion || 1
};
// Create node using existing createNode method
const node = this.createNode(nodeType, position, config);
console.log(`✅ Added n8n node: ${n8nName} (Type: ${n8nType})`);
// Return created node
return node;
}
/**
* Build a readable description from n8n parameters
*/
buildNodeDescription(parameters) {
if (!parameters || Object.keys(parameters).length === 0) {
return '';
}
// Extract key parameters for description
const parts = [];
if (parameters.resource) {
parts.push(`Resource: ${parameters.resource}`);
}
if (parameters.operation) {
parts.push(`Operation: ${parameters.operation}`);
}
return parts.join(' | ');
}
/**
* Add multiple nodes from n8n workflow JSON
*/
addNodesFromN8nWorkflow(workflowJSON) {
console.log('➕ Adding nodes from n8n workflow:', workflowJSON);
if (!workflowJSON.nodes || !Array.isArray(workflowJSON.nodes)) {
console.error('❌ Invalid workflow JSON: missing nodes array');
return [];
}
const createdNodes = [];
workflowJSON.nodes.forEach(nodeData => {
const node = this.addNodeFromN8nJSON(nodeData);
createdNodes.push(node);
});
console.log(`✅ Added ${createdNodes.length} nodes from workflow`);
return createdNodes;
}
Integration Steps
Step 1: Add Bridge Method to node_manager.js
File: static/src/n8n/nodes/node_manager.js
Location: After line 306 (after createNode() method, before createNodeElement())
Action: Copy and paste the three methods above:
1. addNodeFromN8nJSON(n8nNodeData)
2. buildNodeDescription(parameters)
3. addNodesFromN8nWorkflow(workflowJSON)
Step 2: Modify overlay_manager.js
File: static/src/n8n/overlays/overlay_manager.js
Find: Line 3401 (in selectOperation() method)
Current Code:
// Show the JSON for now (Phase 3 final step will add to canvas)
alert(`Canvas JSON Generated!\n\n${JSON.stringify(canvasNodeJSON, null, 2)}\n\nNext: Add to canvas!`);
Replace With:
// Add node to canvas via NodeManager
if (window.nodeManager && window.nodeManager.initialized) {
// Extract node data from workflow JSON
const nodeData = canvasNodeJSON.nodes[0];
// Smart positioning: Place near last node if exists
if (window.nodeManager.nodes.size > 0) {
const lastNode = Array.from(window.nodeManager.nodes.values()).pop();
nodeData.position = {
x: lastNode.position.x + 250,
y: lastNode.position.y
};
}
// Add node using new bridge method
window.nodeManager.addNodeFromN8nJSON(nodeData);
console.log('✅ Node added to canvas:', nodeData.name);
// Optional: Show brief success message
// alert(`✅ ${nodeData.name} added to canvas!`);
} else {
console.error('❌ NodeManager not initialized');
alert('Error: NodeManager not ready. Check browser console.');
}
Step 3: Verify Initialization Order
Check: Where your managers are initialized (likely in main template or initialization file)
Required Order:
// 1. Initialize Canvas Manager first
const canvasManager = new CanvasManager();
canvasManager.init();
window.canvasManager = canvasManager;
// 2. Get canvas viewport
const canvasViewport = canvasManager.getCanvasViewport();
// 3. Initialize Node Manager with viewport
const nodeManager = new NodeManager();
nodeManager.init(canvasViewport);
window.nodeManager = nodeManager;
// 4. Initialize Overlay Manager
const overlayManager = new OverlayManager();
overlayManager.init();
window.overlayManager = overlayManager;
console.log('✅ All managers initialized');
Verification:
- Open browser console
- Type: window.nodeManager
- Should see: NodeManager object with initialized: true
- Type: window.nodeManager.canvasViewport
- Should see: HTML element with class canvas-viewport
Testing Guide
Test 1: Basic Node Addition
Steps:
1. Open your canvas page
2. Open browser console (F12)
3. Click "N8N Node" button
4. Select a platform (e.g., Gmail)
5. Select an operation (e.g., Send Email)
6. Expected Results:
- ✅ Node appears on canvas
- ✅ Node has header with icon and "Action" label
- ✅ Node shows name: "Send Email"
- ✅ Node shows description: "Resource: message | Operation: send"
- ✅ Console shows: "✅ Added n8n node: Send Email"
If Node Doesn't Appear:
- Check console for errors
- Verify: window.nodeManager.initialized === true
- Verify: window.nodeManager.canvasViewport is not null
- Check: window.nodeManager.nodes.size (should increase after adding node)
Test 2: Node Interaction
Steps:
1. Add a node (from Test 1)
2. Click on node
- Expected: Node border turns blue (selected)
3. Drag node header
- Expected: Node follows mouse
4. Release mouse
- Expected: Node stays at new position
5. Press Delete key
- Expected: Node disappears
Console Commands:
// Check node state
window.nodeManager.nodes.size // Should show count
// Get all nodes
window.nodeManager.getAllNodes()
// Get selected nodes
window.nodeManager.getSelectedNodes()
// Get full state
window.nodeManager.getNodesState()
Test 3: Multiple Nodes
Steps:
1. Add 3 different nodes
2. Expected:
- Each node appears at different position (smart spacing)
- Can select each independently
- Can drag each independently
Verify Smart Positioning:
// After adding multiple nodes
window.nodeManager.getAllNodes().forEach(node => {
console.log(`${node.name}: [${node.position.x}, ${node.position.y}]`);
});
// Expected: X positions increase by ~250px for each node
Test 4: State Persistence
Steps:
1. Add 2-3 nodes
2. In console, run:
javascript
const state = window.nodeManager.getNodesState();
console.log('Saved state:', state);
3. Delete all nodes manually (select + Delete)
4. Reload state:
javascript
window.nodeManager.loadNodesState(state);
5. Expected: All nodes reappear at same positions
Advanced Features Already Working
Feature 1: Node Ports (Connection Points)
Status: ✅ Already rendered!
Your nodes already have connection points (blue circles on left/right):
// From createNodeElement() method
<div class="node-ports">
<div class="node-port input" data-port="input"></div>
<div class="node-port output" data-port="output"></div>
</div>
What Works:
- Input port on left side
- Output port on right side
- Hover effect (scales to 1.2x)
What's Missing:
- Click-drag to create connections (this is the "connection lines" you mentioned)
Feature 2: Multi-Select
Status: ✅ Already working!
How to Use:
- Hold Ctrl (Windows) or Cmd (Mac)
- Click multiple nodes
- All selected nodes highlight
Available Operations:
- Delete selected: nodeManager.deleteSelectedNodes()
- Duplicate selected: nodeManager.duplicateSelectedNodes()
Feature 3: Node Types
Status: ✅ Already working!
Your node manager already supports:
- trigger - Green header (🔄 icon)
- action - Blue header (⚡ icon)
- condition - Yellow header (🔀 icon)
- output - Gray header (📤 icon)
The bridge method automatically detects trigger nodes based on n8n type name.
Connection Lines (Final Piece)
What You Have
Your node_manager.js has a connections Map but no methods to create/render connections.
Existing Connection-Related Code:
// Line 215: Storage
this.connections = new Map();
// Line 631: Cleanup
removeNodeConnections(nodeId) {
// Removes connections when node is deleted
this.connections.forEach((conn, connId) => {
if (conn.source === nodeId || conn.target === nodeId) {
this.connections.delete(connId);
}
});
}
What's Missing
Connection Creation Methods:
// NOT YET IMPLEMENTED in your node_manager.js
createConnection(sourceNodeId, targetNodeId) {
// 1. Validate nodes exist
// 2. Store connection data
// 3. Render SVG line between nodes
}
renderConnection(connectionId) {
// 1. Get source/target node positions
// 2. Create SVG path between them
// 3. Add to canvas SVG layer
}
startConnectionDrag(nodeId, portType, event) {
// 1. Mouse down on port
// 2. Draw preview line following mouse
// 3. On mouse up over another port, create connection
}
Connection Implementation Guide
Priority: Phase 5 (After nodes are working)
Recommended Approach:
-
Add SVG Layer to Canvas
javascript // In canvas_manager.js or node_manager.js init() const svgLayer = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svgLayer.id = 'connection-layer'; svgLayer.style.position = 'absolute'; svgLayer.style.top = '0'; svgLayer.style.left = '0'; svgLayer.style.width = '100%'; svgLayer.style.height = '100%'; svgLayer.style.pointerEvents = 'none'; svgLayer.style.zIndex = '1'; this.canvasViewport.appendChild(svgLayer); -
Add Connection Methods
```javascript
// Add to NodeManager class
createConnection(sourceNodeId, targetNodeId, sourcePort = 'output', targetPort = 'input') {
const connectionId = ${sourceNodeId}-${targetNodeId};
// Check if connection already exists
if (this.connections.has(connectionId)) {
console.warn('Connection already exists:', connectionId);
return null;
}
// Validate nodes exist
const sourceNode = this.nodes.get(sourceNodeId);
const targetNode = this.nodes.get(targetNodeId);
if (!sourceNode || !targetNode) {
console.error('Invalid nodes for connection');
return null;
}
// Store connection data
const connection = {
id: connectionId,
source: sourceNodeId,
target: targetNodeId,
sourcePort: sourcePort,
targetPort: targetPort,
created: new Date().toISOString()
};
this.connections.set(connectionId, connection);
// Render visual line
this.renderConnection(connectionId);
console.log('Created connection:', connectionId);
// Trigger event
this.triggerNodeEvent('connection:created', { connection });
return connection;
}
renderConnection(connectionId) {
const connection = this.connections.get(connectionId);
if (!connection) return;
const sourceNode = this.nodes.get(connection.source);
const targetNode = this.nodes.get(connection.target);
if (!sourceNode || !targetNode) return;
// Calculate positions
const sourceX = sourceNode.position.x + NODE_CONFIG.defaultWidth;
const sourceY = sourceNode.position.y + (NODE_CONFIG.defaultHeight / 2);
const targetX = targetNode.position.x;
const targetY = targetNode.position.y + (NODE_CONFIG.defaultHeight / 2);
// Create curved path (Bezier curve)
const midX = sourceX + (targetX - sourceX) / 2;
const path = `M ${sourceX} ${sourceY} C ${midX} ${sourceY}, ${midX} ${targetY}, ${targetX} ${targetY}`;
// Get or create SVG layer
let svg = document.getElementById('connection-layer');
if (!svg) {
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.id = 'connection-layer';
svg.style.position = 'absolute';
svg.style.top = '0';
svg.style.left = '0';
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.pointerEvents = 'none';
svg.style.zIndex = '1';
this.canvasViewport.insertBefore(svg, this.canvasViewport.firstChild);
}
// Create or update path element
let pathElement = svg.querySelector(`[data-connection-id="${connectionId}"]`);
if (!pathElement) {
pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathElement.setAttribute('data-connection-id', connectionId);
pathElement.setAttribute('stroke', '#007bff');
pathElement.setAttribute('stroke-width', '2');
pathElement.setAttribute('fill', 'none');
svg.appendChild(pathElement);
}
pathElement.setAttribute('d', path);
}
deleteConnection(connectionId) {
const connection = this.connections.get(connectionId);
if (!connection) return;
// Remove visual element
const svg = document.getElementById('connection-layer');
if (svg) {
const pathElement = svg.querySelector(`[data-connection-id="${connectionId}"]`);
if (pathElement) {
pathElement.remove();
}
}
// Remove from data
this.connections.delete(connectionId);
console.log('Deleted connection:', connectionId);
this.triggerNodeEvent('connection:deleted', { connectionId });
}
// Update all connection positions (call after node moves)
updateAllConnections() {
this.connections.forEach((conn, connId) => {
this.renderConnection(connId);
});
}
```
- Add Port Click Handlers
```javascript
// Modify addNodeEventListeners() method
// Add this at the end of addNodeEventListeners()
const ports = element.querySelectorAll('.node-port');
ports.forEach(port => {
port.addEventListener('mousedown', (e) => {
e.stopPropagation();
const portType = port.getAttribute('data-port');
this.startConnectionDrag(nodeId, portType, e);
});
});
```
-
Add Connection Drag Logic
```javascript
startConnectionDrag(nodeId, portType, e) {
console.log('Starting connection from:', nodeId, portType);this.isConnecting = true;
this.connectionStart = { nodeId, portType };// Create preview line
// ... (implementation details)
}
// In handleGlobalMouseMove(), add:
if (this.isConnecting && this.connectionStart) {
// Update preview line to follow mouse
// ...
}
// In handleGlobalMouseUp(), add:
if (this.isConnecting) {
// Check if mouse is over a port
// If yes, create connection
// If no, cancel
this.isConnecting = false;
this.connectionStart = null;
}
```
Estimated Time: 3-4 hours for full connection system
Summary: What You Have vs What You Need
Architecture Status
┌─────────────────────────────────────────────────────────────┐
│ USER INTERFACE │
│ ┌──────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ N8N Node Btn │ │ Canvas Viewport│ │ Node Elements │ │
│ │ ✅ WORKS │ │ ✅ WORKS │ │ ✅ WORKS │ │
│ └──────────────┘ └────────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ OVERLAY MANAGER (Menu) │
│ ✅ Node selection popup │
│ ✅ Load from database │
│ ✅ Generate n8n JSON │
│ ❌ Call NodeManager.addNodeFromN8nJSON() ← NEEDS 1 LINE │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ NODE MANAGER (Bridge) │
│ ✅ Node storage (Map) │
│ ✅ Node rendering │
│ ✅ Node interactions (click, drag, delete) │
│ ✅ Multi-select │
│ ✅ Duplicate │
│ ✅ State save/load │
│ ❌ addNodeFromN8nJSON() ← NEEDS NEW METHOD │
│ ❌ Connection creation ← PHASE 5 │
│ ❌ Connection rendering ← PHASE 5 │
└───────────────┬──────────────────────┬──────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌─────────────────────────┐
│ CANVAS MANAGER │ │ CONNECTION LAYER │
│ ✅ Pan/zoom │ │ ❌ SVG layer (Phase 5) │
│ ✅ Grid │ │ ❌ Line rendering │
│ ✅ Viewport ready │ │ ❌ Drag to connect │
└──────────────────────┘ └─────────────────────────┘
Status Breakdown
Phase 1: ✅ COMPLETE - Canvas viewport (pan/zoom/grid)
Phase 2: ✅ COMPLETE - Menu system (n8n node selection)
Phase 3: ✅ COMPLETE - Node rendering system
Phase 4: ⚠️ 95% COMPLETE - Bridge between menu and nodes
- Missing: 1 method + 1 line modification
Phase 5: ❌ TODO - Connection lines
- Needs: SVG layer + connection methods
Next Actions
Immediate (15-30 minutes)
- ✅ Add
addNodeFromN8nJSON()method to node_manager.js (line 307) - ✅ Modify overlay_manager.js line 3401
- ✅ Test: Click N8N button → Select node → See it appear on canvas
- ✅ Test: Drag node, delete node, add multiple nodes
Short-term (2-4 hours)
- ✅ Verify initialization order
- ✅ Add error handling for edge cases
- ✅ Improve smart positioning algorithm
- ✅ Add keyboard shortcuts (Ctrl+D for duplicate, Ctrl+A for select all)
Medium-term (3-4 hours)
- ❌ Implement connection system (Phase 5)
- ❌ Add connection drag-to-create
- ❌ Add connection visual feedback
- ❌ Add connection validation (no circular connections)
Long-term (Optional)
- Backend persistence (save/load workflows to Odoo)
- Node grouping
- Undo/redo system
- Export/import workflows as JSON
- Node execution (actually run workflows)
Code Templates Ready to Use
All code is provided above:
- ✅ addNodeFromN8nJSON() method
- ✅ buildNodeDescription() helper
- ✅ addNodesFromN8nWorkflow() batch method
- ✅ Modified overlay_manager.js integration
- ✅ Connection system methods (for Phase 5)
Just copy, paste, test!
Questions Before You Start?
- Where is your initialization code? (To verify manager order)
- What's your testing environment? (Local dev, staging, production)
- Do you want success notifications? (Alert, toast, console only)
- Connection lines priority? (Immediate or Phase 5?)
Let me know when you're ready to implement, and I'll guide you through each step!