Files
xplane-cockpit/web/src/components/TimerRef.jsx
T
karim ebc33a78b7 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>
2026-06-01 15:07:03 +02:00

91 lines
3.8 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>
{onClose && <button className="nrst-x" onClick={onClose}></button>}
</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>
);
}