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
This commit is contained in:
Georg Ehrke 2019-11-04 14:49:05 +00:00 committed by GitHub
commit 53f554b5f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 360 additions and 137 deletions

View File

@ -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,
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 = true
await this.$store.dispatch('saveCalendarObjectInstance', {
thisAndAllFuture,
calendarId: 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 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
}
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)
}
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()
})
.catch(() => {
vm.error = true
})
.finally(() => {
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 })
}
})
})
}
@ -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
if (recurrenceId === 'next') {
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.$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)
}
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 })
}
})
next()
.catch(() => {
this.error = true
})
.finally(() => {
this.isLoading = false
next()
})
}).catch(() => {
next(false)
})

View File

@ -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
*

View File

@ -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
},
/**