From f24d31b9287042ee3fda7119db88d3a9d45438f5 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Wed, 21 Dec 2022 14:18:21 +0100 Subject: [PATCH] Improve countdown logic use `Intl.RelativeTimeFormat` to support different l10n add week as possible duration --- includes/view/ShiftEntry_view.php | 10 ++- includes/view/Shifts_view.php | 5 +- includes/view/User_view.php | 15 ++-- resources/assets/js/countdown.js | 108 +++++++++------------------ resources/views/pages/design.twig | 24 +++--- resources/views/pages/login.twig | 2 +- src/Controllers/DesignController.php | 34 +++++---- 7 files changed, 87 insertions(+), 111 deletions(-) diff --git a/includes/view/ShiftEntry_view.php b/includes/view/ShiftEntry_view.php index fb63c45c..d3194e0f 100644 --- a/includes/view/ShiftEntry_view.php +++ b/includes/view/ShiftEntry_view.php @@ -1,5 +1,6 @@ format(__('Y-m-d H:i')); return page_with_title( ShiftEntry_create_title() . ': ' . $shift['name'] - . ' %c', + . ' %c', [ Shift_view_header($shift, $room), info(__('Do you want to sign up the following user for this shift?'), true), @@ -116,9 +118,10 @@ function ShiftEntry_create_view_admin( */ function ShiftEntry_create_view_supporter($shift, Room $room, AngelType $angeltype, $signup_user, $users_select) { + $start = Carbon::createFromTimestamp($shift['start'])->format(__('Y-m-d H:i')); return page_with_title( ShiftEntry_create_title() . ': ' . $shift['name'] - . ' %c', + . ' %c', [ Shift_view_header($shift, $room), info(sprintf( @@ -144,9 +147,10 @@ function ShiftEntry_create_view_supporter($shift, Room $room, AngelType $angelty */ function ShiftEntry_create_view_user($shift, Room $room, AngelType $angeltype, $comment) { + $start = Carbon::createFromTimestamp($shift['start'])->format(__('Y-m-d H:i')); return page_with_title( ShiftEntry_create_title() . ': ' . $shift['name'] - . ' %c', + . ' %c', [ Shift_view_header($shift, $room), info(sprintf(__('Do you want to sign up for this shift as %s?'), AngelType_name_render($angeltype)), true), diff --git a/includes/view/Shifts_view.php b/includes/view/Shifts_view.php index 35416e9d..ce8b1fdc 100644 --- a/includes/view/Shifts_view.php +++ b/includes/view/Shifts_view.php @@ -1,5 +1,6 @@ format(__('Y-m-d H:i')); + return page_with_title( - $shift['name'] . ' %c', + $shift['name'] . ' %c', $content ); } diff --git a/includes/view/User_view.php b/includes/view/User_view.php index f03407f8..ed80bee6 100644 --- a/includes/view/User_view.php +++ b/includes/view/User_view.php @@ -189,25 +189,28 @@ function User_shift_state_render($user) $nextShift = array_shift($upcoming_shifts); + $start = Carbon::createFromTimestamp($nextShift['start'])->format(__('Y-m-d H:i')); + if ($nextShift['start'] > time()) { if ($nextShift['start'] - time() > 3600) { - return '' + return '' . __('Next shift %c') . ''; } - return '' + return '' . __('Next shift %c') . ''; } $halfway = ($nextShift['start'] + $nextShift['end']) / 2; if (time() < $halfway) { - return '' + return '' . __('Shift started %c') . ''; } - return '' + $end = Carbon::createFromTimestamp($nextShift['end'])->format(__('Y-m-d H:i')); + return '' . __('Shift ends %c') . ''; } @@ -224,7 +227,9 @@ function User_last_shift_render($user) } $lastShift = array_shift($last_shifts); - return '' + $end = Carbon::createFromTimestamp($lastShift['end'])->format(__('Y-m-d H:i')); + + return '' . __('Shift ended %c') . ''; } diff --git a/resources/assets/js/countdown.js b/resources/assets/js/countdown.js index cf47aaa2..48bfbbba 100644 --- a/resources/assets/js/countdown.js +++ b/resources/assets/js/countdown.js @@ -1,85 +1,47 @@ import { ready } from './ready'; -const lang = document.documentElement.getAttribute('lang'); - -const templateFuture = 'in %value %unit'; -const templatePast = lang === 'en' - ? '%value %unit ago' - : 'vor %value %unit'; - -const yearUnits = lang === 'en' - ? ['year', 'years'] - : ['Jahr', 'Jahren']; - -const monthUnits = lang === 'en' - ? ['month', 'months'] - : ['Monat', 'Monaten']; - -const dayUnits = lang === 'en' - ? ['day', 'days'] - : ['Tag', 'Tagen']; - -const hourUnits = lang === 'en' - ? ['hour', 'hours'] - : ['Stunde', 'Stunden']; - -const minuteUnits = lang === 'en' - ? ['minute', 'minutes'] - : ['Minute', 'Minuten']; - -const secondUnits = lang === 'en' - ? ['second', 'seconds'] - : ['Sekunde', 'Sekunden']; - -const nowString = lang === 'en' ? 'now' : 'jetzt'; - -const secondsHour = 60 * 60; - -const timeFrames = [ - [365 * 24 * 60 * 60, yearUnits], - [30 * 24 * 60 * 60, monthUnits], - [24 * 60 * 60, dayUnits], - [secondsHour, hourUnits], - [60, minuteUnits], - [1, secondUnits], -]; - -/** - * @param {number} timestamp - * @returns {string} - */ -function formatFromNow(timestamp) { - const now = Date.now() / 1000; - const diff = Math.abs(timestamp - now); - const ago = now > timestamp; - - for (const [duration, [singular, plural]] of timeFrames) { - const value = diff < secondsHour - ? Math.floor(diff / duration) - : Math.round(diff / duration); - - if (value) { - const template = ago ? templatePast : templateFuture; - const unit = value === 1 ? singular : plural; - return template - .replace('%value', value) - .replace('%unit', unit); - } - } - - return nowString; -} - /** * Initialises all countdown fields on the page. */ ready(() => { + const lang = document.documentElement.getAttribute('lang'); + + const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }); + + const timeFrames = [ + [60 * 60 * 24 * 365, 'year'], + [60 * 60 * 24 * 30, 'month'], + [60 * 60 * 24 * 7, 'week'], + [60 * 60 * 24, 'day'], + [60 * 60, 'hour'], + [60, 'minute'], + [1, 'second'], + ]; + + /** + * @param {number} timestamp + * @returns {string} + */ + function formatFromNow(timestamp) { + const now = Date.now() / 1000; + const diff = Math.round(timestamp - now); + const absValue = Math.abs(diff); + + for (const [duration, unit] of timeFrames) { + if (absValue >= duration) { + return rtf.format(Math.round(diff / duration), unit); + } + } + + return rtf.format(0, 'second'); + } + document.querySelectorAll('[data-countdown-ts]').forEach((element) => { const timestamp = Number(element.dataset.countdownTs); - const template = element.innerHTML; - element.innerHTML = template.replace('%c', formatFromNow(timestamp)); + const template = element.textContent; + element.textContent = template.replace('%c', formatFromNow(timestamp)); setInterval(() => { - element.innerHTML = template.replace('%c', formatFromNow(timestamp)); + element.textContent = template.replace('%c', formatFromNow(timestamp)); }, 1000); }); }); diff --git a/resources/views/pages/design.twig b/resources/views/pages/design.twig index 6c43c65c..ad15d441 100644 --- a/resources/views/pages/design.twig +++ b/resources/views/pages/design.twig @@ -322,20 +322,20 @@

Countdowns

    -
  • 30s: %c
  • -
  • 30m: %c
  • -
  • 59m: %c
  • -
  • 1h: %c
  • -
  • 1h 30m: %c
  • -
  • 1h 31m: %c
  • -
  • 2h: %c
  • -
  • 2d: %c
  • -
  • 3m: %c
  • -
  • 22y: %c
  • +
  • 30s: %c
  • +
  • 30m: %c
  • +
  • 59m: %c
  • +
  • 1h: %c
  • +
  • 1h 30m: %c
  • +
  • 1h 31m: %c
  • +
  • 2h: %c
  • +
  • 2d: %c
  • +
  • 3m: %c
  • +
  • 22y: %c
    -
  • 30m ago: %c
  • -
  • 45m ago: %c
  • +
  • 30m ago: %c
  • +
  • 45m ago: %c
diff --git a/resources/views/pages/login.twig b/resources/views/pages/login.twig index c7ee7b90..8da88ed5 100644 --- a/resources/views/pages/login.twig +++ b/resources/views/pages/login.twig @@ -21,7 +21,7 @@ {% if date > date() %}

{{ name }}

-
%c
+
%c
{{ date.format(__('Y-m-d')) }}
{% endif %} diff --git a/src/Controllers/DesignController.php b/src/Controllers/DesignController.php index 59c3156e..e52cd187 100644 --- a/src/Controllers/DesignController.php +++ b/src/Controllers/DesignController.php @@ -41,24 +41,26 @@ class DesignController extends BaseController ])); $themes = $this->config->get('themes'); + $date = new \DateTimeImmutable(); $data = [ - 'demo_user' => $demoUser, - 'demo_user_2' => $demoUser2, - 'themes' => $themes, - 'bar_chart' => BarChart::render(...BarChart::generateChartDemoData(23)), - 'timestamp30m' => time() + 30 * 60, - 'timestamp59m' => time() + 59 * 60, - 'timestamp1h' => time() + 1 * 60 * 60, - 'timestamp1h30m' => time() + 90 * 60, - 'timestamp1h31m' => time() + 91 * 60, - 'timestamp2h' => time() + 2 * 60 * 60, - 'timestamp2d' => time() + 2 * 24 * 60 * 60, - 'timestamp3m' => time() + 3 * 30 * 24 * 60 * 60, - 'timestamp22y' => time() + 22 * 365 * 24 * 60 * 60, - 'timestamp30s' => time() + 30, + 'demo_user' => $demoUser, + 'demo_user_2' => $demoUser2, + 'themes' => $themes, + 'bar_chart' => BarChart::render(...BarChart::generateChartDemoData(23)), - 'timestamp30mago' => time() - 30 * 60, - 'timestamp45mago' => time() - 45 * 60, + 'timestamp30m' => $date->add(new \DateInterval('PT30M')), + 'timestamp59m' => $date->add(new \DateInterval('PT59M')), + 'timestamp1h' => $date->add(new \DateInterval('PT1H')), + 'timestamp1h30m' => $date->add(new \DateInterval('PT1H30M')), + 'timestamp1h31m' => $date->add(new \DateInterval('PT1H31M')), + 'timestamp2h' => $date->add(new \DateInterval('PT2H')), + 'timestamp2d' => $date->add(new \DateInterval('P2D')), + 'timestamp3m' => $date->add(new \DateInterval('P3M')), + 'timestamp22y' => $date->add(new \DateInterval('P22Y')), + 'timestamp30s' => $date->add(new \DateInterval('PT30S')), + + 'timestamp30mago' => $date->sub(new \DateInterval('PT30M')), + 'timestamp45mago' => $date->sub(new \DateInterval('PT45M')), ]; return $this->response->withView(