Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 6 additions & 66 deletions src/components/Editor/InvitationResponseButtons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,25 @@
v-if="!isAccepted"
variant="primary"
class="invitation-response-buttons__button"
:disabled="loading"
@click="accept">
{{ t('calendar', 'Accept') }}
</NcButton>
<NcButton
v-if="!isDeclined"
variant="error"
class="invitation-response-buttons__button"
:disabled="loading"
@click="decline">
{{ t('calendar', 'Decline') }}
</NcButton>
<template v-if="!isTentative">
<NcButton
v-if="!narrow"
class="invitation-response-buttons__button"
:disabled="loading"
@click="tentative">
{{ t('calendar', 'Tentative') }}
</NcButton>
<Actions v-else>
<ActionButton
:disabled="loading"
@click="tentative">
<template #icon>
<CalendarQuestionIcon :size="20" />
Expand All @@ -46,16 +42,12 @@
</template>

<script>
import { showError, showSuccess } from '@nextcloud/dialogs'
import {
NcActionButton as ActionButton,
NcActions as Actions,
NcButton,
} from '@nextcloud/vue'
import { mapStores } from 'pinia'
import CalendarQuestionIcon from 'vue-material-design-icons/CalendarQuestionOutline.vue'
import useCalendarObjectInstanceStore from '../../store/calendarObjectInstance.js'
import logger from '../../utils/logger.js'

export default {
name: 'InvitationResponseButtons',
Expand Down Expand Up @@ -88,14 +80,7 @@ export default {
},
},

data() {
return {
loading: false,
}
},

computed: {
...mapStores(useCalendarObjectInstanceStore),
isAccepted() {
return this.attendee.participationStatus === 'ACCEPTED'
},
Expand All @@ -110,61 +95,16 @@ export default {
},

methods: {
async accept() {
try {
await this.setParticipationStatus('ACCEPTED')
showSuccess(this.t('calendar', 'The invitation has been accepted successfully.'))
this.$emit('close')
} catch (e) {
showError(this.t('calendar', 'Failed to accept the invitation.'))
}
},

async decline() {
try {
await this.setParticipationStatus('DECLINED')
showSuccess(this.t('calendar', 'The invitation has been declined successfully.'))
this.$emit('close')
} catch (e) {
showError(this.t('calendar', 'Failed to decline the invitation.'))
}
accept() {
this.$emit('respond', 'ACCEPTED')
},

async tentative() {
try {
await this.setParticipationStatus('TENTATIVE')
showSuccess(this.t('calendar', 'Your participation has been marked as tentative.'))
this.$emit('close')
} catch (e) {
showError(this.t('calendar', 'Failed to set the participation status to tentative.'))
}
decline() {
this.$emit('respond', 'DECLINED')
},

/**
* Set the participation status and save the event
*
* @param {string} participationStatus The new participation status
* @return {Promise<void>}
*/
async setParticipationStatus(participationStatus) {
this.loading = true
try {
this.calendarObjectInstanceStore.changeAttendeesParticipationStatus({
attendee: this.attendee,
participationStatus,
})
// TODO: What about recurring events? Add new buttons like "Accept this and all future"?
// Currently, this will only accept a single occurrence.
await this.calendarObjectInstanceStore.saveCalendarObjectInstance({
thisAndAllFuture: false,
calendarId: this.calendarId,
})
} catch (error) {
logger.error('Failed to set participation status', { error, participationStatus })
throw error
} finally {
this.loading = false
}
tentative() {
this.$emit('respond', 'TENTATIVE')
},
},
}
Expand Down
25 changes: 24 additions & 1 deletion src/mixins/EditorMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,30 @@ export default {
this.requiresActionOnRouteLeave = false
this.closeEditor()
},

/**
* Set the participation status for an attendee and save the event
*
* @param {object} attendee The attendee object
* @param {string} participationStatus The new participation status (ACCEPTED, DECLINED, TENTATIVE)
* @return {Promise<void>}
*/
async saveParticipationStatus(attendee, participationStatus) {
try {
this.calendarObjectInstanceStore.changeAttendeesParticipationStatus({
attendee,
participationStatus,
})
// TODO: What about recurring events? Add new buttons like "Accept this and all future"?
// Currently, this will only accept a single occurrence.
await this.calendarObjectInstanceStore.saveCalendarObjectInstance({
thisAndAllFuture: false,
calendarId: this.calendarId,
})
} catch (error) {
logger.error('Failed to set participation status', { error, participationStatus })
throw error
}
},
/**
* Duplicates a calendar-object and saves it
*
Expand Down
124 changes: 121 additions & 3 deletions src/views/EditFull.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@
label-id="edit-full-modal"
:name="t('calendar', 'Edit event')"
:dark="false"
:no-close="true"
:no-close="operationInProgress"
@close="cancel(false)">
<NcButton class="calendar-edit-full__default-close" variant="tertiary" @click="cancel(false)">
<NcButton
class="calendar-edit-full__default-close"
variant="tertiary"
:disabled="operationInProgress"
@click="cancel(false)">
<template #icon>
<Close :size="20" />
</template>
Expand Down Expand Up @@ -162,7 +166,7 @@
:calendar-id="calendarId"
:narrow="true"
:grow-horizontally="true"
@close="closeEditorAndSkipAction" />
@respond="saveParticipationStatus" />
</div>

<div class="app-full-body">
Expand Down Expand Up @@ -325,13 +329,21 @@
:name="t('calendar', 'Discard changes?')"
:message="t('calendar', 'Are you sure you want to discard the changes made to this event?')"
:buttons="cancelButtons" />

<!-- Loading overlay - rendered last to appear on top -->
<div v-if="operationInProgress" class="edit-full__loading-overlay">
<NcLoadingIcon :size="64" />
<p>{{ operationMessage }}</p>
</div>
</NcModal>
</template>

<script>
import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
import IconDelete from '@mdi/svg/svg/delete.svg?raw'
import { Parameter } from '@nextcloud/calendar-js'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
import { generateUrl } from '@nextcloud/router'
import {
Expand All @@ -343,6 +355,7 @@
NcDialog,
NcEmptyContent,
NcListItemIcon,
NcLoadingIcon,
NcModal,
NcPopover,
} from '@nextcloud/vue'
Expand Down Expand Up @@ -392,6 +405,7 @@
NcEmptyContent,
NcModal,
NcListItemIcon,
NcLoadingIcon,
NcButton,
NcCheckboxRadioSwitch,
NcPopover,
Expand Down Expand Up @@ -447,6 +461,8 @@

showCancelDialog: false,
showFullModal: true,
operationInProgress: false,
operationMessage: '',
}
},

Expand Down Expand Up @@ -775,6 +791,77 @@

this.toggleAllDay()
},

/**
* Override method to add operation locking
*
* @param {boolean} thisAndAllFuture Whether to modify only this or this and all future occurrences
* @return {Promise<void>}
*/
async save(thisAndAllFuture = false) {
if (this.operationInProgress) {
return
}

this.operationInProgress = true
this.operationMessage = t('calendar', 'Saving event…')

try {
// Call the parent method from EditorMixin
await EditorMixin.methods.save.call(this, thisAndAllFuture)
} finally {
this.operationInProgress = false
this.operationMessage = ''
}
},

/**
* Override method to add operation locking
*
* @param {boolean} thisAndAllFuture Whether to delete only this or this and all future occurrences
* @return {Promise<void>}
*/
async delete(thisAndAllFuture = false) {
if (this.operationInProgress) {
return
}

this.operationInProgress = true
this.operationMessage = t('calendar', 'Deleting event…')

try {
await EditorMixin.methods.delete.call(this, thisAndAllFuture)
showSuccess(this.t('calendar', 'Event deleted successfully.'))
this.closeEditorAndSkipAction()
} catch (e) {
showError(this.t('calendar', 'Failed to delete event.'))
} finally {
this.operationInProgress = false
this.operationMessage = ''
}
},

/**
* Override method to add operation locking
*
* @param {string} participationStatus The participation status (ACCEPTED, DECLINED, TENTATIVE)
* @return {Promise<void>}
*/
async saveParticipationStatus(participationStatus) {
this.operationInProgress = true
this.operationMessage = t('calendar', 'Updating participation status…')

try {
await EditorMixin.methods.saveParticipationStatus.call(this, this.userAsAttendee, participationStatus)
showSuccess(this.t('calendar', 'Participation status updated successfully.'))
this.closeEditorAndSkipAction()
} catch (e) {
showError(this.t('calendar', 'Failed to update participation status.'))
} finally {
this.operationInProgress = false
this.operationMessage = ''
}
},
},
}
</script>
Expand Down Expand Up @@ -1084,4 +1171,35 @@
height: auto;
border-radius: var(--border-radius);
}

.edit-full__loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(var(--backdrop-color), 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: calc(var(--default-grid-baseline) * 4);
z-index: 10000;

p {
font-size: calc(var(--default-grid-baseline) * 4);
font-weight: 500;
color: var(--color-text-primary);
margin: 0;
}
}


Check failure on line 1197 in src/views/EditFull.vue

View workflow job for this annotation

GitHub Actions / NPM lint

More than 1 blank line not allowed

.app-full__header__top-close-icon {
&.disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
</style>
Loading
Loading