Track sACN emitters and receivers with peer linking
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -535,9 +535,18 @@
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
body.sacn-mode .node.sacn-consumer {
|
||||
body.sacn-mode .node.sacn-out {
|
||||
opacity: 1;
|
||||
background: #26d;
|
||||
background: #287;
|
||||
}
|
||||
|
||||
body.sacn-mode .node.sacn-in {
|
||||
opacity: 1;
|
||||
background: #268;
|
||||
}
|
||||
|
||||
body.sacn-mode .node.sacn-out.sacn-in {
|
||||
background: linear-gradient(135deg, #287 50%, #268 50%);
|
||||
}
|
||||
|
||||
body.sacn-mode .node .switch-port,
|
||||
@@ -553,13 +562,20 @@
|
||||
padding: 1px 6px;
|
||||
border-radius: 8px;
|
||||
white-space: nowrap;
|
||||
background: #468;
|
||||
color: #fff;
|
||||
max-width: 114px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.node .sacn-info.out-info {
|
||||
background: #254;
|
||||
}
|
||||
|
||||
.node .sacn-info.in-info {
|
||||
background: #245;
|
||||
}
|
||||
|
||||
.node .sacn-hover {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
@@ -607,10 +623,23 @@
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
body.sacn-mode .node.sacn-consumer .sacn-info {
|
||||
body.sacn-mode .node.sacn-out .sacn-info,
|
||||
body.sacn-mode .node.sacn-in .sacn-info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body.sacn-mode .node.sacn-out.sacn-in .sacn-in-hover {
|
||||
top: auto;
|
||||
bottom: -8px;
|
||||
}
|
||||
|
||||
body.sacn-mode .node.sacn-out.sacn-in .sacn-in-hover .sacn-detail-wrapper {
|
||||
bottom: auto;
|
||||
top: 100%;
|
||||
padding-bottom: 0;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.sacn-info .lbl,
|
||||
.sacn-detail .lbl {
|
||||
color: #888;
|
||||
@@ -673,7 +702,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.sacn-mode .node:not(.sacn-consumer):hover .node-info-wrapper {
|
||||
body.sacn-mode .node:not(.sacn-out):not(.sacn-in):hover .node-info-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1226,7 +1255,8 @@
|
||||
if (danteInfo?.isRx) div.classList.add('dante-rx');
|
||||
if (artnetInfo?.isOut) div.classList.add('artnet-out');
|
||||
if (artnetInfo?.isIn) div.classList.add('artnet-in');
|
||||
if (sacnInfo?.isConsumer) div.classList.add('sacn-consumer');
|
||||
if (sacnInfo?.isOut) div.classList.add('sacn-out');
|
||||
if (sacnInfo?.isIn) div.classList.add('sacn-in');
|
||||
|
||||
// Switch port connection
|
||||
if (!isSwitch(node) && switchConnection) {
|
||||
@@ -1463,24 +1493,49 @@
|
||||
if (container) container.remove();
|
||||
}
|
||||
|
||||
// sACN
|
||||
if (sacnInfo?.isConsumer) {
|
||||
let container = div.querySelector(':scope > .sacn-hover');
|
||||
// sACN out
|
||||
if (sacnInfo?.isOut) {
|
||||
let container = div.querySelector(':scope > .sacn-out-hover');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.className = 'sacn-hover';
|
||||
container.innerHTML = '<div class="sacn-info"><span class="lbl">←</span> <span class="sacn-pill-text"></span></div><div class="sacn-detail-wrapper"><div class="sacn-detail"></div></div>';
|
||||
container.className = 'sacn-hover sacn-out-hover';
|
||||
container.innerHTML = '<div class="sacn-info out-info"><span class="lbl">←</span> <span class="sacn-pill-text"></span></div><div class="sacn-detail-wrapper"><div class="sacn-detail"></div></div>';
|
||||
div.appendChild(container);
|
||||
}
|
||||
const textEl = container.querySelector('.sacn-pill-text');
|
||||
const sacnMore = sacnInfo.universes.length > 1 ? ', ...' : '';
|
||||
textEl.textContent = sacnInfo.universes[0] + sacnMore;
|
||||
const firstOut = sacnInfo.outputs[0];
|
||||
const outLabel = firstOut.firstTarget || firstOut.display;
|
||||
const outMore = sacnInfo.outputs.length > 1 ? ', ...' : '';
|
||||
textEl.textContent = outLabel + outMore;
|
||||
|
||||
const detail = container.querySelector('.sacn-detail');
|
||||
detail.innerHTML = '';
|
||||
buildClickableList(detail, sacnInfo.universes, '←', (l, v) => l + ' ' + v);
|
||||
buildClickableList(detail, sacnInfo.outputs.map(o => o.display), '←', (l, v) => l + ' ' + v);
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .sacn-hover');
|
||||
const container = div.querySelector(':scope > .sacn-out-hover');
|
||||
if (container) container.remove();
|
||||
}
|
||||
|
||||
// sACN in
|
||||
if (sacnInfo?.isIn) {
|
||||
let container = div.querySelector(':scope > .sacn-in-hover');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.className = 'sacn-hover sacn-in-hover';
|
||||
container.innerHTML = '<div class="sacn-info in-info"><span class="lbl">→</span> <span class="sacn-pill-text"></span></div><div class="sacn-detail-wrapper"><div class="sacn-detail"></div></div>';
|
||||
div.appendChild(container);
|
||||
}
|
||||
const textEl = container.querySelector('.sacn-pill-text');
|
||||
const firstIn = sacnInfo.inputs[0];
|
||||
const inLabel = firstIn.firstTarget || firstIn.display;
|
||||
const inMore = sacnInfo.inputs.length > 1 ? ', ...' : '';
|
||||
textEl.textContent = inLabel + inMore;
|
||||
|
||||
const detail = container.querySelector('.sacn-detail');
|
||||
detail.innerHTML = '';
|
||||
buildClickableList(detail, sacnInfo.inputs.map(i => i.display), '→', (l, v) => l + ' ' + v);
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .sacn-in-hover');
|
||||
if (container) container.remove();
|
||||
}
|
||||
|
||||
@@ -1944,15 +1999,51 @@
|
||||
const sacnData = data.sacn_nodes || [];
|
||||
const sacnNodes = new Map();
|
||||
|
||||
const sacnUniverseInputs = new Map();
|
||||
const sacnUniverseOutputs = new Map();
|
||||
|
||||
sacnData.forEach(sn => {
|
||||
const name = getShortLabel(sn.node);
|
||||
(sn.inputs || []).forEach(u => {
|
||||
if (!sacnUniverseInputs.has(u)) sacnUniverseInputs.set(u, []);
|
||||
sacnUniverseInputs.get(u).push(name);
|
||||
});
|
||||
(sn.outputs || []).forEach(u => {
|
||||
if (!sacnUniverseOutputs.has(u)) sacnUniverseOutputs.set(u, []);
|
||||
sacnUniverseOutputs.get(u).push(name);
|
||||
});
|
||||
});
|
||||
|
||||
const sacnCollapseNames = (names) => {
|
||||
const counts = {};
|
||||
names.forEach(n => counts[n] = (counts[n] || 0) + 1);
|
||||
return Object.entries(counts).map(([name, count]) => count > 1 ? name + ' x' + count : name);
|
||||
};
|
||||
|
||||
sacnData.forEach(sn => {
|
||||
const nodeId = sn.node?.typeid;
|
||||
if (!nodeId) return;
|
||||
|
||||
const universes = (sn.universes || []).slice().sort((a, b) => a - b).map(u => String(u));
|
||||
const inputs = (sn.inputs || []).slice().sort((a, b) => a - b).map(u => {
|
||||
const sources = sacnCollapseNames(sacnUniverseOutputs.get(u) || []);
|
||||
if (sources.length > 0) {
|
||||
return { display: sources[0] + ' [' + u + ']', firstTarget: sources[0] };
|
||||
}
|
||||
return { display: String(u), firstTarget: null };
|
||||
});
|
||||
const outputs = (sn.outputs || []).slice().sort((a, b) => a - b).map(u => {
|
||||
const dests = sacnCollapseNames(sacnUniverseInputs.get(u) || []);
|
||||
if (dests.length > 0) {
|
||||
return { display: dests[0] + ' [' + u + ']', firstTarget: dests[0] };
|
||||
}
|
||||
return { display: String(u), firstTarget: null };
|
||||
});
|
||||
|
||||
sacnNodes.set(nodeId, {
|
||||
isConsumer: universes.length > 0,
|
||||
universes: universes
|
||||
isOut: outputs.length > 0,
|
||||
isIn: inputs.length > 0,
|
||||
outputs: outputs,
|
||||
inputs: inputs
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user