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 = ">>" + translation + "<<"; } } 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 = {}; 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 = { '&': '&', '&': "&", '<': '<', '>': '>', '"': '"', ''': "'", '’': "’", '‘': "‘", '–': "–", '—': "—", '…': "…", '”': '”' }; 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); } } ThemeManager.themes = []; ThemeManager.changeListeners = []; 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; } 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:", 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'); } } 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 = "×"; 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 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 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) { this._deviceType = deviceType; this._icon = icon; this._callback = callback; } 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; 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"); let iconElement = document.createElement("img"); linkElement.appendChild(iconElement); iconElement.src = Helper.basePath(button.getIcon()); iconElement.classList.add("share-icon"); return linkElement; } } } ShareManager.init(); class SmsShareButton extends ShareButton { constructor(icon) { 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'); }); } } class TelegramShareButton extends ShareButton { constructor(icon) { super(ShareButton.TYPE_ALL, icon, function (link) { let linkToOpen = "https://t.me/share/url?url="+encodeURIComponent(link); window.open(linkToOpen, '_blank'); }); } } class WhatsappShareButton extends ShareButton { constructor(icon) { 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'); }); } } 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 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 fetch(fullUrl, {"credentials": "same-origin"}).then(function (res) { if (raw) { return res.text(); } return res.json(); }).catch(function (e) { console.error("error", e); if (!raw) { return { "success": false, "errors": [ "not-online" ] } } }); } static 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 () { // console.log(setting); 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; } } 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", function () { 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 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); }); // window.addEventListener('hashchange', () => { // Matomo.update() // }); } 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"]()); }]); }); // Matomo.isTrackingPromise = Matomo.query("isTracked") // .then(xml => { // let textContent = xml.firstChild.textContent; // // localStorage.setItem(Matomo.LOCAL_STORAGE_KEY, textContent); // return (textContent === "1") // }); 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"); // return await Matomo.query((shouldTrack) ? "doTrack" : "doIgnore"); if (shouldTrack) { Matomo.push(["forgetUserOptOut"], true); } else { Matomo.push(["optUserOut"], true); } } static async push(arr, force) { // force = Helper.nonNull(force, false); if (!Array.isArray(arr)) { arr = [arr]; } // if (force || await Matomo.getTrackingPromise()) { 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 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"); } let beforeFontSize = fontElement.style.fontSize; let currentFontSize = 1; let diff = 0; let widthDiff = 0; let heightDiff = 0; let containerWidth = 0; let containerHeight = 0; do { currentFontSize += diff / (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; let newDiff = (ignoreWidth ? heightDiff : (ignoreHeight ? widthDiff : Math.min(widthDiff, heightDiff))); if (newDiff === diff) { break; } diff = 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; } 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(); this.source = source; } async stop(delay){ if (Helper.isNotNull(this.source)){ this.pauseTime = ((new Date()).getTime())-this.startTime; return this.source.stop(delay); } return null; } async resume(){ console.log("resume-Time:", Helper.nonNull(this.pauseTime, 0)/1000.0); return this.start(null, Helper.nonNull(this.pauseTime, 0)/1000.0); } } class InitPromise$1 { static addPromise(promise) { if (typeof promise === 'function') { let func = promise; promise = InitPromise$1.mainPromise.then(function(app){ return (func(app)); }); } InitPromise$1.promises.push(promise); } static resolve(app) { InitPromise$1.mainResolver(app); return InitPromise$1.mainPromise.then(function(){ return Promise.all(InitPromise$1.promises); }); } } InitPromise$1.promises = []; InitPromise$1.mainPromise = new Promise(function(resolver){ InitPromise$1.mainResolver = resolver; }); 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]; } handleVisibilityChange() { if (document.hidden){ for (let k in this.channels){ this.channels[k].source.stop(); } } else{ for (let k in this.channels){ if (!this.channels[k].muted){ 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 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) { 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}; } }); 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) { let target = document.elementFromPoint(e.changedTouches[0].pageX, e.changedTouches[0].pageY); if (e.targetTouches.length === 0 && e.changedTouches.length === 1 && self.element.contains(ParentSegment.mouseDownTarget) && self.element.contains(target)) { let position = {x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY}; 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() { 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[2]) && this.children[1].sameAs(this.children[3]) && ( this.rotation === 2 || 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; 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) { 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); } 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", true), volume: 0.7 }, SoundManager.CHANNELS.SOUND); 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); // console.log("LevelID: ", currentLevelInfo["id"]); 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"]); 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([20, 40, 60, 100, 120, 140, 160]); 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); //LevelCounter * 2 - 1, damit der durchschnittswert stimmt Matomo.push(["trackEvent", "LevelSite", "NextLevel", "Level Number Average", this.levelCounter*2-1]); Matomo.push(["trackEvent", "LevelSite", "NextLevel", "Level Number Normal", this.levelCounter]); 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(); 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; console.log("coinPromise - won", this.wonParams); 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.rotation !== 0); }); 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(() => { console.log("coinPromise - tutorial", this.wonParams); 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.TUTORIAL = { FIRST_LEVEL: 67, SECOND_LEVEL: 15, BIG_SEGMENT_LEVEL: 341 }; class MainMenuLevel extends FourWordsLevel{ constructor(templateContainer) { super(templateContainer, 4); } saveAsCurrentLevel() { } // checkHasWon(delayPromise) { // } } 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.startSite(LevelSite); }); 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(); scaleHelper.scaleToFull(levelSegment, levelSegment.parentElement, false, false, 2, 8, null, false); let levelStyle = getComputedStyle(levelSegment); playButton.style.width = levelStyle.getPropertyValue("width"); scaleHelper.scaleToFull(playButton.children[0], playButton, null, null, null, null, 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); return res; } async onFirstStart() { super.onFirstStart(); let playButton = this.findBy("#play-button"); playButton.addEventListener("click", () => { Matomo.push(["trackEvent", "MainMenu", "startButton"]); this.startSite(LevelSite, this.loadLevelPromise); }); 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); 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); soundManager.set({muted: !playSoundButton.checked}, SoundManager.CHANNELS.SOUND); Matomo.push(["trackEvent", "MainMenu", "PlaySound", "Play Sound", (playSoundButton.checked)?1:0]); }); } 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"]) { 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); 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"); } onStart(args) { 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", PrivacyPolicySite); }); 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 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); }); return super.onFirstStart(); } onStart() { Matomo.update("Settings Site"); super.onStart(); } } InitPromise.addPromise(function () { SettingsSite.addSettingsFragment("settings", WordRotatorSettingFragment); }); 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', '')); 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 WhatsappShareButton('img/whatsapp.svg')); ShareManager.addShareButton(new SmsShareButton('img/sms.svg')); ShareManager.addShareButton(new TelegramShareButton('img/telegram.svg')); // ShareManager.addShareButton(new CopyShareButton('img/copy.svg')); let app = new App(); // app.addDeepLink("policy", PrivatePolicySite.name); // bridge für Android window["ThemeManager"] = ThemeManager; window["ThemeManager"]["addChangeListener"] = ThemeManager.addChangeListener; window["app"] = app; window["app"]["refreshCurrentSite"] = app.refreshCurrentSite; 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", true)}); app.start(MenuSite); Translator.setLanguage("de"); // let matomo = new Matomo(); // console.log(await matomo._askIsTracking()); });