From 2bc6b1054bc95b55e2e7b9d888dc8275805f6a44 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sun, 25 Jan 2026 18:22:22 -0800 Subject: [PATCH] Add Dante overlay with TX/RX highlighting and channel info Co-Authored-By: Claude Opus 4.5 --- static/index.html | 222 ++++++++++++++++++++++++++++++++++++++++++++-- types.go | 13 ++- 2 files changed, 225 insertions(+), 10 deletions(-) diff --git a/static/index.html b/static/index.html index 2ed8084..4b69f4d 100644 --- a/static/index.html +++ b/static/index.html @@ -26,6 +26,7 @@ border: 1px solid #444; border-radius: 8px; padding: 10px; + overflow: visible; } .location.top-level { @@ -55,6 +56,7 @@ flex-wrap: wrap; gap: 8px; justify-content: center; + overflow: visible; } .node-row + .node-row { @@ -143,6 +145,7 @@ display: flex; gap: 15px; margin-top: 10px; + overflow: visible; } .children.horizontal { @@ -157,15 +160,134 @@ flex-direction: column; align-items: center; } + + #mode-selector { + position: fixed; + top: 10px; + right: 10px; + z-index: 1000; + display: flex; + gap: 0; + background: #333; + border-radius: 6px; + overflow: hidden; + border: 1px solid #555; + } + + #mode-selector button { + padding: 8px 16px; + border: none; + background: #333; + color: #aaa; + cursor: pointer; + font-size: 12px; + transition: all 0.2s; + } + + #mode-selector button:hover { + background: #444; + } + + #mode-selector button.active { + background: #555; + color: #fff; + } + + body.dante-mode .node { + opacity: 0.3; + } + + body.dante-mode .node.dante-tx { + opacity: 1; + background: #d62; + border: 2px solid #f84; + } + + body.dante-mode .node.dante-rx { + opacity: 1; + background: #26d; + border: 2px solid #48f; + } + + body.dante-mode .node.dante-tx.dante-rx { + background: linear-gradient(135deg, #d62 50%, #26d 50%); + } + + body.dante-mode .node .switch-port, + body.dante-mode .node .uplink, + body.dante-mode .node .root-label { + display: none; + } + + .node .dante-info { + display: none; + position: absolute; + top: -8px; + left: 50%; + transform: translateX(-50%); + font-size: 9px; + padding: 1px 6px; + border-radius: 8px; + white-space: nowrap; + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + z-index: 10; + cursor: default; + } + + .node:has(.dante-info:hover) { + z-index: 1000; + } + + .node .dante-info:hover { + white-space: pre; + max-width: none; + width: max-content; + z-index: 100; + padding: 4px 8px; + } + + .node .dante-info.tx-info { + background: #fca; + color: #630; + } + + .node .dante-info.rx-info { + background: #acf; + color: #036; + } + + body.dante-mode .node.dante-tx .dante-info, + body.dante-mode .node.dante-rx .dante-info { + display: block; + } + + body.dante-mode .node.dante-tx.dante-rx .dante-info.tx-info { + top: -8px; + } + + body.dante-mode .node.dante-tx.dante-rx .dante-info.rx-info { + top: auto; + bottom: -8px; + } +
+ + +
diff --git a/types.go b/types.go index 5763e47..f7be356 100644 --- a/types.go +++ b/types.go @@ -60,12 +60,21 @@ func (s IPSet) Slice() []string { type NameSet map[string]bool +func sortNamesByLength(names []string) { + sort.Slice(names, func(i, j int) bool { + if len(names[i]) != len(names[j]) { + return len(names[i]) < len(names[j]) + } + return names[i] < names[j] + }) +} + func (s NameSet) MarshalJSON() ([]byte, error) { names := make([]string, 0, len(s)) for name := range s { names = append(names, name) } - sort.Strings(names) + sortNamesByLength(names) return json.Marshal(names) } @@ -229,7 +238,7 @@ func (n *Node) DisplayName() string { for name := range n.Names { names = append(names, name) } - sort.Strings(names) + sortNamesByLength(names) return strings.Join(names, "/") }