Skip to content

Commit

Permalink
Merge pull request #5790 from nextcloud/feat/default-calendar
Browse files Browse the repository at this point in the history
feat: add a setting for the default calendar url
  • Loading branch information
st3iny authored Feb 29, 2024
2 parents fd85b52 + 7a307bb commit 21e0c08
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 7 deletions.
5 changes: 3 additions & 2 deletions css/app-settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@
display: block;
}
}

&--timezone {

&--timezone,
&--default-calendar {
width: 100%;

.multiselect {
Expand Down
74 changes: 73 additions & 1 deletion src/components/AppNavigation/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -124,6 +136,8 @@ import {
NcAppNavigationSettings as AppNavigationSettings,
NcSelect,
} from '@nextcloud/vue'
import CalendarPicker from '../Shared/CalendarPicker.vue'
import {
generateRemoteUrl,
generateUrl,
Expand Down Expand Up @@ -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: {
Expand All @@ -171,6 +188,7 @@ export default {
ClipboardArrowLeftOutline,
InformationVariant,
OpenInNewIcon,
CalendarPicker,
},
props: {
loadingCalendars: {
Expand All @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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.
*/
Expand Down
13 changes: 11 additions & 2 deletions src/components/Shared/CalendarPicker.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<NcSelect label="id"
input-id="url"
:input-id="inputId"
:disabled="isDisabled"
:options="options"
:value="valueIds"
Expand All @@ -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',
Expand All @@ -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)) {
Expand Down
4 changes: 4 additions & 0 deletions src/models/principal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

/**
Expand All @@ -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)
Expand Down Expand Up @@ -118,6 +121,7 @@ const mapDavToPrincipal = (dav) => {
isCalendarRoom,
principalId,
userId,
scheduleDefaultCalendarUrl,
})
}

Expand Down
9 changes: 7 additions & 2 deletions src/store/calendarObjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
))
},

/**
Expand Down
8 changes: 8 additions & 0 deletions src/store/calendars.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
35 changes: 35 additions & 0 deletions src/store/principals.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 }
2 changes: 2 additions & 0 deletions tests/javascript/unit/models/principal.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('Test suite: Principal model (models/principal.js)', () => {
isCalendarResource: false,
isCalendarRoom: false,
principalId: null,
scheduleDefaultCalendarUrl: null,
})
})

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

Expand Down

0 comments on commit 21e0c08

Please sign in to comment.