contents/ui/PanelDisplay.js
.pragma library
var percentMode = "percent";
var paceMode = "pace";
var bothMode = "both";
var resetTimeMode = "resetTime";
var runOutMode = "runOut";
function safeMode(value) {
var mode = String(value || percentMode);
if (mode === percentMode || mode === paceMode || mode === bothMode || mode === resetTimeMode || mode === runOutMode) {
return mode;
}
return percentMode;
}
function safeLane(value) {
return value === "primary" || value === "secondary" || value === "tertiary" ? value : "auto";
}
function hasFiniteNumber(value) {
return typeof value === "number" && isFinite(value);
}
function rowHasPercent(row) {
return row !== null && row !== undefined && row.hasPercent === true && hasFiniteNumber(row.usedPercent) && hasFiniteNumber(row.leftPercent);
}
function rowHasPace(row) {
return row !== null && row !== undefined && hasFiniteNumber(row.pacePercent) && row.pacePercent >= 0;
}
function rowHasReset(row) {
if (!row) {
return false;
}
return (typeof row.resetsAt === "string" && row.resetsAt.trim().length > 0) || (typeof row.resetDescription === "string" && row.resetDescription.trim().length > 0) || (typeof row.reset === "string" && row.reset.trim().length > 0);
}
function rowHasRunOut(row) {
return row !== null && row !== undefined && row.paceOnTop === false && hasFiniteNumber(row.paceEtaSeconds) && row.paceEtaSeconds > 0;
}
function rowSupportsMode(row, value) {
var mode = safeMode(value);
switch (mode) {
case paceMode:
return rowHasPace(row);
case bothMode:
return rowHasPercent(row) || rowHasPace(row);
case resetTimeMode:
return rowHasReset(row);
case runOutMode:
return rowHasRunOut(row);
default:
return rowHasPercent(row);
}
}
// The caller supplies provider-specific preference order. This function only
// decides whether a row carries the data required by the selected mode.
function rowForMode(rows, value, laneValue) {
if (!Array.isArray(rows)) {
return null;
}
var lane = safeLane(laneValue);
if (lane !== "auto") {
rows = rows.filter(function(row) { return row && row.lane === lane; });
}
var mode = safeMode(value);
if (mode === bothMode) {
for (var i = 0; i < rows.length; i++) {
if (rowHasPercent(rows[i]) && rowHasPace(rows[i])) {
return rows[i];
}
}
}
for (var j = 0; j < rows.length; j++) {
if (rowSupportsMode(rows[j], mode)) {
return rows[j];
}
}
return null;
}
// Meter positions follow named lanes, not the provider's text preference or
// current consumption. Providers without those lanes keep their single fallback.
function meterRows(rows, laneValue) {
var lane = safeLane(laneValue);
if (lane !== "auto") {
var selected = rowForMode(rows, percentMode, lane);
return selected ? [selected] : [];
}
var result = [];
var primary = rowForMode(rows, percentMode, "primary");
var secondary = rowForMode(rows, percentMode, "secondary");
if (primary) result.push(primary);
if (secondary) result.push(secondary);
if (result.length === 0) {
var fallback = rowForMode(rows, percentMode);
if (fallback) result.push(fallback);
}
return result;
}
// Forecast ETA is a duration observed with one usage snapshot, not an absolute
// timestamp. Advance it from that observation without triggering another CLI run.
function remainingSeconds(durationSeconds, observedAtMs, nowMs) {
if (!hasFiniteNumber(durationSeconds) || durationSeconds < 0 || !hasFiniteNumber(observedAtMs) || !hasFiniteNumber(nowMs)) {
return 0;
}
var elapsedSeconds = Math.max(0, (nowMs - observedAtMs) / 1000);
return Math.max(0, durationSeconds - elapsedSeconds);
}