Skip to content

Commit

Permalink
[wip]
Browse files Browse the repository at this point in the history
[skip ci]

Signed-off-by: Maksim Sukharev <[email protected]>
  • Loading branch information
Antreesy committed Jan 8, 2025
1 parent da9767d commit 1e1bc75
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 2 deletions.
124 changes: 124 additions & 0 deletions src/components/CalendarEventsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ import { t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import usernameToColor from '@nextcloud/vue/dist/Functions/usernameToColor.js'

import { useStore } from '../composables/useStore.js'
import { hasTalkFeature } from '../services/CapabilitiesManager.ts'
import { useGroupwareStore } from '../stores/groupware.ts'
import type { scheduleMeetingParams } from '../types/index.ts'

const props = defineProps<{
token: string,
Expand All @@ -28,10 +35,12 @@ const emit = defineEmits<{
(event: 'close'): void,
}>()

const store = useStore()
const groupwareStore = useGroupwareStore()

const open = ref(false)
const loading = ref(Object.keys(groupwareStore.calendars).length === 0)
const submitting = ref(false)

const calendars = computed(() => groupwareStore.calendars)
const upcomingEvents = computed(() => {
Expand All @@ -48,6 +57,23 @@ const upcomingEvents = computed(() => {
})
})

const calendarOptions = computed(() => groupwareStore.writeableCalendars.map(calendar => ({
value: calendar.uri,
label: calendar.displayname,
color: calendar.color ?? usernameToColor(calendar.uri).color
})))
const canScheduleMeeting = computed(() => {
return hasTalkFeature(props.token, 'schedule-meeting') && store.getters.isModerator && calendarOptions.value.length
})

const selectedCalendar = ref(calendarOptions.value.find(option => {
return option.value === groupwareStore.defaultCalendarUri
}) ?? null)
const selectedDateTimeStart = ref(new Date(moment().add(1, 'hours').startOf('hour')))
const selectedDateTimeEnd = ref(new Date(moment().add(2, 'hours').startOf('hour')))
const newMeetingTitle = ref('')
const newMeetingDescription = ref('')

onBeforeMount(() => {
getCalendars()
})
Expand All @@ -66,6 +92,25 @@ async function getCalendars() {
await groupwareStore.getPersonalCalendars()
loading.value = false
}

/**
* Get user's calendars to identify belonging of known and future events
*/
async function submitNewMeeting() {
if (!selectedCalendar.value) {
return
}

submitting.value = true
await groupwareStore.scheduleMeeting(props.token, {
calendarUri: selectedCalendar.value.value,
start: selectedDateTimeStart.value.getTime() / 1000,
end: selectedDateTimeEnd.value.getTime() / 1000,
title: newMeetingTitle.value || null,
description: newMeetingDescription.value || null,
})
submitting.value = false
}
</script>

<template>
Expand Down Expand Up @@ -118,6 +163,49 @@ async function getCalendars() {
<p>{{ loading ? t('spreed', 'Loading …') : t('spreed', 'No upcoming events') }}</p>
</template>
</NcEmptyContent>

<div v-if="canScheduleMeeting" class="calendar-meeting">
<h4 class="calendar-meeting__header">
{{ t('spreed', 'Schedule a meeting') }}
</h4>
<NcSelect id="schedule_meeting_select"
v-model="selectedCalendar"
:options="calendarOptions"
:input-label="t('spreed', 'Select calendar')">
<template #option="option">
<span class="calendar-badge" :style="{ backgroundColor: option.color }" />
{{ option.label }}
</template>
</NcSelect>
<div class="calendar-meeting__flex-wrapper">
<NcDateTimePickerNative id="schedule_meeting_input"
v-model="selectedDateTimeStart"
:label="t('spreed', 'Meeting start time')"
type="datetime-local" />
<NcDateTimePickerNative id="schedule_meeting_input"
v-model="selectedDateTimeEnd"
:label="t('spreed', 'Meeting end time')"
type="datetime-local" />
</div>
<NcTextField v-model="newMeetingTitle"
:label="t('spreed', 'Event title')"
label-visible />
<NcTextArea v-model="newMeetingDescription"
:label="t('spreed', 'Description')"
resize="vertical"
label-visible />
</div>

<template v-if="canScheduleMeeting" #actions>
<NcButton type="primary"
:disabled="!selectedCalendar || submitting"
@click="submitNewMeeting">
<template v-if="submitting" #icon>
<NcLoadingIcon />
</template>
{{ t('spreed', 'Submit') }}
</NcButton>
</template>
</NcDialog>
</div>
</template>
Expand Down Expand Up @@ -173,6 +261,42 @@ async function getCalendars() {
}
}

.calendar-meeting {
display: flex;
flex-direction: column;
margin: calc(var(--default-grid-baseline) / 2);
gap: var(--default-grid-baseline);

&__header {
margin-block: calc(var(--default-grid-baseline) * 3);
text-align: center;
}

&__flex-wrapper {
display: flex;
flex-flow: row wrap;
gap: calc(var(--default-grid-baseline) * 2);

& > * {
flex-grow: 1;
}
}

// Overwrite default styles
:deep(.native-datetime-picker) {
margin-bottom: var(--default-grid-baseline);

label {
margin-bottom: 2px;
}

input {
margin: 0;
border-width: 1px;
}
}
}

.calendar-badge {
display: inline-block;
width: var(--default-font-size);
Expand Down
25 changes: 24 additions & 1 deletion src/services/groupwareService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'

import type {
OutOfOfficeResponse,
OutOfOfficeResponse, scheduleMeetingParams, scheduleMeetingResponse,
UpcomingEventsResponse,
} from '../types/index.ts'

Expand All @@ -31,7 +31,30 @@ const getUserAbsence = async (userId: string): OutOfOfficeResponse => {
return axios.get(generateOcsUrl('/apps/dav/api/v1/outOfOffice/{userId}/now', { userId }))
}

/**
* Schedule a new meeting for a given conversation.
*
* @param token The conversation token
* @param payload The message object that is destructured
* @param payload.calendarUri The message text
* @param payload.start The display name of the actor
* @param payload.end A reference id to identify the message later again
* @param payload.title The message to be replied to
* @param payload.description The message to be replied to
* @param options options object destructured
*/
const scheduleMeeting = async function(token: string, { calendarUri, start, end, title, description }: scheduleMeetingParams, options?: object): scheduleMeetingResponse {
return axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/meeting', { token }, options), {
calendarUri,
start,
// end,
title,
description,
} as scheduleMeetingParams, options)
}

export {
getUpcomingEvents,
getUserAbsence,
scheduleMeeting,
}
12 changes: 11 additions & 1 deletion src/stores/groupware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import {
import {
getUpcomingEvents,
getUserAbsence,
scheduleMeeting,
} from '../services/groupwareService.ts'
import type {
DavCalendar,
OutOfOfficeResult,
OutOfOfficeResult, scheduleMeetingParams,
UpcomingEvent,
} from '../types/index.ts'

Expand Down Expand Up @@ -111,6 +112,15 @@ export const useGroupwareStore = defineStore('groupware', {
}
},

async scheduleMeeting(token: string, payload: scheduleMeetingParams) {
try {
await scheduleMeeting(token, payload)
await getUpcomingEvents(token)
} catch (error) {
console.error(error)
}
},

/**
* Drop an absence status from the store
* @param token The conversation token
Expand Down
3 changes: 3 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ export type OutOfOfficeResult = {
}
export type OutOfOfficeResponse = ApiResponseUnwrapped<OutOfOfficeResult>

export type scheduleMeetingParams = Required<operations['room-schedule-meeting']>['requestBody']['content']['application/json']
export type scheduleMeetingResponse = ApiResponse<operations['room-schedule-meeting']['responses'][200]['content']['application/json']>

// User preferences response
// from https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-user-preferences-api.html
export type UserPreferencesResponse = ApiResponseUnwrapped<unknown>
Expand Down

0 comments on commit 1e1bc75

Please sign in to comment.