diff --git a/web/src/components/CDU.jsx b/web/src/components/CDU.jsx index ea3a262..7e8afe4 100644 --- a/web/src/components/CDU.jsx +++ b/web/src/components/CDU.jsx @@ -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 = (