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

Commit c44edb07 authored by Richard Steinmetz's avatar Richard Steinmetz Committed by Anna Larch
Browse files

Show modal when dragging recurrences

parent 90c7a33e
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -2,8 +2,8 @@
 * Calendar App
 *
 * @copyright 2021 Richard Steinmetz <richard@steinmetz.cloud>
 *
 * @author Richard Steinmetz <richard@steinmetz.cloud>
 * @license AGPL-3.0-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -91,5 +91,15 @@
		display: flex;
		justify-content: center;
		gap: 0 10px;
 */

.drag-recurrence-modal {
	&__content {
		padding: 15px;

		&__buttons {
			display: flex;
			flex-direction: column;
		}
	}
}
+92 −0
Original line number Diff line number Diff line
<!--
  - @copyright Copyright (c) 2021 Richard Steinmetz <richard@steinmetz.cloud>
  -
  - @author Richard Steinmetz <richard@steinmetz.cloud>
  -
  - @license AGPL-3.0-or-later
  -
  - This program is free software: you can redistribute it and/or modify
  - it under the terms of the GNU Affero General Public License as
  - published by the Free Software Foundation, either version 3 of the
  - License, or (at your option) any later version.
  -
  - This program is distributed in the hope that it will be useful,
  - but WITHOUT ANY WARRANTY; without even the implied warranty of
  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  - GNU Affero General Public License for more details.
  -
  - You should have received a copy of the GNU Affero General Public License
  - along with this program. If not, see <http://www.gnu.org/licenses/>.
  -->

<template>
	<Modal v-if="show" @close="onClose">
		<div class="drag-recurrence-modal__content">
			<SaveButtons
				:can-create-recurrence-exception="canCreateRecurrenceException"
				:force-this-and-all-future="forceThisAndAllFuture"
				:is-new="false"
				class="drag-recurrence-modal__content__buttons"
				@save-this-only="saveAndLeave(false)"
				@save-this-and-all-future="saveAndLeave(true)" />
		</div>
	</Modal>
</template>

<script>
import Modal from '@nextcloud/vue/dist/Components/Modal'
import SaveButtons from './Editor/SaveButtons'
import { mapState } from 'vuex'

export default {
	name: 'DragRecurrenceModal',
	components: {
		Modal,
		SaveButtons,
	},
	data() {
		return {
			isLoading: false,
		}
	},
	computed: {
		...mapState({
			show: state => state.dragRecurrenceModal.show,
			resolve: state => state.dragRecurrenceModal.resolve,
			reject: state => state.dragRecurrenceModal.reject,
			eventComponent: state => state.dragRecurrenceModal.eventComponent,
		}),
		/**
		 * @return {boolean}
		 */
		canCreateRecurrenceException() {
			if (!this.eventComponent) {
				return false
			}

			return this.eventComponent.canCreateRecurrenceExceptions()
		},
		/**
		 * @return {boolean}
		 */
		forceThisAndAllFuture() {
			if (!this.eventComponent) {
				return false
			}

			return this.eventComponent.isMasterItem() || this.eventComponent.isExactForkOfPrimary
		},
	},
	methods: {
		onClose() {
			this.reject('closedByUser')
		},
		/**
		 * @param {boolean} thisAndAllFuture Also update all future instances
		 */
		async saveAndLeave(thisAndAllFuture) {
			this.resolve(thisAndAllFuture || this.forceThisAndAllFuture)
		},
	},
}
</script>
+33 −22
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@
 *
 * @author Georg Ehrke <oc.list@georgehrke.com>
 *
 * @author Richard Steinmetz <richard@steinmetz.cloud>
 *
 * @license AGPL-3.0-or-later
 *
 * This program is free software: you can redistribute it and/or modify
@@ -22,7 +24,6 @@
import { getDurationValueFromFullCalendarDuration } from '../duration'
import getTimezoneManager from '../../services/timezoneDataProviderService'
import logger from '../../utils/logger.js'
import { getObjectAtRecurrenceId } from '../../utils/calendarObject.js'

/**
 * Returns a function to drop an event at a different position
@@ -48,25 +49,23 @@ export default function(store, fcAPI) {
			return
		}

		let objects
		try {
			const objectId = event.extendedProps.objectId
			const recurrenceId = event.extendedProps.recurrenceId
		const recurrenceIdDate = new Date(recurrenceId * 1000)

		let calendarObject
		try {
			calendarObject = await store.dispatch('getEventByObjectId', { objectId })
			objects = await store.dispatch('getCalendarObjectInstanceByObjectIdAndRecurrenceId', {
				objectId,
				recurrenceId,
			})
		} catch (error) {
			console.debug(error)
			store.commit('resetCalendarObjectInstanceObjectIdAndRecurrenceId')
			logger.error('Recurrence was not found', { error })
			revert()
			return
		}

		const eventComponent = getObjectAtRecurrenceId(calendarObject, recurrenceIdDate)
		if (!eventComponent) {
			console.debug('Recurrence-id not found')
			revert()
			return
		}
		const { calendarObject, calendarObjectInstance } = objects
		const eventComponent = calendarObjectInstance.eventComponent

		try {
			// shiftByDuration may throw exceptions in certain cases
@@ -75,25 +74,37 @@ export default function(store, fcAPI) {
			store.commit('resetCalendarObjectToDav', {
				calendarObject,
			})
			console.debug(error)
			store.commit('resetCalendarObjectInstanceObjectIdAndRecurrenceId')
			logger.error('Failed to shift event', { error })
			revert()
			return
		}

		if (eventComponent.canCreateRecurrenceExceptions()) {
			eventComponent.createRecurrenceException()
		try {
			// Show a modal to let the user decide whether to update this or all future instances.
			// Non-recurring events or recurrence exceptions can just be dropped and don't require
			// extra user interaction.
			let thisAndAllFuture = false
			if (eventComponent.isPartOfRecurrenceSet() && eventComponent.canCreateRecurrenceExceptions()) {
				thisAndAllFuture = await store.dispatch('showDragRecurrenceModal', {
					eventComponent,
				})
			}

		try {
			await store.dispatch('updateCalendarObject', {
				calendarObject,
			await store.dispatch('saveCalendarObjectInstance', {
				thisAndAllFuture,
				calendarId: calendarObject.calendarId,
			})
		} catch (error) {
			store.commit('resetCalendarObjectToDav', {
				calendarObject,
			})
			console.debug(error)
			if (error !== 'closedByUser') {
				logger.error('Could not drop event', { error })
			}
			revert()
		} finally {
			store.commit('resetCalendarObjectInstanceObjectIdAndRecurrenceId')
		}
	}
}
+8 −0
Original line number Diff line number Diff line
@@ -1536,7 +1536,15 @@ const actions = {
	async saveCalendarObjectInstance({ state, dispatch, commit }, { thisAndAllFuture, calendarId }) {
		const eventComponent = state.calendarObjectInstance.eventComponent
		const calendarObject = state.calendarObject
		await dispatch('saveCalendarObject', {
			thisAndAllFuture,
			calendarId,
			eventComponent,
			calendarObject,
		})
	},

	async saveCalendarObject({ dispatch, commit }, { thisAndAllFuture, calendarId, calendarObject, eventComponent }) {
		if (eventComponent.isDirty()) {
			const isForkedItem = eventComponent.primaryItem !== null
			let original = null
+86 −0
Original line number Diff line number Diff line
/**
 * @copyright Copyright (c) 2021 Richard Steinmetz <richard@steinmetz.cloud>
 *
 * @author Richard Steinmetz <richard@steinmetz.cloud>
 *
 * @license AGPL-3.0-or-later
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

const state = {
	show: false,
	resolve: null,
	reject: null,
	eventComponent: null,
}

const mutations = {
	/**
	 * Show the drag recurrence modal and save the promise callbacks for the modal
	 *
	 * @param {object} state The Vuex state
	 * @param {object} data The destructuring object
	 * @param {function(boolean): void} data.resolve Call to save the current drag process. Specify thisAndAllFuture via the first argument.
	 * @param {function(any=): void} data.reject Call to cancel the current drag process. Optionally pass an error.
	 * @param data.eventComponent
	 */
	showDragRecurrenceModal(state, { resolve, reject, eventComponent }) {
		state.show = true
		state.resolve = resolve
		state.reject = reject
		state.eventComponent = eventComponent
	},

	/**
	 * Hide the drag recurrence modal but leave the promise callbacks intact to prevent a deadlock
	 *
	 * @param {object} state The Vuex state
	 */
	hideDragRecurrenceModal(state) {
		state.show = false
	},
}

const getters = {}

const actions = {
	/**
	 * Show the drag recurrence modal and save the promise callbacks for the modal
	 *
	 * Automatically hides it after the promise is resolved or rejected (finally)
	 *
	 * @param {object} context The destructuring object for Vuex
	 * @param {Function} context.commit The Vuex commit function
	 * @param {object} data The destructuring object
	 * @param {object} data.eventComponent The event component to pass on to the modal
	 * @return {Promise<boolean>} Will resolve with thisAndAllFuture
	 */
	async showDragRecurrenceModal({ commit }, { eventComponent }) {
		try {
			return await new Promise((resolve, reject) => {
				commit('showDragRecurrenceModal', {
					resolve,
					reject,
					eventComponent,
				})
			})
		} finally {
			commit('hideDragRecurrenceModal')
		}
	},
}

export default { state, mutations, getters, actions }
Loading