[fix](trx-frontend-http): move the map's fullscreen and filter toggles into the bar
Fullscreen and Hide Filters floated in their own block over the map's top-right corner, separate from the filter bar they sit beside. Put them at the right-hand end of the bar, behind a separator. What made this awkward before is that Hide Filters cannot live inside the thing it hides, so the collapse now applies to the filters alone: the bar keeps its two controls and shrinks to them at the map's right edge, leaving the whole map visible and the way back one click away. Fixes #38 Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -1335,7 +1335,8 @@ var mapWindow = window;
|
|||||||
function applyMapOverlayPanelVisibility() {
|
function applyMapOverlayPanelVisibility() {
|
||||||
const panel = document.querySelector("#map-stage .map-overlay-panel");
|
const panel = document.querySelector("#map-stage .map-overlay-panel");
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
panel.classList.toggle("is-hidden", !mapOverlayPanelVisible);
|
panel.classList.toggle("filters-hidden", !mapOverlayPanelVisible);
|
||||||
|
panel.querySelector(".map-overlay-filters")?.classList.toggle("is-hidden", !mapOverlayPanelVisible);
|
||||||
}
|
}
|
||||||
function updateMapOverlayToggleButton() {
|
function updateMapOverlayToggleButton() {
|
||||||
const btn = mapEl("map-overlay-toggle-btn");
|
const btn = mapEl("map-overlay-toggle-btn");
|
||||||
|
|||||||
@@ -1001,6 +1001,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<template id="tmpl-map">
|
<template id="tmpl-map">
|
||||||
<div id="map-stage">
|
<div id="map-stage">
|
||||||
<div class="map-overlay-panel">
|
<div class="map-overlay-panel">
|
||||||
|
<div class="map-overlay-filters">
|
||||||
<div class="map-locator-filter-group">
|
<div class="map-locator-filter-group">
|
||||||
<span class="map-locator-filter-label">Filter by</span>
|
<span class="map-locator-filter-label">Filter by</span>
|
||||||
<div id="map-locator-phase" class="map-locator-phase-row"></div>
|
<div id="map-locator-phase" class="map-locator-phase-row"></div>
|
||||||
@@ -1040,10 +1041,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-corner-controls">
|
<div class="map-overlay-actions">
|
||||||
<button type="button" id="map-fullscreen-btn" class="map-fullscreen-btn">Fullscreen</button>
|
<button type="button" id="map-fullscreen-btn" class="map-fullscreen-btn">Fullscreen</button>
|
||||||
<button type="button" id="map-overlay-toggle-btn" class="map-overlay-toggle-btn">Hide Filters</button>
|
<button type="button" id="map-overlay-toggle-btn" class="map-overlay-toggle-btn">Hide Filters</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="map-band-legend" class="map-band-legend" aria-label="Band color legend"></div>
|
<div id="map-band-legend" class="map-band-legend" aria-label="Band color legend"></div>
|
||||||
<div id="aprs-map"></div>
|
<div id="aprs-map"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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:
|
/* 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
|
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 --
|
its place. Fullscreen and the filter toggle ride at its right-hand end;
|
||||||
the button that hides the filters cannot live inside the thing it hides. */
|
only the filters themselves collapse, so the button that hides them is
|
||||||
|
still there to bring them back. */
|
||||||
.map-overlay-panel {
|
.map-overlay-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.7rem;
|
top: 0.7rem;
|
||||||
/* Clear of Leaflet's zoom buttons, which draw over the top-left corner. */
|
/* Clear of Leaflet's zoom buttons, which draw over the top-left corner. */
|
||||||
left: 3.4rem;
|
left: 3.4rem;
|
||||||
right: 9.6rem;
|
right: 0.7rem;
|
||||||
z-index: 410;
|
z-index: 410;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.35rem 0.5rem;
|
gap: 0.5rem;
|
||||||
width: auto;
|
width: auto;
|
||||||
max-height: calc(100% - 1.4rem);
|
max-height: calc(100% - 1.4rem);
|
||||||
padding: 0.45rem 0.6rem;
|
padding: 0.45rem 0.6rem;
|
||||||
@@ -2584,6 +2585,32 @@ button.map-qso-card:focus-visible {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
transition: opacity 140ms ease, transform 140ms ease, visibility 140ms ease;
|
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 {
|
.map-overlay-panel .map-locator-filter-group {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
/* The label stays beside its control; only the bar itself wraps. */
|
/* 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);
|
transform: translateY(0.25rem);
|
||||||
pointer-events: none;
|
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 {
|
.map-fullscreen-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -4166,7 +4183,7 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay {
|
|||||||
top: 0.55rem;
|
top: 0.55rem;
|
||||||
left: 3.25rem;
|
left: 3.25rem;
|
||||||
right: 0.55rem;
|
right: 0.55rem;
|
||||||
flex-direction: column;
|
flex-flow: column nowrap;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
width: auto;
|
width: auto;
|
||||||
max-height: min(16.5rem, calc(100% - 1.1rem));
|
max-height: min(16.5rem, calc(100% - 1.1rem));
|
||||||
@@ -4179,15 +4196,14 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay {
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
}
|
}
|
||||||
|
.map-overlay-actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
.map-overlay-panel .map-paths-hint {
|
.map-overlay-panel .map-paths-hint {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
.map-corner-controls {
|
|
||||||
top: 0.55rem;
|
|
||||||
right: 0.55rem;
|
|
||||||
}
|
|
||||||
.map-band-legend {
|
.map-band-legend {
|
||||||
left: 0.55rem;
|
left: 0.55rem;
|
||||||
bottom: 0.55rem;
|
bottom: 0.55rem;
|
||||||
|
|||||||
@@ -1637,7 +1637,10 @@ const mapWindow = window as unknown as MapWindow;
|
|||||||
function applyMapOverlayPanelVisibility() {
|
function applyMapOverlayPanelVisibility() {
|
||||||
const panel = document.querySelector("#map-stage .map-overlay-panel");
|
const panel = document.querySelector("#map-stage .map-overlay-panel");
|
||||||
if (!panel) return;
|
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() {
|
function updateMapOverlayToggleButton() {
|
||||||
|
|||||||
@@ -225,9 +225,11 @@ try {
|
|||||||
|
|
||||||
// The map's filter panel is a bar across the top of the map, not a window
|
// 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,
|
// 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
|
// and keep clear of what shares the map's corners — Leaflet's zoom buttons and
|
||||||
// buttons, the Fullscreen/Hide Filters controls, and the band legend. A panel
|
// the band legend. A panel that grew a column would cover the map it filters.
|
||||||
// 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 mapFixture = await startWebFixture({ spectrum: true });
|
||||||
const mapView = await startBrowser(chromium);
|
const mapView = await startBrowser(chromium);
|
||||||
try {
|
try {
|
||||||
@@ -242,7 +244,6 @@ try {
|
|||||||
const panel = document.querySelector(".map-overlay-panel");
|
const panel = document.querySelector(".map-overlay-panel");
|
||||||
const stage = box(document.getElementById("map-stage"));
|
const stage = box(document.getElementById("map-stage"));
|
||||||
const zoom = box(document.querySelector("#aprs-map .leaflet-control-zoom"));
|
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 legend = box(document.getElementById("map-band-legend"));
|
||||||
const panelBox = box(panel);
|
const panelBox = box(panel);
|
||||||
const hits = (a, b) => !!a && !!b
|
const hits = (a, b) => !!a && !!b
|
||||||
@@ -252,8 +253,10 @@ try {
|
|||||||
height: Math.round(panelBox.height),
|
height: Math.round(panelBox.height),
|
||||||
outsideStage: panelBox.right > stage.right + 1 || panelBox.bottom > stage.bottom + 1,
|
outsideStage: panelBox.right > stage.right + 1 || panelBox.bottom > stage.bottom + 1,
|
||||||
hitsZoom: hits(panelBox, zoom),
|
hitsZoom: hits(panelBox, zoom),
|
||||||
hitsCorner: hits(panelBox, corner),
|
|
||||||
hitsLegend: hits(panelBox, legend),
|
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,
|
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.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.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.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.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`);
|
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.locator("#map-overlay-toggle-btn").click();
|
||||||
await mapView.page.waitForTimeout(400);
|
await mapView.page.waitForTimeout(400);
|
||||||
const toggled = await mapView.page.evaluate(() => ({
|
const toggled = await mapView.page.evaluate(() => {
|
||||||
hidden: document.querySelector(".map-overlay-panel").classList.contains("is-hidden"),
|
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(),
|
label: document.getElementById("map-overlay-toggle-btn").textContent.trim(),
|
||||||
}));
|
togglesVisible: visible("map-fullscreen-btn") && visible("map-overlay-toggle-btn"),
|
||||||
assert.equal(toggled.hidden, true, "the filter bar stayed up after Hide Filters");
|
};
|
||||||
|
});
|
||||||
|
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}"`);
|
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, []);
|
assert.deepEqual(mapView.runtimeErrors, []);
|
||||||
} finally {
|
} finally {
|
||||||
await mapView.browser.close();
|
await mapView.browser.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user