From ac8fee351197bc9774b048a8c71faef7a3cf7395 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 26 Apr 2024 16:18:33 +0200 Subject: [PATCH] BaseGrid: Split up entry placement and assembly Allows to add a simpler implementation for fixed layouts. --- .../Widget/Calendar/BaseGrid.php | 138 ++++++++++++------ 1 file changed, 91 insertions(+), 47 deletions(-) diff --git a/library/Notifications/Widget/Calendar/BaseGrid.php b/library/Notifications/Widget/Calendar/BaseGrid.php index 1792bd0b9..d5532ff96 100644 --- a/library/Notifications/Widget/Calendar/BaseGrid.php +++ b/library/Notifications/Widget/Calendar/BaseGrid.php @@ -7,6 +7,7 @@ use DateInterval; use DateTime; use DateTimeInterface; +use Generator; use ipl\Html\Attributes; use ipl\Html\BaseHtmlElement; use ipl\Html\HtmlElement; @@ -19,23 +20,32 @@ use Traversable; /** - * @phpstan-type ContinuationType self::ACROSS_GRID | self::FROM_PREV_GRID | self::TO_NEXT_GRID | self::ACROSS_EDGES + * @phpstan-type GridArea array{0: int, 1: int, 2: int, 3: int} + * @phpstan-type GridContinuationType self::FROM_PREV_GRID | self::TO_NEXT_GRID | self::ACROSS_GRID + * @phpstan-type EdgeContinuationType self::ACROSS_LEFT_EDGE | self::ACROSS_RIGHT_EDGE | self::ACROSS_BOTH_EDGES + * @phpstan-type ContinuationType GridContinuationType | EdgeContinuationType */ abstract class BaseGrid extends BaseHtmlElement { use Translation; - /** @var string Continuation type of the entry row continuing from the previous grid */ - public const FROM_PREV_GRID = 'from-prev-grid'; + /** @var string Continuation of an entry that started on the previous grid */ + protected const FROM_PREV_GRID = 'from-prev-grid'; - /** @var string Continuation type of the entry row continuing to the next grid */ - public const TO_NEXT_GRID = 'to-next-grid'; + /** @var string Continuation of an entry that continues on the next grid */ + protected const TO_NEXT_GRID = 'to-next-grid'; - /** @var string Continuation type of the entry row continuing from the previous grid to the next grid */ - public const ACROSS_GRID = 'across-grid'; + /** @var string Continuation of an entry that started on the previous grid and continues on the next */ + protected const ACROSS_GRID = 'across-grid'; - /** @var string Continuation type of the entry row continuing across edges of the grid */ - public const ACROSS_EDGES = 'across-edges'; + /** @var string Continuation of an entry that started on a previous grid row */ + protected const ACROSS_LEFT_EDGE = 'across-left-edge'; + + /** @var string Continuation of an entry that continues on the next grid row */ + protected const ACROSS_RIGHT_EDGE = 'across-right-edge'; + + /** @var string Continuation of an entry that started on a previous grid row and continues on the next */ + protected const ACROSS_BOTH_EDGES = 'across-both-edges'; /** @var int Return this in {@see getSectionsPerStep} to signal an infinite number of sections */ protected const INFINITE = 0; @@ -108,6 +118,16 @@ abstract protected function calculateGridEnd(): DateTime; abstract protected function getNoOfVisuallyConnectedHours(): int; + /** + * Translate the given grid area positions suitable for the current grid + * + * @param int $rowStart + * @param int $rowEnd + * @param int $colStart + * @param int $colEnd + * + * @return GridArea + */ protected function getGridArea(int $rowStart, int $rowEnd, int $colStart, int $colEnd): array { return [$rowStart, $colStart, $rowEnd, $colEnd]; @@ -181,7 +201,17 @@ public function getExtraEntryCount(DateTime $date): int return $this->extraEntriesCount[$date->format('Y-m-d')] ?? 0; } - protected function assembleGridOverlay(BaseHtmlElement $overlay): void + /** + * Yield the entries to show on the grid and place them using a flowing layout + * + * Entry positions are automatically calculated and can span multiple rows. + * Collisions are prevented and the grid can have a limited number of sections. + * + * @param Traversable $entries + * + * @return Generator + */ + final protected function yieldFlowingEntries(Traversable $entries): Generator { $maxRowSpan = $this->getMaximumRowSpan(); $sectionsPerStep = $this->getSectionsPerStep(); @@ -210,7 +240,7 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void $cellOccupiers = []; /** @var SplObjectStorage $occupiedCells */ $occupiedCells = new SplObjectStorage(); - foreach ($this->provider->getEntries() as $entry) { + foreach ($entries as $entry) { $actualStart = $this->roundToNearestThirtyMinute($entry->getStart()); if ($actualStart < $gridStartsAt) { $entryStartPos = 0; @@ -318,52 +348,28 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void $colEnd + 2 ); - $entryClass = 'area-' . implode('-', $gridArea); - $lastRow = $remainingRows === 1; - - if ($lastRow) { + $isLastRow = $remainingRows === 1; + if ($isLastRow) { $toNextGrid = $gridEndsAt < $entry->getEnd(); } $backward = $continuationType || $fromPrevGrid; - $forward = ! $lastRow || $toNextGrid; - $gradientClass = null; - if ($forward && $backward) { - $gradientClass = 'two-way-gradient'; + $forward = ! $isLastRow || $toNextGrid; + if ($backward && $forward) { + $continuationType = self::ACROSS_BOTH_EDGES; } elseif ($backward) { - $gradientClass = 'opening-gradient'; + $continuationType = self::ACROSS_LEFT_EDGE; } elseif ($forward) { - $gradientClass = 'ending-gradient'; - } - - $entryHtml = new HtmlElement( - 'div', - Attributes::create([ - 'class' => ['entry', $gradientClass, $entryClass], - 'data-entry-id' => $entry->getId(), - 'data-row-start' => $gridArea[0], - 'data-col-start' => $gridArea[1], - 'data-row-end' => $gridArea[2], - 'data-col-end' => $gridArea[3] - ]) - ); - - $this->style->addFor($entryHtml, [ - '--entry-bg' => $this->getEntryColor($entry, 10), - 'grid-area' => sprintf('~"%d / %d / %d / %d"', ...$gridArea), - 'border-color' => $this->getEntryColor($entry, 50) - ]); - - if ($fromPrevGrid) { - $continuationType = $toNextGrid ? self::ACROSS_GRID : self::FROM_PREV_GRID; + $continuationType = self::ACROSS_RIGHT_EDGE; + } elseif ($fromPrevGrid && $toNextGrid) { + $continuationType = self::ACROSS_GRID; + } elseif ($fromPrevGrid) { + $continuationType = self::FROM_PREV_GRID; } elseif ($toNextGrid) { $continuationType = self::TO_NEXT_GRID; - } elseif ($forward) { - $continuationType = self::ACROSS_EDGES; } - $this->assembleEntry($entryHtml, $entry, $continuationType); - $overlay->addHtml($entryHtml); + yield [$gridArea, $continuationType] => $entry; $fromPrevGrid = false; $remainingRows -= 1; @@ -384,6 +390,44 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void } } + protected function assembleGridOverlay(BaseHtmlElement $overlay): void + { + foreach ($this->yieldFlowingEntries($this->provider->getEntries()) as $data => $entry) { + [$gridArea, $continuationType] = $data; + + $gradientClass = null; + if ($continuationType === self::ACROSS_GRID || $continuationType === self::ACROSS_BOTH_EDGES) { + $gradientClass = 'two-way-gradient'; + } elseif ($continuationType === self::FROM_PREV_GRID || $continuationType === self::ACROSS_LEFT_EDGE) { + $gradientClass = 'opening-gradient'; + } elseif ($continuationType === self::TO_NEXT_GRID || $continuationType === self::ACROSS_RIGHT_EDGE) { + $gradientClass = 'ending-gradient'; + } + + $entryHtml = new HtmlElement( + 'div', + Attributes::create([ + 'class' => ['entry', $gradientClass, 'area-' . implode('-', $gridArea)], + 'data-entry-id' => $entry->getId(), + 'data-row-start' => $gridArea[0], + 'data-col-start' => $gridArea[1], + 'data-row-end' => $gridArea[2], + 'data-col-end' => $gridArea[3] + ]) + ); + + $this->style->addFor($entryHtml, [ + '--entry-bg' => $this->getEntryColor($entry, 10), + 'grid-area' => sprintf('~"%d / %d / %d / %d"', ...$gridArea), + 'border-color' => $this->getEntryColor($entry, 50) + ]); + + $this->assembleEntry($entryHtml, $entry, $continuationType); + + $overlay->addHtml($entryHtml); + } + } + /** * Assemble the entry in the grid *