Version Changes
This commit is contained in:
commit
df89539710
259
app.js
Normal file
259
app.js
Normal file
@ -0,0 +1,259 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
M.AutoInit();
|
||||
});
|
||||
/* $(document).ready(function () {
|
||||
$('.modal').modal();
|
||||
$('.tabs').tabs();
|
||||
$('.fixed-action-btn').floatingActionButton();
|
||||
}); */
|
||||
|
||||
//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 rules = JSON.parse(localStorage.getItem(RULE_STORAGE_KEY)) || Rule.defaults();
|
||||
ruleStorage.uid = rules.length;
|
||||
return rules;
|
||||
},
|
||||
save: function (rules) {
|
||||
'use strict';
|
||||
var json = JSON.stringify(rules);
|
||||
localStorage.setItem(RULE_STORAGE_KEY, json);
|
||||
}
|
||||
};
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
shifts: shiftStorage.fetch(),
|
||||
rules: ruleStorage.fetch(),
|
||||
icsFile: null,
|
||||
blob: null,
|
||||
dp_sheet: '',
|
||||
deletedShift: '',
|
||||
remaining: shiftStorage.count(),
|
||||
selectedShift: '',
|
||||
selectedRule: '',
|
||||
saveto: 'dienstplan.ics',
|
||||
uploadFileName: "",
|
||||
config: {
|
||||
moment: {
|
||||
parse_formats: [
|
||||
"ddd, DD/ MMM. 'YY HH:mm",
|
||||
"ddd, DD/ MMM. YYYY HH:mm"
|
||||
],
|
||||
parse_language: 'en',
|
||||
display_language: 'de'
|
||||
},
|
||||
toast_length: 3000
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
shifts: {
|
||||
handler: function (shifts) {
|
||||
'use strict';
|
||||
shiftStorage.save(shifts);
|
||||
this.remaining = shifts.length;
|
||||
M.toast({html:"Änderungen gespeichert.", displayLength:this.config.toast_length});
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
rules: {
|
||||
handler: function (rules) {
|
||||
'use strict';
|
||||
ruleStorage.save(rules);
|
||||
M.toast({html:"Änderungen gespeichert.", displayLength:this.config.toast_length});
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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]";
|
||||
M.toast({html:this.uploadFileName + " ausgewählt", displayLength:this.config.toast_length});
|
||||
},
|
||||
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,
|
||||
});
|
||||
if(workbook.Sheets["Dienstplan"] == null){
|
||||
file.status = "error";
|
||||
} else {
|
||||
vm.parseWorksheet(workbook.Sheets["Dienstplan"]);
|
||||
file.status = "success";
|
||||
}
|
||||
};
|
||||
reader.readAsBinaryString(file);
|
||||
M.toast({html:this.uploadFileName + " wird eingelesen",displayLength: this.config.toast_length});
|
||||
},
|
||||
parseWorksheet: function (dp) {
|
||||
var arr = XLSX.utils.sheet_to_row_object_array(dp, {
|
||||
range: 1
|
||||
});
|
||||
var vm = this;
|
||||
var day;
|
||||
arr.forEach(element => {
|
||||
moment.locale(vm.config.moment.parse_language);
|
||||
if (element.hasOwnProperty('Datum')) {
|
||||
day = moment(element.Datum);
|
||||
}
|
||||
if (element.hasOwnProperty('Dienst')) {
|
||||
var time = moment(element.Zeit);
|
||||
var date = day.hour(time.hour()).minute(time.minute());
|
||||
var bemerkung = !element.Bemerkung ? "" : element.Bemerkung.trim();
|
||||
var art = !element.Dienst ? "" : element.Dienst.trim();
|
||||
var name = !element.__EMPTY ? "" : element.__EMPTY.trim();
|
||||
console.log({day, element});
|
||||
vm.addShift(new Shift(art, name, date, bemerkung));
|
||||
}
|
||||
});
|
||||
},
|
||||
addShift: function (shift) {
|
||||
this.shifts.push(shift);
|
||||
},
|
||||
removeShift: function (shift) {
|
||||
this.shifts.splice(this.shifts.indexOf(shift), 1);
|
||||
M.toast({html:shift.VEventTitle + " gelöscht",displayLength: this.config.toast_length});
|
||||
},
|
||||
cleanStorage: function () {
|
||||
this.shifts = [];
|
||||
M.toast({html:"Alle Einträge gelöscht",displayLength: this.config.toast_length});
|
||||
},
|
||||
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);
|
||||
M.toast({html:this.saveto + " erstellt.",displayLength: this.config.toast_length});
|
||||
},
|
||||
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();
|
||||
$('#shiftModal').modal('open');
|
||||
},
|
||||
saveChanges: function (changedShift) {
|
||||
this.shifts.splice(this.shifts.indexOf(this.keepShift), 1, changedShift);
|
||||
|
||||
$('#shiftModal').modal('close');
|
||||
this.keepShift = '';
|
||||
this.selectedShift = '';
|
||||
|
||||
},
|
||||
discardChanges: function (changedShift) {
|
||||
$('#shiftModal').modal('close');
|
||||
this.keepShift = '';
|
||||
this.selectedShift = '';
|
||||
},
|
||||
selectRule: function (rule) {
|
||||
this.selectedRule = new Rule(rule.duration, rule.art, rule.title);
|
||||
this.keepRule = rule;
|
||||
|
||||
$('#rule_arten').chips({
|
||||
data: this.selectedRule.Arten
|
||||
});
|
||||
$('#rule_titel').chips({
|
||||
data: this.selectedRule.Titel
|
||||
});
|
||||
$('#ruleModal').modal('open');
|
||||
},
|
||||
saveRule: function (changedRule) {
|
||||
changedRule.Arten = $('#rule_arten').chips('data');
|
||||
changedRule.Titel = $('#rule_titel').chips('data');
|
||||
|
||||
this.rules.splice(this.rules.indexOf(this.keepRule), 1, changedRule);
|
||||
|
||||
$('#ruleModal').modal('close');
|
||||
this.keepRule = '';
|
||||
this.selectedRule = '';
|
||||
},
|
||||
discardRule: function (changedRule) {
|
||||
this.rules.splice(this.rules.indexOf(this.keepRule), 1, this.keepRule);
|
||||
|
||||
$('#ruleModal').modal('close');
|
||||
this.keepRule = '';
|
||||
this.selectedRule = '';
|
||||
}
|
||||
|
||||
},
|
||||
directives: {
|
||||
'edit-focus': function (el, value) {
|
||||
if (value) {
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
44
css/page.css
Normal file
44
css/page.css
Normal file
@ -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%;
|
||||
}
|
||||
217
index.html
Normal file
217
index.html
Normal file
@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Dienstplan Converter</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" href="/favicons/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/favicons/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#e6b300">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
|
||||
<link rel="stylesheet" href="css/page.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="navbar-fixed">
|
||||
<nav class="nav-extended indigo">
|
||||
<div class="nav-wrapper container ">
|
||||
<span class="brand-logo">Dienstplan Converter</span>
|
||||
<a href="#" data-activates="mobile-demo" class="button-collapse"><i class="material-icons">menu</i></a>
|
||||
<ul id="nav-mobile" class="right hide-on-med-and-down">
|
||||
<li> </li>
|
||||
</ul>
|
||||
<ul class="side-nav" id="mobile-demo">
|
||||
</ul>
|
||||
<ul class="tabs tabs-transparent">
|
||||
<li class="tab"><a class="active" href="#Dienste">Dienste</a></li>
|
||||
<li class="tab"><a href="#Einstellungen">Regeln</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="body">
|
||||
<main class="container">
|
||||
<div class="card" id="Dienste">
|
||||
<div class="card-content" v-if="shifts.length > 0">
|
||||
<h3 class="card-title">Dienste</h3>
|
||||
<table class="highlight" >
|
||||
<thead class="">
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Wochentag</th>
|
||||
<th>Titel</th>
|
||||
<th>Uhrzeit</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="s in shifts" :key="s.id" @click="selectShift(s)">
|
||||
<td class="text-xs-right align-middle">
|
||||
{{s.FormattedDatum}}
|
||||
</td>
|
||||
<td class="">
|
||||
{{s.Wochentag}}
|
||||
</td>
|
||||
<td class="">
|
||||
{{s.VEventTitle}}
|
||||
</td>
|
||||
<td class="">
|
||||
{{s.Beginn}} - {{s.Ende}}
|
||||
</td>
|
||||
<td class="">
|
||||
<a class="btn-flat red-text wave" @click.stop="removeShift(s)"><i class="material-icons">delete</i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="5" class="text-right"> {{shifts.length }} Dienste</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-content" v-if="shifts.length < 1">
|
||||
Noch keine Dienste da.
|
||||
</div>
|
||||
<div class="card-action" v-if="shifts.length < 1">
|
||||
<label class="btn green tooltipped" data-position="left" data-tooltip="Dienstplan einlesen" for="fileInput">
|
||||
<i class="material-icons">publish</i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-action-btn">
|
||||
<a class="btn-floating btn-large orange darken-2 waves-effect">
|
||||
<i class="large material-icons">work</i>
|
||||
</a>
|
||||
<ul>
|
||||
<li>
|
||||
<label class="btn-floating green tooltipped" data-position="left" data-tooltip="Dienstplan einlesen" for="fileInput">
|
||||
<i class="material-icons">publish</i>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn-floating blue tooltipped" data-position="left" data-tooltip="Kalenderdatei erstellen" @click="createDownloadFile" :disabled=" (remaining > 0) ? null : 'disabled'">
|
||||
<i class="material-icons">description</i>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a class="btn-floating yellow tooltipped" data-position="left" data-tooltip="Kalenderdatei herunterladen" :href="icsFile" v-bind:class="[ icsFile ? '' : 'disabled']" download="dienstplan.ics" @click="downloadFile">
|
||||
<i class="material-icons">get_app</i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="btn-floating red tooltipped" data-position="left" data-tooltip="Tabelle leeren" @click="cleanStorage" :disabled=" (remaining > 0) ? null : 'disabled'"><i class="material-icons">clear_all</i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card" id="Einstellungen">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Regeln für die Dauer</h3>
|
||||
<div>
|
||||
<span class="chip orange">Titel der Veranstaltung</span>
|
||||
<span class="chip indigo">Art des Dienstes</span>
|
||||
</div>
|
||||
<ul class="collection highlight">
|
||||
<li class="collection-item" v-for="r in rules" :key="rules.indexOf(r)" @click="selectRule(r)">
|
||||
<span v-if="rules.indexOf(r) != 0" class="title">sonst </span>
|
||||
<span class="title" v-if="rules.indexOf(r) < rules.length -1">wenn: </span>
|
||||
<span class="secondary-content">{{ r.duration }} min</span>
|
||||
<div>
|
||||
<span v-if="Array.isArray(r.art)">
|
||||
<span class="chip indigo" :class="{'lighten-3' : r.art.length > 1, 'lighten-2' : r.art.length == 1}" v-for="a in r.art" :key="r.art.indexOf(a)">{{a}}</span>
|
||||
</span>
|
||||
<span v-if="Array.isArray(r.title)">
|
||||
<span class="chip orange" :class="{'lighten-3' : r.title.length > 1, 'lighten-2' : r.title.length == 1}" v-for="t in r.title" :key="r.title.indexOf(t)">{{t}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
<div id="ruleModal" class="modal bottom-sheet active">
|
||||
<div class="modal-content">
|
||||
<h4>Regel anpassen</h4>
|
||||
<div class="row">
|
||||
<div class="range-field col m6 s12">
|
||||
<label class="active" for="rule_duration">Dauer in Minuten: {{selectedRule.Dauer}}</label>
|
||||
<input id="rule_duration" type="range" v-model="selectedRule.duration" min="30" max="300" step="10">
|
||||
</div>
|
||||
<div class="input-field col s12 m8 l6">
|
||||
<label class="active" for="shift_kind">Arten</label>
|
||||
<div id="rule_arten" class="chips-initial"></div>
|
||||
</div>
|
||||
<div class="input-field col m8 l6 s12">
|
||||
<label class="active" for="rule_titel">Titel</label>
|
||||
<div id="rule_titel" class="chips-inital"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="waves-effect btn-flat waves-green" @click="saveRule(selectedRule)">Speichern</button>
|
||||
<button class="waves-effect btn-flat waves-red" @click="discardRule(selectedRule)">Verwerfen</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="shiftModal" class="modal bottom-sheet">
|
||||
<div class="modal-content">
|
||||
<h4>{{ selectedShift.VEventTitle || 'kein Titel'}}</h4>
|
||||
<div class="row">
|
||||
<div class="input-field col s6 ">
|
||||
<input id="shift_name" type="text" v-model="selectedShift.Name" placeholder="kein Titel">
|
||||
<label class="active" for="shift_name">Titel</label>
|
||||
</div>
|
||||
<div class="input-field col s6 ">
|
||||
<input id="shift_kind" type="text" v-model="selectedShift.Art" placeholder="keine Art">
|
||||
<label class="active" for="shift_kind">Titel</label>
|
||||
</div>
|
||||
<div class="input-field col s6 ">
|
||||
<input id="shift_date" type="date" class="datepicker" v-model="selectedShift.Datum" placeholder="Datum">
|
||||
<label class="active" for="shift_date">Datum</label>
|
||||
</div>
|
||||
<div class="input-field col s6">
|
||||
<input id="shift_begin" type="time" v-model="selectedShift.Beginn" placeholder="Uhrzeit" class="timepicker" :data-default="selectedShift.Beginn">
|
||||
<label class="active" for="shift_begin">Anfang</label>
|
||||
</div>
|
||||
<div class="input-field col s6 ">
|
||||
<input id="shift_end" type="time" v-model="selectedShift.Ende" placeholder="Ende" class="timepicker">
|
||||
<label class="active" for="shift_end">Ende</label>
|
||||
</div>
|
||||
<div class="input-field col s6 ">
|
||||
<input id="shift_desc" type="text" v-model="selectedShift.Beschreibung" placeholder="keine Beschreibung">
|
||||
<label class="active" for="shift_desc">Titel</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="waves-effect btn-flat waves-green" @click="saveChanges(selectedShift)">Speichern</button>
|
||||
<button class="waves-effect btn-flat waves-red" @click="discardChanges(selectedShift)">Verwerfen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file"
|
||||
name="fileInput"
|
||||
id="fileInput"
|
||||
@change="onFileChange"
|
||||
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
style="display:none;">
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.14.1/xlsx.full.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.23.0/moment-with-locales.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
|
||||
<script src="js/shift.js"></script>
|
||||
<script src="js/vcal.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
207
js/shift.js
Normal file
207
js/shift.js
Normal file
@ -0,0 +1,207 @@
|
||||
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 () {
|
||||
var chips = [];
|
||||
this.forEach(function (el, index, array) {
|
||||
chips.push({
|
||||
tag: el,
|
||||
id: index
|
||||
});
|
||||
});
|
||||
return chips;
|
||||
}
|
||||
Array.prototype.fromChipData = function () {
|
||||
var strings = [];
|
||||
this.forEach(function (el, id, array) {
|
||||
strings.push(el.tag);
|
||||
});
|
||||
return strings;
|
||||
}
|
||||
|
||||
function Rule(duration, arten, titel) {
|
||||
this.duration = duration;
|
||||
this.art = arten;
|
||||
this.title = titel;
|
||||
}
|
||||
|
||||
Rule.prototype = {
|
||||
get Dauer() {
|
||||
return this.duration;
|
||||
},
|
||||
set Dauer(value) {
|
||||
this.duration = value;
|
||||
},
|
||||
get Arten() {
|
||||
return this.art.asChipData();
|
||||
},
|
||||
set Arten(value) {
|
||||
this.art = value.fromChipData();
|
||||
},
|
||||
get Titel() {
|
||||
return this.title.asChipData();
|
||||
},
|
||||
set Titel(value) {
|
||||
this.title = value.fromChipData();
|
||||
}
|
||||
};
|
||||
Rule.prototype.fits = function (art, title) {
|
||||
var artMatch = false;
|
||||
var nameMatch = false;
|
||||
|
||||
if (this.art.length == 0) artMatch = true;
|
||||
else {
|
||||
this.art.forEach(function (el, i) {
|
||||
if (art.includes(el.toLowerCase())) artMatch = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (this.title.length == 0) nameMatch = true;
|
||||
else {
|
||||
this.title.forEach(function (el, i) {
|
||||
if (name.includes(el.toLowerCase())) nameMatch = true;
|
||||
});
|
||||
}
|
||||
|
||||
return artMatch && nameMatch;
|
||||
}
|
||||
Rule.defaults = function () {
|
||||
var rules = [];
|
||||
rules.push(new Rule(60, ['VS'], ['Kinderkonzert']));
|
||||
rules.push(new Rule(120, ['VS'], ["expeditionskonzert", "sinfoniekonzert"]));
|
||||
rules.push(new Rule(150, ['Oa', 'GP'], ['Expeditionskonzert']));
|
||||
rules.push(new Rule(60, ['Oa', 'GP'], ['Expeditionskonzert']));
|
||||
rules.push(new Rule(150, ['Oa', 'OSP'], []));
|
||||
rules.push(new Rule(180, ["VS", "BO", "OHP", "HP", "GP", "Prem", "WA"], []));
|
||||
rules.push(new Rule(60, [], []));
|
||||
rules.push(new Rule(60, [], []));
|
||||
rules.push(new Rule(60, [], []));
|
||||
rules.push(new Rule(60, [], []));
|
||||
rules.push(new Rule(60, [], []));
|
||||
|
||||
return rules;
|
||||
}
|
||||
var DURATION_RULES = Rule.defaults();
|
||||
|
||||
function Shift(kind, name, date, info) {
|
||||
'use strict';
|
||||
this.Datum = date.format(DATE_INPUT_FORMAT);
|
||||
this.Beginn = date.format(TIME_FORMAT);
|
||||
this.Art = kind;
|
||||
this.Beschreibung = info;
|
||||
this.Name = name;
|
||||
Shift.setDurationFromRules(this);
|
||||
}
|
||||
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() {
|
||||
return this._begin.clone().add(this._duration).format(TIME_FORMAT);
|
||||
},
|
||||
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 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) {
|
||||
'use strict';
|
||||
var art = shift.Art.toLowerCase();
|
||||
var name = shift.Name.toLowerCase();
|
||||
var duration = 60;
|
||||
for (var rIndex in DURATION_RULES) {
|
||||
var rule = DURATION_RULES[rIndex];
|
||||
|
||||
if (rule.fits(art, name)) {
|
||||
duration = rule.Dauer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
shift.Dauer = moment.duration(duration, 'm').locale(MOMENT_LOCALE);
|
||||
}
|
||||
Shift.prototype.toVEvent = function () {
|
||||
'use strict';
|
||||
var end = this._begin.clone().add(this._duration);
|
||||
return new VEvent(TIMEZONE_NAME, this._begin, end, this.VEventTitle, this.Beschreibung);
|
||||
};
|
||||
Shift.thaw = function (jsonShift) {
|
||||
'use strict';
|
||||
moment.locale(MOMENT_LOCALE);
|
||||
var begin = moment(jsonShift._begin);
|
||||
var shift = new Shift(jsonShift._kind, jsonShift._name, begin, jsonShift._description);
|
||||
shift.id = jsonShift.id;
|
||||
shift.Dauer = moment.duration(jsonShift._duration);
|
||||
return shift;
|
||||
};
|
||||
128
js/vcal.js
Normal file
128
js/vcal.js
Normal file
@ -0,0 +1,128 @@
|
||||
var VCAL_DATETIME_FORMAT = "YMMDD[T]HHmmss"; //20160216T130500
|
||||
var UID_FORMAT = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
||||
var NEW_LINE = "\r\n";
|
||||
|
||||
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', window.location.href);
|
||||
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(timezoneName, startMoment, endMoment, title, description) {
|
||||
VMeta.call(this);
|
||||
this.tag = "VEVENT";
|
||||
|
||||
this.set('DTSTART;TZID=' + timezoneName, VMeta.formatDate(startMoment));
|
||||
this.set('DTEND;TZID=' + timezoneName, VMeta.formatDate(endMoment));
|
||||
this.set('DTSTAMP', VMeta.formatDate(moment()) + "Z");
|
||||
this.set('UID', VMeta.generateUID());
|
||||
this.set('SUMMARY', title);
|
||||
this.set('DESCRIPTION', description);
|
||||
this.set('CLASS', "PUBLIC");
|
||||
this.set('TRANSP', "OPAQUE");
|
||||
this.set('STATUS', "CONFIRMED");
|
||||
this.set('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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user