Skip to content

Commit

Permalink
Add helpers to convert from VAVAILABILITY to slots and back
Browse files Browse the repository at this point in the history
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
  • Loading branch information
ChristophWurst committed Nov 23, 2021
1 parent de7e9b8 commit 05bcf0f
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 9 deletions.
25 changes: 18 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
"url": "https://github.com/nextcloud/calendar-availability-vue/issues"
},
"homepage": "https://github.com/nextcloud/calendar-availability-vue#readme",
"dependencies": {
"ical.js": "^1.4.0",
"icalzone": "^0.0.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.4",
Expand Down
3 changes: 2 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export default {
postcss(),
commonjs(),
babel({
babelHelpers: 'bundled'
babelHelpers: 'bundled',
presets: ["@babel/preset-env"]
})
]
}
129 changes: 129 additions & 0 deletions src/convert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { getZoneString } from 'icalzone'
import { parse as parseIcal, Component } from 'ical.js'
import { v4 as uuidv4 } from 'uuid'

export function getEmptySlots() {
return {
MO: [],
TU: [],
WE: [],
TH: [],
FR: [],
SA: [],
SU: [],
}
}

export function vavailabilityToSlots(vavailability) {
const parsedIcal = parseIcal(vavailability)

const vcalendarComp = new 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: start.getTime() / 1000,
end: end.getTime() / 1000,
})
})
})

return {
slots,
timezoneId,
}
}

export function slotsToVavailability(slots, timezoneId) {
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 = slots.reduce((acc, slot) => {
const start = new Date(slot.start * 1000)
const end = new Date(slot.end * 1000)

const key = [
start.getHours(),
start.getMinutes(),
end.getHours(),
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(new Date(start * 1000), false))
startTimeProp.setParameter('tzid', timezoneId)
const endTimeProp = availableComp.addPropertyWithValue('dtend', ICAL.Time.fromJSDate(new Date(end * 1000), 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))

vcalendarComp.addSubcomponent(vavailabilityComp)

return vcalendarComp.toString()
}
8 changes: 7 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import CalendarAvailability from './CalendarAvailability.vue'
import { getEmptySlots, slotsToVavailability, vavailabilityToSlots } from './convert'

export { CalendarAvailability }
export {
CalendarAvailability,
getEmptySlots,
slotsToVavailability,
vavailabilityToSlots,
}

0 comments on commit 05bcf0f

Please sign in to comment.