G1000: two-way sim sync, more PFD/MFD fidelity, authentic dialogs
Sync (FlyWithLua companions in plugins/ + server/fmssync.js): - FMS flight-plan two-way sync (App <-> in-sim FMS) via fms-sync.lua - G1000 UI-state publish (page/range/inset) via ui-sync.lua + CDI source, baro, map-range follow - Terrain awareness: elevation grid probe (terrain-probe.lua) -> red/yellow MFD overlay vs aircraft altitude PFD: - AFCS mode annunciation bar from autopilot _status datarefs - CDI source GPS/VLOC colouring, BRG1/BRG2 pointers + DME windows, marker beacons - magenta speed/altitude trend vectors, selected-altitude alerting - time-based (frame-rate-independent) smoothing for attitude/heading/tapes MFD: - nav data bar (DTK/ETE/active leg), airways overlay from earth_awy.dat, compass rose anchored to the ownship Dialogs (NEAREST/FLIGHTPLAN/DIRECT-TO/PROCEDURES): - flat, square, embedded G1000 look (no shadow/rounded/transparency) - compact lower-right placement, no close X (softkey toggles), single window - NEAREST 2-line entries (ILS/VFR, COM freq, runway length), PROC action menu Service worker: network-first HTML so reloads pick up new builds (cache v2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+42
-17
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useXplane } from './api/useXplane.js';
|
||||
import PFD from './components/PFD.jsx';
|
||||
import AutopilotPanel from './components/AutopilotPanel.jsx';
|
||||
@@ -9,6 +9,7 @@ import VFR from './components/VFR.jsx';
|
||||
import Bezel from './components/Bezel.jsx';
|
||||
import DirectTo from './components/DirectTo.jsx';
|
||||
import Proc from './components/Proc.jsx';
|
||||
import FplPage from './components/FplPage.jsx';
|
||||
|
||||
// Compact line icons for the nav rail (stroke = currentColor).
|
||||
const ICONS = {
|
||||
@@ -57,20 +58,42 @@ export default function App() {
|
||||
const [inset, setInset] = useState(false);
|
||||
// INSET map options (base layer + declutter), set from the INSET submenu.
|
||||
const [insetMode, setInsetMode] = useState({ base: 'topo', dcltr: 0 });
|
||||
// The NRST (nearest airports/navaids) window, toggled by the PFD NRST softkey.
|
||||
const [nrst, setNrst] = useState(false);
|
||||
// The TMR/REF (timer / references) window, toggled by the PFD TMR/REF softkey.
|
||||
const [tmr, setTmr] = useState(false);
|
||||
// Like the real G1000, only ONE window is open at a time. A single string
|
||||
// holds the open one (nrst / tmr / dme / alerts / fpl / dto / proc); toggling
|
||||
// the same softkey closes it, opening another replaces it.
|
||||
const [win, setWin] = useState(null);
|
||||
const toggleWin = (id) => setWin((w) => (w === id ? null : id));
|
||||
const nrst = win === 'nrst', tmr = win === 'tmr', dme = win === 'dme', alerts = win === 'alerts';
|
||||
const fpl = win === 'fpl', dto = win === 'dto', proc = win === 'proc';
|
||||
// MFD map mode (base layer), switched via the Map-Opt softkeys.
|
||||
const [mapMode, setMapMode] = useState({ base: 'topo' });
|
||||
// Direct-To (D→) dialog — opened from the bezel on either GDU.
|
||||
const [dto, setDto] = useState(false);
|
||||
// PROC (procedures: SID/STAR/approach) dialog — opened from the bezel.
|
||||
const [proc, setProc] = useState(false);
|
||||
// MFD page group (MAP / FPL / NRST) — selected by the FMS knob, like the real G1000.
|
||||
const MFD_PAGES = ['map', 'fpl', 'nrst'];
|
||||
const [mfdPage, setMfdPage] = useState('map');
|
||||
const cycleMfd = (dir = 1) => setMfdPage((p) => MFD_PAGES[(MFD_PAGES.indexOf(p) + dir + MFD_PAGES.length) % MFD_PAGES.length]);
|
||||
|
||||
// G1000 UI-state sync (Sim → App): follow the in-sim G1000 when the FlyWithLua
|
||||
// companion publishes its state. No-ops until then, so local control still works.
|
||||
const uiInset = xp.values.uiInset, uiPage = xp.values.uiMfdPage;
|
||||
useEffect(() => { if (uiInset === 0 || uiInset === 1) setInset(!!uiInset); }, [uiInset]);
|
||||
useEffect(() => { if (typeof uiPage === 'number' && MFD_PAGES[uiPage]) setMfdPage(MFD_PAGES[uiPage]); }, [uiPage]);
|
||||
const connKind = xp.xpConnected ? 'ok' : xp.connected ? 'warn' : 'bad';
|
||||
const connText = xp.xpConnected ? 'X-PLANE' : xp.connected ? 'NO SIM' : 'OFFLINE';
|
||||
|
||||
// G1000 side-window dialogs — rendered inside the bezel display so they sit in
|
||||
// the display's lower-right (like the real unit), not over the whole app.
|
||||
const dialogs = (
|
||||
<>
|
||||
{dto && <DirectTo xp={xp} onClose={() => setWin(null)} />}
|
||||
{proc && <Proc xp={xp} onClose={() => setWin(null)} />}
|
||||
{fpl && (
|
||||
<div className="gwin-backdrop" onClick={() => setWin(null)}>
|
||||
<div onClick={(e) => e.stopPropagation()}><FplPage xp={xp} onClose={() => setWin(null)} /></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`app ${navWide ? 'nav-wide' : 'nav-narrow'}`}>
|
||||
<aside className="sidebar">
|
||||
@@ -104,15 +127,19 @@ export default function App() {
|
||||
{tab === 'pfd' && (
|
||||
<Bezel variant="pfd" xp={xp} knobMode={knobMode} svt3d={svt3d} onToggleSvt={() => setSvt3d((v) => !v)}
|
||||
inset={inset} onSetInset={setInset} insetMode={insetMode} onInsetMode={setInsetMode}
|
||||
nrst={nrst} onToggleNrst={() => setNrst((v) => !v)} onDirect={() => setDto(true)}
|
||||
tmr={tmr} onToggleTmr={() => setTmr((v) => !v)} onProc={() => setProc(true)}>
|
||||
<PFD values={xp.values} svt={svt3d} inset={inset} insetMode={insetMode} nrst={nrst} onCloseNrst={() => setNrst(false)}
|
||||
tmr={tmr} onCloseTmr={() => setTmr(false)} flightPlan={xp.flightPlan} fp={xp.fp} />
|
||||
nrst={nrst} onToggleNrst={() => toggleWin('nrst')} onDirect={() => toggleWin('dto')}
|
||||
tmr={tmr} onToggleTmr={() => toggleWin('tmr')} dme={dme} onToggleDme={() => toggleWin('dme')}
|
||||
alerts={alerts} onToggleAlerts={() => toggleWin('alerts')} onProc={() => toggleWin('proc')} onFpl={() => toggleWin('fpl')}>
|
||||
<PFD values={xp.values} command={xp.command} svt={svt3d} inset={inset} insetMode={insetMode} nrst={nrst} onCloseNrst={() => setWin(null)}
|
||||
tmr={tmr} onCloseTmr={() => setWin(null)} dme={dme} onCloseDme={() => setWin(null)}
|
||||
alerts={alerts} onCloseAlerts={() => setWin(null)} flightPlan={xp.flightPlan} fp={xp.fp} />
|
||||
{dialogs}
|
||||
</Bezel>
|
||||
)}
|
||||
{tab === 'mfd' && (
|
||||
<Bezel variant="mfd" xp={xp} knobMode={knobMode} mapMode={mapMode} onMapMode={setMapMode} onDirect={() => setDto(true)} onProc={() => setProc(true)}>
|
||||
<MFD values={xp.values} flightPlan={xp.flightPlan} fp={xp.fp} mapMode={mapMode} />
|
||||
<Bezel variant="mfd" xp={xp} knobMode={knobMode} mapMode={mapMode} onMapMode={setMapMode} onDirect={() => toggleWin('dto')} onProc={() => toggleWin('proc')} onFms={cycleMfd} onFpl={() => setMfdPage('fpl')}>
|
||||
<MFD values={xp.values} flightPlan={xp.flightPlan} fp={xp.fp} mapMode={mapMode} page={mfdPage} onCycle={cycleMfd} xp={xp} />
|
||||
{dialogs}
|
||||
</Bezel>
|
||||
)}
|
||||
{tab === 'map' && <MapView values={xp.values} flightPlan={xp.flightPlan} fp={xp.fp} />}
|
||||
@@ -120,8 +147,6 @@ export default function App() {
|
||||
{tab === 'vfr' && <VFR xp={xp} />}
|
||||
{tab === 'ap' && <AutopilotPanel xp={xp} />}
|
||||
</main>
|
||||
{dto && <DirectTo xp={xp} onClose={() => setDto(false)} />}
|
||||
{proc && <Proc xp={xp} onClose={() => setProc(false)} />}
|
||||
{settings && (
|
||||
<div className="dlg-backdrop" onClick={() => setSettings(false)}>
|
||||
<div className="dlg" onClick={(e) => e.stopPropagation()} style={{ minWidth: 360 }}>
|
||||
|
||||
Reference in New Issue
Block a user