2018-10-24 23:28:56 +02:00

7332 lines
236 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class InitPromise
{
static addPromise(promise)
{
if (typeof promise === 'function')
{
let func = promise;
promise = InitPromise.mainPromise.then(function(app){
return (func(app));
});
}
InitPromise.promises.push(promise);
}
static resolve(app)
{
InitPromise.mainResolver(app);
return InitPromise.mainPromise.then(function(){
return Promise.all(InitPromise.promises);
});
}
}
InitPromise.promises = [];
InitPromise.mainPromise = new Promise(function(resolver){
InitPromise.mainResolver = resolver;
});
class AndroidBridge {
static addDefinition(definition, object) {
if (typeof definition !== "function") {
if (typeof definition === "string"){
let parts = definition.split(".");
for (let i = parts.length-1; i >= 1; i--) {
let newObject = {};
newObject[parts[i]] = object;
object = newObject;
}
definition = parts[0];
console.log("parts for", definition, parts, object);
}
let textDefinition = definition;
definition = () => {
console.log("defining", textDefinition, object);
window[textDefinition] = object;
};
}
AndroidBridge.definitions.push(definition);
}
static applyDefinitions() {
for (let i = 0; i < AndroidBridge.definitions.length; i++) {
AndroidBridge.definitions[i]();
}
return Promise.resolve();
}
}
AndroidBridge.definitions = [];
AndroidBridge.addDefinition("InitPromise.addPromise", InitPromise.addPromise);
class MenuAction {
constructor(title, callback, showFor, order) {
this.title = Helper.nonNull(title, null);
this.callback = callback;
this.showFor = Helper.nonNull(showFor, MenuAction.SHOW_FOR_MEDIUM);
this.order = Helper.nonNull(order, 1000);
this._liClass = "";
this._menu = null;
this._activated = true;
this._visible = true;
this.id = MenuAction.maxId++;
this._icon = null;
this._shouldTranslate = true;
this._copies = [];
}
setTitle(title)
{
this.title = title;
}
setShouldTranslate(shouldTranslate)
{
this._shouldTranslate = shouldTranslate;
}
getShowFor(){
return this.showFor;
}
getTitle()
{
return this.title;
}
getShouldTranslate()
{
return this._shouldTranslate;
}
remove(removeCopies) {
removeCopies = Helper.nonNull(removeCopies, false);
if (Helper.isNotNull(this._menu)) {
console.log(this._menu);
this._menu.removeAction(this);
this._menu = null;
}
if (removeCopies)
{
for (let i = 0; i < this._copies.length; i++) {
this._copies[i].remove();
}
}
}
getMenu() {
return this._menu;
}
setMenu(value) {
this._menu = value;
}
getVisible() {
return this._visible;
}
setVisible(value) {
if (value !== this._visible) {
this._visible = value;
this.redraw();
}
}
getActivated() {
return this._activated;
}
getIcon() {
return this._icon;
}
setIcon(value) {
this._icon = value;
}
getId()
{
return this.id;
}
redraw() {
if (Helper.isNotNull(this._menu)) {
this._menu.updateAction(this);
}
}
copy(instance){
let copy = Helper.nonNull(instance, new MenuAction());
copy.title = this.title;
copy.callback = this.callback;
copy.showFor = this.showFor;
copy.order = this.order;
copy._liClass = this._liClass;
copy._activated = this._activated;
copy._visible = this._visible;
copy._icon = this._icon;
copy._shouldTranslate = this._shouldTranslate;
copy._menu = null;
copy.id = MenuAction.maxId++;
this._copies.push(copy);
return copy;
}
redrawMenu()
{
if (Helper.isNotNull(this._menu)) {
this._menu.redraw();
}
}
}
MenuAction.maxId = 0;
MenuAction.SHOW_ALWAYS = "always";
MenuAction.SHOW_FOR_MEDIUM = "medium";
MenuAction.SHOW_FOR_LARGE = "large";
MenuAction.SHOW_NEVER = "never";
class OpenSubmenuAction extends MenuAction {
constructor(title, menu, showFor, order) {
super(title, function (action) {
action.getSubmenu().toggle();
action.redraw();
}, showFor, order);
this.submenu = menu;
menu.setParentAction(this);
}
getSubmenu() {
return this.submenu;
}
copy(instance) {
instance = super.copy(Helper.nonNull(instance, new OpenSubmenuAction(null, this.submenu.copy())));
return instance;
}
}
class Menu {
constructor(parentElementSelector) {
this.actions = [];
this.submenus = [];
if (typeof parentElementSelector === 'string') {
this.parentElements = document.querySelectorAll(parentElementSelector);
}
else if (Array.isArray(parentElementSelector)) {
this.parentElements = parentElementSelector;
}
else {
this.parentElements = [parentElementSelector];
}
}
copy(instance) {
instance = Helper.nonNull(instance, new Menu([]));
instance.actions = [];
for (let i = 0, n = this.actions.length; i < n; i++) {
instance.actions.push(this.actions[i].copy());
}
instance.submenus = [];
for (let i = 0, n = this.submenus.length; i < n; i++) {
instance.submenus.push(this.submenus[i].copy());
}
return instance;
}
addAction(action) {
if (Helper.includesNot(this.actions, action)) {
this.actions.push(action);
this.redraw();
action.setMenu(this);
if (action instanceof OpenSubmenuAction) {
this.submenus.push(action.getSubmenu());
}
}
}
draw() {
if (Helper.isNotNull(this.parentElements)) {
this.sortActions();
let actionElements = [];
for (let i = 0; i < this.actions.length; i++) {
let element = this.renderAction(this.actions[i]);
this.actions[i]._htmlElement = element;
actionElements.push(element);
}
for (let i = 0, n = this.parentElements.length; i < n; i++) {
this.parentElements[i].removeAllChildren();
for (let i2 = 0, n2 = actionElements.length; i2 < n2; i2++) {
this.parentElements[i].appendChild(Helper.cloneNode(actionElements[i2]));
}
this.parentElements[i].onclick = this._getOnClickListener();
}
}
}
_getOnClickListener() {
let menu = this;
return function (event) {
let _element = event.target;
if (_element.matches('.action') || _element.matches('.action *')) {
// while (!_element.matches('.action > a')) {
// _element = _element.parentNode;
// }
_element = _element.closest(".action");
let actionId = parseInt(_element.dataset["id"]);
for (let i = 0, n = menu.actions.length; i < n; i++) {
if (menu.actions[i].id === actionId) {
if (typeof menu.actions[i].callback === 'function' && menu.actions[i].getActivated()) {
menu.actions[i].callback(menu.actions[i], event);
}
return menu.actions[i];
}
}
for (let i = 0, n = menu.submenus.length; i < n; i++) {
if (menu.submenus[i].click(actionId, event)) {
return menu.submenus[i];
}
}
}
return null;
};
}
/** @protected */
renderAction(action) {
let aElement = document.createElement("a");
if (typeof action.callback === 'string') {
aElement.href = action.callback;
}
if (Helper.isNotNull(action.getIcon())) {
let iconElement = document.createElement("img");
iconElement.src = action.getIcon();
iconElement.classList.add('action-image');
if (action.getShouldTranslate()) {
iconElement.dataset["translationTitle"] = action.title;
}
aElement.appendChild(iconElement);
}
let title = action.getTitle();
if (action.getShouldTranslate()) {
title = Translator.makePersistentTranslation(title);
}
else {
title = document.createTextNode(title);
}
aElement.appendChild(title);
return this.renderLiElement(aElement, action)
}
/** @protected */
renderLiElement(aElement, action) {
let liElement = document.createElement("li");
liElement.classList.add('action');
if (action._liClass.trim() !== "") {
liElement.classList.add(action._liClass);
}
liElement.appendChild(aElement);
liElement.dataset["id"] = action.id;
if (Helper.isNotNull(action.getIcon())) {
liElement.classList.add("img");
}
if (!action.getVisible()) {
liElement.classList.add("hidden");
}
if (action instanceof OpenSubmenuAction) {
action.getSubmenu().draw();
liElement.appendChild(action.getSubmenu().getParentElement());
liElement.classList.add("is-dropdown-submenu-parent");
liElement.classList.add("opens-right");
}
return liElement;
}
/** @private */
sortActions() {
this.actions = this.actions.sort(function (first, second) {
return first.order - second.order;
});
}
_getElementsForAction(action) {
let elements = [];
for (let i = 0; i < this.parentElements.length; i++) {
let elem = this.parentElements[i].querySelector("[data-id=\"" + action.getId() + "\"]");
Helper.isNull(elem) || elements.push(elem);
}
return elements
}
updateAction(action) {
let oldElements = this._getElementsForAction(action);
if (oldElements.length === 0) {
return;
}
let element = this.renderAction(action);
action._htmlElement = element;
for (let i = 0; i < oldElements.length; i++) {
oldElements[i].replaceWith(Helper.cloneNode(element));
}
}
removeAction(action) {
let index = this.actions.indexOf(action);
if (index > 0) {
this.actions.splice(index, 1);
let oldElements = this._getElementsForAction(action);
for (let i = 0, n = oldElements.length; i < n; i++) {
oldElements[i].remove();
}
if (action instanceof OpenSubmenuAction) {
let index = this.submenus.indexOf(action.getSubmenu());
this.submenus.splice(index, 1);
}
}
}
redraw() {
this.draw();
}
}
Menu.SHOW_ALWAYS = "always";
Menu.SHOW_FOR_MEDIUM = "medium";
Menu.SHOW_FOR_SMEDIUM = "smedium";
Menu.SHOW_FOR_LARGE = "large";
Menu.SHOW_NEVER = "never";
class Submenu extends Menu
{
constructor()
{
let menuElement = document.createElement("ul");
menuElement.classList.add("menu");
menuElement.classList.add("vertical");
menuElement.classList.add("submenu");
menuElement.classList.add("is-dropdown-submenu");
menuElement.classList.add("first-sub");
super(menuElement);
this.parentAction = null;
this.isOpen = false;
}
copy(instance)
{
instance = super.copy(Helper.nonNull(instance, new Submenu()));
instance.parentElements = [];
for (let i = 0, n = this.parentElements.length; i < n; i++) {
instance.parentElements.push(Helper.cloneNode(this.parentElements[i]));
}
instance.parentAction = this.parentAction;
instance.isOpen = this.isOpen;
return instance;
}
setParentAction(action)
{
this.parentAction = action;
}
draw()
{
super.draw();
if (Helper.isNotNull(this.parentElements))
{
let self = this;
for (let i = 0; i < this.parentElements.length; i++) {
let closeListener = document.createElement("div");
closeListener.classList.add("close-listener");
closeListener.onclick = function(e){
console.log(e);
self.close();
};
this.parentElements[i].insertBefore(closeListener, this.parentElements[i].firstElementChild);
}
}
}
getParentElement()
{
return this.parentElements[0];
}
_getOnClickListener()
{
return function () {};
}
click(actionId, event)
{
for (let i = 0, n = this.actions.length; i < n; i++) {
if (this.actions[i].id === actionId)
{
if (typeof this.actions[i].callback === 'function' && this.actions[i].getActivated()) {
this.actions[i].callback(this.actions[i], event);
}
this.close();
return true;
}
}
return false;
}
toggle()
{
if (this.isOpen)
{
this.close();
}
else
{
this.open();
}
}
open()
{
this.isOpen = true;
for (let i = 0, n = this.parentElements.length; i < n; i++) {
this.parentElements[i].classList.add("js-dropdown-active");
}
if (Helper.isNotNull(this.parentAction))
{
this.parentAction.redraw();
}
}
close()
{
this.isOpen = false;
for (let i = 0, n = this.parentElements.length; i < n; i++) {
this.parentElements[i].classList.remove("js-dropdown-active");
}
if (Helper.isNotNull(this.parentAction))
{
this.parentAction.redraw();
}
}
}
class TranslatorDB {
constructor() {
this._indexedDB = indexedDB || mozIndexedDB || webkitIndexedDB || msIndexedDB;
this._version = 3;
let self = this;
this._dbPromise = new Promise(function (resolve, reject) {
let request = self._indexedDB.open("Translator", self._version);
request.onupgradeneeded = function (event) {
let db = event.target.result;
self._upgradeDb(db);
};
request.onsuccess = function (event) {
let db = event.target.result;
resolve(db);
};
request.onerror = function (event) {
reject(event);
};
}).catch(function(e){
console.error(e);
});
}
_upgradeDb(db) {
try {
db.deleteObjectStore("currentLang");
db.deleteObjectStore("translations");
}
catch (e) {
console.warn(e);
}
let currentLangObjectStore = db.createObjectStore("currentLang", {"keyPath": "id"});
let translationsObjectStore = db.createObjectStore("translations", {"keyPath": ["lang","key"]});
translationsObjectStore.createIndex("lang", "lang", {"unique": false});
}
setLanguage(lang) {
this._dbPromise.then(function (db) {
let transaction = TranslatorDB._openTransaction(["currentLang"], "readwrite", db);
let currentLangObjectStore = transaction.objectStore("currentLang");
currentLangObjectStore.put({"id": 1, "lang": lang});
}).catch(function(e){
console.error(e);
});
}
saveTranslationsForLang(lang, translations) {
return this._dbPromise.then(function (db) {
return new Promise(function (resolve) {
let transaction = TranslatorDB._openTransaction(["translations"], "readwrite", db);
let translationsObjectStore = transaction.objectStore("translations");
for (let k in translations) {
translationsObjectStore.put({"lang": lang, "key": k, "translation": translations[k]});
}
transaction.oncomplete = function () {
resolve();
};
});
}).catch(function(e){
// console.error(e);
});
}
loadTranslationsForLang(lang) {
return this._dbPromise.then(function (db) {
return new Promise(function (resolve) {
let transaction = TranslatorDB._openTransaction(["translations"], "readonly", db);
let translationsObjectStore = transaction.objectStore("translations");
let index = translationsObjectStore.index("lang");
let request = index.openCursor(IDBKeyRange.only(lang));
let translations = {};
request.onsuccess = function (e) {
let cursor = e.target.result;
if (cursor) {
let translation = cursor.value;
translations[translation["key"]] = translation["translation"];
cursor.continue();
}
};
transaction.oncomplete = function(){
resolve(translations);
};
});
}).catch(function(e){
console.error(e);
return {};
});
}
getLanguage() {
return this._dbPromise.then(function (db) {
return new Promise(function (resolve) {
let transaction = TranslatorDB._openTransaction(["currentLang"], "readonly", db);
let currentLangObjectStore = transaction.objectStore("currentLang");
let req = currentLangObjectStore.get(1);
req.onsuccess = function (e) {
let data = e.currentTarget.result;
if (data)
{
resolve(data["lang"]);
}
else
{
resolve(null);
}
};
req.onerror = function (e) {
resolve(null);
};
});
}).catch(function(e){
// console.error(e);
});
}
static _openTransaction(name, transactionMode, db) {
let transaction = null;
try {
transaction = db.transaction(name, transactionMode);
}
catch (e) {
console.warn(e);
transaction = db.transaction(name);
}
return transaction;
}
}
class Translator {
constructor() {
this._translations = [];
this._db = new TranslatorDB();
this._currentLanguage = null;
this._supportedLanguages = Translator.supportedLanguages;
this._baseLanguage = Translator.baseLanguage;
this._languageBasePath = Translator.languageBasePath;
this._markUntranslatedTranslations = Translator.markUntranslatedTranslations;
this._markTranslations = Translator.markTranslations;
let self = this;
this._initPromise = this.loadBaseLanguage().then(function () {
return self.loadUserLanguage();
});
}
_loadLanguage(language) {
let self = this;
return fetch(Helper.basePath(this._languageBasePath + language + ".json")).then(function (result) {
return result.json();
}).then(function (res) {
self._translations[language] = Object.assign(res, self._translations[language]);
self._db.saveTranslationsForLang(language, self._translations[language]);
}).catch(function (err) {
console.error("could not load lang " + language + " because of error: ", err);
});
}
loadBaseLanguage() {
let self = this;
return this._loadLanguage(this._baseLanguage).then(function () {
self._currentLanguage = self._baseLanguage;
if (typeof document !== 'undefined') {
document.getElementsByTagName("html")[0].setAttribute("lang", self._baseLanguage);
}
});
};
static setLanguage(language) {
let instance = Translator.getInstance();
if (instance) {
return instance.setLanguage(language);
}
}
setLanguage(language) {
if (this._currentLanguage === language) {
this.updateTranslations();
return Promise.resolve();
}
if (this._supportedLanguages.indexOf(language) === -1) {
return Promise.resolve();
}
this._currentLanguage = language;
if (typeof localStorage !== 'undefined') {
localStorage.setItem("language", language);
}
this._db.setLanguage(language);
let self = this;
return this._loadLanguage(language).then(function () {
if (typeof document !== 'undefined') {
document.getElementsByTagName("html")[0].setAttribute("lang", language);
}
self.updateTranslations();
});
}
static translate(key, args) {
let instance = Translator.getInstance();
if (instance) {
return instance.translate(key, args);
}
return "";
}
translate(key, args) {
if (typeof key === 'object' && Helper.isNotNull(key)) {
key = this.addDynamicTranslation(key);
}
let translation = null;
if (Helper.isNotNull(this._translations[this._currentLanguage]) && Helper.isNotNull(this._translations[this._currentLanguage][key])) {
translation = this._translations[this._currentLanguage][key];
}
if (Helper.isNull(translation)) {
if (Translator.logMissingTranslations) {
console.warn("missing translation for language " + this._currentLanguage + " and key " + key);
}
if (Helper.isNotNull(this._translations[this._baseLanguage])) {
translation = this._translations[this._baseLanguage][key];
}
if (Helper.isNull(translation)) {
if (Translator.logMissingTranslations) {
console.error("missing base translation for key " + key + ". FIX IT");
}
translation = key;
}
if (this._markUntranslatedTranslations) {
translation = "&gt;&gt;" + translation + "&lt;&lt;";
}
}
if (this._markTranslations) {
translation = "$" + translation + "$";
}
if (args !== undefined) {
translation = translation.format(args);
}
return translation;
}
static addDynamicTranslation(trans) {
let instance = Translator.getInstance();
if (instance) {
return instance.addDynamicTranslation(trans);
}
}
addDynamicTranslation(trans) {
let key = trans["key"];
delete trans["key"];
for (let lang in trans) {
if (trans.hasOwnProperty(lang)) {
if (Helper.isNull(this._translations[lang])) {
this._translations[lang] = {};
}
this._translations[lang][key] = trans[lang];
}
}
return key;
}
updateTranslations() {
if (typeof document !== 'undefined') {
let elements = document.querySelectorAll("[data-translation]");
for (let i = 0, max = elements.length; i < max; i++) {
if (elements[i].dataset["translation"] != "") {
try {
elements[i].innerHTML = this.translate(elements[i].dataset["translation"], (elements[i].dataset["translationArgs"] !== undefined) ? JSON.parse(elements[i].dataset["translationArgs"]) : undefined);
}
catch (err) {
console.error("wrong configured translation: " + err);
}
}
for (let k in elements[i].dataset) {
if (k.startsWith("translation") && !k.endsWith("Args")) {
try {
elements[i][k.substr(11).toLowerCase()] = this.translate(elements[i].dataset[k], (elements[i].dataset[k + "Args"] !== undefined) ? JSON.parse(elements[i].dataset[k + "Args"]) : undefined);
}
catch (err) {
console.error("wrong configured translation: " + err);
}
}
}
}
}
}
loadUserLanguage() {
let userLanguage = localStorage.getItem("language");
if (Helper.isNull(userLanguage) || this._supportedLanguages.indexOf(userLanguage) === -1) {
let userLanguages = [];
if (Helper.isNotNull(navigator.languages)) {
userLanguages = navigator.languages.slice(0); //.slice(0) klont das Array. Behebt einen Bug in Firefox
}
if (navigator.language !== undefined) {
userLanguages.push(navigator.language);
}
//sicherstellen, dass überhaupt eine Sprache gefunden wird
userLanguages.push(this._baseLanguage);
if (userLanguages !== undefined) {
for (let i = 0, numLanguages = userLanguages.length; i < numLanguages; i++) {
if (this._supportedLanguages.indexOf(userLanguages[i]) !== -1) {
userLanguage = userLanguages[i];
break;
}
}
}
}
return this.setLanguage(userLanguage.toLowerCase())
}
static makePersistentTranslation(key, args, tag) {
tag = Helper.nonNull(tag, "span");
if (typeof key === 'object') {
key = Translator.addDynamicTranslation(key);
}
if (typeof document !== 'undefined') {
let htmlElem = document.createElement(tag);
htmlElem.dataset["translation"] = key;
if (args !== undefined) {
htmlElem.dataset["translationArgs"] = JSON.stringify(args);
}
htmlElem.innerHTML = Translator.translate(key, args);
return htmlElem;
}
}
static generateChangeLanguageMenuAction() {
let submenu = new Submenu();
submenu.addAction(new MenuAction("en", function () {
Translator.getInstance().setLanguage("en");
}));
submenu.addAction(new MenuAction("de", function () {
Translator.getInstance().setLanguage("de");
}));
return new OpenSubmenuAction("current-lang", submenu, Menu.SHOW_ALWAYS)
}
static init() {
Translator.instance = new Translator();
// Translator.loadBaseLanguage().then(function () {
// Translator.loadUserLanguage();
// });
}
static getInstance() {
return Translator.instance;
}
}
Translator.logMissingTranslations = false;
Translator.instance = null;
Translator.baseLanguage = "en";
Translator.supportedLanguages = [
"de",
"en"
];
Translator.markUntranslatedTranslations = true;
Translator.markTranslations = false;
Translator.languageBasePath = "js/lang/";
Translator.currentLanguage = null;
Translator.translations = {};
AndroidBridge.addDefinition("Translator.setLanguage", Translator.setLanguage);
class Helper {
static init() {
Helper.heightMmToPxFactor = null;
Helper.widthMmToPxFactor = null;
}
static includesNot(array, value, fromIndex) {
return -1 === array.indexOf(value, fromIndex);
}
static includes(array, value, fromIndex) {
return !Helper.includesNot(array, value, fromIndex);
}
static isSet() {
if (arguments.length > 0) {
const object = arguments[0];
let keys = Array.prototype.slice.call(arguments, 1);
return (Helper.isNotNull(object) && (keys.length === 0 || Helper.isSet.apply(null, [object[keys[0]]].concat(keys.slice(1)))));
}
return false;
}
static isNull(variable) {
return (variable === null || variable === undefined);
}
static isNotNull(variable) {
return !Helper.isNull(variable);
}
static nonNull(val1, val2) {
for (let i = 0; i < arguments.length; i++) {
if (Helper.isNotNull(arguments[i])) {
return arguments[i];
}
}
return null;
}
static notEmpty(value) {
return !Helper.empty(value);
}
static buildQuery(values) {
let queryStrings = [];
for (let k in values) {
queryStrings.push(encodeURIComponent(k) + "=" + encodeURIComponent(values[k]));
}
return "?" + queryStrings.join("&");
}
static empty(value) {
return (Helper.isNull(value) || (typeof value === 'string' && value.trim() === ""))
}
static inflateElementsFromString(string) {
let template = document.createElement('template');
template.innerHTML = string;
return template.content.childNodes;
}
static createLoadingSymbol() {
let svgNS = "http://www.w3.org/2000/svg";
let loader = document.createElement("div");
loader.className = 'loader';
let svg = document.createElementNS(svgNS, "svg");
svg.setAttribute('viewBox', "0 0 32 32");
svg.setAttribute("widh", "32");
svg.setAttribute("height", "32");
let circle = document.createElementNS(svgNS, "circle");
circle.setAttribute("id", "spinner");
circle.setAttribute("cx", "16");
circle.setAttribute("cy", "16");
circle.setAttribute("r", "14");
circle.setAttribute("fill", "none");
svg.appendChild(circle);
loader.appendChild(svg);
return loader;
}
static basePath(url) {
return SystemSettings.getBasePath() + url;
}
static isMobileApple() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
}
static isMobile() {
return (navigator.userAgent.match(/Android|BlackBerry|Opera Mini|IEMobile/i) !== null || Helper.isMobileApple() || (typeof window.orientation !== "undefined" || window.orientation === false || window.orientation === null));
}
static select(e) {
let range = document.createRange();
range.selectNodeContents(e);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
static format(number, leadingZeros) {
number = "" + number;
while (number.length < leadingZeros) {
number = "0" + number;
}
return number;
}
static cloneNode(srcNode) {
let destNode = srcNode.cloneNode(true);
destNode.onclick = srcNode.onclick;
return destNode;
}
static encodeToBase(stringToEncode, base) {
let encodedString = "";
let charlength = Math.floor(Math.log(265) / Math.log(base));
for (let i = 0; i < stringToEncode.length; i++) {
let value = stringToEncode.charCodeAt(i).toString(base);
let joinLength = value.length % charlength;
if (joinLength > 0) {
let joinArray = new Array(charlength + 1 - (joinLength)); //+1, da join nur zwischen elemente einfügt
value = joinArray.join("0") + value;
}
encodedString += value;
}
return encodedString;
}
static decodeToBase(stringToDecode, base) {
let charlength = Math.floor(Math.log(265) / Math.log(base));
let values = stringToDecode.match(new RegExp(".{1," + charlength + "}", "g")) || [];
let encodedString = "";
for (let i = 0, n = values.length; i < n; i++) {
encodedString += String.fromCharCode(parseInt(values[i], base));
}
return encodedString;
}
static toggleVisibility(elem) {
if (elem.style.display === "none") {
elem.style.display = "";
return true;
}
else {
elem.style.display = "none";
return false;
}
}
static print(content) {
let printContent = document.getElementById("print-content");
if (content instanceof Element) {
printContent.removeAllChildren();
printContent.appendChild(content);
}
else {
printContent.innerHTML = content;
}
window.print();
}
static strftime(sFormat, date, useUTC) {
if (!(date instanceof Date)) date = new Date(date);
useUTC = Helper.nonNull(useUTC, false);
let nDay = (useUTC) ? date.getUTCDay() : date.getDay(),
nDate = (useUTC) ? date.getUTCDate() : date.getDate(),
nMonth = (useUTC) ? date.getUTCMonth() : date.getMonth(),
nYear = (useUTC) ? date.getUTCFullYear() : date.getFullYear(),
nHour = (useUTC) ? date.getUTCHours() : date.getHours(),
aDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
aMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
aDayCount = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
isLeapYear = function () {
if ((nYear & 3) !== 0) return false;
return nYear % 100 !== 0 || nYear % 400 === 0;
},
getThursday = function () {
let target = new Date(date);
target.setDate(nDate - ((nDay + 6) % 7) + 3);
return target;
},
zeroPad = function (nNum, nPad) {
return ('' + (Math.pow(10, nPad) + nNum)).slice(1);
};
return sFormat.replace(/%[a-z]/gi, function (sMatch) {
return {
'%a': Translator.makePersistentTranslation(aDays[nDay].slice(0, 3)).outerHTML,
'%A': Translator.makePersistentTranslation(aDays[nDay]).outerHTML,
'%b': Translator.makePersistentTranslation(aMonths[nMonth].slice(0, 3)).outerHTML,
'%B': Translator.makePersistentTranslation(aMonths[nMonth]).outerHTML,
'%c': date.toUTCString(),
'%C': Math.floor(nYear / 100),
'%d': zeroPad(nDate, 2),
'%e': nDate,
'%f': zeroPad(date.getTime() % 1000, 4),
'%F': date.toISOString().slice(0, 10),
'%G': getThursday().getFullYear(),
'%g': ('' + getThursday().getFullYear()).slice(2),
'%H': zeroPad(nHour, 2),
'%I': zeroPad((nHour + 11) % 12 + 1, 2),
'%j': zeroPad(aDayCount[nMonth] + nDate + ((nMonth > 1 && isLeapYear()) ? 1 : 0), 3),
'%k': '' + nHour,
'%l': (nHour + 11) % 12 + 1,
'%m': zeroPad(nMonth + 1, 2),
'%M': zeroPad(date.getMinutes(), 2),
'%p': (nHour < 12) ? 'AM' : 'PM',
'%P': (nHour < 12) ? 'am' : 'pm',
'%s': Math.round(date.getTime() / 1000),
'%S': zeroPad(date.getSeconds(), 2),
'%u': nDay || 7,
'%V': (function () {
let target = getThursday(),
n1stThu = target.valueOf();
target.setMonth(0, 1);
let nJan1 = target.getDay();
if (nJan1 !== 4) target.setMonth(0, 1 + ((4 - nJan1) + 7) % 7);
return zeroPad(1 + Math.ceil((n1stThu - target) / 604800000), 2);
})(),
'%w': '' + nDay,
'%x': date.toLocaleDateString(),
'%X': date.toLocaleTimeString(),
'%y': ('' + nYear).slice(2),
'%Y': nYear,
'%z': date.toTimeString().replace(/.+GMT([+-]\d+).+/, '$1'),
'%Z': date.toTimeString().replace(/.+\((.+?)\)$/, '$1')
}[sMatch] || sMatch;
});
}
static cloneJson(obj) {
// https://stackoverflow.com/questions/4120475/how-to-create-and-clone-a-json-object/17502990#17502990
let i;
// basic type deep copy
if (Helper.isNull(obj) || typeof obj !== 'object') {
return obj
}
// array deep copy
if (obj instanceof Array) {
let cloneA = [];
for (i = 0; i < obj.length; ++i) {
cloneA[i] = Helper.cloneJson(obj[i]);
}
return cloneA;
}
if (obj instanceof Date)
{
return new Date(obj.getTime());
}
// object deep copy
let cloneO = {};
for (i in obj) {
cloneO[i] = Helper.cloneJson(obj[i]);
}
return cloneO;
}
static htmlspecialcharsDecode(text) {
const map = {
'&amp;': '&',
'&#038;': "&",
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#039;': "'",
'&#8217;': "",
'&#8216;': "",
'&#8211;': "",
'&#8212;': "—",
'&#8230;': "…",
'&#8221;': '”'
};
if (Helper.isNotNull(text) && typeof text.replace === "function") {
return text.replace(/\&[\w\d\#]{2,5}\;/g, function (m) {
return map[m];
});
}
return text;
}
static formDataFromObject(obj) {
let formData = new FormData();
for (let k in obj) {
formData.set(k, obj[k]);
}
return formData;
}
static scaleContentRecursive(element, content) {
let elementStyle = window.getComputedStyle(element);
let contentStyle = window.getComputedStyle(content);
if (contentStyle.height > elementStyle.height || contentStyle.width > elementStyle.width) {
return Helper.scaleDownContentRecursive(element, content);
}
}
static scaleDownContentRecursive(element, content) {
Helper.convertChildrenToRelativeRecursive(element);
let elementStyle = window.getComputedStyle(element);
let contentStyle = window.getComputedStyle(content);
let runs = 0;
let fontSize = parseFloat(contentStyle.getPropertyValue("font-size"));
let width = contentStyle.width;
let height = contentStyle.height;
while (contentStyle.height > elementStyle.height || contentStyle.width > elementStyle.width) {
fontSize *= 0.95;
if (height > elementStyle.height) {
height *= 0.95;
}
if (width > contentStyle.width) {
width *= 0.95;
}
content.style["font-size"] = fontSize + "px";
content.style["max-height"] = height + "px";
content.style["max-width"] = width + "px";
runs++;
if (runs > 2000) {
console.log("breaked");
break;
}
}
Helper.convertToRelative(content);
contentStyle = window.getComputedStyle(content);
content.style["font-size"] = (parseFloat(contentStyle.getPropertyValue("font-size")) / parseFloat(document.documentElement.clientHeight) * 100) + "vh";
}
static convertChildrenToRelativeRecursive(element) {
let children = element.childNodes;
for (let i = 0, n = children.length; i < n; i++) {
if (children[i] instanceof Element) {
Helper.convertToRelative(children[i]);
Helper.convertChildrenToRelativeRecursive(children[i]);
}
}
}
static convertToRelative(element) {
let hasTransitionClass = (element.classList.contains("no-transtition"));
element.classList.add("no-transition");
let parent = element.parentNode;
console.log(element);
let elementStyle = window.getComputedStyle(element);
let parentStyle = window.getComputedStyle(parent);
let fontSize = parseFloat(elementStyle.getPropertyValue("font-size")) / parseFloat(parentStyle.getPropertyValue("font-size"));
let maxHeight = elementStyle.height;
let maxWidth = elementStyle.width;
let pHeight = parentStyle.height;
let pWidth = parentStyle.width;
let relativeAttributes = element.style;
relativeAttributes['max-height'] = Math.floor(maxHeight / pHeight * 100) + "%";
relativeAttributes['margin-left'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-left')) / pWidth * 100) + "%";
relativeAttributes['margin-right'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-right')) / pWidth * 100) + "%";
relativeAttributes['margin-top'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-top')) / pHeight * 100) + "%";
relativeAttributes['margin-bottom'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-bottom')) / pHeight * 100) + "%";
relativeAttributes['max-width'] = Math.floor(maxWidth / pWidth * 100) + "%";
relativeAttributes["font-size"] = fontSize + "em";
// console.log(relativeAttributes);
// element.css(relativeAttributes);
if (!hasTransitionClass) {
element.classList.remove("no-transition");
}
}
static isChrome() {
let isChromium = window.chrome,
winNav = window.navigator,
vendorName = winNav.vendor,
isOpera = winNav.userAgent.indexOf("OPR") > -1,
isIEedge = winNav.userAgent.indexOf("Edge") > -1,
isIOSChrome = winNav.userAgent.match("CriOS");
if (isIOSChrome) {
return true;
} else {
return isChromium !== null &&
typeof isChromium !== "undefined" &&
vendorName === "Google Inc." &&
isOpera === false &&
isIEedge === false;
}
}
static getIndexedObject(array, keyValue) {
let obj = {};
for (let i = 0, n = array.length; i < n; i++) {
obj[array[i][keyValue]] = array[i];
}
return obj;
}
static invertKeyValues(obj) {
let new_obj = {};
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
new_obj[obj[prop]] = prop;
}
}
return new_obj;
}
static toArray(object) {
let res = [];
for (let k in object) {
res.push(object[k]);
}
return res;
}
}
Helper.init();
class SystemSettings {
static setBasePath(basePath) {
SystemSettings._basePath = basePath;
}
static getBasePath() {
return SystemSettings._basePath;
}
static set(key, value) {
SystemSettings._settings[key] = value;
}
static get(key, defaultValue) {
return Helper.nonNull(SystemSettings._settings[key], defaultValue);
}
static has(key){
return Helper.nonNull(SystemSettings._settings[key]);
}
}
SystemSettings.setBasePath("/");
SystemSettings._settings = {};
class ThemeManager {
static init() {
ThemeManager.loadCurrentTheme();
}
static changeCurrentTheme(newTheme) {
let theme = null;
if (typeof newTheme === 'string') {
let themes = ThemeManager.themes.filter(function (theme) {
return theme._name === newTheme;
});
if (themes.length > 0) {
theme = themes[0];
}
}
else if (ThemeManager.themes.indexOf(newTheme) !== -1) {
theme = newTheme;
}
if (Helper.isNotNull(theme)) {
localStorage.setItem("currentTheme", theme._name);
let themePromise = new Promise(function (resolve) {
document.querySelector("nav.top-bar").addEventListener("transitionend", function(){
resolve();
});
});
document.body.className = theme._className;
ThemeManager.currentTheme = theme;
for (let i = 0, n = ThemeManager.changeListeners.length; i < n; i++) {
ThemeManager.changeListeners[i](ThemeManager.currentTheme, themePromise);
}
}
}
static addTheme(theme) {
ThemeManager.themes.push(theme);
}
static loadCurrentTheme() {
ThemeManager.changeCurrentTheme(localStorage.getItem("currentTheme"));
if (Helper.isNull(ThemeManager.currentTheme)) {
let className = document.body.className;
let themes = ThemeManager.themes.filter(function (theme) {
return theme._className === className;
});
if (themes.length > 0) {
ThemeManager.changeCurrentTheme(themes[0]);
}
else if (ThemeManager.themes.length > 0) {
ThemeManager.changeCurrentTheme(ThemeManager.themes[0]);
}
}
}
static generateChangeThemeMenuAction() {
return new MenuAction(ThemeManager.currentTheme._name, function (action) {
let currentThemeIndex = ThemeManager.themes.indexOf(ThemeManager.currentTheme);
let nextIndex = (currentThemeIndex + 1) % ThemeManager.themes.length;
ThemeManager.changeCurrentTheme(ThemeManager.themes[nextIndex]);
action.title = ThemeManager.currentTheme._name;
action._menu.redraw();
}, Menu.SHOW_ALWAYS)
}
static addChangeListener(listener) {
ThemeManager.changeListeners.push(listener);
}
static getCurrentTheme(){
return ThemeManager.currentTheme;
}
}
ThemeManager.currentTheme = null;
ThemeManager.themes = [];
ThemeManager.changeListeners = [];
AndroidBridge.addDefinition("ThemeManager", {
"addChangeListener": ThemeManager.addChangeListener,
"getCurrentTheme": ThemeManager.getCurrentTheme,
});
class CookieCompliance {
static async showIfNeeded(cookieContainer) {
let cookieCompliance = new CookieCompliance(cookieContainer);
return cookieCompliance.showIfNeeded();
}
constructor(cookieContainerId) {
this.cookieContainerId = cookieContainerId;
this.dropCookie = true;
this.cookieDuration = 365 * 10;
this.cookieName = 'complianceCookie';
this.cookieValue = 'true';
}
async showIfNeeded() {
if (CookieCompliance.checkCookie(this.cookieName) !== this.cookieValue) {
return this.show();
}
return Promise.resolve();
}
removeMe() {
this.createCookie(this.cookieName, this.cookieValue, this.cookieDuration);
}
createCookie(name, value, days) {
let expires;
if (Helper.isNotNull(days)) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
else {
expires = "";
}
if (this.dropCookie) {
document.cookie = name + "=" + value + expires + "; path=/";
}
}
eraseCookie(name) {
this.createCookie(name, "", -1);
}
static checkCookie(name) {
const nameEQ = name + "=";
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let c = cookies[i];
while (c.charAt(0) === ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length, c.length);
}
}
return null;
}
show() {
let cookieCompliance = this;
const cookieMessage = document.getElementById(this.cookieContainerId);
cookieMessage.style.display = 'block';
return new Promise(r => {
cookieMessage.querySelector("#close-cookie-msg").onclick = function () {
cookieCompliance.removeMe();
cookieMessage.remove();
r();
};
});
}
}
class ActionBarMenu extends Menu {
static init() {
function parseStyleToObject(str) {
let styleObject = {};
if (typeof str !== 'string') {
return styleObject;
}
str = str.trim().slice(1, -1); // browsers re-quote string style values
if (!str) {
return styleObject;
}
styleObject = str.split('&').reduce(function (ret, param) {
const parts = param.replace(/\+/g, ' ').split('=');
let key = parts[0];
let val = parts[1];
key = decodeURIComponent(key);
// missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
val = val === undefined ? null : decodeURIComponent(val);
if (!ret.hasOwnProperty(key)) {
ret[key] = val;
} else if (Array.isArray(ret[key])) {
ret[key].push(val);
} else {
ret[key] = [ret[key], val];
}
return ret;
}, {});
return styleObject;
}
let cssStyle = document.getElementsByClassName('foundation-mq');
if (cssStyle.length === 0) {
return;
}
let queries = [];
cssStyle = parseStyleToObject(window.getComputedStyle(cssStyle[0]).getPropertyValue('font-family'));
for (let key in cssStyle) {
if (cssStyle.hasOwnProperty(key)) {
queries.push({
_name: key,
value: 'only screen and (min-width: ' + cssStyle[key] + ')'
});
}
}
window.addEventListener('resize', function () {
if (Helper.isNotNull(ActionBarMenu.currentMenu)) {
ActionBarMenu.currentMenu.updateToggleButton();
}
});
let responsiveMenu = document.getElementById("responsive-menu");
document.getElementById("responsive-menu-toggle").onclick = function () {
if (window.getComputedStyle(responsiveMenu).getPropertyValue('display') === 'none') {
responsiveMenu.style.display = 'block';
}
else if (Helper.isNotNull(ActionBarMenu.currentMenu)) {
ActionBarMenu.currentMenu.close();
}
};
responsiveMenu.firstElementChild.addEventListener("click", function (e) {
if (e.target === responsiveMenu.firstElementChild && Helper.isNotNull(ActionBarMenu.currentMenu)) {
ActionBarMenu.currentMenu.close();
}
}
);
ActionBarMenu.queries = queries;
}
static _getCurrentSize() {
let matched;
for (let i = 0; i < ActionBarMenu.queries.length; i++) {
let query = ActionBarMenu.queries[i];
if (matchMedia(query.value).matches) {
matched = query;
}
}
if (typeof matched === 'object') {
return matched._name;
} else {
return matched;
}
}
static filterVisibleElements(elements) {
let visibleElements = [];
for (let i = 0, n = elements.length; i < n; i++) {
if (!elements[i].classList.contains("hidden")) {
visibleElements.push(elements[i]);
}
}
return visibleElements;
}
renderLiElement(aElement, action) {
let liElement = super.renderLiElement(aElement, action);
liElement.classList.add(action.getShowFor());
return liElement;
}
updateToggleButton() {
let size = ActionBarMenu._getCurrentSize();
let firstParentElement = this.parentElements[0];
if ((size === "medium" || size === "smedium" || size === "small") && ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_FOR_LARGE)).length > 0 ||
(size === "smedium" || size === "small") && ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_FOR_MEDIUM)).length > 0 ||
(size === "small") && ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_FOR_SMEDIUM)).length > 0 ||
ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_NEVER)).length > 0) {
document.getElementById("responsive-menu-toggle").style.display = 'block';
} else {
document.getElementById("responsive-menu-toggle").style.display = 'none';
if (Helper.isNotNull(ActionBarMenu.currentMenu)) {
ActionBarMenu.currentMenu.close();
}
}
}
_getOnClickListener() {
let superListener = super._getOnClickListener();
return function (event) {
let action = superListener(event);
if (!(action instanceof OpenSubmenuAction) && Helper.isNotNull(ActionBarMenu.currentMenu)) {
ActionBarMenu.currentMenu.close();
}
}
}
draw(parentElement) {
let returnValue = super.draw(parentElement);
this.updateToggleButton();
ActionBarMenu.currentMenu = this;
return returnValue;
}
close() {
document.getElementById("responsive-menu").style.display = 'none';
for (let i = 0, n = this.submenus.length; i < n; i++) {
this.submenus[i].close();
}
}
removeAction(action) {
let res = super.removeAction(action);
this.updateToggleButton();
return res;
}
}
ActionBarMenu.queries = [];
ActionBarMenu.currentMenu = null;
ActionBarMenu.init();
class ViewInflater {
static inflate(viewUrl, parentUrls) {
parentUrls = Helper.nonNull(parentUrls, []).slice(0);
let resultPromise = Promise.resolve();
if (viewUrl instanceof Element) {
resultPromise = Promise.resolve(viewUrl);
}
else {
if (parentUrls.indexOf(viewUrl) !== -1) {
return Promise.reject("views are in a circuit! cannot resolve view for url " + parentUrls[0] + "! url " + viewUrl + " is in stack before!");
}
parentUrls.push(viewUrl);
resultPromise = fetch(Helper.basePath(viewUrl), {credentials: "same-origin"}).then(function (result) {
return result.text();
}).then(function (htmlText) {
let doc = (new DOMParser()).parseFromString(htmlText, "text/html");
if (Helper.isNull(doc)) {
doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = htmlText;
}
return doc.body.firstChild
});
}
return resultPromise.then(function (parentElement) {
let promises = [];
let childViews = parentElement.querySelectorAll("[data-view]");
for (let i = 0, n = childViews.length; i < n; i++) {
promises.push(ViewInflater.inflate(childViews[i].dataset["view"], parentUrls).then(function (element) {
childViews[i].replaceWith(element);
}));
}
return Promise.all(promises).then(function () {
return parentElement;
});
});
}
}
class Context {
constructor(view) {
let self = this;
this._siteContent = null;
this.firstStart = true;
this.inflatePromise = new Promise(function (resolver) {
self.inflatePromiseResolver = resolver;
});
this.fragments = {};
if (Helper.isNotNull(view)) {
this.inflateView(view);
}
}
onConstruct() {
let results = [];
for (let k in this.fragments) {
results.push(this.fragments[k].onConstruct.apply(this.fragments[k], arguments));
results.push(this.fragments[k].inflatePromise);
}
return Promise.all(results);
}
onStart() {
if (this.firstStart) {
this.onFirstStart();
this.firstStart = false;
}
for (let k in this.fragments) {
let fragment = this.fragments[k];
fragment.onStart.apply(this.fragments[k], arguments);
this.fragments[k].inflatePromise.then(function (fragmentView) {
if (fragment.isActive()) {
fragmentView.classList.remove("hidden");
}
else {
fragmentView.classList.add("hidden");
}
});
}
}
onFirstStart() {
// for (let k in this.fragments) {
// this.fragments[k].onFirstStart.apply(this.fragments[k], arguments);
// }
}
onPause() {
for (let k in this.fragments) {
this.fragments[k].onPause.apply(this.fragments[k], arguments);
}
}
onDestroy() {
for (let k in this.fragments) {
this.fragments[k].onDestroy.apply(this.fragments[k], arguments);
}
}
addFragment(viewQuery, fragment) {
this.fragments[viewQuery] = fragment;
this.inflatePromise = this.inflatePromise.then(function (siteContent) {
return fragment.inflatePromise.then(function (fragmentView) {
siteContent.querySelector(viewQuery).appendChild(fragmentView);
return siteContent;
});
});
}
/** @protected */
inflateView(link) {
let self = this;
this.inflatePromiseResolver(ViewInflater.inflate(link).then(function (siteContent) {
self._siteContent = siteContent;
return siteContent;
}));
return this.inflatePromise;
}
findBy(query, all, asPromise) {
all = Helper.nonNull(all, false);
asPromise = Helper.nonNull(asPromise, false);
let getVal = function (root) {
let res = null;
if (all) {
res = root.querySelectorAll(query);
if (root.matches(query)) {
res.push(root);
}
}
else {
if (root.matches(query)) {
res = root;
}
else {
res = root.querySelector(query);
}
}
return res;
};
if (asPromise) {
return this.inflatePromise.then(function (rootView) {
return getVal(rootView);
});
}
return getVal(this._siteContent);
}
}
class AbstractSite extends Context {
constructor(siteManager, view, deepLink) {
super(view);
this.isVisible = false;
this.siteManager = siteManager;
this.isFinishing = false;
this.actionMenu = null;
this.url = "";
this.deepLink = deepLink;
this.startArgs = {};
this.title = siteManager.getDefaultTitle();
}
setTitle(titleElement, title) {
if (typeof titleElement === "string") {
title = titleElement;
titleElement = document.createTextNode(titleElement);
}
this.title = {
element: titleElement
};
this.title["title"] = Helper.nonNull(title, this.title["title"]);
if (this.isVisible) {
this.siteManager.updateTitle();
}
}
startStartsite() {
return this.startSite(this.siteManager.getStartSiteName());
}
inflateView(link) {
let self = this;
return super.inflateView(link).then(function (res) {
let promises = [];
for (let i = 0, n = self.fragments.length; i < n; i++) {
promises.push(self.fragments[i].inflatePromise);
}
return Promise.all(promises).then(function () {
return res;
});
});
}
onConstruct(args) {
this.startArgs = args;
if (Helper.isNotNull(this.deepLink)) {
this.setUrlFromParams(args);
}
return super.onConstruct(args);
}
onStart(args) {
this.isVisible = true;
let res = super.onStart(args);
this.actionMenu.redraw();
return res;
}
onPause(args) {
super.onPause(args);
this.isVisible = false;
}
finish(result) {
if (!this.isFinishing) {
this.isFinishing = true;
this.siteManager.endSite(this, result);
}
}
startSite(siteName, args) {
return this.siteManager.startSite(siteName, args);
}
toForeground() {
this.siteManager.toForeground(this);
}
finishAndStartNext(siteName, startParams, finishResult) {
this.startSite(siteName, startParams);
this.finish(finishResult);
}
createActionBarMenu(menu) {
let defaultActions = this.siteManager.getDefaultActions();
for (let i = 0, n = defaultActions.length; i < n; i++) {
menu.addAction(defaultActions[i].copy());
}
return menu;
}
setUrl(url) {
this.url = url;
this.siteManager.updateUrl(this);
}
setUrlFromParams(params) {
this.setUrl(this.deepLink + Helper.buildQuery(params));
}
updateUrlParams(params) {
this.startArgs = Object.assign(this.startArgs, params);
this.setUrlFromParams(this.startArgs);
}
getUrl() {
return this.url;
}
getFullUrl() {
return Helper.basePath(this.url);
}
onBackPressed() {
}
addListener(event, selector, listenerFunction) {
this.siteManager.addListener(this, event, selector, listenerFunction);
}
addKeyListener(keycode, listenerFunction)
{
this.siteManager.addKeyListener(this, keycode, listenerFunction);
}
addKeyAndEventListener(keycode, event, selector, listenerFunction) {
this.siteManager.addKeyAndEventListener(this, keycode, event, selector, listenerFunction);
}
}
class SiteContainer {
constructor(site, finishResolver) {
this._site = site;
this._siteContent = null;
this._pauseParameters = {};
this._startParameters = {};
this._finishResolver = finishResolver;
}
getSite() {
return this._site;
}
setSite(site) {
if (site instanceof AbstractSite) {
this._site = site;
}
}
getSiteContent() {
return this._siteContent;
}
setSiteContent(value) {
this._siteContent = value;
}
getPauseParameters() {
return this._pauseParameters;
}
setPauseParameters(value) {
this._pauseParameters = value;
}
getStartParameters() {
return this._startParameters;
}
setStartParameters(value) {
this._startParameters = value;
}
getFinishResolver() {
return this._finishResolver;
}
setFinishResolver(value) {
this._finishResolver = value;
}
}
class SiteManager {
constructor(siteDivId, actionBarMenuSelector) {
this.siteDiv = document.getElementById(siteDivId);
this.siteContainerStack = [];
this.currentSiteContainerToShow = null;
this.actionBarMenuSelector = Helper.nonNull(actionBarMenuSelector, '.action-bar');
this.siteStartingPromise = Promise.resolve();
this.defaultActions = [];
this.startSiteName = null;
this.titleElement = document.querySelector(".top-bar-title");
const defaultTitleElem = document.createElement("span");
while (this.titleElement.childNodes.length > 0) {
const child = this.titleElement.firstChild;
child.remove();
defaultTitleElem.appendChild(child);
}
this.defaultTitle = {
element: defaultTitleElem,
title: document.title
};
let siteManager = this;
window.onpopstate = function (e) {
if (siteManager.siteContainerStack.length >= 1) {
let site = siteManager.siteContainerStack[siteManager.siteContainerStack.length - 1].getSite();
if (site.onBackPressed() !== false) {
siteManager.endSite(site);
}
}
};
}
getDefaultTitle() {
return this.defaultTitle;
}
setStartSiteName(startSiteName) {
this.startSiteName = startSiteName;
}
getStartSiteName() {
return this.startSiteName;
}
addDefaultAction(action) {
this.defaultActions.push(action);
}
getDefaultActions() {
return this.defaultActions;
}
getCurrentSite(){
if (Helper.isNotNull(this.currentSiteContainerToShow)){
return this.currentSiteContainerToShow.getSite();
}
return null;
}
async findSite(filter){
for (let i = this.siteContainerStack.length-1; i >= 0; i--) {
if (await filter(this.siteContainerStack[i].getSite())){
return this.siteContainerStack[i].getSite();
}
}
return null;
}
async startSite(siteConstructor, paramsPromise) {
if (!(siteConstructor.prototype instanceof AbstractSite)) {
throw {
"error": "wrong class given! Expected AbstractSite, given " + siteConstructor.name
};
}
let site = new siteConstructor(this);
let resolver = {};
let finishPromise = new Promise(function (resolve, reject) {
resolver.resolve = resolve;
resolver.reject = reject;
});
let siteContainer = new SiteContainer(site, resolver);
this.siteDiv.removeAllChildren().appendChild(Helper.createLoadingSymbol());
this.siteStartingPromise = Promise.resolve(paramsPromise).then(async (params) => {
siteContainer.setStartParameters(params);
await Promise.all([site.onConstruct(params), site.inflatePromise]);
site.actionMenu = site.createActionBarMenu(this.buildActionBarMenu());
return this.show(siteContainer);
}).catch((e) => {
console.error("site start error for site ", siteConstructor.name, e);
});
return finishPromise;
}
endSite(site, result) {
let manager = this;
this.siteStartingPromise.then(function () {
let index = manager.findContainerIndexBySite(site);
let container = manager.siteContainerStack.splice(index, 1);
container = container[0];
let showSiteContainer = null;
if (container === manager.currentSiteContainerToShow) {
manager.currentSiteContainerToShow.getSite().onPause();
manager.currentSiteContainerToShow = null;
let newSiteContainerIndex = manager.siteContainerStack.length - 1;
if (newSiteContainerIndex < 0) {
manager.showAppEndedMessage();
manager.startSite(manager.startSiteName);
return;
}
manager.siteDiv.removeAllChildren().appendChild(Helper.createLoadingSymbol());
showSiteContainer = manager.siteContainerStack[newSiteContainerIndex];
}
container.getSite().onDestroy();
Promise.resolve(result).then(function (resValue) {
container.getFinishResolver().resolve(resValue);
if (Helper.isNotNull(showSiteContainer)) {
manager.show(showSiteContainer);
}
});
});
}
addListener(site, event, _selector, listener) {
this.siteDiv.addEventListener(event, function (_event) {
let _element = _event.target;
if (site.isVisible && _element.matches(_selector)) {
listener(_element, _event);
}
});
}
addKeyAndEventListener(site, keycode, event, selector, listener) {
this.addListener(site, event, selector, listener);
this.addKeyListener(site, keycode, listener);
}
addKeyListener(site, keycode, listener) {
window.addEventListener("keydown", function (e) {
if (site.isVisible && e.which === keycode) {
listener(this, e);
}
});
}
toForeground(site) {
let index = this.findContainerIndexBySite(site);
let container = this.siteContainerStack.splice(index, 1);
container = container[0];
this.show(container);
}
refreshCurrentSite() {
return this.show(this.currentSiteContainerToShow);
}
/** @private */
show(siteContainer) {
if (Helper.isNotNull(this.currentSiteContainerToShow)) {
this.currentSiteContainerToShow.setPauseParameters(this.currentSiteContainerToShow.getSite().onPause());
this.currentSiteContainerToShow.setSiteContent(this.siteDiv.innerHTML);
}
this.siteDiv.removeAllChildren().appendChild(Helper.createLoadingSymbol());
let siteManager = this;
this.currentSiteContainerToShow = siteContainer;
if (-1 === this.siteContainerStack.indexOf(siteContainer)) {
this.siteContainerStack.push(siteContainer);
}
return siteContainer.getSite().inflatePromise.then(function (data) {
siteContainer.getSite().actionMenu.redraw();
siteManager.siteDiv.removeAllChildren().appendChild(data);
siteManager.updateTitle();
Translator.getInstance().updateTranslations();
return data;
}).then(function (data) {
siteContainer.getSite().onStart(siteContainer.getPauseParameters());
history.pushState({
'siteName': siteContainer.getSite().constructor.name,
'siteData': data.outerHTML,
'stackPosition': siteManager.siteContainerStack.length - 1
}, siteContainer.getSite().constructor.name, siteContainer.getSite().getFullUrl());
});
}
updateUrl(site) {
if (Helper.isNotNull(this.currentSiteContainerToShow) && this.currentSiteContainerToShow.getSite() === site) {
let self = this;
history.replaceState({
'siteName': site.constructor.name,
'siteData': site._siteContent.outerHTML,
'stackPosition': self.siteContainerStack.length - 1
}, site.constructor.name, site.getFullUrl());
}
}
getCurrentSite() {
if (this.currentSiteContainerToShow != null)
return this.currentSiteContainerToShow.getSite();
}
redrawCurrentActionBar() {
if (this.currentSiteContainerToShow != null)
this.currentSiteContainerToShow.getSite().actionMenu.redraw();
}
updateTitle() {
let title = this.getCurrentSite().title;
this.titleElement.removeAllChildren().appendChild(title.element);
document.title = Helper.nonNull(title.title, this.defaultTitle.title);
}
/** @private */
findContainerIndexBySite(site) {
for (let i = 0, n = this.siteContainerStack.length; i < n; i++) {
if (this.siteContainerStack[i].getSite() === site) {
return i;
}
}
return -1;
}
/** @private */
findContainerBySite(site) {
let index = this.findContainerIndexBySite(site);
if (index === -1) {
return null;
}
return this.siteContainerStack[index];
}
/** @private */
showAppEndedMessage() {
this.siteDiv.removeAllChildren().appendChild(Translator.makePersistentTranslation("The app has ended! Please close the window."));
}
/** @private */
buildActionBarMenu() {
return new ActionBarMenu(this.actionBarMenuSelector);
}
}
class PauseSite extends AbstractSite {
onConstruct(args) {
let pausedElement = null;
if (Helper.isSet(args, "url")) {
pausedElement = args["url"];
}
else {
pausedElement = document.createElement("div");
pausedElement.innerHTML = "Paused...";
}
this.inflateView(pausedElement);
}
}
class App {
constructor() {
this._siteManager = null;
this._actionBarMenuSelector = '.action-bar';
this._basePath = SystemSettings.getBasePath();
this._siteContentId = 'site-content';
this._deepLinks = new Map();
this._defaultActions = [];
this._addThemeAction = false;
this._showCookieCompliance = true;
this._startSite = null;
}
getSiteManager()
{
return this._siteManager;
}
addDefaultAction(action) {
this._defaultActions.push(action);
}
setAddThemeAction(addThemeAction) {
this._addThemeAction = addThemeAction;
}
getSiteContentId() {
return this._siteContentId;
}
setSiteContentId(value) {
this._siteContentId = value;
}
getActionBarMenuSelector() {
return this._actionBarMenuSelector;
}
setActionBarMenuSelector(value) {
this._actionBarMenuSelector = value;
}
getBasePath() {
return this._basePath;
}
setBasePath(value) {
this._basePath = value;
}
addDeepLink(alias, site) {
this._deepLinks.set(alias.toLowerCase(), site);
}
setShowCookieCompliance(cookieCompliance)
{
this._showCookieCompliance = cookieCompliance;
}
refreshCurrentSite()
{
this._siteManager.refreshCurrentSite();
}
pause(elementToShow){
this.startSite(PauseSite, {"url": elementToShow});
}
resume(){
const currentSite = this._siteManager.getCurrentSite();
if (currentSite instanceof PauseSite)
{
currentSite.finish();
}
}
_resolveDeepLink(deepLink) {
deepLink = deepLink.toLowerCase();
if (this._deepLinks.has(deepLink)) {
return this._deepLinks.get(deepLink);
}
return null;
}
_getDeepLink() {
let deepLink = "";
if (window.location.pathname.search(this._basePath) === 0) {
deepLink = window.location.pathname.substr(this._basePath.length).trim();
}
if (deepLink.charAt(0) === '/') {
deepLink = deepLink.substr(1).trim();
}
if (deepLink.charAt(deepLink.length - 1) === '/') {
deepLink = deepLink.substr(0, deepLink.length - 2).trim();
}
if (deepLink.length === 0 && window.location.hash) {
deepLink = window.location.hash.substr(1).trim();
}
return this._resolveDeepLink(deepLink);
}
_addDeepLinksListener() {
let app = this;
let elements = document.getElementsByClassName("deep-link");
for (let i = 0, n = elements.length; i < n; i++) {
elements[i].addEventListener("click", function (e) {
e.preventDefault();
app._siteManager.startSite(Helper.nonNull(app._resolveDeepLink(this.dataset["siteName"]), app._startSite), App._extractParams(this.dataset["siteArgs"]));
return true;
});
}
}
removeDefaultAction(action)
{
let index = this._defaultActions.indexOf(action);
if (index >= 0)
{
this._defaultActions[index].remove(true);
this._defaultActions.splice(index, 1);
}
}
startSite(site, parameter)
{
return this._siteManager.startSite(site, parameter);
}
start(fallbackStartSite) {
SystemSettings.setBasePath(this._basePath);
let startSite = Helper.nonNull(this._getDeepLink(), fallbackStartSite);
let startParams = App._getStartParams();
this._startSite = fallbackStartSite;
Translator.init();
ThemeManager.init();
if (this._addThemeAction) {
this.addDefaultAction(ThemeManager.generateChangeThemeMenuAction());
}
this._siteManager = new SiteManager(this._siteContentId, this._actionBarMenuSelector);
this._siteManager.defaultActions = this._defaultActions;
this._siteManager.setStartSiteName(fallbackStartSite);
this._siteManager.startSite(startSite, startParams);
this._addDeepLinksListener();
if (this._showCookieCompliance)
{
this._cookieClosePromise = CookieCompliance.showIfNeeded('cookie-compliance');
}
}
getCurrentSite(){
return this._siteManager.getCurrentSite();
}
async findSite(filter){
return this._siteManager.findSite(filter);
}
static _extractParams(paramString) {
if (Helper.isNull(paramString)) {
return null;
}
let result = {}, tmp = [];
let items = paramString.split("&");
for (let index = 0; index < items.length; index++) {
tmp = items[index].split("=");
if (tmp[0].trim().length > 0) {
result[tmp[0]] = decodeURIComponent(tmp[1]);
}
}
return result;
}
static _getStartParams() {
return App._extractParams(window.location.search.substr(1));
}
}
class Dialog {
constructor(content, title) {
this.resolver = null;
this.content = null;
this.backgroundElement = null;
this.cancelable = true;
this.title = Helper.nonNull(title, "");
this.translatable = true;
this.additionalClasses = "";
this.buttons = [];
this.result = null;
if (Helper.isNotNull(content)) {
this.setContent(content);
}
}
setTitle(title) {
this.title = title;
return this;
}
setTranslatable(translatable) {
this.translatable = translatable;
}
setAdditionalClasses(classes) {
this.additionalClasses = classes;
}
getTitle() {
return this.title;
}
setCancelable(cancelable) {
this.cancelable = (cancelable === true);
return this;
}
async setContent(content) {
this.contentPromise = Promise.resolve(content);
this.content = await this.contentPromise;
return this;
}
addButton(elementOrText, listenerOrResult, shouldClose) {
shouldClose = Helper.nonNull(shouldClose, true);
let button = null;
if (typeof elementOrText === "string") {
button = document.createElement("button");
button.classList.add("button");
button.classList.add("right");
button.appendChild(Translator.makePersistentTranslation(elementOrText));
}
else {
button = elementOrText;
}
let self = this;
if (typeof listenerOrResult !== "function") {
let result = listenerOrResult;
listenerOrResult = function () {
self.result = result;
};
}
let callback = null;
if (shouldClose) {
callback = function (e) {
if (Helper.isNotNull(listenerOrResult)) {
listenerOrResult(e);
}
self.close();
};
}
else {
callback = listenerOrResult;
}
if (Helper.isNotNull(callback)) {
button.addEventListener("click", callback);
}
this.buttons.push(button);
}
async show() {
let titleElement = document.createElement("span");
titleElement.classList.add("title");
if (this.translatable && this.title !== "") {
titleElement.appendChild(Translator.makePersistentTranslation(this.title));
}
else {
titleElement.innerHTML = this.title;
}
let titleBar = document.createElement("div");
titleBar.appendChild(titleElement);
let contentContainer = document.createElement("div");
contentContainer.classList.add("content-container");
let modalDialog = document.createElement("div");
modalDialog.className = this.additionalClasses;
modalDialog.classList.add("modal");
modalDialog.appendChild(titleBar);
modalDialog.appendChild(contentContainer);
let buttonBar = document.createElement("div");
buttonBar.classList.add("modal-button-container");
for (let i = 0, n = this.buttons.length; i < n; i++) {
buttonBar.appendChild(this.buttons[i]);
}
await this.contentPromise;
if (!(this.content instanceof Node)) {
this.content = (this.translatable) ? Translator.makePersistentTranslation(this.content) : document.createTextNode(this.content);
}
contentContainer.appendChild(this.content);
this.backgroundElement = document.createElement("div");
this.backgroundElement.classList.add("background");
this.backgroundElement.appendChild(modalDialog);
this.backgroundElement.querySelector(".modal").appendChild(buttonBar);
this.backgroundElement.style.display = "block";
let self = this;
if (this.cancelable) {
let closeButton = document.createElement("span");
closeButton.classList.add("close");
closeButton.innerHTML = "&times;";
titleBar.appendChild(closeButton);
closeButton.addEventListener("click", function () {
self.close();
});
window.addEventListener("click", function (e) {
if (e.target === self.backgroundElement) {
self.close();
}
});
}
document.body.appendChild(this.backgroundElement);
Translator.getInstance().updateTranslations();
return new Promise(function (resolve) {
self.resolver = resolve;
});
}
close() {
if (Helper.isNotNull(this.backgroundElement)) {
this.backgroundElement.style.display = "none";
this.backgroundElement.remove();
this.backgroundElement = null;
}
if (Helper.isNotNull(this.resolver)) {
this.resolver(this.result);
}
}
addDefaultButton(){
this.addButton("confirm-button");
}
}
class FlashMessenger {
static deleteMessage(_idNumber, _delayInMilliSeconds) {
_delayInMilliSeconds = Helper.nonNull(_delayInMilliSeconds, 0);
setTimeout(function () {
let elem = document.getElementById("flashMessage" + _idNumber);
elem.fadeOut(.2).then(function () {
elem.remove();
});
}, _delayInMilliSeconds);
}
static addMessage(messageType, messageText, timeToShow, translate){
let translationArgs = null;
if (Helper.isNull(messageText) || typeof messageText === "object")
{
translationArgs = messageText;
messageText = messageType;
messageType = FlashMessenger.MESSAGE_TYPE_SUCCESS;
translate = true;
}
translate = Helper.nonNull(translate, false);
let id = FlashMessenger.messageCount;
let wrapper = document.createElement("div");
let _flashMessage = document.createElement("div");
_flashMessage.className = "flashMessage " + messageType;
_flashMessage.id = "flashMessage" + id;
_flashMessage.style.opacity = '0';
_flashMessage.addEventListener("click", function () {
FlashMessenger.deleteMessage(id);
});
_flashMessage.appendChild((translate) ? Translator.makePersistentTranslation(messageText, translationArgs, "span") : document.createTextNode(messageText));
wrapper.appendChild(_flashMessage);
document.getElementById("flashMessageContainer").appendChild(wrapper);
_flashMessage.fadeIn();
timeToShow = Helper.nonNull(timeToShow, FlashMessenger.defaultTimeToShow);
if (timeToShow > 0) {
FlashMessenger.deleteMessage(FlashMessenger.messageCount, timeToShow);
}
FlashMessenger.messageCount++;
}
}
FlashMessenger.messageCount = 0;
FlashMessenger.defaultTimeToShow = 3500;
FlashMessenger.LENGTH_SHORT= 1000;
FlashMessenger.MESSAGE_TYPE_SUCCESS = 'success';
FlashMessenger.MESSAGE_TYPE_ERROR = 'error';
FlashMessenger.MESSAGE_TYPE_DEFAULT = 'default';
FlashMessenger.MESSAGE_TYPE_INFO = 'info';
FlashMessenger.MESSAGE_TYPE_WARNING = 'warning';
class MyDb {
constructor(dbName, version) {
let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
this._conn = indexedDB.open(dbName, version);
let myDB = this;
this._conn.onupgradeneeded = function (upgradeEvent) {
myDB.upgrade(myDB._conn.result, upgradeEvent.oldVersion, upgradeEvent.newVersion, upgradeEvent);
};
this.queryPromise = new Promise(function (resolve) {
myDB._conn.onsuccess = function (e) {
myDB._db = myDB._conn.result;
resolve(e);
};
});
}
openTransaction(name, transactionMode, callback) {
let myDb = this;
if (typeof transactionMode === 'function' && Helper.isNull(callback)) {
callback = transactionMode;
transactionMode = "read";
}
return this.queryPromise.then(function () {
let res = null;
try {
res = myDb._conn.result.transaction(name, transactionMode);
}
catch (e) {
console.warn(e);
res = myDb._conn.result.transaction(name);
}
callback(res);
});
}
openStore(name, transactionMode, callback) {
if (typeof transactionMode === 'function' && Helper.isNull(callback)) {
callback = transactionMode;
transactionMode = "readonly";
}
return this.openTransaction(name, transactionMode, function (t) {
callback(t.objectStore(name));
});
}
saveObj(obj, objectStore) {
let self = this;
return new Promise(function (resolve) {
self.openStore(objectStore, "readwrite", function (store) {
let request = store.put(obj);
request.onsuccess = resolve;
request.onerror = function (e) {
throw {
"type": "indexed-db-error",
"event": e
}
};
});
});
}
saveMany(manyObj, objectStore) {
let self = this;
return new Promise(function (resolve) {
self.openStore(objectStore, "readwrite", function (store) {
let promises = [];
for (let i = 0, n = manyObj.length; i < n; i++) {
promises.push(new Promise(function (resolveInner) {
let request = store.put(manyObj[i]);
request.onsuccess = resolveInner;
request.onerror = function (e) {
throw {
"type": "indexed-db-error",
"event": e
}
};
}));
}
resolve(Promise.all(promises));
});
});
}
load(key, objectStore) {
let self = this;
return new Promise( (resolve, reject) => {
self.openStore(objectStore, function (store) {
let request = store.get(key);
request.onsuccess = function (e) {
resolve(e.currentTarget.result);
};
request.onerror = function (e) {
console.warn(e);
throw {
"type": "indexed-db-load-error",
"event": e
}
};
}).catch(e => {
console.warn(e);
reject(e);
});
});
}
loadAll(objectStore, query, count)
{
let self = this;
return new Promise((resolve, reject) => {
self.openStore(objectStore, function (store) {
let request = store.getAll(query, count);
request.onsuccess = function (e) {
resolve(e.currentTarget.result);
};
request.onerror = function (e) {
console.warn(e);
throw {
"type": "indexed-db-load-error",
"event": e
}
};
}).catch(e => {
console.warn(e);
reject(e);
});
});
}
loadMany(index, value, objectStore, limit, direction) {
let self = this;
return new Promise(function (resolve) {
self.openStore(objectStore, function (store) {
let indexRequest = store.index(index);
indexRequest.onerror = function (e) {
throw {
"type": "indexed-db-index-error",
"event": e
}
};
let request = indexRequest.openCursor(value, direction);
request.onerror = function (e) {
throw {
"type": "indexed-db-index-error",
"event": e
}
};
let objects = [];
let numberResults = 0;
request.onsuccess = function (e) {
let cursor = e.target.result;
if (cursor) {
objects.push(cursor.value);
numberResults++;
if (Helper.isNull(limit) || numberResults < limit) {
cursor.continue();
return;
}
}
resolve(objects);
};
});
});
}
remove(id, objectStore) {
let self = this;
return new Promise(function (resolve) {
self.openStore(objectStore, "readwrite", function (store) {
let deleteRequest = store.delete(id);
deleteRequest.onerror = function (e) {
throw {
"type": "indexed-db-delete-error",
"event": e
}
};
deleteRequest.onsuccess = function (e) {
resolve();
};
});
});
}
removeMany(ids, objectStore) {
let self = this;
return new Promise(function (resolve) {
self.openStore(objectStore, "readwrite", function (store) {
let promises = [];
for (let i = 0, n = ids.length; i < n; i++) {
let deleteRequest = store.delete(ids[i]);
deleteRequest.onerror = function (e) {
throw {
"type": "indexed-db-delete-error",
"event": e
}
};
promises.push(new Promise(function (resolve) {
deleteRequest.onsuccess = function () {
resolve();
};
}));
}
resolve(Promise.all(promises));
});
});
}
removeWithIndex(index, value, objectStore) {
let self = this;
return new Promise(function (resolve) {
self.openStore(objectStore, "readwrite", function (store) {
let indexRequest = store.index(index);
indexRequest.onerror = function (e) {
throw {
"type": "indexed-db-index-error",
"event": e
}
};
let request = indexRequest.openCursor(value);
request.onerror = function (e) {
throw {
"type": "indexed-db-index-error",
"event": e
}
};
request.onsuccess = function (e) {
let cursor = e.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
else {
resolve();
}
};
});
});
}
removeAll(objectStore){
return new Promise((resolve) => {
this.openStore(objectStore, "readwrite", (store) => {
let req = store.clear();
req.onerror = (e) => {
throw {
"type":"indexed-db-index-error",
"event": e
}
};
req.onsuccess = resolve;
});
})
}
upgrade(db) {
};
}
class ScriptLoader {
static loadScript(scriptSrc) {
if (Helper.isNotNull(ScriptLoader.scriptPromises[scriptSrc])) {
return ScriptLoader.scriptPromises[scriptSrc];
}
else {
let scriptPromise = new Promise(function (resolve) {
let script = document.createElement("script");
script.src = Helper.basePath(scriptSrc);
script.onload = resolve;
document.body.appendChild(script);
});
ScriptLoader.scriptPromises[scriptSrc] = scriptPromise;
return scriptPromise;
}
}
static loadCss(cssFile, media){
if (Helper.isNotNull(ScriptLoader.cssPromises[cssFile])) {
return ScriptLoader.cssPromises[cssFile];
}
else {
media = Helper.nonNull(media, "all");
let cssPromise = new Promise(function (resolve) {
let link = document.createElement("link");
link.rel='stylesheet';
link.type="text/css";
link.href = Helper.basePath(cssFile);
link.media = media;
link.onload = resolve;
document.head.appendChild(link);
});
ScriptLoader.cssPromises[cssFile] = cssPromise;
return cssPromise;
}
}
}
ScriptLoader.scriptPromises = {};
ScriptLoader.cssPromises = {};
class ShareButton {
constructor(deviceType, icon, callback, shouldLoadImg)
{
this._deviceType = deviceType;
this._icon = icon;
this._callback = callback;
if (Helper.nonNull(shouldLoadImg, false)){
this._icon = ViewInflater.inflate(this._icon);
}
}
shouldShowFor(deviceType)
{
return (deviceType === (deviceType & this._deviceType))
}
getIcon()
{
return this._icon;
}
getCallback()
{
return this._callback;
}
}
ShareButton.TYPE_DESKTOP = 1;
ShareButton.TYPE_MOBILE_APPLE = 2;
ShareButton.TYPE_MOBILE_LEFTOVER = 4;
ShareButton.TYPE_MOBILE = ShareButton.TYPE_MOBILE_APPLE+ShareButton.TYPE_MOBILE_LEFTOVER;
ShareButton.TYPE_ALL = ShareButton.TYPE_DESKTOP+ShareButton.TYPE_MOBILE;
AndroidBridge.addDefinition(() => {
window["ShareButton"] = ShareButton;
window["ShareButton"]["TYPE_ALL"] = ShareButton.TYPE_ALL;
});
class MultipleShareButton extends ShareButton{
constructor(deviceType, icon, callbacks, shouldLoadImg)
{
if (Array.isArray(deviceType) && deviceType[0] instanceof ShareButton){
let btn = deviceType[0];
callbacks = deviceType;
deviceType = btn._deviceType;
icon = btn._icon;
shouldLoadImg = Helper.nonNull(shouldLoadImg, icon);
}
super(deviceType, icon, function (link, element, event) {
if (!Array.isArray(callbacks)){
callbacks = [callbacks];
}
for (let i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof ShareButton){
callbacks[i].getCallback()(link, element, event);
}
else {
console.log(callbacks, i);
callbacks[i](link, element, event);
}
}
}, shouldLoadImg);
}
}
class ShareManager {
static init() {
ShareManager.shareButtons = [];
}
static addShareButton(shareButton) {
ShareManager.shareButtons.push(shareButton);
}
static generateDefaultShareElement(shareUrl) {
return ShareManager.generateShareElement(shareUrl, ShareManager.getDefaultGenerateCallback());
}
static generateDefaultShareElementForButtons(shareUrl, buttons) {
return ShareManager.generateShareElementForButtons(shareUrl, buttons, ShareManager.getDefaultGenerateCallback());
}
static generateShareElement(shareUrl, generateCallback) {
return ShareManager.generateShareElementForButtons(shareUrl, ShareManager.shareButtons, generateCallback);
}
static generateShareElementForButtons(shareUrl, buttons, generateCallback) {
let shareButtonElement = document.createElement("div");
let currentDeviceType = ShareManager.getCurrentDeviceType();
for (let i = 0, n = buttons.length; i < n; i++) {
if (buttons[i].shouldShowFor(currentDeviceType)) {
let elem = generateCallback(buttons[i], shareUrl);
elem.onclick = function (event) {
buttons[i].getCallback()(shareUrl, this, event);
};
shareButtonElement.appendChild(elem);
}
}
return shareButtonElement;
}
static getCurrentDeviceType() {
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
return ShareButton.TYPE_MOBILE_APPLE;
}
else if ((navigator.userAgent.match(/Android|BlackBerry|Opera Mini|IEMobile/i) !== null || (typeof window.orientation !== "undefined"))) {
return ShareButton.TYPE_MOBILE_LEFTOVER;
}
else {
return ShareButton.TYPE_DESKTOP;
}
}
static getDefaultGenerateCallback() {
return function (button) {
let linkElement = document.createElement("a");
linkElement.classList.add("share-icon");
let iconUrl = button.getIcon();
if (typeof iconUrl === "string") {
let iconElement = document.createElement("img");
iconElement.src = Helper.basePath(button.getIcon());
iconElement.classList.add("share-icon");
linkElement.appendChild(iconElement);
}
else {
Promise.resolve(iconUrl).then(elem => {
linkElement.appendChild(elem);
});
}
return linkElement;
}
}
}
ShareManager.init();
AndroidBridge.addDefinition("ShareManager.addShareButton", ShareManager.addShareButton);
class SmsShareButton extends ShareButton
{
constructor(icon, shouldLoadImg) {
super(ShareButton.TYPE_MOBILE, icon, function (link) {
let linkToOpen = "";
if (ShareManager.getCurrentDeviceType() === ShareButton.TYPE_MOBILE_APPLE) {
linkToOpen = "sms:&body="+encodeURIComponent(link);
}
else {
linkToOpen = "sms:?&body=" + encodeURIComponent(link);
}
window.open(linkToOpen, '_blank', "noopener");
}, shouldLoadImg);
}
}
class TelegramShareButton extends ShareButton {
constructor(icon, shouldLoadImg) {
super(ShareButton.TYPE_ALL, icon, function (link) {
let linkToOpen = "https://t.me/share/url?url="+encodeURIComponent(link);
window.open(linkToOpen, '_blank', "noopener");
}, shouldLoadImg);
}
}
class WhatsappShareButton extends ShareButton {
constructor(icon, shouldLoadImg) {
super(ShareButton.TYPE_ALL, icon, function (link) {
let linkToOpen = "";
if (ShareManager.getCurrentDeviceType() === ShareButton.TYPE_DESKTOP) {
linkToOpen = "https://web.whatsapp.com/send?text="+encodeURIComponent(link);
}
else {
linkToOpen = "whatsapp://send?text=" + encodeURIComponent(link);
}
window.open(linkToOpen, '_blank', "noopener");
}, shouldLoadImg);
}
}
class Fragment extends Context
{
constructor(site, view)
{
super(view);
this.site = site;
this.active = true;
}
getSite()
{
return this.site;
}
isActive()
{
return this.active;
}
}
class Theme
{
constructor(name, className, icon)
{
this._name = name;
this._className = className;
this._icon = icon;
}
}
function applyPolyfills() {
if (!String.prototype.format) {
String.prototype["format"] = function (args) {
return this.replace(/{(\d+)}/g, function (match, number) {
return args[number] !== undefined
? args[number]
: match
;
});
};
}
Object["assign"] = Helper.nonNull(Object["assign"], function (base, obj) {
base = Helper.nonNull(base, {});
if (obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
return base;
// if (obj instanceof Date) {
// temp = new obj.constructor(); //or new Date(obj);
// }
// else {
// temp = obj.constructor();
// }
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
base[key] = obj[key];
delete obj['isActiveClone'];
}
}
return base;
});
if (typeof window !== 'undefined') {
if (Helper.isNotNull(window["Node"]) && !window["Node"]["prototype"]["removeAllChildren"]) {
Node.prototype["removeAllChildren"] = function () {
while (this.firstChild) {
this.removeChild(this.firstChild);
}
return this;
};
}
if (HTMLElement) {
HTMLElement.prototype["fadeOut"] = Helper.nonNull(HTMLElement.prototype["fadeOut"],
function (time, effect, delay) {
time = Helper.nonNull(time, 0.5);
effect = Helper.nonNull(effect, "ease-in-out");
delay = Helper.nonNull(delay, 0);
this.style.transition = "opacity " + time + "s " + effect + " " + delay + "s";
let elem = this;
let animPromise = new Promise(function (resolve) {
let transEndLis = function (e) {
elem.removeEventListener("transitionend", transEndLis);
elem.removeEventListener("transitioncancel", transCancelledLis);
elem.style.opacity = null;
elem.style.transition = null;
resolve(true, e);
};
let transCancelledLis = function (e) {
elem.removeEventListener("transitionend", transEndLis);
elem.removeEventListener("transitioncancel", transCancelledLis);
elem.style.opacity = null;
elem.style.transition = null;
resolve(false, e);
};
elem.addEventListener("transitionend", transEndLis);
elem.addEventListener("transitioncancel", transCancelledLis);
//Fallback
setTimeout(() => {
resolve(false);
}, (time + delay) * 1000);
});
//Nach Seitenneuzeichnen, damit chrome das immer macht (und FF auch)
requestAnimationFrame(function () {
requestAnimationFrame(function () {
elem.style.opacity = 0;
});
});
return animPromise
});
HTMLElement.prototype["fadeIn"] = Helper.nonNull(HTMLElement.prototype["fadeIn"], function (time, effect, delay) {
time = Helper.nonNull(time, 0.5);
effect = Helper.nonNull(effect, "ease-in-out");
delay = Helper.nonNull(delay, 0);
this.style.transition = "opacity " + time + "s " + effect + " " + delay + "s";
let elem = this;
let animPromise = new Promise(function (resolve) {
let transEndLis = function (e) {
elem.removeEventListener("transitionend", transEndLis);
elem.removeEventListener("transitioncancel", transCancelledLis);
elem.style.opacity = null;
elem.style.transition = null;
resolve(true, e);
};
let transCancelledLis = function (e) {
elem.removeEventListener("transitionend", transEndLis);
elem.removeEventListener("transitioncancel", transCancelledLis);
elem.style.opacity = null;
elem.style.transition = null;
resolve(false, e);
};
elem.addEventListener("transitionend", transEndLis);
elem.addEventListener("transitioncancel", transCancelledLis);
if (getComputedStyle(elem).getPropertyValue("opacity") === "1") {
resolve(false);
}
//Fallback
setTimeout(() => {
resolve(false);
}, (time + delay) * 1000);
//Nach Seitenneuzeichnen, damit chrome das immer macht (und FF auch)
requestAnimationFrame(function () {
requestAnimationFrame(function () {
elem.style.opacity = 1;
});
});
});
return animPromise;
});
}
if (Node) {
Node.prototype["replaceWith"] = Helper.nonNull(Node.prototype["replaceWith"], function (elem) {
this.parentElement.replaceChild(elem, this);
});
Node.prototype["remove"] = Helper.nonNull(Node.prototype["remove"], function () {
this.parentElement.removeChild(this);
});
}
if (Element) {
Element.prototype.matches = Helper.nonNull(Element.prototype.matches, Helper.nonNull(Element.prototype["matchesSelector"], Element.prototype["webkitMatchesSelector"]));
window["Element"]["prototype"]["closest"] = Helper.nonNull(window["Element"]["prototype"]["getAll"], function (s) {
// if (!Element.prototype.matches)
// Element.prototype.matches = Element.prototype.msMatchesSelector ||
// Element.prototype.webkitMatchesSelector;
//
// if (!Element.prototype.closest)
// Element.prototype.closest = function(s) {
let el = this;
if (!document.documentElement.contains(el)) return null;
do {
if (el.matches(s)) return el;
el = el.parentElement;
} while (el !== null);
return null;
// };
});
}
window["IDBObjectStore"]["prototype"]["getAll"] = Helper.nonNull(window["IDBObjectStore"]["prototype"]["getAll"], function () {
let res = {};
let items = [];
this.openCursor().onsuccess = function (e) {
let cursor = e.target.result;
if (Helper.isNotNull(cursor)) {
items.push(cursor.value);
cursor.continue();
}
else if (Helper.isNotNull(res.onsuccess)) {
res.onsuccess({currentTarget: {result: items}});
}
};
return res;
});
}
String.prototype.startsWith = Helper.nonNull(String.prototype.startsWith, function (searchString, position) {
position = position || 0;
return this.indexOf(searchString, position) === position;
});
String.prototype.endsWith = Helper.nonNull(String.prototype.endsWith, function (searchString, position) {
var subjectString = this.toString();
if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
position = subjectString.length;
}
position -= searchString.length;
var lastIndex = subjectString.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
});
window["fetch"] = Helper.nonNull(window["fetch"], function (url) {
console.log("customFetch", url);
let request = null;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
request = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
request = new ActiveXObject('Microsoft.XMLHTTP');
}
catch (e) {
}
}
}
let resultPromise = new Promise(function (resolve) {
request.onload = function () {
let data = this.responseText;
let response = {
json: function () {
return Promise.resolve(JSON.parse(data));
},
text: function () {
return Promise.resolve(data);
}
};
resolve(response);
};
request.onerror = function (err) {
resolve(Promise.reject(err));
};
});
request.open('get', url, true);
request.send();
return resultPromise;
});
}
class Constants{}
Constants.SCRIPTS = {
CKEDITOR:"version/2/ckeditor/ckeditor.js",
LIST_JS: "version/1/listjs/list.min.js"
};
class DataManager {
static async load(url, isCachable, raw) {
isCachable = Helper.nonNull(isCachable, false);
raw = Helper.nonNull(raw, false);
let fullUrl = (isCachable) ? Helper.basePath(DataManager.cachePath + url) : Helper.basePath(DataManager.dataPath + url);
return this._load(fullUrl, raw);
}
static async _load(url, raw) {
return fetch(url, {"credentials": "same-origin"}).then(function (res) {
if (raw) {
return res.text();
}
return res.json();
}).catch(function (e) {
if (!raw) {
return {
"success": false,
"errors": [
"not-online"
]
}
}
});
}
static async loadStatic(url, raw) {
raw = Helper.nonNull(raw, false);
let fullUrl = Helper.basePath(url);
return this._load(fullUrl, raw);
}
static async send(url, params) {
let fullUrl = Helper.basePath(DataManager.dataPath + url);
if (!(params instanceof FormData)) {
let newParams = new FormData();
for (let k in params) {
newParams.append(k, params[k]);
}
params = newParams;
}
return fetch(fullUrl, {
"credentials": "same-origin",
"method": "POST",
"body": params
}).then(function (res) {
return res.json();
}).catch(function (e) {
console.error("error", e);
return {
"success": false,
"errors": [
"not-online"
]
}
});
}
static buildQuery(values) {
return Helper.buildQuery(values);
}
}
DataManager.dataPath = "data/";
DataManager.cachePath = "cached/";
class Form {
constructor(formElem, url, method, isCachable) {
this.formElem = formElem;
this.method = Helper.nonNull(method, Helper.nonNull(formElem["method"], "POST"));
this.isCachable = (Helper.nonNull(isCachable, this.method.toLowerCase() === "get") === true);
this.isBusy = false;
if (typeof url === "string")
{
this.submitHandler = function(values){
if (self.method.toLowerCase() === "get") {
return (DataManager.load(url + DataManager.buildQuery(values), self.isCachable));
}
else {
return (DataManager.send(url, values));
}
};
}
else {
this.submitHandler = url;
}
let self = this;
this.submitCallback = null;
this.errorCallback = async function (errors) {
await self.setErrors(errors);
};
formElem.addEventListener("submit", async function (e) {
console.log("submitting!", e);
e.preventDefault();
await self.doSubmit(e);
});
for (let i = 0, n = formElem.elements.length; i < n; i++) {
let elem = formElem.elements[i];
elem.addEventListener("change", function () {
if (this.value.trim() !== "") {
this.classList.add("notEmpty");
}
else {
this.classList.remove("notEmpty");
}
this.setCustomValidity("");
});
elem.addEventListener("keydown", function () {
this.setCustomValidity("");
});
}
}
onError(errorHandler, ownHandlerForOptimisticLocking){
ownHandlerForOptimisticLocking = Helper.nonNull(ownHandlerForOptimisticLocking, true);
let callback = null;
if (ownHandlerForOptimisticLocking){
callback = function(errors){
if (Array.isArray(errors) && errors.indexOf("optimistic-locking-exception") >= 0){
let dialog = new Dialog("optimistic-locking-dialog", "optimistic-locking-dialog-title");
dialog.addDefaultButton();
dialog.show();
}
else
{
errorHandler(errors);
}
};
}
else
{
callback = errorHandler;
}
this.errorCallback = callback;
}
doSubmit() {
if (!this.isBusy) {
let self = this;
return this.submit().then(function (res) {
if (res["success"]) {
if (self.submitCallback !== null) {
return self.submitCallback(res["result"]);
}
}
else if (Helper.isNotNull(self.errorCallback)) {
return self.errorCallback(res["errors"]);
}
});
}
}
load(url, isCached) {
this.setValues(DataManager.load(url, isCached).then(function (values) {
if (values["success"]) {
return values["result"];
}
return {};
}));
return this;
}
setValues(valuePromise) {
this.setIsBusy(true);
let self = this;
return Promise.resolve(valuePromise).then(function (values) {
self.setIsBusy(false);
for (let k in values) {
if (Helper.isNotNull(self.formElem.elements[k])) {
if (Helper.isNotNull(self.formElem.elements[k].options) && Helper.isNotNull(values[k+"Options"]))
{
let options = self.formElem.elements[k].options;
for (let val in values[k+"Options"])
{
let option = document.createElement("option");
option.value = val;
option.innerText = values[k+"Options"][val];
options.add(option);
}
}
self.formElem.elements[k].value = Helper.htmlspecialcharsDecode(values[k]);
if (Helper.isNotNull(values[k]) && (""+values[k]).trim() !== "") {
self.formElem.elements[k].classList.add("notEmpty");
}
else {
self.formElem.elements[k].classList.remove("notEmpty");
}
}
}
return self;
});
}
async setErrors(errors) {
let hasElem = false;
let firstError = null;
for (let k in errors) {
if (Helper.isNotNull(this.formElem.elements[k]) && this.formElem.elements[k].type !== "hidden"
&& Helper.isNull(this.formElem.elements[k].readonly) && (
Helper.isNull(this.formElem.elements[k].disabled) || !this.formElem.elements[k].disabled)
) {
this.formElem.elements[k].setCustomValidity(Translator.translate(Helper.nonNull(errors[k], "form-default-error")));
hasElem = true;
}
if (Helper.isNull(firstError)) {
firstError = Helper.nonNull(errors[k], "form-default-error");
}
}
if (!hasElem && Helper.isNotNull(firstError)) {
for (let k in this.formElem.elements) {
if (this.formElem.elements[k].type !== "hidden") {
this.formElem.elements[k].setCustomValidity(Translator.translate(firstError));
hasElem = true;
break;
}
}
}
if (hasElem) {
this.formElem.querySelector("input[type=submit]").click();
}
}
setIsBusy(isBusy) {
this.isBusy = isBusy;
if (this.isBusy) {
this.formElem.classList.add("sending");
}
else {
this.formElem.classList.remove("sending");
}
}
submit() {
let self = this;
return new Promise(function (resolve) {
self.setIsBusy(true);
let values = new FormData(self.formElem);
resolve(self.submitHandler(values));
}).then(function (data) {
self.setIsBusy(false);
return data;
});
}
onSubmit(callback) {
this.submitCallback = callback;
}
}
class SettingsManager {
static getInstance() {
if (SettingsManager._instance === null) {
SettingsManager._instance = new SettingsManager();
}
return SettingsManager._instance;
}
constructor() {
this._settings = null;
this._localStorageKey = "settings";
}
getSettings() {
if (Helper.isNull(this._settings)) {
this._loadSettings();
}
return this._settings;
}
getSetting(name, defaultValue) {
const settings = this.getSettings();
if (Helper.isNotNull(settings[name])) {
return settings[name].value;
}
else {
return defaultValue;
}
}
deleteSetting(name) {
this.getSettings();
delete this._settings[name];
this._saveSettings();
}
setSetting(name, value) {
this.getSettings();
this._settings[name] = {
date: new Date().getTime(),
value: value
};
this._saveSettings();
}
setSettings(settingsObject) {
this.getSettings();
for (const k in settingsObject) {
this._settings[k] = settingsObject[k];
}
this._saveSettings();
}
hasSetting(name)
{
return Helper.nonNull(this._settings[name]);
}
_loadSettings() {
this._settings = localStorage.getItem(this._localStorageKey);
if (this._settings === null) {
this._settings = {};
}
else {
this._settings = JSON.parse(this._settings);
}
}
_saveSettings() {
if (this._settings !== null) {
localStorage.setItem(this._localStorageKey, JSON.stringify(this._settings));
}
}
}
SettingsManager._instance = null;
class LocalStorageSettingsFragment extends Fragment {
onFirstStart() {
let res = super.onFirstStart();
let settings = this.findBy(".setting", true);
const settingsManager = SettingsManager.getInstance();
for (let i = 0; i < settings.length; i++) {
let setting = settings[i];
const name = setting.name;
let value;
if (!setting["dataset"]["raw"]) {
value = settingsManager.getSetting(name);
} else {
value = localStorage.getItem(name);
}
let isCheckable = false;
if (setting instanceof HTMLInputElement && (setting.type === 'checkbox' || setting.type === 'radio')) {
isCheckable = true;
}
if (((!setting["dataset"]["raw"] && !settingsManager.hasSetting(name)) || (setting["dataset"]["raw"] && value === null))
&& Helper.isNotNull(settings[i]["dataset"]["default"])) {
value = setting["dataset"]["default"];
if (Helper.isNotNull(setting["dataset"]["defaultTranslateable"])) {
setting["dataset"]["translation"] = "";
setting["dataset"]["translationValue"] = value;
value = Translator.translate(value);
}
}
if (Helper.isNotNull(value)) {
if (isCheckable) {
setting.checked = (value === setting.value);
}
else {
setting.value = value;
}
if (value !== "") {
setting.classList.add("notEmpty");
}
}
setting.addEventListener("change", function () {
let value = this.value;
if (isCheckable && !this.checked) {
value = null;
}
if (!setting["dataset"]["raw"]) {
settingsManager.setSetting(name, value);
} else {
localStorage.setItem(name, value);
}
delete setting["dataset"]["translationValue"];
delete setting["dataset"]["translation"];
});
}
return res;
}
onStart() {
let res = super.onStart();
let settings = this.findBy(".setting", true);
const settingsManager = SettingsManager.getInstance();
for (let i = 0; i < settings.length; i++) {
let setting = settings[i];
const name = setting.name;
let value;
if (!setting["dataset"]["raw"]) {
value = settingsManager.getSetting(name);
} else {
value = localStorage.getItem(name);
}
let isCheckable = false;
if (setting instanceof HTMLInputElement && (setting.type === 'checkbox' || setting.type === 'radio')) {
isCheckable = true;
}
if (Helper.isNotNull(value)) {
if (isCheckable) {
setting.checked = (value === setting.value);
}
else {
setting.value = value;
}
if (value !== "") {
setting.classList.add("notEmpty");
}
}
}
return res;
}
}
class SmartColumn{
constructor(name, label, translateable){
this._name = name;
this._label = label;
this._translateable = Helper.nonNull(translateable, true);
this._sortable = true;
this._index = -1;
this._clickListener = null;
}
setClickListener(listener)
{
this._clickListener = listener;
return this;
}
setIndex(index)
{
this._index = index;
}
getName()
{
return this._name;
}
getLabel()
{
return this._label;
}
getHeadElement()
{
const headElement = document.createElement("th");
headElement.appendChild((this._translateable)?Translator.makePersistentTranslation(this._label):document.createTextNode(this._label));
if (this._sortable)
{
headElement.classList.add("sort");
headElement["dataset"]["sort"] = this._name;
}
headElement["dataset"]["column"] = this._index;
this._headElement = headElement;
return this._headElement;
}
getValueName(){
return this._name;
}
prepareData(myData, rowData)
{
return myData;
}
getItemElement(){
const element = document.createElement("td");
element.classList.add(this._name);
element["dataset"]["column"] = this._index;
if (Helper.isNotNull(this._clickListener))
{
element.classList.add("clickable");
}
return element;
}
click(tableCell, table, event){
if (Helper.isNotNull(this._clickListener))
{
this._clickListener(tableCell, table, event);
}
}
}
class ConstSmartColumn extends SmartColumn{
constructor(name, label, translatable, valueTranslatable) {
super(name, label, translatable);
this._sortable = false;
this._valueTranslatable = Helper.nonNull(valueTranslatable, false);
}
getValueName() {
return null;
}
getItemElement(){
const element = super.getItemElement();
element.classList.remove(this._name);
element.appendChild((this._valueTranslatable)?Translator.makePersistentTranslation(this._name):document.createTextNode(this._name));
return element;
}
}
class DataSmartColumn extends SmartColumn{
constructor(name, label, translateable) {
translateable = Helper.nonNull(translateable, false);
super(name, label, translateable);
}
getHeadElement() {
return document.createTextNode("");
}
getValueName() {
return {
"data":[this._name]
};
}
getItemElement() {
return document.createTextNode("");
}
}
class ImgConstSmartColumn extends ConstSmartColumn{
constructor(name, label, translateable) {
super(name, label, translateable);
this._valueTranslatable = false;
}
getItemElement() {
const element = super.getItemElement();
const imgElement = document.createElement("img");
imgElement["src"] = this._name;
element.removeAllChildren().appendChild(imgElement);
return element;
}
}
class ListHelper {
constructor(id, options, values) {
this._tableElement = id;
this._options = Helper.nonNull(options, {});
this._values = values;
if (typeof this._tableElement === "string") {
this._tableElement = document.getElementById(this._tableElement);
}
this._columns = [];
if (Array.isArray(options)) {
this._columns = options;
}
else if (Helper.isNotNull(options["columns"])) {
this._columns = options["columns"];
}
}
prepareData(data) {
console.log("prepareData", data);
if (Helper.isNotNull(data)) {
for (let i = 0, n = data.length; i < n; i++) {
data[i] = this.prepareDataset(data[i]);
}
}
return data;
}
prepareDataset(dataset) {
console.log("prepareDataset", dataset);
for (let i = 0, n = this._columns.length; i < n; i++) {
if (Helper.isNotNull(dataset[this._columns[i].getName()])) {
dataset[this._columns[i].getName()] = this._columns[i].prepareData(dataset[this._columns[i].getName()], dataset);
}
}
return dataset;
}
createTable() {
if (Helper.isNotNull(this._columns)) {
this.updateColumns();
}
let id = this._tableElement;
let options = this._options;
let values = this._values;
options["item"] = Helper.nonNull(options["item"], id["id"] + "-template-item");
options["page"] = Helper.nonNull(options["page"], 5);
options["pagination"] = Helper.nonNull(options["pagination"], {
"outerWindow": 1,
"innerWindow": 1
});
let template = document.getElementById(options["item"]);
if (template) {
options["item"] = template.outerHTML;
template.remove();
}
values = this.prepareData(values);
const list = new List(id, options, values);
let self = this;
id.querySelector("." + Helper.nonNull(options["listClass"], "list")).addEventListener("click", function (e) {
let columnElem = e.target.closest("td[data-column]");
const column = parseInt(columnElem["dataset"]["column"]);
if (self._columns.length > column) {
self._columns[column].click(columnElem, list, e);
}
});
this.list = list;
return list;
}
updateColumns() {
const head = document.createElement("tr");
const item = document.createElement("tr");
const valueNames = [];
for (let i = 0, n = this._columns.length; i < n; i++) {
this._columns[i].setIndex(i);
head.appendChild(this._columns[i].getHeadElement());
item.appendChild(this._columns[i].getItemElement());
const valueName = this._columns[i].getValueName();
if (Helper.isNotNull(valueName)) {
valueNames.push(valueName);
}
}
const header = this._tableElement.querySelector("thead");
const footer = this._tableElement.querySelector("tfoot");
if (Helper.isNotNull(header)) {
header.removeAllChildren().appendChild(head);
}
if (Helper.isNotNull(footer)) {
footer.removeAllChildren().appendChild(Helper.cloneNode(head));
}
this._options["item"] = item.outerHTML;
this._options["valueNames"] = valueNames;
}
getList() {
return this.list;
}
updateItem(valueName, value, newValues) {
const items = this.list.get(valueName, value);
if (Helper.isNotNull(items) && items.length >= 1) {
newValues = this.prepareDataset(newValues);
items[0].values(newValues);
}
}
setBusy(isBusy) {
if (isBusy) {
this._tableElement.classList.add("sending");
}
else {
this._tableElement.classList.remove("sending");
}
}
}
class SettingsSite extends AbstractSite {
constructor(siteManager) {
super(siteManager, SettingsSite.template, "settings");
for (let k in SettingsSite.settingsFragments) {
this.addSettingsFragment(k, new SettingsSite.settingsFragments[k](this));
}
this.active = null;
}
addSettingsFragment(name, settingsFragment) {
this.addFragment("#settings-fragments", settingsFragment);
delete this.fragments["#settings-fragments"];
this.fragments[name] = settingsFragment;
}
onStart() {
let res = super.onStart();
if (Helper.isNotNull(this.active) && !this.fragments[this.active].isActive()) {
this.setActive(null);
}
this.buildList();
return res;
}
setActive(name) {
if (Helper.isNotNull(this.active)) {
this.fragments[this.active].inflatePromise.then(function (view) {
view.classList.remove("active");
});
this.findBy("#show-fragment-" + this.active).classList.remove("active");
}
this.active = name;
if (Helper.isNotNull(this.active)) {
this.fragments[this.active].inflatePromise.then(function (view) {
view.classList.add("active");
});
this.findBy("#show-fragment-" + this.active).classList.add("active");
}
}
buildList() {
let listNameElem = this.findBy("#settings-fragment-list");
listNameElem.removeAllChildren();
let self = this;
for (let k in this.fragments) {
if (this.fragments[k].isActive()) {
let liElement = document.createElement("li");
liElement.id = "show-fragment-" + k;
liElement.appendChild(Translator.makePersistentTranslation(k, null, "a"));
liElement.addEventListener("click", function () {
self.setActive(k);
});
listNameElem.appendChild(liElement);
if (Helper.isNull(this.active)) {
this.setActive(k);
}
}
}
}
static addSettingsFragment(name, settingsFragment) {
SettingsSite.settingsFragments[name] = settingsFragment;
}
static setAddSettingsSite(addLink) {
SettingsSite.shouldAddSettingsSite = addLink;
}
static setTemplate(template) {
SettingsSite.template = template;
}
}
SettingsSite.template = 'core/html/settings.html';
SettingsSite.settingsFragments = {};
SettingsSite.shouldAddSettingsSite = true;
SettingsSite.settingsAction = null;
SettingsSite.shouldAddSettingsAction = true;
InitPromise.addPromise(function (app) {
if (SettingsSite.shouldAddSettingsSite) {
app.addDeepLink("settings", SettingsSite);
if (Helper.isNull(SettingsSite.settingsAction)) {
let settingsAction = new MenuAction("settings", async () => {
let currentSite = app.getCurrentSite();
if (currentSite instanceof SettingsSite) {
currentSite.finish();
}
else {
let settingsSite = await app.findSite((site) => {
return (site instanceof SettingsSite);
});
if (Helper.isNotNull(settingsSite)) {
settingsSite.toForeground();
}
else {
app.startSite(SettingsSite);
}
}
}, MenuAction.SHOW_FOR_LARGE, 10000);
settingsAction.setIcon("img/settings.png");
SettingsSite.settingsAction = settingsAction;
}
if (SettingsSite.shouldAddSettingsAction) {
app.addDefaultAction(SettingsSite.settingsAction);
}
}
});
class UserManager {
static init(app) {
UserManager.getMeUrl = null;
UserManager.userData = {
online: false,
id: null,
accesses: ["default"]
};
UserManager.app = app;
UserManager.fetchMePromise = new Promise(function (resolve) {
UserManager.fetchMePromiseResolver = resolve;
});
}
static setData(data) {
UserManager.userData = Object.assign(UserManager.userData, data);
let siteManager = UserManager.app.getSiteManager();
if (siteManager)
siteManager.redrawCurrentActionBar();
}
static fetchMe(url) {
UserManager.getMeUrl = Helper.nonNull(url, UserManager.getMeUrl);
return DataManager.load(UserManager.getMeUrl).then(function (result) {
if (result["success"]) {
UserManager.setData(result["result"]);
}
UserManager.fetchMePromiseResolver();
});
}
static logOut() {
return DataManager.load("u/logout").then(function (data) {
if (data["success"]) {
UserManager.setData(data["result"]);
let siteManager = UserManager.app.getSiteManager();
if (siteManager)
siteManager.refreshCurrentSite();
FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate("logged-out-successfully"));
}
});
}
static hasAccess(access) {
// console.log("Has access", access, UserManager.userData["accesses"].indexOf(access), UserManager.userData);
return (UserManager.userData["accesses"].indexOf(access) >= 0)
}
static addCurrentUserListener(userId, listener) {
UserManager.addIsLoggedInListener(function (isLoggedIn) {
listener(isLoggedIn && UserManager.isCurrentUser(userId));
});
}
static addIsLoggedInListener(listener) {
this.fetchMePromise.then(function () {
listener(UserManager.isLoggedIn());
});
}
static isCurrentUser(userId) {
return UserManager.userData.id === userId;
}
static isLoggedIn() {
return Helper.isNotNull(UserManager.userData) && Helper.isNotNull(UserManager.userData.id);
}
}
InitPromise.addPromise(function(app){
UserManager.init(app);
return UserManager.fetchMe("u/me").then(function(){
UserManager.addIsLoggedInListener(function (isLoggedIn) {
if (isLoggedIn) {
const settingsManager = SettingsManager.getInstance();
const settings = Helper.cloneJson(settingsManager.getSettings());
for (let k in settings) {
settings[k]["value"] = JSON.stringify(settings[k]["value"]);
}
DataManager.send("u/syncSettings", settings).then(function(res){
if (res["success"])
{
for (let k in res["result"])
{
res["result"][k]["value"] = JSON.parse(res["result"][k]["value"]);
}
settingsManager.setSettings(res["result"]);
}
});
}
});
});
});
class UserAction extends MenuAction {
constructor(title, callback, icon, order, access) {
super(title, callback, icon, order);
this._access = Helper.nonNull(access, "default");
}
getVisible() {
// console.log("Action-access: ", this._access);
return (super.getVisible() && UserManager.hasAccess(this._access));
}
getAccess() {
return this._access;
}
copy(instance){
let copy = super.copy(Helper.nonNull(instance, new UserAction()));
copy._access = this._access;
return copy;
}
}
class NotAllowedSite extends AbstractSite{
constructor(siteManager) {
super(siteManager, 'userManagement/html/403.html');
}
}
class UserSite extends AbstractSite {
constructor(siteManager, view, deepLink, access) {
super(siteManager, view, deepLink);
this._access = access;
}
onConstruct(args) {
if (!UserManager.hasAccess(this._access))
{
this.startSite(NotAllowedSite);
this.finish({
"error":403
});
return;
}
return super.onConstruct(args);
}
onStart(args) {
if (!UserManager.hasAccess(this._access))
{
this.startSite(NotAllowedSite);
this.finish({
"error":403
});
return;
}
return super.onStart(args);
}
}
class LoginForm extends Form {
constructor(formElem, url, method, isCachable) {
super(formElem, url, method, isCachable);
let emailElem = formElem.querySelector("#email");
let passwordElem = formElem.querySelector("#password");
let listener = function(){
emailElem.setCustomValidity("");
passwordElem.setCustomValidity("");
};
emailElem.addEventListener("keydown", listener);
passwordElem.addEventListener("keydown", listener);
}
}
class RegistrationForm extends Form {
constructor(formElem, url, method, isCachable) {
super(formElem, url, method, isCachable);
// this.pw1 = formElem.querySelector("#password1");
// this.pw2 = formElem.querySelector("#password2");
// let self=this;
// this.pw1.addEventListener("change", function(){
// self.checkPw();
// });
// this.pw2.addEventListener("change", function(){
// self.checkPw();
// });
}
checkPw(){
// if (this.pw1.value !== this.pw2.value || this.pw1.value.length < 8)
// {
//
// }
}
}
class UserFragment extends Fragment{
constructor(site, view, access) {
super(site, view);
this._access = access;
}
isActive() {
return super.isActive() && UserManager.hasAccess(this._access);
}
}
class PasswordSettingsFragment extends UserFragment{
constructor(site)
{
super(site, "userManagement/html/fragments/passwordSettings.html", "online");
}
onFirstStart() {
let res = super.onFirstStart();
let form = new Form(document.getElementById("change-password-form"), "u/passwordSettings/set", "post");
form.onSubmit(function(res){
for (let i = 0, n = res.length; i < n; i++)
{
FlashMessenger.addMessage(res[i]);
}
form.setValues({
"oldPassword":"",
"newPassword1":"",
"newPassword2":""
});
});
return res;
}
}
InitPromise.addPromise(function(){
SettingsSite.addSettingsFragment("password-settings", PasswordSettingsFragment);
});
class UserSettingsFragment extends UserFragment{
constructor(site)
{
super(site, "userManagement/html/fragments/userSettings.html", "online");
}
onFirstStart() {
let res = super.onFirstStart();
(new Form(document.getElementById("user-settings-form"), "u/userSettings/set", "post")).load('u/userSettings').onSubmit(function(res){
for (let i = 0, n = res.length; i < n; i++)
{
FlashMessenger.addMessage(res[i]);
}
});
return res;
}
}
InitPromise.addPromise(function(){
SettingsSite.addSettingsFragment("user-settings", UserSettingsFragment);
});
class EditUserRolesSite extends UserSite {
constructor(siteManager) {
super(siteManager, 'userManagement/html/editUserRoles.html', "userRoles", "admin");
}
onConstruct(args) {
let res = super.onConstruct(args);
this.userId = args["id"];
let self = this;
return Promise.all([
ScriptLoader.loadScript(Constants.SCRIPTS.LIST_JS),
DataManager.load("u/userRoles" + DataManager.buildQuery({"id": self.userId})).then(function (res) {
if (!res["success"]) {
FlashMessenger.addMessage(res["errors"][0]);
self.finish();
}
else {
self.setUserRoles(res["result"]["userRoles"]);
self.setAvailableRoles(res["result"]["availableRoles"]);
self.setUsername(res["result"]["username"]);
}
})
]).then(function () {
return res;
});
}
onFirstStart() {
this.findBy("#username").innerHTML = this.username;
const userRolesElement = this.findBy("#userRoles");
const availableRolesElement = this.findBy("#availableRoles");
const imgColumnUserRoles = new ImgConstSmartColumn("img/minus.png", "", false);
const imgColumnAvailableRoles = new ImgConstSmartColumn("img/plus.png", "", false);
const userRolesColumns = [
new DataSmartColumn("id"),
new SmartColumn("name", "name"),
new SmartColumn("description", "description"),
imgColumnUserRoles,
];
const availableRolesColumns = [
new DataSmartColumn("id"),
new SmartColumn("name", "name"),
new SmartColumn("description", "description"),
imgColumnAvailableRoles,
];
const userRolesListHelper = new ListHelper(userRolesElement, userRolesColumns, this.userRoles);
const availableRolesListHelper = new ListHelper(availableRolesElement, availableRolesColumns, this.availableRoles);
const userRolesTable = userRolesListHelper.createTable();
const availableRolesTable = availableRolesListHelper.createTable();
let self = this;
const changeRoleFunction = function (roleId, addRole) {
userRolesListHelper.setBusy(true);
availableRolesListHelper.setBusy(true);
return DataManager.send("u/changeUserRole", {
"id": roleId,
"userId": self.userId,
"add": addRole
}).then(function (res) {
userRolesListHelper.setBusy(false);
availableRolesListHelper.setBusy(false);
if (!res["success"]) {
FlashMessenger.addMessage(res["errors"][0]);
return res;
}
let removingTable = null;
let addingTable = null;
if (res["result"]["hasRole"]) {
removingTable = availableRolesTable;
addingTable = userRolesTable;
}
else {
addingTable = availableRolesTable;
removingTable = userRolesTable;
}
const rowData = removingTable.get("id", roleId);
if (rowData.length === 1) {
addingTable.add(rowData[0].values());
removingTable.remove("id", roleId);
}
return res;
});
};
imgColumnUserRoles.setClickListener(function (cell) {
let userRoleId = cell.closest("tr")["dataset"]["id"];
changeRoleFunction(userRoleId, false);
});
imgColumnAvailableRoles.setClickListener(function (cell) {
let availableRoleId = cell.closest("tr")["dataset"]["id"];
changeRoleFunction(availableRoleId, true);
});
}
setUserRoles(userRoles) {
this.userRoles = userRoles;
}
setAvailableRoles(availableRoles) {
this.availableRoles = availableRoles;
}
setUsername(username) {
this.username = username;
}
}
InitPromise.addPromise(function (app) {
app.addDeepLink("userRoles", EditUserRolesSite);
app.addDefaultAction(new UserAction('userRoles', function(){
app.startSite(EditUserRolesSite);
}, null, 1100, "admin"));
});
class ForgotPasswordSite extends UserSite{
constructor(siteManager) {
super(siteManager, 'userManagement/html/forgotPassword.html', "forgotPassword", "offline");
}
onFirstStart() {
let self = this;
(new Form(document.getElementById("forgot-password-form"), "u/newPassword", "post")).onSubmit(function(res){
// UserManager.setData(res);
// self.startStartsite();
FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate("new-password-code-send"));
self.finish();
});
}
}
InitPromise.addPromise(function(app){
app.addDeepLink("forgotPassword", ForgotPasswordSite);
});
class LoginSite extends UserSite {
constructor(siteManager) {
super(siteManager, 'userManagement/html/login.html', "login", "offline");
}
onFirstStart() {
let self = this;
(new LoginForm(document.getElementById("login-form"), "u/login", "post")).onSubmit(function (res) {
UserManager.setData(res);
self.startStartsite();
FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate("login-success"));
self.finish();
});
this.findBy("#forgot-password-link").addEventListener("click", function () {
self.startSite(ForgotPasswordSite);
self.finish();
});
}
}
LoginSite.loginAction = null;
LoginSite.logoutAction = null;
LoginSite.addLoginAction = true;
LoginSite.addLogoutAction = true;
InitPromise.addPromise(function (app) {
app.addDeepLink("login", LoginSite);
if (Helper.isNull(LoginSite.loginAction)) {
LoginSite.loginAction = new UserAction('login', function () {
app.startSite(LoginSite);
}, Menu.SHOW_NEVER, 1100, "offline");
}
if (Helper.isNull(LoginSite.logoutAction)) {
LoginSite.logoutAction = new UserAction('logout', function () {
UserManager.logOut();
}, Menu.SHOW_NEVER, 1100, "online");
}
if (LoginSite.addLoginAction){
app.addDefaultAction(LoginSite.loginAction);
}
if (LoginSite.addLogoutAction){
app.addDefaultAction(LoginSite.logoutAction);
}
});
class RegistrationSite extends UserSite {
constructor(siteManager) {
super(siteManager, 'userManagement/html/registration.html', "registration", "offline");
}
onFirstStart() {
(new RegistrationForm(document.getElementById("registration-form"), "u/registration", "post")).onSubmit(function (res) {
FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate("registration-success"));
});
}
}
RegistrationSite.action = null;
RegistrationSite.addAction = true;
InitPromise.addPromise(function (app) {
app.addDeepLink("registration", RegistrationSite);
if (Helper.isNull(RegistrationSite.action)) {
RegistrationSite.action = new UserAction('registration', function () {
app.startSite(RegistrationSite);
}, null, 1100, "offline");
}
if (RegistrationSite.addAction) {
app.addDefaultAction(RegistrationSite.action);
}
});
class SetNewPasswordSite extends UserSite {
constructor(siteManager) {
super(siteManager, 'userManagement/html/setNewPassword.html', "newPassword", "offline");
}
onConstruct(args) {
this.code = args["code"];
return super.onConstruct(args);
}
onFirstStart() {
let formElem = document.getElementById("new-password-form");
document.getElementById("code").value = this.code;
let self = this;
(new Form(formElem, "c/code", "post")).onSubmit(function(res){
FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate("password-updated"));
self.startSite(LoginSite);
self.finish();
});
}
}
InitPromise.addPromise(function(app){
app.addDeepLink("newPassword", SetNewPasswordSite);
});
class InstallManager {
static init() {
window.addEventListener('beforeinstallprompt', e => {
e.preventDefault();
this.setDeferredPrompt(e);
});
}
static setDeferredPrompt(e){
this.deferredPromt = e;
if (this.canInstallListener) {
this.canInstallListener(this.deferredPromt);
}
}
static async prompt(){
if (Helper.isNotNull(this.deferredPromt)){
this.deferredPromt["prompt"]();
return this.deferredPromt["userChoice"];
}
return Promise.resolve({
"outcome":"dismissed",
"platform":""
});
}
static setCanInstallListener(listener, callIfCanInstall) {
this.canInstallListener = listener;
callIfCanInstall = Helper.nonNull(callIfCanInstall, true);
if (callIfCanInstall && Helper.nonNull(this.deferredPromt)) {
this.canInstallListener(this.deferredPromt);
}
}
}
InstallManager.init();
class Matomo {
static init() {
Matomo.isTrackingPromise = new Promise(async (resolve) => {
let shouldTrack = Helper.nonNull(localStorage.getItem(Matomo.LOCAL_STORAGE_KEY), "1");
if (Helper.isNull(shouldTrack)) {
shouldTrack = await Matomo._askIsTracking();
localStorage.setItem(Matomo.LOCAL_STORAGE_KEY, shouldTrack);
}
else {
shouldTrack = (shouldTrack === "1");
Matomo.setTrack(shouldTrack);
}
resolve(shouldTrack);
});
Matomo.isTrackingPromise.then(() => {
Matomo.push(['trackPageView'], true);
Matomo.push(['enableLinkTracking'], true);
Matomo.push(['setTrackerUrl', Matomo.TRACK_SITE + '/piwik.php'], true);
Matomo.push(['setSiteId', Matomo.SIDE_ID + ""], true);
let d = document, g = d.createElement('script'), s = d.getElementsByTagName('head')[0];
g.type = 'text/javascript';
g.async = true;
g.defer = true;
g.src = Matomo.TRACK_SITE + '/piwik.js';
s.appendChild(g);
});
}
static update(title) {
if (Helper.nonNull(Matomo.currentUrl)) {
Matomo.push(['setReferrerUrl', Matomo.currentUrl]);
}
Matomo.currentUrl = window.location.pathname + window.location.search;
Matomo.push(['setCustomUrl', Matomo.currentUrl]);
Matomo.push(['setDocumentTitle', title]);
// remove all previously assigned custom variables, requires Matomo (formerly Piwik) 3.0.2
Matomo.push(['deleteCustomVariables', 'page']);
Matomo.push(['setGenerationTimeMs', 0]);
Matomo.push(['trackPageView']);
// make Matomo aware of newly added content
var content = document.getElementById('site-content');
Matomo.push(['MediaAnalytics::scanForMedia', content]);
Matomo.push(['FormAnalytics::scanForForms', content]);
Matomo.push(['trackContentImpressionsWithinNode', content]);
Matomo.push(['enableLinkTracking']);
}
static async _askIsTracking() {
Matomo.isTrackingPromise = new Promise(resolve => {
Matomo.push([function () {
resolve(!this["isUserOptedOut"]());
}]);
Matomo.push([function () {
resolve(!this["isUserOptedOut"]());
}]);
});
return Matomo.isTrackingPromise;
}
static async query(method) {
return fetch(Matomo.TRACK_SITE + Matomo.BASE_PATH + method, {
"mode": "cors",
"credentials": "include",
}).then(res => res.text()).then(text => (new window.DOMParser()).parseFromString(text, "text/xml"));
}
static getTrackingPromise() {
return Matomo.isTrackingPromise;
}
static async setTrack(shouldTrack) {
Matomo.isTrackingPromise = Promise.resolve(shouldTrack);
localStorage.setItem(Matomo.LOCAL_STORAGE_KEY, (shouldTrack === true) ? "1" : "0");
if (shouldTrack) {
Matomo.push(["forgetUserOptOut"], true);
}
else {
Matomo.push(["optUserOut"], true);
}
}
static async trackEvent(event, name, label, value){
let ev = ["trackEvent", event, name];
if (Helper.isNotNull(label)){
ev.push(label);
}
if (Helper.isNotNull(value) && !isNaN(parseFloat(value)) && isFinite(value)){
ev.push(value);
}
return this.push(ev);
}
static async push(arr, force) {
if (!Array.isArray(arr)) {
arr = [arr];
}
window["_paq"].push(arr);
}
}
Matomo.currentUrl = null;
Matomo.LOCAL_STORAGE_KEY = "matomoShouldTrack";
Matomo.TRACK_SITE = "//matomo.silas.link";
Matomo.BASE_PATH = "/index.php?module=API&method=AjaxOptOut.";
Matomo.SIDE_ID = "1";
InitPromise.addPromise(() => {
window["_paq"] = window["_paq"] || [];
Matomo.init();
});
class MatomoShareButton extends MultipleShareButton{
constructor(baseButton, platform, shouldLoadImg) {
super([baseButton, (url) => {
Matomo.trackEvent("shared", url, platform);
}], shouldLoadImg);
}
}
AndroidBridge.addDefinition("MatomoShareButton", MatomoShareButton);
class ScaleHelper {
async scaleTo(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay, addListener) {
addListener = Helper.nonNull(addListener, true);
animationDelay = Helper.nonNull(animationDelay, 0);
let newFontSize = await this._getNewFontSize(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay === 0);
if (animationDelay > 0) {
await new Promise(r => {
setTimeout(r, animationDelay);
fontElement.style.fontSize = newFontSize + "px";
});
}
let self = this;
let listener = function () {
return new Promise(resolve => {
let timeout = (typeof addListener === 'number') ? addListener : 255;
setTimeout(() => {
resolve(self.scaleTo(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay, false));
}, timeout);
});
};
if (addListener !== false) {
window.addEventListener("resize", listener);
}
return listener;
}
async scaleToFull(fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animDelay, addListener) {
return this.scaleTo(1, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animDelay, addListener);
}
async _getNewFontSize(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, setFontSize) {
margin = Helper.nonNull(margin, 10);
ignoreHeight = Helper.nonNull(ignoreHeight, false);
ignoreWidth = Helper.nonNull(ignoreWidth, false);
fontWeight = Helper.nonNull(fontWeight, fontElement.innerHTML.length);
setFontSize = Helper.nonNull(setFontSize, true);
let hasNoTransitionClass = container.classList.contains("no-transition");
if (!hasNoTransitionClass) {
container.classList.add("no-transition");
}
const numChanged = 5;
let oldDiffIndex = 0;
let oldDiff = [];
for (let i = 0; i < numChanged; i++) {
oldDiff.push(0);
}
let beforeFontSize = fontElement.style.fontSize;
let currentFontSize = 1;
let widthDiff = 0;
let heightDiff = 0;
let containerWidth = 0;
let containerHeight = 0;
do {
currentFontSize += oldDiff[oldDiffIndex] / (fontWeight + 1);
fontElement.style.fontSize = currentFontSize + 'px';
let containerStyle = window.getComputedStyle(container);
containerWidth = containerStyle.getPropertyValue("width").replace('px', '');
containerHeight = containerStyle.getPropertyValue("height").replace('px', '');
widthDiff = containerWidth - fontElement.offsetWidth;
heightDiff = containerHeight - fontElement.offsetHeight;
oldDiffIndex = (oldDiffIndex+1)%numChanged;
let newDiff = (ignoreWidth ? heightDiff : (ignoreHeight ? widthDiff : Math.min(widthDiff, heightDiff)));
if (newDiff === oldDiff[(oldDiffIndex+1)%numChanged]) {
break;
}
oldDiff[oldDiffIndex] = newDiff;
} while ((widthDiff > (1 - scale) * containerWidth || ignoreWidth) && (heightDiff > (1 - scale) * containerHeight || ignoreHeight));
currentFontSize -= margin;
fontElement.style.fontSize = ((setFontSize) ? currentFontSize + "px" : beforeFontSize);
if (!hasNoTransitionClass) {
await new Promise((r) => {
setTimeout(r, 50);
});
container.classList.remove("no-transition");
}
return currentFontSize;
}
}
class AudioChain {
constructor(context, sourceBuffer, chainFunction) {
this.buffer = sourceBuffer;
this.shouldLoop = false;
this.loopStart = null;
this.loopEnd = null;
this.chainFunction = chainFunction;
this.context = context;
this.startTime = null;
this.pauseTime = null;
this.source = null;
this.running = false;
}
setBuffer(buffer) {
this.buffer = buffer;
}
setLooping(shouldLoop, loopStart, loopEnd) {
this.shouldLoop = shouldLoop;
if (Helper.isNotNull(loopStart)) {
this.loopStart = loopStart;
}
if (Helper.isNotNull(loopEnd)) {
this.loopEnd = loopEnd;
}
}
async start(delay, offset, duration) {
let source = this.context.createBufferSource();
source.loop = this.shouldLoop;
if (Helper.isNotNull(this.loopStart)) {
source.loopStart = this.loopStart;
}
if (Helper.isNotNull(this.loopEnd)) {
source.loopEnd = this.loopEnd;
}
source.buffer = this.buffer;
await this.chainFunction(source);
source.start(delay, offset, duration);
this.startTime = (new Date()).getTime() - (Helper.nonNull(offset, 0) * 1000);
this.source = source;
this.running = true;
}
async stop(delay) {
if (Helper.isNotNull(this.source)) {
this.pauseTime = ((new Date()).getTime()) - this.startTime;
this.running = false;
return this.source.stop(delay);
}
return null;
}
async resume() {
if (!this.running) {
return this.start(null, Helper.nonNull(this.pauseTime, 0) / 1000.0);
}
}
}
class SoundManager {
static getInstance() {
if (Helper.isNull(SoundManager.instance)) {
SoundManager.instance = new SoundManager();
}
return SoundManager.instance;
}
constructor() {
this.channels = {};
this.context = new AudioContext();
window.addEventListener("visibilitychange", () => {
this.handleVisibilityChange();
});
}
set(options, channel) {
channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
let audioObject = Helper.nonNull(this.channels[channel], {});
if (typeof options === "string") {
options = {audio: options};
}
let audio = options.audio;
if (Helper.isNotNull(audio)) {
audioObject.loadedPromise = fetch(audio).then(res => res.arrayBuffer()).then(arrayBuffer => this.context.decodeAudioData(arrayBuffer));
this.stop(channel);
}
audioObject.muted = Helper.nonNull(options.muted, audioObject.muted, false);
audioObject.volume = Helper.nonNull(options.volume, audioObject.volume, 1);
audioObject.loop = Helper.nonNull(options.loop, audioObject.loop, false);
audioObject.timeOffset = Helper.nonNull(options.timeOffset, audioObject.timeOffset, 0);
this.channels[channel] = audioObject;
if (audioObject.muted) {
this.stop(channel);
}
return this.channels[channel];
}
async play(channel, audioOrOptions) {
channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
if (Helper.isNull(audioOrOptions)) {
audioOrOptions = {};
} else if (!(typeof audioOrOptions === "object")) {
audioOrOptions = {
audio: audioOrOptions
};
}
audioOrOptions.timeOffset = Helper.nonNull(audioOrOptions.timeOffset, 0);
this.stop(channel);
this.set(audioOrOptions, channel);
if (!this.channels[channel].muted) {
let buffer = await this.channels[channel].loadedPromise;
let source = new AudioChain(this.context, buffer, (sourceNode) => {
let gain = this.context.createGain();
gain.gain.value = this.channels[channel].volume;
sourceNode.connect(gain);
gain.connect(this.context.destination);
});
source.setBuffer(buffer);
//to prevent gap in mp3-files
source.setLooping(this.channels[channel].loop, 0.3, buffer.duration - 0.3);
source.start();
this.channels[channel].source = source;
}
return this.channels[channel];
}
stop(channel) {
channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
let oldAudio = this.channels[channel];
if (Helper.nonNull(oldAudio) && Helper.nonNull(oldAudio.source)) {
oldAudio.source.stop();
}
}
get(channel) {
channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
return this.channels[channel];
}
async resume(channel){
channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
if (!this.channels[channel].muted && Helper.isNotNull(this.channels[channel].source)) {
return this.channels[channel].source.resume();
}
}
handleVisibilityChange() {
if (document.hidden) {
for (let k in this.channels) {
if (Helper.isNotNull(this.channels[k].source)) {
this.channels[k].source.stop();
}
}
}
else {
for (let k in this.channels) {
if (!this.channels[k].muted && Helper.isNotNull(this.channels[k].source)) {
this.channels[k].source.resume();
}
}
}
}
}
SoundManager.CHANNELS = {
MUSIC: "music",
SOUND: "sound",
DEFAULT: "default"
};
class Code {
constructor(args) {
if (typeof args === "string") {
args = {
"code": args
};
}
this.args = args;
this.isCacheable = false;
}
setIsCacheable(isCacheable) {
this.isCacheable = isCacheable;
}
getIsCacheable() {
return this.isCacheable;
}
activate() {
return DataManager.send("c/code", this.args);
}
}
class CodeSite extends AbstractSite {
constructor(siteManager) {
super(siteManager, "core/html/load.html", "code");
}
onConstruct(args) {
super.onConstruct(args);
console.log(args);
let resPromise = Promise.resolve();
if (Helper.isNotNull(args["code"])) {
let code = args["code"];
let isCachable = Helper.nonNull(args["cachable"], false);
let codeObject = new Code(code);
codeObject.setIsCacheable(isCachable);
let self = this;
resPromise = codeObject.activate().then(function (res) {
if (!res["success"]) {
FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate(res["errors"][0]));
}
else {
FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate(Helper.nonNull(res["result"]["successMessage"], "code-activated")));
}
self.finish();
});
}
}
}
InitPromise.addPromise(function (app) {
app.addDeepLink("code", CodeSite);
});
class ContactSite extends AbstractSite{
constructor(siteManager) {
super(siteManager, 'contact/html/contact.html', ContactSite.DEEP_LINK);
}
onFirstStart() {
new Form(this.findBy("#contact-form"), "contact", "post").onSubmit((d) => {
FlashMessenger.addMessage("contact-message-sent");
this.finish();
});
super.onFirstStart();
}
}
ContactSite.DEEP_LINK = "contactMe";
InitPromise.addPromise((app) => {
if (ContactSite.DEEP_LINK){
app.addDeepLink(ContactSite.DEEP_LINK, ContactSite);
}
});
class WordRotatorBaseSite extends AbstractSite {
// createActionBarMenu(menu) {
// menu = super.createActionBarMenu(menu);
// // menu.addAction(SettingsSite.settingsAction.copy());
// // let actions = menu.actions;
// // for (let i = 0; i < actions.length; i++) {
// // if (actions[i].title === "login" || actions[i].title === "registration"){
// // actions[i].setVisible(false);
// // }
// // }
// return menu;
// }
}
class TemplateContainer{
constructor(leafTemplate, parentTemplate, rowTemplate, triangleTemplate){
this.leafTemplate = leafTemplate;
this.parentTemplate = parentTemplate;
this.rowTemplate = rowTemplate;
this.triangleTemplate = triangleTemplate;
}
copyLeafTemplate()
{
return Helper.cloneNode(this.leafTemplate);
}
copyParentTemplate()
{
return Helper.cloneNode(this.parentTemplate);
}
copyRowTemplate()
{
return Helper.cloneNode(this.rowTemplate);
}
copyTriangleTemplate()
{
return Helper.cloneNode(this.triangleTemplate);
}
}
class Segment{
constructor(element){
this.rotation = 0;
this.element = element;
this.parent = null;
}
getCurrentRotations(rotations){
return rotations;
}
sameAs(otherSegment){
return false;
}
setParent(parent)
{
this.parent = parent;
}
getLevel()
{
if (this.parent!==null)
{
return this.parent.getLevel();
}
}
canRotate(){
return false;
}
isSolved(){
return (this.rotation === 0);
}
async rotate(){
return Promise.resolve();
};
_updateElement(){};
applyRotations(rotations){
return rotations;
}
applyLocks(locks)
{
return locks;
}
getCurrentLocked(lockedArray){
return lockedArray;
}
getElement()
{
return this.element;
}
}
class LeafSegment extends Segment {
constructor(element, leaf) {
super(element);
this.leaf = 'A';
if (Helper.isNotNull(leaf)) {
this.setLeaf(leaf);
}
}
sameAs(otherSegment) {
// debugger;
return (otherSegment instanceof LeafSegment && otherSegment.leaf === this.leaf);
}
setLeaf(leaf) {
this.leaf = leaf;
}
_updateElement() {
this.element.querySelector(".leaf-element").removeAllChildren().appendChild(document.createTextNode(this.leaf));
}
}
class ParentSegment extends Segment {
static initListener() {
window.addEventListener("mousedown", (e) => {
ParentSegment.mouseDownTarget = e.target;
ParentSegment.clickPosition = {x: e.pageX, y: e.pageY};
});
window.addEventListener("mouseup", (e) => {
ParentSegment.mouseDownTarget = null;
ParentSegment.clickPosition = {};
});
window.addEventListener("touchstart", (e) => {
if (e.targetTouches.length === 1) {
ParentSegment.mouseDownTarget = e.targetTouches[0].target;
ParentSegment.clickPosition = {x: e.targetTouches[0].pageX, y: e.targetTouches[0].pageY};
}
else if (Array.isArray(e.path) && e.path.length >= 1) {
ParentSegment.mouseDownTarget = e.path[0];
ParentSegment.clickPosition = null;
}
});
window.addEventListener("touchend", (e) => {
ParentSegment.mouseDownTarget = null;
ParentSegment.clickPosition = {};
});
}
setIsRotatable(rotatable) {
this.rotatable = rotatable;
this._updateElement();
}
constructor(element) {
super(element);
this.children = [];
this.class = "rotate-0";
this.rotatable = true;
let self = this;
this.touchendListener = function (e) {
console.log("touchend", e);
let target = null;
let position = null;
if (e.changedTouches.length >= 1) {
target = document.elementFromPoint(e.changedTouches[0].pageX, e.changedTouches[0].pageY);
position = {x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY};
}
else if (Array.isArray(e.path) && e.path.length >= 1) {
target = e.path[0];
}
if (e.targetTouches.length === 0 && self.element.contains(ParentSegment.mouseDownTarget) && self.element.contains(target)) {
self.getLevel().segmentClickedListener(self);
self.rotate(ParentSegment.mouseDownTarget, target, ParentSegment.clickPosition, position);
e.stopPropagation();
e.preventDefault();
}
};
this.mouseupListener = function (e) {
if (ParentSegment.mouseDownTarget !== null && self.element.contains(ParentSegment.mouseDownTarget) && self.element.contains(e.target)) {
let position = {x: e.pageX, y: e.pageY};
self.getLevel().segmentClickedListener(self);
self.rotate(ParentSegment.mouseDownTarget, e.target, ParentSegment.clickPosition, position);
e.stopPropagation();
e.preventDefault();
}
};
}
canRotate() {
return (this.rotatable && !this.getLevel().getHasWon());
}
async rotate(firstElem, secondElem, firstPosition, secondPosition) {
const timeout = 250;
const clickTolerance = 5;
let rotationDirection = 1;
if (Helper.isNotNull(secondElem) && Helper.isNotNull(firstElem) &&
(Helper.isNull(firstPosition) || Helper.isNull(secondPosition) ||
Math.abs(firstPosition.x - secondPosition.x) > clickTolerance ||
Math.abs(firstPosition.y - secondPosition.y) > clickTolerance)) {
let firstIndex = -1;
let secondIndex = -1;
let rotationIndexes = [0, 1, 3, 2];
for (let i = 0; i < this.children.length; i++) {
if (this.children[rotationIndexes[i]].element === firstElem || this.children[rotationIndexes[i]].element.contains(firstElem)) {
firstIndex = (i + this.rotation / 90) % 4;
}
if (this.children[rotationIndexes[i]].element === secondElem || this.children[rotationIndexes[i]].element.contains(secondElem)) {
secondIndex = (i + this.rotation / 90) % 4;
}
}
if (firstIndex >= 0 && secondIndex >= 0) {
if (firstIndex === 2 && (secondIndex === 0 || secondIndex === 1)
|| firstIndex === 1 && (secondIndex === 0 || secondIndex === 3)
|| (firstIndex === 0 && secondIndex === 3)
|| (firstIndex === 3 && secondIndex === 2)) {
rotationDirection = -1;
}
}
}
if (this.canRotate()) {
this.rotation += 360 + 90 * rotationDirection;
this.rotation %= 360;
let currentRotation = this.rotation;
this._updateRotationClass();
this.element.classList.add("rotating");
if (rotationDirection === -1) {
this.element.classList.add("reverse");
}
let delayPromise = new Promise(function (resolve) {
setTimeout(resolve, timeout);
}).then(() => {
if (this.rotation === currentRotation) {
this.element.classList.remove("rotating");
this.element.classList.remove("reverse");
}
});
this.getLevel().checkHasWon(delayPromise);
return delayPromise;
}
}
sameAs(otherSegment) {
if (!(otherSegment instanceof ParentSegment) || otherSegment.children.length !== this.children.length) {
return false;
}
for (let i = 0; i < this.children.length; i++) {
if (!this.children[i].sameAs(otherSegment.children[i])) {
return false;
}
}
return true;
}
applyRotations(rotations) {
this.rotation = rotations[0];
if (isNaN(this.rotation)) {
this.rotation = 0;
}
rotations.splice(0, 1);
for (let i = 0, n = this.children.length; i < n; i++) {
rotations = this.children[i].applyRotations(rotations);
}
return rotations;
}
applyLocks(locks) {
this.rotatable = locks[0];
locks.splice(0, 1);
for (let i = 0, n = this.children.length; i < n; i++) {
locks = this.children[i].applyLocks(locks);
}
return locks;
}
getCurrentRotations(rotations) {
rotations.push(this.rotation);
for (let i = 0, n = this.children.length; i < n; i++) {
rotations = this.children[i].getCurrentRotations(rotations);
}
return rotations;
}
getCurrentLocked(locked) {
locked.push(this.rotatable);
for (let i = 0, n = this.children.length; i < n; i++) {
locked = this.children[i].getCurrentLocked(locked);
}
return locked;
}
isSolved(checkChildren) {
checkChildren = Helper.nonNull(checkChildren, true);
if (checkChildren) {
for (let i = 0, n = this.children.length; i < n; i++) {
if (!this.children[i].isSolved()) {
return false;
}
}
}
return (this.rotation === 0 || (
this.children[0].sameAs(this.children[3]) && this.children[1].sameAs(this.children[2]) && (
this.rotation === 180 || this.children[0].sameAs(this.children[1]))))
}
setChildren(children) {
this.children = [];
for (let i = 0, n = children.length; i < n; i++) {
this.addChild(children[i]);
}
}
addChild(child) {
this.children.push(child);
child.setParent(this);
this._updateElement();
}
_updateRotationClass() {
// this.style.transform = "rotate("+this.rotation+"deg)";
this.element.classList.remove(this.class);
this.class = "rotate-" + this.rotation;
if (this.class === "rotate-0") {
this.class = "rotate-360";
}
this.element.classList.add(this.class);
}
_updateElement() {
let layer = this._getLayer();
if (layer >= 2) {
this.element.classList.add("layer-" + layer);
}
if (!this.rotatable) {
this.element.classList.add("locked");
}
const childContainer = this.element.querySelector(".child-container");
childContainer.removeAllChildren();
this._updateRotationClass();
this.element.removeEventListener("mouseup", this.mouseupListener);
this.element.removeEventListener("touchend", this.touchendListener);
this.element.addEventListener("mouseup", this.mouseupListener);
this.element.addEventListener("touchend", this.touchendListener);
for (let i = 0, n = this.children.length; i < n; i++) {
this.children[i]._updateElement();
childContainer.appendChild(this.children[i].getElement());
if (i % 2 === 1 && this.children.length - 1 !== i) {
childContainer.appendChild(document.createElement("br"));
}
}
}
_getLayer() {
if (this.children.length >= 1 && this.children[0] && this.children[0] instanceof ParentSegment) {
return this.children[0]._getLayer() + 1;
}
return 1;
}
}
ParentSegment.initListener();
class Level {
constructor(templateContainer) {
this.rootSegment = null;
this.words = [];
this.startRotations = [];
this.templateContainer = templateContainer;
this.hasWon = false;
this.id = null;
this.wonResolver = null;
this.giveUpResolver = null;
const self = this;
this.wonPromise = new Promise((resolve, reject) => {
self.wonResolver = resolve;
self.giveUpResolver = reject;
});
this.segmentClickedListener = () => {};
}
saveAsCurrentLevel(){
let rotations = this.getCurrentRotations();
let locked = this.getCurrentLocked();
localStorage.setItem("currentLevel", JSON.stringify({"id": this.id, "rotations": rotations, "locks":locked}));
}
getCurrentLocked(){
if (this.rootSegment !== null)
{
return this.rootSegment.getCurrentLocked([]);
}
return [];
}
getCurrentRotations(){
if (this.rootSegment !== null)
{
return this.rootSegment.getCurrentRotations([]);
}
return [];
}
setLocks(locks)
{
if (this.rootSegment !== null){
this.rootSegment.applyLocks(locks);
}
}
setId(id)
{
this.id = id;
}
getId()
{
return this.id;
}
getLevel()
{
return this;
}
setRootSegment(rootSegment)
{
this.rootSegment = rootSegment;
this.rootSegment.setParent(this);
if (this.startRotations)
{
this.applyRotations();
}
}
setWords(words)
{
this.words = [];
for (let i = 0, n = words.length; i < n; i++) {
this.words.push(words[i].toUpperCase());
}
}
setStartRotations(rotations)
{
this.startRotations = rotations;
}
applyRotations(rotations)
{
if (this.rootSegment)
{
rotations = Helper.nonNull(rotations, this.startRotations);
this.rootSegment.applyRotations(rotations);
}
}
getHasWon()
{
return this.hasWon;
}
checkHasWon(delayPromise) {
if (this.rootSegment.isSolved()){
this.hasWon = true;
const self = this;
Promise.resolve(delayPromise).then(()=>{
self.wonResolver(true);
});
return true;
}
this.saveAsCurrentLevel();
return false;
}
getWonPromise(){
return this.wonPromise;
}
getRootSegment(){
return this.rootSegment;
}
createSegments() {};
getRotatableSegments(){
return Level._getRotatableSegmentsFrom(this.rootSegment);
}
setSegmentClickedListener(listener){
this.segmentClickedListener = listener;
}
static _getRotatableSegmentsFrom(segment){
let rotatable = [];
if (segment.canRotate())
{
rotatable.push(segment);
}
if (segment instanceof ParentSegment){
for (let i = 0; i < segment.children.length; i++) {
rotatable.push.apply(rotatable, Level._getRotatableSegmentsFrom(segment.children[i]));
}
}
return rotatable;
}
static _createLeafsForWord(word, leafSegmentTemplate)
{
let leafSegments = [];
for (let i = 0, n = word.length; i < n; i++) {
leafSegments.push(new LeafSegment(Helper.cloneNode(leafSegmentTemplate), word.charAt(i)));
}
return leafSegments;
}
}
class RowSegment extends ParentSegment{
constructor(element) {
super(element);
this.rotatable = false;
}
applyRotations(rotations)
{
for (let i = 0, n = this.children.length; i < n; i++) {
rotations = this.children[i].applyRotations(rotations);
}
return rotations;
}
getCurrentRotations(rotations){
for (let i = 0, n = this.children.length; i < n; i++) {
rotations = this.children[i].getCurrentRotations(rotations);
}
return rotations;
}
getCurrentLocked(locked) {
for (let i = 0, n = this.children.length; i < n; i++) {
locked = this.children[i].getCurrentLocked(locked);
}
return locked;
}
applyLocks(locks) {
for (let i = 0, n = this.children.length; i < n; i++) {
locks = this.children[i].applyLocks(locks);
}
return locks;
}
_updateElement() {
const childContainer = this.element.querySelector(".child-container");
childContainer.removeAllChildren();
this._updateRotationClass();
const self = this;
this.element.onclick = function (e) {
self.rotate();
e.stopPropagation();
};
for (let i = 0, n = this.children.length; i < n; i++) {
this.children[i]._updateElement();
childContainer.appendChild(this.children[i].getElement());
}
}
}
class RowLevel extends Level {
constructor(container, wordLength) {
super(container);
this.wordLength = wordLength;
}
createSegments() {
if (this.words.length >= 2 && this.words[0].length >= this.wordLength && this.words[1].length >= this.wordLength) {
let leafsWordOne = Level._createLeafsForWord(this.words[0], this.templateContainer.copyLeafTemplate());
let leafsWordTwo = Level._createLeafsForWord(this.words[1], this.templateContainer.copyLeafTemplate());
let rootSegment = new RowSegment(this.templateContainer.copyRowTemplate());
for (let i = 0, n = this.wordLength / 2; i < n; i++) {
let parent = new ParentSegment(this.templateContainer.copyParentTemplate());
parent.addChild(leafsWordOne[2 * i]);
parent.addChild(leafsWordOne[2 * i + 1]);
parent.addChild(leafsWordTwo[2 * i]);
parent.addChild(leafsWordTwo[2 * i + 1]);
rootSegment.addChild(parent);
}
// rootSegment.applyRotations(this.startRotations);
this.setRootSegment(rootSegment);
}
}
}
class SimpleLevel extends RowLevel{
constructor(container) {
super(container, 6);
}
}
class RowLevel8 extends RowLevel{
constructor(container) {
super(container, 8);
}
}
class RowLevel10 extends RowLevel{
constructor(container) {
super(container, 10);
}
}
class TriangleSegment extends RowSegment{
}
class SixWordsRowLevel extends Level {
constructor(templateContainer, wordLength) {
super(templateContainer);
this.wordLength = wordLength;
}
createSegments() {
if (this.words.length >= 6 &&
this.words[0].length >= this.wordLength &&
this.words[1].length >= this.wordLength &&
this.words[2].length >= this.wordLength &&
this.words[3].length >= this.wordLength &&
this.words[4].length >= this.wordLength &&
this.words[5].length >= this.wordLength
) {
let leafsWords = [];
leafsWords[0] = Level._createLeafsForWord(this.words[0], this.templateContainer.copyLeafTemplate());
leafsWords[1] = Level._createLeafsForWord(this.words[1], this.templateContainer.copyLeafTemplate());
leafsWords[2] = Level._createLeafsForWord(this.words[2], this.templateContainer.copyLeafTemplate());
leafsWords[3] = Level._createLeafsForWord(this.words[3], this.templateContainer.copyLeafTemplate());
leafsWords[4] = Level._createLeafsForWord(this.words[4], this.templateContainer.copyLeafTemplate());
leafsWords[5] = Level._createLeafsForWord(this.words[5], this.templateContainer.copyLeafTemplate());
let rootSegment = new RowSegment(this.templateContainer.copyRowTemplate());
for (let i = 0; i < this.wordLength / 4; i++) {
let parents = [];
parents[0] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[1] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[2] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[3] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[4] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[5] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[0].addChild(leafsWords[0][4*i]);
parents[0].addChild(leafsWords[0][4*i+1]);
parents[0].addChild(leafsWords[1][4*i]);
parents[0].addChild(leafsWords[1][4*i+1]);
parents[1].addChild(leafsWords[0][4*i+2]);
parents[1].addChild(leafsWords[0][4*i+3]);
parents[1].addChild(leafsWords[1][4*i+2]);
parents[1].addChild(leafsWords[1][4*i+3]);
parents[2].addChild(leafsWords[2][4*i]);
parents[2].addChild(leafsWords[2][4*i+1]);
parents[2].addChild(leafsWords[3][4*i]);
parents[2].addChild(leafsWords[3][4*i+1]);
parents[3].addChild(leafsWords[2][4*i+2]);
parents[3].addChild(leafsWords[2][4*i+3]);
parents[3].addChild(leafsWords[3][4*i+2]);
parents[3].addChild(leafsWords[3][4*i+3]);
parents[4].addChild(leafsWords[4][4*i]);
parents[4].addChild(leafsWords[4][4*i+1]);
parents[4].addChild(leafsWords[5][4*i]);
parents[4].addChild(leafsWords[5][4*i+1]);
parents[5].addChild(leafsWords[4][4*i+2]);
parents[5].addChild(leafsWords[4][4*i+3]);
parents[5].addChild(leafsWords[5][4*i+2]);
parents[5].addChild(leafsWords[5][4*i+3]);
let parent = new ParentSegment(this.templateContainer.copyParentTemplate());
let triangle = new TriangleSegment(this.templateContainer.copyTriangleTemplate());
if (i % 2 === 0) {
parent.addChild(parents[0]);
parent.addChild(parents[1]);
parent.addChild(parents[2]);
parent.addChild(parents[3]);
let rowSegment = new RowSegment(this.templateContainer.copyRowTemplate());
rowSegment.addChild(parents[4]);
rowSegment.addChild(parents[5]);
triangle.addChild(parent);
triangle.addChild(rowSegment);
triangle.getElement().classList.add("type-1");
}
else {
let rowSegment = new RowSegment(this.templateContainer.copyRowTemplate());
rowSegment.addChild(parents[0]);
rowSegment.addChild(parents[1]);
triangle.addChild(rowSegment);
triangle.addChild(parent);
parent.addChild(parents[2]);
parent.addChild(parents[3]);
parent.addChild(parents[4]);
parent.addChild(parents[5]);
triangle.getElement().classList.add("type-2");
}
rootSegment.addChild(triangle);
}
this.setRootSegment(rootSegment);
}
}
}
class SixWordsRowLevel8 extends SixWordsRowLevel {
constructor(templateContainer) {
super(templateContainer, 8);
}
}
class SixWordsRowLevel12 extends SixWordsRowLevel {
constructor(templateContainer) {
super(templateContainer, 12);
}
}
class FourWordsLevel extends Level {
constructor(templateContainer, wordLength) {
super(templateContainer);
this.wordLength = wordLength;
}
createSegments() {
if (this.words.length >= 4 &&
this.words[0].length >= this.wordLength &&
this.words[1].length >= this.wordLength &&
this.words[2].length >= this.wordLength &&
this.words[3].length >= this.wordLength
) {
let leafsWords = [];
leafsWords[0] = Level._createLeafsForWord(this.words[0], this.templateContainer.copyLeafTemplate());
leafsWords[1] = Level._createLeafsForWord(this.words[1], this.templateContainer.copyLeafTemplate());
leafsWords[2] = Level._createLeafsForWord(this.words[2], this.templateContainer.copyLeafTemplate());
leafsWords[3] = Level._createLeafsForWord(this.words[3], this.templateContainer.copyLeafTemplate());
let rootSegment = new RowSegment(this.templateContainer.copyRowTemplate());
for (let i = 0; i < this.wordLength / 4; i++) {
let parents = [];
parents[0] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[1] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[2] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[3] = new ParentSegment(this.templateContainer.copyParentTemplate());
parents[0].addChild(leafsWords[0][4 * i]);
parents[0].addChild(leafsWords[0][4 * i + 1]);
parents[0].addChild(leafsWords[1][4 * i]);
parents[0].addChild(leafsWords[1][4 * i + 1]);
parents[1].addChild(leafsWords[0][4 * i + 2]);
parents[1].addChild(leafsWords[0][4 * i + 3]);
parents[1].addChild(leafsWords[1][4 * i + 2]);
parents[1].addChild(leafsWords[1][4 * i + 3]);
parents[2].addChild(leafsWords[2][4 * i]);
parents[2].addChild(leafsWords[2][4 * i + 1]);
parents[2].addChild(leafsWords[3][4 * i]);
parents[2].addChild(leafsWords[3][4 * i + 1]);
parents[3].addChild(leafsWords[2][4 * i + 2]);
parents[3].addChild(leafsWords[2][4 * i + 3]);
parents[3].addChild(leafsWords[3][4 * i + 2]);
parents[3].addChild(leafsWords[3][4 * i + 3]);
let parent = new ParentSegment(this.templateContainer.copyParentTemplate());
parent.addChild(parents[0]);
parent.addChild(parents[1]);
parent.addChild(parents[2]);
parent.addChild(parents[3]);
rootSegment.addChild(parent);
}
this.setRootSegment(rootSegment);
}
}
}
class FourWordsLevel8 extends FourWordsLevel{
constructor(templateContainer) {
super(templateContainer, 8);
}
}
class FourWordsLevel12 extends FourWordsLevel{
constructor(templateContainer) {
super(templateContainer, 12);
}
}
class LevelHelper {
static setLevelType(typeId, level) {
LevelHelper.types[typeId] = level;
}
static getLevelClass(type) {
return LevelHelper.types[type];
}
static inflateLevel(jsonLevel, templateContainer) {
let level = new (LevelHelper.types[jsonLevel["rendererType"]])(templateContainer);
level.setWords(jsonLevel["words"]);
level.setId(jsonLevel["id"]);
for (let i = 0, n = jsonLevel["rotations"].length; i < n; i++) {
if (jsonLevel["rotations"][i] <= 4) {
jsonLevel["rotations"][i] = 90 * jsonLevel["rotations"][i];
}
}
level.setStartRotations(jsonLevel["rotations"]);
return level;
}
}
LevelHelper.types = {
20: SimpleLevel,
40: RowLevel8,
60: RowLevel10,
100: SixWordsRowLevel8,
120: FourWordsLevel8,
140: SixWordsRowLevel12,
160: FourWordsLevel12,
};
class WordRotatorDb extends MyDb {
static getInstance() {
if (Helper.isNull(WordRotatorDb.instance)) {
WordRotatorDb.instance = new WordRotatorDb();
}
return WordRotatorDb.instance;
}
constructor() {
super("wordRotator", 3);
}
upgrade(db, oldVersion, newVersion, e) {
console.log("upgrading!");
if (Helper.isNull(oldVersion) || oldVersion < 1 && newVersion >= 1) {
let levelObjectStore = db.createObjectStore(WordRotatorDb.OBJECT_STORE.LEVEL, {"keyPath": "id"});
}
if (Helper.isNull(oldVersion) || oldVersion < 2 && newVersion >= 2) {
let levelObjectStore = e.target.transaction.objectStore(WordRotatorDb.OBJECT_STORE.LEVEL);
levelObjectStore.createIndex("played", ["deleted", "played", "difficulty", "id"], {"unique": false});
}
if (Helper.isNull(oldVersion) || oldVersion < 3 && newVersion >= 3) {
let levelObjectStore = e.target.transaction.objectStore(WordRotatorDb.OBJECT_STORE.LEVEL);
levelObjectStore.createIndex("difficulty", "difficulty", {"unique": false});
}
};
async saveManyLevels(levels) {
return this.saveMany(levels, WordRotatorDb.OBJECT_STORE.LEVEL).catch(e => {
console.error("insert error!", e);
});
}
async loadLevel(levelId) {
return this.load(levelId, WordRotatorDb.OBJECT_STORE.LEVEL);
}
async loadNextLevel(rendererTypes) {
const levels = await this.loadMany("difficulty", IDBKeyRange.lowerBound(0), WordRotatorDb.OBJECT_STORE.LEVEL);
let wrongLevels = [];
let newLevels = [];
let difficulty = -1;
for (let i = 0, n = levels.length; i < n; i++) {
if ((difficulty < 0 || difficulty === levels[i]["difficulty"]) && !levels[i]["deleted"] && !levels[i]["played"] && rendererTypes.indexOf(levels[i]["rendererType"]) !== -1) {
newLevels.push(levels[i]);
difficulty = levels[i]["difficulty"];
}
else if (levels[i]["difficulty"] !== 0 && !levels[i]["deleted"] && !levels[i]["played"] ) {
wrongLevels.push(levels[i]);
}
}
if (newLevels.length === 0) {
return null;
}
return newLevels[Math.round(Math.random() * newLevels.length) % newLevels.length];
}
async saveLevelPlayed(levelId) {
const level = await this.loadLevel(levelId);
level.played = true;
return await this.saveObj(level, WordRotatorDb.OBJECT_STORE.LEVEL);
}
}
WordRotatorDb.OBJECT_STORE = {
LEVEL: "level",
};
WordRotatorDb.instance = null;
class EndSite extends WordRotatorBaseSite{
constructor(siteManager) {
super(siteManager, "html/application/end.html");
}
onStart(args) {
Matomo.update("End Site");
return super.onStart(args);
}
}
class LevelSite extends WordRotatorBaseSite {
constructor(siteManager) {
super(siteManager, "html/application/level.html");
}
createActionBarMenu(menu) {
menu = super.createActionBarMenu(menu);
let coinAction = new MenuAction(Helper.nonNull(localStorage.getItem("coins"), "0"), () => {
}, MenuAction.SHOW_ALWAYS, 900);
coinAction.setShouldTranslate(false);
coinAction._liClass = "coin-counter";
menu.addAction(coinAction);
this.coinAction = coinAction;
return menu;
}
onConstruct(args) {
this.levelCounter = Helper.nonNull(localStorage.getItem("levelCounter"), 1);
this.levelScaler = () => {
};
this.wonParams = {
aborted: false,
coinCounterTimer: null,
};
this.coinPromise = Promise.resolve();
let settingsManager = SettingsManager.getInstance();
let soundManager = SoundManager.getInstance();
soundManager.set({
audio: "sound/single_coin_fall_on_concrete_.mp3",
muted: (settingsManager.getSetting("play-sound", "1") !== "1"),
volume: 0.7
}, SoundManager.CHANNELS.SOUND);
soundManager.resume(SoundManager.CHANNELS.MUSIC);
return super.onConstruct(args);
}
async onFirstStart() {
super.onFirstStart();
let leafSegmentTemplate = this.findBy("#segment-leaf-template");
let parentSegmentTemplate = this.findBy("#segment-parent-template");
let rowSegmentTemplate = this.findBy("#segment-row-template");
let triangleTemplate = this.findBy("#segment-triangle-template");
leafSegmentTemplate.id = null;
parentSegmentTemplate.id = null;
rowSegmentTemplate.id = null;
triangleTemplate.id = null;
leafSegmentTemplate.remove();
parentSegmentTemplate.remove();
rowSegmentTemplate.remove();
triangleTemplate.remove();
let self = this;
let continueButton = this.findBy("#continue-button");
continueButton.addEventListener("click", () => {
self.nextLevel();
});
let wonText = this.findBy("#won-text");
let scaleHelper = new ScaleHelper();
this.continueButtonScaler = await scaleHelper.scaleToFull(continueButton, continueButton.parentElement, false, true, 2);
this.wonTextScaler = await scaleHelper.scaleToFull(wonText, wonText.parentElement, false, false, 2, null, 5);
this.wonText = wonText;
this.wonText.style.fontSize = "0";
//Benutze Document, da Element außerhalb von Seite (eigentlich unschön!)
this.levelCounterActionContainer = document.getElementById("level-number-container");
this.levelCounterAction = document.getElementById("level-number");
this.levelCounterAction.innerText = this.levelCounter;
this.levelNumberScaler = await scaleHelper.scaleToFull(this.levelCounterAction, this.levelCounterActionContainer, false, false, 4);
this.levelCounterActionContainer.classList.add("visible");
this.templateContainer = new TemplateContainer(leafSegmentTemplate, parentSegmentTemplate, rowSegmentTemplate, triangleTemplate);
this.coinTemplate = this.findBy("#coin-template");
this.coinContainer = this.findBy("#coin-container");
this.coinTemplate.id = null;
this.coinContainer.removeAllChildren();
this.findBy("#help-button").addEventListener("click", () => {
this.help();
});
this.loadLastLevel();
}
async loadLastLevel() {
try {
let currentLevelInfo = localStorage.getItem("currentLevel");
if (currentLevelInfo !== null) {
currentLevelInfo = JSON.parse(currentLevelInfo);
const db = WordRotatorDb.getInstance();
const levelJson = await db.loadLevel(currentLevelInfo["id"]);
if (levelJson === null) {
return this.nextLevel();
}
const level = LevelHelper.inflateLevel(levelJson, this.templateContainer);
level.setStartRotations(currentLevelInfo["rotations"]);
const self = this;
level.getWonPromise().then(() => {
self.levelWon(level);
});
level.createSegments();
level.setLocks(currentLevelInfo["locks"]);
level.getRootSegment()._updateElement();
level.saveAsCurrentLevel();
let levelSegment = this.findBy("#level");
levelSegment.removeAllChildren().appendChild(level.getRootSegment().getElement());
let scaleHelper = new ScaleHelper();
this.levelScaler = await scaleHelper.scaleToFull(levelSegment, levelSegment.parentElement, false, false, 1, level.words[0].length * 1.5, null, 0);
this.level = level;
let res = this.tutorial();
Matomo.push(["trackEvent", "LevelSite", "LoadLastLevel"]);
this.level.checkHasWon();
return res;
}
}
catch (e) {
console.error(e);
}
return this.nextLevel();
}
async nextLevel() {
try {
this._siteContent.classList.remove('won');
this.wonText.style.fontSize = "0";
const db = WordRotatorDb.getInstance();
const nextLevelJson = await db.loadNextLevel(LevelSite.RENDERER_TYPES);
if (nextLevelJson === null) {
this.startSite(EndSite);
this.finish();
return;
}
const level = LevelHelper.inflateLevel(nextLevelJson, this.templateContainer);
const self = this;
level.getWonPromise().then(() => {
self.levelWon(level);
});
level.createSegments();
level.getRootSegment()._updateElement();
level.saveAsCurrentLevel();
let levelSegment = this.findBy("#level");
levelSegment.removeAllChildren().appendChild(level.getRootSegment().getElement());
let scaleHelper = new ScaleHelper();
this.levelScaler = await scaleHelper.scaleToFull(levelSegment, levelSegment.parentElement, false, false, 1, level.words[0].length * 1.5, null, 0);
this.level = level;
this.levelCounterAction.innerText = this.levelCounter;
this.levelNumberScaler();
this.coinAction.setTitle(Helper.nonNull(localStorage.getItem("coins"), "0"));
this.coinAction.redraw();
this.wonParams.aborted = true;
clearTimeout(this.wonParams.coinCounterTimer);
Matomo.push(["trackEvent", "LevelSite", "NextLevel", "Level Number Normal", this.levelCounter]);
this.level.checkHasWon();
return this.tutorial();
}
catch (e) {
console.error(e);
}
}
onStart(args) {
Matomo.update("Level Site");
let res = super.onStart(args);
if (this.levelCounterAction) {
this.levelCounterAction.innerText = this.levelCounter;
this.levelCounterActionContainer.classList.add("visible");
}
this.levelScaler();
let settingsManager = SettingsManager.getInstance();
let soundManager = SoundManager.getInstance();
soundManager.set({
audio: "sound/single_coin_fall_on_concrete_.mp3",
muted: (settingsManager.getSetting("play-sound", "1") !== "1"),
volume: 0.7
}, SoundManager.CHANNELS.SOUND);
return res;
}
onPause(args) {
super.onPause(args);
this.levelCounterActionContainer.classList.remove("visible");
}
async levelWon(level) {
try {
const db = WordRotatorDb.getInstance();
const savePromise = db.saveLevelPlayed(level.getId());
this.levelCounter++;
localStorage.setItem("levelCounter", this.levelCounter);
this._siteContent.classList.add('won');
localStorage.removeItem("currentLevel");
let continueButton = this.findBy("#continue-button");
continueButton.style.transition = "none";
continueButton.style.opacity = 0;
this.coinContainer.removeAllChildren();
let coinsPerLevel = SystemSettings.get("coinsPerLevel", 5);
let coinsBefore = 0;
let soundManager = SoundManager.getInstance();
let audioOptions = soundManager.get(SoundManager.CHANNELS.SOUND);
this.coinPromise = this.coinPromise.then(() => {
coinsBefore = parseInt(Helper.nonNull(localStorage.getItem("coins"), "0"));
localStorage.setItem("coins", coinsBefore + parseInt(coinsPerLevel));
}).then(() => {
return Promise.all([new Promise((r) => {
setTimeout(() => {
r(continueButton.fadeIn());
}, 500);
}), audioOptions.loadedPromise.catch(e => {
console.error(e);
})]);
});
this.wonParams.aborted = false;
for (let i = 0; i < coinsPerLevel; i++) {
let coinElem = Helper.cloneNode(this.coinTemplate);
this.coinContainer.appendChild(coinElem);
this.coinPromise = this.coinPromise.then(() => {
return new Promise(r => {
let timeout = 350;
if (!this.wonParams.aborted) {
coinElem.fadeIn(timeout / 1000);
soundManager.play(SoundManager.CHANNELS.SOUND);
this.wonParams.coinCounterTimer = setTimeout(() => {
if (!this.wonParams.aborted) {
this.coinAction.setTitle(++coinsBefore);
this.coinAction.redraw();
}
}, timeout / 2);
}
else {
r();
}
//Always do the next promise for garbage collection
setTimeout(r, timeout);
})
});
}
this.coinPromise = this.coinPromise.catch((e) => {
console.error(e);
});
this.wonTextScaler();
this.continueButtonScaler();
this.levelScaler();
Matomo.push(["trackEvent", "LevelSite", "LevelWon", "Coins", parseInt(Helper.nonNull(localStorage.getItem("coins"), "0"))]);
await savePromise;
}
catch (e) {
console.error(e);
}
}
help() {
let cost = SystemSettings.get("costForHelp", 25);
let currentCoins = parseInt(Helper.nonNull(localStorage.getItem("coins"), 0));
if (currentCoins >= cost) {
currentCoins -= cost;
localStorage.setItem("coins", currentCoins);
this.coinAction.title = currentCoins;
this.coinAction.redraw();
let rotatables = this.level.getRotatableSegments();
rotatables = rotatables.filter((segment) => {
return (!segment.isSolved(false));
});
let index = Math.floor(Math.random() * rotatables.length);
let segmentToHelp = rotatables[index];
while (segmentToHelp.rotation !== 0) {
segmentToHelp.rotate();
}
segmentToHelp.setIsRotatable(false);
this.level.saveAsCurrentLevel();
Matomo.push(["trackEvent", "LevelSite", "Help", "Coins", parseInt(Helper.nonNull(localStorage.getItem("coins"), "0"))]);
}
else {
FlashMessenger.addMessage("not-enough-coins");
Matomo.push(["trackEvent", "LevelSite", "Help", "Not enough Coins", parseInt(Helper.nonNull(localStorage.getItem("coins"), "0"))]);
}
}
async tutorial() {
if (this.level.id === LevelSite.TUTORIAL.FIRST_LEVEL) {
let currentStep = Helper.nonNull(localStorage.getItem("tutorial-step"), "1");
let scaleHelper = new ScaleHelper();
this._siteContent.classList.add("tutorial");
this._siteContent.classList.add("step-" + currentStep);
switch (currentStep) {
case "1": {
this.level.setSegmentClickedListener(() => {
this._siteContent.classList.remove("step-1");
localStorage.setItem("tutorial-step", "2");
this.tutorial();
});
let textElem = this.findBy(".tutorial-text .step-1");
await this.levelScaler();
scaleHelper.scaleToFull(textElem, textElem.parentElement, null, true, 1, 2);
break;
}
case "2": {
this.level.setSegmentClickedListener(() => {
});
this.level.getWonPromise().then(() => {
this._siteContent.classList.remove("tutorial");
this._siteContent.classList.remove("step-2");
localStorage.removeItem("tutorial-step");
this.coinPromise = this.coinPromise.then(() => {
FlashMessenger.addMessage("extra-coins-after-first-level");
localStorage.setItem("coins", parseInt(Helper.nonNull(localStorage.getItem("coins"), "0")) + 50);
this.coinAction.setTitle(Helper.nonNull(localStorage.getItem("coins"), "0"));
this.coinAction.redraw();
});
// this.levelScaler();
});
let textElem = this.findBy(".tutorial-text .step-2");
await this.levelScaler();
scaleHelper.scaleToFull(textElem, textElem.parentElement, null, true, 1, 2);
break;
}
}
}
else if (this.level.id === LevelSite.TUTORIAL.SECOND_LEVEL) {
let currentStep = Helper.nonNull(localStorage.getItem("tutorial-step"), "3");
switch (currentStep) {
case "3": {
let scaleHelper = new ScaleHelper();
this._siteContent.classList.add("tutorial");
this._siteContent.classList.add("step-" + currentStep);
let eventListener = () => {
this._siteContent.classList.remove("tutorial");
this._siteContent.classList.remove("step-3");
localStorage.setItem("tutorial-step", "4");
this.findBy("#help-button").removeEventListener("click", eventListener);
this.levelScaler();
};
this.findBy("#help-button").addEventListener("click", eventListener);
let textElem = this.findBy(".tutorial-text .step-3");
await this.levelScaler();
scaleHelper.scaleToFull(textElem, textElem.parentElement, null, true, 1, 2);
break;
}
}
}
else if (this.level.id === LevelSite.TUTORIAL.BIG_SEGMENT_LEVEL) {
let currentStep = Helper.nonNull(localStorage.getItem("tutorial-step"), "4");
switch (currentStep) {
case "4": {
let scaleHelper = new ScaleHelper();
this._siteContent.classList.add("tutorial");
this._siteContent.classList.add("step-" + currentStep);
let rotatableSegments = this.level.getRotatableSegments();
let firstSegment = rotatableSegments[0];
let pointer = this.findBy("#tutorial-pointer");
pointer.remove();
firstSegment.element.appendChild(pointer);
this.level.setSegmentClickedListener((segment) => {
if (firstSegment === segment) {
this._siteContent.classList.remove("tutorial");
this._siteContent.classList.remove("step-4");
localStorage.setItem("tutorial-step", "5");
this.levelScaler();
}
});
let textElem = this.findBy(".tutorial-text .step-4");
await this.levelScaler();
scaleHelper.scaleToFull(textElem, textElem.parentElement, null, true, 1, 2);
break;
}
}
}
}
}
LevelSite.RENDERER_TYPES = [20, 40, 60, 100, 120, 140, 160];
LevelSite.TUTORIAL = {
FIRST_LEVEL: 67,
SECOND_LEVEL: 15,
BIG_SEGMENT_LEVEL: 341
};
class MainMenuLevel extends FourWordsLevel{
constructor(templateContainer) {
super(templateContainer, 4);
}
saveAsCurrentLevel() {
}
// checkHasWon(delayPromise) {
// }
}
class ShareDialog extends Dialog{
constructor() {
let viewPromise = ViewInflater.inflate("html/application/dialog/share.html").then(view => {
view.appendChild(ShareManager.generateDefaultShareElement(window.location.hostname + Helper.basePath("")));
let closeListener = () => {
this.close();
};
view.querySelectorAll("a").forEach((element) => {
element.addEventListener("click", closeListener);
});
return view;
});
super(viewPromise, "share-dialog");
}
}
class MenuSite extends WordRotatorBaseSite {
constructor(siteManager) {
super(siteManager, "html/application/menu.html");
this.loadLevelPromise = this.loadLevels();
this.listener = null;
}
onStart(args) {
Matomo.update("Menu Site");
let res = super.onStart(args);
let level = new MainMenuLevel(this.templateContainer);
level.setWords(["WORD", "ROTA", "TORW", "ORDR"]);
level.createSegments();
level.getWonPromise().then(() => {
Matomo.push(["trackEvent", "MainMenu", "levelSolved"]);
this.startLevelSite();
});
let segment = level.getRootSegment();
segment._updateElement();
let levelSegment = this.findBy("#level");
levelSegment.removeAllChildren().appendChild(segment.getElement());
let rotationsSegments = level.getRotatableSegments();
let randomRotationFunction = () => {
let timeout = Math.random() * 4500 + 1500;
this.randomRotateTimeout = setTimeout(() => {
let indexBlocked = -1;
let indexesNotRight = [];
for (let i = 0; i < rotationsSegments.length; i++) {
if (rotationsSegments[i].rotation !== 0) {
indexesNotRight.push(i);
if (indexesNotRight.length >= 2) {
break;
}
}
}
if (indexesNotRight.length === 1) {
indexBlocked = indexesNotRight[0];
}
let index = Math.floor(Math.random() * rotationsSegments.length);
if (index === indexBlocked) {
index = (index + 1) % rotationsSegments.length;
}
rotationsSegments[index].rotate();
randomRotationFunction();
}, timeout);
};
randomRotationFunction();
this.listener = async () => {
let playButton = this.findBy("#play-button");
let levelNumber = this.findBy("#level-number");
levelNumber.innerText = Helper.nonNull(localStorage.getItem("levelCounter"), 1);
let levelSegment = this.findBy("#level");
let scaleHelper = new ScaleHelper();
await scaleHelper.scaleToFull(levelSegment, levelSegment.parentElement, false, false, 2, 8, null, false);
// debugger;
let levelStyle = getComputedStyle(levelSegment);
playButton.style.width = levelStyle.getPropertyValue("width");
scaleHelper.scaleToFull(playButton.children[0], playButton, null, null, null, 4, null, false);
await scaleHelper.scaleTo(0.2, levelNumber.parentElement, levelNumber.parentElement.parentElement, null, null, null, 10, null, false);
scaleHelper.scaleToFull(levelNumber, levelNumber.parentElement, false, false, 8, null, null, false);
};
this.listener();
window.addEventListener("resize", this.listener);
//Musikbuttons update, falls in den Einstellungen umgestellt
let settingsManager = SettingsManager.getInstance();
let playSoundButton = this.findBy("#play-sound");
playSoundButton.checked = (settingsManager.getSetting("play-sound", "1") === "1");
let playMusicButton = this.findBy("#play-music");
playMusicButton.checked = (settingsManager.getSetting("play-music", "1") === "1");
return res;
}
async startLevelSite() {
this.startSite(LevelSite, Promise.race([this.loadLevelPromise, new Promise(async resolve => {
const db = WordRotatorDb.getInstance();
let level = await db.loadNextLevel(LevelSite.RENDERER_TYPES);
if (level !== null) {
resolve();
}
})]));
}
async onFirstStart() {
super.onFirstStart();
let playButton = this.findBy("#play-button");
playButton.addEventListener("click", () => {
Matomo.push(["trackEvent", "MainMenu", "startButton"]);
this.startLevelSite();
});
let leafSegmentTemplate = this.findBy("#segment-leaf-template");
let parentSegmentTemplate = this.findBy("#segment-parent-template");
let rowSegmentTemplate = this.findBy("#segment-row-template");
let triangleTemplate = this.findBy("#segment-triangle-template");
leafSegmentTemplate.id = null;
parentSegmentTemplate.id = null;
rowSegmentTemplate.id = null;
triangleTemplate.id = null;
leafSegmentTemplate.remove();
parentSegmentTemplate.remove();
rowSegmentTemplate.remove();
triangleTemplate.remove();
this.templateContainer = new TemplateContainer(leafSegmentTemplate, parentSegmentTemplate, rowSegmentTemplate, triangleTemplate);
if (Helper.nonNull(MenuSite.app._cookieClosePromise)) {
MenuSite.app._cookieClosePromise.then(() => {
if (this.listener) {
this.listener();
}
});
}
let settingsManager = SettingsManager.getInstance();
let soundManager = SoundManager.getInstance();
let playMusicButton = this.findBy("#play-music");
playMusicButton.checked = settingsManager.getSetting("play-music", true);
playMusicButton.addEventListener("change", () => {
settingsManager.setSetting("play-music", (playMusicButton.checked)?"1":"0");
soundManager.set({muted: !playMusicButton.checked}, SoundManager.CHANNELS.MUSIC);
if (playMusicButton.checked) {
soundManager.play(SoundManager.CHANNELS.MUSIC);
}
Matomo.push(["trackEvent", "MainMenu", "PlayMusic", "Play Music", (playMusicButton.checked) ? 1 : 0]);
});
let playSoundButton = this.findBy("#play-sound");
playSoundButton.checked = settingsManager.getSetting("play-sound", true);
playSoundButton.addEventListener("change", () => {
settingsManager.setSetting("play-sound", (playSoundButton.checked)?"1":"0");
soundManager.set({muted: !playSoundButton.checked}, SoundManager.CHANNELS.SOUND);
Matomo.push(["trackEvent", "MainMenu", "PlaySound", "Play Sound", (playSoundButton.checked) ? 1 : 0]);
});
this.findBy("#share-button").addEventListener("click", () => {
new ShareDialog().show();
});
// this.findBy("#share-buttons").appendChild(ShareManager.generateDefaultShareElement("https://wordrotator.silas.link"));
}
onPause(args) {
clearTimeout(this.randomRotateTimeout);
window.removeEventListener("resize", this.listener);
super.onPause(args);
}
async loadLevels() {
const dateLastSync = Helper.nonNull(localStorage.getItem("date-last-sync"), 0);
const db = WordRotatorDb.getInstance();
let newLastSync = null;
let maxRuns = 1;
let levelPromises = [];
for (let run = 0; run < maxRuns; run++) {
let res = await DataManager.load("wordRotator/levels" + DataManager.buildQuery({
"currentRun": run,
"dateLastSync": dateLastSync
}));
if (!res["success"]) {
if (await db.loadNextLevel(LevelSite.RENDERER_TYPES) === null) {
FlashMessenger.addMessage("sync-error", null, 6000);
}
newLastSync = null;
break;
}
res = res["result"];
newLastSync = Helper.nonNull(newLastSync, res["currentSyncDate"]);
maxRuns = res["maxRuns"];
let levels = res["levels"];
for (let i = 0, n = levels.length; i < n; i++) {
let currentLevel = levels[i];
levelPromises.push(db.loadLevel(levels[i]["id"]).then(level => {
currentLevel["played"] = (Helper.nonNull(Helper.nonNull(level, {}).played, false));
return currentLevel;
}));
}
}
let levels = await Promise.all(levelPromises);
console.log("levels to save", levels);
await db.saveManyLevels(levels);
if (newLastSync != null && newLastSync !== "null") {
localStorage.setItem("date-last-sync", newLastSync);
}
}
}
MenuSite.app = null;
InitPromise.addPromise(app => {
MenuSite.app = app;
});
class PrivacyPolicySite extends WordRotatorBaseSite {
constructor(siteManager) {
super(siteManager, "html/application/privacyPolicy.html", "privacyPolicy");
}
onFirstStart() {
let trackSwitch =this.findBy("#track-switch");
trackSwitch.addEventListener("change", function (e) {
Matomo.setTrack(this.checked === true);
e.stopPropagation();
e.preventDefault();
});
super.onFirstStart();
}
onStart(args) {
let trackSwitch =this.findBy("#track-switch");
let shouldTrack = (Helper.nonNull(localStorage.getItem("matomoShouldTrack"), "1") === "1");
trackSwitch.checked = shouldTrack;
Matomo.update("Privacy Policy Site");
return super.onStart(args);
}
}
InitPromise.addPromise(app => {
app.addDeepLink("privacyPolicy", PrivacyPolicySite);
});
class CreditsSite extends WordRotatorBaseSite{
constructor(siteManager) {
super(siteManager, "html/application/credits.html", "credits");
}
onStart(args) {
Matomo.update("Credits Site");
return super.onStart(args);
}
}
InitPromise.addPromise(app => {
app.addDeepLink("credits", CreditsSite);
});
class ChooseThemeDialog extends Dialog {
constructor() {
let viewPromise = ViewInflater.inflate("html/application/dialog/chooseTheme.html").then(view => {
let template = view.querySelector("#choose-theme-template");
template.remove();
template.id = null;
let themeTemplateContainer = view.querySelector("#theme-choose-container");
for (let i = 0; i < ThemeManager.themes.length; i++) {
let themeElem = Helper.cloneNode(template);
let theme = ThemeManager.themes[i];
themeElem.querySelector(".name").appendChild(Translator.makePersistentTranslation(theme._name));
themeElem["dataset"]["theme"] = theme._name;
themeElem.addEventListener("click", () => {
this.result = themeElem["dataset"]["theme"];
this.close();
});
themeTemplateContainer.appendChild(themeElem);
}
return view;
});
super(viewPromise, "choose-theme-dialog-title");
}
}
class ImpressumSite extends WordRotatorBaseSite{
constructor(siteManager) {
super(siteManager, "html/application/impressum.html", "impressum");
}
}
InitPromise.addPromise(app => {
app.addDeepLink("impressum", ImpressumSite);
});
class WordRotatorSettingFragment extends LocalStorageSettingsFragment {
constructor(site) {
super(site, "html/application/fragment/settings.html");
}
onFirstStart() {
let currentThemeName = ThemeManager.currentTheme._name;
SettingsManager.getInstance().setSetting("theme", currentThemeName);
let themeNameElem = this.findBy("#theme-name");
themeNameElem.removeAllChildren().appendChild(Translator.makePersistentTranslation(currentThemeName));
this.findBy("#theme-chooser").addEventListener("click", async () => {
let newTheme = await (new ChooseThemeDialog()).show();
if (Helper.isNotNull(newTheme)) {
SettingsManager.getInstance().setSetting("theme", newTheme);
ThemeManager.changeCurrentTheme(newTheme);
themeNameElem.removeAllChildren().appendChild(Translator.makePersistentTranslation(newTheme));
}
});
this.findBy("#reset-levels").addEventListener("click", () => {
localStorage.removeItem("currentLevel");
localStorage.removeItem("date-last-sync");
localStorage.removeItem("levelCounter");
localStorage.removeItem("tutorial-step");
WordRotatorDb.getInstance().removeAll(WordRotatorDb.OBJECT_STORE.LEVEL);
});
if (location.hostname.includes("beta") || location.hostname.includes("127.0.0.1")) {
this.findBy("#reset-levels").classList.remove("hidden");
}
let playMusicButton = this.findBy("#play-music");
playMusicButton.addEventListener("change", () => {
let soundManager = SoundManager.getInstance();
soundManager.set({muted: !playMusicButton.checked}, SoundManager.CHANNELS.MUSIC);
if (playMusicButton.checked) {
soundManager.play(SoundManager.CHANNELS.MUSIC);
}
});
this.findBy("#track-switch").addEventListener("change", function (e) {
Matomo.setTrack(this.checked === true);
e.stopPropagation();
e.preventDefault();
});
this.findBy("#credits-button").addEventListener("click", () => {
this.getSite().startSite(CreditsSite);
});
this.findBy("#privacy-policy-button").addEventListener("click", () => {
this.getSite().startSite(PrivacyPolicySite);
});
this.findBy("#contact-button").addEventListener("click", () => {
this.getSite().startSite(ContactSite);
});
this.findBy("#impressum-button").addEventListener("click", () => {
this.getSite().startSite(ImpressumSite);
});
InstallManager.setCanInstallListener(() => {
let installButton = this.findBy("#install-button");
installButton.addEventListener("click", () => {
installButton.classList.add("hidden");
InstallManager.prompt().then((e) => {
console.log("clicked", e);
if (e["outcome"] === "accepted"){
Matomo.trackEvent("installed", "installed");
}
});
});
installButton.classList.remove("hidden");
});
return super.onFirstStart();
}
onStart() {
Matomo.update("Settings Site");
super.onStart();
}
}
InitPromise.addPromise(function () {
SettingsSite.addSettingsFragment("settings", WordRotatorSettingFragment);
});
class SelectWordsSite extends UserSite{
constructor(siteManager) {
super(siteManager, "version/2/html/selectWords.html", null, "select-words");
}
async onConstruct(args) {
let res = await super.onConstruct(args);
this.stats = (await DataManager.load("words"))["result"];
this.words = this.stats["wordsToCheck"];
console.log(this.stats);
return res;
}
onFirstStart() {
super.onFirstStart();
this.findBy("#not-checked").appendChild(document.createTextNode(this.stats["wordsNotChecked"]));
this.findBy("#checked").appendChild(document.createTextNode(this.stats["wordsChecked"]));
this.findBy("#not-sure").appendChild(document.createTextNode(this.stats["wordsUnsure"]));
this.findBy("#deleted").appendChild(document.createTextNode(this.stats["wordsDeleted"]));
this.findBy("#unused").appendChild(document.createTextNode(this.stats["wordsNotUsed"]));
let template = this.findBy("#word-template");
template.id = null;
template.remove();
let container = this.findBy("#word-container");
let numWords = this.words.length;
for (let i = 0; i < numWords; i++) {
let wordElement = Helper.cloneNode(template);
wordElement.dataset["id"] = -1;
this.setWord(wordElement, this.words[i]);
container.appendChild(wordElement);
wordElement.querySelector(".button-ok").addEventListener("click", async () => {
let newWord = (await DataManager.send("checkWord", {
"wordId":wordElement.dataset["id"],
"action":"1"
}))["result"];
this.setWord(wordElement, newWord[0]);
});
wordElement.querySelector(".button-unsure").addEventListener("click", async () => {
let newWord = (await DataManager.send("checkWord", {
"wordId":wordElement.dataset["id"],
"action":"2"
}))["result"];
this.setWord(wordElement, newWord[0]);
});
wordElement.querySelector(".button-delete").addEventListener("click", async () => {
let newWord = (await DataManager.send("checkWord", {
"wordId":wordElement.dataset["id"],
"action":"3"
}))["result"];
this.setWord(wordElement, newWord[0]);
});
}
}
setWord(wordElement, word){
wordElement.querySelector(".word").removeAllChildren().appendChild(document.createTextNode(word["word"]));
wordElement.dataset["id"] = word["id"];
}
}
InitPromise.addPromise(app => {
app.addDefaultAction(new UserAction("select-words", () => {
app.startSite(SelectWordsSite);
}, null, null, "select-words"));
});
class DeleteWordsSite extends UserSite {
constructor(siteManager) {
super(siteManager, "version/2/html/deleteLevels.html", null, "admin");
}
async onConstruct(args) {
let res = super.onConstruct(args);
this.words = (await DataManager.load("getDoubleUsedWordsAction"))["result"];
return res;
}
onFirstStart() {
super.onFirstStart();
let levelTemplate = this.findBy("#level-template");
let wordTemplate = this.findBy("#word-template");
let wordContainer = this.findBy("#word-container");
levelTemplate.id = null;
levelTemplate.remove();
wordTemplate.id = null;
wordTemplate.remove();
for (let k in this.words) {
let wordElem = Helper.cloneNode(wordTemplate);
wordElem.querySelector(".name").appendChild(document.createTextNode(k));
let levelContainer = wordElem.querySelector(".level-container");
for (let j = 0; j < this.words[k].length; j++) {
let level = this.words[k][j];
let levelElem = Helper.cloneNode(levelTemplate);
levelElem.querySelector(".id").appendChild(document.createTextNode(level["id"]));
levelElem.querySelector(".words").appendChild(document.createTextNode(level["words"]));
levelElem.querySelector(".positions").appendChild(document.createTextNode(level["rotations"]));
levelElem.querySelector(".delete-button").addEventListener("click", async () => {
let res = await DataManager.send("deleteLevel", {"levelId": level["id"]});
if (res["success"]){
levelElem.remove();
}
});
levelContainer.appendChild(levelElem);
}
wordContainer.appendChild(wordElem);
}
}
}
InitPromise.addPromise(app => {
app.addDefaultAction(new UserAction("delete-levels", () => {
app.startSite(DeleteWordsSite);
},null, null, "admin"));
});
let basePath = "/pwa/wordRotator/public/";
if (window.location.pathname.includes("publicTest/"))
{
basePath = "/pwa/wordRotator/publicTest/";
}
SystemSettings.setBasePath(basePath);
Translator.supportedLanguages = ["de"];
Translator.markTranslations = false;
Matomo.SIDE_ID = "2";
applyPolyfills();
ThemeManager.addTheme(new Theme('red', 'red'));
ThemeManager.addTheme(new Theme("blue", "blue"));
ThemeManager.addTheme(new Theme("black", "black"));
ThemeManager.addTheme(new Theme("green", "green"));
ThemeManager.addTheme(new Theme("pink", "pink"));
ThemeManager.addTheme(new Theme("dark", "dark"));
ShareManager.addShareButton(new MatomoShareButton(new WhatsappShareButton('img/whatsapp.svg'), "whatsapp", true));
ShareManager.addShareButton(new MatomoShareButton(new SmsShareButton('img/sms.svg'), "sms", true));
ShareManager.addShareButton(new MatomoShareButton(new TelegramShareButton('img/telegram.svg'), "telegram", true));
// ShareManager.addShareButton(new CopyShareButton('img/copy.svg'));
let app = new App();
AndroidBridge.addDefinition(() => {
window["app"] = app;
window["app"]["pause"] = app.pause;
window["app"]["resume"] = app.resume;
// window["app"]["refreshCurrentSite"] = app.refreshCurrentSite;
});
// bridge für Android
// window["Translator"] = Translator;
// window["Translator"]["setLanguage"] = Translator.setLanguage;
SettingsSite.setTemplate("html/application/setting-template.html");
// SettingsSite.shouldAddSettingsAction = false;
RegistrationSite.addAction = false;
LoginSite.addLoginAction = false;
InitPromise.resolve(app).then(async function(){
SettingsSite.settingsAction.showFor = MenuAction.SHOW_ALWAYS;
let settingsManager = SettingsManager.getInstance();
let soundManager = SoundManager.getInstance();
soundManager.play(SoundManager.CHANNELS.MUSIC, {audio: "sound/brightAndBeautifull__.mp3", loop: true, volume: 0.6, muted: (settingsManager.getSetting("play-music", "1") !== "1")});
app.start(MenuSite);
Translator.setLanguage("de");
InstallManager.setCanInstallListener(e => {
console.log("can install!", e);
});
window["applyAndroidBridge"] = AndroidBridge.applyDefinitions;
});