Add Map settings toggle to show/hide live air traffic

Add a "Map settings" popover (gear button) on the Overview Live map with a
"Show live air traffic" switch, backed by a new client-side showAirTraffic
preference (default on, persisted in localStorage and synced like other
prefs). When off, the aircraft overlay clears and polling pauses to save
OpenSky credits; the count badge and caption reflect the hidden state.
Toggling on re-fetches immediately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-07-13 22:31:56 +02:00
co-authored by Claude Opus 4.8
parent a578555b43
commit fee171a71f
7 changed files with 73 additions and 30 deletions
+47 -7
View File
@@ -6,8 +6,9 @@ import Icon from './Icon.vue'
import Settings from './Settings.vue'
import Logbook from './Logbook.vue'
import Documents from './Documents.vue'
import Toggle from './settings/Toggle.vue'
import { getDevices, sendCommand, getOpenSkyStates } from '../api.js'
import { formatTime } from '../prefs.js'
import { formatTime, prefs } from '../prefs.js'
const props = defineProps({
email: { type: String, default: '' },
@@ -28,9 +29,11 @@ const search = ref('')
const aircraft = ref([]) // [{ icao24, callsign, lat, lng, heading, velocity, altitude, onGround }]
const airspace = reactive({ unavailable: false, detail: '', loaded: false })
const airborneCount = computed(() => aircraft.value.filter((a) => !a.onGround).length)
const mapMenu = ref(false) // "Map settings" popover open state
let airTimer = null
async function refreshAirspace() {
if (!prefs.showAirTraffic) return
const { states, unavailable, detail } = await getOpenSkyStates()
aircraft.value = states
airspace.unavailable = unavailable
@@ -38,13 +41,14 @@ async function refreshAirspace() {
airspace.loaded = true
}
// Poll OpenSky only while the Overview map is on screen, on a credit-friendly
// cadence (state vectors refresh at most every ~10s upstream anyway).
// Poll OpenSky only while the Overview map is on screen and the overlay is
// enabled, on a credit-friendly cadence (state vectors refresh at most every
// ~10s upstream anyway).
function startAirspace() {
if (airTimer) return
refreshAirspace()
airTimer = setInterval(() => {
if (active.value === 'Overview') refreshAirspace()
if (active.value === 'Overview' && prefs.showAirTraffic) refreshAirspace()
}, 30000)
}
function stopAirspace() {
@@ -282,6 +286,16 @@ watch(active, (v) => {
if (v === 'Overview') refreshAirspace()
})
// React to the "Show live air traffic" map toggle: fetch at once when enabled,
// clear the overlay (and pause polling) when disabled.
watch(
() => prefs.showAirTraffic,
(on) => {
if (on) refreshAirspace()
else aircraft.value = []
},
)
onMounted(async () => {
;(await getDevices()).forEach(upsert)
connect()
@@ -408,7 +422,7 @@ onBeforeUnmount(() => {
</div>
<div class="flex items-center gap-2">
<span
v-if="airborneCount"
v-if="prefs.showAirTraffic && airborneCount"
class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-semibold"
:class="badgeClass.accent"
title="Live aircraft from OpenSky Network"
@@ -422,10 +436,36 @@ onBeforeUnmount(() => {
>
<span class="h-1.5 w-1.5 rounded-full bg-current"></span>{{ flyingCount }} drones
</span>
<!-- Map settings -->
<div class="relative">
<button
type="button"
class="grid h-7 w-7 place-items-center rounded-md text-ink-muted transition hover:bg-surface-2 hover:text-ink"
:class="mapMenu ? 'bg-surface-2 text-ink' : ''"
title="Map settings"
aria-label="Map settings"
@click="mapMenu = !mapMenu"
>
<Icon name="settings" :size="16" />
</button>
<template v-if="mapMenu">
<div class="fixed inset-0 z-10" @click="mapMenu = false"></div>
<div class="panel absolute right-0 z-20 mt-1.5 w-64 p-3.5 shadow-lg">
<div class="eyebrow mb-2.5">Map settings</div>
<label class="flex items-center justify-between gap-3">
<span class="text-sm text-ink-secondary">Show live air traffic</span>
<Toggle v-model="prefs.showAirTraffic" />
</label>
</div>
</template>
</div>
</div>
</div>
<DeviceMap :position="position" :trail="trail" :aircraft="aircraft" />
<p v-if="airspace.loaded && airspace.unavailable" class="mt-2.5 text-xs text-ink-muted">
<DeviceMap :position="position" :trail="trail" :aircraft="prefs.showAirTraffic ? aircraft : []" />
<p v-if="!prefs.showAirTraffic" class="mt-2.5 text-xs text-ink-muted">
Live air traffic hidden · enable it in Map settings
</p>
<p v-else-if="airspace.loaded && airspace.unavailable" class="mt-2.5 text-xs text-ink-muted">
{{ airspace.detail || 'Live air traffic is unavailable.' }}
</p>
<p v-else class="mt-2.5 text-xs text-ink-muted">