Initial commit: X-Plane G1000 web cockpit + bridge + Tauri desktop app

- server/: Node bridge (datarefs/commands, navdata, CIFP procedures, flight plan)
- web/: React cockpit (PFD/MFD/Map, VFR six-pack, AFCS, FMS CDU), PWA, collapsible sidebar
- desktop/: Tauri 2 launcher (Bun sidecar, system tray, updater) + Linux build via Docker
- scripts/: prep-desktop, build-linux, Gitea release + latest.json

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 15:07:03 +02:00
commit ebc33a78b7
110 changed files with 14671 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
import React, { useState } from 'react';
import { useXplane } from './api/useXplane.js';
import PFD from './components/PFD.jsx';
import AutopilotPanel from './components/AutopilotPanel.jsx';
import MFD from './components/MFD.jsx';
import MapView from './components/MapView.jsx';
import CDU from './components/CDU.jsx';
import VFR from './components/VFR.jsx';
import Bezel from './components/Bezel.jsx';
import DirectTo from './components/DirectTo.jsx';
import Proc from './components/Proc.jsx';
// Compact line icons for the nav rail (stroke = currentColor).
const ICONS = {
pfd: 'M11 3a8 8 0 100 16 8 8 0 000-16zM3.5 11h15M7 8l1.5 1M15 8l-1.5 1',
mfd: 'M3 6l5-2 6 2 5-2v12l-5 2-6-2-5 2zM8 4v12M14 6v12',
map: 'M11 2c-3.3 0-6 2.6-6 5.9 0 4.4 6 11.1 6 11.1s6-6.7 6-11.1C17 4.6 14.3 2 11 2z',
fms: 'M4 6h14M4 11h14M4 16h9',
ap: 'M11 4a7 7 0 100 14 7 7 0 000-14zM11 4v3M11 15v3M4 11h3M15 11h3',
vfr: 'M11 4a7 7 0 100 14 7 7 0 000-14zM11 11l4.5-3',
};
function Icon({ name }) {
return (
<svg className="snav-ic" viewBox="0 0 22 22" width="22" height="22" fill="none"
stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
<path d={ICONS[name]} />
{name === 'map' && <circle cx="11" cy="8" r="2" />}
</svg>
);
}
const TABS = [
{ id: 'pfd', label: 'PFD' },
{ id: 'mfd', label: 'MFD' },
{ id: 'map', label: 'Map' },
{ id: 'fms', label: 'FMS' },
{ id: 'vfr', label: 'VFR' },
{ id: 'ap', label: 'Autopilot' },
];
export default function App() {
const xp = useXplane();
const [tab, setTab] = useState(() => location.hash.replace('#', '') || 'pfd');
// Collapsible nav rail: narrow (icons) ↔ wide (icons + labels), remembered.
const [navWide, setNavWide] = useState(() => localStorage.getItem('navWide') === '1');
const go = (id) => { setTab(id); history.replaceState(null, '', `#${id}`); };
const toggleNav = () => setNavWide((w) => { localStorage.setItem('navWide', w ? '0' : '1'); return !w; });
// Synthetic-terrain (3D) vs. classic blue/brown attitude — toggled by the
// PFD → SYN TERR softkey, exactly like the real XPLANE 1000.
const [svt3d, setSvt3d] = useState(true);
// The PFD INSET map (bottom-left) is off by default and toggled by its softkey.
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);
// 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);
const connKind = xp.xpConnected ? 'ok' : xp.connected ? 'warn' : 'bad';
const connText = xp.xpConnected ? 'X-PLANE' : xp.connected ? 'NO SIM' : 'OFFLINE';
return (
<div className={`app ${navWide ? 'nav-wide' : 'nav-narrow'}`}>
<aside className="sidebar">
<button className="sb-top" onClick={toggleNav} title="Menü ein-/ausklappen">
<span className="brand">G<span>1000</span></span>
<span className="sb-chev">{navWide ? '◂' : '▸'}</span>
</button>
<nav className="snav">
{TABS.map((t) => (
<button key={t.id} className={tab === t.id ? 'snav-i active' : 'snav-i'}
onClick={() => go(t.id)} title={t.label}>
<Icon name={t.id} />
<span className="snav-lbl">{t.label}</span>
</button>
))}
</nav>
<div className={`sb-conn ${connKind}`} title={connText}>
<span className="dot" />
<span className="snav-lbl">{connText}</span>
</div>
</aside>
<main className="screen">
{tab === 'pfd' && (
<Bezel variant="pfd" xp={xp} 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} />
</Bezel>
)}
{tab === 'mfd' && (
<Bezel variant="mfd" xp={xp} mapMode={mapMode} onMapMode={setMapMode} onDirect={() => setDto(true)} onProc={() => setProc(true)}>
<MFD values={xp.values} flightPlan={xp.flightPlan} fp={xp.fp} mapMode={mapMode} />
</Bezel>
)}
{tab === 'map' && <MapView values={xp.values} flightPlan={xp.flightPlan} fp={xp.fp} />}
{tab === 'fms' && <CDU xp={xp} />}
{tab === 'vfr' && <VFR values={xp.values} />}
{tab === 'ap' && <AutopilotPanel xp={xp} />}
</main>
{dto && <DirectTo xp={xp} onClose={() => setDto(false)} />}
{proc && <Proc xp={xp} onClose={() => setProc(false)} />}
</div>
);
}