This commit is contained in:
Julius Härtl
2023-10-18 12:44:19 +02:00
committed by GitHub
11 changed files with 517 additions and 55 deletions

View File

@@ -325,4 +325,4 @@
"Limit deck usage of groups" : "Nutzung auf Gruppen einschränken",
"Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them." : "Wenn Sie Deck einschränken, können Benutzer, die nicht zu diesen Gruppen gehören, keine eigenen Boards erstellen. Die Benutzer können weiterhin an Boards arbeiten, die für sie freigegeben wurden."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
}

View File

@@ -30,7 +30,9 @@ use OCA\Deck\Activity\CommentEventHandler;
use OCA\Deck\Capabilities;
use OCA\Deck\Collaboration\Resources\ResourceProvider;
use OCA\Deck\Collaboration\Resources\ResourceProviderCard;
use OCA\Deck\Dashboard\DeckWidget;
use OCA\Deck\Dashboard\DeckWidgetToday;
use OCA\Deck\Dashboard\DeckWidgetTomorrow;
use OCA\Deck\Dashboard\DeckWidgetUpcoming;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Event\AclCreatedEvent;
@@ -135,7 +137,9 @@ class Application extends App implements IBootstrap {
$context->registerSearchProvider(DeckProvider::class);
$context->registerSearchProvider(CardCommentProvider::class);
$context->registerDashboardWidget(DeckWidget::class);
$context->registerDashboardWidget(DeckWidgetUpcoming::class);
$context->registerDashboardWidget(DeckWidgetToday::class);
$context->registerDashboardWidget(DeckWidgetTomorrow::class);
$context->registerReferenceProvider(CreateCardReferenceProvider::class);

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Dashboard;
use OCP\Dashboard\IWidget;
use OCP\IL10N;
class DeckWidgetToday implements IWidget {
/**
*
* @var IL10N
*/
private $l10n;
public function __construct(IL10N $l10n) {
$this->l10n = $l10n;
}
/**
* @inheritDoc
*/
public function getId(): string {
return 'deckToday';
}
/**
* @inheritDoc
*/
public function getTitle(): string {
return $this->l10n->t('Cards due today');
}
/**
* @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');
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Deck\Dashboard;
use OCP\Dashboard\IWidget;
use OCP\IL10N;
class DeckWidgetTomorrow implements IWidget {
/**
*
* @var IL10N
*/
private $l10n;
public function __construct(IL10N $l10n) {
$this->l10n = $l10n;
}
/**
* @inheritDoc
*/
public function getId(): string {
return 'deckTomorrow';
}
/**
* @inheritDoc
*/
public function getTitle(): string {
return $this->l10n->t('Cards due tomorrow');
}
/**
* @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');
}
}

View File

@@ -40,7 +40,7 @@ use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Util;
class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
class DeckWidgetUpcoming implements IAPIWidget, IButtonWidget, IIconWidget {
private IL10N $l10n;
private OverviewService $dashboardService;
private IURLGenerator $urlGenerator;

View File

@@ -0,0 +1,79 @@
<template>
<a :key="card.id"
:href="cardLink"
target="_blank"
class="card">
<div class="card--header">
<DueDate class="right" :card="card" />
<span class="title">{{ card.title }}</span>
</div>
<ul v-if="card.labels && card.labels.length"
class="labels">
<li v-for="label in card.labels" :key="label.id" :style="labelStyle(label)">
<span>{{ label.title }}</span>
</li>
</ul>
</a>
</template>
<script>
import DueDate from '../cards/badges/DueDate.vue'
import { generateUrl } from '@nextcloud/router'
import labelStyle from '../../mixins/labelStyle.js'
export default {
name: 'Card',
components: { DueDate },
mixins: [labelStyle],
props: {
card: {
type: Object,
required: true,
},
},
computed: {
cardLink() {
return generateUrl('/apps/deck') + `#/board/${this.card.boardId}/card/${this.card.id}`
},
},
}
</script>
<style lang="scss" scoped>
@import '../../css/labels';
.card {
display: block;
border-radius: var(--border-radius-large);
padding: 8px;
height: 60px;
&:hover {
background-color: var(--color-background-hover);
}
}
.card--header {
overflow: hidden;
.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
}
.labels {
margin-left: 0;
}
.duedate:deep(.due) {
margin: 0 0 0 10px;
padding: 2px 4px;
font-size: 90%;
}
.right {
float: right;
}
</style>

View File

@@ -26,25 +26,64 @@ import './shared-init.js'
const debug = process.env.NODE_ENV !== 'production'
let _imports = null
const getAsyncImports = async () => {
if (_imports) {
return _imports
}
const { default: Vue } = await import('vue')
const { default: Vuex } = await import('vuex')
const { default: dashboard } = await import('./store/dashboard.js')
Vue.prototype.t = t
Vue.prototype.n = n
Vue.prototype.OC = OC
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
dashboard,
},
strict: debug,
})
_imports = {
store, Vue,
}
return _imports
}
document.addEventListener('DOMContentLoaded', () => {
OCA.Dashboard.register('deck', async (el) => {
const { default: Vue } = await import('vue')
const { default: Vuex } = await import('vuex')
const { default: dashboard } = await import('./store/dashboard.js')
const { Vue, store } = await getAsyncImports()
const { default: DashboardUpcoming } = await import('./views/DashboardUpcoming.vue')
const { default: Dashboard } = await import('./views/Dashboard.vue')
Vue.prototype.t = t
Vue.prototype.n = n
Vue.prototype.OC = OC
Vue.use(Vuex)
const View = Vue.extend(DashboardUpcoming)
const vm = new View({
propsData: {},
store,
}).$mount(el)
return vm
})
const store = new Vuex.Store({
modules: {
dashboard,
},
strict: debug,
})
const View = Vue.extend(Dashboard)
OCA.Dashboard.register('deckToday', async (el) => {
const { Vue, store } = await getAsyncImports()
const { default: DashboardToday } = await import('./views/DashboardToday.vue')
const View = Vue.extend(DashboardToday)
const vm = new View({
propsData: {},
store,
}).$mount(el)
return vm
})
OCA.Dashboard.register('deckTomorrow', async (el) => {
const { Vue, store } = await getAsyncImports()
const { default: DashboardTomorrow } = await import('./views/DashboardTomorrow.vue')
const View = Vue.extend(DashboardTomorrow)
const vm = new View({
propsData: {},
store,

View File

@@ -29,6 +29,7 @@ const apiClient = new OverviewApi()
export default {
state: {
assignedCards: [],
loading: false,
},
getters: {
assignedCardsDashboard: state => {
@@ -39,18 +40,27 @@ export default {
setAssignedCards(state, assignedCards) {
state.assignedCards = assignedCards
},
setLoading(state, promise) {
state.loading = promise
},
},
actions: {
async loadUpcoming({ commit }) {
commit('setCurrentBoard', null)
const upcommingCards = await apiClient.get('upcoming')
for (const dueStatus in upcommingCards) {
for (const idx in upcommingCards[dueStatus]) {
commit('addCard', upcommingCards[dueStatus][idx])
}
async loadUpcoming({ state, commit }) {
if (state.loading) {
return state.loading
}
commit('setAssignedCards', upcommingCards)
const promise = (async () => {
commit('setCurrentBoard', null)
const assignedCards = await apiClient.get('upcoming')
const assignedCardsFlat = assignedCards.flat()
for (const i in assignedCardsFlat) {
commit('addCard', assignedCardsFlat[i])
}
commit('setAssignedCards', assignedCardsFlat)
commit('setLoading', false)
})()
commit('setLoading', promise)
return promise
},
},
}

View File

@@ -0,0 +1,91 @@
<!--
- @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @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 <http://www.gnu.org/licenses/>.
-
-->
<template>
<NcDashboardWidget :items="cards"
empty-content-icon="icon-deck"
:empty-content-message="t('deck', 'No upcoming cards')"
:show-more-text="t('deck', 'upcoming cards today')"
:show-more-url="showMoreUrl"
:loading="loading"
@hide="() => {}"
@markDone="() => {}">
<template #default="{ item }">
<Card :card="item" />
</template>
</NcDashboardWidget>
</template>
<script>
import { NcDashboardWidget } from '@nextcloud/vue'
import { mapGetters } from 'vuex'
import Card from '../components/dashboard/Card.vue'
import { generateUrl } from '@nextcloud/router'
export default {
name: 'DashboardToday',
components: {
NcDashboardWidget,
Card,
},
data() {
return {
loading: false,
}
},
computed: {
...mapGetters([
'assignedCardsDashboard',
]),
cards() {
const today = new Date()
const list = [
...this.assignedCardsDashboard,
].filter((card) => {
return card.duedate !== null
}).filter((card) => {
return (new Date(card.duedate)).getDate() === (new Date(today)).getDate()
})
list.sort((a, b) => {
return (new Date(a.duedate)).getTime() - (new Date(b.duedate)).getTime()
})
return list
},
showMoreUrl() {
return this.cards.length > 7 ? generateUrl('/apps/deck') : null
},
},
beforeMount() {
this.loading = true
this.$store.dispatch('loadUpcoming').then(() => {
this.loading = false
})
},
}
</script>
<style lang="scss" scoped>
#deck-widget-empty-content {
text-align: center;
margin-top: 5vh;
}
</style>

View File

@@ -0,0 +1,92 @@
<!--
- @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @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 <http://www.gnu.org/licenses/>.
-
-->
<template>
<NcDashboardWidget :items="cards"
empty-content-icon="icon-deck"
:empty-content-message="t('deck', 'No upcoming cards')"
:show-more-text="t('deck', 'upcoming cards tomorrow')"
:show-more-url="showMoreUrl"
:loading="loading"
@hide="() => {}"
@markDone="() => {}">
<template #default="{ item }">
<Card :card="item" />
</template>
</NcDashboardWidget>
</template>
<script>
import { NcDashboardWidget } from '@nextcloud/vue'
import { mapGetters } from 'vuex'
import Card from '../components/dashboard/Card.vue'
import { generateUrl } from '@nextcloud/router'
export default {
name: 'DashboardTomorrow',
components: {
NcDashboardWidget,
Card,
},
data() {
return {
loading: false,
}
},
computed: {
...mapGetters([
'assignedCardsDashboard',
]),
cards() {
const tomorrow = new Date()
tomorrow.setDate(tomorrow.getDate() + 1)
const list = [
...this.assignedCardsDashboard,
].filter((card) => {
return card.duedate !== null
}).filter((card) => {
return (new Date(card.duedate)).getDate() === (new Date(tomorrow)).getDate()
})
list.sort((a, b) => {
return (new Date(a.duedate)).getTime() - (new Date(b.duedate)).getTime()
})
return list
},
showMoreUrl() {
return this.cards.length > 7 ? generateUrl('/apps/deck') : null
},
},
beforeMount() {
this.loading = true
this.$store.dispatch('loadUpcoming').then(() => {
this.loading = false
})
},
}
</script>
<style lang="scss" scoped>
#deck-widget-empty-content {
text-align: center;
margin-top: 5vh;
}
</style>

View File

@@ -30,21 +30,7 @@
@hide="() => {}"
@markDone="() => {}">
<template #default="{ item }">
<a :key="item.id"
:href="cardLink(item)"
target="_blank"
class="card">
<div class="card--header">
<DueDate class="right" :card="item" />
<span class="title">{{ item.title }}</span>
</div>
<ul v-if="item.labels && item.labels.length"
class="labels">
<li v-for="label in item.labels" :key="label.id" :style="labelStyle(label)">
<span>{{ label.title }}</span>
</li>
</ul>
</a>
<Card :card="item" />
</template>
</NcDashboardWidget>
<div class="center-button">
@@ -65,22 +51,20 @@
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import { NcButton, NcDashboardWidget, NcModal } from '@nextcloud/vue'
import { mapGetters } from 'vuex'
import labelStyle from './../mixins/labelStyle.js'
import DueDate from '../components/cards/badges/DueDate.vue'
import Card from '../components/dashboard/Card.vue'
import { generateUrl } from '@nextcloud/router'
import CreateNewCardCustomPicker from './CreateNewCardCustomPicker.vue'
export default {
name: 'Dashboard',
name: 'DashboardUpcoming',
components: {
CreateNewCardCustomPicker,
NcModal,
DueDate,
NcDashboardWidget,
NcButton,
PlusIcon,
Card,
},
mixins: [labelStyle],
data() {
return {
loading: false,
@@ -102,11 +86,6 @@ export default {
})
return list.slice(0, 5)
},
cardLink() {
return (card) => {
return generateUrl('/apps/deck') + `#/board/${card.boardId}/card/${card.id}`
}
},
showMoreUrl() {
return this.cards.length > 7 ? generateUrl('/apps/deck') : null
},
@@ -126,8 +105,6 @@ export default {
</script>
<style lang="scss" scoped>
@import './../css/labels';
.center-button {
display: flex;
align-items: center;