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