Auto-install Lua, smooth all panels, airspace overlay + launcher region picker
FlyWithLua auto-install: bridge drops fms-sync/ui-sync/terrain-probe into X-Plane's FlyWithLua Scripts dir on startup and self-updates (content-compare). Graceful when no X-Plane / no FlyWithLua. /api/lua/install + status in health. Desktop app bundles the scripts and passes LUA_SRC_DIR to the sidecar. Smoothing: shared useEased/useEasedAngle hook (api/ease.js) with render-bail on settle. VFR steam gauges now interpolate to 60fps instead of stepping at the ~10Hz value stream. MFD ownship no longer vibrates — position/heading eased in a single rAF loop, follow-pan without animated-panTo pile-up (pauses on range zoom). Airspace overlay: server/airspace.js loads per-region GeoJSON, classifies (B/C/D/TMA/CTR/MOA/Restricted/Prohibited/Danger), bbox query, and downloads regions on demand — FAA (US, key-free) and OpenAIP (Europe, user key). New AIRSPACE softkey draws chart-coloured boundaries (B blue, C magenta, D dashed), non-interactive so map-clicks still drop waypoints. Launcher gains a "Lufträume" section to pick/download regions via the running bridge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+52
-1
@@ -8,7 +8,7 @@ const xpPath = $('xpPath'), portEl = $('port'), demoEl = $('demo');
|
||||
const startBtn = $('startBtn'), liveCard = $('liveCard'), urlEl = $('url');
|
||||
const statusEl = $('status'), statusText = $('statusText'), logEl = $('log');
|
||||
|
||||
let running = false, healthTimer = null;
|
||||
let running = false, healthTimer = null, serverPort = 0;
|
||||
|
||||
function setStatus(kind, text) {
|
||||
statusEl.className = 'status ' + kind;
|
||||
@@ -73,12 +73,14 @@ startBtn.addEventListener('click', async () => {
|
||||
demo: demoEl.checked,
|
||||
});
|
||||
running = true;
|
||||
serverPort = info.port;
|
||||
urlEl.textContent = info.url;
|
||||
liveCard.classList.remove('hidden');
|
||||
startBtn.textContent = 'Server stoppen';
|
||||
startBtn.classList.add('stop');
|
||||
setStatus('warn', 'Server läuft · warte auf Sim');
|
||||
pollHealth(info.port);
|
||||
loadRegions();
|
||||
} catch (e) {
|
||||
appendLog('Fehler: ' + e);
|
||||
setStatus('off', 'Fehler');
|
||||
@@ -96,6 +98,8 @@ async function stop() {
|
||||
|
||||
function resetUi() {
|
||||
running = false;
|
||||
serverPort = 0;
|
||||
const ar = $('aspRegions'); if (ar) ar.innerHTML = '';
|
||||
liveCard.classList.add('hidden');
|
||||
startBtn.textContent = 'Server starten';
|
||||
startBtn.classList.remove('stop');
|
||||
@@ -125,6 +129,53 @@ function pollHealth(port) {
|
||||
healthTimer = setInterval(check, 3000);
|
||||
}
|
||||
|
||||
/* ---------------- airspace regions ---------------- */
|
||||
const aspBase = () => `http://127.0.0.1:${serverPort}/api/airspace`;
|
||||
|
||||
async function loadRegions() {
|
||||
const wrap = $('aspRegions');
|
||||
if (!wrap || !serverPort) return;
|
||||
try {
|
||||
const r = await fetch(`${aspBase()}/regions`, { cache: 'no-store' });
|
||||
const { regions } = await r.json();
|
||||
wrap.innerHTML = '';
|
||||
for (const reg of regions) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'asp-row';
|
||||
const installed = reg.installed > 0;
|
||||
row.innerHTML = `
|
||||
<span class="asp-name">${reg.label}${reg.needsKey ? ' <em>· Key</em>' : ''}</span>
|
||||
<span class="asp-count">${installed ? reg.installed + ' Zonen' : '—'}</span>
|
||||
<button class="btn ghost sm" data-region="${reg.id}" data-key="${reg.needsKey ? 1 : 0}">${installed ? 'Aktualisieren' : 'Laden'}</button>`;
|
||||
wrap.appendChild(row);
|
||||
}
|
||||
wrap.querySelectorAll('button[data-region]').forEach((btn) =>
|
||||
btn.addEventListener('click', () => installRegion(btn)));
|
||||
} catch (e) { appendLog('airspace: ' + e); }
|
||||
}
|
||||
|
||||
async function installRegion(btn) {
|
||||
const region = btn.dataset.region;
|
||||
const needsKey = btn.dataset.key === '1';
|
||||
const apiKey = $('aspKey').value.trim();
|
||||
const hint = $('aspHint');
|
||||
if (needsKey && !apiKey) { hint.textContent = '⚠ OpenAIP-API-Key oben eingeben'; hint.className = 'hint bad'; return; }
|
||||
btn.disabled = true; const was = btn.textContent; btn.textContent = 'Lädt…';
|
||||
hint.textContent = `Lade ${region.toUpperCase()} … (Fortschritt im Server-Log)`; hint.className = 'hint';
|
||||
try {
|
||||
const r = await fetch(`${aspBase()}/install`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ region, apiKey: needsKey ? apiKey : undefined }),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.ok) { hint.textContent = `✓ ${region.toUpperCase()}: ${d.features} Zonen geladen`; hint.className = 'hint ok'; }
|
||||
else { hint.textContent = '⚠ ' + (d.error || 'Fehler'); hint.className = 'hint bad'; }
|
||||
} catch (e) { hint.textContent = '⚠ ' + e; hint.className = 'hint bad'; }
|
||||
finally { btn.disabled = false; btn.textContent = was; loadRegions(); }
|
||||
}
|
||||
|
||||
$('aspKeyLink')?.addEventListener('click', (e) => { e.preventDefault(); openUrl('https://www.openaip.net/'); });
|
||||
|
||||
$('copy').addEventListener('click', async () => {
|
||||
try { await navigator.clipboard.writeText(urlEl.textContent); $('copy').textContent = '✓'; setTimeout(() => ($('copy').textContent = '⧉'), 1200); } catch {}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user