import { Helper, InitPromise, Fragment, Translator } from './pwa-lib.js'; class DelayPromise extends Promise { static async delay(delay) { return new Promise((resolve) => { setTimeout(resolve, delay); }); } } 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); }); // window.addEventListener('hashchange', () => { // Matomo.update() // }); } static update(title) { if (Helper.nonNull(Matomo.currentUrl)){ Matomo.push(['setReferrerUrl', Matomo.currentUrl]); } Matomo.currentUrl = window.location.pathname+window.location.search; Matomo.push(['setCustomUrl', Matomo.currentUrl]); Matomo.push(['setDocumentTitle', title]); // remove all previously assigned custom variables, requires Matomo (formerly Piwik) 3.0.2 Matomo.push(['deleteCustomVariables', 'page']); Matomo.push(['setGenerationTimeMs', 0]); Matomo.push(['trackPageView']); // make Matomo aware of newly added content var content = document.getElementById('site-content'); Matomo.push(['MediaAnalytics::scanForMedia', content]); Matomo.push(['FormAnalytics::scanForForms', content]); Matomo.push(['trackContentImpressionsWithinNode', content]); Matomo.push(['enableLinkTracking']); } static async _askIsTracking() { Matomo.isTrackingPromise = new Promise(resolve => { Matomo.push([function () { resolve(!this["isUserOptedOut"]()); }]); Matomo.push([function () { resolve(!this["isUserOptedOut"]()); }]); }); // Matomo.isTrackingPromise = Matomo.query("isTracked") // .then(xml => { // let textContent = xml.firstChild.textContent; // // localStorage.setItem(Matomo.LOCAL_STORAGE_KEY, textContent); // return (textContent === "1") // }); return Matomo.isTrackingPromise; } static async query(method) { return fetch(Matomo.TRACK_SITE + Matomo.BASE_PATH + method, { "mode": "cors", "credentials": "include", }).then(res => res.text()).then(text => (new window.DOMParser()).parseFromString(text, "text/xml")); } static getTrackingPromise() { return Matomo.isTrackingPromise; } static async setTrack(shouldTrack) { Matomo.isTrackingPromise = Promise.resolve(shouldTrack); localStorage.setItem(Matomo.LOCAL_STORAGE_KEY, (shouldTrack === true) ? "1" : "0"); // return await Matomo.query((shouldTrack) ? "doTrack" : "doIgnore"); if (shouldTrack) { Matomo.push(["forgetUserOptOut"], true); } else { Matomo.push(["optUserOut"], true); } } static async push(arr, force) { // force = Helper.nonNull(force, false); if (!Array.isArray(arr)) { arr = [arr]; } // if (force || await Matomo.getTrackingPromise()) { window["_paq"].push(arr); // } } } Matomo.currentUrl = null; Matomo.LOCAL_STORAGE_KEY = "matomoShouldTrack"; Matomo.TRACK_SITE = "//matomo.silas.link"; Matomo.BASE_PATH = "/index.php?module=API&method=AjaxOptOut."; Matomo.SIDE_ID = "1"; InitPromise.addPromise(() => { window["_paq"] = window["_paq"] || []; Matomo.init(); }); class RotateHelper { rotate(element, degrees){ let rotateText = element.innerText; element.removeAllChildren(); let partDegree = degrees/rotateText.length; for(let i = 0; i < rotateText.length; i++){ let child = document.createElement("span"); child.innerText = rotateText.charAt(i); child.style.transform ="rotate("+(partDegree*i)+"deg)"; child.classList.add("rotated"); element.appendChild(child); } } } class ScaleHelper { async scaleTo(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay, addListener) { addListener = Helper.nonNull(addListener, true); animationDelay = Helper.nonNull(animationDelay, 0); let newFontSize = await this._getNewFontSize(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay === 0); if (animationDelay > 0) { await new Promise(r => { setTimeout(r, animationDelay); fontElement.style.fontSize = newFontSize + "px"; }); } let self = this; let listener = function () { return new Promise(resolve => { let timeout = (typeof addListener === 'number') ? addListener : 255; setTimeout(() => { resolve(self.scaleTo(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay, false)); }, timeout); }); }; if (addListener !== false) { window.addEventListener("resize", listener); } return listener; } async scaleToFull(fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animDelay, addListener) { return this.scaleTo(1, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animDelay, addListener); } async _getNewFontSize(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, setFontSize) { margin = Helper.nonNull(margin, 10); ignoreHeight = Helper.nonNull(ignoreHeight, false); ignoreWidth = Helper.nonNull(ignoreWidth, false); fontWeight = Helper.nonNull(fontWeight, fontElement.innerHTML.length); setFontSize = Helper.nonNull(setFontSize, true); let hasNoTransitionClass = container.classList.contains("no-transition"); if (!hasNoTransitionClass) { container.classList.add("no-transition"); } let beforeFontSize = fontElement.style.fontSize; let currentFontSize = 1; let diff = 0; let widthDiff = 0; let heightDiff = 0; let containerWidth = 0; let containerHeight = 0; do { currentFontSize += diff / (fontWeight + 1); fontElement.style.fontSize = currentFontSize + 'px'; let containerStyle = window.getComputedStyle(container); containerWidth = containerStyle.getPropertyValue("width").replace('px', ''); containerHeight = containerStyle.getPropertyValue("height").replace('px', ''); widthDiff = containerWidth - fontElement.offsetWidth; heightDiff = containerHeight - fontElement.offsetHeight; let newDiff = (ignoreWidth ? heightDiff : (ignoreHeight ? widthDiff : Math.min(widthDiff, heightDiff))); if (newDiff === diff) { break; } diff = newDiff; } while ((widthDiff > (1 - scale) * containerWidth || ignoreWidth) && (heightDiff > (1 - scale) * containerHeight || ignoreHeight)); currentFontSize -= margin; fontElement.style.fontSize = ((setFontSize) ? currentFontSize + "px" : beforeFontSize); if (!hasNoTransitionClass) { await new Promise((r) => { setTimeout(r, 50); }); container.classList.remove("no-transition"); } return currentFontSize; } } class AudioChain { constructor(context, sourceBuffer, chainFunction) { this.buffer = sourceBuffer; this.shouldLoop = false; this.loopStart = null; this.loopEnd = null; this.chainFunction = chainFunction; this.context = context; this.startTime = null; this.pauseTime = null; this.source = null; 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(); this.source = source; this.running = true; } async stop(delay) { if (Helper.isNotNull(this.source)) { this.pauseTime = ((new Date()).getTime()) - this.startTime; return this.source.stop(delay); } this.running = false; 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 TabbedFragment extends Fragment { constructor(site) { super(site, 'pwaAssets/html/fragment/tabbedFragment.html'); this.fragments = {}; this.tabHeadingTemplate = null; this.tabHeadingContainer = null; this.tabContainer = null; this.tabKeys = []; this.currentTabIndex = 0; } onFirstStart() { super.onFirstStart(); this.tabContainer = this.findBy(".tab-container"); this.showTab(0); } addFragment(name, fragment, translationArgs, isTranslatable) { isTranslatable = Helper.nonNull(isTranslatable, true); if (translationArgs === false) { isTranslatable = false; } this.fragments[name] = fragment; const tabIndex = this.tabKeys.length; this.tabKeys.push(name); const _self = this; this.inflatePromise = this.inflatePromise.then(function (siteContent) { if (Helper.isNull(_self.tabHeadingTemplate)) { _self.tabHeadingTemplate = siteContent.querySelector(".tab-header-template"); } const newTabHeader = Helper.cloneNode(_self.tabHeadingTemplate); newTabHeader.classList.add("tab-" + tabIndex); newTabHeader.querySelector(".tab-name").appendChild((isTranslatable) ? Translator.makePersistentTranslation(name, translationArgs) : document.createTextNode(name)); newTabHeader.addEventListener("click", function(){ _self.showTab(tabIndex); }); if (Helper.isNull(_self.tabHeadingContainer)) { _self.tabHeadingContainer = siteContent.querySelector(".tab-header-container"); } _self.tabHeadingContainer.appendChild(newTabHeader); return siteContent; }); } showTab(index) { if (index >= 0 && index < this.tabKeys.length) { this.findBy(".tab-" + this.currentTabIndex).classList.remove("active"); this.findBy(".tab-" + index).classList.add("active"); this.tabContainer.removeAllChildren().appendChild(Helper.createLoadingSymbol()); this.currentTabIndex = index; const _self = this; this.fragments[this.tabKeys[index]].inflatePromise.then(tabView => { if (_self.currentTabIndex === index) { _self.tabContainer.removeAllChildren().appendChild(tabView); } }); } } } export { DelayPromise, InstallManager, Matomo, RotateHelper, ScaleHelper, AudioChain, SoundManager, TabbedFragment };