[style](trx-frontend-http): make the spectrum control strip one strip
The row of controls under the plot held four different control heights, units as loose text beside the field they belonged to, and a quarter of its width as a hole in the middle. Between about 1100 and 1400 px it came apart: the bandwidth cluster wrapped to two lines while the level cluster stayed on one, so the two sat at heights that matched neither each other nor anything else on the page. Every control stays, in its order, with its name and its behaviour. This is the styling and the layout. A field is now one box -- name, value and unit inside a single border -- so a number cannot be read apart from the unit it is in. Fields, buttons, the peak-hold select and the contrast slider are one height, border-box so a button's own border cannot add two pixels to it, and 2.4rem under a coarse pointer where a fingertip needs the room. The contrast readout holds a fixed, tabular slot, so the row no longer twitches between 1.0 and 0.9. The container wraps and a cluster does not: a cluster that will not fit drops whole to the next line and starts it left-aligned. The slack goes to a spacer rather than to `space-between`, which is what opened the hole. Two things this turned up. The select carries `status-input` for other layouts' sake, which drew a box inside the field's box. And the narrow -screen rules lived in a media query earlier in the file than the rules they override -- identical specificity, so the later one won and the phone layout had been overflowing sideways rather than stacking. The narrow rules now sit directly after what they override. The layout test measures the strip at three widths: one height across every control, no overflow, inside the plot, and clusters either sharing a line or each having one -- never one floating against the middle of the other. docs/Spectrum-Controls-Rework.md records what was wrong and what was deliberately left alone: the two different Autos, the settings that do not persist, the one-shot buttons, and Sweet-spot's silence while it retunes the SDR. Those are behaviour, and are for another day. Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -163,13 +163,14 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<div id="spectrum-size-grip" title="Drag to resize spectrum height" aria-label="Resize spectrum height"></div>
|
||||
<div id="spectrum-controls">
|
||||
<div id="spectrum-bw-row">
|
||||
<label id="spectrum-bw-label">Bandwidth <input type="number" id="spectrum-bw-input" value="" step="0.1" min="0.1" /> kHz</label>
|
||||
<button id="spectrum-bw-set-btn" type="button">Set</button>
|
||||
<button id="spectrum-bw-auto-btn" type="button">Auto BW</button>
|
||||
<button id="spectrum-bw-sweet-btn" type="button">Sweet-spot</button>
|
||||
<label id="spectrum-bw-label" class="spectrum-field"><span class="spectrum-field-name">Bandwidth</span><input type="number" id="spectrum-bw-input" value="" step="0.1" min="0.1" /><span class="spectrum-field-unit">kHz</span></label>
|
||||
<button id="spectrum-bw-set-btn" class="spectrum-btn" type="button">Set</button>
|
||||
<button id="spectrum-bw-auto-btn" class="spectrum-btn" type="button">Auto BW</button>
|
||||
<button id="spectrum-bw-sweet-btn" class="spectrum-btn" type="button">Sweet-spot</button>
|
||||
</div>
|
||||
<div class="spectrum-controls-spacer" aria-hidden="true"></div>
|
||||
<div id="spectrum-level-row">
|
||||
<label class="overview-control" id="spectrum-peak-hold-label">Peak Hold
|
||||
<label class="overview-control spectrum-field" id="spectrum-peak-hold-label"><span class="spectrum-field-name">Peak Hold</span>
|
||||
<select id="overview-peak-hold" class="status-input">
|
||||
<option value="0">Off</option>
|
||||
<option value="500">0.5 s</option>
|
||||
@@ -182,10 +183,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<option value="60000">60 s</option>
|
||||
</select>
|
||||
</label>
|
||||
<label id="spectrum-floor-label">Floor <input type="number" id="spectrum-floor-input" value="-115" step="5" /> dB</label>
|
||||
<label id="spectrum-range-label">Range <input type="number" id="spectrum-range-input" value="90" step="10" min="10" /> dB</label>
|
||||
<button id="spectrum-auto-btn" type="button">Auto</button>
|
||||
<label id="spectrum-gamma-label">Contrast <input type="range" id="spectrum-gamma-input" min="0.2" max="3.0" step="0.1" value="1.0" /><span id="spectrum-gamma-value">1.0</span></label>
|
||||
<label id="spectrum-floor-label" class="spectrum-field"><span class="spectrum-field-name">Floor</span><input type="number" id="spectrum-floor-input" value="-115" step="5" /><span class="spectrum-field-unit">dB</span></label>
|
||||
<label id="spectrum-range-label" class="spectrum-field"><span class="spectrum-field-name">Range</span><input type="number" id="spectrum-range-input" value="90" step="10" min="10" /><span class="spectrum-field-unit">dB</span></label>
|
||||
<button id="spectrum-auto-btn" class="spectrum-btn" type="button">Auto</button>
|
||||
<label id="spectrum-gamma-label" class="spectrum-field"><span class="spectrum-field-name">Contrast</span><input type="range" id="spectrum-gamma-input" min="0.2" max="3.0" step="0.1" value="1.0" /><span id="spectrum-gamma-value" class="spectrum-field-value">1.0</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="spectrum-hint" class="spectrum-hint-mouse">Scroll to zoom · Ctrl+Scroll to tune · Drag to pan · Drag BW edges to resize · +/- zoom · Arrows pan · 0 reset</div>
|
||||
|
||||
@@ -4131,47 +4131,6 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay {
|
||||
.signal-measure #sig-result {
|
||||
width: 100%;
|
||||
}
|
||||
#spectrum-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.45rem;
|
||||
padding-top: 0.45rem;
|
||||
}
|
||||
#spectrum-bw-row,
|
||||
#spectrum-level-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem 0.4rem;
|
||||
}
|
||||
#spectrum-bw-label,
|
||||
#spectrum-floor-label,
|
||||
#spectrum-range-label,
|
||||
#spectrum-peak-hold-label {
|
||||
flex: 1 1 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#spectrum-gamma-label {
|
||||
flex: 1 1 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#spectrum-gamma-input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 3rem;
|
||||
}
|
||||
#spectrum-bw-input,
|
||||
#spectrum-floor-input,
|
||||
#spectrum-range-input,
|
||||
#overview-peak-hold {
|
||||
flex: 1 1 auto;
|
||||
min-width: 3rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#spectrum-bw-set-btn,
|
||||
#spectrum-bw-auto-btn,
|
||||
#spectrum-bw-sweet-btn,
|
||||
#spectrum-auto-btn {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.spectrum-edge-shift {
|
||||
width: 0.95rem;
|
||||
}
|
||||
@@ -4821,111 +4780,166 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay {
|
||||
white-space: nowrap;
|
||||
z-index: 10;
|
||||
}
|
||||
/* ── Spectrum control strip ────────────────────────────────────────────
|
||||
Two clusters of controls under the plot: the receiver's bandwidth on the
|
||||
left, the display's levels on the right. Everything in here is one height
|
||||
and one shape — a row whose controls each size themselves reads as a pile
|
||||
rather than a strip.
|
||||
|
||||
The container wraps; a cluster does not. A cluster that cannot fit drops
|
||||
whole to the next line and starts it left-aligned, rather than splitting and
|
||||
leaving the cluster beside it floating at a height of its own, which is what
|
||||
the two rows did to each other between 1100 and 1400 px. */
|
||||
#spectrum-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding: 3px 4px 0;
|
||||
gap: 0.6rem;
|
||||
gap: 0.35rem 0.5rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
#spectrum-bw-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
#spectrum-bw-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
min-width: 0;
|
||||
}
|
||||
#spectrum-bw-input {
|
||||
width: 4.5rem;
|
||||
padding: 1px 4px;
|
||||
font-size: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
background: var(--input-bg);
|
||||
color: var(--text);
|
||||
text-align: right;
|
||||
height: 1.5rem;
|
||||
}
|
||||
#spectrum-bw-set-btn,
|
||||
#spectrum-bw-auto-btn,
|
||||
#spectrum-bw-sweet-btn {
|
||||
height: 1.5rem;
|
||||
min-height: 0;
|
||||
padding: 0 8px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
#spectrum-bw-row,
|
||||
#spectrum-level-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
#spectrum-floor-label {
|
||||
display: flex;
|
||||
/* Takes the slack, so the clusters reach the ends of a wide strip without
|
||||
`space-between` opening a quarter-width hole in the middle of it. */
|
||||
.spectrum-controls-spacer {
|
||||
flex: 1 1 1.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* A field is one box: name, value and unit share a border, so a number can
|
||||
never be read apart from the unit it is in. */
|
||||
.spectrum-field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
/* Border-box on both field and button, or the button's own border and
|
||||
padding add to the height and the two end up a couple of pixels apart —
|
||||
which is the pile-of-controls look this is meant to end. */
|
||||
box-sizing: border-box;
|
||||
height: 1.7rem;
|
||||
padding: 0 0.4rem 0 0.5rem;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 6px;
|
||||
background: var(--input-bg);
|
||||
white-space: nowrap;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
#spectrum-floor-input {
|
||||
width: 3.4rem;
|
||||
padding: 1px 4px;
|
||||
font-size: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
background: var(--input-bg);
|
||||
color: var(--text);
|
||||
text-align: right;
|
||||
height: 1.5rem;
|
||||
.spectrum-field:focus-within {
|
||||
border-color: color-mix(in srgb, var(--accent-green) 45%, var(--border-light));
|
||||
}
|
||||
#spectrum-range-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
.spectrum-field-name {
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
#spectrum-range-input {
|
||||
width: 3.4rem;
|
||||
padding: 1px 4px;
|
||||
font-size: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
background: var(--input-bg);
|
||||
/* Unit and readout hold a fixed, tabular slot: the row must not twitch as a
|
||||
value goes from 1.0 to 0.9. */
|
||||
.spectrum-field-unit,
|
||||
.spectrum-field-value {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 1.7rem;
|
||||
}
|
||||
.spectrum-field-value {
|
||||
color: var(--text);
|
||||
text-align: right;
|
||||
height: 1.5rem;
|
||||
}
|
||||
#spectrum-auto-btn {
|
||||
height: 1.5rem;
|
||||
.spectrum-field input[type="number"] {
|
||||
width: 3.2rem;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font-size: 0.78rem;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.spectrum-field input[type="number"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
#spectrum-bw-input {
|
||||
width: 3.6rem;
|
||||
}
|
||||
/* The select carries `status-input` for the sake of other layouts, which gives
|
||||
it a border and a background of its own; inside a field that reads as a box
|
||||
drawn inside a box. */
|
||||
#spectrum-controls .spectrum-field select {
|
||||
width: auto;
|
||||
height: 1.35rem;
|
||||
min-height: 0;
|
||||
padding: 0 8px;
|
||||
font-size: 0.75rem;
|
||||
padding: 0 0.1rem;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
#spectrum-gamma-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
#spectrum-gamma-input {
|
||||
width: 5rem;
|
||||
height: 1.5rem;
|
||||
.spectrum-field input[type="range"] {
|
||||
width: 5.5rem;
|
||||
height: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
#spectrum-gamma-value {
|
||||
font-size: 0.75rem;
|
||||
min-width: 1.6rem;
|
||||
text-align: right;
|
||||
.spectrum-btn {
|
||||
box-sizing: border-box;
|
||||
height: 1.7rem;
|
||||
min-height: 0;
|
||||
padding: 0 0.6rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.73rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Fingers need more than a 27 px target; a mouse does not, and the strip has
|
||||
no width to spare on a desktop. */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.spectrum-field,
|
||||
.spectrum-btn {
|
||||
height: 2.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Narrow screens: the same fields, stacked. This sits after the rules it
|
||||
overrides — `#spectrum-bw-row` in both places has identical specificity, so
|
||||
whichever comes last wins, and from inside the earlier media query the
|
||||
narrow rules silently lost. */
|
||||
@media (max-width: 640px) {
|
||||
/* Narrow: the clusters stack, and a field takes the width so its name and
|
||||
its value sit at opposite ends of a line the thumb can hit. */
|
||||
#spectrum-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.45rem;
|
||||
padding-top: 0.45rem;
|
||||
}
|
||||
#spectrum-bw-row,
|
||||
#spectrum-level-row {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem 0.4rem;
|
||||
}
|
||||
.spectrum-controls-spacer {
|
||||
display: none;
|
||||
}
|
||||
#spectrum-controls .spectrum-field {
|
||||
flex: 1 1 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#spectrum-controls .spectrum-field input[type="number"],
|
||||
#spectrum-controls .spectrum-field input[type="range"],
|
||||
#spectrum-controls .spectrum-field select {
|
||||
flex: 1 1 auto;
|
||||
min-width: 3rem;
|
||||
}
|
||||
#spectrum-controls .spectrum-btn {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
.spectrum-hint-mouse,
|
||||
.spectrum-hint-touch {
|
||||
|
||||
@@ -196,6 +196,62 @@ try {
|
||||
await fixture.close();
|
||||
}
|
||||
|
||||
// The strip of controls under the plot. Its two clusters — the receiver's
|
||||
// bandwidth and the display's levels — used to size themselves independently:
|
||||
// four control heights on one line, and between about 1100 and 1400 px the
|
||||
// left cluster wrapped to two lines while the right one did not, leaving the
|
||||
// two at heights that matched neither each other nor anything else.
|
||||
const stripFixture = await startWebFixture({ spectrum: true });
|
||||
const strip = await startBrowser(chromium);
|
||||
try {
|
||||
for (const width of [1600, 1200, 900]) {
|
||||
await strip.page.setViewportSize({ width, height: 950 });
|
||||
await strip.page.goto(stripFixture.origin, { waitUntil: "domcontentloaded" });
|
||||
await strip.page.locator("#spectrum-panel").waitFor({ state: "visible" });
|
||||
await strip.page.waitForTimeout(1600);
|
||||
|
||||
const measured = await strip.page.evaluate(() => {
|
||||
const controls = document.getElementById("spectrum-controls");
|
||||
const box = (element) => element.getBoundingClientRect();
|
||||
const clusters = [...controls.children]
|
||||
.filter((child) => child.id)
|
||||
.map((child) => ({ id: child.id, top: Math.round(box(child).top) }));
|
||||
const parts = [...controls.querySelectorAll(".spectrum-field, .spectrum-btn")];
|
||||
return {
|
||||
heights: [...new Set(parts.map((part) => Math.round(box(part).height)))],
|
||||
count: parts.length,
|
||||
clusters,
|
||||
// Rows are lines of the strip: clusters sharing a top are on one line.
|
||||
lines: new Set(clusters.map((cluster) => cluster.top)).size,
|
||||
overflows: controls.scrollWidth > controls.clientWidth + 1,
|
||||
insidePanel: box(controls).right
|
||||
<= box(document.getElementById("spectrum-panel")).right + 1,
|
||||
};
|
||||
});
|
||||
|
||||
// Five fields and four buttons: bandwidth, Set, Auto BW, Sweet-spot, peak
|
||||
// hold, floor, range, Auto, contrast.
|
||||
assert.equal(measured.count, 9, `the strip has ${measured.count} controls at ${width}px`);
|
||||
assert.deepEqual(measured.heights.length, 1,
|
||||
`controls are ${measured.heights.join(", ")}px tall at ${width}px`);
|
||||
assert.equal(measured.overflows, false, `the strip overflows at ${width}px`);
|
||||
assert.equal(measured.insidePanel, true, `the strip runs past the plot at ${width}px`);
|
||||
|
||||
// Either both clusters share a line, or each has one to itself. What must
|
||||
// never happen is one cluster floating against the middle of the other.
|
||||
const tops = measured.clusters.map((cluster) => cluster.top);
|
||||
assert.ok(
|
||||
measured.lines === 1 || measured.lines === measured.clusters.length,
|
||||
`clusters sit at ${tops.join(", ")} at ${width}px`,
|
||||
);
|
||||
}
|
||||
|
||||
assert.deepEqual(strip.runtimeErrors, []);
|
||||
} finally {
|
||||
await strip.browser.close();
|
||||
await stripFixture.close();
|
||||
}
|
||||
|
||||
// The band plan is fetched once at startup, which can land before the session
|
||||
// exists. It used to fail silently and never retry, so the allocations only
|
||||
// turned up if the operator reloaded the page by hand.
|
||||
|
||||
Reference in New Issue
Block a user