diff --git a/appinfo/routes.php b/appinfo/routes.php index 6330381cf..6a1482cd8 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -136,7 +136,6 @@ ['name' => 'comments_api#delete', 'url' => '/api/v1.0/cards/{cardId}/comments/{commentId}', 'verb' => 'DELETE'], // dashboard - ['name' => 'overview_api#findAllWithDue', 'url' => '/api/v1.0/overview/due', 'verb' => 'GET'], - ['name' => 'overview_api#findAssignedCards', 'url' => '/api/v1.0/overview/assigned', 'verb' => 'GET'], + ['name' => 'overview_api#upcomingCards', 'url' => '/api/v1.0/overview/upcoming', 'verb' => 'GET'], ] ]; diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 74aa858f4..d4fd3a7a9 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -29,6 +29,7 @@ use OCA\Deck\Capabilities; use OCA\Deck\Collaboration\Resources\ResourceProvider; use OCA\Deck\Collaboration\Resources\ResourceProviderCard; +use OCA\Deck\Dashboard\DeckWidget; use OCA\Deck\Db\Acl; use OCA\Deck\Db\AclMapper; use OCA\Deck\Db\AssignedUsersMapper; @@ -43,6 +44,7 @@ use OCP\Collaboration\Resources\IManager; use OCP\Collaboration\Resources\IProviderManager; use OCP\Comments\CommentsEntityEvent; +use OCP\Dashboard\RegisterWidgetEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\FullTextSearch\IFullTextSearchManager; @@ -85,6 +87,15 @@ public function __construct(array $urlParams = []) { $container->registerService('database4ByteSupport', static function () use ($server) { return $server->getDatabaseConnection()->supports4ByteText(); }); + + $version = OC_Util::getVersion()[0]; + if ($version >= 20) { + /** @var IEventDispatcher $dispatcher */ + $dispatcher = $container->getServer()->query(IEventDispatcher::class); + $dispatcher->addListener(RegisterWidgetEvent::class, function (RegisterWidgetEvent $event) use ($container) { + $event->registerWidget(DeckWidget::class); + }); + } } public function register(): void { diff --git a/lib/Controller/OverviewApiController.php b/lib/Controller/OverviewApiController.php index 7635dc6c6..9d082fd59 100644 --- a/lib/Controller/OverviewApiController.php +++ b/lib/Controller/OverviewApiController.php @@ -48,14 +48,7 @@ public function __construct($appName, IRequest $request, OverviewService $dashbo /** * @NoAdminRequired */ - public function findAllWithDue(): DataResponse { - return new DataResponse($this->dashboardService->findAllWithDue($this->userId)); - } - - /** - * @NoAdminRequired - */ - public function findAssignedCards(): DataResponse { - return new DataResponse($this->dashboardService->findAssignedCards($this->userId)); + public function upcomingCards(): DataResponse { + return new DataResponse($this->dashboardService->findUpcomingCards($this->userId)); } } diff --git a/lib/Dashboard/DeckWidget.php b/lib/Dashboard/DeckWidget.php new file mode 100644 index 000000000..4035a5d6e --- /dev/null +++ b/lib/Dashboard/DeckWidget.php @@ -0,0 +1,84 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Deck\Dashboard; + +use OCP\Dashboard\IWidget; +use OCP\IL10N; + +class DeckWidget implements IWidget { + + /** + * @var IL10N + */ + private $l10n; + + public function __construct(IL10N $l10n) { + $this->l10n = $l10n; + } + + /** + * @inheritDoc + */ + public function getId(): string { + return 'deck'; + } + + /** + * @inheritDoc + */ + public function getTitle(): string { + return $this->l10n->t('Upcoming cards'); + } + + /** + * @inheritDoc + */ + public function getOrder(): int { + return 20; + } + + /** + * @inheritDoc + */ + public function getIconClass(): string { + return 'icon-deck'; + } + + /** + * @inheritDoc + */ + public function getUrl(): ?string { + return null; + } + + /** + * @inheritDoc + */ + public function load(): void { + \OCP\Util::addScript('deck', 'dashboard'); + } +} diff --git a/lib/Service/OverviewService.php b/lib/Service/OverviewService.php index fc0168c82..8099108e6 100644 --- a/lib/Service/OverviewService.php +++ b/lib/Service/OverviewService.php @@ -107,23 +107,34 @@ public function findAllWithDue(string $userId): array { return $allDueCards; } - public function findAssignedCards(string $userId): array { + public function findUpcomingCards(string $userId): array { $userBoards = $this->findAllBoardsFromUser($userId); - $allAssignedCards = []; + $findCards = []; foreach ($userBoards as $userBoard) { $service = $this; - $allAssignedCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) { - $service->enrich($card, $userId); - $cardData = $card->jsonSerialize(); - $cardData['boardId'] = $userBoard->getId(); - return $cardData; - }, $this->cardMapper->findAssignedCards($userBoard->getId(), $userId)); + + if (count($userBoard->getAcl()) === 0) { + // get cards with due date + $findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) { + $service->enrich($card, $userId); + $cardData = $card->jsonSerialize(); + $cardData['boardId'] = $userBoard->getId(); + return $cardData; + }, $this->cardMapper->findAllWithDue($userBoard->getId())); + } else { + // get assigned cards + $findCards[] = array_map(static function ($card) use ($service, $userBoard, $userId) { + $service->enrich($card, $userId); + $cardData = $card->jsonSerialize(); + $cardData['boardId'] = $userBoard->getId(); + return $cardData; + }, $this->cardMapper->findAssignedCards($userBoard->getId(), $userId)); + } } - return $allAssignedCards; + return $findCards; } // FIXME: This is duplicate code with the board service - private function findAllBoardsFromUser(string $userId): array { $userInfo = $this->getBoardPrerequisites($userId); $userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null); diff --git a/package-lock.json b/package-lock.json index 7df8930a1..64132d461 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3851,6 +3851,23 @@ } } }, + "@nextcloud/vue-dashboard": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@nextcloud/vue-dashboard/-/vue-dashboard-0.1.8.tgz", + "integrity": "sha512-OGr1oK/WF9+bYHK8dE8Vjwh3IDNamN+9MSti1VO7zuUSm5A9EGCzAghR7zzCG4O43rAJEDcvnQwsYIiA6g4Yrw==", + "requires": { + "@nextcloud/vue": "^2.3.0", + "core-js": "^3.6.4", + "vue": "^2.6.11" + }, + "dependencies": { + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + } + } + }, "@nextcloud/webpack-vue-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@nextcloud/webpack-vue-config/-/webpack-vue-config-1.1.0.tgz", diff --git a/package.json b/package.json index c3381d651..d1a62cc47 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@nextcloud/moment": "^1.1.1", "@nextcloud/router": "^1.1.0", "@nextcloud/vue": "^2.6.0", + "@nextcloud/vue-dashboard": "^0.1.8", "blueimp-md5": "^2.17.0", "dompurify": "^2.0.12", "lodash": "^4.17.20", diff --git a/src/components/AttachmentDragAndDrop.vue b/src/components/AttachmentDragAndDrop.vue index 4d24cfe97..68ceeb893 100644 --- a/src/components/AttachmentDragAndDrop.vue +++ b/src/components/AttachmentDragAndDrop.vue @@ -68,7 +68,13 @@ import { Modal } from '@nextcloud/vue' import attachmentUpload from '../mixins/attachmentUpload' import { loadState } from '@nextcloud/initial-state' -const maxUploadSizeState = loadState('deck', 'maxUploadSize') + +let maxUploadSizeState +try { + maxUploadSizeState = loadState('deck', 'maxUploadSize') +} catch (e) { + maxUploadSizeState = -1 +} export default { name: 'AttachmentDragAndDrop', diff --git a/src/components/cards/CardItem.vue b/src/components/cards/CardItem.vue index 5d2a47fda..288808f2a 100644 --- a/src/components/cards/CardItem.vue +++ b/src/components/cards/CardItem.vue @@ -47,17 +47,11 @@ -
- -
- {{ relativeDate }} -
-
-
+ - import ClickOutside from 'vue-click-outside' import { mapState, mapGetters } from 'vuex' -import moment from 'moment' import CardBadges from './CardBadges' import Color from '../../mixins/color' import labelStyle from '../../mixins/labelStyle' import AttachmentDragAndDrop from '../AttachmentDragAndDrop' import CardMenu from './CardMenu' +import DueDate from './badges/DueDate' export default { name: 'CardItem', - components: { CardBadges, AttachmentDragAndDrop, CardMenu }, + components: { CardBadges, AttachmentDragAndDrop, CardMenu, DueDate }, directives: { ClickOutside, }, @@ -128,29 +122,7 @@ export default { currentCard() { return this.card && this.$route && this.$route.params.cardId === this.card.id }, - relativeDate() { - const diff = moment(this.$root.time).diff(this.card.duedate, 'seconds') - if (diff >= 0 && diff < 45) { - return t('core', 'seconds ago') - } - return moment(this.card.duedate).fromNow() - }, - dueIcon() { - const days = Math.floor(moment(this.card.duedate).diff(this.$root.time, 'seconds') / 60 / 60 / 24) - if (days < 0) { - return 'icon-calendar due icon overdue' - } - if (days === 0) { - return 'icon-calendar-dark due icon now' - } - if (days === 1) { - return 'icon-calendar-dark due icon next' - } - return 'icon-calendar-dark due icon' - }, - dueDateTooltip() { - return moment(this.card.duedate).format('LLLL') - }, + }, methods: { openCard() { @@ -178,10 +150,6 @@ export default { @import './../../css/animations'; @import './../../css/variables'; - body.dark .card { - border: 1px solid var(--color-border); - } - .card:hover { box-shadow: 0 0 5px 1px var(--color-box-shadow); } @@ -226,42 +194,7 @@ export default { } } - .labels { - flex-grow: 1; - flex-shrink: 1; - min-width: 0; - display: flex; - flex-direction: row; - margin-left: $card-padding; - margin-right: $card-padding; - margin-top: -5px; - - li { - flex-grow: 0; - flex-shrink: 1; - display: flex; - flex-direction: row; - overflow: hidden; - padding: 0px 5px; - border-radius: 15px; - font-size: 85%; - margin-right: 3px; - margin-bottom: 3px; - - &:hover { - overflow: unset; - } - - span { - flex-grow: 0; - flex-shrink: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - } + @import './../../css/labels'; .card-controls { display: flex; @@ -282,49 +215,6 @@ export default { align-items: flex-start; } - .icon.due { - background-position: 4px center; - border-radius: 3px; - margin-top: 9px; - margin-bottom: 9px; - padding: 3px 4px; - padding-right: 0; - font-size: 90%; - display: flex; - align-items: center; - opacity: .5; - flex-shrink: 1; - z-index: 2; - - .icon { - background-size: contain; - } - - &.overdue { - background-color: var(--color-error); - color: var(--color-primary-text); - opacity: .7; - padding: 3px 4px; - } - &.now { - background-color: var(--color-warning); - opacity: .7; - padding: 3px 4px; - } - &.next { - background-color: var(--color-background-dark); - opacity: .7; - padding: 3px 4px; - } - - span { - margin-left: 20px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - } - .compact { min-height: 44px; diff --git a/src/components/cards/badges/DueDate.vue b/src/components/cards/badges/DueDate.vue new file mode 100644 index 000000000..c2a998d1b --- /dev/null +++ b/src/components/cards/badges/DueDate.vue @@ -0,0 +1,115 @@ + + + + + + + diff --git a/src/components/navigation/AppNavigation.vue b/src/components/navigation/AppNavigation.vue index 194e2a76d..9da7f47c6 100644 --- a/src/components/navigation/AppNavigation.vue +++ b/src/components/navigation/AppNavigation.vue @@ -24,9 +24,9 @@