function toggleVisibility(idImg, idDiv) {
var div = document.getElementById(idDiv);
var img = document.getElementById(idImg);
if (div.style.display === "none") {
div.style.display = "flex";
img.src = "res/dark/hide.png";
} else {
div.style.display = "none";
img.src = "res/dark/show.png";
}
}
class APIScanner
{
static api;
static settings;
static scanned;
constructor(parent) {
APIScanner.api = parent;
this.qr = null;
this.cameras = [];
}
Start(id, subroute, parser) {
APIScanner.settings = {
subroute: subroute,
parser: parser,
id: id
};
APIScanner.scanned = {primary: {}, secondary: {}};
for (let i in parser) {
if (i!=subroute) {
APIScanner.scanned.secondary[parser[i]] = null;
} }
// Find already parsed IDs
const subData = APIScanner.api.activeRoute.resourcesIndex[id].data.SUB[parser[subroute]];
for (let i in subData) {
APIScanner.scanned.primary[subData[i].ID] = true;
}
this.qr = new QrScanner(
document.getElementById("qrscanner_video"),
APIScanner.parse,
{returnDetailedScanResult: true, highlightScanRegion: true, highlightCodeOutline: true}
);
if (QrScanner.hasCamera()) {
let _this = this;
QrScanner.listCameras(true)
.then(result => _this.cameras = result)
.catch(error => alert(error || 'Camera detection failed.'));
}
this.cameraIndex = 0;
document.getElementById("qrscanner_text").innerHTML = "";
this.qr.start();
}
Stop() {
this.qr.stop();
this.qr.destroy();
this.qr = null;
}
SwitchCamera() {
if (this.qr!==null && QrScanner.hasCamera()) {
++this.cameraIndex;
if (this.cameraIndex>=this.cameras.length) {
this.cameraIndex = 0;
}
this.qr.setCamera(this.cameras[this.cameraIndex].id);
}
}
static parse(result) {
let scan = result.data.split("/");
if (scan.length==3) {
let route = scan[1];
let subid = scan[2];
// Is it a route we should parse?
if (typeof(APIScanner.settings.parser[route])!="undefined") {
const marker = APIScanner.settings.parser[route];
// Is it the primary route and if yes, has this ID been parsed before?
if (route==APIScanner.settings.subroute && typeof(APIScanner.scanned.primary[subid])=="undefined") {
APIScanner.scanned.primary[subid] = true;
let index = -1;
const options = APIScanner.api.activeRoute.options[marker];
for (let i in options) {
if (options[i].ID==subid) {
index = i;
break;
} }
// Has the ID been found?
if (index>=0) {
document.getElementById("qrscanner_text").innerHTML += options[index].NAME + " ";
APIScanner.api.Request("POST", APIScanner.scanned.secondary, APIScanner.settings.id, APIScanner.settings.subroute, subid);
}
} else if(route!=APIScanner.settings.subroute) { // Is it a secondary field?
APIScanner.scanned.secondary[marker] = subid;
} } } }
}
class APIResource // This holds ONE table record
{
constructor(route, group, data) {
this.route = route;
this.group = group;
this.ID = data.ID;
group.resourcesIndex[this.ID] = this;
route.resourcesIndex[this.ID] = this;
this.data = data;
this.show = false;
// TODO: This SHOULD be determined by the client through comparing of data with privileges and not be preprocessed by server. Should it?
this.localAdmin = data.admin;
}
Render(includingBox = false) {
var data = this.data;
data.SHOWBUTTON = this.show;
data.SHOWCONTENT = this.show;
data.SELECTBOX = " ";
var ret = "";
return (includingBox ? "
" : "")
+ this.route.tpl.Render(data, "LIST", (this.route.admin || this.localAdmin ? ":ADMIN" : ""))
+ (includingBox ? "
" : "");
}
}
class APIGroup
{
constructor(route, id, resources) {
this.route = route;
this.ID = id;
this.resources = []; // List of APIResources
this.resourcesIndex = {}; // resources indexed by ID
this.show = true;
this.route.groupsIndex[id] = this;
for (var i in resources) {
this.Add(resources[i]);
}
}
Add(resource) {
this.resources.push(new APIResource(this.route, this, resource));
}
Delete(id) {
var index = this.route.findIndexInEntries(id, this.resources);
delete this.resources[index];
this.resources.splice(index, 1);
}
Clear() {
for (var i in this.resources) {
delete this.resources[i];
}
this.resources = [];
this.resourcesIndex = {};
}
Render() {
var ret = "" + this.route.tpl.Render({"GROUP": this.ID}, "GROUP_BEGIN", "");
for (var k in this.resources) {
ret+= this.resources[k].Render(true);
}
ret+= this.route.tpl.Render({"GROUP": this.ID}, "GROUP_END", "") + " ";
return ret;
}
}
class APIFilter // This holds ONE filter element
{
constructor(data, type) {
this.Set(data, type);
}
Get() {
return {and: this.and, field: this.field, op: this.op, value: this.value};
}
Set(data, type) {
this.and = typeof(data.and)!="undefined" ? data.and : "and";
this.field = typeof(data.field)!="undefined" ? data.field : "none";
this.op = typeof(data.op)!="undefined" ? data.op : "";
this.value = typeof(data.value)!="undefined" ? data.value : ("Multiple"==type ? [] : "");
}
}
class APIRoute // This holds a list of resources
{
constructor(parent, route, info) {
this.parent = parent;
this.route = route;
this.isPage = info.isPage;
this.mainFields = info.mainFields;
this.filter = [];
this.tplFile = info.template;
this.tpl = null;
this.image = typeof(info.image)!="undefined" ? info.image : null;
this.name = info.name;
this.admin = false;
this.options = [];
this.selected = [];
this.dropzones = {};
this.groups = []; // List of APIResourceGroups
this.groupsIndex = {};
this.resourcesIndex = {}; // Index of APIResources by ID linking to entries inside their respective group
}
Add() {
// Get JSON
var send = {};
for (var i in this.mainFields) {
if (this.mainFields[i].add) {
var e = document.getElementById("ADD/"+i);
if (e!=null) {
if (this.mainFields[i].type=="Multiple") {
// Multiple values can be selected
send[i] = Array.from(e.selectedOptions).map(el=>el.value);
} else if (this.mainFields[i].type=="Checkbox") {
send[i] = e.checked;
} else if (e.value=="__NULL__") {
// Null is selected
send[i] = null;
} else {
// Only one value can be selected/input
send[i] = e.value;
}
} else if (this.mainFields[i].type=="DateTime") {
var eDate = document.getElementById("ADD/"+i+"/DATE");
var eTime = document.getElementById("ADD/"+i+"/TIME");
if (null!=eDate && null!=eTime) {
send[i] = eDate.value + " " + eTime.value;
} } } }
this.parent.Request("POST", send);
}
Save(id) {
// Get JSON
var json = this.findEntry(id).data.MAIN;
var send = {};
var element = document.getElementById("LIST/"+id);
element.className = "list_entry";
for (var i in this.mainFields) {
if (this.mainFields[i].edit) {
var e = document.getElementById("LIST/"+id+"/"+i);
if (e!=null) {
if (this.mainFields[i].type=="Multiple") {
// Multiple values can be selected
send[i] = Array.from(e.selectedOptions).map(el=>el.value);
} else if (this.mainFields[i].type=="Checkbox") {
send[i] = e.checked;
} else if (e.value=="__NULL__") {
// Null is selected
send[i] = null;
} else {
// Only one value can be selected/input
send[i] = e.value;
}
} else if (this.mainFields[i].type=="DateTime") {
var eDate = document.getElementById("LIST/"+id+"/"+i+"/DATE");
var eTime = document.getElementById("LIST/"+id+"/"+i+"/TIME");
if (null!=eDate && null!=eTime) {
send[i] = eDate.value + " " + eTime.value;
} } } }
this.parent.Request("PATCH", send, id);
}
Edit(id) {
var _this = this;
var entry = this.findEntry(id);
var element = document.getElementById("LIST/"+id);
element.innerHTML = this.tpl.Render(entry.data, "LIST", ":EDIT");
element.className = "list_entry list_edit";
this.renderHelper(id);
}
Cancel(id) {
var entry = this.findEntry(id);
var element = document.getElementById("LIST/"+id);
element.innerHTML = this.tpl.Render(entry.data, "LIST", ":ADMIN");
element.className = "list_entry";
}
SubShowAdd(id, markers) {
var entry = this.findEntry(id);
let marker;
var data = {"ID": id};
if (Array.isArray(markers)) {
marker = markers[0];
for (let i=1; iel.value);
} else if (this.mainFields[data.field].type=="Checkbox") {
data.value = eValue.checked;
} else if (eValue.value=="__NULL__") {
// Null is selected
data.value = null;
} else {
// Only one value can be selected/input
data.value = eValue.value;
} } }
if ("undefined"!=typeof(this.filter[id])) {
this.filter[id].Set(data, "none"!=data.field ? this.mainFields[data.field].type : "none");
}
this.parent.Request("GET");
}
SelectChange(id, state) {
var selectedIndex = this.selected.indexOf(id);
if (state && 0>selectedIndex) {
this.selected.push(id);
} else if (!state && -1" + this.renderEntry(this.resourcesIndex[responseJson.content.ID]) + "";
var elList = document.getElementById("GROUP/" + responseJson.content.GROUP);
elList.insertAdjacentHTML("beforeend", entryHtml);
} else if (method=="GET") {
// Update everything
this.admin = responseJson.admin;
this.options = responseJson.options;
for (var k in this.filter) {
delete this.filter[k];
}
this.selected = [];
if ("undefined"!=typeof(responseJson.selected)) {
this.selected = responseJson.selected;
}
this.filter = [];
for (var l in responseJson.filter) {
this.filter.push(new APIFilter(responseJson.filter[l], "none"!=responseJson.filter[l].field ? this.mainFields[responseJson.filter[l].field].type : "none"));
}
for (var iG in this.groups) {
this.groups[iG].Clear();
delete this.groups[iG];
}
this.groups = [];
this.groupsIndex = {};
for (var groupID in responseJson.content) {
this.groups.push(new APIGroup(this, responseJson.content[groupID].ID, responseJson.content[groupID].ENTRIES));
}
if (this.tplFile!=null) {
this.reloadTemplate(this.tplFile);
} } }
findIndexInEntries(id, data) {
for (var i in data) {
if (data[i].ID==id) {
return i;
} }
return null;
}
findEntry(id) {
if (id in this.resourcesIndex) {
return this.resourcesIndex[id];
}
return null;
}
reloadTemplate() {
var _this = this;
var request = new XMLHttpRequest();
request.open("GET", this.tplFile, true);
request.setRequestHeader("Accept", "text/html");
request.onreadystatechange = function() {
if (request.readyState != 4 || request.status != 200) return;
_this.tpl = new TemplateEngine(request.responseText, _this.options);
_this.render();
};
request.send();
}
renderEntry(entry) {
var data = entry.data;
data.SHOWBUTTON = entry.show;
data.SHOWCONTENT = entry.show;
data.SELECTBOX = " ";
return this.tpl.Render(entry.data, "LIST", (this.admin || entry.localAdmin ? ":ADMIN" : ""));
}
getFilter() {
var filter = [];
for (var i in this.filter) {
filter.push(this.filter[i].Get());
}
return filter;
}
renderFilterEntry(id) {
var html = "";
var data = this.filter[id].Get();
// Render and/or combiner
if (id>0) {
html+= "Und "
+"Oder ";
} else {
html+= " ";
}
// Render Field selector
html+= "Ungefiltert ";
for (var i in this.mainFields) {
if (this.mainFields[i].filter) {
html+= ""+this.mainFields[i].label+" ";
} }
html+= " ";
if ("undefined"!=typeof(this.mainFields[data.field])) {
// Render operator
// Render value input
switch (this.mainFields[data.field].type) {
case "Checkbox":
html+= "ist ";
html+= " ";
break;
case "Multiple":
html+= ""
+"enthält "
+"enthält nicht "
+"";
if ("undefined"!=typeof(this.options[data.field])) {
for (var j in this.options[data.field]) {
html+= ""
+this.options[data.field][j].NAME+" ";
} }
html+= " ";
break;
case "Select":
html+= ""
+"= "
+""==data.op ? " selected" : "")+">!= "
+"";
if ("undefined"!=typeof(this.options[data.field])) {
for (var k in this.options[data.field]) {
html+= ""
+this.options[data.field][k].NAME+" ";
} }
html+= " ";
break;
case "String":
html+= "enthält "
+"enthält nicht "
+" ";
break;
case "Integer":
html+= "= "
+""==data.op ? " selected" : "")+">!= "
+"<= "
+"="==data.op ? " selected" : "")+">>= "
+" ";
break;
case "DateTime":
html+= ""
+"<= "
+"="==data.op ? " selected" : "")+">>= ";
var datetimesplit = data.value.split(" ");
html+= " ";
html+= " ";
break;
} }
// Render deletor
html+= " ";
return html;
}
renderFilter() {
var ret = "Filter: ";
for (var i in this.filter) {
ret+= ""+this.renderFilterEntry(i)+" ";
}
ret+= " ";
if (this.filter.length>1) {
ret+= " ";
}
return ret+" ";
}
render() {
var metaHtml = "" + this.tpl.Render(this.meta, "META", "") + " ";
var entriesHtml = "";
for (var j in this.groups) {
entriesHtml+= this.groups[j].Render();
}
var addHtml = (this.admin ? "" + this.tpl.Render(this.options, "ADD.MAIN", "") + " " : "");
document.getElementById("content").innerHTML = metaHtml + addHtml + entriesHtml;
//this.renderHelper();
}
renderHelper(id = null) {
var _this = this;
if (null!==id && !!document.getElementById("dropzone_"+id)) {
this.dropzones[id] = new Dropzone("form#dropzone_"+id, {
url: document.getElementById("dropzone_"+id).action,
//url: this.route + "/" + id + "/Bilder",
createImageThumbnails: false,
headers: {
"Authorization": "Bearer " + _this.parent.jwt,
"Accept": "application/json"
},
dictDefaultMessage: "Datei Drop "
}).on("sending", function(file, xhr, formData) {
_this.Cancel(id);
_this.parent.showMessages(["Datei wird hochgeladen! Dies kann je nach Größe eine Weile dauern."]);
formData.append("secToken", _this.parent.secToken);
}).on("complete", function(file) {
if (file.xhr.status==201) {
_this.parent.showMessages(["Datei erfolgreich hochgeladen!"]);
}
var json = _this.parent.parseResponse(file.xhr.responseText);
_this.parent.parseJson(json, file.xhr.status);
_this.parseContent(json, "GET", id);
});
} }
}
class APIManager
{
constructor() {
this.jwt = this.getCookie("jwt");
this.emulate = null;
this.secToken = this.getCookie("secToken");
this.loggedIn = null;
this.root = "https://app.fw-innenstadt.de";
this.scanner = new APIScanner(this);
this.routes = {"/index.php/": new APIRoute(this, "/index.php/", {
link: "/index.php/", name: "Start", image: "/wappen48.png",
template: "/res/main.html", isPage: true, marker: "START",
mainFields: []})};
this.path = this.getCookie("path");
if (this.path=="") {
this.path = "/index.php/";
this.activeRoute = this.routes[this.path];
}
this.Request("GET");
}
Request(method, json = {}, id = null, sub = null, subid = null) {
var _this = this;
var request = new XMLHttpRequest();
var path = this.path + (id!=null ? "/" + id + (sub!=null ? "/" + sub + (subid!=null ? "/"+subid : "") : "") : "");
var accept = "application/json";
var loadElement = document.getElementById("loadImage");
if (null!=loadElement) {
loadElement.innerHTML = " ";
}
json.secToken = typeof(this.secToken)!="undefined" ? this.secToken : "";
if ("undefined"!=typeof(this.activeRoute)) {
var hasParam = false;
// Append filter if exists
if (this.activeRoute.filter.length>0) {
path += "?filter="+encodeURI(JSON.stringify(this.activeRoute.getFilter()));
hasParam = true;
}
// Append selection
if (this.activeRoute.selected.length>0) {
path += (hasParam ? "&" : "?")+"selected="+encodeURI(JSON.stringify(this.activeRoute.selected));
hasParam = true;
}
// Append print request
if (typeof(json.Print)!="undefined") {
path += (hasParam ? "&" : "?")+"print="+json.Print;
accept = "text/html";
hasParam = true;
}
// Append user emulation
if (this.emulate!=null) {
path += (hasParam ? "&" : "?")+"emulate="+this.emulate;
hasParam = true;
}
}
request.open(method, path, true);
if (this.jwt!="") {
request.setRequestHeader("Authorization", "Bearer " + this.jwt);
}
if (method!="GET") {
request.setRequestHeader("Content-Type", "application/json");
}
request.setRequestHeader("Accept", accept);
request.onreadystatechange = function() {
if (request.readyState != 4) return;
var loadElement = document.getElementById("loadImage");
if (null!=loadElement) {
loadElement.innerHTML = "";
}
if ("text/html"==accept) {
if (200==request.status) {
var windowUrl = 'about:blank';
var uniqueName = new Date();
var windowName = 'Print' + uniqueName.getTime();
var printWindow = window.open(windowUrl, windowName, 'left=50000,top=50000,width=0,height=0');
printWindow.document.write("");
printWindow.document.write(request.responseText);
printWindow.document.write("");
printWindow.document.close();
printWindow.focus();
} else {
_this.showMessages(["Es konnte kein Ausdruck abgerufen werden!"]);
}
} else if ("application/json"==accept) {
var responseJson = _this.parseResponse(request.responseText);
_this.parseJson(responseJson, request.status);
if (request.status>=200 && request.status<300) {
if (typeof(_this.activeRoute)!="undefined") {
_this.activeRoute.parseContent(responseJson, method, id, sub);
} } } };
request.send(JSON.stringify(json));
}
Open(path) {
if (typeof(this.routes[path])!="undefined") {
this.path = path;
this.setCookie("path", path);
this.activeRoute = this.routes[path];
this.Request("GET");
} else if ("/index.php/Logout"==path) {
this.path = path;
this.setCookie("path", "/index.php/");
this.activeRoute = this.routes["/index.php/"];
this.Request("GET");
}
}
Login() {
var button = document.getElementById("loginButton");
button.disabled = true;
button.className = "login disabled";
var json = {
"login": document.getElementById("loginUser").value,
"password": document.getElementById("loginPassword").value
};
this.path = "/index.php/";
this.activeRoute = this.routes[this.path];
this.setCookie("path", this.path);
this.Request("POST", json);
}
/* "Private" functions */
registerRoutes(routes) {
for (var i in routes) {
if (typeof(this.routes[routes[i].link])=="undefined") {
this.routes[routes[i].link] = new APIRoute(this, routes[i].link, routes[i]);
} }
if (typeof(this.activeRoute)=="undefined") {
this.activeRoute = this.routes[this.path];
}
this.renderNavigation();
}
renderNavigation() {
let nav = document.getElementById("navigation");
let selectnav = document.getElementById("navigation_select");
nav.innerHTML = "";
selectnav.innerHTML = "";
for (let i in this.routes) {
if (this.routes[i].isPage) {
let selected = this.routes[i]==this.activeRoute ? " selected" : "";
selectnav.innerHTML+= "☰ " + this.routes[i].name + " ";
let cssClass = this.routes[i]==this.activeRoute ? " class='current'" : "";
if (this.routes[i].image!=null) {
nav.innerHTML+= " ";
} else {
nav.innerHTML+= "" + this.routes[i].name + " ";
} } }
nav.innerHTML+= " ";
selectnav.innerHTML+= "Logout ";
}
parseResponse(responseText) {
var json = {};
try {
json = JSON.parse(responseText);
} catch (SyntaxError) {
this.showMessages(["Parsen der Antwort gescheitert. Es folgt die Antwort!", responseText]);
}
return json;
}
parseJson(json, status) {
if (typeof(json.status.newjwt)!="undefined" && json.status.newjwt!=null) {
this.setCookie("jwt", json.status.newjwt);
this.jwt = json.status.newjwt;
}
if (typeof(json.status.secToken)!="undefined" && json.status.secToken!=null) {
this.setCookie("secToken", json.status.secToken);
this.secToken = json.status.secToken;
}
if (typeof(json.status.loggedIn)!="undefined" && json.status.loggedIn) {
if (json.status.loggedIn!=this.loggedIn) {
this.changeLoginScreen(true);
}
if (status>=300) {
this.showMessages(["Die Anfrage wurde mit Code " + status + " abgelehnt. (" + this.translateHttpStatus(status) + ")"]);
}
} else {
this.changeLoginScreen(false);
}
if (typeof(json.messages)!="undefined") {
this.showMessages(json.messages);
}
if (typeof(json.pages)!="undefined") {
this.registerRoutes(json.pages);
}
}
translateHttpStatus(status) {
switch (status) {
case 200: return "OK";
case 201: return "Erstellt";
case 400: return "Fehlerhafte Anfrage";
case 401: return "Anmeldung erforderlich";
case 403: return "Keine Berechtigung";
case 404: return "Nicht gefunden";
case 500: return "Fehler auf Server";
default: return "" + status + " = Unbekannt";
}
}
changeLoginScreen(newStatus) {
var s = document.getElementById('header').style;
if (this.loggedIn==null) {
if (!newStatus) {
s.display = "block";
s.opacity = 1;
}
} else if (newStatus) {
(function fadeOut(){(s.opacity-=0.1)<0?s.display="none":setTimeout(fadeOut,40)})();
} else {
var button = document.getElementById("loginButton");
button.disabled = false;
button.className = "login";
s.display = "block";
(function fadeIn(){(s.opacity-=-0.1)>1?s.opacity=1:setTimeout(fadeIn,40)})();
}
this.loggedIn = newStatus;
document.getElementById("navigation").className = (this.loggedIn ? "" : "hidden");
document.getElementById("content").className = (this.loggedIn ? "" : "hidden");
}
showMessages(messages) {
for (var i in messages) {
Toastify({
text: messages[i],
duration: 3000,
close: true,
gravity: "top", // `top` or `bottom`
position: "right", // `left`, `center` or `right`
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
stopOnFocus: true, // Prevents dismissing of toast on hover
}).showToast();
} }
setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;SameSite=Strict;Secure";
}
getCookie(cname){
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i