diff --git a/src/components/Editor/FreeBusy/RoomAvailabilityList.vue b/src/components/Editor/FreeBusy/RoomAvailabilityList.vue
new file mode 100644
index 000000000..c689d4800
--- /dev/null
+++ b/src/components/Editor/FreeBusy/RoomAvailabilityList.vue
@@ -0,0 +1,208 @@
+
+
+ $emit('update:show-dialog', e)">
+
+
+
+
+
+
+
diff --git a/src/components/Editor/FreeBusy/RoomAvailabilityModal.vue b/src/components/Editor/FreeBusy/RoomAvailabilityModal.vue
new file mode 100644
index 000000000..153f9a312
--- /dev/null
+++ b/src/components/Editor/FreeBusy/RoomAvailabilityModal.vue
@@ -0,0 +1,409 @@
+
+
+ $emit('update:show', e)">
+
+
+
+
+
+ {{ $t('calendar', 'Today') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
handleActions('picker', date)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ color.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Editor/Resources/ResourceListSearch.vue b/src/components/Editor/Resources/ResourceListSearch.vue
index 1f17e41bf..182ec25c3 100644
--- a/src/components/Editor/Resources/ResourceListSearch.vue
+++ b/src/components/Editor/Resources/ResourceListSearch.vue
@@ -5,6 +5,15 @@
+
+ {{ $t('calendar', 'Show all rooms') }}
+
+
+
+
diff --git a/src/models/attendee.js b/src/models/attendee.js
index 516422e9e..aa411338b 100644
--- a/src/models/attendee.js
+++ b/src/models/attendee.js
@@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+import { AttendeeProperty } from '@nextcloud/calendar-js'
+
/**
* Creates a complete attendee object based on given props
*
@@ -50,7 +52,25 @@ const mapAttendeePropertyToAttendeeObject = (attendeeProperty) => {
})
}
+/**
+ * Maps a principal object to to our attendee object
+ *
+ * @param {object} principalObject An attendee object created by mapDavToPrincipal()
+ * @param {boolean} isOrganizer Should it be an attendee or an organizer?
+ * @return {AttendeeProperty}
+ */
+const mapPrincipalObjectToAttendeeObject = (principalObject, isOrganizer = false) => {
+ const attendeeProperty = AttendeeProperty.fromNameAndEMail(
+ principalObject.displayname,
+ principalObject.emailAddress,
+ isOrganizer,
+ )
+ attendeeProperty.userType = principalObject.calendarUserType
+ return mapAttendeePropertyToAttendeeObject(attendeeProperty)
+}
+
export {
getDefaultAttendeeObject,
mapAttendeePropertyToAttendeeObject,
+ mapPrincipalObjectToAttendeeObject,
}
diff --git a/src/services/caldavService.js b/src/services/caldavService.js
index 8796f56a9..305004451 100644
--- a/src/services/caldavService.js
+++ b/src/services/caldavService.js
@@ -257,6 +257,17 @@ const findPrincipalByUrl = async (url) => {
return getClient().findPrincipal(url)
}
+/**
+ * Finds all principals in a collection at the given URL
+ *
+ * @param {string} url The URL of the principal collection
+ * @param {object} options Passed to cdav-library/Principal::getPropFindList()
+ * @return {Promise}
+ */
+const findPrincipalsInCollection = async (url, options = {}) => {
+ return getClient().findPrincipalsInCollection(url, options)
+}
+
export {
initializeClientForUserView,
initializeClientForPublicView,
@@ -274,4 +285,5 @@ export {
principalPropertySearchByDisplaynameOrEmail,
advancedPrincipalPropertySearch,
findPrincipalByUrl,
+ findPrincipalsInCollection,
}
diff --git a/src/services/freeBusySlotService.js b/src/services/freeBusySlotService.js
index b88ac93c2..3cca3d298 100644
--- a/src/services/freeBusySlotService.js
+++ b/src/services/freeBusySlotService.js
@@ -12,7 +12,7 @@ import logger from '../utils/logger.js'
/**
* Get the first available slot for an event using freebusy API
*
- * @param {AttendeeProperty} organizer The organizer of the event
+ * @param {Principal} organizer The organizer of the event
* @param {AttendeeProperty[]} attendees Array of the event's attendees
* @param {Date} start The start date and time of the event
* @param {Date} end The end date and time of the event
@@ -242,6 +242,10 @@ function checkTimes(currentCheckedTime, duration, events) {
}
// make a function that sorts a list of objects by the "start" property
+/**
+ *
+ * @param events
+ */
function sortEvents(events) {
// remove events that have the same start and end time, if not done causes problems
const mappedEvents = new Map()
diff --git a/src/store/principals.js b/src/store/principals.js
index b94363466..5a0d4c88d 100644
--- a/src/store/principals.js
+++ b/src/store/principals.js
@@ -5,6 +5,7 @@
import {
findPrincipalByUrl,
getCurrentUserPrincipal,
+ findPrincipalsInCollection,
} from '../services/caldavService.js'
import logger from '../utils/logger.js'
import {
@@ -13,6 +14,7 @@ import {
} from '../models/principal.js'
import { defineStore } from 'pinia'
import Vue from 'vue'
+import { generateRemoteUrl } from '@nextcloud/router'
export default defineStore('principals', {
state: () => {
@@ -54,6 +56,22 @@ export default defineStore('principals', {
* @return {string|undefined}
*/
getCurrentUserPrincipalEmail: (state) => state.principalsById[state.currentUserPrincipal]?.emailAddress,
+
+ /**
+ * Gets all room principals
+ *
+ * @param {object} state the store data
+ * @return {object[]}
+ */
+ getRoomPrincipals: (state) => state.principals.filter((principal) => principal.isCalendarRoom),
+
+ /**
+ * Gets all resource principals
+ *
+ * @param {object} state the store data
+ * @return {object[]}
+ */
+ getResourcePrincipals: (state) => state.principals.filter((principal) => principal.isCalendarResource),
},
actions: {
/**
@@ -78,6 +96,34 @@ export default defineStore('principals', {
},
+ /**
+ * Fetches all principals of all rooms and resources from the DAV server and commits it to the state
+ *
+ * @return {Promise}
+ */
+ async fetchRoomAndResourcePrincipals() {
+ const options = {
+ enableCalDAVResourceBooking: true,
+ }
+ const principalCollections = await Promise.all([
+ findPrincipalsInCollection(generateRemoteUrl('dav/principals/calendar-rooms/'), options),
+ findPrincipalsInCollection(generateRemoteUrl('dav/principals/calendar-resources/'), options),
+ ])
+ for (const principals of principalCollections) {
+ if (!principals) {
+ // TODO - handle error
+ continue
+ }
+
+ logger.debug('Fetched principals', { principals })
+ for (const principal of principals) {
+ this.addPrincipalMutation({
+ principal: mapDavToPrincipal(principal),
+ })
+ }
+ }
+ },
+
/**
* Fetches the current-user-principal
*
diff --git a/src/views/Calendar.vue b/src/views/Calendar.vue
index 5c57bcacc..3fec8c555 100644
--- a/src/views/Calendar.vue
+++ b/src/views/Calendar.vue
@@ -351,6 +351,12 @@ export default {
}
await this.loadMomentLocale()
+
+ await this.principalsStore.fetchRoomAndResourcePrincipals()
+ logger.debug('Fetched rooms and resources', {
+ rooms: this.principalsStore.getRoomPrincipals,
+ resources: this.principalsStore.getResourcePrincipals,
+ })
},
methods: {
/**
diff --git a/tests/javascript/unit/models/attendee.test.js b/tests/javascript/unit/models/attendee.test.js
index d5b862cfe..42d05d7d4 100644
--- a/tests/javascript/unit/models/attendee.test.js
+++ b/tests/javascript/unit/models/attendee.test.js
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import {getDefaultAttendeeObject, mapAttendeePropertyToAttendeeObject} from "../../../../src/models/attendee.js";
+import {getDefaultAttendeeObject, mapAttendeePropertyToAttendeeObject, mapPrincipalObjectToAttendeeObject} from "../../../../src/models/attendee.js";
+import { AttendeeProperty } from '@nextcloud/calendar-js'
describe('Test suite: Attendee model (models/attendee.js)', () => {
@@ -148,4 +149,76 @@ describe('Test suite: Attendee model (models/attendee.js)', () => {
uri: 'mailto:jdoe@example.com',
})
})
+
+ it('should map a principal object to an attendee object', () => {
+ const principalModel = {
+ id: 'L3JlbW90ZS5waHAvZGF2L3ByaW5jaXBhbHMvY2FsZW5kYXItcm9vbXMvcm9vbS0xMjMv',
+ dav: {},
+ calendarUserType: 'ROOM',
+ principalScheme: 'principal:principals/calendar-rooms/room-123',
+ emailAddress: 'room-123@example.com',
+ displayname: 'ROOM 123',
+ url: '/remote.php/dav/principals/calendar-rooms/room-123/',
+ isUser: false,
+ isGroup: false,
+ isCircle: false,
+ isCalendarResource: false,
+ isCalendarRoom: true,
+ principalId: 'room-123',
+ userId: null,
+ }
+
+ const attendeeProperty = new AttendeeProperty('ATTENDEE')
+ attendeeProperty.commonName = 'ROOM 123'
+ attendeeProperty.email = 'room-123@example.com'
+ attendeeProperty.userType = 'ROOM'
+
+ const actual = mapPrincipalObjectToAttendeeObject(principalModel)
+ expect(actual).toMatchObject({
+ commonName: 'ROOM 123',
+ member: null,
+ calendarUserType: 'ROOM',
+ participationStatus: 'NEEDS-ACTION',
+ role: 'REQ-PARTICIPANT',
+ rsvp: false,
+ uri: 'mailto:room-123@example.com',
+ })
+ expect(actual.attendeeProperty.toString()).toBe(attendeeProperty.toString())
+ })
+
+ it('should map a principal object to an attendee object (organizer)', () => {
+ const principalModel = {
+ id: 'L3JlbW90ZS5waHAvZGF2L3ByaW5jaXBhbHMvY2FsZW5kYXItcmVzb3VyY2VzL3Byb2plY3Rvci0xMjMv',
+ dav: {},
+ calendarUserType: 'RESOURCE',
+ principalScheme: 'principal:principals/calendar-resources/projector-123',
+ emailAddress: 'projector-123@example.com',
+ displayname: 'Projector 123',
+ url: '/remote.php/dav/principals/calendar-resources/projector-123/',
+ isUser: false,
+ isGroup: false,
+ isCircle: false,
+ isCalendarResource: true,
+ isCalendarRoom: false,
+ principalId: 'projector-123',
+ userId: null,
+ }
+
+ const attendeeProperty = new AttendeeProperty('ORGANIZER')
+ attendeeProperty.commonName = 'Projector 123'
+ attendeeProperty.email = 'projector-123@example.com'
+ attendeeProperty.userType = 'RESOURCE'
+
+ const actual = mapPrincipalObjectToAttendeeObject(principalModel, true)
+ expect(actual).toMatchObject({
+ commonName: 'Projector 123',
+ member: null,
+ calendarUserType: 'RESOURCE',
+ participationStatus: 'NEEDS-ACTION',
+ role: 'REQ-PARTICIPANT',
+ rsvp: false,
+ uri: 'mailto:projector-123@example.com',
+ })
+ expect(actual.attendeeProperty.toString()).toBe(attendeeProperty.toString())
+ })
})