feat: add a setting for the default calendar url

Co-authored-by: Lukas Boersma <mail@lukas-boersma.com>
Co-authored-by: szaimen <szaimen@e.mail.de>
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
Lukas Boersma 2020-06-13 12:45:01 +02:00 committed by Richard Steinmetz
parent fd85b52787
commit 7a307bbcf0
No known key found for this signature in database
GPG Key ID: 27137D9E7D273FB2
8 changed files with 143 additions and 7 deletions

View File

@ -73,8 +73,9 @@
display: block;
}
}
&--timezone {
&--timezone,
&--default-calendar {
width: 100%;
.multiselect {

View File

@ -20,7 +20,7 @@
-->
<template>
<AppNavigationSettings exclude-click-outside-classes="import-modal"
<AppNavigationSettings :exclude-click-outside-selectors="['.vs__dropdown-menu', '.modal-wrapper']"
:name="settingsTitle">
<ul class="settings-fieldset-interior">
<SettingsImportSection :is-disabled="loadingCalendars" />
@ -71,6 +71,18 @@
label="label"
@option:selected="changeSlotDuration" />
</li>
<!-- TODO: remove version check once Nextcloud 28 is not supported anymore -->
<li v-if="currentUserPrincipal && defaultCalendarOptions.length > 1 && nextcloudVersion >= 29"
class="settings-fieldset-interior-item settings-fieldset-interior-item--default-calendar">
<label :for="defaultCalendarPickerId">
{{ $t('calendar', 'Default calendar for invitations and new events') }}
</label>
<CalendarPicker :value="defaultCalendar"
:calendars="defaultCalendarOptions"
:disabled="savingDefaultCalendarId"
:input-id="defaultCalendarPickerId"
@select-calendar="changeDefaultCalendar" />
</li>
<li class="settings-fieldset-interior-item settings-fieldset-interior-item--defaultReminder">
<label for="defaultReminder">{{ $t('calendar', 'Default reminder') }}</label>
<NcSelect :id="defaultReminder"
@ -124,6 +136,8 @@ import {
NcAppNavigationSettings as AppNavigationSettings,
NcSelect,
} from '@nextcloud/vue'
import CalendarPicker from '../Shared/CalendarPicker.vue'
import {
generateRemoteUrl,
generateUrl,
@ -156,6 +170,9 @@ import ClipboardArrowLeftOutline from 'vue-material-design-icons/ClipboardArrowL
import InformationVariant from 'vue-material-design-icons/InformationVariant.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import logger from '../../utils/logger.js'
import { randomId } from '../../utils/randomId.js'
export default {
name: 'Settings',
components: {
@ -171,6 +188,7 @@ export default {
ClipboardArrowLeftOutline,
InformationVariant,
OpenInNewIcon,
CalendarPicker,
},
props: {
loadingCalendars: {
@ -186,14 +204,18 @@ export default {
savingPopover: false,
savingSlotDuration: false,
savingDefaultReminder: false,
savingDefaultCalendarId: false,
savingWeekend: false,
savingWeekNumber: false,
savingDefaultCalendar: false,
displayKeyboardShortcuts: false,
defaultCalendarPickerId: randomId(),
}
},
computed: {
...mapGetters({
birthdayCalendar: 'hasBirthdayCalendar',
currentUserPrincipal: 'getCurrentUserPrincipal',
}),
...mapState({
eventLimit: state => state.settings.eventLimit,
@ -271,6 +293,28 @@ export default {
nextcloudVersion() {
return parseInt(OC.config.version.split('.')[0])
},
defaultCalendarOptions() {
return this.$store.state.calendars.calendars
.filter(calendar => !calendar.readOnly && !calendar.isSharedWithMe)
},
/**
* The default calendar for new events and inivitations
*
* @return {object|undefined} The default calendar or undefined if none is available
*/
defaultCalendar() {
const defaultCalendarUrl = this.currentUserPrincipal.scheduleDefaultCalendarUrl
const calendar = this.defaultCalendarOptions
.find(calendar => calendar.url === defaultCalendarUrl)
// If the default calendar is not or no longer available,
// pick the first calendar in the list of available calendars.
if (!calendar) {
return this.defaultCalendarOptions[0]
}
return calendar
},
},
methods: {
async toggleBirthdayEnabled() {
@ -396,6 +440,34 @@ export default {
this.savingDefaultReminder = false
}
},
/**
* Changes the default calendar for new events
*
* @param {object} selectedCalendar The new selected default calendar
*/
async changeDefaultCalendar(selectedCalendar) {
if (!selectedCalendar) {
return
}
this.savingDefaultCalendar = true
try {
await this.$store.dispatch('changePrincipalScheduleDefaultCalendarUrl', {
principal: this.currentUserPrincipal,
scheduleDefaultCalendarUrl: selectedCalendar.url,
})
} catch (error) {
logger.error('Error while changing default calendar', {
error,
calendarUrl: selectedCalendar.url,
selectedCalendar,
})
showError(this.$t('calendar', 'Failed to save default calendar'))
} finally {
this.savingDefaultCalendar = false
}
},
/**
* Copies the primary CalDAV url to the user's clipboard.
*/

View File

@ -1,6 +1,6 @@
<template>
<NcSelect label="id"
input-id="url"
:input-id="inputId"
:disabled="isDisabled"
:options="options"
:value="valueIds"
@ -25,6 +25,7 @@
<script>
import { NcSelect } from '@nextcloud/vue'
import CalendarPickerOption from './CalendarPickerOption.vue'
import { randomId } from '../../utils/randomId.js'
export default {
name: 'CalendarPicker',
@ -49,12 +50,20 @@ export default {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
inputId: {
type: String,
default: () => randomId(),
},
},
computed: {
isDisabled() {
// for pickers where multiple can be selected (zero or more) we don't want to disable the picker
// for calendars where only one calendar can be selected, disable if there are < 2
return this.multiple ? this.calendars.length < 1 : this.calendars.length < 2
return this.disabled || (this.multiple ? this.calendars.length < 1 : this.calendars.length < 2)
},
valueIds() {
if (Array.isArray(this.value)) {

View File

@ -65,6 +65,8 @@ const getDefaultPrincipalObject = (props) => Object.assign({}, {
isCalendarRoom: false,
// The id of the principal without prefix. e.g. userId / groupId / etc.
principalId: null,
// The url of the default calendar for invitations
scheduleDefaultCalendarUrl: null,
}, props)
/**
@ -80,6 +82,7 @@ const mapDavToPrincipal = (dav) => {
const emailAddress = dav.email
const displayname = dav.displayname
const scheduleDefaultCalendarUrl = dav.scheduleDefaultCalendarUrl
const isUser = dav.principalScheme.startsWith(PRINCIPAL_PREFIX_USER)
const isGroup = dav.principalScheme.startsWith(PRINCIPAL_PREFIX_GROUP)
@ -118,6 +121,7 @@ const mapDavToPrincipal = (dav) => {
isCalendarRoom,
principalId,
userId,
scheduleDefaultCalendarUrl,
})
}

View File

@ -352,8 +352,13 @@ const actions = {
vObject.undirtify()
}
const firstCalendar = context.getters.sortedCalendars[0].id
return Promise.resolve(mapCalendarJsToCalendarObject(calendar, firstCalendar))
const defaultCalendarUrl = context.getters.getCurrentUserPrincipal.scheduleDefaultCalendarUrl
const defaultCalendar = context.getters.getCalendarByUrl(defaultCalendarUrl)
return Promise.resolve(mapCalendarJsToCalendarObject(
calendar,
defaultCalendar?.id ?? context.getters.sortedCalendars[0].id,
))
},
/**

View File

@ -522,6 +522,14 @@ const getters = {
*/
getCalendarById: (state) => (calendarId) => state.calendarsById[calendarId],
/**
* Gets a calendar by its url
*
* @param {object} state the store data
* @return {function({String}): {Object}}
*/
getCalendarByUrl: (state) => (url) => state.calendars.find((calendar) => calendar.url === url),
/**
* Gets the contact's birthday calendar or null
*

View File

@ -66,6 +66,22 @@ const mutations = {
setCurrentUserPrincipal(state, { principalId }) {
state.currentUserPrincipal = principalId
},
/**
* Changes the schedule-default-calendar-URL of a principal
*
* @param {object} state The vuex state
* @param {object} data The destructuring object
* @param {object} data.principal The principal to modify
* @param {string} data.scheduleDefaultCalendarUrl The new schedule-default-calendar-URL
*/
changePrincipalScheduleDefaultCalendarUrl(state, { principal, scheduleDefaultCalendarUrl }) {
Vue.set(
state.principalsById[principal.id],
'scheduleDefaultCalendarUrl',
scheduleDefaultCalendarUrl,
)
},
}
const getters = {
@ -147,6 +163,25 @@ const actions = {
context.commit('setCurrentUserPrincipal', { principalId: principal.id })
logger.debug(`Current user principal is ${principal.url}`)
},
/**
* Change a principal's schedule-default-calendar-URL
*
* @param {object} context The vuex context
* @param {object} data The destructuring object
* @param {object} data.principal The principal to modify
* @param {string} data.scheduleDefaultCalendarUrl The new schedule-default-calendar-URL
* @return {Promise<void>}
*/
async changePrincipalScheduleDefaultCalendarUrl(context, { principal, scheduleDefaultCalendarUrl }) {
principal.dav.scheduleDefaultCalendarUrl = scheduleDefaultCalendarUrl
await principal.dav.update()
context.commit('changePrincipalScheduleDefaultCalendarUrl', {
principal,
scheduleDefaultCalendarUrl,
})
},
}
export default { state, mutations, getters, actions }

View File

@ -40,6 +40,7 @@ describe('Test suite: Principal model (models/principal.js)', () => {
isCalendarResource: false,
isCalendarRoom: false,
principalId: null,
scheduleDefaultCalendarUrl: null,
})
})
@ -63,6 +64,7 @@ describe('Test suite: Principal model (models/principal.js)', () => {
isCalendarRoom: false,
principalId: 'bar',
otherProp: 'foo',
scheduleDefaultCalendarUrl: null,
})
})