From 516a9035656cb53cb47f66908818ed2afbf578df Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 11 Dec 2024 20:22:10 -0500 Subject: [PATCH] Add relative-date-time component --- src/lib/UI/Component.php | 16 +++--- src/public/script/mpj.js | 111 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/src/lib/UI/Component.php b/src/lib/UI/Component.php index d5ccde8..1152530 100644 --- a/src/lib/UI/Component.php +++ b/src/lib/UI/Component.php @@ -36,7 +36,9 @@ class Component
 '; - foreach ($reqs->items as /** @var Request $req */ $req) { ?> + foreach ($reqs->items as /** @var Request $req */ $req) { + $lastPrayed = $req->lastPrayed; + $lastActivity = $lastPrayed->getOrDefault($req->history[0]->asOf); ?>
@@ -115,9 +115,9 @@ class Component public static function relativeDate(string $date): string { $parsed = new DateTimeImmutable($date); - $inZone = $parsed->setTimezone(new DateTimeZone($_REQUEST['time_zone'])); - return sprintf('%s', date_format($inZone, 'l, F j, Y \a\t g:ia T'), - RelativeDate::between('now', $parsed)); + $iso = $parsed->format('c'); + $title = $parsed->setTimezone(new DateTimeZone($_REQUEST['time_zone']))->format('l, F j, Y \a\t g:ia T'); + return "$iso"; } /** diff --git a/src/public/script/mpj.js b/src/public/script/mpj.js index 946b751..d9d7c72 100644 --- a/src/public/script/mpj.js +++ b/src/public/script/mpj.js @@ -1,7 +1,7 @@ "use strict" /** myPrayerJournal script */ -this.mpj = { +window.mpj = { /** * Show a message via toast * @param {string} message The message to show @@ -102,3 +102,112 @@ htmx.on("htmx:configRequest", function (evt) { }) mpj.deriveTimeZone() + +class RelativeDateTime extends HTMLElement { + static observedAttributes = ["interval"] + + static #LessThanXMinutes = Symbol() + static #XMinutes = Symbol() + static #AboutXHours = Symbol() + static #XDays = Symbol() + static #AboutXMonths = Symbol() + static #XMonths = Symbol() + static #AboutXYears = Symbol() + static #OverXYears = Symbol() + static #AlmostXYears = Symbol() + + static #messages = new Map([ + [RelativeDateTime.#LessThanXMinutes, ['less than a minute', 'less than %d minutes']], + [RelativeDateTime.#XMinutes, ['a minute', '%d minutes']], + [RelativeDateTime.#AboutXHours, ['about an hour', 'about %d hours']], + [RelativeDateTime.#XDays, ['a day', '%d days']], + [RelativeDateTime.#AboutXMonths, ['about a month', 'about %d months']], + [RelativeDateTime.#XMonths, ['a month', '%d months']], + [RelativeDateTime.#AboutXYears, ['about a year', 'about %d years']], + [RelativeDateTime.#OverXYears, ['over a year', 'over %d years']], + [RelativeDateTime.#AlmostXYears, ['almost a year', 'almost %d years']], + ]) + + static #aDay = 1440.0 + static #almost2Days = 2520.0 + static #aMonth = 43200.0 + static #twoMonths = 86400.0 + + /** @type Date The date/time for this relative date */ + #jsDate + /** @type ?number The interval timer for this element */ + #timeOut = null + + constructor() { + super(); + } + + #update() { + + const now = new Date() + const minutes = Math.abs((this.#jsDate.getTime() - now.getTime()) / 60 / 1000); + const months = Math.round(minutes / RelativeDateTime.#aMonth); + const years = Math.floor(months / 12); + + /** @type symbol */ + let typ + /** @type number */ + let nbr + + if (minutes < 1.0) { + typ = RelativeDateTime.#LessThanXMinutes + nbr = 1 + } else if (minutes < 45.0) { + typ = RelativeDateTime.#XMinutes + nbr = Math.round(minutes) + } else if (minutes < 90.0) { + typ = RelativeDateTime.#AboutXHours + nbr = 1 + } else if (minutes < RelativeDateTime.#aDay) { + typ = RelativeDateTime.#AboutXHours + nbr = Math.round(minutes / 60) + } else if (minutes < RelativeDateTime.#almost2Days) { + typ = RelativeDateTime.#XDays + nbr = 1 + } else if (minutes < RelativeDateTime.#aMonth) { + typ = RelativeDateTime.#XDays + nbr = Math.round(minutes / RelativeDateTime.#aDay) + } else if (minutes < RelativeDateTime.#twoMonths) { + typ = RelativeDateTime.#AboutXMonths + nbr = Math.round(minutes / RelativeDateTime.#aMonth) + } else if (months < 12) { + typ = RelativeDateTime.#XMonths + nbr = Math.round(minutes / RelativeDateTime.#aMonth) + } else if (months % 12 < 3) { + typ = RelativeDateTime.#AboutXYears + nbr = years + } else if (months % 12 < 9) { + typ = RelativeDateTime.#OverXYears + nbr = years + } else { + typ = RelativeDateTime.#AlmostXYears + nbr = years + 1 + } + + const tmpl = RelativeDateTime.#messages.get(typ) + const message = nbr === 1 ? tmpl[0] : tmpl[1].replace("%d", nbr.toString()) + this.innerText = this.#jsDate < now ? `${message} ago` : `in ${message}` + } + + connectedCallback() { + this.#jsDate = new Date(this.innerText) + this.#update() + } + + disconnectedCallback() { + if (this.#timeOut) clearInterval(this.#timeOut) + } + + attributeChangedCallback(name, oldValue, newValue) { + if (this.#timeOut) clearInterval(this.#timeOut) + if (this.#jsDate) this.#update() + this.#timeOut = setInterval(() => this.#update, parseInt(newValue)) + } +} + +customElements.define("relative-date-time", RelativeDateTime)