Implement Calendar Dashboard Widget
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
parent
25e852346e
commit
eb1c1aab7b
|
@ -12,7 +12,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.2', '7.3', '7.4']
|
||||
nextcloud-versions: ['master', 'stable17', 'stable18']
|
||||
nextcloud-versions: ['master']
|
||||
exclude:
|
||||
- php-versions: '7.4'
|
||||
nextcloud-versions: 'stable17'
|
||||
|
|
|
@ -23,19 +23,39 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace OCA\Calendar\AppInfo;
|
||||
|
||||
use OCA\Calendar\Dashboard\CalendarWidget;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
|
||||
/**
|
||||
* Class Application
|
||||
*
|
||||
* @package OCA\Calendar\AppInfo
|
||||
*/
|
||||
class Application extends App {
|
||||
class Application extends App implements IBootstrap {
|
||||
|
||||
/** @var string */
|
||||
public const APP_ID = 'calendar';
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(array $params=[]) {
|
||||
parent::__construct('calendar', $params);
|
||||
parent::__construct(self::APP_ID, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function register(IRegistrationContext $context): void {
|
||||
$context->registerDashboardWidget(CalendarWidget::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function boot(IBootContext $context): void {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<?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\Calendar\Dashboard;
|
||||
|
||||
use OCA\Calendar\AppInfo\Application;
|
||||
use OCA\Calendar\Service\JSDataService;
|
||||
use OCP\Dashboard\IWidget;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IL10N;
|
||||
|
||||
class CalendarWidget implements IWidget {
|
||||
|
||||
/**
|
||||
* @var IL10N
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
/**
|
||||
* @var IInitialStateService
|
||||
*/
|
||||
private $initialStateService;
|
||||
|
||||
/**
|
||||
* @var JSDataService
|
||||
*/
|
||||
private $dataService;
|
||||
|
||||
/**
|
||||
* CalendarWidget constructor.
|
||||
* @param IL10N $l10n
|
||||
* @param IInitialStateService $initialStateService
|
||||
* @param JSDataService $dataService
|
||||
*/
|
||||
public function __construct(IL10N $l10n,
|
||||
IInitialStateService $initialStateService,
|
||||
JSDataService $dataService) {
|
||||
$this->l10n = $l10n;
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->dataService = $dataService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getId(): string {
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTitle(): string {
|
||||
return $this->l10n->t('Upcoming events');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOrder(): int {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getIconClass(): string {
|
||||
return 'icon-calendar-dark';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getUrl(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function load(): void {
|
||||
\OCP\Util::addScript('calendar', 'dashboard');
|
||||
|
||||
$this->initialStateService->provideLazyInitialState(Application::APP_ID, 'dashboard_data', function () {
|
||||
return $this->dataService;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2020 Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Calendar\Service;
|
||||
|
||||
use OCA\Calendar\AppInfo\Application;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class JSDataService implements \JsonSerializable {
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/**
|
||||
* JSDataService constructor.
|
||||
*
|
||||
* @param IConfig $config
|
||||
* @param IUserSession $userSession
|
||||
*/
|
||||
public function __construct(IConfig $config,
|
||||
IUserSession $userSession) {
|
||||
$this->config = $config;
|
||||
$this->userSession = $userSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
$user = $this->userSession->getUser();
|
||||
|
||||
if ($user === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$defaultTimezone = $this->config->getAppValue(Application::APP_ID, 'timezone', 'automatic');
|
||||
$defaultShowTasks = $this->config->getAppValue(Application::APP_ID, 'showTasks', 'yes');
|
||||
$timezone = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'timezone', $defaultTimezone);
|
||||
$showTasks = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'showTasks', $defaultShowTasks) === 'yes';
|
||||
|
||||
return [
|
||||
'timezone' => $timezone,
|
||||
'show_tasks' => $showTasks,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1933,6 +1933,16 @@
|
|||
"vue2-datepicker": "^3.6.2"
|
||||
}
|
||||
},
|
||||
"@nextcloud/vue-dashboard": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/vue-dashboard/-/vue-dashboard-0.1.3.tgz",
|
||||
"integrity": "sha512-7b02zkarX7b18IRQmZEW1NM+dvtcUih2M0+CZyuQfcvfyMQudOz+BdA/oD1p7PmdBds1IR8OvY1+CnpmgAzfQg==",
|
||||
"requires": {
|
||||
"@nextcloud/vue": "^2.3.0",
|
||||
"core-js": "^3.6.4",
|
||||
"vue": "^2.6.11"
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"@nextcloud/moment": "^1.1.0",
|
||||
"@nextcloud/router": "^1.1.0",
|
||||
"@nextcloud/vue": "^2.6.0",
|
||||
"@nextcloud/vue-dashboard": "^0.1.3",
|
||||
"autosize": "^4.0.2",
|
||||
"calendar-js": "git+https://github.com/nextcloud/calendar-js.git",
|
||||
"cdav-library": "github:nextcloud/cdav-library",
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
import Dashboard from './views/Dashboard'
|
||||
import store from './store'
|
||||
|
||||
// eslint-disable-next-line
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
|
||||
// eslint-disable-next-line
|
||||
__webpack_public_path__ = generateFilePath('calendar', '', 'js/')
|
||||
|
||||
Vue.prototype.t = translate
|
||||
Vue.prototype.n = translatePlural
|
||||
Vue.prototype.OC = OC
|
||||
Vue.prototype.OCA = OCA
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
OCA.Dashboard.register('calendar', (el) => {
|
||||
const View = Vue.extend(Dashboard)
|
||||
new View({
|
||||
store,
|
||||
propsData: {},
|
||||
}).$mount(el)
|
||||
})
|
||||
|
||||
})
|
|
@ -0,0 +1,312 @@
|
|||
<!--
|
||||
- @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>
|
||||
<DashboardWidget
|
||||
id="calendar_panel"
|
||||
:items="items"
|
||||
:loading="loading">
|
||||
<template #default="{ item }">
|
||||
<EmptyContent v-if="item.isEmptyItem"
|
||||
id="calendar-widget-empty-content"
|
||||
class="half-screen"
|
||||
icon="icon-checkmark">
|
||||
<template #desc>
|
||||
{{ t('calendar', 'No more events today') }}
|
||||
</template>
|
||||
</EmptyContent>
|
||||
<DashboardWidgetItem v-else :item="item">
|
||||
<template #avatar>
|
||||
<div
|
||||
v-if="item.componentName === 'VEVENT'"
|
||||
class="calendar-dot"
|
||||
:style="{'background-color': item.calendarColor}"
|
||||
:title="item.calendarDisplayName" />
|
||||
<div v-else
|
||||
class="vtodo-checkbox"
|
||||
:style="{'color': item.calendarColor}"
|
||||
:title="item.calendarDisplayName" />
|
||||
</template>
|
||||
</DashboardWidgetItem>
|
||||
</template>
|
||||
<template #empty-content>
|
||||
<EmptyContent
|
||||
id="calendar-widget-empty-content"
|
||||
icon="icon-calendar-dark">
|
||||
<template #desc>
|
||||
<p class="empty-label">
|
||||
{{ t('calendar', 'No upcoming events') }}
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
class="button"
|
||||
:href="clickStartNew">
|
||||
{{ t('calendar', 'Create a new event') }}
|
||||
</a>
|
||||
</p>
|
||||
</template>
|
||||
</EmptyContent>
|
||||
</template>
|
||||
</DashboardWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DashboardWidget, DashboardWidgetItem } from '@nextcloud/vue-dashboard'
|
||||
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import moment from '@nextcloud/moment'
|
||||
import { imagePath, generateUrl } from '@nextcloud/router'
|
||||
import { initializeClientForUserView } from '../services/caldavService'
|
||||
import { dateFactory } from '../utils/date'
|
||||
import pLimit from 'p-limit'
|
||||
import { eventSourceFunction } from '../fullcalendar/eventSources/eventSourceFunction'
|
||||
import getTimezoneManager from '../services/timezoneDataProviderService'
|
||||
import loadMomentLocalization from '../utils/moment.js'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
DashboardWidget,
|
||||
DashboardWidgetItem,
|
||||
EmptyContent,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
events: null,
|
||||
locale: 'en',
|
||||
imagePath: imagePath('calendar', 'illustrations/calendar'),
|
||||
loading: true,
|
||||
now: dateFactory(),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Format loaded events
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
items() {
|
||||
if (!Array.isArray(this.events) || this.events.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const firstEvent = this.events[0]
|
||||
const endOfToday = moment(this.now).endOf('day')
|
||||
if (endOfToday.isBefore(firstEvent.startDate)) {
|
||||
return [{
|
||||
isEmptyItem: true,
|
||||
}].concat(this.events.slice(0, 4))
|
||||
}
|
||||
|
||||
return this.events
|
||||
},
|
||||
/**
|
||||
* Redirects to the new event route
|
||||
* @returns {String}
|
||||
*/
|
||||
clickStartNew() {
|
||||
return generateUrl('apps/calendar') + '/new'
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initialize()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Initialize the widget
|
||||
*/
|
||||
async initialize() {
|
||||
const start = dateFactory()
|
||||
const end = dateFactory()
|
||||
end.setDate(end.getDate() + 14)
|
||||
|
||||
await this.initializeEnvironment()
|
||||
const expandedEvents = await this.fetchExpandedEvents(start, end)
|
||||
this.events = await this.formatEvents(expandedEvents)
|
||||
this.loading = false
|
||||
},
|
||||
/**
|
||||
* Initialize everything necessary,
|
||||
* before we can fetch events
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initializeEnvironment() {
|
||||
await initializeClientForUserView()
|
||||
await this.$store.dispatch('fetchCurrentUserPrincipal')
|
||||
await this.$store.dispatch('getCalendars')
|
||||
|
||||
const {
|
||||
show_tasks: showTasks,
|
||||
timezone,
|
||||
} = loadState('calendar', 'dashboard_data')
|
||||
const locale = await loadMomentLocalization()
|
||||
|
||||
this.$store.commit('loadSettingsFromServer', {
|
||||
timezone,
|
||||
showTasks,
|
||||
})
|
||||
this.$store.commit('setMomentLocale', {
|
||||
locale,
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Fetch events
|
||||
*
|
||||
* @param {Date} from Start of time-range
|
||||
* @param {Date} to End of time-range
|
||||
*
|
||||
* @returns {Promise<Object[]>}
|
||||
*/
|
||||
async fetchExpandedEvents(from, to) {
|
||||
const timeZone = this.$store.getters.getResolvedTimezone
|
||||
let timezoneObject = getTimezoneManager().getTimezoneForId(timeZone)
|
||||
if (!timezoneObject) {
|
||||
timezoneObject = getTimezoneManager().getTimezoneForId('UTC')
|
||||
}
|
||||
|
||||
const limit = pLimit(10)
|
||||
const fetchEventPromises = []
|
||||
for (const calendar of this.$store.getters.enabledCalendars) {
|
||||
fetchEventPromises.push(limit(async() => {
|
||||
let timeRangeId
|
||||
try {
|
||||
timeRangeId = await this.$store.dispatch('getEventsFromCalendarInTimeRange', {
|
||||
calendar,
|
||||
from,
|
||||
to,
|
||||
})
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
|
||||
const calendarObjects = this.$store.getters.getCalendarObjectsByTimeRangeId(timeRangeId)
|
||||
return eventSourceFunction(calendarObjects, calendar, from, to, timezoneObject)
|
||||
}))
|
||||
}
|
||||
|
||||
const expandedEvents = await Promise.all(fetchEventPromises)
|
||||
return expandedEvents.flat()
|
||||
},
|
||||
/**
|
||||
* @param {Object[]} expandedEvents Array of fullcalendar events
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
formatEvents(expandedEvents) {
|
||||
return expandedEvents
|
||||
.sort((a, b) => a.start.getTime() - b.start.getTime())
|
||||
.filter(event => !event.classNames.includes('fc-event-nc-task-completed'))
|
||||
.slice(0, 7)
|
||||
.map((event) => ({
|
||||
isEmptyItem: false,
|
||||
componentName: event.extendedProps.objectType,
|
||||
targetUrl: event.extendedProps.objectType === 'VEVENT'
|
||||
? this.getCalendarAppUrl(event)
|
||||
: this.getTasksAppUrl(event),
|
||||
subText: this.formatSubtext(event),
|
||||
mainText: event.title,
|
||||
startDate: event.start,
|
||||
calendarColor: this.$store.state.calendars.calendarsById[event.extendedProps.calendarId].color,
|
||||
calendarDisplayName: this.$store.state.calendars.calendarsById[event.extendedProps.calendarId].displayname,
|
||||
}))
|
||||
},
|
||||
/**
|
||||
* @param {Object} event The full-calendar formatted event
|
||||
* @returns {String}
|
||||
*/
|
||||
formatSubtext(event) {
|
||||
const locale = this.$store.state.settings.momentLocale
|
||||
|
||||
if (event.allDay) {
|
||||
return moment(event.start).locale(locale).calendar(null, {
|
||||
// TRANSLATORS Please translate only the text in brackets and keep the brackets!
|
||||
sameDay: t('calendar', '[Today]'),
|
||||
// TRANSLATORS Please translate only the text in brackets and keep the brackets!
|
||||
nextDay: t('calendar', '[Tomorrow]'),
|
||||
nextWeek: 'dddd',
|
||||
// TRANSLATORS Please translate only the text in brackets and keep the brackets!
|
||||
lastDay: t('calendar', '[Yesterday]'),
|
||||
// TRANSLATORS Please translate only the text in brackets and keep the brackets!
|
||||
lastWeek: t('calendar', '[Last] dddd'),
|
||||
sameElse: () => '[replace-from-now]',
|
||||
}).replace('replace-from-now', moment(event.start).locale(locale).fromNow())
|
||||
} else {
|
||||
return moment(event.start).locale(locale).calendar(null, {
|
||||
sameElse: () => '[replace-from-now]',
|
||||
}).replace('replace-from-now', moment(event.start).locale(locale).fromNow())
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Object} data The data destructuring object
|
||||
* @param {Object} data.extendedProps Extended Properties of the FC object
|
||||
* @returns {string}
|
||||
*/
|
||||
getCalendarAppUrl({ extendedProps }) {
|
||||
return generateUrl('apps/calendar') + '/edit/' + extendedProps.objectId + '/' + extendedProps.recurrenceId
|
||||
},
|
||||
/**
|
||||
* @param {Object} data The data destructuring object
|
||||
* @param {Object} data.extendedProps Extended Properties of the FC object
|
||||
* @returns {string}
|
||||
*/
|
||||
getTasksAppUrl({ extendedProps }) {
|
||||
const davUrlParts = extendedProps.davUrl.split('/')
|
||||
const taskId = davUrlParts.pop()
|
||||
const calendarId = davUrlParts.pop()
|
||||
return generateUrl('apps/tasks') + `/#/calendars/${calendarId}/tasks/${taskId}`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../fonts/scss/iconfont-calendar-app';
|
||||
|
||||
#calendar_panel {
|
||||
.vtodo-checkbox {
|
||||
border-color: transparent;
|
||||
@include iconfont('checkbox');
|
||||
}
|
||||
|
||||
.calendar-dot {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
margin-top: 0.2rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#calendar-widget-empty-content {
|
||||
text-align: center;
|
||||
margin-top: 5vh;
|
||||
|
||||
&.half-screen {
|
||||
margin-top: 0;
|
||||
height: 120px;
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
|
||||
.empty-label {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2020 Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Calendar\Dashboard;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
use OCA\Calendar\Service\JSDataService;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IL10N;
|
||||
|
||||
class CalendarWidgetTest extends TestCase {
|
||||
|
||||
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $l10n;
|
||||
|
||||
/** @var IInitialStateService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $initialState;
|
||||
|
||||
/** @var JSDataService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $service;
|
||||
|
||||
/** @var CalendarWidget */
|
||||
private $widget;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->initialState = $this->createMock(IInitialStateService::class);
|
||||
$this->service = $this->createMock(JSDataService::class);
|
||||
|
||||
$this->widget = new CalendarWidget($this->l10n, $this->initialState, $this->service);
|
||||
}
|
||||
|
||||
public function testGetId(): void {
|
||||
$this->assertEquals('calendar', $this->widget->getId());
|
||||
}
|
||||
|
||||
public function testGetTitle(): void {
|
||||
$this->l10n->expects($this->exactly(1))
|
||||
->method('t')
|
||||
->willReturnArgument(0);
|
||||
|
||||
$this->assertEquals('Upcoming events', $this->widget->getTitle());
|
||||
}
|
||||
|
||||
public function testGetOrder(): void {
|
||||
$this->assertEquals(2, $this->widget->getOrder());
|
||||
}
|
||||
|
||||
public function testGetIconClass(): void {
|
||||
$this->assertEquals('icon-calendar-dark', $this->widget->getIconClass());
|
||||
}
|
||||
|
||||
public function testGetUrl(): void {
|
||||
$this->assertNull($this->widget->getUrl());
|
||||
}
|
||||
|
||||
public function testLoad(): void {
|
||||
$this->initialState->expects($this->once())
|
||||
->method('provideLazyInitialState')
|
||||
->with('calendar', 'dashboard_data', $this->callback(function ($actual) {
|
||||
$fnResult = $actual();
|
||||
return $fnResult === $this->service;
|
||||
}));
|
||||
|
||||
$this->widget->load();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2020 Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Calendar\Service;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class JsDataServiceTest extends TestCase {
|
||||
|
||||
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $config;
|
||||
|
||||
/** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $userSession;
|
||||
|
||||
/** @var JSDataService */
|
||||
private $service;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
|
||||
$this->service = new JSDataService($this->config, $this->userSession);
|
||||
}
|
||||
|
||||
public function testJsonSerialize(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('john.doe');
|
||||
$this->userSession->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
|
||||
$this->config->expects($this->at(0))
|
||||
->method('getAppValue')
|
||||
->with('calendar', 'timezone', 'automatic')
|
||||
->willReturn('default-app-value-timezone');
|
||||
$this->config->expects($this->at(1))
|
||||
->method('getAppValue')
|
||||
->with('calendar', 'showTasks', 'yes')
|
||||
->willReturn('default-app-value-showTasks');
|
||||
$this->config->expects($this->at(2))
|
||||
->method('getUserValue')
|
||||
->with('john.doe', 'calendar', 'timezone', 'default-app-value-timezone')
|
||||
->willReturn('timezone-config-value');
|
||||
$this->config->expects($this->at(3))
|
||||
->method('getUserValue')
|
||||
->with('john.doe', 'calendar', 'showTasks', 'default-app-value-showTasks')
|
||||
->willReturn('yes');
|
||||
|
||||
$this->assertEquals([
|
||||
'timezone' => 'timezone-config-value',
|
||||
'show_tasks' => true,
|
||||
], $this->service->jsonSerialize());
|
||||
}
|
||||
|
||||
public function testJsonSerializeNoUserSession(): void {
|
||||
$this->userSession->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn(null);
|
||||
|
||||
$this->config->expects($this->never())
|
||||
->method('getAppValue');
|
||||
$this->config->expects($this->never())
|
||||
->method('getUserValue');
|
||||
|
||||
$this->assertEmpty($this->service->jsonSerialize());
|
||||
}
|
||||
}
|
|
@ -12,11 +12,14 @@ const SCOPE_VERSION = JSON.stringify(versionHash)
|
|||
const ICONFONT_NAME = `iconfont-calendar-app-${versionHash}`
|
||||
|
||||
module.exports = {
|
||||
entry: path.join(__dirname, 'src', 'main.js'),
|
||||
entry: {
|
||||
calendar: path.join(__dirname, 'src', 'main.js'),
|
||||
dashboard: path.join(__dirname, 'src', 'dashboard.js'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, './js'),
|
||||
publicPath: '/js/',
|
||||
filename: 'calendar.js',
|
||||
filename: '[name].js',
|
||||
chunkFilename: 'chunks/calendar.[name].[contenthash].js'
|
||||
},
|
||||
module: {
|
||||
|
|
Loading…
Reference in New Issue