FMS/CDU: 6th line-select key, HOLD page, route STEP — manual-complete
- 6 LSKs per side now (real Boeing CDU layout); LEGS shows 6 rows; FPLN bottom links (ROUTE MENU / VNAV) moved to the 6th key. - HOLD page: hold fix (active wpt), inbound course, turn direction, leg time. - STEP (p31): 6R on LEGS steps a cyan review cursor through the route, auto- paging, with a "viewing <wpt>" readout — the CDU side of plan review. - Page keys: added HOLD (now FPLN/LEGS/DEP-ARR/DIR-INTC · FIX/HOLD/VNAV/PROG · MENU). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+39
-10
@@ -30,8 +30,8 @@ function destPoint(lat, lon, brgDeg, dNm) {
|
||||
return { lat: deg(p2), lon: ((deg(l2) + 540) % 360) - 180 };
|
||||
}
|
||||
|
||||
const PAGE_KEYS = [['fpln', 'FPLN'], ['legs', 'LEGS'], ['deparr', 'DEP/ARR'], ['dir', 'DIR-INTC'], ['fix', 'FIX'], ['vnav', 'VNAV'], ['prog', 'PROG'], ['menu', 'MENU']];
|
||||
const LEG_ROWS = 5;
|
||||
const PAGE_KEYS = [['fpln', 'FPLN'], ['legs', 'LEGS'], ['deparr', 'DEP/ARR'], ['dir', 'DIR-INTC'], ['fix', 'FIX'], ['hold', 'HOLD'], ['vnav', 'VNAV'], ['prog', 'PROG'], ['menu', 'MENU']];
|
||||
const LEG_ROWS = 6;
|
||||
const VNAV_PAGES = ['CLB', 'CRZ', 'DES'];
|
||||
// a flight-plan leg with no coordinates is a discontinuity (e.g. a VECTORS or
|
||||
// heading-to-altitude procedure leg) — shown as "DISCONTINUITY" and stitched
|
||||
@@ -67,6 +67,13 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
const [fixRef, setFixRef] = useState(null); // resolved reference navaid {id,lat,lon}
|
||||
const [fixRad, setFixRad] = useState(''); // crossing radial (deg)
|
||||
const [fixDist, setFixDist] = useState(''); // distance along radial (NM)
|
||||
// HOLD (manual: HOLD page): a holding pattern at a fix — inbound course,
|
||||
// turn direction, leg time. Defaults to the active waypoint.
|
||||
const [holdCrs, setHoldCrs] = useState('');
|
||||
const [holdTurn, setHoldTurn] = useState('R');
|
||||
const [holdTime, setHoldTime] = useState('1.0');
|
||||
// STEP (p31): a review cursor stepped through the route with the 6R key.
|
||||
const [stepIdx, setStepIdx] = useState(null);
|
||||
// MOD/ACT: the EXEC light arms while edits are pending, like the real CDU.
|
||||
const [mod, setMod] = useState(false);
|
||||
// saved-plan list (MENU)
|
||||
@@ -153,8 +160,8 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
if (side === 'L' && r === 0 && scr) return setOrigin(scr);
|
||||
if (side === 'R' && r === 0 && scr) return setDest(scr);
|
||||
if (side === 'R' && r === 1) { if (scr) { setFltNo(scr); setScr(''); } return; }
|
||||
if (side === 'L' && r === 4) return setPage('menu');
|
||||
if (side === 'R' && r === 4) return setPage('vnav');
|
||||
if (side === 'L' && r === 5) return setPage('menu');
|
||||
if (side === 'R' && r === 5) return setPage('vnav');
|
||||
return;
|
||||
}
|
||||
if (page === 'legs') {
|
||||
@@ -165,6 +172,10 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
if (i < wps.length && isDisco(wps[i])) { fp.remove(i); setMod(true); return; } // clear discontinuity
|
||||
if (i >= 1 && i < wps.length) { fp.setActive(i); setMod(true); }
|
||||
}
|
||||
// STEP (6R): advance a review cursor through the route, auto-paging (p31)
|
||||
if (side === 'R' && r === 5 && wps.length) {
|
||||
setStepIdx((s) => { const n = ((s == null ? active - 1 : s) + 1) % wps.length; setLegPage(Math.floor(n / LEG_ROWS)); return n; });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (page === 'deparr') {
|
||||
@@ -191,6 +202,12 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
if (side === 'R' && r === 0) return insertFix();
|
||||
return;
|
||||
}
|
||||
if (page === 'hold') {
|
||||
if (side === 'L' && r === 1 && scr) { setHoldCrs(scr); setScr(''); return; }
|
||||
if (side === 'R' && r === 1) { setHoldTurn((t) => (t === 'R' ? 'L' : 'R')); return; }
|
||||
if (side === 'L' && r === 2 && scr) { setHoldTime(scr); setScr(''); return; }
|
||||
return;
|
||||
}
|
||||
if (page === 'vnav') {
|
||||
if (vnavPage === 0) { // CLB: trans alt (1L), speed/alt limits (2L, 3L)
|
||||
if (side === 'L' && r === 0 && scr) { setTransAlt(scr); setScr(''); return; }
|
||||
@@ -244,17 +261,17 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
const w = wps[i], prev = wps[i - 1];
|
||||
if (isDisco(w)) { rows.push({ disco: true }); continue; }
|
||||
const linkable = prev && !isDisco(prev);
|
||||
rows.push({ id: w.id, type: w.type, dtk: linkable ? Math.round(brng(prev, w)) : null, d: linkable ? distNm(prev, w) : 0, orig: i === 0, act: i === active, missed: w.missed });
|
||||
rows.push({ id: w.id, type: w.type, dtk: linkable ? Math.round(brng(prev, w)) : null, d: linkable ? distNm(prev, w) : 0, orig: i === 0, act: i === active, missed: w.missed, step: i === stepIdx });
|
||||
}
|
||||
else if (i === wps.length) rows.push({ add: true });
|
||||
else rows.push({ blank: true });
|
||||
}
|
||||
body = (<div className="cdu-cols2">{rows.map((row, r) => (
|
||||
<div className={`cdu-row ${row.act ? 'act' : ''} ${row.missed ? 'dim' : ''} ${row.disco ? 'disco' : ''}`} key={r}>
|
||||
body = (<><div className="cdu-cols2">{rows.map((row, r) => (
|
||||
<div className={`cdu-row ${row.act ? 'act' : ''} ${row.missed ? 'dim' : ''} ${row.disco ? 'disco' : ''} ${row.step ? 'step' : ''}`} key={r}>
|
||||
{row.blank ? <span className="cdu-empty">·</span> : row.add ? <span className="cdu-add"><----- ENTER WPT</span>
|
||||
: row.disco ? <span className="cdu-add">─── DISCONTINUITY ───</span>
|
||||
: (<><span className="cdu-wpt">{row.id}<i>{row.type}</i></span><span className="cdu-dtk">{row.dtk == null ? '---' : `${String(row.dtk).padStart(3, '0')}°`}</span><span className="cdu-dist">{row.orig ? 'ORIG' : row.d.toFixed(1)}</span></>)}
|
||||
</div>))}</div>);
|
||||
</div>))}</div><div className="cdu-note small">STEP through route: 6R{stepIdx != null ? ` · viewing ${wps[stepIdx]?.id || ''}` : ''}</div></>);
|
||||
} else if (page === 'deparr') {
|
||||
const kind = cat === 'sids' ? 'DEPART' : cat === 'stars' ? 'ARRIVAL' : 'APPROACH';
|
||||
if (selProc) {
|
||||
@@ -317,6 +334,18 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
<div className="cdu-note small">REF:1L · RADIAL:2L · DIST:3L · INSERT:1R</div>
|
||||
</div>
|
||||
);
|
||||
} else if (page === 'hold') {
|
||||
const hf = wps[active] || wps[wps.length - 1];
|
||||
title = 'HOLD';
|
||||
body = (
|
||||
<div className="cdu-vnav">
|
||||
<div className="cdu-fl"><label>HOLD AT</label><b>{hf?.id || '----'}</b></div>
|
||||
<div className="cdu-fl"><label>INBD CRS</label><b>{holdCrs ? `${holdCrs}°` : '---°'}</b></div>
|
||||
<div className="cdu-fl r"><label>TURN</label><b>{holdTurn === 'R' ? 'RIGHT' : 'LEFT'}</b></div>
|
||||
<div className="cdu-fl"><label>LEG TIME</label><b>{holdTime} MIN</b></div>
|
||||
<div className="cdu-note small">INBD CRS:2L · TURN:2R · LEG TIME:3L</div>
|
||||
</div>
|
||||
);
|
||||
} else if (page === 'vnav') {
|
||||
title = `ACT VNAV ${VNAV_PAGES[vnavPage]}`; pageNo = `${vnavPage + 1}/3`;
|
||||
if (vnavPage === 0) { // CLB (manual p21-22)
|
||||
@@ -370,7 +399,7 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
<div className="cdu">
|
||||
<div className="cdu-unit">
|
||||
<div className="cdu-screenwrap">
|
||||
<div className="cdu-lsks left">{[0, 1, 2, 3, 4].map((r) => <Lsk key={r} side="L" r={r} />)}</div>
|
||||
<div className="cdu-lsks left">{[0, 1, 2, 3, 4, 5].map((r) => <Lsk key={r} side="L" r={r} />)}</div>
|
||||
<div className="cdu-screen">
|
||||
<div className="cdu-hdr"><span>{title}</span><span>{pageNo}</span></div>
|
||||
<div className="cdu-body">{body}</div>
|
||||
@@ -380,7 +409,7 @@ export default function CDU({ xp, vnav: vnavCfg, onVnav }) {
|
||||
{exportMsg && !msg && <span className="cdu-msg ok">{exportMsg.ok ? 'EXPORTED ✓' : exportMsg.error}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="cdu-lsks right">{[0, 1, 2, 3, 4].map((r) => <Lsk key={r} side="R" r={r} />)}</div>
|
||||
<div className="cdu-lsks right">{[0, 1, 2, 3, 4, 5].map((r) => <Lsk key={r} side="R" r={r} />)}</div>
|
||||
</div>
|
||||
|
||||
{/* page keys */}
|
||||
|
||||
@@ -670,6 +670,7 @@ body {
|
||||
.cdu-k.fn.exec.arm { background: linear-gradient(#1f8f47, #146b34); color: #fff; border-color: #2ee06a; box-shadow: 0 0 9px rgba(46,224,106,.6); }
|
||||
.cdu-row.disco { background: rgba(255,176,0,.10); }
|
||||
.cdu-row.disco .cdu-add { color: #ffb000; }
|
||||
.cdu-row.step { box-shadow: inset 3px 0 0 #34e0ff; background: rgba(52,224,255,.10); }
|
||||
/* page-key row + multi-page FMS bodies */
|
||||
.cdu-pages { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin: 2px 0 6px; }
|
||||
.cdu-k.pg { font-size: 11px; letter-spacing: .3px; color: #9fb0bd; padding: 8px 2px; }
|
||||
|
||||
Reference in New Issue
Block a user