Loading src/mixins/EditorMixin.js +80 −132 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 * Loading 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 } Loading @@ -388,7 +390,7 @@ export default { return } this.calendarObject.resetToDav() await this.$store.dispatch('resetCalendarObjectInstance') this.requiresActionOnRouteLeave = false this.closeEditor() }, Loading @@ -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 }, /** Loading Loading @@ -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 }, /** Loading Loading @@ -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 Loading @@ -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 }) }) } Loading Loading @@ -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 Loading @@ -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) }) Loading src/store/calendarObjectInstance.js +275 −2 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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 * Loading Loading @@ -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 * Loading src/store/calendars.js +4 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -779,6 +779,8 @@ const actions = { }, calendarObjectId: calendarObject.id, }) return calendarObject }, /** Loading Loading
src/mixins/EditorMixin.js +80 −132 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 * Loading 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 } Loading @@ -388,7 +390,7 @@ export default { return } this.calendarObject.resetToDav() await this.$store.dispatch('resetCalendarObjectInstance') this.requiresActionOnRouteLeave = false this.closeEditor() }, Loading @@ -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 }, /** Loading Loading @@ -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 }, /** Loading Loading @@ -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 Loading @@ -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 }) }) } Loading Loading @@ -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 Loading @@ -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) }) Loading
src/store/calendarObjectInstance.js +275 −2 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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 * Loading Loading @@ -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 * Loading
src/store/calendars.js +4 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -779,6 +779,8 @@ const actions = { }, calendarObjectId: calendarObject.id, }) return calendarObject }, /** Loading