import React, { useState, useEffect } from 'react'; import { num, navSearch } from '../api/useXplane.js'; const R_NM = 3440.065; const rad = (d) => (d * Math.PI) / 180; const deg = (r) => (r * 180) / Math.PI; function distNm(a, b) { const dLat = rad(b.lat - a.lat), dLon = rad(b.lon - a.lon); const s = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLon / 2) ** 2; return 2 * R_NM * Math.asin(Math.min(1, Math.sqrt(s))); } function bearing(a, b) { const y = Math.sin(rad(b.lon - a.lon)) * Math.cos(rad(b.lat)); const x = Math.cos(rad(a.lat)) * Math.sin(rad(b.lat)) - Math.sin(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.cos(rad(b.lon - a.lon)); return (deg(Math.atan2(y, x)) + 360) % 360; } export default function FMS({ xp }) { const { flightPlan, fp, values, exportMsg } = xp; const wps = flightPlan.waypoints || []; const [entry, setEntry] = useState(''); const [hits, setHits] = useState([]); // live ident search against X-Plane's nav database useEffect(() => { const q = entry.trim(); if (q.length < 2 || /[,\s]/.test(q)) { setHits([]); return; } let alive = true; navSearch(q).then((r) => alive && setHits(r.slice(0, 6))); return () => { alive = false; }; }, [entry]); const add = (id) => { fp.add(id || entry.trim()); setEntry(''); setHits([]); }; let total = 0; const rows = wps.map((w, i) => { const prev = wps[i - 1]; const d = prev ? distNm(prev, w) : 0; const brg = prev ? bearing(prev, w) : null; total += d; return { w, i, d, brg }; }); const gs = num(values.groundspeed) * 1.94384; const ete = gs > 20 ? total / gs : null; // hours const active = Math.max(1, Math.min(wps.length - 1, flightPlan?.activeLeg ?? 1)); return (
FLIGHT PLAN {total.toFixed(0)} NM{ete ? ` · ETE ${fmtHrs(ete)}` : ''}
#WPTDTKDIST
{rows.length === 0 &&
Kein Flugplan — Wegpunkt eingeben oder auf der Map tippen.
} {rows.map(({ w, i, d, brg }) => (
i >= 1 && fp.setActive(i)} title={i >= 1 ? 'Als aktives Bein setzen' : ''}> {i + 1} {w.id}{w.type} {brg == null ? '—' : `${String(Math.round(brg)).padStart(3, '0')}°`} {i === 0 ? 'ORIG' : `${d.toFixed(1)}`}
))}
{hits.length > 0 && (
{hits.map((h) => ( ))}
)}
setEntry(e.target.value.toUpperCase())} onKeyDown={(e) => e.key === 'Enter' && add()} placeholder="IDENT (z.B. KSEA, SEA) oder LAT,LON" autoCapitalize="characters" autoCorrect="off" spellCheck="false" />
{exportMsg && (
{exportMsg.ok ? (exportMsg.intoXplane ? `✓ Gespeichert in X-Plane: ${shorten(exportMsg.file)} — im Flieger-FMS unter „Load" wählen.` : `✓ Datei geschrieben: ${shorten(exportMsg.file)} (X-Plane-Ordner nicht gefunden — XPLANE_ROOT setzen).`) : `✗ ${exportMsg.error}`}
)}
); } function fmtHrs(h) { const m = Math.round(h * 60); return `${Math.floor(m / 60)}:${String(m % 60).padStart(2, '0')}`; } function shorten(p) { return p && p.length > 48 ? '…' + p.slice(-46) : p; }