Files
dotfiles/shell/plugins/panels/clock/Model.js
T

132 lines
5.0 KiB
JavaScript

// Clock label formats and the memento mori bar. Calendar date math lives in
// qs.Ui's CalendarModel.js, shared with the Quick Settings calendar.
// ---- Bar label formats. Right-clicking the clock walks these in order and
// writes the result back to shell.json, so the label the bar shows and
// the format the config stores are always the same thing.
//
// The locale-shaped time presets are each followed by their 12-hour twin, so
// the walk from a 24-hour label to the same label in AM/PM is a single right
// click rather than a lap of the ring. The ISO preset is deliberately left
// without one: ISO 8601 writes time on a 24-hour clock, so an AM/PM variant
// would contradict the only thing that format is for.
var CLOCK_FORMATS = [
"dddd HH:mm",
"dddd h:mm AP",
"HH:mm",
"h:mm AP",
"ddd d MMM HH:mm",
"ddd d MMM h:mm AP",
"d MMMM 'W'ww yyyy",
"yyyy-MM-dd HH:mm"
]
// Vertical bars have room for a few stacked lines and nothing else, so the
// ring stays short. AM/PM costs a fourth line, which is why only the plain
// time carries it here.
var VERTICAL_CLOCK_FORMATS = [
"HH\n—\nmm",
"h\n—\nmm\nAP",
"dd\nMMM\n'W'ww\n''yy",
"HH\nmm"
]
function clockFormats(vertical) {
return vertical ? VERTICAL_CLOCK_FORMATS.slice() : CLOCK_FORMATS.slice()
}
// The presets in a fixed order, plus the configured alternate and current
// format when they are something else. The order must not depend on which
// entry is current: cycling writes the result back to shell.json, and a ring
// that reshuffled itself around the current value would bounce between two
// entries instead of walking.
function clockFormatRing(configured, configuredAlt, presets) {
var ring = []
var candidates = (presets || []).concat([configuredAlt, configured])
for (var i = 0; i < candidates.length; i++) {
var format = String(candidates[i] === undefined || candidates[i] === null ? "" : candidates[i])
if (format === "" || ring.indexOf(format) !== -1) continue
ring.push(format)
}
return ring.length > 0 ? ring : ["HH:mm"]
}
// Next entry after `current`. An unknown current format (a hand-written one
// that is not in the ring) starts the walk at the top.
function nextClockFormat(ring, current) {
if (!ring || ring.length === 0) return ""
var index = ring.indexOf(String(current === undefined || current === null ? "" : current))
return ring[(index + 1) % ring.length]
}
// Memento mori. The default span is a round number rather than anything from
// an actuarial table: the point of the bar is the reminder, not the
// arithmetic, and whoever wants a different number can say so.
var DEFAULT_LIFE_EXPECTANCY = 90
// A birth year rather than an age, so the bar keeps counting on its own
// instead of going stale the moment it is entered. 0 means "not set", which
// is also what a blank, malformed, future, or implausibly distant year means.
function parseBirthYear(value, currentYear) {
var now = Math.round(Number(currentYear))
if (!isFinite(now)) return 0
var text = String(value === undefined || value === null ? "" : value).replace(/^\s+|\s+$/g, "")
if (!/^\d{4}$/.test(text)) return 0
var year = parseInt(text, 10)
if (!isFinite(year) || year > now || year < now - 120) return 0
return year
}
// Whole years, the way people say their age: born in 1979 makes you 47 for
// all of 2026, whichever side of your birthday today falls.
function ageFromBirthYear(birthYear, currentYear) {
var born = parseBirthYear(birthYear, currentYear)
if (born <= 0) return 0
return Math.round(Number(currentYear)) - born
}
// 0 means "not set", which is also what a blank, negative, fractional, or
// absurd entry means — the life bar simply stays hidden.
function parseAge(value) {
var text = String(value === undefined || value === null ? "" : value).replace(/^\s+|\s+$/g, "")
if (!/^\d+$/.test(text)) return 0
var years = parseInt(text, 10)
if (!isFinite(years) || years <= 0 || years > 120) return 0
return years
}
// Unset or nonsense falls back to the default rather than to zero, so the
// bar always has something to measure against.
function parseLifeExpectancy(value) {
var text = String(value === undefined || value === null ? "" : value).replace(/^\s+|\s+$/g, "")
if (!/^\d+$/.test(text)) return DEFAULT_LIFE_EXPECTANCY
var years = parseInt(text, 10)
if (!isFinite(years) || years <= 0 || years > 150) return DEFAULT_LIFE_EXPECTANCY
return years
}
function lifeProgress(age, expectancy) {
var years = parseAge(age)
var span = parseLifeExpectancy(expectancy)
if (years <= 0 || span <= 0) return 0
return Math.max(0, Math.min(1, years / span))
}
function lifeProgressPercent(age, expectancy) {
return Math.round(lifeProgress(age, expectancy) * 100)
}
if (typeof module !== "undefined") {
module.exports = {
parseAge: parseAge,
parseBirthYear: parseBirthYear,
ageFromBirthYear: ageFromBirthYear,
parseLifeExpectancy: parseLifeExpectancy,
lifeProgress: lifeProgress,
lifeProgressPercent: lifeProgressPercent,
clockFormats: clockFormats,
clockFormatRing: clockFormatRing,
nextClockFormat: nextClockFormat
}
}