import React, { useEffect, useState } from 'react'; import { num } from '../api/useXplane.js'; // G1000 PROC dialog. Pick a destination/airport, a category (Departure / Arrival // / Approach), then a procedure + transition; LOAD inserts the procedure's leg // fixes into the active flight plan. Procedures come from X-Plane's own CIFP data // via /api/nav/procs and /api/nav/proc (resolved to coordinates server-side). const CATS = [ { id: 'approach', label: 'APPROACH', key: 'approaches', t: 'approach' }, { id: 'arrival', label: 'ARRIVAL', key: 'stars', t: 'star' }, { id: 'departure', label: 'DEPARTURE', key: 'sids', t: 'sid' }, ]; export default function Proc({ xp, onClose }) { const { flightPlan, fp, values } = xp; // Default airport: the plan's destination if it's an airport, else blank. const wps = flightPlan?.waypoints || []; const destGuess = [...wps].reverse().find((w) => w.type === 'APT')?.id || ''; const [icao, setIcao] = useState(destGuess); const [query, setQuery] = useState(destGuess); const [procs, setProcs] = useState(null); const [err, setErr] = useState(''); const [cat, setCat] = useState('approach'); const [view, setView] = useState('menu'); // 'menu' (PDF action list) | 'pick' const [selProc, setSelProc] = useState(null); // { name, transitions } const [selTrans, setSelTrans] = useState(''); const [legs, setLegs] = useState([]); // Fetch the procedure summary whenever the airport changes. useEffect(() => { const id = icao.trim().toUpperCase(); if (id.length < 3) { setProcs(null); return; } let alive = true; setErr(''); setProcs(null); setSelProc(null); setSelTrans(''); setLegs([]); fetch(`/api/nav/procs?icao=${id}`).then((r) => r.ok ? r.json() : Promise.reject(r.status)) .then((d) => { if (alive) setProcs(d); }) .catch(() => { if (alive) setErr(`keine Prozeduren für ${id}`); }); return () => { alive = false; }; }, [icao]); // Preview the resolved legs when a procedure+transition is chosen. useEffect(() => { if (!procs || !selProc) { setLegs([]); return; } const c = CATS.find((c) => c.id === cat); let alive = true; const t = encodeURIComponent(selTrans || ''); fetch(`/api/nav/proc?icao=${procs.icao}&type=${c.t}&name=${encodeURIComponent(selProc.name)}&trans=${t}`) .then((r) => r.ok ? r.json() : []).then((d) => { if (alive) setLegs(d); }); return () => { alive = false; }; }, [procs, cat, selProc, selTrans]); const catList = procs ? (procs[CATS.find((c) => c.id === cat).key] || []) : []; const load = () => { if (!legs.length) return; const existing = wps.slice(); // Departures go to the front, arrivals/approaches to the end. const merged = cat === 'departure' ? [...legs, ...existing] : [...existing, ...legs]; fp.set({ name: 'ACTIVE', waypoints: merged, activeLeg: cat === 'departure' ? 1 : existing.length || 1 }); onClose(); }; const catLabel = CATS.find((c) => c.id === cat).label; // The PDF's action menu. SELECT … opens our picker for that category; // ACTIVATE … are shown for authenticity (armed-procedure actions). if (view === 'menu') { const item = (label, onClick, sel) => ( ); const sel = (c) => { setCat(c); setSelProc(null); setSelTrans(''); setView('pick'); }; return (
e.stopPropagation()}>
PROCEDURES
{item('ACTIVATE VECTOR-TO-FINAL', () => {})} {item('ACTIVATE APPROACH', () => {})} {item('ACTIVATE MISSED APPROACH', () => {})} {item('SELECT APPROACH', () => sel('approach'), true)} {item('SELECT ARRIVAL', () => sel('arrival'))} {item('SELECT DEPARTURE', () => sel('departure'))}
); } return (
e.stopPropagation()}>
{catLabel}
setQuery(e.target.value.toUpperCase())} onKeyDown={(e) => e.key === 'Enter' && setIcao(query)} placeholder="ICAO (z.B. KSEA)" autoCapitalize="characters" autoCorrect="off" spellCheck="false" />
{err &&
{err}
}
{procs ? `${catList.length}` : '—'} PROC
{catList.map((p) => ( ))} {procs && catList.length === 0 &&
keine
}
TRANS
{selProc?.transitions.map((t) => ( ))} {selProc && selProc.transitions.length === 0 &&
}
{legs.length} FIXES
{legs.map((l, i) => (
{l.id}{l.alt ? {l.alt}ft : null}
))}
); }