feat: enhance waveform viewer with unit info display and event selection functionality

This commit is contained in:
Brian Harrison
2026-04-03 15:08:57 -04:00
parent 23e4febba6
commit e4730376ad
3 changed files with 191 additions and 38 deletions

View File

@@ -132,6 +132,49 @@
.ch-vert { color: #3fb950; }
.ch-long { color: #d29922; }
.ch-mic { color: #bc8cff; }
#unit-bar {
background: #0d1117;
border-bottom: 1px solid #21262d;
padding: 8px 20px;
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
font-size: 12px;
}
.unit-field { display: flex; flex-direction: column; gap: 1px; }
.unit-field .uf-label { color: #484f58; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; }
.unit-field .uf-value { color: #c9d1d9; font-family: monospace; font-size: 13px; }
.unit-field .uf-value.highlight { color: #58a6ff; font-weight: 600; }
.event-chips {
display: flex;
gap: 5px;
flex-wrap: wrap;
margin-left: 8px;
}
.event-chip {
background: #21262d;
border: 1px solid #30363d;
border-radius: 5px;
color: #8b949e;
cursor: pointer;
font-size: 12px;
padding: 3px 10px;
transition: all 0.12s;
}
.event-chip:hover { background: #1f6feb; border-color: #1f6feb; color: #fff; }
.event-chip.active { background: #1f6feb; border-color: #388bfd; color: #fff; font-weight: 600; }
#connect-btn {
background: #238636;
margin-left: auto;
}
#connect-btn:hover { background: #2ea043; }
#connect-btn:disabled { background: #21262d; color: #484f58; }
</style>
</head>
<body>
@@ -147,15 +190,35 @@
<input type="text" id="dev-host" value="" placeholder="e.g. 10.0.0.5" />
<label>TCP port</label>
<input type="number" id="dev-tcp-port" value="9034" />
<label>Event #</label>
<input type="number" id="event-index" value="0" min="0" style="width:55px" />
</div>
<button id="load-btn" onclick="loadWaveform()">Load Waveform</button>
<button id="connect-btn" onclick="connectUnit()">Connect</button>
<button id="load-btn" onclick="loadWaveform()" disabled>Load Waveform</button>
<button id="prev-btn" onclick="stepEvent(-1)" disabled>◀ Prev</button>
<button id="next-btn" onclick="stepEvent(+1)" disabled>Next ▶</button>
</header>
<div id="status-bar">Ready — enter device host and click Load Waveform.</div>
<!-- Unit info bar — hidden until connected -->
<div id="unit-bar" style="display:none">
<div class="unit-field">
<span class="uf-label">Serial</span>
<span class="uf-value" id="u-serial"></span>
</div>
<div class="unit-field">
<span class="uf-label">Firmware</span>
<span class="uf-value" id="u-fw"></span>
</div>
<div class="unit-field">
<span class="uf-label">Sample rate</span>
<span class="uf-value" id="u-sr"></span>
</div>
<div class="unit-field">
<span class="uf-label">Events</span>
<span class="uf-value highlight" id="u-count"></span>
</div>
<div class="event-chips" id="event-chips"></div>
</div>
<div id="status-bar">Ready — enter device host and click Connect.</div>
<div id="empty-state">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
@@ -176,6 +239,8 @@
let charts = {};
let lastData = null;
let unitInfo = null;
let currentEventIndex = 0;
function setStatus(msg, cls = '') {
const bar = document.getElementById('status-bar');
@@ -191,11 +256,84 @@
bar.appendChild(pill);
}
async function connectUnit() {
const apiBase = document.getElementById('api-base').value.replace(/\/$/, '');
const devHost = document.getElementById('dev-host').value.trim();
const tcpPort = document.getElementById('dev-tcp-port').value;
if (!devHost) { setStatus('Enter a device host first.', 'error'); return; }
const btn = document.getElementById('connect-btn');
btn.disabled = true;
btn.textContent = 'Connecting…';
setStatus('Connecting to unit…', 'loading');
const url = `${apiBase}/device/info?host=${encodeURIComponent(devHost)}&tcp_port=${tcpPort}`;
try {
const resp = await fetch(url);
if (!resp.ok) {
const err = await resp.json().catch(() => ({ detail: resp.statusText }));
throw new Error(err.detail || resp.statusText);
}
unitInfo = await resp.json();
} catch (e) {
setStatus(`Error: ${e.message}`, 'error');
btn.disabled = false;
btn.textContent = 'Connect';
return;
}
// Populate unit bar
document.getElementById('u-serial').textContent = unitInfo.serial || '—';
document.getElementById('u-fw').textContent = unitInfo.firmware_version || '—';
const sr = unitInfo.compliance_config?.sample_rate;
document.getElementById('u-sr').textContent = sr ? `${sr} sps` : '—';
const count = unitInfo.event_count ?? 0;
document.getElementById('u-count').textContent = count;
// Build event chips
const chipsEl = document.getElementById('event-chips');
chipsEl.innerHTML = '';
for (let i = 0; i < count; i++) {
const chip = document.createElement('button');
chip.className = 'event-chip' + (i === 0 ? ' active' : '');
chip.textContent = `Event ${i}`;
chip.onclick = () => selectEvent(i);
chipsEl.appendChild(chip);
}
document.getElementById('unit-bar').style.display = 'flex';
document.getElementById('load-btn').disabled = count === 0;
document.getElementById('prev-btn').disabled = true;
document.getElementById('next-btn').disabled = count <= 1;
btn.disabled = false;
btn.textContent = 'Reconnect';
if (count === 0) {
setStatus('Connected — no events stored on device.', 'ok');
} else {
setStatus(`Connected — ${count} event${count !== 1 ? 's' : ''} stored. Select an event or click Load Waveform.`, 'ok');
}
}
function selectEvent(idx) {
currentEventIndex = idx;
// Update chip highlight
document.querySelectorAll('.event-chip').forEach((c, i) => {
c.classList.toggle('active', i === idx);
});
document.getElementById('prev-btn').disabled = idx <= 0;
const count = unitInfo?.event_count ?? 0;
document.getElementById('next-btn').disabled = idx >= count - 1;
loadWaveform();
}
async function loadWaveform() {
const apiBase = document.getElementById('api-base').value.replace(/\/$/, '');
const devHost = document.getElementById('dev-host').value.trim();
const tcpPort = document.getElementById('dev-tcp-port').value;
const evIndex = parseInt(document.getElementById('event-index').value, 10);
const apiBase = document.getElementById('api-base').value.replace(/\/$/, '');
const devHost = document.getElementById('dev-host').value.trim();
const tcpPort = document.getElementById('dev-tcp-port').value;
const evIndex = currentEventIndex;
if (!devHost) { setStatus('Enter a device host first.', 'error'); return; }
@@ -222,15 +360,12 @@
lastData = data;
renderWaveform(data);
btn.disabled = false;
document.getElementById('prev-btn').disabled = evIndex <= 0;
document.getElementById('next-btn').disabled = false;
}
function stepEvent(delta) {
const el = document.getElementById('event-index');
const next = Math.max(0, parseInt(el.value, 10) + delta);
el.value = next;
loadWaveform();
const count = unitInfo?.event_count ?? 0;
const next = Math.max(0, Math.min(count - 1, currentEventIndex + delta));
selectEvent(next);
}
function renderWaveform(data) {
@@ -379,9 +514,11 @@
}
}
// Allow Enter key on inputs to trigger load
document.querySelectorAll('input').forEach(el => {
el.addEventListener('keydown', e => { if (e.key === 'Enter') loadWaveform(); });
// Allow Enter key on connection inputs to trigger connect
['api-base', 'dev-host', 'dev-tcp-port'].forEach(id => {
document.getElementById(id).addEventListener('keydown', e => {
if (e.key === 'Enter') connectUnit();
});
});
</script>
</body>