Improve countdown logic
use `Intl.RelativeTimeFormat` to support different l10n add week as possible duration
This commit is contained in:
parent
98a3187899
commit
f24d31b928
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Room;
|
||||
use Engelsystem\Models\User\User;
|
||||
|
@ -89,9 +90,10 @@ function ShiftEntry_create_view_admin(
|
|||
$signup_user,
|
||||
$users_select
|
||||
) {
|
||||
$start = Carbon::createFromTimestamp($shift['start'])->format(__('Y-m-d H:i'));
|
||||
return page_with_title(
|
||||
ShiftEntry_create_title() . ': ' . $shift['name']
|
||||
. ' <small data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
. ' <small title="' . $start . '" data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
[
|
||||
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']
|
||||
. ' <small data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
. ' <small title="' . $start . '" data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
[
|
||||
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']
|
||||
. ' <small data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
. ' <small title="' . $start . '" data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
[
|
||||
Shift_view_header($shift, $room),
|
||||
info(sprintf(__('Do you want to sign up for this shift as %s?'), AngelType_name_render($angeltype)), true),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Engelsystem\Models\AngelType;
|
||||
use Engelsystem\Models\Room;
|
||||
use Engelsystem\Models\Shifts\ShiftType;
|
||||
|
@ -195,8 +196,10 @@ function Shift_view($shift, ShiftType $shifttype, Room $room, $angeltypes_source
|
|||
$content[] = Shift_editor_info_render($shift);
|
||||
}
|
||||
|
||||
$start = Carbon::createFromTimestamp($shift['start'])->format(__('Y-m-d H:i'));
|
||||
|
||||
return page_with_title(
|
||||
$shift['name'] . ' <small data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
$shift['name'] . ' <small title="' . $start . '" data-countdown-ts="' . $shift['start'] . '">%c</small>',
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 '<span class="text-success" data-countdown-ts="' . $nextShift['start'] . '">'
|
||||
return '<span class="text-success" title="' . $start . '" data-countdown-ts="' . $nextShift['start'] . '">'
|
||||
. __('Next shift %c')
|
||||
. '</span>';
|
||||
}
|
||||
return '<span class="text-warning" data-countdown-ts="' . $nextShift['start'] . '">'
|
||||
return '<span class="text-warning" title="' . $start . '" data-countdown-ts="' . $nextShift['start'] . '">'
|
||||
. __('Next shift %c')
|
||||
. '</span>';
|
||||
}
|
||||
$halfway = ($nextShift['start'] + $nextShift['end']) / 2;
|
||||
|
||||
if (time() < $halfway) {
|
||||
return '<span class="text-danger" data-countdown-ts="' . $nextShift['start'] . '">'
|
||||
return '<span class="text-danger" title="' . $start . '" data-countdown-ts="' . $nextShift['start'] . '">'
|
||||
. __('Shift started %c')
|
||||
. '</span>';
|
||||
}
|
||||
|
||||
return '<span class="text-danger" data-countdown-ts="' . $nextShift['end'] . '">'
|
||||
$end = Carbon::createFromTimestamp($nextShift['end'])->format(__('Y-m-d H:i'));
|
||||
return '<span class="text-danger" title="' . $end . '" data-countdown-ts="' . $nextShift['end'] . '">'
|
||||
. __('Shift ends %c')
|
||||
. '</span>';
|
||||
}
|
||||
|
@ -224,7 +227,9 @@ function User_last_shift_render($user)
|
|||
}
|
||||
|
||||
$lastShift = array_shift($last_shifts);
|
||||
return '<span data-countdown-ts="' . $lastShift['end'] . '">'
|
||||
$end = Carbon::createFromTimestamp($lastShift['end'])->format(__('Y-m-d H:i'));
|
||||
|
||||
return '<span title="' . $end . '" data-countdown-ts="' . $lastShift['end'] . '">'
|
||||
. __('Shift ended %c')
|
||||
. '</span>';
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -322,20 +322,20 @@
|
|||
<div class="col">
|
||||
<h4>Countdowns</h4>
|
||||
<ul>
|
||||
<li data-countdown-ts="{{ timestamp30s }}">30s: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp30m }}">30m: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp59m }}">59m: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp1h }}">1h: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp1h30m }}">1h 30m: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp1h31m }}">1h 31m: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp2h }}">2h: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp2d }}">2d: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp3m }}">3m: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp22y }}">22y: %c</li>
|
||||
<li title="{{ timestamp30s.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp30s.getTimestamp() }}">30s: %c</li>
|
||||
<li title="{{ timestamp30m.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp30m.getTimestamp() }}">30m: %c</li>
|
||||
<li title="{{ timestamp59m.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp59m.getTimestamp() }}">59m: %c</li>
|
||||
<li title="{{ timestamp1h.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp1h.getTimestamp() }}">1h: %c</li>
|
||||
<li title="{{ timestamp1h30m.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp1h30m.getTimestamp() }}">1h 30m: %c</li>
|
||||
<li title="{{ timestamp1h31m.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp1h31m.getTimestamp() }}">1h 31m: %c</li>
|
||||
<li title="{{ timestamp2h.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp2h.getTimestamp() }}">2h: %c</li>
|
||||
<li title="{{ timestamp2d.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp2d.getTimestamp() }}">2d: %c</li>
|
||||
<li title="{{ timestamp3m.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp3m.getTimestamp() }}">3m: %c</li>
|
||||
<li title="{{ timestamp22y.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp22y.getTimestamp() }}">22y: %c</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li data-countdown-ts="{{ timestamp30mago }}">30m ago: %c</li>
|
||||
<li data-countdown-ts="{{ timestamp45mago }}">45m ago: %c</li>
|
||||
<li title="{{ timestamp30mago.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp30mago.getTimestamp() }}">30m ago: %c</li>
|
||||
<li title="{{ timestamp45mago.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ timestamp45mago.getTimestamp() }}">45m ago: %c</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{% if date > date() %}
|
||||
<div class="col-sm-3 text-center d-none d-sm-block">
|
||||
<h4>{{ name }}</h4>
|
||||
<div class="h2 text-body" data-countdown-ts="{{ date.getTimestamp }}">%c</div>
|
||||
<div class="h2 text-body" title="{{ date.format(__('Y-m-d H:i')) }}" data-countdown-ts="{{ date.getTimestamp() }}">%c</div>
|
||||
<small>{{ date.format(__('Y-m-d')) }}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue