UNPKG

@nextcloud/calendar-availability-vue

Version:

Weekly calendar availability component for Nextcloud apps

321 lines (320 loc) 11.4 kB
import './assets/index-UKC4AV1t.css'; import NcDateTimePickerNative from "@nextcloud/vue/dist/Components/NcDateTimePickerNative.js"; import NcButton from "@nextcloud/vue/dist/Components/NcButton.js"; import IconDelete from "vue-material-design-icons/Delete.vue"; import IconAdd from "vue-material-design-icons/Plus.vue"; import { getFirstDay } from "@nextcloud/l10n"; import { getZoneString } from "icalzone"; import ICAL from "ical.js"; import { v4 } from "uuid"; import { getLoggerBuilder } from "@nextcloud/logger"; function normalizeComponent(scriptExports, render2, staticRenderFns, functionalTemplate, injectStyles, scopeId, moduleIdentifier, shadowMode) { var options = typeof scriptExports === "function" ? scriptExports.options : scriptExports; if (render2) { options.render = render2; options.staticRenderFns = staticRenderFns; options._compiled = true; } { options._scopeId = "data-v-" + scopeId; } return { exports: scriptExports, options }; } const _sfc_main = { name: "CalendarAvailability", components: { NcDateTimePickerNative, NcButton, IconAdd, IconDelete }, props: { slots: { type: Object, required: true }, loading: { type: Boolean, default: false }, l10nTo: { type: String, required: true }, l10nDeleteSlot: { type: String, required: true }, l10nEmptyDay: { type: String, required: true }, l10nAddSlot: { type: String, required: true }, l10nWeekDayListLabel: { type: String, default: "Weekdays" }, l10nMonday: { type: String, required: true }, l10nTuesday: { type: String, required: true }, l10nWednesday: { type: String, required: true }, l10nThursday: { type: String, required: true }, l10nFriday: { type: String, required: true }, l10nSaturday: { type: String, required: true }, l10nSunday: { type: String, required: true }, l10nStartPickerLabel: { type: Function, default: (dayName) => "Pick a start time for ".concat(dayName) }, l10nEndPickerLabel: { type: Function, default: (dayName) => "Pick a end time for ".concat(dayName) } }, data() { return { internalSlots: this.slotsToInternalData(this.slots) }; }, watch: { slots() { this.internalSlots = this.slotsToInternalData(this.slots); } }, methods: { timeStampSlotsToDateObjectSlots(slots) { return slots.map((slot) => ({ start: new Date(slot.start * 1e3), end: new Date(slot.end * 1e3) })); }, slotsToInternalData() { const moToSa = [ { id: "MO", displayName: this.l10nMonday, slots: this.timeStampSlotsToDateObjectSlots(this.slots.MO) }, { id: "TU", displayName: this.l10nTuesday, slots: this.timeStampSlotsToDateObjectSlots(this.slots.TU) }, { id: "WE", displayName: this.l10nWednesday, slots: this.timeStampSlotsToDateObjectSlots(this.slots.WE) }, { id: "TH", displayName: this.l10nThursday, slots: this.timeStampSlotsToDateObjectSlots(this.slots.TH) }, { id: "FR", displayName: this.l10nFriday, slots: this.timeStampSlotsToDateObjectSlots(this.slots.FR) }, { id: "SA", displayName: this.l10nSaturday, slots: this.timeStampSlotsToDateObjectSlots(this.slots.SA) } ]; const sunday = { id: "SU", displayName: this.l10nSunday, slots: this.timeStampSlotsToDateObjectSlots(this.slots.SU) }; return getFirstDay() === 1 ? [...moToSa, sunday] : [sunday, ...moToSa]; }, internalDataToSlots() { const converted = {}; this.internalSlots.forEach(({ id, slots }) => { converted[id] = slots.map((slot) => ({ start: Math.round(slot.start.getTime() / 1e3), end: Math.round(slot.end.getTime() / 1e3) })); }); return converted; }, addSlot(day) { const start = /* @__PURE__ */ new Date(); start.setHours(9, 0, 0, 0); const end = /* @__PURE__ */ new Date(); end.setHours(17, 0, 0, 0); day.slots.push({ start, end }); this.onChangeSlots(); }, removeSlot(day, idx) { day.slots.splice(idx, 1); this.onChangeSlots(); }, onChangeSlots() { this.$emit("update:slots", this.internalDataToSlots()); } } }; var _sfc_render = function render() { var _vm = this, _c = _vm._self._c; return _c("ul", { staticClass: "week-day-container", attrs: { "aria-label": _vm.l10nWeekDayListLabel } }, [_vm._l(_vm.internalSlots, function(day) { return [_c("li", { key: "day-label-".concat(day.id), staticClass: "day-container" }, [_c("div", { staticClass: "label-weekday" }, [_c("span", { attrs: { "id": day.displayName + "-label" } }, [_vm._v(_vm._s(day.displayName))])]), _c("div", { key: "day-slots-".concat(day.id), staticClass: "availability-slots" }, [_c("div", { staticClass: "availability-slot-group" }, [_vm._l(day.slots, function(slot, idx) { var _a, _b; return [_c("div", { key: "slot-".concat(day.id, "-").concat(idx), staticClass: "availability-slot" }, [_c("NcDateTimePickerNative", { staticClass: "start-date", attrs: { "id": "start-".concat(day.id, "-").concat(idx), "type": "time", "label": (_a = _vm.l10nStartPickerLabel) == null ? void 0 : _a.call(_vm, day.displayName), "hide-label": true }, on: { "change": _vm.onChangeSlots }, model: { value: slot.start, callback: function($$v) { _vm.$set(slot, "start", $$v); }, expression: "slot.start" } }), _c("span", { staticClass: "to-text" }, [_vm._v(" " + _vm._s(_vm.l10nTo) + " ")]), _c("NcDateTimePickerNative", { staticClass: "end-date", attrs: { "id": "end-".concat(day.id, "-").concat(idx), "type": "time", "label": (_b = _vm.l10nEndPickerLabel) == null ? void 0 : _b.call(_vm, day.displayName), "hide-label": true }, on: { "change": _vm.onChangeSlots }, model: { value: slot.end, callback: function($$v) { _vm.$set(slot, "end", $$v); }, expression: "slot.end" } }), _c("NcButton", { key: "slot-".concat(day.id, "-").concat(idx, "-btn"), staticClass: "button", attrs: { "type": "tertiary", "aria-label": _vm.l10nDeleteSlot, "title": _vm.l10nDeleteSlot }, on: { "click": function($event) { return _vm.removeSlot(day, idx); } }, scopedSlots: _vm._u([{ key: "icon", fn: function() { return [_c("IconDelete", { attrs: { "size": 20 } })]; }, proxy: true }], null, true) })], 1)]; })], 2), day.slots.length === 0 ? _c("span", { staticClass: "empty-content" }, [_vm._v(" " + _vm._s(_vm.l10nEmptyDay) + " ")]) : _vm._e()]), _c("NcButton", { key: "add-slot-".concat(day.id), staticClass: "add-another button", attrs: { "disabled": _vm.loading, "title": _vm.l10nAddSlot, "aria-label": _vm.l10nAddSlot }, on: { "click": function($event) { return _vm.addSlot(day); } }, scopedSlots: _vm._u([{ key: "icon", fn: function() { return [_c("IconAdd", { attrs: { "size": 20 } })]; }, proxy: true }], null, true) })], 1)]; })], 2); }; var _sfc_staticRenderFns = []; var __component__ = /* @__PURE__ */ normalizeComponent( _sfc_main, _sfc_render, _sfc_staticRenderFns, false, null, "c15ebf8b" ); const CalendarAvailability = __component__.exports; const logger = getLoggerBuilder().detectUser().setApp("@nextcloud/calendar-availability-vue").build(); function getEmptySlots() { return { MO: [], TU: [], WE: [], TH: [], FR: [], SA: [], SU: [] }; } function vavailabilityToSlots(vavailability) { const parsedIcal = ICAL.parse(vavailability); 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"); const slots = getEmptySlots(); availableComps.forEach((availableComp) => { const startIcalDate = availableComp.getFirstProperty("dtstart").getFirstValue(); const endIcalDate = availableComp.getFirstProperty("dtend").getFirstValue(); const rrule = availableComp.getFirstProperty("rrule"); const start = /* @__PURE__ */ new Date(); start.setHours(startIcalDate.hour, startIcalDate.minute, 0, 0); const end = /* @__PURE__ */ new Date(); end.setHours(endIcalDate.hour, endIcalDate.minute, 0, 0); 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() / 1e3, end: end.getTime() / 1e3 }); }); }); return { slots, timezoneId }; } function slotsToVavailability(slots, timezoneId) { const vcalendarComp = new ICAL.Component("vcalendar"); vcalendarComp.addPropertyWithValue("prodid", "Nextcloud DAV app"); const predefinedTimezoneIcal = getZoneString(timezoneId); if (predefinedTimezoneIcal) { const timezoneComp = new ICAL.Component(ICAL.parse(predefinedTimezoneIcal)); vcalendarComp.addSubcomponent(timezoneComp); } else { const timezoneComp = new ICAL.Component("vtimezone"); timezoneComp.addPropertyWithValue("tzid", timezoneId); vcalendarComp.addSubcomponent(timezoneComp); } const vavailabilityComp = new ICAL.Component("vavailability"); const deduplicated = slots.reduce((acc, slot) => { var _a; const start = new Date(slot.start * 1e3); const end = new Date(slot.end * 1e3); const key = [ start.getHours(), start.getMinutes(), end.getHours(), end.getMinutes() ].join("-"); return { ...acc, [key]: [...(_a = acc[key]) != null ? _a : [], slot] }; }, {}); Object.keys(deduplicated).map((key) => { const slots2 = deduplicated[key]; const start = slots2[0].start; const end = slots2[0].end; const days = slots2.map((slot) => slot.day).filter((day, index, self) => self.indexOf(day) === index); const availableComp = new ICAL.Component("available"); const startTimeProp = availableComp.addPropertyWithValue("dtstart", ICAL.Time.fromJSDate(new Date(start * 1e3), false)); startTimeProp.setParameter("tzid", timezoneId); const endTimeProp = availableComp.addPropertyWithValue("dtend", ICAL.Time.fromJSDate(new Date(end * 1e3), false)); endTimeProp.setParameter("tzid", timezoneId); availableComp.addPropertyWithValue("uid", v4()); availableComp.addPropertyWithValue("rrule", { freq: "WEEKLY", byday: days }); return availableComp; }).map(vavailabilityComp.addSubcomponent.bind(vavailabilityComp)); vcalendarComp.addSubcomponent(vavailabilityComp); return vcalendarComp.toString(); } export { CalendarAvailability, getEmptySlots, slotsToVavailability, vavailabilityToSlots };