[feat](trx-frontend): add map fullscreen toggle
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -3217,6 +3217,7 @@ let aprsMapBaseLayer = null;
|
|||||||
let aprsMapReceiverMarker = null;
|
let aprsMapReceiverMarker = null;
|
||||||
let aprsRadioPath = null;
|
let aprsRadioPath = null;
|
||||||
let selectedLocatorMarker = null;
|
let selectedLocatorMarker = null;
|
||||||
|
let mapFullscreenListenerBound = false;
|
||||||
const stationMarkers = new Map();
|
const stationMarkers = new Map();
|
||||||
const locatorMarkers = new Map();
|
const locatorMarkers = new Map();
|
||||||
const mapMarkers = new Set();
|
const mapMarkers = new Set();
|
||||||
@@ -3663,6 +3664,45 @@ function updateMapBaseLayerForTheme(theme) {
|
|||||||
aprsMapBaseLayer = L.tileLayer(spec.url, spec.options).addTo(aprsMap);
|
aprsMapBaseLayer = L.tileLayer(spec.url, spec.options).addTo(aprsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStageEl() {
|
||||||
|
return document.getElementById("map-stage");
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapIsFullscreen() {
|
||||||
|
const stage = mapStageEl();
|
||||||
|
if (!stage) return false;
|
||||||
|
return document.fullscreenElement === stage || document.webkitFullscreenElement === stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMapFullscreenButton() {
|
||||||
|
const btn = document.getElementById("map-fullscreen-btn");
|
||||||
|
if (!btn) return;
|
||||||
|
btn.textContent = mapIsFullscreen() ? "Exit Fullscreen" : "Fullscreen";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleMapFullscreen() {
|
||||||
|
const stage = mapStageEl();
|
||||||
|
if (!stage) return;
|
||||||
|
try {
|
||||||
|
if (mapIsFullscreen()) {
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
await document.exitFullscreen();
|
||||||
|
} else if (document.webkitExitFullscreen) {
|
||||||
|
await document.webkitExitFullscreen();
|
||||||
|
}
|
||||||
|
} else if (stage.requestFullscreen) {
|
||||||
|
await stage.requestFullscreen();
|
||||||
|
} else if (stage.webkitRequestFullscreen) {
|
||||||
|
await stage.webkitRequestFullscreen();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Map fullscreen toggle failed", err);
|
||||||
|
} finally {
|
||||||
|
updateMapFullscreenButton();
|
||||||
|
sizeAprsMapToViewport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initAprsMap() {
|
function initAprsMap() {
|
||||||
const mapEl = document.getElementById("aprs-map");
|
const mapEl = document.getElementById("aprs-map");
|
||||||
if (!mapEl) return;
|
if (!mapEl) return;
|
||||||
@@ -3776,6 +3816,7 @@ function initAprsMap() {
|
|||||||
|
|
||||||
const locatorPhaseEl = document.getElementById("map-locator-phase");
|
const locatorPhaseEl = document.getElementById("map-locator-phase");
|
||||||
const locatorChoiceEl = document.getElementById("map-locator-choice-filter");
|
const locatorChoiceEl = document.getElementById("map-locator-choice-filter");
|
||||||
|
const fullscreenBtn = document.getElementById("map-fullscreen-btn");
|
||||||
if (locatorPhaseEl) {
|
if (locatorPhaseEl) {
|
||||||
locatorPhaseEl.addEventListener("click", (e) => {
|
locatorPhaseEl.addEventListener("click", (e) => {
|
||||||
const btn = e.target.closest(".map-locator-phase-btn[data-phase]");
|
const btn = e.target.closest(".map-locator-phase-btn[data-phase]");
|
||||||
@@ -3815,6 +3856,21 @@ function initAprsMap() {
|
|||||||
applyMapFilter();
|
applyMapFilter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (fullscreenBtn) {
|
||||||
|
fullscreenBtn.addEventListener("click", () => {
|
||||||
|
toggleMapFullscreen();
|
||||||
|
});
|
||||||
|
updateMapFullscreenButton();
|
||||||
|
}
|
||||||
|
if (!mapFullscreenListenerBound) {
|
||||||
|
const onFullscreenChange = () => {
|
||||||
|
updateMapFullscreenButton();
|
||||||
|
sizeAprsMapToViewport();
|
||||||
|
};
|
||||||
|
document.addEventListener("fullscreenchange", onFullscreenChange);
|
||||||
|
document.addEventListener("webkitfullscreenchange", onFullscreenChange);
|
||||||
|
mapFullscreenListenerBound = true;
|
||||||
|
}
|
||||||
rebuildMapLocatorFilters();
|
rebuildMapLocatorFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3822,16 +3878,21 @@ function sizeAprsMapToViewport() {
|
|||||||
const mapEl = document.getElementById("aprs-map");
|
const mapEl = document.getElementById("aprs-map");
|
||||||
if (!mapEl) return;
|
if (!mapEl) return;
|
||||||
const mapRect = mapEl.getBoundingClientRect();
|
const mapRect = mapEl.getBoundingClientRect();
|
||||||
|
const stage = mapStageEl();
|
||||||
const width = mapEl.clientWidth || mapRect.width;
|
const width = mapEl.clientWidth || mapRect.width;
|
||||||
const footer = document.querySelector(".footer");
|
const footer = document.querySelector(".footer");
|
||||||
let bottom = window.innerHeight;
|
let bottom = mapIsFullscreen() && stage
|
||||||
if (footer) {
|
? stage.getBoundingClientRect().bottom
|
||||||
|
: window.innerHeight;
|
||||||
|
if (!mapIsFullscreen() && footer) {
|
||||||
const fr = footer.getBoundingClientRect();
|
const fr = footer.getBoundingClientRect();
|
||||||
if (fr.top > mapRect.top + 50) bottom = fr.top;
|
if (fr.top > mapRect.top + 50) bottom = fr.top;
|
||||||
}
|
}
|
||||||
const available = Math.max(0, Math.floor(bottom - mapRect.top - 8));
|
const available = Math.max(0, Math.floor(bottom - mapRect.top - 8));
|
||||||
const widthDriven = width > 0 ? Math.floor(width / 2.05) : available;
|
const widthDriven = width > 0 ? Math.floor(width / 2.05) : available;
|
||||||
const viewportCap = Math.floor(window.innerHeight * 0.56);
|
const viewportCap = mapIsFullscreen()
|
||||||
|
? Math.floor(window.innerHeight * 0.9)
|
||||||
|
: Math.floor(window.innerHeight * 0.56);
|
||||||
const minHeight = Math.min(260, available);
|
const minHeight = Math.min(260, available);
|
||||||
const target = Math.max(minHeight, Math.min(available, viewportCap, widthDriven));
|
const target = Math.max(minHeight, Math.min(available, viewportCap, widthDriven));
|
||||||
mapEl.style.height = `${target}px`;
|
mapEl.style.height = `${target}px`;
|
||||||
|
|||||||
@@ -579,7 +579,10 @@
|
|||||||
<div id="map-locator-choice-filter" class="map-locator-chip-row"></div>
|
<div id="map-locator-choice-filter" class="map-locator-chip-row"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="aprs-map"></div>
|
<div id="map-stage">
|
||||||
|
<button type="button" id="map-fullscreen-btn" class="map-fullscreen-btn">Fullscreen</button>
|
||||||
|
<div id="aprs-map"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-about" class="tab-panel" style="display:none;">
|
<div id="tab-about" class="tab-panel" style="display:none;">
|
||||||
<div id="auth-badge" style="display:none; margin-bottom: 1rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">Authenticated as: <strong id="auth-role-badge">--</strong></div>
|
<div id="auth-badge" style="display:none; margin-bottom: 1rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">Authenticated as: <strong id="auth-role-badge">--</strong></div>
|
||||||
|
|||||||
@@ -1094,12 +1094,51 @@ small { color: var(--text-muted); }
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
#aprs-map {
|
#map-stage {
|
||||||
|
position: relative;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
#aprs-map {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
.map-fullscreen-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.7rem;
|
||||||
|
right: 0.7rem;
|
||||||
|
z-index: 410;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 1.9rem;
|
||||||
|
padding: 0.18rem 0.65rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--border-light) 74%, transparent);
|
||||||
|
background: color-mix(in srgb, var(--card-bg) 82%, transparent);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.76rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
cursor: pointer;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
.map-fullscreen-btn:hover {
|
||||||
|
border-color: color-mix(in srgb, var(--accent-green) 34%, var(--border-light));
|
||||||
|
color: var(--text-heading);
|
||||||
|
}
|
||||||
|
#map-stage:fullscreen,
|
||||||
|
#map-stage:-webkit-full-screen {
|
||||||
|
background: var(--bg);
|
||||||
|
padding: 0.75rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#map-stage:fullscreen #aprs-map,
|
||||||
|
#map-stage:-webkit-full-screen #aprs-map {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
.aprs-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
|
.aprs-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
|
||||||
.aprs-summary {
|
.aprs-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
Reference in New Issue
Block a user