Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 3a076313 authored by Georg Ehrke's avatar Georg Ehrke
Browse files

Move CalendarGrid to its dedicated component

parent 5c6aeda0
Loading
Loading
Loading
Loading
+228 −0
Original line number Diff line number Diff line
<!--
  - @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
  -
  - @author Georg Ehrke <oc.list@georgehrke.com>
  -
  - @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>
	<FullCalendar
		ref="fullCalendar"
		:options="options" />
</template>

<script>
// Import FullCalendar itself
import FullCalendar from '@fullcalendar/vue'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin from '@fullcalendar/interaction'
import listPlugin from '@fullcalendar/list'
import timeGridPlugin from '@fullcalendar/timegrid'

// Import event sources
import eventSource from '../fullcalendar/eventSources/eventSource.js'

// Import interaction handlers
import eventAllow from '../fullcalendar/interaction/eventAllow.js'
import eventClick from '../fullcalendar/interaction/eventClick.js'
import eventDrop from '../fullcalendar/interaction/eventDrop.js'
import eventResize from '../fullcalendar/interaction/eventResize.js'
import navLinkDayClick from '../fullcalendar/interaction/navLinkDayClick.js'
import navLinkWeekClick from '../fullcalendar/interaction/navLinkWeekClick.js'
import select from '../fullcalendar/interaction/select.js'

// Import localization plugins
import { getDateFormattingConfig } from '../fullcalendar/localization/dateFormattingConfig.js'
import { getFullCalendarLocale } from '../fullcalendar/localization/localeProvider.js'
import MomentPlugin from '../fullcalendar/localization/momentPlugin.js'

// Import rendering handlers
import dayCellClassNames from '../fullcalendar/rendering/dayCellClassNames.js'
import eventContent from '../fullcalendar/rendering/eventContent.js'
import eventOrder from '../fullcalendar/rendering/eventOrder.js'

// Import timezone plugins
import VTimezoneNamedTimezone from '../fullcalendar/timezones/vtimezoneNamedTimezoneImpl.js'

// Import other dependencies
import { mapGetters, mapState } from 'vuex'
import debounce from 'debounce'
import { getLocale } from '@nextcloud/l10n'
import { getYYYYMMDDFromFirstdayParam } from '../utils/date.js'
import { getFirstDayOfWeekFromMomentLocale } from '../utils/moment.js'

export default {
	name: 'CalendarGrid',
	components: {
		FullCalendar,
	},
	props: {
		/**
		 * Whether or not the user is authenticated
		 */
		isAuthenticatedUser: {
			type: Boolean,
			required: true,
		},
	},
	data() {
		return {
			updateTodayJob: null,
			updateTodayJobPreviousDate: null,
		}
	},
	computed: {
		...mapGetters({
			timezoneId: 'getResolvedTimezone',
		}),
		...mapState({
			locale: (state) => state.settings.momentLocale,
			eventLimit: state => state.settings.eventLimit,
			skipPopover: state => state.settings.skipPopover,
			showWeekends: state => state.settings.showWeekends,
			showWeekNumbers: state => state.settings.showWeekNumbers,
			slotDuration: state => state.settings.slotDuration,
			showTasks: state => state.settings.showTasks,
			timezone: state => state.settings.timezone,
			modificationCount: state => state.calendarObjects.modificationCount,
		}),
		options() {
			return {
				// Initialization:
				initialDate: getYYYYMMDDFromFirstdayParam(this.$route.params.firstDay),
				initialView: this.$route.params.view,
				// Data
				eventSources: this.eventSources,
				// Plugins
				plugins: this.plugins,
				// Interaction
				editable: this.isEditable,
				selectable: this.isAuthenticatedUser,
				eventAllow,
				eventClick: eventClick(this.$store, this.$router, this.$route, window),
				eventDrop: (...args) => eventDrop(this.$store, this.$refs.fullCalendar.getApi())(...args),
				eventResize: eventResize(this.$store),
				navLinkDayClick: navLinkDayClick(this.$router, this.$route),
				navLinkWeekClick: navLinkWeekClick(this.$router, this.$route),
				select: select(this.$store, this.$router, this.$route, window),
				navLinks: true,
				// Localization
				...getDateFormattingConfig(),
				locale: getFullCalendarLocale(getLocale(), this.locale),
				firstDay: getFirstDayOfWeekFromMomentLocale(this.locale),
				// Rendering
				dayCellClassNames,
				eventDidMount: eventContent,
				eventOrder: ['start', '-duration', 'allDay', eventOrder],
				forceEventDuration: false,
				headerToolbar: false,
				height: '100%',
				slotDuration: this.slotDuration,
				weekNumbers: this.showWeekNumbers,
				weekends: this.showWeekends,
				dayMaxEventRows: this.eventLimit,
				selectMirror: true,
				lazyFetching: false,
				nowIndicator: true,
				progressiveEventRendering: true,
				unselectAuto: true,
				// Timezones:
				timeZone: this.timezoneId,
			}
		},
		eventSources() {
			return this.$store.getters.enabledCalendars.map(eventSource(this.$store))
		},
		plugins() {
			return [
				dayGridPlugin,
				interactionPlugin,
				listPlugin,
				timeGridPlugin,
				MomentPlugin,
				VTimezoneNamedTimezone,
			]
		},
		isEditable() {
			// We do not allow drag and drop when the editor is open.
			return this.isAuthenticatedUser
				&& this.$route.name !== 'EditPopoverView'
				&& this.$route.name !== 'EditSidebarView'
		},
	},
	watch: {
		modificationCount: debounce(function() {
			const calendarApi = this.$refs.fullCalendar.getApi()
			calendarApi.refetchEvents()
		}, 50),
	},
	created() {
		this.updateTodayJob = setInterval(() => {
			const newDate = getYYYYMMDDFromFirstdayParam('now')

			if (this.updateTodayJobPreviousDate === null) {
				this.updateTodayJobPreviousDate = newDate
				return
			}

			if (this.updateTodayJobPreviousDate !== newDate) {
				this.updateTodayJobPreviousDate = newDate

				const calendarApi = this.$refs.fullCalendar.getApi()
				calendarApi.render()
			}
		}, 1000)

		/**
		 * This view is not used as a router view,
		 * hence we can't use beforeRouteUpdate directly.
		 */
		this.$router.beforeEach((to, from, next) => {
			if (to.params.firstDay !== from.params.firstDay) {
				const calendarApi = this.$refs.fullCalendar.getApi()
				calendarApi.gotoDate(getYYYYMMDDFromFirstdayParam(to.params.firstDay))
			}
			if (to.params.view !== from.params.view) {
				const calendarApi = this.$refs.fullCalendar.getApi()
				calendarApi.changeView(to.params.view)
				this.saveNewView(to.params.view)
			}

			if ((from.name === 'NewPopoverView' || from.name === 'NewSidebarView')
				&& to.name !== 'NewPopoverView'
				&& to.name !== 'NewSidebarView') {
				const calendarApi = this.$refs.fullCalendar.getApi()
				calendarApi.unselect()
			}

			next()
		})
	},
	methods: {
		/**
		 * When a user changes the view, remember it and
		 * use it the next time they open the calendar app
		 */
		saveNewView: debounce(function(initialView) {
			if (this.isAuthenticatedUser) {
				this.$store.dispatch('setInitialView', { initialView })
			}
		}, 5000),
	},
}
</script>
+5 −3
Original line number Diff line number Diff line
@@ -24,9 +24,11 @@ import { translatePlural as n } from '@nextcloud/l10n'
/**
 * Provide the string when the event limit is hit
 *
 * @param {Number} count Number of omitted event
 * @param {Object} data Data destructuring object
 * @param {Number} data.num Number of omitted event
 * @returns {string}
 */
export default function(count) {
	return n('calendar', '+%n more', '+%n more', count)
export default function({ num }) {
	// TODO: this is broken, because singular and plural are equal
	return n('calendar', '+%n more', '+%n more', num)
}
+0 −35
Original line number Diff line number Diff line
/**
 * @copyright Copyright (c) 2020 Georg Ehrke
 *
 * @author Georg Ehrke <oc.list@georgehrke.com>
 *
 * @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/>.
 *
 */

/**
 * Determines the height of the full-calendar element
 *
 * @param {Window} window The window object
 * @param {Node=} header The header-element
 * @returns {Function}
 */
export default function(window, header = null) {
	return function() {
		const headerHeight = header ? header.offsetHeight : 0
		return window.innerHeight - headerHeight
	}
}
+17 −205
Original line number Diff line number Diff line
@@ -43,46 +43,8 @@
		</AppNavigation>
		<EmbedTopNavigation v-if="isEmbedded" />
		<AppContent>
			<!-- Full calendar -->
			<FullCalendar
				ref="fullCalendar"
				:default-view="defaultView"
				:editable="isEditable"
				:force-event-duration="true"
				:header="showHeader"
				:height="windowResize"
				:slot-duration="slotDuration"
				:week-numbers="showWeekNumbers"
				:weekends="showWeekends"
				:event-sources="eventSources"
				:event-order="eventOrder"
				:plugins="plugins"
				:time-zone="timezoneId"
				:day-render="dayRender"
				:event-allow="eventAllow"
				:event-limit="eventLimit"
				:event-limit-text="eventLimitText"
				:default-date="defaultDate"
				:locales="locales"
				:locale="fullCalendarLocale"
				:first-day="firstDay"
				:selectable="isSelectable"
				:time-grid-event-min-height="16"
				:select-mirror="true"
				:lazy-fetching="false"
				:nav-links="true"
				:nav-link-day-click="navLinkDayClick"
				:nav-link-week-click="navLinkWeekClick"
				:now-indicator="true"
				:progressive-event-rendering="true"
				:unselect-auto="false"
				:week-numbers-within-days="true"
				:event-render="eventRender"
				@eventClick="eventClick"
				@eventDrop="eventDrop"
				@eventResize="eventResize"
				@select="select" />

			<CalendarGrid
				:is-authenticated-user="isAuthenticatedUser" />
			<EmptyCalendar
				v-if="showEmptyCalendarScreen" />
		</AppContent>
@@ -92,57 +54,38 @@
</template>

<script>
import FullCalendar from '@fullcalendar/vue'
import '@fullcalendar/core/main.css'
import dayGridPlugin from '@fullcalendar/daygrid'
import '@fullcalendar/daygrid/main.css'
import interactionPlugin from '@fullcalendar/interaction'
import listPlugin from '@fullcalendar/list'
import '@fullcalendar/list/main.css'
import timeGridPlugin from '@fullcalendar/timegrid'
import '@fullcalendar/timegrid/main.css'
// Import vue components
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import AppNavigationSpacer from '@nextcloud/vue/dist/Components/AppNavigationSpacer'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import Content from '@nextcloud/vue/dist/Components/Content'
import debounce from 'debounce'
import { uidToHexColor } from '../utils/color.js'
import AppNavigationHeader from '../components/AppNavigation/AppNavigationHeader.vue'
import CalendarList from '../components/AppNavigation/CalendarList.vue'
import Settings from '../components/AppNavigation/Settings.vue'
import CalendarListNew from '../components/AppNavigation/CalendarList/CalendarListNew.vue'
import EmbedTopNavigation from '../components/AppNavigation/EmbedTopNavigation.vue'
import EmptyCalendar from '../components/EmptyCalendar.vue'
import CalendarGrid from '../components/CalendarGrid.vue'

// Import CalDAV related methods
import {
	initializeClientForPublicView,
	initializeClientForUserView,
} from '../services/caldavService.js'

// Import others
import { uidToHexColor } from '../utils/color.js'
import {
	dateFactory,
	getUnixTimestampFromDate,
	getYYYYMMDDFromFirstdayParam,
} from '../utils/date.js'
import eventAllow from '../fullcalendar/eventAllow'
import eventClick from '../fullcalendar/eventClick'
import eventDrop from '../fullcalendar/eventDrop'
import eventOrder from '../fullcalendar/eventOrder'
import eventResize from '../fullcalendar/eventResize'
import eventSource from '../fullcalendar/eventSource'
import navLinkDayClick from '../fullcalendar/navLinkDayClick.js'
import navLinkWeekClick from '../fullcalendar/navLinkWeekClick'
import select from '../fullcalendar/select'
import VTimezoneNamedTimezone from '../fullcalendar/vtimezoneNamedTimezoneImpl'
import AppNavigationHeader from '../components/AppNavigation/AppNavigationHeader.vue'
import CalendarList from '../components/AppNavigation/CalendarList.vue'
import Settings from '../components/AppNavigation/Settings.vue'
import CalendarListNew from '../components/AppNavigation/CalendarList/CalendarListNew.vue'
import getTimezoneManager from '../services/timezoneDataProviderService'
import {
	mapGetters,
	mapState,
} from 'vuex'
import eventRender from '../fullcalendar/eventRender.js'
import EmbedTopNavigation from '../components/AppNavigation/EmbedTopNavigation.vue'
import EmptyCalendar from '../components/EmptyCalendar.vue'
import { getLocale } from '@nextcloud/l10n'
import loadMomentLocalization from '../utils/moment.js'
import eventLimitText from '../fullcalendar/eventLimitText.js'
import windowResize from '../fullcalendar/windowResize.js'
import dayRender from '../fullcalendar/dayRender.js'
import { loadState } from '@nextcloud/initial-state'
import {
	showWarning,
@@ -152,6 +95,7 @@ import '@nextcloud/dialogs/styles/toast.scss'
export default {
	name: 'Calendar',
	components: {
		CalendarGrid,
		EmptyCalendar,
		EmbedTopNavigation,
		Settings,
@@ -160,7 +104,6 @@ export default {
		Content,
		AppContent,
		AppNavigation,
		FullCalendar,
		AppNavigationSpacer,
		CalendarListNew,
	},
@@ -171,9 +114,6 @@ export default {
			updateTodayJob: null,
			updateTodayJobPreviousDate: null,
			showEmptyCalendarScreen: false,
			fullCalendarLocale: 'en',
			locales: [],
			firstDay: 0,
		}
	},
	computed: {
@@ -193,24 +133,6 @@ export default {
		defaultDate() {
			return getYYYYMMDDFromFirstdayParam(this.$route.params.firstDay)
		},
		defaultView() {
			return this.$route.params.view
		},
		eventSources() {
			return this.$store.getters.enabledCalendars.map(eventSource(this.$store))
		},
		plugins() {
			return [
				dayGridPlugin,
				interactionPlugin,
				listPlugin,
				timeGridPlugin,
				VTimezoneNamedTimezone,
			]
		},
		eventAllow() {
			return eventAllow
		},
		isEditable() {
			// We do not allow drag and drop when the editor is open.
			return !this.isPublicShare
@@ -243,37 +165,6 @@ export default {

			return null
		},
		eventOrder() {
			return ['start', '-duration', 'allDay', eventOrder]
		},
	},
	beforeRouteUpdate(to, from, next) {
		if (to.params.firstDay !== from.params.firstDay) {
			const calendarApi = this.$refs.fullCalendar.getApi()
			calendarApi.gotoDate(getYYYYMMDDFromFirstdayParam(to.params.firstDay))
		}
		if (to.params.view !== from.params.view) {
			const calendarApi = this.$refs.fullCalendar.getApi()
			calendarApi.changeView(to.params.view)
			this.saveNewView(to.params.view)
		}

		if ((from.name === 'NewPopoverView' || from.name === 'NewSidebarView')
			&& to.name !== 'NewPopoverView'
			&& to.name !== 'NewSidebarView') {
			const calendarApi = this.$refs.fullCalendar.getApi()
			calendarApi.unselect()
		}

		next()

		next()
	},
	watch: {
		modificationCount: debounce(function() {
			const calendarApi = this.$refs.fullCalendar.getApi()
			calendarApi.refetchEvents()
		}, 50),
	},
	created() {
		this.timeFrameCacheExpiryJob = setInterval(() => {
@@ -292,22 +183,6 @@ export default {
				})
			}
		}, 1000 * 60)

		this.updateTodayJob = setInterval(() => {
			const newDate = getYYYYMMDDFromFirstdayParam('now')

			if (this.updateTodayJobPreviousDate === null) {
				this.updateTodayJobPreviousDate = newDate
				return
			}

			if (this.updateTodayJobPreviousDate !== newDate) {
				this.updateTodayJobPreviousDate = newDate

				const calendarApi = this.$refs.fullCalendar.getApi()
				calendarApi.render()
			}
		}, 1000)
	},
	async beforeMount() {
		this.$store.commit('loadSettingsFromServer', {
@@ -381,72 +256,9 @@ export default {
			toastElement.classList.add('toast-calendar-multiline')
		}

		this.loadFullCalendarLocale()
		this.loadMomentLocale()
		await this.loadMomentLocale()
	},
	methods: {
		saveNewView: debounce(function(initialView) {
			if (this.isAuthenticatedUser) {
				this.$store.dispatch('setInitialView', { initialView })
			}
		}, 5000),
		dayRender(...args) {
			return dayRender(...args)
		},
		eventClick(...args) {
			return eventClick(this.$store, this.$router, this.$route, window)(...args)
		},
		eventDrop(...args) {
			return eventDrop(this.$store, this.$refs.fullCalendar.getApi())(...args)
		},
		eventResize(...args) {
			return eventResize(this.$store)(...args)
		},
		select(...args) {
			return select(this.$store, this.$router, this.$route, window)(...args)
		},
		eventRender(...args) {
			return eventRender(...args)
		},
		navLinkDayClick(...args) {
			return navLinkDayClick(this.$router, this.$route)(...args)
		},
		navLinkWeekClick(...args) {
			return navLinkWeekClick(this.$router, this.$route)(...args)
		},
		eventLimitText(...args) {
			return eventLimitText(...args)
		},
		windowResize(...args) {
			return windowResize(window, document.querySelector('#header'))(...args)
		},
		/**
		 * Loads the locale data for full-calendar
		 *
		 * @returns {Promise<void>}
		 */
		async loadFullCalendarLocale() {
			let locale = getLocale().replace('_', '-').toLowerCase()
			try {
				// try to load the default locale first
				const fcLocale = await import('@fullcalendar/core/locales/' + locale)
				this.locales.push(fcLocale)
				// We have to update firstDay manually till https://github.com/fullcalendar/fullcalendar-vue/issues/36 is fixed
				this.firstDay = fcLocale.week.dow
				this.fullCalendarLocale = locale
			} catch (e) {
				try {
					locale = locale.split('-')[0]
					const fcLocale = await import('@fullcalendar/core/locales/' + locale)
					this.locales.push(fcLocale)
					// We have to update firstDay manually till https://github.com/fullcalendar/fullcalendar-vue/issues/36 is fixed
					this.firstDay = fcLocale.week.dow
					this.fullCalendarLocale = locale
				} catch (e) {
					console.debug('falling back to english locale')
				}
			}
		},
		/**
		 * Loads the locale data for moment.js
		 *