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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -35,8 +35,8 @@
})()
</script>
<title>PilotVault — Control Panel</title>
<script type="module" crossorigin src="./assets/index-uk1cBykG.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CoIRbg0g.css">
<script type="module" crossorigin src="./assets/index-Dk_VY0uv.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-Bvd5MzZB.css">
</head>
<body>
<div id="app"></div>
+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>
<DeviceMap :position="position" :trail="trail" :aircraft="aircraft" />
<p v-if="airspace.loaded && airspace.unavailable" class="mt-2.5 text-xs text-ink-muted">
</div>
<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">
+3
View File
@@ -26,6 +26,9 @@ const defaults = {
dateFormat: 'MDY', // MDY | DMY | YMD | ISO
timeFormat: '24', // 12 | 24
reduceMotion: false,
// Live map: overlay live OpenSky air traffic (client-side toggle; the OpenSky
// integration itself is still gated in Settings → Integrations).
showAirTraffic: true,
// Security (prototype)
twoFactor: false,
}