diff --git a/css/app-settings.scss b/css/app-settings.scss index bec64f9129..905d8efcdf 100644 --- a/css/app-settings.scss +++ b/css/app-settings.scss @@ -55,7 +55,8 @@ } &--slotDuration, - &--timezone { + &--timezone, + &--default-calendar { width: 100%; .multiselect { diff --git a/lib/Controller/PublicViewController.php b/lib/Controller/PublicViewController.php index 2ace63956e..31bf3fba72 100644 --- a/lib/Controller/PublicViewController.php +++ b/lib/Controller/PublicViewController.php @@ -120,6 +120,7 @@ private function publicIndex(string $token, $defaultSkipPopover = $this->config->getAppValue($this->appName, 'skipPopover', 'yes'); $defaultTimezone = $this->config->getAppValue($this->appName, 'timezone', 'automatic'); $defaultSlotDuration = $this->config->getAppValue($this->appName, 'slotDuration', '00:30:00'); + $defaultCalendarId = $this->config->getAppValue($this->appName, 'defaultCalendarId', 'none'); $defaultShowTasks = $this->config->getAppValue($this->appName, 'showTasks', 'yes'); $appVersion = $this->config->getAppValue($this->appName, 'installed_version'); @@ -134,6 +135,7 @@ private function publicIndex(string $token, $this->initialStateService->provideInitialState($this->appName, 'talk_enabled', false); $this->initialStateService->provideInitialState($this->appName, 'timezone', $defaultTimezone); $this->initialStateService->provideInitialState($this->appName, 'slot_duration', $defaultSlotDuration); + $this->initialStateService->provideInitialState($this->appName, 'default_calendar_id', $defaultCalendarId); $this->initialStateService->provideInitialState($this->appName, 'show_tasks', $defaultShowTasks === 'yes'); $this->initialStateService->provideInitialState($this->appName, 'tasks_enabled', false); diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index e31f605346..96726266cb 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -87,6 +87,8 @@ public function setConfig(string $key, return $this->setEventLimit($value); case 'slotDuration': return $this->setSlotDuration($value); + case 'defaultCalendarId': + return $this->setDefaultCalendarId($value); case 'showTasks': return $this->setShowTasks($value); default: @@ -310,4 +312,25 @@ private function setSlotDuration(string $value):JSONResponse { return new JSONResponse(); } + + /** + * sets defaultCalendarId for user + * + * @param string $value User-selected option for default calendar when creating new events + * @return JSONResponse + */ + private function setDefaultCalendarId(string $value):JSONResponse { + try { + $this->config->setUserValue( + $this->userId, + $this->appName, + 'defaultCalendarId', + $value + ); + } catch (\Exception $e) { + return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + return new JSONResponse(); + } } diff --git a/lib/Controller/ViewController.php b/lib/Controller/ViewController.php index 69346c649d..38e712a92f 100644 --- a/lib/Controller/ViewController.php +++ b/lib/Controller/ViewController.php @@ -94,6 +94,7 @@ public function index():TemplateResponse { $defaultSkipPopover = $this->config->getAppValue($this->appName, 'skipPopover', 'no'); $defaultTimezone = $this->config->getAppValue($this->appName, 'timezone', 'automatic'); $defaultSlotDuration = $this->config->getAppValue($this->appName, 'slotDuration', '00:30:00'); + $defaultCalendarIdGlobal = $this->config->getAppValue($this->appName, 'defaultCalendarId', ''); $defaultShowTasks = $this->config->getAppValue($this->appName, 'showTasks', 'yes'); $appVersion = $this->config->getAppValue($this->appName, 'installed_version'); @@ -105,6 +106,7 @@ public function index():TemplateResponse { $skipPopover = $this->config->getUserValue($this->userId, $this->appName, 'skipPopover', $defaultSkipPopover) === 'yes'; $timezone = $this->config->getUserValue($this->userId, $this->appName, 'timezone', $defaultTimezone); $slotDuration = $this->config->getUserValue($this->userId, $this->appName, 'slotDuration', $defaultSlotDuration); + $defaultCalendarIdUser = $this->config->getUserValue($this->userId, $this->appName, 'defaultCalendarId', $defaultCalendarId); $showTasks = $this->config->getUserValue($this->userId, $this->appName, 'showTasks', $defaultShowTasks) === 'yes'; $talkEnabled = $this->appManager->isEnabledForUser('spreed'); @@ -120,6 +122,7 @@ public function index():TemplateResponse { $this->initialStateService->provideInitialState($this->appName, 'talk_enabled', $talkEnabled); $this->initialStateService->provideInitialState($this->appName, 'timezone', $timezone); $this->initialStateService->provideInitialState($this->appName, 'slot_duration', $slotDuration); + $this->initialStateService->provideInitialState($this->appName, 'default_calendar_id', $defaultCalendarIdUser); $this->initialStateService->provideInitialState($this->appName, 'show_tasks', $showTasks); $this->initialStateService->provideInitialState($this->appName, 'tasks_enabled', $tasksEnabled); diff --git a/src/components/AppNavigation/Settings.vue b/src/components/AppNavigation/Settings.vue index d493ca3693..b0de63ffd4 100644 --- a/src/components/AppNavigation/Settings.vue +++ b/src/components/AppNavigation/Settings.vue @@ -76,6 +76,16 @@ @select="changeSlotDuration" /> +
  • + +
  • {{ $t('calendar', 'Copy primary CalDAV address') }} @@ -100,6 +110,8 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox' import AppNavigationSettings from '@nextcloud/vue/dist/Components/AppNavigationSettings' import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' +import CalendarPicker from '../Shared/CalendarPicker.vue' + import { generateRemoteUrl, } from '@nextcloud/router' @@ -129,6 +141,7 @@ export default { Multiselect, SettingsImportSection, SettingsTimezoneSelect, + CalendarPicker, }, props: { loadingCalendars: { @@ -143,6 +156,7 @@ export default { savingTasks: false, savingPopover: false, savingSlotDuration: false, + savingDefaultCalendarId: false, savingWeekend: false, savingWeekNumber: false, displayKeyboardShortcuts: false, @@ -161,6 +175,7 @@ export default { slotDuration: state => state.settings.slotDuration, timezone: state => state.settings.timezone, locale: (state) => state.settings.momentLocale, + defaultCalendarId: state => state.settings.defaultCalendarId, }), isBirthdayCalendarDisabled() { return this.savingBirthdayCalendar || this.loadingCalendars @@ -204,6 +219,28 @@ export default { selectedDurationOption() { return this.slotDurationOptions.find(o => o.value === this.slotDuration) }, + defaultCalendarOptions() { + return this.$store.state.calendars.calendars.filter(o => !o.readOnly) + }, + defaultCalendar() { + var calendar = this.defaultCalendarOptions.find(o => o.id === this.defaultCalendarId) + // If the default calendar is not or no longer available, + // pick the first calendar in the list of available calendars. + if(calendar === undefined) { + calendar = this.defaultCalendarOptions[0] + } + if(calendar === undefined) { + // Create a dummy calendar object so that the calendarpicker + // will not complain when no calendar is available. + calendar = { + color: '#000', + displayName: 'No calendar', + owner: '', + isSharedWithMe: false, + } + } + return calendar + }, }, methods: { async toggleBirthdayEnabled() { @@ -305,6 +342,28 @@ export default { this.savingSlotDuration = 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('setDefaultCalendarId', { + calendarId: selectedCalendar.id, + }) + this.savingDefaultCalendar = false + } catch (error) { + console.error(error) + showError(this.$t('calendar', 'New setting was not saved successfully.')) + this.savingDefaultCalendar = false + } + }, /** * Copies the primary CalDAV url to the user's clipboard. */ diff --git a/src/components/Shared/CalendarPicker.vue b/src/components/Shared/CalendarPicker.vue index d951791cb8..923fa43ff6 100644 --- a/src/components/Shared/CalendarPicker.vue +++ b/src/components/Shared/CalendarPicker.vue @@ -2,7 +2,7 @@ @@ -37,10 +37,14 @@ export default { type: Boolean, default: false, }, + disabled: { + type: Boolean, + default: false, + }, }, computed: { - isDisabled() { - return this.calendars.length < 2 + isMultiselectDisabled() { + return this.disabled || this.calendars.length < 2 }, }, methods: { diff --git a/src/mixins/EditorMixin.js b/src/mixins/EditorMixin.js index 939e8ab373..cb0a8437db 100644 --- a/src/mixins/EditorMixin.js +++ b/src/mixins/EditorMixin.js @@ -649,9 +649,10 @@ export default { const start = parseInt(to.params.dtstart, 10) const end = parseInt(to.params.dtend, 10) const timezoneId = vm.$store.getters.getResolvedTimezone + const calendarId = vm.$store.state.settings.defaultCalendarId try { - await vm.$store.dispatch('getCalendarObjectInstanceForNewEvent', { isAllDay, start, end, timezoneId }) + await vm.$store.dispatch('getCalendarObjectInstanceForNewEvent', { isAllDay, start, end, timezoneId, calendarId }) vm.calendarId = vm.calendarObject.calendarId } catch (error) { console.debug(error) diff --git a/src/store/calendarObjectInstance.js b/src/store/calendarObjectInstance.js index 1781becb31..1aa1da0eaa 100644 --- a/src/store/calendarObjectInstance.js +++ b/src/store/calendarObjectInstance.js @@ -1413,7 +1413,7 @@ const actions = { * @param {String} data.timezoneId The timezoneId of the new event * @returns {Promise<{calendarObject: Object, calendarObjectInstance: Object}>} */ - async getCalendarObjectInstanceForNewEvent({ state, dispatch, commit }, { isAllDay, start, end, timezoneId }) { + async getCalendarObjectInstanceForNewEvent({ state, dispatch, commit }, { isAllDay, start, end, timezoneId, calendarId }) { if (state.isNew === true) { return Promise.resolve({ calendarObject: state.calendarObject, @@ -1421,7 +1421,7 @@ const actions = { }) } - const calendarObject = await dispatch('createNewEvent', { start, end, isAllDay, timezoneId }) + const calendarObject = await dispatch('createNewEvent', { start, end, isAllDay, timezoneId, calendarId }) const startDate = new Date(start * 1000) const eventComponent = getObjectAtRecurrenceId(calendarObject, startDate) const calendarObjectInstance = mapEventComponentToEventObject(eventComponent) diff --git a/src/store/calendarObjects.js b/src/store/calendarObjects.js index 4c5d9cca4a..78e2eeda4e 100644 --- a/src/store/calendarObjects.js +++ b/src/store/calendarObjects.js @@ -312,7 +312,7 @@ const actions = { * @param {Boolean} data.isAllDay foo * @returns {Promise} */ - createNewEvent(context, { start, end, timezoneId, isAllDay }) { + createNewEvent(context, { start, end, timezoneId, isAllDay, calendarId }) { const timezoneManager = getTimezoneManager() const timezone = timezoneManager.getTimezoneForId(timezoneId) @@ -336,8 +336,7 @@ const actions = { vObject.undirtify() } - const firstCalendar = context.getters.sortedCalendars[0].id - return Promise.resolve(mapCalendarJsToCalendarObject(calendar, firstCalendar)) + return Promise.resolve(mapCalendarJsToCalendarObject(calendar, calendarId || context.getters.sortedCalendars[0].id)) }, /** diff --git a/src/store/settings.js b/src/store/settings.js index 782a630342..834d888d38 100644 --- a/src/store/settings.js +++ b/src/store/settings.js @@ -39,6 +39,7 @@ const state = { talkEnabled: false, tasksEnabled: false, timezone: null, + defaultCalendarId: null, } const mutations = { @@ -110,6 +111,17 @@ const mutations = { state.timezone = timezoneId }, + /** + * Updates the user's default calendar + * + * @param {Object} state The Vuex state + * @param {Object} data The destructuring object + * @param {String} data.calendarId The new calendar id + */ + setDefaultCalendarId(state, { calendarId }) { + state.defaultCalendarId = calendarId + }, + /** * Initialize settings * @@ -130,6 +142,7 @@ const mutations = { state.talkEnabled = settings.talkEnabled state.tasksEnabled = settings.tasksEnabled state.timezone = settings.timezone + state.defaultCalendarId = settings.defaultCalendarId }, /** @@ -294,6 +307,24 @@ const actions = { context.commit('setSlotDuration', { slotDuration }) }, + /** + * Updates the user's default calendar for new events + * + * @param {Object} context The Vuex context + * @param {Object} data The destructuring object + * @param {String} data.slotDuration The id of the new default calendar + */ + async setDefaultCalendarId(context, { calendarId }) { + if (context.state.defaultCalendarId === calendarId) { + return + } + + await HttpClient.post(getLinkToConfig('defaultCalendarId'), { + value: calendarId, + }) + context.commit('setDefaultCalendarId', { calendarId }) + }, + /** * Updates the user's timezone * diff --git a/src/views/Calendar.vue b/src/views/Calendar.vue index d1d4b35ff0..5800db7e83 100644 --- a/src/views/Calendar.vue +++ b/src/views/Calendar.vue @@ -44,6 +44,7 @@ :header="showHeader" :height="windowResize" :slot-duration="slotDuration" + :default-calendar-id="defaultCalendarId" :week-numbers="showWeekNumbers" :weekends="showWeekends" :event-sources="eventSources" @@ -171,6 +172,7 @@ export default { showWeekends: state => state.settings.showWeekends, showWeekNumbers: state => state.settings.showWeekNumbers, slotDuration: state => state.settings.slotDuration, + defaultCalendarId: state => state.settings.defaultCalendarId, showTasks: state => state.settings.showTasks, timezone: state => state.settings.timezone, modificationCount: state => state.calendarObjects.modificationCount, @@ -303,6 +305,7 @@ export default { showWeekNumbers: loadState('calendar', 'show_week_numbers'), skipPopover: loadState('calendar', 'skip_popover'), slotDuration: loadState('calendar', 'slot_duration'), + defaultCalendarId: loadState('calendar', 'default_calendar_id'), talkEnabled: loadState('calendar', 'talk_enabled'), tasksEnabled: loadState('calendar', 'tasks_enabled'), timezone: loadState('calendar', 'timezone'),