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

Unverified Commit ed4b50b4 authored by Georg Ehrke's avatar Georg Ehrke
Browse files

Store calendar object instance in vue, allow to transfer data from popover to sidebar

parent 06c59906
Loading
Loading
Loading
Loading
+80 −132
Original line number Diff line number Diff line
@@ -21,11 +21,11 @@
 */
import rfcProps from '../models/rfcProps'
import logger from '../utils/logger.js'
import { mapEventComponentToCalendarObjectInstanceObject } from '../models/calendarObjectInstance.js'
import { getIllustrationForTitle } from '../utils/illustration.js'
import { getPrefixedRoute } from '../utils/router.js'
import { dateFactory } from '../utils/date.js'
import { uidToHexColor } from '../utils/color.js'
import { mapState } from 'vuex'

/**
 * This is a mixin for the editor. It contains common Vue stuff, that is
@@ -36,12 +36,6 @@ import { uidToHexColor } from '../utils/color.js'
export default {
	data() {
		return {
			// The calendar object from the Vuex store
			calendarObject: null,
			// The event component representing the open event
			eventComponent: null,
			// The calendar object instance object derived from the eventComponent
			calendarObjectInstance: null,
			// Indicator whether or not the event is currently loading
			isLoading: true,
			// Stores error if any occurred
@@ -60,6 +54,13 @@ export default {
		}
	},
	computed: {
		...mapState({
			calendarObject: (state) => state.calendarObjectInstance.calendarObject || null,
			calendarObjectInstance: (state) => state.calendarObjectInstance.calendarObjectInstance || null,
		}),
		eventComponent() {
			return this.calendarObjectInstance ? this.calendarObjectInstance.eventComponent : null
		},
		/**
		 * Returns the events title or an empty string if the event is still loading
		 *
@@ -373,11 +374,12 @@ export default {
				name: getPrefixedRoute(this.$store.state.route.name, 'CalendarView'),
				params,
			})
			this.$store.commit('resetCalendarObjectInstanceObjectIdAndRecurrenceId')
		},
		/**
		 * Resets the calendar-object back to it's original state and closes the editor
		 */
		cancel() {
		async cancel() {
			if (this.isLoading) {
				return
			}
@@ -388,7 +390,7 @@ export default {
				return
			}

			this.calendarObject.resetToDav()
			await this.$store.dispatch('resetCalendarObjectInstance')
			this.requiresActionOnRouteLeave = false
			this.closeEditor()
		},
@@ -406,42 +408,15 @@ export default {
			if (this.isReadOnly) {
				return
			}

			const isNewEvent = this.calendarObject.id === 'new'

			if (this.forceThisAndAllFuture) {
				thisAndAllFuture = true
			}

			if (this.eventComponent.isDirty()) {
			this.isLoading = true

				let original, fork
				if (this.eventComponent.canCreateRecurrenceExceptions() && !isNewEvent) {
					[original, fork] = this.eventComponent.createRecurrenceException(thisAndAllFuture)
				}

				await this.$store.dispatch('updateCalendarObject', {
					calendarObject: this.calendarObject,
				})

				if (!isNewEvent && thisAndAllFuture && original.root !== fork.root) {
					await this.$store.dispatch('createCalendarObjectFromFork', {
						eventComponent: fork,
			await this.$store.dispatch('saveCalendarObjectInstance', {
				thisAndAllFuture,
				calendarId: this.calendarId,
			})
				}
			}

			if (this.calendarId !== this.calendarObject.calendarId) {
				this.isLoading = true

				await this.$store.dispatch('moveCalendarObject', {
					calendarObject: this.calendarObject,
					newCalendarId: this.calendarId,
				})
			}

			this.isLoading = false
		},
		/**
@@ -471,18 +446,7 @@ export default {
			}

			this.isLoading = true

			const isRecurrenceSetEmpty = this.eventComponent.removeThisOccurrence(thisAndAllFuture)
			if (isRecurrenceSetEmpty) {
				await this.$store.dispatch('deleteCalendarObject', {
					calendarObject: this.calendarObject,
				})
			} else {
				await this.$store.dispatch('updateCalendarObject', {
					calendarObject: this.calendarObject,
				})
			}

			await this.$store.dispatch('deleteCalendarObjectInstance', { thisAndAllFuture })
			this.isLoading = false
		},
		/**
@@ -593,6 +557,18 @@ export default {
				calendarObjectInstance: this.calendarObjectInstance,
			})
		},
		/**
		 * Resets the internal state after changing the viewed calendar-object
		 */
		resetState() {
			this.isLoading = true
			this.error = false
			this.calendarId = null
			this.requiresActionOnRouteLeave = true
			this.forceThisAndAllFuture = false
			this.isEditingMasterItem = false
			this.isRecurrenceException = false
		},
	},
	/**
	 * This is executed before entering the Editor routes
@@ -604,64 +580,52 @@ export default {
	beforeRouteEnter(to, from, next) {
		if (to.name === 'NewSidebarView' || to.name === 'NewPopoverView') {
			next(vm => {
				vm.isLoading = true
				vm.error = false
				vm.calendarId = null
				vm.requiresActionOnRouteLeave = true
				vm.forceThisAndAllFuture = false
				vm.resetState()

				const isAllDay = (to.params.allDay === '1')
				const start = to.params.dtstart
				const end = to.params.dtend
				const start = parseInt(to.params.dtstart, 10)
				const end = parseInt(to.params.dtend, 10)
				const timezoneId = vm.$store.getters.getResolvedTimezone
				const recurrenceIdDate = new Date(start * 1000)

				vm.$store.dispatch('createNewEvent', { start, end, isAllDay, timezoneId })
					.then((calendarObject) => {
						vm.calendarObject = calendarObject
				vm.$store.dispatch('getCalendarObjectInstanceForNewEvent', { isAllDay, start, end, timezoneId })
					.then(({ calendarObject }) => {
						vm.calendarId = calendarObject.calendarId
						vm.eventComponent = calendarObject.getObjectAtRecurrenceId(recurrenceIdDate)
						vm.calendarObjectInstance = mapEventComponentToCalendarObjectInstanceObject(vm.eventComponent)

					})
					.catch(() => {
						vm.error = true
					})
					.finally(() => {
						vm.isLoading = false
					})
			})
		} else {
			next(vm => {
				vm.isLoading = true
				vm.error = false
				vm.calendarId = null
				vm.requiresActionOnRouteLeave = true
				vm.forceThisAndAllFuture = false

				vm.resetState()
				const objectId = to.params.object
				const recurrenceId = to.params.recurrenceId

				vm.$store.dispatch('getEventByObjectId', { objectId })
					.then(() => {
						vm.calendarObject = vm.$store.getters.getCalendarObjectById(objectId)
						vm.calendarId = vm.calendarObject.calendarId

				if (recurrenceId === 'next') {
							const recurrenceIdDate = dateFactory()
							vm.eventComponent = vm.calendarObject.getClosestRecurrence(recurrenceIdDate)
						} else {
							const recurrenceIdDate = new Date(recurrenceId * 1000)
							vm.eventComponent = vm.calendarObject.getObjectAtRecurrenceId(recurrenceIdDate)
					const closeToDate = dateFactory()
					// TODO: can we replace this by simply returning the new route since we are inside next()
					// Probably not though, because it's async
					vm.$store.dispatch('resolveClosestRecurrenceIdForCalendarObject', { objectId, closeToDate })
						.then(recurrenceId => {
							const params = Object.assign({}, vm.$route.params, { recurrenceId })
							vm.$router.replace({ name: vm.$route.name, params })
						})
					return
				}

						vm.calendarObjectInstance = mapEventComponentToCalendarObjectInstanceObject(vm.eventComponent)
				vm.$store.dispatch('getCalendarObjectInstanceByObjectIdAndRecurrenceId', { objectId, recurrenceId })
					.then(({ calendarObject }) => {
						vm.calendarId = calendarObject.calendarId
						vm.isEditingMasterItem = vm.eventComponent.isMasterItem()
						vm.isRecurrenceException = vm.eventComponent.isRecurrenceException()

						vm.isLoading = false

						if (recurrenceId === 'next') {
							const params = Object.assign({}, vm.$route.params, {
								recurrenceId: vm.eventComponent.getReferenceRecurrenceId().unixTime,
					})
							vm.$router.replace({ name: vm.$route.name, params })
						}
					.catch(() => {
						vm.error = true
					})
					.finally(() => {
						vm.isLoading = false
					})
			})
		}
@@ -691,16 +655,8 @@ export default {
			const start = to.params.dtstart
			const end = to.params.dtend
			const timezoneId = this.$store.getters.getResolvedTimezone

			this.$store.dispatch('updateTimeOfNewEvent', {
				calendarObjectInstance: this.calendarObjectInstance,
				start,
				end,
				isAllDay,
				timezoneId,
			})

			next()
			this.$store.dispatch('updateCalendarObjectInstanceForNewEvent', { isAllDay, start, end, timezoneId })
				.then(() => next())
		} else {
			// If both the objectId and recurrenceId remained the same
			// there is no need to update. This is usally the case when navigating
@@ -714,41 +670,33 @@ export default {
			this.isLoading = true

			this.save().then(() => {
				this.error = false
				this.calendarId = null
				this.requiresActionOnRouteLeave = true
				this.forceThisAndAllFuture = false
				this.resetState()

				const objectId = to.params.object
				const recurrenceId = to.params.recurrenceId

				this.$store.dispatch('getEventByObjectId', { objectId })
					.then(() => {
						this.calendarObject = this.$store.getters.getCalendarObjectById(objectId)
						this.calendarId = this.calendarObject.calendarId

				if (recurrenceId === 'next') {
							const recurrenceIdDate = dateFactory()
							this.eventComponent = this.calendarObject.getClosestRecurrence(recurrenceIdDate)
						} else {
							const recurrenceIdDate = new Date(recurrenceId * 1000)
							this.eventComponent = this.calendarObject.getObjectAtRecurrenceId(recurrenceIdDate)
					const closeToDate = dateFactory()
					this.$store.dispatch('resolveClosestRecurrenceIdForCalendarObject', { objectId, closeToDate })
						.then(recurrenceId => {
							const params = Object.assign({}, this.$route.params, { recurrenceId })
							next({ name: this.$route.name, params })
						})
					return
				}

						this.calendarObjectInstance = mapEventComponentToCalendarObjectInstanceObject(this.eventComponent)
				this.$store.dispatch('getCalendarObjectInstanceByObjectIdAndRecurrenceId', { objectId, recurrenceId })
					.then(({ calendarObject }) => {
						this.calendarId = calendarObject.calendarId
						this.isEditingMasterItem = this.eventComponent.isMasterItem()
						this.isRecurrenceException = this.eventComponent.isRecurrenceException()

						this.isLoading = false

						if (recurrenceId === 'next') {
							const params = Object.assign({}, this.$route.params, {
								recurrenceId: this.eventComponent.getReferenceRecurrenceId().unixTime,
					})
							this.$router.replace({ name: this.$route.name, params })
						}
					.catch(() => {
						this.error = true
					})
					.finally(() => {
						this.isLoading = false
						next()
					})
			}).catch(() => {
				next(false)
			})
+275 −2
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import Property from 'calendar-js/src/properties/property.js'
import { getBySetPositionAndBySetFromDate, getWeekDayFromDate } from '../utils/recurrence.js'
import {
	getAlarmFromAlarmComponent,
	getDefaultCalendarObjectInstanceObject,
	getDefaultCalendarObjectInstanceObject, mapEventComponentToCalendarObjectInstanceObject,
} from '../models/calendarObjectInstance.js'
import {
	getAmountAndUnitForTimedEvents,
@@ -40,10 +40,64 @@ import {
	getTotalSecondsFromAmountAndUnitForTimedEvents, getTotalSecondsFromAmountHourMinutesAndUnitForAllDayEvents,
} from '../utils/alarms.js'

const state = {}
const state = {
	isNew: null,
	calendarObject: null,
	calendarObjectInstance: null,
	existingEvent: {
		objectId: null,
		recurrenceId: null,
	},
}

const mutations = {

	/**
	 * Set a calendar-object-instance that will be opened in the editor (existing event)
	 *
	 * @param {Object} state The Vuex state
	 * @param {Object} data The destructuring object
	 * @param {Object} data.calendarObject The calendar-object currently being edited
	 * @param {Object} data.calendarObjectInstance The calendar-object-instance currently being edited
	 * @param {String} data.objectId The objectId of the calendar-object
	 * @param {number} data.recurrenceId The recurrence-id of the calendar-object-instance
	 */
	setCalendarObjectInstanceForExistingEvent(state, { calendarObject, calendarObjectInstance, objectId, recurrenceId }) {
		state.isNew = false
		state.calendarObject = calendarObject
		state.calendarObjectInstance = calendarObjectInstance
		state.existingEvent.objectId = objectId
		state.existingEvent.recurrenceId = recurrenceId
	},

	/**
	 * Set a calendar-object-instance that will be opened in the editor (new event)
	 *
	 * @param {Object} state The Vuex state
	 * @param {Object} data The destructuring object
	 * @param {Object} data.calendarObject The calendar-object currently being created
	 * @param {Object} data.calendarObjectInstance The calendar-object-instance currently being crated
	 */
	setCalendarObjectInstanceForNewEvent(state, { calendarObject, calendarObjectInstance }) {
		state.isNew = true
		state.calendarObject = calendarObject
		state.calendarObjectInstance = calendarObjectInstance
		state.existingEvent.objectId = null
		state.existingEvent.recurrenceId = null
	},

	/**
	 *
	 * @param {Object} state The Vuex state
	 */
	resetCalendarObjectInstanceObjectIdAndRecurrenceId(state) {
		state.isNew = false
		state.calendarObject = null
		state.calendarObjectInstance = null
		state.existingEvent.objectId = null
		state.existingEvent.recurrenceId = null
	},

	/**
	 * Change the title of the event
	 *
@@ -1249,6 +1303,225 @@ const getters = {}

const actions = {

	/**
	 * Returns the closest existing recurrence-id of a calendar-object
	 * close to the given date.
	 * This is either the next occurrence in the future or
	 * in case there are no more future occurrences the closest
	 * occurrence in the past
	 *
	 * @param {Object} vuex The vuex destructuring object
	 * @param {Object} vuex.state The Vuex state
	 * @param {Function} vuex.dispatch The Vuex dispatch function
	 * @param {Function} vuex.commit The Vuex commit function
	 * @param {Object} data The destructuring object
	 * @param {String} data.objectId The objectId of the calendar-object to edit
	 * @param {Date} data.closeToDate The date to get a close occurrence to
	 * @returns {Promise<Number>}
	 */
	async resolveClosestRecurrenceIdForCalendarObject({ state, dispatch, commit }, { objectId, closeToDate }) {
		const calendarObject = await dispatch('getEventByObjectId', { objectId })
		const eventComponent = calendarObject.getClosestRecurrence(closeToDate)

		return eventComponent.getReferenceRecurrenceId().unixTime
	},

	/**
	 * Gets the calendar-object and calendar-object-instance
	 * for a given objectId and recurrenceId.
	 *
	 * If the recurrenceId does not represent a valid instance,
	 * an error will be thrown.
	 *
	 * @param {Object} vuex The vuex destructuring object
	 * @param {Object} vuex.state The Vuex state
	 * @param {Function} vuex.dispatch The Vuex dispatch function
	 * @param {Function} vuex.commit The Vuex commit function
	 * @param {Object} data The destructuring object
	 * @param {String} data.objectId The objectId of the calendar-object to edit
	 * @param {Number} data.recurrenceId The recurrence-id to edit
	 * @returns {Promise<{calendarObject: Object, calendarObjectInstance: Object}>}
	 */
	async getCalendarObjectInstanceByObjectIdAndRecurrenceId({ state, dispatch, commit }, { objectId, recurrenceId }) {
		if (state.existingEvent.objectId === objectId && state.existingEvent.recurrenceId === recurrenceId) {
			return Promise.resolve({
				calendarObject: state.calendarObject,
				calendarObjectInstance: state.calendarObjectInstance,
			})
		}

		const recurrenceIdDate = new Date(recurrenceId * 1000)
		const calendarObject = await dispatch('getEventByObjectId', { objectId })
		const eventComponent = calendarObject.getObjectAtRecurrenceId(recurrenceIdDate)
		if (eventComponent === null) {
			throw new Error('Not a valid recurrence-id')
		}

		const calendarObjectInstance = mapEventComponentToCalendarObjectInstanceObject(eventComponent)
		commit('setCalendarObjectInstanceForExistingEvent', {
			calendarObject,
			calendarObjectInstance,
			objectId,
			recurrenceId,
		})

		return {
			calendarObject,
			calendarObjectInstance,
		}
	},

	/**
	 * Gets the new calendar-object-instance.
	 *
	 * @param {Object} vuex The vuex destructuring object
	 * @param {Object} vuex.state The Vuex state
	 * @param {Function} vuex.dispatch The Vuex dispatch function
	 * @param {Function} vuex.commit The Vuex commit function
	 * @param {Object} data The destructuring object
	 * @param {Boolean} data.isAllDay Whether or not the new event is supposed to be all-day
	 * @param {Number} data.start The start of the new event (unixtime)
	 * @param {Number} data.end The end of the new event (unixtime)
	 * @param {String} data.timezoneId The timezoneId of the new event
	 * @returns {Promise<{calendarObject: Object, calendarObjectInstance: Object}>}
	 */
	async getCalendarObjectInstanceForNewEvent({ state, dispatch, commit }, { isAllDay, start, end, timezoneId }) {
		if (state.isNew === true) {
			return Promise.resolve({
				calendarObject: state.calendarObject,
				calendarObjectInstance: state.calendarObjectInstance,
			})
		}

		const calendarObject = await dispatch('createNewEvent', { start, end, isAllDay, timezoneId })
		const startDate = new Date(start * 1000)
		const eventComponent = calendarObject.getObjectAtRecurrenceId(startDate)
		const calendarObjectInstance = mapEventComponentToCalendarObjectInstanceObject(eventComponent)

		commit('setCalendarObjectInstanceForNewEvent', {
			calendarObject,
			calendarObjectInstance,
		})

		return {
			calendarObject,
			calendarObjectInstance,
		}
	},

	/**
	 * Updates the existing calendar-object-instance.
	 *
	 * @param {Object} vuex The vuex destructuring object
	 * @param {Object} vuex.state The Vuex state
	 * @param {Function} vuex.dispatch The Vuex dispatch function
	 * @param {Function} vuex.commit The Vuex commit function
	 * @param {Object} data The destructuring object
	 * @param {Boolean} data.isAllDay Whether or not the new event is supposed to be all-day
	 * @param {Number} data.start The start of the new event (unixtime)
	 * @param {Number} data.end The end of the new event (unixtime)
	 * @param {String} data.timezoneId The timezoneId of the new event
	 * @returns {Promise<{calendarObject: Object, calendarObjectInstance: Object}>}
	 */
	async updateCalendarObjectInstanceForNewEvent({ state, dispatch, commit }, { isAllDay, start, end, timezoneId }) {
		await dispatch('updateTimeOfNewEvent', {
			calendarObjectInstance: state.calendarObjectInstance,
			start,
			end,
			isAllDay,
			timezoneId,
		})
		commit('setCalendarObjectInstanceForNewEvent', {
			calendarObject: state.calendarObject,
			calendarObjectInstance: state.calendarObjectInstance,
		})

		return {
			calendarObject: state.calendarObject,
			calendarObjectInstance: state.calendarObjectInstance,
		}
	},

	/**
	 * Saves changes made to a single calendar-object-instance
	 *
	 * @param {Object} vuex The vuex destructuring object
	 * @param {Object} vuex.state The Vuex state
	 * @param {Function} vuex.dispatch The Vuex dispatch function
	 * @param {Function} vuex.commit The Vuex commit function
	 * @param {Object} data The destructuring object
	 * @param {Boolean} data.thisAndAllFuture Whether or not to save changes for all future occurrences or just this one
	 * @param {String} data.calendarId The new calendar-id to store it in
	 * @returns {Promise<void>}
	 */
	async saveCalendarObjectInstance({ state, dispatch, commit }, { thisAndAllFuture, calendarId }) {
		const eventComponent = state.calendarObjectInstance.eventComponent
		const calendarObject = state.calendarObject
		const isNewEvent = calendarObject.id === 'new'

		if (eventComponent.isDirty()) {
			let original, fork
			if (eventComponent.canCreateRecurrenceExceptions() && !isNewEvent) {
				[original, fork] = eventComponent.createRecurrenceException(thisAndAllFuture)
			}

			await dispatch('updateCalendarObject', { calendarObject })

			if (!isNewEvent && thisAndAllFuture && original.root !== fork.root) {
				await dispatch('createCalendarObjectFromFork', {
					eventComponent: fork,
					calendarId: calendarId,
				})
			}
		}

		if (calendarId !== state.calendarObject.calendarId) {
			await dispatch('moveCalendarObject', {
				calendarObject,
				newCalendarId: calendarId,
			})
		}
	},

	/**
	 * Deletes a calendar-object-instance
	 *
	 * @param {Object} vuex The vuex destructuring object
	 * @param {Object} vuex.state The Vuex state
	 * @param {Function} vuex.dispatch The Vuex dispatch function
	 * @param {Function} vuex.commit The Vuex commit function
	 * @param {Object} data The destructuring object
	 * @param {Boolean} data.thisAndAllFuture Whether or not to delete all future occurrences or just this one
	 * @returns {Promise<void>}
	 */
	async deleteCalendarObjectInstance({ state, dispatch, commit }, { thisAndAllFuture }) {
		const eventComponent = state.calendarObjectInstance.eventComponent
		const isRecurrenceSetEmpty = eventComponent.removeThisOccurrence(thisAndAllFuture)
		const calendarObject = state.calendarObject

		if (isRecurrenceSetEmpty) {
			await dispatch('deleteCalendarObject', { calendarObject })
		} else {
			await dispatch('updateCalendarObject', { calendarObject })
		}
	},

	/**
	 * Resets a calendar-object-instance to it's original data and
	 * removes all data from the calendar-object-instance store
	 *
	 * @param {Object} vuex The vuex destructuring object
	 * @param {Object} vuex.state The Vuex state
	 * @param {Function} vuex.dispatch The Vuex dispatch function
	 * @param {Function} vuex.commit The Vuex commit function
	 * @returns {Promise<void>}
	 */
	async resetCalendarObjectInstance({ state, commit }) {
		if (state.calendarObject) {
			state.calendarObject.resetToDav()
		}
	},

	/**
	 * Change the timezone of the event's start
	 *
+4 −2
Original line number Diff line number Diff line
@@ -748,13 +748,13 @@ const actions = {
	 * @param {Object} context the store mutations
	 * @param {Object} data destructuring object
	 * @param {String} data.objectId Id of the object to fetch
	 * @returns {Promise<void>}
	 * @returns {Promise<CalendarObject>}
	 */
	async getEventByObjectId(context, { objectId }) {
		// TODO - we should still check if the calendar-object is up to date
		//  - Just send head and compare etags
		if (context.getters.getCalendarObjectById(objectId)) {
			return Promise.resolve(true)
			return Promise.resolve(context.getters.getCalendarObjectById(objectId))
		}

		// This might throw an exception, but we will leave it up to the methods
@@ -779,6 +779,8 @@ const actions = {
			},
			calendarObjectId: calendarObject.id,
		})

		return calendarObject
	},

	/**