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

Replace CalDAV availability component with component lib #29796

Merged
merged 1 commit into from
Feb 17, 2022
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
117 changes: 9 additions & 108 deletions apps/dav/src/service/CalendarService.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getClient } from '../dav/client'
import ICAL from 'ical.js'
import logger from './logger'
import { parseXML } from 'webdav/dist/node/tools/dav'
import { getZoneString } from 'icalzone'
import { v4 as uuidv4 } from 'uuid'

import {
slotsToVavailability,
vavailabilityToSlots,
} from '@nextcloud/calendar-availability-vue'

/**
*
Expand Down Expand Up @@ -67,44 +69,7 @@ export async function findScheduleInboxAvailability() {
return undefined
}

const parsedIcal = ICAL.parse(availability)

const vcalendarComp = new ICAL.Component(parsedIcal)
const vavailabilityComp = vcalendarComp.getFirstSubcomponent('vavailability')

let timezoneId
const timezoneComp = vcalendarComp.getFirstSubcomponent('vtimezone')
if (timezoneComp) {
timezoneId = timezoneComp.getFirstProperty('tzid').getFirstValue()
}

const availableComps = vavailabilityComp.getAllSubcomponents('available')
// Combine all AVAILABLE blocks into a week of slots
const slots = getEmptySlots()
availableComps.forEach((availableComp) => {
const start = availableComp.getFirstProperty('dtstart').getFirstValue().toJSDate()
const end = availableComp.getFirstProperty('dtend').getFirstValue().toJSDate()
const rrule = availableComp.getFirstProperty('rrule')

if (rrule.getFirstValue().freq !== 'WEEKLY') {
logger.warn('rrule not supported', {
rrule: rrule.toICALString(),
})
return
}

rrule.getFirstValue().getComponent('BYDAY').forEach(day => {
slots[day].push({
start,
end,
})
})
})

return {
slots,
timezoneId,
}
return vavailabilityToSlots(availability)
}

/**
Expand All @@ -117,74 +82,10 @@ export async function saveScheduleInboxAvailability(slots, timezoneId) {
day: dayId,
})))]

const vcalendarComp = new ICAL.Component('vcalendar')
vcalendarComp.addPropertyWithValue('prodid', 'Nextcloud DAV app')

// Store time zone info
// If possible we use the info from a time zone database
const predefinedTimezoneIcal = getZoneString(timezoneId)
if (predefinedTimezoneIcal) {
const timezoneComp = new ICAL.Component(ICAL.parse(predefinedTimezoneIcal))
vcalendarComp.addSubcomponent(timezoneComp)
} else {
// Fall back to a simple markup
const timezoneComp = new ICAL.Component('vtimezone')
timezoneComp.addPropertyWithValue('tzid', timezoneId)
vcalendarComp.addSubcomponent(timezoneComp)
}

// Store availability info
const vavailabilityComp = new ICAL.Component('vavailability')

// Deduplicate by start and end time
const deduplicated = all.reduce((acc, slot) => {
const key = [
slot.start.getHours(),
slot.start.getMinutes(),
slot.end.getHours(),
slot.end.getMinutes(),
].join('-')

return {
...acc,
[key]: [...(acc[key] ?? []), slot],
}
}, {})

// Create an AVAILABILITY component for every recurring slot
Object.keys(deduplicated).map(key => {
const slots = deduplicated[key]
const start = slots[0].start
const end = slots[0].end
// Combine days but make them also unique
const days = slots.map(slot => slot.day).filter((day, index, self) => self.indexOf(day) === index)

const availableComp = new ICAL.Component('available')

// Define DTSTART and DTEND
const startTimeProp = availableComp.addPropertyWithValue('dtstart', ICAL.Time.fromJSDate(start, false))
startTimeProp.setParameter('tzid', timezoneId)
const endTimeProp = availableComp.addPropertyWithValue('dtend', ICAL.Time.fromJSDate(end, false))
endTimeProp.setParameter('tzid', timezoneId)

// Add mandatory UID
availableComp.addPropertyWithValue('uid', uuidv4())

// TODO: add optional summary

// Define RRULE
availableComp.addPropertyWithValue('rrule', {
freq: 'WEEKLY',
byday: days,
})

return availableComp
}).map(vavailabilityComp.addSubcomponent.bind(vavailabilityComp))
const vavailability = slotsToVavailability(all, timezoneId)

vcalendarComp.addSubcomponent(vavailabilityComp)
logger.debug('New availability ical created', {
asObject: vcalendarComp,
asString: vcalendarComp.toString(),
vavailability,
})

const client = getClient('calendars')
Expand All @@ -194,7 +95,7 @@ export async function saveScheduleInboxAvailability(slots, timezoneId) {
<x0:propertyupdate xmlns:x0="DAV:">
<x0:set>
<x0:prop>
<x1:calendar-availability xmlns:x1="urn:ietf:params:xml:ns:caldav">${vcalendarComp.toString()}</x1:calendar-availability>
<x1:calendar-availability xmlns:x1="urn:ietf:params:xml:ns:caldav">${vavailability}</x1:calendar-availability>
</x0:prop>
</x0:set>
</x0:propertyupdate>`,
Expand Down
140 changes: 28 additions & 112 deletions apps/dav/src/views/Availability.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,19 @@
<TimezonePicker v-model="timezone" />
</span>
</div>
<div class="grid-table">
<template v-for="day in daysOfTheWeek">
<div :key="`day-label-${day.id}`" class="label-weekday">
{{ day.displayName }}
</div>
<div :key="`day-slots-${day.id}`" class="availability-slots">
<div class="availability-slot-group">
<template v-for="(slot, idx) in day.slots">
<div :key="`slot-${day.id}-${idx}`" class="availability-slot">
<DatetimePicker v-model="slot.start"
type="time"
class="start-date"
format="H:mm" />
<span class="to-text">
{{ $t('dav', 'to') }}
</span>
<DatetimePicker v-model="slot.end"
type="time"
class="end-date"
format="H:mm" />
<button :key="`slot-${day.id}-${idx}-btn`"
class="icon-delete delete-slot button"
:title="$t('dav', 'Delete slot')"
@click="deleteSlot(day, idx)" />
</div>
</template>
</div>
<span v-if="day.slots.length === 0"
class="empty-content">
{{ $t('dav', 'No working hours set') }}
</span>
</div>
<button :key="`add-slot-${day.id}`"
:disabled="loading"
class="icon-add add-another button"
:title="$t('dav', 'Add slot')"
@click="addSlot(day)" />
</template>
</div>
<CalendarAvailability :slots.sync="slots"
:loading="loading"
:l10n-to="$t('dav', 'to')"
:l10n-delete-slot="$t('dav', 'Delete slot')"
:l10n-empty-day="$t('dav', 'No working hours set')"
:l10n-add-slot="$t('dav', 'Add slot')"
:l10n-monday="$t('dav', 'Monday')"
:l10n-tuesday="$t('dav', 'Tuesday')"
:l10n-wednesday="$t('dav', 'Wednesday')"
:l10n-thursday="$t('dav', 'Thursday')"
:l10n-friday="$t('dav', 'Friday')"
:l10n-saturday="$t('dav', 'Saturday')"
:l10n-sunday="$t('dav', 'Sunday')" />
<button :disabled="loading || saving"
class="button primary"
@click="save">
Expand All @@ -60,83 +34,46 @@
</template>

<script>
import DatetimePicker from '@nextcloud/vue/dist/Components/DatetimePicker'
import { CalendarAvailability } from '@nextcloud/calendar-availability-vue'
import {
findScheduleInboxAvailability,
getEmptySlots,
saveScheduleInboxAvailability,
} from '../service/CalendarService'
import { getFirstDay } from '@nextcloud/l10n'
import jstz from 'jstimezonedetect'
import TimezonePicker from '@nextcloud/vue/dist/Components/TimezonePicker'
export default {
name: 'Availability',
components: {
DatetimePicker,
CalendarAvailability,
TimezonePicker,
},
data() {
// Try to determine the current timezone, and fall back to UTC otherwise
const defaultTimezone = jstz.determine()
const defaultTimezoneId = defaultTimezone ? defaultTimezone.name() : 'UTC'

const moToSa = [
{
id: 'MO',
displayName: this.$t('dav', 'Monday'),
slots: [],
},
{
id: 'TU',
displayName: this.$t('dav', 'Tuesday'),
slots: [],
},
{
id: 'WE',
displayName: this.$t('dav', 'Wednesday'),
slots: [],
},
{
id: 'TH',
displayName: this.$t('dav', 'Thursday'),
slots: [],
},
{
id: 'FR',
displayName: this.$t('dav', 'Friday'),
slots: [],
},
{
id: 'SA',
displayName: this.$t('dav', 'Saturday'),
slots: [],
},
]
const sunday = {
id: 'SU',
displayName: this.$t('dav', 'Sunday'),
slots: [],
}
const daysOfTheWeek = getFirstDay() === 1 ? [...moToSa, sunday] : [sunday, ...moToSa]
return {
loading: true,
saving: false,
timezone: defaultTimezoneId,
daysOfTheWeek,
slots: getEmptySlots(),
}
},
async mounted() {
try {
const { slots, timezoneId } = await findScheduleInboxAvailability()
if (slots) {
this.daysOfTheWeek.forEach(day => {
day.slots.push(...slots[day.id])
})
}
if (timezoneId) {
this.timezone = timezoneId
const slotData = await findScheduleInboxAvailability()
if (!slotData) {
console.info('no availability is set')
this.slots = getEmptySlots()
} else {
const { slots, timezoneId } = slotData
this.slots = slots
if (timezoneId) {
this.timezone = timezoneId
}
console.info('availability loaded', this.slots, this.timezoneId)
}
console.info('availability loaded', this.daysOfTheWeek)
} catch (e) {
console.error('could not load existing availability', e)

Expand All @@ -146,32 +83,11 @@ export default {
}
},
methods: {
addSlot(day) {
const start = new Date()
start.setHours(9)
start.setMinutes(0)
start.setSeconds(0)
const end = new Date()
end.setHours(17)
end.setMinutes(0)
end.setSeconds(0)
day.slots.push({
start,
end,
})
},
deleteSlot(day, idx) {
day.slots.splice(idx, 1)
},
async save() {
try {
this.saving = true

const slots = getEmptySlots()
this.daysOfTheWeek.forEach(day => {
day.slots.forEach(slot => slots[day.id].push(slot))
})
await saveScheduleInboxAvailability(slots, this.timezone)
await saveScheduleInboxAvailability(this.slots, this.timezone)

// TODO: show a nice toast
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions dist/core-common.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/core-common.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/dav-settings-personal-availability.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/dav-settings-personal-availability.js.map

Large diffs are not rendered by default.

Loading