Skip to content

Commit

Permalink
feat: add a setting for the default calendar url
Browse files Browse the repository at this point in the history
Co-authored-by: Lukas Boersma <[email protected]>
Co-authored-by: szaimen <[email protected]>
Signed-off-by: Richard Steinmetz <[email protected]>
  • Loading branch information
2 people authored and st3iny committed Feb 28, 2024
1 parent 27a33d8 commit 3bfa8c8
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 6 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
69 changes: 68 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,17 @@
label="label"
@option:selected="changeSlotDuration" />
</li>
<li v-if="currentUserPrincipal && defaultCalendarOptions.length > 1"

Check warning on line 74 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L74

Added line #L74 was not covered by tests
class="settings-fieldset-interior-item settings-fieldset-interior-item--default-calendar">
<!-- TODO: link label and select -->
<label>

Check warning on line 77 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L76-L77

Added lines #L76 - L77 were not covered by tests
{{ $t('calendar', 'Default calendar for invitations and new events') }}
</label>
<CalendarPicker :value="defaultCalendar"

Check warning on line 80 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L79-L80

Added lines #L79 - L80 were not covered by tests
:calendars="defaultCalendarOptions"
:disabled="savingDefaultCalendarId"
@select-calendar="changeDefaultCalendar" />

Check warning on line 83 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L82-L83

Added lines #L82 - L83 were not covered by tests
</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 +135,8 @@ import {
NcAppNavigationSettings as AppNavigationSettings,
NcSelect,
} from '@nextcloud/vue'
import CalendarPicker from '../Shared/CalendarPicker.vue'

Check warning on line 138 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L138

Added line #L138 was not covered by tests
import {
generateRemoteUrl,
generateUrl,
Expand Down Expand Up @@ -155,6 +168,7 @@ import { getDefaultAlarms } from '../../defaults/defaultAlarmProvider.js'
import ClipboardArrowLeftOutline from 'vue-material-design-icons/ClipboardArrowLeftOutline.vue'
import InformationVariant from 'vue-material-design-icons/InformationVariant.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import logger from '../../utils/logger.js'
export default {
name: 'Settings',
Expand All @@ -171,6 +185,7 @@ export default {
ClipboardArrowLeftOutline,
InformationVariant,
OpenInNewIcon,
CalendarPicker,
},
props: {
loadingCalendars: {
Expand All @@ -186,14 +201,17 @@ export default {
savingPopover: false,
savingSlotDuration: false,
savingDefaultReminder: false,
savingDefaultCalendarId: false,
savingWeekend: false,
savingWeekNumber: false,
savingDefaultCalendar: false,
displayKeyboardShortcuts: false,
}
},
computed: {
...mapGetters({
birthdayCalendar: 'hasBirthdayCalendar',
currentUserPrincipal: 'getCurrentUserPrincipal',

Check warning on line 214 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L214

Added line #L214 was not covered by tests
}),
...mapState({
eventLimit: state => state.settings.eventLimit,
Expand Down Expand Up @@ -272,6 +290,27 @@ export default {
nextcloudVersion() {
return parseInt(OC.config.version.split('.')[0])
},
defaultCalendarOptions() {
return this.$store.state.calendars.calendars.filter(calendar => !calendar.readOnly)
},

Check warning on line 295 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L295

Added line #L295 was not covered by tests
/**
* The default calendar for new events and inivitations
*
* @return {object|undefined} The default calendar or undefined if none is available
*/

Check warning on line 300 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L300

Added line #L300 was not covered by tests
defaultCalendar() {
const defaultCalendarUrl = this.currentUserPrincipal.scheduleDefaultCalendarUrl

Check warning on line 302 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L302

Added line #L302 was not covered by tests
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]
}

Check warning on line 311 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L308-L311

Added lines #L308 - L311 were not covered by tests
return calendar
},

Check warning on line 313 in src/components/AppNavigation/Settings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L313

Added line #L313 was not covered by tests
},
methods: {
async toggleBirthdayEnabled() {
Expand Down Expand Up @@ -397,6 +436,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
6 changes: 5 additions & 1 deletion src/components/Shared/CalendarPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,16 @@ export default {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,

Check warning on line 53 in src/components/Shared/CalendarPicker.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Shared/CalendarPicker.vue#L52-L53

Added lines #L52 - L53 were not covered by tests
default: false,
},
},
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)

Check warning on line 356 in src/store/calendarObjects.js

View check run for this annotation

Codecov / codecov/patch

src/store/calendarObjects.js#L355-L356

Added lines #L355 - L356 were not covered by tests

return Promise.resolve(mapCalendarJsToCalendarObject(

Check warning on line 358 in src/store/calendarObjects.js

View check run for this annotation

Codecov / codecov/patch

src/store/calendarObjects.js#L358

Added line #L358 was not covered by tests
calendar,
defaultCalendar?.id ?? context.getters.sortedCalendars[0].id,

Check warning on line 360 in src/store/calendarObjects.js

View check run for this annotation

Codecov / codecov/patch

src/store/calendarObjects.js#L360

Added line #L360 was not covered by tests
))
},

/**
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),

Check warning on line 531 in src/store/calendars.js

View check run for this annotation

Codecov / codecov/patch

src/store/calendars.js#L531

Added line #L531 was not covered by tests

/**
* 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(

Check warning on line 79 in src/store/principals.js

View check run for this annotation

Codecov / codecov/patch

src/store/principals.js#L78-L79

Added lines #L78 - L79 were not covered by tests
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

Check warning on line 177 in src/store/principals.js

View check run for this annotation

Codecov / codecov/patch

src/store/principals.js#L176-L177

Added lines #L176 - L177 were not covered by tests

await principal.dav.update()
context.commit('changePrincipalScheduleDefaultCalendarUrl', {

Check warning on line 180 in src/store/principals.js

View check run for this annotation

Codecov / codecov/patch

src/store/principals.js#L179-L180

Added lines #L179 - L180 were not covered by tests
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 3bfa8c8

Please sign in to comment.