commit b82d9748ac16724ae6026d07eb1f6e2b9a611bd4 Author: chrosey Date: Sat Jul 30 12:35:06 2022 +0200 updated handling of entries in comment diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4d6fce5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/orchester_dienstplan.iml b/.idea/orchester_dienstplan.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/orchester_dienstplan.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..764ab3f --- /dev/null +++ b/app.js @@ -0,0 +1,633 @@ +if (!String.prototype.includes) { + String.prototype.includes = function () { + 'use strict'; + return String.prototype.indexOf.apply(this, arguments) !== -1; + }; +} +//extend FileReader +if (!FileReader.prototype.readAsBinaryString) { + FileReader.prototype.readAsBinaryString = function (fileData) { + var binary = ""; + var pt = this; + var reader = new FileReader(); + reader.onload = function (e) { + var bytes = new Uint8Array(reader.result); + var length = bytes.byteLength; + for (var i = 0; i < length; i++) { + binary += String.fromCharCode(bytes[i]); + } + //pt.result - readonly so assign binary + pt.content = binary; + $(pt).trigger('onload'); + } + reader.readAsArrayBuffer(file); + } +} + +//localStorage persistence +var SHIFT_STORAGE_KEY = "dienstplan_chrosey"; +var RULE_STORAGE_KEY = "regeln_chrosey"; +var shiftStorage = { + fetch: function () { + 'use strict'; + var parsed = JSON.parse(localStorage.getItem(SHIFT_STORAGE_KEY) || '[]'), + shifts = []; + parsed.forEach(function (el, index) { + var shift = Shift.thaw(el); + shift.id = index; + shifts.push(shift); + }); + shiftStorage.uid = shifts.length; + return shifts; + }, + save : function (shifts) { + 'use strict'; + var json = JSON.stringify(shifts) + localStorage.setItem(SHIFT_STORAGE_KEY, json); + }, + count: function () { + return JSON.parse(localStorage.getItem(SHIFT_STORAGE_KEY) || '[]').length; + } +}; +var ruleStorage = { + fetch: function () { + 'use strict'; + var parsed = JSON.parse(localStorage.getItem(RULE_STORAGE_KEY)) | []; + var rules = parsed.length > 0 ? + parsed.map((e, i) => { + var r = Rule.thaw(e); + r.id = i; + return r; + }) : + Rule.defaults(); + ruleStorage.uid = rules.length; + return rules; + }, + save : function (rules) { + 'use strict'; + var json = JSON.stringify(rules); + localStorage.setItem(RULE_STORAGE_KEY, json); + } +}; + +Vue.component('ask-format-modal', { + template: ` + + `, + data() { + return { + picked: null + } + }, + props : ["options"], + methods: { + submitPick() { + this.$emit('picked-format', this.picked); + $('#ask-format-modal').modal('close'); + } + } + +}); + +Vue.component('chip-input', { + template: ` +
+ `, + data() { + return { + instance: null, + chips : [] + } + }, + + computed: { + chipsData() { + return this.instance.chipsData; + } + }, + + watch: { + initData: { + deep: true, + handler(n, o) { + if (n !== o) { + this.initialize(); + this.$emit('init'); + } + } + } + }, + props: { + name : String, + initData: Array + }, + + methods: { + initialize() { + this.chips = this.initData.map(e => e); + var el = $('#' + this.name)[0]; + this.instance = M.Chips.init(el, { + data : this.chips, + onChipAdd : () => { + this.$emit("change", this.chipsData); + }, + onChipDelete: () => { + this.$emit("change", this.chipsData); + } + }); + } + }, + + mounted() { + this.initialize(); + } +}); + +var app = new Vue({ + el : '#app', + data : { + shifts : shiftStorage.fetch(), + rules : ruleStorage.fetch(), + icsFile : null, + blob : null, + dp_sheet : '', + deletedShift : '', + format : '', + remaining : shiftStorage.count(), + selectedShift : new Shift({}), + selectedShiftIndex: -1, + selectedRule : new Rule({}), + selectedRuleIndex : -1, + saveto : 'dienstplan.ics', + uploadFileName : "", + availableFormats : ["Erfurt", "Stuttgart", "X"], + stepper : null, + timepickers : null, + + config: { + moment : { + parse_formats : [ + "ddd, DD/ MMM. 'YY HH:mm", + "ddd, DD/ MMM. YYYY HH:mm" + ], + parse_language : 'en', + display_language: 'de' + }, + stepper : { + firstActive: 0, + }, + toast : { + displayLength: 3000 + }, + timepicker: { + twelveHour: false, + } + }, + }, + watch: { + shifts: { + handler: function (shifts) { + 'use strict'; + shiftStorage.save(shifts); + this.remaining = shifts.length; + this.icsFile = null; + this.blob = null; + this.makeToast("Änderungen gespeichert."); + }, + deep : true + }, + rules : { + handler: function (rules) { + 'use strict'; + ruleStorage.save(rules); + this.makeToast("Änderungen gespeichert."); + }, + deep : true + } + }, + + computed: { + groupedTermine() { + return _.chain(this.shifts).sortBy(e => e.Datum).groupBy(e => e.Datum).value(); + } + }, + + methods : { + updateArten(value) { + this.selectedRule.Arten = value; + }, + + updateTitel(value) { + this.selectedRule.Titel = value; + }, + + makeToast(message) { + var toastOptions = this.config.toast; + toastOptions.html = message; + M.toast(toastOptions); + }, + + openModal(elementID) { + var element = document.getElementById(elementID); + var modal = M.Modal.getInstance(element); + modal.open(); + }, + + closeModal(elementID) { + var element = document.getElementById(elementID); + var modal = M.Modal.getInstance(element); + modal.close(); + }, + + + onFileChange: function (event) { + var files = event.target.files || event.dataTransfer.files; + this.handleInputFile(files[0]); + this.uploadFileName = files[0].name + " [" + Math.round(files[0].size / 1024) + " kB]"; + this.makeToast(this.uploadFileName + " ausgewählt"); + }, + + handleInputFile: function (file) { + var reader = new FileReader(); + var vm = this; + reader.onload = (e) => { + var data = !e ? reader.content : e.target.result; + var workbook = XLSX.read(data, { + type : 'binary', + cellDates: true, + }); + var isErfurterDienstplan = workbook.SheetNames.indexOf("Dienstplan") > -1; + + if (isErfurterDienstplan) { + vm.parseForErfurt(workbook.Sheets["Dienstplan"]); + } else { + this.workbook = workbook; + this.askForDienstplanFormat(); + } + }; + reader.readAsBinaryString(file); + }, + + askForDienstplanFormat: function () { + this.makeToast("Dienstplanformat nicht erkannt."); + this.openModal('ask-format-modal'); + }, + + parseForErfurt: function (dp) { + var arr = XLSX.utils.sheet_to_row_object_array(dp, { + range: 1 + }); + this.format = "Erfurt"; + var vm = this; + var day; + + this.makeToast("Erfurter Dienstplan erkannt."); + + arr.forEach(element => { + moment.locale(vm.config.moment.parse_language); + if (element.hasOwnProperty('Datum')) { + day = moment(element.Datum); + } + + if (element.hasOwnProperty('Bemerkung')) { + if (element.Bemerkung.toString().search(/\d\d:\d\d\s/) >= 0) { + // prüfe ob eine Uhrzeit drinnen steht + let sonderzeit = moment(element.Bemerkung, 'HH:mm'); + let name = element.Bemerkung.toString().replace(/\d\d:\d\d\s/, '').trim(); + let splittedName = name.split(' '); + + let terminArt = ''; + splittedName.forEach(function (item) { + vm.rules.forEach(function (rule) { + rule.Arten.forEach(function (art) { + if (art.tag == item) { + terminArt = art.tag; + name.replace(art.tag, ''); + } + }) + }) + }); + + let termin = { + datum : day.clone().hour(sonderzeit.hour()).minute(sonderzeit.minute()), + art : terminArt, + beschreibung: '', + name : name.trim() + } + vm.addShift(new Shift(termin)); + beschreibung = ''; + } + } + + if (element.hasOwnProperty('Dienst')) { + + let times = []; + let art, beschreibung, name = ""; + if (element.Zeit.toString().indexOf(' + ') > 0) { + // in der Zeitspalte stehen mehrere Uhrzeiten. + let tempTimes = element.Zeit.toString().split(' + '); + tempTimes.forEach(function (time) { + let mom = moment(time, 'HH:mm'); + times.push([mom.hour(), mom.minute()]); + }) + } else if (element.Zeit.toString().indexOf(' - ') > 0) { + // in der Zeitspalte stehen mehrere Uhrzeiten als Zeitspanne + let tempTimes = element.Zeit.toString().split(' - '); + + let mom = moment(tempTimes[0], 'HH:mm'); + let momEnd = moment(tempTimes[1], 'HH:mm'); + times.push([mom.hour(), mom.minute(), momEnd.hour(), momEnd.minute()]); + } else { + if (element.Zeit) { + let mom = moment(element.Zeit); + times.push([mom.hour(), mom.minute()]); + } else { + times.push([0, 0]); + } + } + + art = element.Dienst.trim(); + + if (element.hasOwnProperty('__EMPTY')) { + name = element.__EMPTY.trim(); + } + + if (element.hasOwnProperty('Bemerkung') + && element.Bemerkung.toString().search(/\d\d:\d\d\s/) == -1) { + beschreibung = element.Bemerkung; + } + + times.forEach(time => { + var termin = { + datum : day.clone().hour(time[0]).minute(time[1]), + art : art, + beschreibung: beschreibung, + name : name + } + if (time.length === 4) { + // Wenn die Zeit mehr Werte hat, dann behandle die nächsten 2 als ende + termin.end = day.clone().hour(time[2]).minute(time[3]); + termin.dontSetDurationFromRules = true; + } + vm.addShift(new Shift(termin)); + }); + + } + }); + }, + + changeTime: function (hours) { + let temp = this.shifts; + this.shifts = []; + temp.forEach(shift => { + shift.updateBeginn(hours); + this.addShift(shift); + }); + }, + + updateBeginn: function (shift, hours) { + shift.updateBeginn(hours); + this.shifts.splice(this.shifts.indexOf(shift), 1, shift); + }, + + parseForStuttgart: function (dp) { + var arr = XLSX.utils.sheet_to_json(dp, { + header : "A", + blankrows: false, + }); + var vm = this; + var day; + + this.makeToast("Stuttgarter Dienstplan erkannt."); + + arr.forEach(element => { + moment.locale(vm.config.moment.parse_language); + if (element.hasOwnProperty('C')) { + day = moment(element.C); + } + if (element.hasOwnProperty('D') && moment(element.D, "HH:mm").isValid()) { + var termin = { + ort : element.H ? element.H.trim() : "", + art : element.E ? element.E.trim() : "", + beschreibung: element.H ? element.H.trim() : "", + name : element.F ? element.F.trim() : "" + } + var time = day.clone(); + if (typeof (element.D) === "object") { + time = moment(element.D); + termin.datum = day.clone().hour(time.hour()).minute(time.minute()); + } else if (element.D.indexOf("-") > -1) { + var tArray = element.D.split(" - "); + time = moment(tArray[0], "HH:mm"); + termin.datum = day.clone().hour(time.hour()).minute(time.minute()); + termin.ende = moment(tArray[1], "HH:mm").format("HH:mm"); + } + vm.addShift(new Shift(termin)); + + } + }); + }, + + parseForX: function (dp, sheetName) { + moment.locale(this.config.moment.parse_language); + var arr = XLSX.utils.sheet_to_json(dp, { + header : "A", + blankrows: false, + }); + var vm = this; + var month = moment(sheetName.substr(sheetName.indexOf(" ") + 1), "MMMM YYYY", "de"); + var day; + + arr.forEach(element => { + var art, name, beschreibung = null; + var time = moment().hour(0).minute(0); + try { + if (element.hasOwnProperty('A')) { + day = moment(element.A, "D.", "de"); + day = Number(element.A); + } + if ((!element.F && !element.H && !element.J) || element.I == "Spielort/ Extras" || element.J == "Spielort/Extras") { + + } else { + + + if (element.hasOwnProperty("D") && element.hasOwnProperty("E")) { + // Probe + time = moment(element.D); + art = element.E.trim(); + name = element.F ? element.F.trim() : ""; + beschreibung = typeof (element.I) != 'undefined' ? element.I.trim() : + typeof (element.J) != 'undefined' ? element.J.trim() : + ""; + } else if (element.hasOwnProperty("G") && element.hasOwnProperty("H")) { + // Vorstellung + time = moment(element.G); + art = "VS"; + name = element.H.trim(); + beschreibung = typeof (element.I) != 'undefined' ? element.I.trim() : + typeof (element.J) != 'undefined' ? element.J.trim() : + ""; + } else if (element.hasOwnProperty("I")) { + // Spielort/Extras + beschreibung = element.I.trim(); + name = name ? name : "Spielort/Extras"; + } else if (element.hasOwnProperty("J")) { + // Spielort/Extras Fallback + beschreibung = element.J.trim(); + name = name ? name : "Spielort/Extras"; + } + + var datumStr = day + '.' + month.clone().format("MM.YY") + time.format(" HH:mm"); + var termin = { + ort : "", + art : art, + beschreibung: beschreibung, + name : name, + datum : moment(datumStr, "D.MM.YY HH:mm") + } + vm.addShift(new Shift(termin)); + } + } catch (error) { + console.error("Fehler beim Parsen", element, error); + } + + }); + }, + + parsePickedFormat: function (format) { + this.makeToast(`Versuche ${format} zu lesen.`); + switch (format) { + case "Erfurt": + this.parseForErfurt(this.workbook.Sheets["Dienstplan"]); + break; + case "Stuttgart": + var sheetName = this.workbook.SheetNames[0]; + this.parseForStuttgart(this.workbook.Sheets[sheetName]); + break; + case "X": + var sheetName = this.workbook.SheetNames[0]; + this.parseForX(this.workbook.Sheets[sheetName], sheetName); + break; + default: + break; + } + + this.workbook = null; + }, + + addShift: function (shift) { + this.shifts.push(shift); + }, + + removeShift: function (shift) { + this.shifts.splice(this.shifts.indexOf(shift), 1); + this.makeToast(shift.VEventTitle + " gelöscht"); + }, + + cleanStorage: function () { + this.shifts = []; + this.makeToast("Alle Einträge gelöscht"); + }, + + createDownloadFile: function () { + var vCal = new VCalendar("Dienstplan Kalender"); + this.shifts.forEach(function (shift) { + vCal.add(shift.toVEvent()); + }); + var calString = vCal.toString(); + this.blob = new Blob([calString], { + type: 'text/plain' + }); + + if (this.icsFile !== null) { + window.URL.revokeObjectURL(this.icsFile); + } + + this.icsFile = window.URL.createObjectURL(this.blob); + this.makeToast(this.saveto + " erstellt."); + }, + + downloadFile: function () { + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob(this.blob, this.saveto); + } + }, + + selectShift: function (shift) { + this.selectedShift = Shift.thaw(shift); + + this.keepShift = shift; + M.updateTextFields(); + this.openModal('shiftModal'); + }, + + saveChanges: function (changedShift) { + this.shifts.splice(this.shifts.indexOf(this.keepShift), 1, changedShift); + + this.closeModal('shiftModal'); + this.keepShift = ''; + this.selectedShift = ''; + }, + + discardChanges: function (changedShift) { + this.closeModal('shiftModal'); + this.keepShift = ''; + this.selectedShift = ''; + }, + + editRule: function (rule) { + this.selectedRule = Rule.thaw(rule); + this.selectedRuleIndex = this.rules.indexOf(rule); + this.openModal('ruleModal'); + }, + + saveRule: function () { + this.rules[this.selectedRuleIndex] = this.selectedRule; + + this.closeModal('ruleModal'); + this.selectedRule = new Rule(); + }, + + discardRule: function () { + this.closeModal('ruleModal'); + + this.selectedRule = new Rule(); + }, + + applyRules: function () { + var shifts = this.shifts; + shifts.forEach(function (shift) { + Shift.setDurationFromRules(shift, this.rules); + }); + this.shifts = shifts; + } + + }, + directives: { + 'edit-focus': function (el, value) { + if (value) { + el.focus(); + } + } + }, + mounted : function () { + M.AutoInit(); + var els = document.querySelectorAll('.timepicker'); + this.timepickers = M.Timepicker.init(els, this.config.timepicker); + var el = document.querySelector(".stepper"); + this.stepper = new MStepper(el, this.config.stepper); + } +}) diff --git a/css/page.css b/css/page.css new file mode 100644 index 0000000..ddd794c --- /dev/null +++ b/css/page.css @@ -0,0 +1,44 @@ +.custom-file-control:lang(de)::after { + content: "Datei wählen..."; +} + +.custom-file-control:lang(de)::before { + content: "Durchsuchen"; +} + +#drop { + padding: 3em; + outline: 2px dashed black; + outline-offset: -10px; +} + +.edit { + display: none; +} + +footer { + padding-bottom: 15px; +} + +.click-me { + cursor: pointer; +} + +#body { + display: flex; + min-height: calc(100vh - 64px); + flex-direction: column; + padding-top: 56px; +} + +main { + flex: 1 0 auto; +} + +.deleteMe { + cursor: pointer; +} + +.modal { + max-height: 70%; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..edfa6b3 --- /dev/null +++ b/index.html @@ -0,0 +1,274 @@ + + + + + + Dienstplan Converter + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ +
+ + + + + + + + + + + + + + diff --git a/js/shift.js b/js/shift.js new file mode 100644 index 0000000..2a7f6dd --- /dev/null +++ b/js/shift.js @@ -0,0 +1,319 @@ +var DATE_LOCALE_FORMAT = "D.M.YY"; //10.2.16 +var DATE_INPUT_FORMAT = "YYYY-MM-DD"; //2016-02-10 +var TIME_FORMAT = "HH:mm"; // 17:30 +var WEEKDAY_FORMAT = "dddd"; //Monday +var MOMENT_LOCALE = "de"; +var TIMEZONE_NAME = "Europe/Berlin"; +//requires moment.js + +Array.prototype.asChipData = function () { + return this.map((e, i) => { + return { + tag: e, + id : i + }; + }) +} +Array.prototype.fromChipData = function () { + return this.map(e => { + return e.tag; + }) +} + +function Rule() { + var options = {}; + + if (arguments[0]) options = arguments[0]; + + var default_args = { + 'arten': [], + 'dauer': 60, + 'titel': [], + 'name' : "Standard" + } + for (var index in default_args) { + if (typeof options[index] == "undefined") options[index] = default_args[index]; + } + + this._duration = options.dauer; + this._arten = options.arten; + this._titel = options.titel; + this._name = options.name; +} + +Rule.prototype = { + get Dauer() { + return this._duration; + }, + set Dauer(value) { + this._duration = value; + }, + + get Arten() { + return this._arten.asChipData(); + }, + set Arten(value) { + this._arten = value.fromChipData(); + }, + + get Titel() { + return this._titel.asChipData(); + }, + set Titel(value) { + this._titel = value.fromChipData(); + }, + + get Name() { + return this._name; + }, + set Name(value) { + this._name = value; + }, +}; +Rule.prototype.fits = function (art, title) { + var artMatch = false; + var nameMatch = false; + + if (this._arten.length == 0) artMatch = true; + else { + this._arten.forEach(function (el, i) { + if (art.includes(el.toLowerCase())) artMatch = true; + }); + } + + if (this._titel.length == 0) nameMatch = true; + else { + this._titel.forEach(function (el, i) { + if (name.includes(el.toLowerCase())) nameMatch = true; + }); + } + + return artMatch && nameMatch; +} +Rule.thaw = function (json) { + return new Rule({ + dauer: json._duration, + name : json._name, + arten: json._arten, + titel: json._titel + }); +} +Rule.defaults = function () { + var rules = []; + var input = [{ + name : "EF #01", + dauer: 60, + arten: ['VS'], + titel: ['Kinderkonzert'] + }, + { + name : "EF #02", + dauer: 120, + arten: ['VS'], + titel: ["Expeditionskonzert", "Sinfoniekonzert"] + }, + { + name : "EF #03", + dauer: 150, + arten: ['Oa', 'GP'], + titel: ['Expeditionskonzert'] + }, + { + name : "EF #04", + dauer: 60, + arten: ['Oa', 'GP'], + titel: ['Expeditionskonzert'] + }, + { + name : "EF #05", + dauer: 150, + arten: ['Oa', 'OSP'] + }, + { + name : "EF #06", + dauer: 180, + arten: ["VS", "BO", "OHP", "HP", "GP", "Prem", "WA"] + }, + { + name : "Standard", + dauer: 60 + } + ]; + input.forEach(e => { + rules.push(new Rule(e)); + }); + + return rules; +} +var DURATION_RULES = Rule.defaults(); + +function Shift() { + var options = {}; + + if (arguments[0]) options = arguments[0]; + + var default_args = { + 'art' : "", + 'name' : "DUMMY", + 'datum' : moment(), + 'end' : moment(), + 'beschreibung': "", + 'ort' : "", + } + for (var index in default_args) { + if (typeof options[index] == "undefined") options[index] = default_args[index]; + } + + this.Datum = options.datum.format(DATE_INPUT_FORMAT); + this.Beginn = options.datum.format(TIME_FORMAT); + this.Ende = options.end.format(TIME_FORMAT); + this.Art = options.art; + this.Beschreibung = options.beschreibung; + this.Name = options.name; + this.Ort = options.ort; + + if (typeof options.ende != "undefined") { + this.Ende = options.ende; + } else { + if (!options.dontSetDurationFromRules) { + Shift.setDurationFromRules(this, DURATION_RULES); + } + } + +} + +Shift.prototype = { + get Wochentag() { + return this._date.clone().locale(MOMENT_LOCALE).format(WEEKDAY_FORMAT); + }, + set Wochentag(value) { + throw "kann nicht gesetzt werden."; + }, + + get FormattedDatum() { + return this._date.clone().format(DATE_LOCALE_FORMAT); + }, + set FormattedDatum(value) { + var dateMoment = moment(value, DATE_LOCALE_FORMAT); + this._date = dateMoment; + }, + + get Datum() { + return this._date.clone().format(DATE_INPUT_FORMAT); + }, + set Datum(value) { + var dateMoment = moment(value).startOf('day'); + this._date = dateMoment.clone(); + }, + + get Beginn() { + return this._begin.clone().format(TIME_FORMAT); + }, + set Beginn(value) { + var dateMoment = moment(this.Datum + " " + value, DATE_INPUT_FORMAT + " " + TIME_FORMAT); + this._begin = dateMoment.clone(); + }, + get Ende() { + var ende = this._begin.clone().add(this._duration).format(TIME_FORMAT); + return ende; + }, + set Ende(value) { + var dateMoment = moment(this.Datum + " " + value, DATE_INPUT_FORMAT + " " + TIME_FORMAT); + if (this._begin > dateMoment) { + dateMoment.add(1, 'd'); + } + this._duration = moment.duration(dateMoment.diff(this._begin)); + }, + + get Dauer() { + return this._duration; + }, + set Dauer(duration) { + this._duration = duration; + }, + + get Name() { + return this._name; + }, + set Name(value) { + this._name = value ? value.trim() : ""; + }, + + get Ort() { + return this._ort; + }, + set Ort(value) { + this._ort = value ? value.trim() : ""; + }, + + get VEventTitle() { + return this.Name + " " + this.Art; + }, + set VEventTitle(value) { + throw "kann nicht gesetzt werden."; + }, + + get Art() { + return this._kind; + }, + set Art(value) { + + this._kind = value ? value.trim() : ""; + }, + + get Beschreibung() { + return this._description; + }, + set Beschreibung(value) { + this._description = value ? value.trim() : ""; + } + +} +Shift.setDurationFromRules = function (shift, rules) { + 'use strict'; + var isAllDayEvent = shift.Beginn == "00:00"; + if (isAllDayEvent) { + shift.Dauer = moment.duration(24, 'h').locale(MOMENT_LOCALE); + return; + } + var art = shift.Art.toLowerCase(); + var titel = shift.Name.toLowerCase(); + var duration = 60; + for (var rIndex in rules) { + var rule = rules[rIndex]; + + if (rule.fits(art, titel)) { + duration = rule.Dauer; + break; + } + } + shift.Dauer = moment.duration(duration, 'm').locale(MOMENT_LOCALE); +} +Shift.prototype.toVEvent = function () { + var end = this._begin.clone().add(this._duration); + return new VEvent({ + startMoment: this._begin, + endMoment : end, + title : this.VEventTitle, + description: this.Beschreibung, + location : this.Ort + }); +}; +Shift.thaw = function (jsonShift) { + moment.locale(MOMENT_LOCALE); + var begin = moment(jsonShift._begin); + var shift = new Shift({ + art : jsonShift._kind, + name : jsonShift._name, + datum : begin, + beschreibung: jsonShift._description, + ort : jsonShift._ort, + + }); + shift.id = jsonShift.id; + shift.Dauer = moment.duration(jsonShift._duration); + return shift; +}; +Shift.prototype.updateBeginn = function (hour) { + this._begin = this._begin.add('hours', hour); +} diff --git a/js/vcal.js b/js/vcal.js new file mode 100644 index 0000000..b8e4c78 --- /dev/null +++ b/js/vcal.js @@ -0,0 +1,154 @@ +var VCAL_DATETIME_FORMAT = "YMMDD[T]HHmmss"; //20160216T130500 +var UID_FORMAT = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; +var NEW_LINE = "\r\n"; +var productID = window.title; + +function VMeta() { + this.properties = new Map(); + this.children = []; + this.tag = "VMETA"; +} +VMeta.formatDate = function (aMoment) { + 'use strict'; + return aMoment.format(VCAL_DATETIME_FORMAT); +}; +VMeta.generateUID = function () { + 'use strict'; + return UID_FORMAT.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, + v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +}; +VMeta.prototype.set = function (key, value) { + 'use strict'; + this.properties.set(key, value); +}; +VMeta.prototype.get = function (key) { + 'use strict'; + this.properties.get(key); +}; +VMeta.prototype.add = function (child) { + 'use strict'; + this.children.push(child); +}; +VMeta.prototype.mapToString = function () { + 'use strict'; + var output = ""; + this.properties.forEach(function (value, key, map) { + output += (NEW_LINE + key + ":" + value); + }); + return output; +}; +VMeta.prototype.childrenToString = function () { + 'use strict'; + var output = ""; + this.children.forEach(function (child) { + output += (NEW_LINE + child.toString()); + }); + return output; +}; +VMeta.prototype.toString = function () { + 'use strict'; + var output = "BEGIN:" + this.tag; + output += this.mapToString(); + output += this.childrenToString(); + output += (NEW_LINE + "END:" + this.tag); + return output; +}; + +function VCalendar(calendarName) { + 'use strict'; + VMeta.call(this); + this.add(VTimeZone.Berlin()); + this.tag = "VCALENDAR"; + this.set('X-WR-CALNAME', calendarName); + this.set('VERSION', '2.0'); + this.set('PRODID', productID); + this.set('METHOD', "PUBLISH"); + this.set('X-WR-TIMEZONE', "Europe/Berlin"); + this.set('CALSCALE', "GREGORIAN"); +}; +VCalendar.prototype = Object.create(VMeta.prototype); +VCalendar.prototype.constructor = VCalendar; + +function VEvent() { + var evt = window.event || arguments[1] || arguments.callee.caller.arguments[0]; + var target = evt.target || evt.srcElement; + + var options = {}; + + if (arguments[0]) options = arguments[0]; + + var default_args = { + 'tzName' : "Europe/Berlin", + 'startMoment' : moment(), + 'endMoment' : moment(), + 'uid' : VMeta.generateUID(), + 'dtStamp' : VMeta.formatDate(moment()) + "Z", + 'title' : "", + 'description' : "", + 'location' : "", + 'organizer' : "", + + } + for (var index in default_args) { + if (typeof options[index] == "undefined") options[index] = default_args[index]; + } + + VMeta.call(this); + this.tag = "VEVENT"; + + this.set('DTSTART;TZID=' + options.tzName, VMeta.formatDate(options.startMoment)); + this.set('DTEND;TZID=' + options.tzName, VMeta.formatDate(options.endMoment)); + this.set('DTSTAMP', options.dtStamp); + this.set('UID', options.uid); + this.set('SUMMARY', options.title); + this.set('DESCRIPTION', options.description); + this.set('LOCATION', options.location) + this.set('CLASS', "PUBLIC"); + this.set('TRANSP', "OPAQUE"); + this.set('STATUS', "CONFIRMED"); + this.set('ORGANIZER', options.organizer); +} + +VEvent.prototype = Object.create(VMeta.prototype); +VEvent.prototype.constructor = VEvent; + +function VTimeZone() { + VMeta.call(this); + this.tag = "VTIMEZONE"; +} +VTimeZone.prototype = Object.create(VMeta.prototype); +VTimeZone.prototype.constructor = VTimeZone; +VTimeZone.Berlin = function () { + var tz = new VTimeZone(); + tz.children = [VTimeDef.MESZ(), VTimeDef.MEZ()]; + tz.set("TZID", "Europe/Berlin"); + return tz; +} + +function VTimeDef(name) { + VMeta.call(this); + this.tag = name; +} +VTimeDef.prototype = Object.create(VMeta.prototype); +VTimeDef.prototype.constructor = VTimeDef; +VTimeDef.MESZ = function () { + var timedef = new VTimeDef("DAYLIGHT"); + timedef.set("TZOFFSETFROM", "+0100"); + timedef.set("RRULE", "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU"); + timedef.set("DTSTART", "19819329T020000"); + timedef.set("TZNAME", "MESZ"); + timedef.set("TZOFFSETTO", "+0200"); + return timedef; +} +VTimeDef.MEZ = function () { + var timedef = new VTimeDef("STANDARD"); + timedef.set("TZOFFSETFROM", "+0200"); + timedef.set("RRULE", "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU"); + timedef.set("DTSTART", "19961027T030000"); + timedef.set("TZNAME", "MEZ"); + timedef.set("TZOFFSETTO", "+0100"); + return timedef; +}