Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a setting for the default calendar url #5790

Merged
merged 1 commit into from
Feb 29, 2024
Merged
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
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">

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L78 - L79 were not covered by tests
<CalendarPicker :value="defaultCalendar"
:calendars="defaultCalendarOptions"
:disabled="savingDefaultCalendarId"

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L81 - L82 were not covered by tests
:input-id="defaultCalendarPickerId"
@select-calendar="changeDefaultCalendar" />
</li>

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L84-L85

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L139

Added line #L139 was not covered by tests

import {
generateRemoteUrl,
generateUrl,
Expand Down Expand Up @@ -156,6 +170,9 @@
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'

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L173-L175

Added lines #L173 - L175 were not covered by tests
export default {
name: 'Settings',
components: {
Expand All @@ -171,6 +188,7 @@
ClipboardArrowLeftOutline,
InformationVariant,
OpenInNewIcon,
CalendarPicker,

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L191

Added line #L191 was not covered by tests
},
props: {
loadingCalendars: {
Expand All @@ -186,14 +204,18 @@
savingPopover: false,
savingSlotDuration: false,
savingDefaultReminder: false,
savingDefaultCalendarId: false,

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L207

Added line #L207 was not covered by tests
savingWeekend: false,
savingWeekNumber: false,
savingDefaultCalendar: false,

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L210

Added line #L210 was not covered by tests
displayKeyboardShortcuts: false,
defaultCalendarPickerId: randomId(),

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L212

Added line #L212 was not covered by tests
}
},
computed: {
...mapGetters({
birthdayCalendar: 'hasBirthdayCalendar',
currentUserPrincipal: 'getCurrentUserPrincipal',
}),
...mapState({
eventLimit: state => state.settings.eventLimit,
Expand Down Expand Up @@ -271,6 +293,28 @@
nextcloudVersion() {
return parseInt(OC.config.version.split('.')[0])
},
defaultCalendarOptions() {

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L296

Added line #L296 was not covered by tests
return this.$store.state.calendars.calendars
.filter(calendar => !calendar.readOnly && !calendar.isSharedWithMe)
},
/**
* The default calendar for new events and inivitations

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L301

Added line #L301 was not covered by tests
*
* @return {object|undefined} The default calendar or undefined if none is available

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L303

Added line #L303 was not covered by tests
*/
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) {

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L309-L312

Added lines #L309 - L312 were not covered by tests
return this.defaultCalendarOptions[0]
}

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

View check run for this annotation

Codecov / codecov/patch

src/components/AppNavigation/Settings.vue#L314-L315

Added lines #L314 - L315 were not covered by tests
return calendar
},
},
methods: {
async toggleBirthdayEnabled() {
Expand Down Expand Up @@ -396,6 +440,34 @@
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 @@
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,

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

View check run for this annotation

Codecov / codecov/patch

src/components/Shared/CalendarPicker.vue#L55

Added line #L55 was not covered by tests
},
inputId: {
type: String,

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

View check run for this annotation

Codecov / codecov/patch

src/components/Shared/CalendarPicker.vue#L57-L58

Added lines #L57 - L58 were not covered by tests
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 @@
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 @@
*/
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 @@
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 @@
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
Loading