Skip to content

Commit 6494311

Browse files
Schedule detail: Introduce Timescale
1 parent e72f8d7 commit 6494311

File tree

3 files changed

+154
-1
lines changed

3 files changed

+154
-1
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
/* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Notifications\Widget\TimeGrid;
6+
7+
use DateTime;
8+
use IntlDateFormatter;
9+
use ipl\Html\Attributes;
10+
use ipl\Html\BaseHtmlElement;
11+
use ipl\Html\HtmlElement;
12+
use ipl\Html\Text;
13+
use ipl\I18n\Translation;
14+
use ipl\Web\Style;
15+
use Locale;
16+
17+
/**
18+
* Creates a localized timescale for the TimeGrid
19+
*/
20+
class Timescale extends BaseHtmlElement
21+
{
22+
use Translation;
23+
24+
protected $tag = 'div';
25+
26+
protected $defaultAttributes = ['class' => 'timescale'];
27+
28+
/** @var int The number of days shown */
29+
protected $days;
30+
31+
/** @var Style */
32+
protected $style;
33+
34+
/**
35+
* Create a new Timescale
36+
*
37+
* @param int $days
38+
* @param Style $style
39+
*/
40+
public function __construct(int $days, Style $style)
41+
{
42+
$this->days = $days;
43+
$this->style = $style;
44+
}
45+
46+
public function assemble(): void
47+
{
48+
if ($this->days === 1) {
49+
$timestampPerDay = 12;
50+
} elseif ($this->days <= 7) {
51+
$timestampPerDay = 2;
52+
} else {
53+
$timestampPerDay = 1;
54+
}
55+
56+
$this->style->addFor($this, ['--timestampsPerDay' => $timestampPerDay * 2]); // *2 for .ticks
57+
58+
$dateFormatter = new IntlDateFormatter(
59+
Locale::getDefault(),
60+
IntlDateFormatter::NONE,
61+
IntlDateFormatter::SHORT
62+
);
63+
64+
$timeIntervals = 24 / $timestampPerDay;
65+
66+
$time = new DateTime();
67+
$dayTimestamps = [];
68+
for ($i = 0; $i < $timestampPerDay; $i++) {
69+
// am-pm is separated by non-breaking whitespace
70+
$parts = preg_split('/\s/u', $dateFormatter->format($time->setTime($i * $timeIntervals, 0)));
71+
72+
$stamp = [new HtmlElement('span', null, new Text($parts[0]))];
73+
if (isset($parts[1])) {
74+
$stamp[] = new HtmlElement('span', null, new Text($parts[1]));
75+
}
76+
77+
$dayTimestamps[] = new HtmlElement('span', new Attributes(['class' => 'timestamp']), ...$stamp);
78+
$dayTimestamps[] = new HtmlElement('span', new Attributes(['class' => 'ticks']));
79+
}
80+
81+
$allTimestamps = array_merge(...array_fill(0, $this->days, $dayTimestamps));
82+
// clone is required because $allTimestamps contains references of same object
83+
$allTimestamps[] = (clone $allTimestamps[0])->addAttributes(['class' => 'midnight']); // extra stamp of 12AM
84+
85+
$this->addHtml(...$allTimestamps);
86+
}
87+
}

library/Notifications/Widget/Timeline.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Icinga\Module\Notifications\Widget\TimeGrid\DynamicGrid;
1212
use Icinga\Module\Notifications\Widget\TimeGrid\EntryProvider;
1313
use Icinga\Module\Notifications\Widget\TimeGrid\GridStep;
14+
use Icinga\Module\Notifications\Widget\TimeGrid\Timescale;
1415
use Icinga\Module\Notifications\Widget\Timeline\Entry;
1516
use Icinga\Module\Notifications\Widget\Timeline\MinimalGrid;
1617
use Icinga\Module\Notifications\Widget\Timeline\Rotation;
@@ -316,6 +317,9 @@ protected function assemble()
316317
Text::create($this->translate('Result'))
317318
)
318319
);
320+
321+
$this->getGrid()
322+
->addHtml(new Timescale($this->days, $this->getStyle()));
319323
}
320324

321325
$this->addHtml(

public/css/timeline.less

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@
33
.timeline {
44
display: flex;
55
flex-direction: column;
6+
overflow: hidden;
67

78
.time-grid {
89
--sidebarWidth: 12em;
910
--stepRowHeight: 4em;
1011
--primaryRowHeight: 4em;
12+
--daysHeaderHeight: 3em;
1113
position: relative;
14+
margin-right: 1em; // make midnight timestamp visible
1215

1316
.time-grid-header {
1417
box-sizing: border-box;
1518
position: sticky;
1619
z-index: 1;
1720
top: 0;
21+
height: var(--daysHeaderHeight);
1822
}
1923

2024
.row-title {
@@ -61,14 +65,63 @@
6165
}
6266

6367
&::after {
68+
--primaryRowsWithoutResultRow: ~"calc(var(--primaryRows) - 1)";
6469
content: '';
6570
display: block;
6671
border-top: 1px solid black;
6772
position: absolute;
68-
bottom: var(--stepRowHeight);
73+
top: ~"calc((var(--stepRowHeight) * var(--primaryRowsWithoutResultRow)) + var(--daysHeaderHeight))";
6974
right: 0;
7075
left: 0;
7176
}
77+
78+
.timescale {
79+
display: grid;
80+
grid-template-columns: repeat(~"calc(var(--primaryColumns) * var(--timestampsPerDay))", minmax(0,1fr));
81+
border-left: 1px solid @gray-lighter; // this is required to maintain the grid layout
82+
grid-area: ~"4 / 2 / 4 / 3";
83+
84+
.ticks {
85+
position: relative;
86+
border-right: 1px solid @gray-lighter;
87+
border-left: 1px solid @gray-lighter;
88+
89+
&:after { // overlaps the unnecessary part of border-left
90+
content: '';
91+
position: absolute;
92+
top: 0.25em;
93+
left: -1px; // overlap the border-left
94+
right: 0;
95+
bottom: 0;
96+
background: @body-bg-color;
97+
}
98+
}
99+
100+
.timestamp {
101+
display: flex;
102+
flex-direction: column;
103+
align-items: center;
104+
margin-top: 0.5em;
105+
padding-top: 0.5em;
106+
font-size: .5em;
107+
position: relative;
108+
left: -50%;
109+
line-height: 1;
110+
111+
&.midnight {
112+
left: 50%;
113+
}
114+
115+
> span:last-child {
116+
opacity: 0.5;
117+
}
118+
}
119+
120+
span:nth-last-of-type(2), // last .ticks before .midnight
121+
.midnight {
122+
grid-area: ~"1 / -2 / 1 / -1";
123+
}
124+
}
72125
}
73126
}
74127

@@ -134,6 +187,11 @@
134187
font-size: .75em;
135188
opacity: .8;
136189
}
190+
191+
.timescale .timestamp {
192+
color: @gray-semilight;
193+
background: @body-bg-color;
194+
}
137195
}
138196

139197
.timeline.minimal-layout .empty-notice {
@@ -143,3 +201,7 @@
143201
.days-header .column-title .date {
144202
font-size: .75em;
145203
}
204+
205+
#layout.twocols .schedule .timescale:has(:nth-child(n+62)) { // month view (--timestampsPerDay * --primaryColumns = 62)
206+
display: none;
207+
}

0 commit comments

Comments
 (0)