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

Unverified Commit 53f554b5 authored by Georg Ehrke's avatar Georg Ehrke Committed by GitHub
Browse files

Merge pull request #1626 from nextcloud/bugfix/1590/transfer_data_from_popover_to_sidebar

Store calendar object instance in vue, allow to transfer data from popover to sidebar
parents 9572f07a ed4b50b4
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
	},

	/**