// Helpers for present mode: a clock, and how the talk is going against the minutes planned in the outline. import { flatten } from './outline.js'; export function formatClock(totalSeconds) { const s = Math.max(0, Math.round(totalSeconds)); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = s % 60; const mm = String(m).padStart(h ? 2 : 1, '0'); return `${h ? `${h}:` : ''}${mm}:${String(sec).padStart(2, '0')}`; } // The outline as a list of steps to move through, with the minutes planned so far at the *end* of each step. export function outlineSteps(tree) { let planned = 0; return flatten(tree).map(({ node, depth, label }) => { const minutes = Number.isFinite(node.minutes) && node.minutes > 0 ? node.minutes : 0; planned += minutes; return { id: node.id, label, depth, text: node.text ?? '', ref: node.ref ?? '', note: node.note ?? '', kind: node.kind ?? '', scripture: node.scripture ?? null, attachments: node.attachments ?? [], minutes, plannedEnd: planned }; }); } // How the talk is going. Each point has a window in the plan: it should start when the earlier points' minutes are // used up and end when its own are. Being on a point before its window starts means you are ahead; still on it // after its window ends means you are behind; anywhere in the window is on pace. // -> { plannedSeconds (when it should start), endSeconds, totalSeconds, diffSeconds (+ ahead, - behind) } or null when no minutes are planned. export function pacing(steps, index, elapsedSeconds) { const total = steps.length ? steps[steps.length - 1].plannedEnd : 0; if (!total) return null; const startSeconds = (index > 0 ? steps[index - 1].plannedEnd : 0) * 60; const endSeconds = (steps[index]?.plannedEnd ?? total) * 60; let diff = 0; if (elapsedSeconds < startSeconds) diff = startSeconds - elapsedSeconds; else if (elapsedSeconds > endSeconds) diff = endSeconds - elapsedSeconds; return { plannedSeconds: startSeconds, endSeconds, totalSeconds: total * 60, diffSeconds: diff }; } export const paceText = (p) => { if (!p) return ''; const d = Math.round(p.diffSeconds); if (Math.abs(d) < 20) return 'On pace'; return d > 0 ? `${formatClock(d)} ahead` : `${formatClock(-d)} behind`; }; // How the planned minutes compare with the time you have. `limit` is the session length (null/0 = not set). // -> { state: 'unset' | 'under' | 'on' | 'over', diff (minutes, + to spare, - over), text } export function planStatus(total, limit) { const t = Number(total) || 0; const l = Number(limit) || 0; if (!l) return { state: 'unset', diff: 0, text: '' }; const diff = l - t; if (t === 0) return { state: 'under', diff, text: `No minutes planned yet for ${l} min` }; if (diff === 0) return { state: 'on', diff, text: `${t} of ${l} min planned, right on time` }; if (diff > 0) return { state: 'under', diff, text: `${t} of ${l} min planned, ${diff} min to spare` }; return { state: 'over', diff, text: `${t} of ${l} min planned, ${-diff} min over` }; } // The first point that carries the plan past the session length, or null. export function firstOverPoint(steps, limit) { const l = Number(limit) || 0; return l ? steps.find((s) => s.plannedEnd > l) ?? null : null; }