Add interactive graph editor connections: port-based edges, drag-connect, and detach

- Add visible typed ports on graph nodes (colored dots for each edge type)
- Route edges to specific port positions instead of node center
- Drag from output port to compatible input port to create/change connections
- Right-click edge context menu with Disconnect option
- Delete key detaches selected edge
- Mark nested edges (composite layers, zones) as non-editable with dotted style
- Add resolve_ref helper for empty-string sentinel to clear reference fields
- Apply resolve_ref across all storage stores for consistent detach support
- Add connection mapping module (graph-connections.js) with API field resolution
- Add i18n keys for connection operations (en/ru/zh)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 17:15:33 +03:00
parent ff24ec95e6
commit b370bb7d75
17 changed files with 661 additions and 60 deletions
@@ -5,6 +5,7 @@ from datetime import datetime, timezone
from typing import List, Optional
from wled_controller.storage.output_target import OutputTarget
from wled_controller.storage.utils import resolve_ref
DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds
@@ -68,11 +69,11 @@ class WledOutputTarget(OutputTarget):
"""Apply mutable field updates for WLED targets."""
super().update_fields(name=name, description=description, tags=tags)
if device_id is not None:
self.device_id = device_id
self.device_id = resolve_ref(device_id, self.device_id)
if color_strip_source_id is not None:
self.color_strip_source_id = color_strip_source_id
self.color_strip_source_id = resolve_ref(color_strip_source_id, self.color_strip_source_id)
if brightness_value_source_id is not None:
self.brightness_value_source_id = brightness_value_source_id
self.brightness_value_source_id = resolve_ref(brightness_value_source_id, self.brightness_value_source_id)
if fps is not None:
self.fps = fps
if keepalive_interval is not None: