import {HtmlUtils} from "./HtmlUtils"; import {iframeListener} from "../Api/IframeListener"; export type CoWebsiteStateChangedCallback = () => void; enum iframeStates { closed = 1, loading, // loading an iframe can be slow, so we show some placeholder until it is ready opened, } const cowebsiteDivId = "cowebsite"; // the id of the parent div of the iframe. const animationTime = 500; //time used by the css transitions, in ms. class CoWebsiteManager { private opened: iframeStates = iframeStates.closed; private observers = new Array(); /** * Quickly going in and out of an iframe trigger can create conflicts between the iframe states. * So we use this promise to queue up every cowebsite state transition */ private currentOperationPromise: Promise = Promise.resolve(); private cowebsiteDiv: HTMLDivElement; constructor() { this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); } private close(): void { this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition this.cowebsiteDiv.classList.add('hidden'); this.opened = iframeStates.closed; } private load(): void { this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition this.cowebsiteDiv.classList.add('loading'); this.opened = iframeStates.loading; } private open(): void { this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition this.opened = iframeStates.opened; } public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void { this.load(); this.cowebsiteDiv.innerHTML = ``; setTimeout(() => { HtmlUtils.getElementByIdOrFail('cowebsite-close').addEventListener('click', () => { this.closeCoWebsite(); }); }, 100); const iframe = document.createElement('iframe'); iframe.id = 'cowebsite-iframe'; iframe.src = (new URL(url, base)).toString(); if (allowPolicy) { iframe.allow = allowPolicy; } const onloadPromise = new Promise((resolve) => { iframe.onload = () => resolve(); }); if (allowApi) { iframeListener.registerIframe(iframe); } this.cowebsiteDiv.appendChild(iframe); const onTimeoutPromise = new Promise((resolve) => { setTimeout(() => resolve(), 2000); }); this.currentOperationPromise = this.currentOperationPromise.then(() =>Promise.race([onloadPromise, onTimeoutPromise])).then(() => { this.open(); setTimeout(() => { this.fire(); }, animationTime) }).catch(() => this.closeCoWebsite()); } /** * Just like loadCoWebsite but the div can be filled by the user. */ public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise): void { this.load(); this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => { this.open(); setTimeout(() => { this.fire(); }, animationTime); }).catch(() => this.closeCoWebsite()); } public closeCoWebsite(): Promise { this.currentOperationPromise = this.currentOperationPromise.then(() => new Promise((resolve, reject) => { if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example this.close(); this.fire(); const iframe = this.cowebsiteDiv.querySelector('iframe'); if (iframe) { iframeListener.unregisterIframe(iframe); } setTimeout(() => { this.cowebsiteDiv.innerHTML = ``; resolve(); }, animationTime) })); return this.currentOperationPromise; } public getGameSize(): {width: number, height: number} { if (this.opened !== iframeStates.opened) { return { width: window.innerWidth, height: window.innerHeight } } if (window.innerWidth >= window.innerHeight) { return { width: window.innerWidth / 2, height: window.innerHeight } } else { return { width: window.innerWidth, height: window.innerHeight / 2 } } } //todo: is it still useful to allow any kind of observers? public onStateChange(observer: CoWebsiteStateChangedCallback) { this.observers.push(observer); } private fire(): void { for (const callback of this.observers) { callback(); } } } export const coWebsiteManager = new CoWebsiteManager();