-
-
- TRX paths on popup, directed decode paths when target locator is known
+
+
+ Filter by
+
+
+
+ Show
+
+
+
+ Rig
+
+
+
+ Search
+
+
+
+ History
+
+
+
+ Paths
+
+
+
+ TRX paths on popup, directed decode paths when target locator is known
+
-
-
-
-
+
+
+
+
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
index 1adcf86d..1b941d4d 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
@@ -2558,19 +2558,20 @@ button.map-qso-card:focus-visible {
}
/* A toolbar across the top of the map rather than a panel parked in a corner:
it leaves the map itself unobscured, and the bottom-left band legend keeps
- its place. It stops short of the corner controls, which stay outside it --
- the button that hides the filters cannot live inside the thing it hides. */
+ its place. Fullscreen and the filter toggle ride at its right-hand end;
+ only the filters themselves collapse, so the button that hides them is
+ still there to bring them back. */
.map-overlay-panel {
position: absolute;
top: 0.7rem;
/* Clear of Leaflet's zoom buttons, which draw over the top-left corner. */
left: 3.4rem;
- right: 9.6rem;
+ right: 0.7rem;
z-index: 410;
display: flex;
- flex-flow: row wrap;
+ flex-flow: row nowrap;
align-items: center;
- gap: 0.35rem 0.5rem;
+ gap: 0.5rem;
width: auto;
max-height: calc(100% - 1.4rem);
padding: 0.45rem 0.6rem;
@@ -2584,6 +2585,32 @@ button.map-qso-card:focus-visible {
overflow: auto;
transition: opacity 140ms ease, transform 140ms ease, visibility 140ms ease;
}
+.map-overlay-filters {
+ display: flex;
+ flex: 1 1 auto;
+ flex-flow: row wrap;
+ align-items: center;
+ gap: 0.35rem 0.5rem;
+ min-width: 0;
+}
+.map-overlay-filters.is-hidden {
+ display: none;
+}
+/* With the filters collapsed the bar has no reason to span the map. */
+.map-overlay-panel.filters-hidden {
+ left: auto;
+}
+.map-overlay-actions {
+ display: flex;
+ flex: 0 0 auto;
+ align-items: center;
+ gap: 0.4rem;
+}
+/* Set off from the filters, but only while there are filters to set off. */
+.map-overlay-panel:not(.filters-hidden) .map-overlay-actions {
+ padding-left: 0.5rem;
+ border-left: 1px solid color-mix(in srgb, var(--border-light) 55%, transparent);
+}
.map-overlay-panel .map-locator-filter-group {
flex: 0 1 auto;
/* The label stays beside its control; only the bar itself wraps. */
@@ -2628,16 +2655,6 @@ button.map-qso-card:focus-visible {
transform: translateY(0.25rem);
pointer-events: none;
}
-.map-corner-controls {
- position: absolute;
- top: 0.7rem;
- right: 0.7rem;
- z-index: 410;
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- gap: 0.4rem;
-}
.map-fullscreen-btn {
display: inline-flex;
align-items: center;
@@ -4166,7 +4183,7 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay {
top: 0.55rem;
left: 3.25rem;
right: 0.55rem;
- flex-direction: column;
+ flex-flow: column nowrap;
align-items: stretch;
width: auto;
max-height: min(16.5rem, calc(100% - 1.1rem));
@@ -4179,15 +4196,14 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay {
padding-right: 0;
border-right: 0;
}
+ .map-overlay-actions {
+ justify-content: flex-end;
+ }
.map-overlay-panel .map-paths-hint {
display: block;
font-size: 0.75rem;
color: var(--text-muted);
}
- .map-corner-controls {
- top: 0.55rem;
- right: 0.55rem;
- }
.map-band-legend {
left: 0.55rem;
bottom: 0.55rem;
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/map-core.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/map-core.ts
index 57b6d968..221c859f 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/map-core.ts
+++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/map-core.ts
@@ -1637,7 +1637,10 @@ const mapWindow = window as unknown as MapWindow;
function applyMapOverlayPanelVisibility() {
const panel = document.querySelector("#map-stage .map-overlay-panel");
if (!panel) return;
- panel.classList.toggle("is-hidden", !mapOverlayPanelVisible);
+ // Only the filters collapse. The bar itself stays, because it carries the
+ // button that brings them back.
+ panel.classList.toggle("filters-hidden", !mapOverlayPanelVisible);
+ panel.querySelector(".map-overlay-filters")?.classList.toggle("is-hidden", !mapOverlayPanelVisible);
}
function updateMapOverlayToggleButton() {
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs
index 6b6fcff7..5185be4a 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs
+++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs
@@ -376,6 +376,18 @@ try {
`text grew without a resize and the strip overlaps by ${unresized.overlap}px`);
assert.equal(unresized.iconsOnly, true, "the strip kept its labels with no room for them");
+ // The frequency readouts are typed into, so the browser remembers what went
+ // in and offers it back in a dropdown over the reading — Edge does this by
+ // default. Nothing here wants to be autofilled from what was tuned last week.
+ const autofill = await page.evaluate(() => ["freq", "center-freq"].map((id) => {
+ const input = document.getElementById(id);
+ return { id, autocomplete: input?.getAttribute("autocomplete") ?? null };
+ }));
+ for (const field of autofill) {
+ assert.equal(field.autocomplete, "off",
+ `#${field.id} offers autofill (autocomplete=${field.autocomplete})`);
+ }
+
} finally {
await browser.close();
await fixture.close();
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/spectrum-layout.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/spectrum-layout.mjs
index ed4251d0..b7bfed91 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/spectrum-layout.mjs
+++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/spectrum-layout.mjs
@@ -225,9 +225,11 @@ try {
// The map's filter panel is a bar across the top of the map, not a window
// sitting on it: it has to stay one or two rows tall, span most of the width,
-// and keep clear of the things that share the map's corners — Leaflet's zoom
-// buttons, the Fullscreen/Hide Filters controls, and the band legend. A panel
-// that grew a column would cover the map it filters.
+// and keep clear of what shares the map's corners — Leaflet's zoom buttons and
+// the band legend. A panel that grew a column would cover the map it filters.
+// Fullscreen and the filter toggle ride at the bar's right-hand end, so only
+// the filters collapse: the bar itself has to survive Hide Filters, or there
+// is nothing left to click to bring them back.
const mapFixture = await startWebFixture({ spectrum: true });
const mapView = await startBrowser(chromium);
try {
@@ -242,7 +244,6 @@ try {
const panel = document.querySelector(".map-overlay-panel");
const stage = box(document.getElementById("map-stage"));
const zoom = box(document.querySelector("#aprs-map .leaflet-control-zoom"));
- const corner = box(document.querySelector(".map-corner-controls"));
const legend = box(document.getElementById("map-band-legend"));
const panelBox = box(panel);
const hits = (a, b) => !!a && !!b
@@ -252,8 +253,10 @@ try {
height: Math.round(panelBox.height),
outsideStage: panelBox.right > stage.right + 1 || panelBox.bottom > stage.bottom + 1,
hitsZoom: hits(panelBox, zoom),
- hitsCorner: hits(panelBox, corner),
hitsLegend: hits(panelBox, legend),
+ // Both controls belong to the bar now, not to a floating corner block.
+ actionsInBar: [...panel.querySelectorAll(".map-overlay-actions button")]
+ .map((button) => button.id).join(","),
clipped: panel.scrollWidth > panel.clientWidth + 1 || panel.scrollHeight > panel.clientHeight + 1,
};
});
@@ -261,21 +264,41 @@ try {
assert.ok(bar.height <= 140, `the filter bar is ${bar.height}px tall at ${width}px, not a bar`);
assert.equal(bar.outsideStage, false, `the filter bar runs off the map at ${width}px`);
assert.equal(bar.hitsZoom, false, `the filter bar covers the zoom buttons at ${width}px`);
- assert.equal(bar.hitsCorner, false, `the filter bar covers the map controls at ${width}px`);
+ assert.equal(bar.actionsInBar, "map-fullscreen-btn,map-overlay-toggle-btn",
+ `the bar carries "${bar.actionsInBar}" at ${width}px`);
assert.equal(bar.hitsLegend, false, `the filter bar covers the band legend at ${width}px`);
assert.equal(bar.clipped, false, `the filter bar is clipping its own controls at ${width}px`);
}
- // Hiding it still works, and gives the whole map back.
+ // Hiding gives the map back, but leaves the bar itself — collapsed to its
+ // two controls at the right-hand edge — so the filters can be brought back.
await mapView.page.locator("#map-overlay-toggle-btn").click();
await mapView.page.waitForTimeout(400);
- const toggled = await mapView.page.evaluate(() => ({
- hidden: document.querySelector(".map-overlay-panel").classList.contains("is-hidden"),
- label: document.getElementById("map-overlay-toggle-btn").textContent.trim(),
- }));
- assert.equal(toggled.hidden, true, "the filter bar stayed up after Hide Filters");
+ const toggled = await mapView.page.evaluate(() => {
+ const panel = document.querySelector(".map-overlay-panel");
+ const stage = document.getElementById("map-stage").getBoundingClientRect();
+ const panelBox = panel.getBoundingClientRect();
+ const visible = (id) => document.getElementById(id).getBoundingClientRect().width > 0;
+ return {
+ filtersHidden: panel.querySelector(".map-overlay-filters").classList.contains("is-hidden"),
+ widthPct: Math.round((panelBox.width / stage.width) * 100),
+ rightGap: Math.round(stage.right - panelBox.right),
+ label: document.getElementById("map-overlay-toggle-btn").textContent.trim(),
+ togglesVisible: visible("map-fullscreen-btn") && visible("map-overlay-toggle-btn"),
+ };
+ });
+ assert.equal(toggled.filtersHidden, true, "the filters stayed up after Hide Filters");
+ assert.equal(toggled.togglesVisible, true, "Hide Filters took its own button down with it");
+ assert.ok(toggled.widthPct < 30, `the collapsed bar still covers ${toggled.widthPct}% of the map`);
+ assert.ok(toggled.rightGap < 30, `the collapsed bar sits ${toggled.rightGap}px from the map's edge`);
assert.equal(toggled.label, "Show Filters", `the toggle still reads "${toggled.label}"`);
+ await mapView.page.locator("#map-overlay-toggle-btn").click();
+ await mapView.page.waitForTimeout(400);
+ const restored = await mapView.page.evaluate(() =>
+ !document.querySelector(".map-overlay-filters").classList.contains("is-hidden"));
+ assert.equal(restored, true, "Show Filters did not bring the filters back");
+
assert.deepEqual(mapView.runtimeErrors, []);
} finally {
await mapView.browser.close();