Skip to Content

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.js generates 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:

  1. 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);

  2. 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);
});
}
```

  1. 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);
});
});
```

  1. 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)

  1. ✅ Add addNodeFromN8nJSON() method to node_manager.js (line 307)
  2. ✅ Modify overlay_manager.js line 3401
  3. ✅ Test: Click N8N button → Select node → See it appear on canvas
  4. ✅ Test: Drag node, delete node, add multiple nodes

Short-term (2-4 hours)

  1. ✅ Verify initialization order
  2. ✅ Add error handling for edge cases
  3. ✅ Improve smart positioning algorithm
  4. ✅ Add keyboard shortcuts (Ctrl+D for duplicate, Ctrl+A for select all)

Medium-term (3-4 hours)

  1. ❌ Implement connection system (Phase 5)
  2. ❌ Add connection drag-to-create
  3. ❌ Add connection visual feedback
  4. ❌ Add connection validation (no circular connections)

Long-term (Optional)

  1. Backend persistence (save/load workflows to Odoo)
  2. Node grouping
  3. Undo/redo system
  4. Export/import workflows as JSON
  5. 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?

  1. Where is your initialization code? (To verify manager order)
  2. What's your testing environment? (Local dev, staging, production)
  3. Do you want success notifications? (Alert, toast, console only)
  4. Connection lines priority? (Immediate or Phase 5?)

Let me know when you're ready to implement, and I'll guide you through each step!

Was this helpful?