Files
xplane-cockpit/web/src/components/TimerRef.jsx
T
karim 38b048ad41 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>
2026-06-02 02:17:06 +02:00

90 lines
3.7 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useRef, useState } from 'react';
import { num } from '../api/useXplane.js';
// G1000 TMR/REF window (PFD). The real unit shows a generic timer plus the
// reference V-speeds and barometric minimums. This implements the timer
// (count-up or count-down with START/STOP/RESET) and the V-speed / minimums
// references with simple on/off bugs. Self-contained — no sim dependency.
const VSPEEDS = [
{ key: 'vr', label: 'Vr', def: 55 },
{ key: 'vx', label: 'Vx', def: 62 },
{ key: 'vy', label: 'Vy', def: 74 },
{ key: 'vg', label: 'Vg', def: 68 }, // best glide
];
function fmt(sec) {
const s = Math.max(0, Math.floor(sec));
const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), ss = s % 60;
const pad = (n) => String(n).padStart(2, '0');
return h > 0 ? `${pad(h)}:${pad(m)}:${pad(ss)}` : `${pad(m)}:${pad(ss)}`;
}
export default function TimerRef({ values, onClose }) {
const [dir, setDir] = useState('up'); // 'up' | 'dn'
const [running, setRunning] = useState(false);
const [elapsed, setElapsed] = useState(0); // seconds
const [target, setTarget] = useState(300); // count-down start (s)
const [vbugs, setVbugs] = useState({}); // key -> bool (shown on tape, future)
const [minsOn, setMinsOn] = useState(false);
const [mins, setMins] = useState(500); // baro minimums (ft)
const tickRef = useRef(null);
useEffect(() => {
if (!running) return;
const t0 = Date.now() - elapsed * 1000;
tickRef.current = setInterval(() => setElapsed((Date.now() - t0) / 1000), 250);
return () => clearInterval(tickRef.current);
}, [running]); // eslint-disable-line
const shown = dir === 'dn' ? Math.max(0, target - elapsed) : elapsed;
const alt = num(values.altitude);
const belowMins = minsOn && alt > 0 && alt <= mins;
const reset = () => { setRunning(false); setElapsed(0); };
return (
<div className="tmr-window">
<div className="nrst-head">
<span className="nrst-title">TIMER / REFERENCES</span>
</div>
<div className="tmr-body">
<div className="tmr-clock">{fmt(shown)}</div>
<div className="tmr-dir">
<button className={dir === 'up' ? 'on' : ''} onClick={() => { setDir('up'); }}>UP</button>
<button className={dir === 'dn' ? 'on' : ''} onClick={() => { setDir('dn'); }}>DN</button>
</div>
<div className="tmr-ctl">
<button className="fbtn add" onClick={() => setRunning((r) => !r)}>{running ? 'STOP' : 'START'}</button>
<button className="fbtn" onClick={reset}>RESET</button>
</div>
{dir === 'dn' && (
<div className="tmr-target">
<label>FROM</label>
<button onClick={() => setTarget((t) => Math.max(60, t - 60))}></button>
<span>{fmt(target)}</span>
<button onClick={() => setTarget((t) => t + 60)}></button>
</div>
)}
<div className="tmr-sec">REFERENCES V-SPEEDS</div>
<div className="tmr-vspeeds">
{VSPEEDS.map((v) => (
<button key={v.key} className={vbugs[v.key] ? 'on' : ''}
onClick={() => setVbugs((b) => ({ ...b, [v.key]: !b[v.key] }))}>
<i>{v.label}</i><b>{v.def}</b><u>KT</u>
</button>
))}
</div>
<div className="tmr-mins">
<button className={minsOn ? 'on' : ''} onClick={() => setMinsOn((m) => !m)}>MINIMUMS</button>
<button onClick={() => setMins((m) => Math.max(0, m - 100))}></button>
<span className={belowMins ? 'alert' : ''}>{mins} FT</span>
<button onClick={() => setMins((m) => m + 100)}></button>
</div>
{belowMins && <div className="tmr-minalert">MINIMUMS</div>}
</div>
</div>
);
}