UI improvements: flow names, table tooltips, artmap mappings, sorting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { getLabel, getShortLabel, isSwitch, getSpeedClass } from './nodes.js';
|
||||
import { getLabel, getShortLabel, getFirstName, isSwitch, getSpeedClass } from './nodes.js';
|
||||
import { addClickableValue, buildLinkStats, buildDanteDetail, buildClickableList } from './ui.js';
|
||||
import { nodeElements, locationElements, usedNodeIds, usedLocationIds } from './state.js';
|
||||
|
||||
@@ -186,7 +186,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
||||
|
||||
const detail = container.querySelector('.dante-detail');
|
||||
detail.innerHTML = '';
|
||||
buildDanteDetail(detail, danteInfo.txTo, '→', node.id, danteInfo.txToPeerIds);
|
||||
buildDanteDetail(detail, danteInfo.txTo, '→', danteInfo.nodeName, danteInfo.txToPeerNames);
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .dante-tx-hover');
|
||||
if (container) container.remove();
|
||||
@@ -207,7 +207,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
||||
|
||||
const detail = container.querySelector('.dante-detail');
|
||||
detail.innerHTML = '';
|
||||
buildDanteDetail(detail, danteInfo.rxFrom, '←', node.id, danteInfo.rxFromPeerIds);
|
||||
buildDanteDetail(detail, danteInfo.rxFrom, '←', danteInfo.nodeName, danteInfo.rxFromPeerNames);
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .dante-rx-hover');
|
||||
if (container) container.remove();
|
||||
@@ -230,7 +230,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
||||
const detail = container.querySelector('.artnet-detail');
|
||||
detail.innerHTML = '';
|
||||
buildClickableList(detail, artnetInfo.outputs.map(o => o.display), '←', (l, v) => l + ' ' + v,
|
||||
{ protocol: 'artnet', nodeId: node.id, universes: artnetInfo.outputs.map(o => o.universe) });
|
||||
{ protocol: 'artnet', nodeName: getFirstName(node), universes: artnetInfo.outputs.map(o => o.universe) });
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .artnet-out-hover');
|
||||
if (container) container.remove();
|
||||
@@ -253,7 +253,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
||||
const detail = container.querySelector('.artnet-detail');
|
||||
detail.innerHTML = '';
|
||||
buildClickableList(detail, artnetInfo.inputs.map(i => i.display), '→', (l, v) => l + ' ' + v,
|
||||
{ protocol: 'artnet', nodeId: node.id, universes: artnetInfo.inputs.map(i => i.universe) });
|
||||
{ protocol: 'artnet', nodeName: getFirstName(node), universes: artnetInfo.inputs.map(i => i.universe) });
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .artnet-in-hover');
|
||||
if (container) container.remove();
|
||||
@@ -276,7 +276,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
||||
const detail = container.querySelector('.sacn-detail');
|
||||
detail.innerHTML = '';
|
||||
buildClickableList(detail, sacnInfo.outputs.map(o => o.display), '←', (l, v) => l + ' ' + v,
|
||||
{ protocol: 'sacn', nodeId: node.id, universes: sacnInfo.outputs.map(o => o.universe) });
|
||||
{ protocol: 'sacn', nodeName: getFirstName(node), universes: sacnInfo.outputs.map(o => o.universe) });
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .sacn-out-hover');
|
||||
if (container) container.remove();
|
||||
@@ -299,7 +299,7 @@ export function createNodeElement(node, switchConnection, nodeLocation, uplinkIn
|
||||
const detail = container.querySelector('.sacn-detail');
|
||||
detail.innerHTML = '';
|
||||
buildClickableList(detail, sacnInfo.inputs.map(i => i.display), '→', (l, v) => l + ' ' + v,
|
||||
{ protocol: 'sacn', nodeId: node.id, universes: sacnInfo.inputs.map(i => i.universe) });
|
||||
{ protocol: 'sacn', nodeName: getFirstName(node), universes: sacnInfo.inputs.map(i => i.universe) });
|
||||
} else {
|
||||
const container = div.querySelector(':scope > .sacn-in-hover');
|
||||
if (container) container.remove();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getShortLabel, isSwitch, findInterface } from './nodes.js';
|
||||
import { getShortLabel, getFirstName, isSwitch, findInterface } from './nodes.js';
|
||||
import { flowViewData, currentMode, currentView } from './state.js';
|
||||
|
||||
function scrollToNode(typeid) {
|
||||
@@ -89,7 +89,7 @@ export function showFlowView(flowSpec) {
|
||||
else {
|
||||
const sourceNode = nodesByTypeId.get(sourceId);
|
||||
const destNode = nodesByTypeId.get(destId);
|
||||
title = 'Dante: ' + getShortLabel(sourceNode) + ' → ' + getShortLabel(destNode);
|
||||
title = 'Dante: ' + getFirstName(sourceNode) + ' → ' + getFirstName(destNode);
|
||||
const path = findPath(graph, sourceId, destId);
|
||||
if (path) paths.push({ path, sourceId, destId });
|
||||
else error = 'No path found between nodes';
|
||||
@@ -102,7 +102,7 @@ export function showFlowView(flowSpec) {
|
||||
else {
|
||||
const sourceNode = nodesByTypeId.get(sourceId);
|
||||
const danteTx = sourceNode.dante_flows?.tx || [];
|
||||
title = 'Dante TX: ' + getShortLabel(sourceNode) + (txChannel ? ' ch ' + txChannel : '');
|
||||
title = 'Dante TX: ' + getFirstName(sourceNode) + (txChannel ? ' ch ' + txChannel : '');
|
||||
const destIds = new Set();
|
||||
danteTx.forEach(peer => {
|
||||
if (txChannel) {
|
||||
@@ -149,8 +149,8 @@ export function showFlowView(flowSpec) {
|
||||
const isSource = sourceIds.includes(clickedNodeId);
|
||||
const isDest = destIds.includes(clickedNodeId);
|
||||
if (isSource) {
|
||||
const destNames = destIds.filter(id => id !== clickedNodeId).map(id => getShortLabel(nodesByTypeId.get(id))).join(', ');
|
||||
title = protoName + ' ' + universe + ': ' + getShortLabel(clickedNode) + ' → ' + (destNames || '?');
|
||||
const destNames = destIds.filter(id => id !== clickedNodeId).map(id => getFirstName(nodesByTypeId.get(id))).join(', ');
|
||||
title = protoName + ' ' + universe + ': ' + getFirstName(clickedNode) + ' → ' + (destNames || '?');
|
||||
destIds.forEach(destId => {
|
||||
if (destId !== clickedNodeId) {
|
||||
const path = findPath(graph, clickedNodeId, destId);
|
||||
@@ -158,8 +158,8 @@ export function showFlowView(flowSpec) {
|
||||
}
|
||||
});
|
||||
} else if (isDest) {
|
||||
const sourceNames = sourceIds.map(id => getShortLabel(nodesByTypeId.get(id))).join(', ');
|
||||
title = protoName + ' ' + universe + ': ' + (sourceNames || '?') + ' → ' + getShortLabel(clickedNode);
|
||||
const sourceNames = sourceIds.map(id => getFirstName(nodesByTypeId.get(id))).join(', ');
|
||||
title = protoName + ' ' + universe + ': ' + (sourceNames || '?') + ' → ' + getFirstName(clickedNode);
|
||||
sourceIds.forEach(sourceId => {
|
||||
const path = findPath(graph, sourceId, clickedNodeId);
|
||||
if (path) paths.push({ path, sourceId, destId: clickedNodeId });
|
||||
@@ -324,8 +324,9 @@ export function renderFlowPath(pathInfo, nodesByTypeId, flowUniverse, flowProtoc
|
||||
|
||||
const nodeEl = document.createElement('div');
|
||||
nodeEl.className = 'flow-node';
|
||||
const isSourceNode = step.nodeId === sourceId && sourceId !== destId;
|
||||
if (isSwitch(node)) nodeEl.classList.add('switch');
|
||||
if (step.nodeId === sourceId && sourceId !== destId) nodeEl.classList.add('source');
|
||||
if (isSourceNode) nodeEl.classList.add('source');
|
||||
else if (step.nodeId === destId) nodeEl.classList.add('dest');
|
||||
nodeEl.textContent = getShortLabel(node);
|
||||
nodeEl.addEventListener('click', (e) => {
|
||||
@@ -333,29 +334,51 @@ export function renderFlowPath(pathInfo, nodesByTypeId, flowUniverse, flowProtoc
|
||||
closeFlowView();
|
||||
scrollToNode(step.nodeId);
|
||||
});
|
||||
container.appendChild(nodeEl);
|
||||
|
||||
let mappingsEl = null;
|
||||
if (node.artmap_mappings && node.artmap_mappings.length > 0 && flowUniverse !== undefined) {
|
||||
const relevantMappings = getRelevantMappings(node.artmap_mappings, flowProtocol, flowUniverse);
|
||||
if (relevantMappings.length > 0) {
|
||||
const mappingsEl = document.createElement('div');
|
||||
mappingsEl = document.createElement('div');
|
||||
mappingsEl.className = 'flow-artmap-mappings';
|
||||
if (isSourceNode) mappingsEl.classList.add('before-node');
|
||||
const currentPrefix = flowProtocol + ':' + flowUniverse;
|
||||
const nodeName = getFirstName(node);
|
||||
relevantMappings.forEach(m => {
|
||||
const mappingEl = document.createElement('div');
|
||||
mappingEl.className = 'artmap-mapping';
|
||||
mappingEl.textContent = m.from + ' → ' + m.to;
|
||||
const fromSpan = document.createElement('span');
|
||||
fromSpan.className = 'from';
|
||||
fromSpan.textContent = m.from;
|
||||
const arrowSpan = document.createElement('span');
|
||||
arrowSpan.textContent = '→';
|
||||
const toSpan = document.createElement('span');
|
||||
toSpan.className = 'to';
|
||||
toSpan.textContent = m.to;
|
||||
mappingEl.appendChild(fromSpan);
|
||||
mappingEl.appendChild(arrowSpan);
|
||||
mappingEl.appendChild(toSpan);
|
||||
mappingEl.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const toProto = m.to.split(':')[0];
|
||||
const toUniverse = parseInt(m.to.split(':')[1], 10);
|
||||
if (!isNaN(toUniverse)) {
|
||||
openFlowHash(toProto, toUniverse);
|
||||
const fromBase = m.from.split(':').slice(0, 2).join(':');
|
||||
const target = fromBase === currentPrefix ? m.to : m.from;
|
||||
const targetProto = target.split(':')[0];
|
||||
const targetUniverse = parseInt(target.split(':')[1], 10);
|
||||
if (!isNaN(targetUniverse)) {
|
||||
openFlowHash(targetProto, targetUniverse, nodeName);
|
||||
}
|
||||
});
|
||||
mappingsEl.appendChild(mappingEl);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (mappingsEl && isSourceNode) {
|
||||
container.appendChild(mappingsEl);
|
||||
}
|
||||
container.appendChild(nodeEl);
|
||||
if (mappingsEl && !isSourceNode) {
|
||||
container.appendChild(mappingsEl);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export function render(data, config) {
|
||||
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
||||
const channels = (peer.channels || []).map(formatDanteChannel);
|
||||
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
|
||||
return { text: peerName + channelSummary, peerId: peer.node_id };
|
||||
return { text: peerName + channelSummary, peerName };
|
||||
});
|
||||
|
||||
const rxEntries = danteRx.map(peer => {
|
||||
@@ -131,7 +131,7 @@ export function render(data, config) {
|
||||
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
||||
const channels = (peer.channels || []).map(formatDanteChannel);
|
||||
const channelSummary = channels.length > 0 ? '\n ' + channels.join('\n ') : '';
|
||||
return { text: peerName + channelSummary, peerId: peer.node_id };
|
||||
return { text: peerName + channelSummary, peerName };
|
||||
});
|
||||
|
||||
txEntries.sort((a, b) => a.text.split('\n')[0].localeCompare(b.text.split('\n')[0]));
|
||||
@@ -141,9 +141,10 @@ export function render(data, config) {
|
||||
isTx: danteTx.length > 0,
|
||||
isRx: danteRx.length > 0,
|
||||
txTo: txEntries.map(e => e.text),
|
||||
txToPeerIds: txEntries.map(e => e.peerId),
|
||||
txToPeerNames: txEntries.map(e => e.peerName),
|
||||
rxFrom: rxEntries.map(e => e.text),
|
||||
rxFromPeerIds: rxEntries.map(e => e.peerId)
|
||||
rxFromPeerNames: rxEntries.map(e => e.peerName),
|
||||
nodeName: getFirstName(node)
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getLabel, isSwitch, getInterfaceSpeed, getInterfaceErrors, getInterfaceRates } from './nodes.js';
|
||||
import { getLabel, getFirstName, isSwitch, getInterfaceSpeed, getInterfaceErrors, getInterfaceRates } from './nodes.js';
|
||||
import { buildSwitchUplinks } from './topology.js';
|
||||
import { escapeHtml, formatUniverse } from './format.js';
|
||||
import { tableData, tableSortKeys, setTableSortKeys } from './state.js';
|
||||
@@ -65,9 +65,9 @@ export function renderTable() {
|
||||
sortTable(th.dataset.sort);
|
||||
renderTable();
|
||||
});
|
||||
const sortKey = tableSortKeys.find(k => k.column === th.dataset.sort);
|
||||
if (sortKey) {
|
||||
th.classList.add(sortKey.asc ? 'sorted-asc' : 'sorted-desc');
|
||||
const primarySort = tableSortKeys[0];
|
||||
if (primarySort && primarySort.column === th.dataset.sort) {
|
||||
th.classList.add(primarySort.asc ? 'sorted-asc' : 'sorted-desc');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -217,15 +217,19 @@ export function renderDanteTable() {
|
||||
nodes.forEach(node => nodesByTypeId.set(node.id, node));
|
||||
let rows = [];
|
||||
nodes.forEach(node => {
|
||||
const name = getLabel(node);
|
||||
const name = getFirstName(node);
|
||||
const nameTitle = getLabel(node);
|
||||
const tx = node.dante_flows?.tx || [];
|
||||
tx.forEach(peer => {
|
||||
const peerNode = nodesByTypeId.get(peer.node_id);
|
||||
const peerName = peerNode ? getLabel(peerNode) : '??';
|
||||
const peerName = peerNode ? getFirstName(peerNode) : '??';
|
||||
const peerTitle = peerNode ? getLabel(peerNode) : '??';
|
||||
(peer.channels || []).forEach(ch => {
|
||||
rows.push({
|
||||
source: name,
|
||||
sourceTitle: nameTitle,
|
||||
dest: peerName,
|
||||
destTitle: peerTitle,
|
||||
txChannel: ch.tx_channel,
|
||||
rxChannel: ch.rx_channel,
|
||||
type: ch.type || '',
|
||||
@@ -233,7 +237,7 @@ export function renderDanteTable() {
|
||||
});
|
||||
});
|
||||
if (!peer.channels || peer.channels.length === 0) {
|
||||
rows.push({ source: name, dest: peerName, txChannel: '', rxChannel: 0, type: '', status: 'active' });
|
||||
rows.push({ source: name, sourceTitle: nameTitle, dest: peerName, destTitle: peerTitle, txChannel: '', rxChannel: 0, type: '', status: 'active' });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -252,9 +256,9 @@ export function renderDanteTable() {
|
||||
rows.forEach(r => {
|
||||
const statusClass = r.status === 'no-source' ? 'status-warn' : 'status-ok';
|
||||
html += '<tr>';
|
||||
html += '<td>' + escapeHtml(r.source) + '</td>';
|
||||
html += '<td' + (r.sourceTitle !== r.source ? ' data-tooltip="' + escapeHtml(r.sourceTitle) + '"' : '') + '>' + escapeHtml(r.source) + '</td>';
|
||||
html += '<td>' + escapeHtml(r.txChannel) + '</td>';
|
||||
html += '<td>' + escapeHtml(r.dest) + '</td>';
|
||||
html += '<td' + (r.destTitle !== r.dest ? ' data-tooltip="' + escapeHtml(r.destTitle) + '"' : '') + '>' + escapeHtml(r.dest) + '</td>';
|
||||
html += '<td class="numeric">' + (r.rxChannel || '') + '</td>';
|
||||
html += '<td>' + escapeHtml(r.type) + '</td>';
|
||||
html += '<td class="' + statusClass + '">' + escapeHtml(r.status) + '</td>';
|
||||
@@ -271,14 +275,15 @@ export function renderArtnetTable() {
|
||||
const rxByUniverse = new Map();
|
||||
|
||||
nodes.forEach(node => {
|
||||
const name = getLabel(node);
|
||||
const name = getFirstName(node);
|
||||
const title = getLabel(node);
|
||||
(node.artnet_inputs || []).forEach(u => {
|
||||
if (!txByUniverse.has(u)) txByUniverse.set(u, []);
|
||||
txByUniverse.get(u).push(name);
|
||||
txByUniverse.get(u).push({ name, title });
|
||||
});
|
||||
(node.artnet_outputs || []).forEach(u => {
|
||||
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
||||
rxByUniverse.get(u).push(name);
|
||||
rxByUniverse.get(u).push({ name, title });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -292,8 +297,10 @@ export function renderArtnetTable() {
|
||||
rows.push({
|
||||
universe: u,
|
||||
universeStr: formatUniverse(u),
|
||||
tx: txNodes[i] || '',
|
||||
rx: rxNodes[i] || ''
|
||||
tx: txNodes[i]?.name || '',
|
||||
txTitle: txNodes[i]?.title || '',
|
||||
rx: rxNodes[i]?.name || '',
|
||||
rxTitle: rxNodes[i]?.title || ''
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -308,9 +315,9 @@ export function renderArtnetTable() {
|
||||
|
||||
rows.forEach(r => {
|
||||
html += '<tr>';
|
||||
html += '<td>' + escapeHtml(r.tx) + '</td>';
|
||||
html += '<td' + (r.txTitle && r.txTitle !== r.tx ? ' data-tooltip="' + escapeHtml(r.txTitle) + '"' : '') + '>' + escapeHtml(r.tx) + '</td>';
|
||||
html += '<td>' + r.universeStr + '</td>';
|
||||
html += '<td>' + escapeHtml(r.rx) + '</td>';
|
||||
html += '<td' + (r.rxTitle && r.rxTitle !== r.rx ? ' data-tooltip="' + escapeHtml(r.rxTitle) + '"' : '') + '>' + escapeHtml(r.rx) + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
@@ -324,24 +331,26 @@ export function renderSacnTable() {
|
||||
const rxByUniverse = new Map();
|
||||
|
||||
nodes.forEach(node => {
|
||||
const name = getLabel(node);
|
||||
const name = getFirstName(node);
|
||||
const title = getLabel(node);
|
||||
(node.sacn_outputs || []).forEach(u => {
|
||||
if (!txByUniverse.has(u)) txByUniverse.set(u, []);
|
||||
txByUniverse.get(u).push(name);
|
||||
txByUniverse.get(u).push({ name, title });
|
||||
});
|
||||
(node.multicast_groups || []).forEach(g => {
|
||||
if (typeof g === 'string' && g.startsWith('sacn:')) {
|
||||
const u = parseInt(g.substring(5), 10);
|
||||
if (!isNaN(u)) {
|
||||
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
||||
rxByUniverse.get(u).push(name);
|
||||
rxByUniverse.get(u).push({ name, title });
|
||||
}
|
||||
}
|
||||
});
|
||||
(node.sacn_unicast_inputs || []).forEach(u => {
|
||||
if (!rxByUniverse.has(u)) rxByUniverse.set(u, []);
|
||||
if (!rxByUniverse.get(u).includes(name)) {
|
||||
rxByUniverse.get(u).push(name);
|
||||
const existing = rxByUniverse.get(u);
|
||||
if (!existing.some(e => e.name === name)) {
|
||||
existing.push({ name, title });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -355,8 +364,10 @@ export function renderSacnTable() {
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
rows.push({
|
||||
universe: u,
|
||||
tx: txNodes[i] || '',
|
||||
rx: rxNodes[i] || ''
|
||||
tx: txNodes[i]?.name || '',
|
||||
txTitle: txNodes[i]?.title || '',
|
||||
rx: rxNodes[i]?.name || '',
|
||||
rxTitle: rxNodes[i]?.title || ''
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -371,9 +382,9 @@ export function renderSacnTable() {
|
||||
|
||||
rows.forEach(r => {
|
||||
html += '<tr>';
|
||||
html += '<td>' + escapeHtml(r.tx) + '</td>';
|
||||
html += '<td' + (r.txTitle && r.txTitle !== r.tx ? ' data-tooltip="' + escapeHtml(r.txTitle) + '"' : '') + '>' + escapeHtml(r.tx) + '</td>';
|
||||
html += '<td class="numeric">' + r.universe + '</td>';
|
||||
html += '<td>' + escapeHtml(r.rx) + '</td>';
|
||||
html += '<td' + (r.rxTitle && r.rxTitle !== r.rx ? ' data-tooltip="' + escapeHtml(r.rxTitle) + '"' : '') + '>' + escapeHtml(r.rx) + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export function buildClickableList(container, items, label, plainFormat, flowInf
|
||||
val.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (flowInfo && flowInfo.universes && flowInfo.universes[idx] !== undefined) {
|
||||
openFlowHash(flowInfo.protocol, flowInfo.universes[idx], flowInfo.nodeId);
|
||||
openFlowHash(flowInfo.protocol, flowInfo.universes[idx], flowInfo.nodeName);
|
||||
} else {
|
||||
navigator.clipboard.writeText(item);
|
||||
}
|
||||
@@ -69,10 +69,10 @@ export function buildLinkStats(container, portLabel, speed, errIn, errOut, rates
|
||||
});
|
||||
}
|
||||
|
||||
export function buildDanteDetail(container, entries, arrow, sourceNodeId, peerNodeIds) {
|
||||
export function buildDanteDetail(container, entries, arrow, sourceNodeName, peerNodeNames) {
|
||||
const plainLines = [];
|
||||
entries.forEach((entry, entryIdx) => {
|
||||
const peerNodeId = peerNodeIds ? peerNodeIds[entryIdx] : null;
|
||||
const peerNodeName = peerNodeNames ? peerNodeNames[entryIdx] : null;
|
||||
entry.split('\n').forEach((line, lineIdx) => {
|
||||
if (entryIdx > 0 && lineIdx === 0) {
|
||||
container.appendChild(document.createTextNode('\n\n'));
|
||||
@@ -94,9 +94,9 @@ export function buildDanteDetail(container, entries, arrow, sourceNodeId, peerNo
|
||||
val.textContent = line;
|
||||
val.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (sourceNodeId && peerNodeId) {
|
||||
const src = arrow === '→' ? sourceNodeId : peerNodeId;
|
||||
const dst = arrow === '→' ? peerNodeId : sourceNodeId;
|
||||
if (sourceNodeName && peerNodeName) {
|
||||
const src = arrow === '→' ? sourceNodeName : peerNodeName;
|
||||
const dst = arrow === '→' ? peerNodeName : sourceNodeName;
|
||||
openFlowHash('dante', src, 'to', dst);
|
||||
} else {
|
||||
navigator.clipboard.writeText(line);
|
||||
|
||||
@@ -379,6 +379,37 @@ body.table-view #table-container {
|
||||
color: #f44;
|
||||
}
|
||||
|
||||
.data-table td[data-tooltip] {
|
||||
position: relative;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.data-table td[data-tooltip]::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 100%;
|
||||
margin-top: 4px;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.5);
|
||||
border: 1px solid #555;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.data-table td[data-tooltip]:hover::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
body.dante-mode .node {
|
||||
opacity: 0.3;
|
||||
}
|
||||
@@ -851,14 +882,22 @@ body.sacn-mode .node.sacn-out.sacn-in .sacn-in-hover .sacn-detail-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-left: 20px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #1a1a2e;
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid #5a5aff;
|
||||
box-shadow: 0 0 0 2px #5a5aff;
|
||||
}
|
||||
|
||||
.flow-artmap-mappings.before-node {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.artmap-mapping {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
color: #aaf;
|
||||
cursor: pointer;
|
||||
@@ -866,6 +905,14 @@ body.sacn-mode .node.sacn-out.sacn-in .sacn-in-hover .sacn-detail-wrapper {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.artmap-mapping .from {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.artmap-mapping .to {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.artmap-mapping:hover {
|
||||
background: #2a2a4e;
|
||||
color: #ccf;
|
||||
|
||||
Reference in New Issue
Block a user