From d0b737ad9897ffc0758547e53d7d2505f5e5a19d Mon Sep 17 00:00:00 2001 From: raviks789 <33730024+raviks789@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:07:44 +0200 Subject: [PATCH] Improve visual representation of the entries Entries starting the previous day or ending the next day must be distinguishable. --- .../Widget/Calendar/BaseGrid.php | 202 ++++++++++++++++-- public/css/calendar.less | 10 + 2 files changed, 190 insertions(+), 22 deletions(-) diff --git a/library/Notifications/Widget/Calendar/BaseGrid.php b/library/Notifications/Widget/Calendar/BaseGrid.php index 6ab86bd7d..044650464 100644 --- a/library/Notifications/Widget/Calendar/BaseGrid.php +++ b/library/Notifications/Widget/Calendar/BaseGrid.php @@ -242,9 +242,13 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void } $this->extraEntriesCount = []; + /** @var Entry $entry */ foreach ($occupiedCells as $entry) { $continuation = false; $rows = $occupiedCells->getInfo(); + $entryStartDate = clone $entry->getStart(); + $entryEndDate = clone $entry->getEnd(); + foreach ($rows as $row => $hours) { list($rowStart, $rowSpan) = $rowPlacements[spl_object_id($entry)][$row]; $rowEnd = $rowStart + $rowSpan; @@ -256,11 +260,11 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void // Calculate number of entries that are not displayed in the grid for each date if ($rowStart > $row + $sectionsPerStep) { - $stepRow = $rowStart - $this->getSectionsPerStep(); - $startOffset = $this->getStepOffset($stepRow, $gridArea[1] - 1); - $endOffset = $this->getStepOffset($stepRow, $gridArea[3] - 2); + $startOffset = $this->getStepOffset($rowStart - $sectionsPerStep, $colStart - 1); + $endOffset = $this->getStepOffset($rowEnd - $sectionsPerStep, $colEnd - 2); $startDate = (clone $this->getGridStart())->add(new DateInterval("P$startOffset" . 'D')); $duration = $endOffset - $startOffset; + for ($i = 0; $i <= $duration; $i++) { $countIdx = $startDate->format('Y-m-d'); if (! isset($this->extraEntriesCount[$countIdx])) { @@ -275,11 +279,64 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void continue; } - $style->add(".$entryClass", [ - 'grid-area' => sprintf('~"%d / %d / %d / %d"', ...$gridArea), - 'background-color' => $entry->getAttendee()->getColor() . dechex((int) (256 * 0.1)), - 'border-color' => $entry->getAttendee()->getColor() . dechex((int) (256 * 0.5)) - ]); + $startOffset = $this->getStepOffset($rowStart - 1, $colStart - 1); + $endOffset = $this->getStepOffset($rowEnd - 2, $colEnd - 2); + + $startDate = clone $this->getGridStart(); + $startDate->add(new DateInterval("P$startOffset" . 'D')); + + $noOfVisuallyConnectedDays = intval($this->getNoOfVisuallyConnectedHours() / 24); + if ($noOfVisuallyConnectedDays === 7) { + $rowStartDate = clone $gridStartsAt; + $startDaysOffset = $startOffset - $startOffset % 7; + $rowStartDate = $rowStartDate->add(new DateInterval('P' . $startDaysOffset . 'D')); + + $endDaysOffset = $endOffset + (6 - $endOffset % 7); + $rowEndDate = clone $gridStartsAt; + $rowEndDate = $rowEndDate->add(new DateInterval('P' . $endDaysOffset . 'D')); + $gradient = 'horizontal'; + } else { + $rowStartDate = clone $startDate; + $rowEndDate = clone $startDate; + $gradient = 'vertical'; + } + + $rowEndDate->setTime(23, 59); + + $entryColor = $entry->getAttendee()->getColor(); + if ($entryStartDate < $rowStartDate && $entryEndDate > $rowEndDate) { + $gradientDirection = $gradient === 'horizontal' ? 'right' : 'top'; + $oppDirection = $gradientDirection === 'top' ? 'bottom' : 'left'; + $entryAtrr = $this->getEntryHtmlAttr($entryColor, $gradientDirection, $oppDirection); + } elseif ($entryEndDate > $rowEndDate) { + $gradientDirection = $gradient === 'horizontal' ? 'right' : 'bottom'; + $entryAtrr = $this->getEntryHtmlAttr($entryColor, $gradientDirection); + } elseif ($entryStartDate < $rowStartDate) { + $gradientDirection = $gradient === 'horizontal' ? 'left' : 'top'; + $entryAtrr = $this->getEntryHtmlAttr($entryColor, $gradientDirection); + } else { + $entryAtrr = $this->getEntryHtmlAttr($entryColor); + } + + $entryAtrr['grid-area'] = sprintf('~"%d / %d / %d / %d"', ...$gridArea); + + $style->add(".$entryClass", $entryAtrr); + $startText = false; + $endText = false; + if ( + $rowStartDate->format('Y-m-d') === $gridStartsAt->format('Y-m-d') + && $entryStartDate < $gridStartsAt + ) { + $startText = true; + } + + $rowEndDate->add(new DateInterval('P1D')); + if ( + $rowEndDate->format('Y-m-d') === $gridEndsAt->format('Y-m-d') + && $entryEndDate > $gridEndsAt + ) { + $endText = true; + } $entryHtml = new HtmlElement( 'div', @@ -292,7 +349,8 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void 'data-col-end' => $gridArea[3] ]) ); - $this->assembleEntry($entryHtml, $entry, $continuation); + + $this->assembleEntry($entryHtml, $entry, $startText, $endText, $continuation); $overlay->addHtml($entryHtml); $continuation = true; @@ -300,8 +358,74 @@ protected function assembleGridOverlay(BaseHtmlElement $overlay): void } } - protected function assembleEntry(BaseHtmlElement $html, Entry $entry, bool $isContinuation): void - { + protected function getEntryHtmlAttr( + string $entryColor, + ?string $direction = null, + ?string $opposite = null + ): array { + $alpha = dechex((int) (256 * 0.1)); + switch ($direction) { + case 'top': + case 'left': + $style = [ + 'background' => sprintf( + '~"linear-gradient(to %s, %s%s calc(100%% - 1em), transparent)"', + $direction, + $entryColor, + $alpha + ), + 'border-color' => $entryColor . $alpha, + 'border-left' => 'none', + "border-$direction" => 'none', + 'border-radius' => $direction === 'top' ? '0 0 0.25em 0.25em' : '0 0.25em 0.25em 0' + ]; + + if ($opposite !== null) { + $style['background'] = sprintf( + '~"linear-gradient' + . '(to %3$s, transparent, %1$s%2$s 0.5em, %1$s%2$s calc(100%% - 0.5em), transparent)' + . '"', + $entryColor, + $alpha, + $direction + ); + $style["border-$opposite"] = 'none'; + $style['border-radius'] = 0; + } + + break; + case 'bottom': + case 'right': + $style = [ + 'background' => sprintf( + '~"linear-gradient(to %s, %s%s calc(100%% - 1em), transparent)"', + $direction, + $entryColor, + $alpha + ), + 'border-color' => $entryColor . $alpha, + "border-$direction" => 'none', + 'border-radius' => $direction === 'bottom' ? '0.25em 0.25em 0 0' : '0.25em 0 0 0.25em' + ]; + + break; + default: + $style = [ + 'background-color' => $entryColor . $alpha, + 'border-color' => $entryColor . $alpha + ]; + } + + return $style; + } + + protected function assembleEntry( + BaseHtmlElement $html, + Entry $entry, + bool $startText, + bool $endText, + bool $isContinuation + ): void { if (($url = $entry->getUrl()) !== null) { $entryContainer = new Link(null, $url); $html->addHtml($entryContainer); @@ -310,6 +434,37 @@ protected function assembleEntry(BaseHtmlElement $html, Entry $entry, bool $isCo } $title = new HtmlElement('div', Attributes::create(['class' => 'title'])); + $content = new HtmlElement( + 'div', + Attributes::create( + [ + 'class' => 'content' + ] + ) + ); + + if ($startText) { + $content->addHtml( + HtmlElement::create( + 'div', + ['class' => 'starts-at'], + $this->translate(sprintf('starts %s', $entry->getStart()->format('d/m/y'))) + ) + ); + $titleAttr = $this->translate( + 'starts ' + . $entry->getStart()->format('H:i') + . ' | ' . $entry->getAttendee()->getName() + . ': ' . $entry->getDescription() + ); + } else { + $titleAttr = $entry->getStart()->format('H:i') + . ' | ' . $entry->getAttendee()->getName() + . ': ' . $entry->getDescription(); + } + + $content->addAttributes(['title' => $titleAttr]); + if (! $isContinuation) { $title->addHtml(new HtmlElement( 'time', @@ -329,16 +484,7 @@ protected function assembleEntry(BaseHtmlElement $html, Entry $entry, bool $isCo ) ); - $entryContainer->addHtml(new HtmlElement( - 'div', - Attributes::create( - [ - 'class' => 'content', - 'title' => $entry->getStart()->format('H:i') - . ' | ' . $entry->getAttendee()->getName() - . ': ' . $entry->getDescription() - ] - ), + $content->addHtml( $title, new HtmlElement( 'div', @@ -349,7 +495,19 @@ protected function assembleEntry(BaseHtmlElement $html, Entry $entry, bool $isCo Text::create($entry->getDescription()) ) ) - )); + ); + + if ($endText) { + $content->addHtml( + HtmlElement::create( + 'div', + ['class' => 'ends-at'], + $this->translate(sprintf('ends %s', $entry->getEnd()->format('d/m/y H:i'))) + ) + ); + } + + $entryContainer->addHtml($content); } protected function roundToNearestThirtyMinute(DateTime $time): DateTime diff --git a/public/css/calendar.less b/public/css/calendar.less index 7f8f5630a..cb0c2384c 100644 --- a/public/css/calendar.less +++ b/public/css/calendar.less @@ -318,6 +318,16 @@ text-decoration: none; } + .starts-at, + .ends-at { + z-index: -1; + mix-blend-mode: multiply; + color: @text-color-light; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .extra-count:hover { text-decoration: underline; }