Add visual graph editor for entity interconnections
SVG-based node graph with ELK.js autolayout showing all 13 entity types and their relationships. Features include: - Pan/zoom canvas with bounds clamping and dead-zone click detection - Interactive minimap with viewport rectangle, click-to-pan, drag-to-move, and dual resize handles (bottom-left/bottom-right) - Movable toolbar with drag handle and inline zoom percentage indicator - Entity-type SVG icons from Lucide icon set with subtype-specific overrides - Command palette search (/) with keyboard navigation and fly-to - Node selection with upstream/downstream chain highlighting - Double-click to zoom-to-card, edit/delete overlay on hover - Legend panel, orphan node detection, running state indicators - Full i18n support with languageChanged re-render - Catmull-Rom-to-cubic bezier edge routing for smooth curves Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,8 +28,10 @@
|
||||
<link rel="stylesheet" href="/static/css/automations.css">
|
||||
<link rel="stylesheet" href="/static/css/tree-nav.css">
|
||||
<link rel="stylesheet" href="/static/css/tutorials.css">
|
||||
<link rel="stylesheet" href="/static/css/graph-editor.css">
|
||||
<link rel="stylesheet" href="/static/css/mobile.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/elkjs@0.9.3/lib/elk.bundled.js"></script>
|
||||
</head>
|
||||
<body style="visibility: hidden;">
|
||||
<canvas id="bg-anim-canvas"></canvas>
|
||||
@@ -51,6 +53,7 @@
|
||||
<button class="tab-btn" data-tab="automations" onclick="switchTab('automations')" role="tab" aria-selected="false" aria-controls="tab-automations" id="tab-btn-automations" title="Ctrl+2"><svg class="icon" viewBox="0 0 24 24"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg> <span data-i18n="automations.title">Automations</span><span class="tab-badge" id="tab-badge-automations" style="display:none"></span></button>
|
||||
<button class="tab-btn" data-tab="targets" onclick="switchTab('targets')" role="tab" aria-selected="false" aria-controls="tab-targets" id="tab-btn-targets" title="Ctrl+3"><svg class="icon" viewBox="0 0 24 24"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg> <span data-i18n="targets.title">Targets</span><span class="tab-badge" id="tab-badge-targets" style="display:none"></span></button>
|
||||
<button class="tab-btn" data-tab="streams" onclick="switchTab('streams')" role="tab" aria-selected="false" aria-controls="tab-streams" id="tab-btn-streams" title="Ctrl+4"><svg class="icon" viewBox="0 0 24 24"><path d="m17 2-5 5-5-5"/><rect width="20" height="15" x="2" y="7" rx="2"/></svg> <span data-i18n="streams.title">Sources</span></button>
|
||||
<button class="tab-btn" data-tab="graph" onclick="switchTab('graph')" role="tab" aria-selected="false" aria-controls="tab-graph" id="tab-btn-graph" title="Ctrl+5"><svg class="icon" viewBox="0 0 24 24"><circle cx="5" cy="6" r="3"/><circle cx="19" cy="6" r="3"/><circle cx="12" cy="18" r="3"/><path d="M7.5 7.5 10.5 16"/><path d="M16.5 7.5 13.5 16"/></svg> <span data-i18n="graph.title">Graph</span></button>
|
||||
</div>
|
||||
<div class="header-toolbar">
|
||||
<a href="/docs" target="_blank" class="header-link" data-i18n-title="app.api_docs" title="API Docs">API</a>
|
||||
@@ -139,6 +142,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" id="tab-graph" role="tabpanel" aria-labelledby="tab-btn-graph">
|
||||
<div id="graph-editor-content">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// Apply saved tab immediately during parse to prevent visible jump
|
||||
@@ -147,6 +156,7 @@
|
||||
var saved = hash ? hash.split('/')[0] : localStorage.getItem('activeTab');
|
||||
if (saved === 'devices') saved = 'targets';
|
||||
if (!saved || !document.getElementById('tab-' + saved)) saved = 'dashboard';
|
||||
/* graph tab is valid */
|
||||
document.querySelectorAll('.tab-btn').forEach(function(btn) { btn.classList.toggle('active', btn.dataset.tab === saved); });
|
||||
document.querySelectorAll('.tab-panel').forEach(function(panel) { panel.classList.toggle('active', panel.id === 'tab-' + saved); });
|
||||
})();
|
||||
@@ -382,6 +392,7 @@
|
||||
document.getElementById('automations-content').innerHTML = loginMsg;
|
||||
document.getElementById('targets-panel-content').innerHTML = loginMsg;
|
||||
document.getElementById('streams-list').innerHTML = loginMsg;
|
||||
document.getElementById('graph-editor-content').innerHTML = loginMsg;
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
|
||||
Reference in New Issue
Block a user