webui: add calibration editor to the V-Scale toolbar
Adds the Cal row (Scale / Offset / Unit / Reset) to #vscale-menu, its CSS, and the refreshVScaleMenu() / commitCal() logic that wires it to calTable and the hub via setCalibrationWS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0e9ec61226
commit
4e3a90b2c6
@@ -3514,6 +3514,32 @@ function escHtml(s) {
|
|||||||
════════════════════════════════════════════════════════════════ */
|
════════════════════════════════════════════════════════════════ */
|
||||||
let _vsMenuKey = null, _vsMenuPlotId = null;
|
let _vsMenuKey = null, _vsMenuPlotId = null;
|
||||||
|
|
||||||
|
// Re-read the calibration fields from calTable for the currently open toolbar.
|
||||||
|
// Safe to call when the toolbar is closed.
|
||||||
|
function refreshVScaleMenu() {
|
||||||
|
if (!_vsMenuKey) return;
|
||||||
|
const cal = calForKey(_vsMenuKey);
|
||||||
|
const base = baseSigForKey(_vsMenuKey);
|
||||||
|
const meta = findSignalMeta(_vsMenuKey);
|
||||||
|
const n = meta ? numElements(meta) : 1;
|
||||||
|
const lbl = document.getElementById('vscale-cal-lbl');
|
||||||
|
lbl.textContent = n > 1 ? 'Cal (' + base + ', ' + n + ' elem)' : 'Cal (' + base + ')';
|
||||||
|
const scaleEl = document.getElementById('vscale-cal-scale');
|
||||||
|
const offsetEl = document.getElementById('vscale-cal-offset');
|
||||||
|
const unitEl = document.getElementById('vscale-cal-unit');
|
||||||
|
// Skip the field the user is currently typing in, so a hub broadcast does not
|
||||||
|
// yank the caret out from under them.
|
||||||
|
const focused = document.activeElement;
|
||||||
|
if (focused !== scaleEl) scaleEl.value = cal.scale;
|
||||||
|
if (focused !== offsetEl) offsetEl.value = cal.offset;
|
||||||
|
if (focused !== unitEl) unitEl.value = cal.unit;
|
||||||
|
[scaleEl, offsetEl, unitEl].forEach(el => el.classList.remove('cal-invalid'));
|
||||||
|
const srcLabel = srcLabelForKey(_vsMenuKey);
|
||||||
|
const usable = srcLabel !== '' && base !== '';
|
||||||
|
[scaleEl, offsetEl, unitEl, document.getElementById('btn-cal-reset')]
|
||||||
|
.forEach(el => { el.disabled = !usable; });
|
||||||
|
}
|
||||||
|
|
||||||
function showVScaleMenu(key, plotId) {
|
function showVScaleMenu(key, plotId) {
|
||||||
hideSignalMenu();
|
hideSignalMenu();
|
||||||
// If the toolbar was open for a different plot, hide that bar first.
|
// If the toolbar was open for a different plot, hide that bar first.
|
||||||
@@ -3562,6 +3588,8 @@ function showVScaleMenu(key, plotId) {
|
|||||||
btn.classList.toggle('active', btn.dataset.type === (vs.digitalInMixed ? 'digital' : 'analog')));
|
btn.classList.toggle('active', btn.dataset.type === (vs.digitalInMixed ? 'digital' : 'analog')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshVScaleMenu();
|
||||||
|
|
||||||
// Move the toolbar div into this plot's vscale bar.
|
// Move the toolbar div into this plot's vscale bar.
|
||||||
const bar = document.getElementById('vstb-' + plotId);
|
const bar = document.getElementById('vstb-' + plotId);
|
||||||
if (bar) {
|
if (bar) {
|
||||||
@@ -3686,6 +3714,46 @@ function initVScaleMenu() {
|
|||||||
if (p) { createUPlot(p); p.needsRedraw = true; }
|
if (p) { createUPlot(p); p.needsRedraw = true; }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// ── Calibration ───────────────────────────────────────────────────────
|
||||||
|
// Commit the three fields as one entry. Validation mirrors the hub exactly
|
||||||
|
// (Calib.normaliseCal); an invalid value marks the field and is not sent, so
|
||||||
|
// the last accepted value stays in force.
|
||||||
|
function commitCal() {
|
||||||
|
if (!_vsMenuKey) return;
|
||||||
|
const scaleEl = document.getElementById('vscale-cal-scale');
|
||||||
|
const offsetEl = document.getElementById('vscale-cal-offset');
|
||||||
|
const unitEl = document.getElementById('vscale-cal-unit');
|
||||||
|
const source = srcLabelForKey(_vsMenuKey);
|
||||||
|
const signal = baseSigForKey(_vsMenuKey);
|
||||||
|
const entry = Calib.normaliseCal({
|
||||||
|
source, signal,
|
||||||
|
scale: parseFloat(scaleEl.value),
|
||||||
|
offset: parseFloat(offsetEl.value),
|
||||||
|
unit: unitEl.value,
|
||||||
|
});
|
||||||
|
const scaleBad = entry === null && !(isFinite(parseFloat(scaleEl.value)) && parseFloat(scaleEl.value) !== 0);
|
||||||
|
scaleEl.classList.toggle('cal-invalid', scaleBad);
|
||||||
|
offsetEl.classList.toggle('cal-invalid', entry === null && !isFinite(parseFloat(offsetEl.value)));
|
||||||
|
if (entry === null) return;
|
||||||
|
calTable.set(entry);
|
||||||
|
setCalibrationWS(entry.source, entry.signal, entry.scale, entry.offset, entry.unit);
|
||||||
|
applyCalibrationChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('vscale-cal-scale').addEventListener('change', commitCal);
|
||||||
|
document.getElementById('vscale-cal-offset').addEventListener('change', commitCal);
|
||||||
|
document.getElementById('vscale-cal-unit').addEventListener('change', commitCal);
|
||||||
|
document.getElementById('btn-cal-reset').addEventListener('click', () => {
|
||||||
|
if (!_vsMenuKey) return;
|
||||||
|
const source = srcLabelForKey(_vsMenuKey);
|
||||||
|
const signal = baseSigForKey(_vsMenuKey);
|
||||||
|
if (!source || !signal) return;
|
||||||
|
calTable.set({ source, signal, scale: 1, offset: 0, unit: '' });
|
||||||
|
setCalibrationWS(source, signal, 1, 0, '');
|
||||||
|
applyCalibrationChanged();
|
||||||
|
refreshVScaleMenu();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('btn-vscale-close').addEventListener('click', hideVScaleMenu);
|
document.getElementById('btn-vscale-close').addEventListener('click', hideVScaleMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,18 @@
|
|||||||
<button class="ctx-btn" data-type="digital">Digital</button>
|
<button class="ctx-btn" data-type="digital">Digital</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="vstb-sep"></div>
|
||||||
|
<div id="vscale-cal-row" style="display:flex;align-items:center;gap:4px">
|
||||||
|
<label class="vstb-lbl" id="vscale-cal-lbl"
|
||||||
|
title="Data calibration: value = raw × Scale + Offset. Applies to the plot, cursors, hover readout, CSV export and trigger threshold.">Cal</label>
|
||||||
|
<label class="vstb-lbl">Scale</label>
|
||||||
|
<input type="number" id="vscale-cal-scale" class="ctx-num ctx-num-sm" step="any" value="1">
|
||||||
|
<label class="vstb-lbl">Offset</label>
|
||||||
|
<input type="number" id="vscale-cal-offset" class="ctx-num ctx-num-sm" step="any" value="0">
|
||||||
|
<label class="vstb-lbl">Unit</label>
|
||||||
|
<input type="text" id="vscale-cal-unit" class="ctx-num ctx-num-xs" maxlength="16" placeholder="—">
|
||||||
|
<button class="ctx-btn" id="btn-cal-reset" title="Clear this signal's calibration">Reset</button>
|
||||||
|
</div>
|
||||||
<button id="btn-vscale-close" class="vstb-close" title="Close">✕</button>
|
<button id="btn-vscale-close" class="vstb-close" title="Close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -386,6 +386,11 @@ input[type=range].trig-range::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
.vstb-close:hover { color:var(--red); }
|
.vstb-close:hover { color:var(--red); }
|
||||||
.plot-vscale-bar { display:none; }
|
.plot-vscale-bar { display:none; }
|
||||||
|
.vstb-sep { width:1px; height:16px; background:var(--surface1); flex-shrink:0; }
|
||||||
|
.ctx-num-sm { width:70px; }
|
||||||
|
.ctx-num-xs { width:46px; }
|
||||||
|
#vscale-cal-lbl { color:var(--mauve); font-weight:600; }
|
||||||
|
.cal-invalid { border-color:var(--red) !important; }
|
||||||
|
|
||||||
/* ── Per-plot cursor value readout (in plot card header) ────────── */
|
/* ── Per-plot cursor value readout (in plot card header) ────────── */
|
||||||
.plot-cursor-ro {
|
.plot-cursor-ro {
|
||||||
|
|||||||
Reference in New Issue
Block a user