diff --git a/lib/Controller/ViewController.php b/lib/Controller/ViewController.php index b5653f1e22..8b095a834a 100644 --- a/lib/Controller/ViewController.php +++ b/lib/Controller/ViewController.php @@ -178,7 +178,7 @@ public function getCalendarDotSvg(string $color = "#0082c9"): FileDisplayRespons if (preg_match('/^([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) { $validColor = '#' . $color; } - $svg = ''; + $svg = ''; $folderName = implode('_', [ 'calendar', $this->userId @@ -188,9 +188,11 @@ public function getCalendarDotSvg(string $color = "#0082c9"): FileDisplayRespons } catch (NotFoundException $e) { $folder = $this->appData->newFolder($folderName); } - $file = $folder->newFile($color . '.svg', $svg); + $filename = $color . '.svg'; + $file = $folder->fileExists($filename) ? $folder->getFile($filename) : $folder->newFile($filename, $svg); $response = new FileDisplayResponse($file); $response->cacheFor(24 * 3600); // 1 day + $response->setHeaders(['Content-Type' => 'image/svg+xml']); return $response; } } diff --git a/lib/Dashboard/CalendarWidget.php b/lib/Dashboard/CalendarWidget.php index 662cf24f8a..ab71f8c13f 100644 --- a/lib/Dashboard/CalendarWidget.php +++ b/lib/Dashboard/CalendarWidget.php @@ -30,6 +30,7 @@ use DateTimeImmutable; use OCA\Calendar\AppInfo\Application; use OCA\Calendar\Service\JSDataService; +use OCA\DAV\CalDAV\CalendarImpl; use OCP\AppFramework\Services\IInitialState; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Calendar\IManager; @@ -40,10 +41,17 @@ use OCP\Dashboard\Model\WidgetButton; use OCP\Dashboard\Model\WidgetItem; use OCP\Dashboard\Model\WidgetOptions; +use OCP\IConfig; use OCP\IDateTimeFormatter; use OCP\IL10N; use OCP\IURLGenerator; +use OCP\IUserManager; use OCP\Util; +use Sabre\VObject\Component\VEvent; +use Sabre\VObject\Component\VTimeZone; +use Sabre\VObject\Parameter; +use Sabre\VObject\Property\VCard\Date; +use Sabre\Xml\Reader; class CalendarWidget implements IAPIWidget, IButtonWidget, IIconWidget, IOptionWidget { private IL10N $l10n; @@ -53,6 +61,7 @@ class CalendarWidget implements IAPIWidget, IButtonWidget, IIconWidget, IOptionW private IURLGenerator $urlGenerator; private IManager $calendarManager; private ITimeFactory $timeFactory; + private IConfig $config; /** * CalendarWidget constructor. @@ -70,7 +79,8 @@ public function __construct(IL10N $l10n, IDateTimeFormatter $dateTimeFormatter, IURLGenerator $urlGenerator, IManager $calendarManager, - ITimeFactory $timeFactory) { + ITimeFactory $timeFactory, + IConfig $config) { $this->l10n = $l10n; $this->initialStateService = $initialStateService; $this->dataService = $dataService; @@ -78,6 +88,7 @@ public function __construct(IL10N $l10n, $this->urlGenerator = $urlGenerator; $this->calendarManager = $calendarManager; $this->timeFactory = $timeFactory; + $this->config = $config; } /** @@ -143,34 +154,96 @@ public function load(): void { * @param int $limit Max 14 items is the default */ public function getItems(string $userId, ?string $since = null, int $limit = 7): array { + // This is hw JS does it: + // const start = dateFactory() + // const end = dateFactory() + // end.setDate(end.getDate() + 14) + // const startOfToday = moment(start).startOf('day').toDate() + // get all vevents in this time range + // if "show tasks" is enabled, get all todos in the time range + // sort events by time + // filter events by COMPLETED and CANCELLED + // filter out all events that are before the start of the day: + // decorate the items with url / task url, colour, etc + $calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $userId); $count = count($calendars); if ($count === 0) { return []; } - $dateTime = (new DateTimeImmutable())->setTimestamp($this->timeFactory->getTime()); - $inTwoWeeks = $dateTime->add(new DateInterval('P14D')); - $options = [ - 'timerange' => [ - 'start' => $dateTime, - 'end' => $inTwoWeeks, - ] - ]; + $widgetItems = []; foreach ($calendars as $calendar) { - $searchResult = $calendar->search('', [], $options, $limit); - foreach ($searchResult as $calendarEvent) { - /** @var DateTimeImmutable $startDate */ - $startDate = $calendarEvent['objects'][0]['DTSTART'][0]; - $widget = new WidgetItem( - $calendarEvent['objects'][0]['SUMMARY'][0] ?? 'New Event', - $this->dateTimeFormatter->formatTimeSpan(DateTime::createFromImmutable($startDate)), - $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('calendar.view.index', ['objectId' => $calendarEvent['uid']])), - $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('calendar.view.getCalendarDotSvg', ['color' => $calendar->getDisplayColor() ?? '#0082c9'])), // default NC blue fallback - (string) $startDate->getTimestamp(), - ); - $widgetItems[] = $widget; + $timezone = null; + if($calendar instanceof CalendarImpl) { + $tz = $calendar->getCalendarTimezoneString() ?? 'UTC'; + $timezone = new \DateTimeZone($tz); + } + // make sure to include all day events + $startTimeWithTimezoneMidnight = $this->timeFactory->getDateTime('today', $timezone); + $startTimeWithTimezoneNow = $this->timeFactory->getDateTime('now', $timezone); + $endDate = clone $startTimeWithTimezoneMidnight; + $endDate->modify('+15 days'); + $options = [ + 'timerange' => [ + 'start' => $startTimeWithTimezoneMidnight, + 'end' => $endDate, + ], + 'types' => [ + 'VEVENT' + ], + 'sort_asc' => [ + 'firstoccurence' + ] + ]; + if($this->config->getUserValue($userId, Application::APP_ID, 'showTasks') === 'yes') { + $options['types'][] = 'VTODO'; } + $searchResults = $calendar->search('', [], $options, $limit); + foreach ($searchResults as $calendarEvent) { + $dtstart = DateTime::createFromImmutable($calendarEvent['objects'][0]['DTSTART'][0]); + if($calendarEvent['type'] === 'VEVENT') { + if($calendarEvent['objects'][0]['STATUS'][0] === 'CANCELLED') { + continue; + } + $timestring = $this->createVeventString($calendarEvent); + if($timestring === null) { + continue; + } + $widgetItems[] = new WidgetItem( + $calendarEvent['objects'][0]['SUMMARY'][0] ?? 'Untitled Event', + $timestring, + $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('calendar.view.index', ['objectId' => $calendarEvent['uid']])), + $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('calendar.view.getCalendarDotSvg', ['color' => $calendar->getDisplayColor() ?? '#0082c9'])), // default NC blue fallback + (string) $dtstart->getTimestamp(), + ); + } + } +// if($this->config->getUserValue($userId, Application::APP_ID, 'showTasks') === 'yes') { +// $vTodoOptions = [ +// 'types' => [ +// 'VTODO' +// ], +// 'sort_desc' => [ +// 'id' +// ] +// ]; +// $vTodoSearchResults = $calendar->search('', [], $vTodoOptions, $limit); +// foreach($vTodoSearchResults as $vTodo) { +// if($vTodo['objects'][0]['STATUS'][0] === 'COMPLETED') { +// continue; +// } +// $timestring = $this->createVTodoString($vTodo); +// $widget = new WidgetItem( +// $vTodo['objects'][0]['SUMMARY'][0] ?? 'Untitled Task', +// $timestring, +// $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('calendar.view.index', ['objectId' => $calendarEvent['uid']])), +// $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('calendar.view.getCalendarDotSvg', ['color' => $calendar->getDisplayColor() ?? '#0082c9'])), // default NC blue fallback +// (string) $dtstart->getTimestamp(), +// ); +// $widgetItems[] = $widget; +// } +// } } return $widgetItems; } @@ -196,4 +269,32 @@ public function getWidgetButtons(string $userId): array { public function getWidgetOptions(): WidgetOptions { return new WidgetOptions(true); } + + private function createVeventString(array $calendarEvent) { + $dtstart = DateTime::createFromImmutable($calendarEvent['objects'][0]['DTSTART'][0]); + if(isset($calendarEvent['objects'][0]['STATUS']) && $calendarEvent['objects'][0]['STATUS'][0] === 'CANCELLED') { + return null; + } + if (isset($calendarEvent['objects'][0]['DTEND'])) { + /** @var Property\ICalendar\DateTime $dtend */ + $dtend = DateTime::createFromImmutable($calendarEvent['objects'][0]['DTEND'][0]); + } elseif(isset($calendarEvent['objects'][0]['DURATION'])) { + $dtend = clone $dtstart; + $dtend = $dtend->add(new DateInterval($calendarEvent['objects'][0]['DURATION'][0])); + } else { + $dtend = clone $dtstart; + } + + // End is in the past, skipping + if($dtend->getTimestamp() < $this->timeFactory->getTime()) { + return null; + } + + // all day (and longer) events + if($dtstart->diff($dtend)->days >= 1) { + return $this->dateTimeFormatter->formatDate($dtstart); + } + + return $this->dateTimeFormatter->formatDateTime($dtstart, 'short') . ' - ' . $this->dateTimeFormatter->formatTime($dtend, 'short'); + } } diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index b31c6da328..d79a07fc0d 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -34,16 +34,13 @@ + :main-text="item.title ?? ''" + :sub-text="item.subtitle" + :target-url="item.link"> @@ -64,20 +61,18 @@