Skip to content

Commit 53a24ae

Browse files
Timeline: Visualize future rotations
- Introduce class FutureEntry
1 parent 003a5e8 commit 53a24ae

File tree

4 files changed

+119
-11
lines changed

4 files changed

+119
-11
lines changed

library/Notifications/Widget/TimeGrid/BaseGrid.php

+15
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DateInterval;
88
use DateTime;
99
use Generator;
10+
use Icinga\Module\Notifications\Widget\Timeline\FutureEntry;
1011
use ipl\Html\Attributes;
1112
use ipl\Html\BaseHtmlElement;
1213
use ipl\Html\HtmlElement;
@@ -421,6 +422,20 @@ final protected function yieldFixedEntries(Traversable $entries): Generator
421422
}
422423

423424
$rowStart = $position + $rowStartModifier;
425+
426+
if ($entry instanceof FutureEntry) {
427+
$gridArea = $this->getGridArea(
428+
$rowStart,
429+
$rowStart + 1,
430+
1,
431+
$gridBorderAt + 1
432+
);
433+
434+
yield $gridArea => $entry;
435+
436+
continue;
437+
}
438+
424439
if ($rowStart > $lastRow) {
425440
$lastRow = $rowStart;
426441
}

library/Notifications/Widget/Timeline.php

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Icinga\Module\Notifications\Widget\TimeGrid\Timescale;
1515
use Icinga\Module\Notifications\Widget\TimeGrid\Util;
1616
use Icinga\Module\Notifications\Widget\Timeline\Entry;
17+
use Icinga\Module\Notifications\Widget\Timeline\FutureEntry;
1718
use Icinga\Module\Notifications\Widget\Timeline\MinimalGrid;
1819
use Icinga\Module\Notifications\Widget\Timeline\Rotation;
1920
use IntlDateFormatter;
@@ -173,7 +174,9 @@ public function getEntries(): Traversable
173174

174175
$occupiedCells = [];
175176
foreach ($rotations as $rotation) {
177+
$entryFound = false;
176178
foreach ($rotation->fetchTimeperiodEntries($this->start, $this->getGrid()->getGridEnd()) as $entry) {
179+
$entryFound = true;
177180
if (! $this->minimalLayout) {
178181
$entry->setPosition($maxPriority - $rotation->getPriority());
179182

@@ -182,6 +185,10 @@ public function getEntries(): Traversable
182185

183186
$occupiedCells += $getDesiredCells($entry);
184187
}
188+
189+
if (! $entryFound && ! $this->minimalLayout) {
190+
yield (new FutureEntry())->setPosition($maxPriority - $rotation->getPriority());
191+
}
185192
}
186193

187194
$entryToCellsMap = new SplObjectStorage();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Notifications\Widget\Timeline;
6+
7+
use Icinga\Module\Notifications\Widget\TimeGrid\Entry;
8+
use ipl\Html\Attributes;
9+
use ipl\Html\BaseHtmlElement;
10+
use ipl\Html\HtmlElement;
11+
use ipl\Web\Widget\Icon;
12+
13+
/**
14+
* FutureEntry
15+
*
16+
* Visualize a future entry of the rotation
17+
*
18+
* @extends Entry<0>
19+
*/
20+
class FutureEntry extends Entry
21+
{
22+
public function __construct()
23+
{
24+
parent::__construct(0);
25+
26+
$this->setContinuationType(Entry::TO_NEXT_GRID);
27+
}
28+
29+
public function getColor(int $transparency): string
30+
{
31+
// --base-disabled (#d0d3da) -> hsl(222, 12%, 84%) + transparency
32+
return sprintf('~"hsl(222 12%% 84%% / %d%%)"', $transparency);
33+
}
34+
35+
protected function assembleContainer(BaseHtmlElement $container): void
36+
{
37+
$futureBadge = new HtmlElement(
38+
'div',
39+
new Attributes([
40+
'title' => $this->translate('Rotation starts in the future'),
41+
$container->getAttributes()->get('class')
42+
]),
43+
new Icon('angle-right')
44+
);
45+
46+
$container
47+
->setAttribute('class', 'future-entry') // override the default class
48+
->addHtml($futureBadge);
49+
}
50+
}

public/css/timeline.less

+47-11
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,55 @@
4848
}
4949
}
5050

51-
.overlay .entry {
52-
margin-top: 1em;
53-
margin-bottom: 1em;
54-
z-index: 2; // overlap the .clock .time-hand
51+
.overlay {
52+
.entry {
53+
margin-top: 1em;
54+
margin-bottom: 1em;
55+
z-index: 2; // overlap the .clock .time-hand
56+
57+
.title {
58+
height: 100%;
59+
flex-wrap: nowrap;
60+
align-items: baseline;
61+
padding: .15em .5em;
62+
63+
.name {
64+
.text-ellipsis();
65+
}
66+
}
67+
}
5568

56-
.title {
57-
height: 100%;
58-
flex-wrap: nowrap;
59-
align-items: baseline;
60-
padding: .15em .5em;
69+
.future-entry {
70+
display: flex;
71+
justify-content: end;
6172

62-
.name {
63-
.text-ellipsis();
73+
.entry {
74+
display: flex;
75+
align-items: center;
76+
justify-content: center;
77+
78+
position: relative;
79+
padding-left: 6px; // 2px before + 1px border + 2px after + 1px border
80+
81+
width: 3em;
82+
flex-shrink: 0;
83+
84+
&:before,
85+
&:after {
86+
content: '';
87+
display: block;
88+
position: absolute;
89+
border: 1px solid var(--entry-border-color);
90+
border-right: transparent;
91+
height: ~"calc(100% + 2px)"; // border top and bottom
92+
width: 100%;
93+
left: 2px;
94+
.rounded-corners(0.25em);
95+
}
96+
97+
&:after {
98+
left: 5px; // 2px before + 1px border + 2px after
99+
}
64100
}
65101
}
66102
}

0 commit comments

Comments
 (0)