2020-08-13 18:21:48 +02:00
|
|
|
import {HtmlUtils} from "./HtmlUtils";
|
2021-03-17 11:52:41 +01:00
|
|
|
import {Subject} from "rxjs";
|
2021-03-06 16:00:07 +01:00
|
|
|
import {iframeListener} from "../Api/IframeListener";
|
2021-06-07 17:48:39 +02:00
|
|
|
import {touchScreenManager} from "../Touch/TouchScreenManager";
|
2020-08-13 18:21:48 +02:00
|
|
|
|
2020-10-27 16:59:12 +01:00
|
|
|
enum iframeStates {
|
|
|
|
closed = 1,
|
|
|
|
loading, // loading an iframe can be slow, so we show some placeholder until it is ready
|
|
|
|
opened,
|
|
|
|
}
|
|
|
|
|
2021-03-17 11:52:41 +01:00
|
|
|
const cowebsiteDivId = 'cowebsite'; // the id of the whole container.
|
|
|
|
const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe.
|
|
|
|
const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe.
|
2021-06-03 20:05:39 +02:00
|
|
|
export const cowebsiteCloseButtonId = 'cowebsite-close';
|
2021-03-17 18:57:00 +01:00
|
|
|
const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen';
|
|
|
|
const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open';
|
|
|
|
const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
|
2020-10-27 16:59:12 +01:00
|
|
|
const animationTime = 500; //time used by the css transitions, in ms.
|
|
|
|
|
2021-06-07 17:48:39 +02:00
|
|
|
interface TouchMoveCoordinates {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
}
|
|
|
|
|
2020-10-27 16:59:12 +01:00
|
|
|
class CoWebsiteManager {
|
2021-03-06 16:00:07 +01:00
|
|
|
|
|
|
|
private opened: iframeStates = iframeStates.closed;
|
2020-08-13 18:21:48 +02:00
|
|
|
|
2021-03-18 15:05:15 +01:00
|
|
|
private _onResize: Subject<void> = new Subject();
|
|
|
|
public onResize = this._onResize.asObservable();
|
2020-11-10 15:22:30 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2020-11-16 16:15:21 +01:00
|
|
|
private currentOperationPromise: Promise<void> = Promise.resolve();
|
2021-03-06 16:00:07 +01:00
|
|
|
private cowebsiteDiv: HTMLDivElement;
|
2021-03-15 18:43:13 +01:00
|
|
|
private resizing: boolean = false;
|
2021-03-17 11:52:41 +01:00
|
|
|
private cowebsiteMainDom: HTMLDivElement;
|
|
|
|
private cowebsiteAsideDom: HTMLDivElement;
|
2021-06-07 17:48:39 +02:00
|
|
|
private previousTouchMoveCoordinates: TouchMoveCoordinates|null = null; //only use on touchscreens to track touch movement
|
2021-05-12 10:35:14 +02:00
|
|
|
|
2021-03-17 11:52:41 +01:00
|
|
|
get width(): number {
|
|
|
|
return this.cowebsiteDiv.clientWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
set width(width: number) {
|
|
|
|
this.cowebsiteDiv.style.width = width+'px';
|
|
|
|
}
|
2021-03-18 15:05:15 +01:00
|
|
|
|
|
|
|
get height(): number {
|
|
|
|
return this.cowebsiteDiv.clientHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
set height(height: number) {
|
|
|
|
this.cowebsiteDiv.style.height = height+'px';
|
|
|
|
}
|
2021-03-28 16:53:15 +02:00
|
|
|
|
2021-03-18 15:05:15 +01:00
|
|
|
get verticalMode(): boolean {
|
|
|
|
return window.innerWidth < window.innerHeight;
|
|
|
|
}
|
2021-03-28 16:53:15 +02:00
|
|
|
|
2021-03-18 15:05:15 +01:00
|
|
|
get isFullScreen(): boolean {
|
2021-03-24 15:51:18 +01:00
|
|
|
return this.verticalMode ? this.height === window.innerHeight : this.width === window.innerWidth;
|
2021-03-18 15:05:15 +01:00
|
|
|
}
|
2021-03-06 16:00:07 +01:00
|
|
|
|
2020-11-16 16:15:21 +01:00
|
|
|
constructor() {
|
|
|
|
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
2021-03-17 11:52:41 +01:00
|
|
|
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteMainDomId);
|
|
|
|
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteAsideDomId);
|
|
|
|
|
2021-06-07 17:48:39 +02:00
|
|
|
if (touchScreenManager.supportTouchScreen) {
|
|
|
|
this.initResizeListeners(true);
|
|
|
|
}
|
|
|
|
this.initResizeListeners(false);
|
2021-03-17 11:52:41 +01:00
|
|
|
|
2021-06-03 20:05:39 +02:00
|
|
|
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
|
|
|
|
buttonCloseFrame.addEventListener('click', () => {
|
|
|
|
buttonCloseFrame.blur();
|
2021-03-17 11:52:41 +01:00
|
|
|
this.closeCoWebsite();
|
|
|
|
});
|
2021-06-03 20:05:39 +02:00
|
|
|
|
|
|
|
const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId);
|
|
|
|
buttonFullScreenFrame.addEventListener('click', () => {
|
|
|
|
buttonFullScreenFrame.blur();
|
2021-03-17 18:57:00 +01:00
|
|
|
this.fullscreen();
|
|
|
|
});
|
2021-03-17 11:52:41 +01:00
|
|
|
}
|
|
|
|
|
2021-06-07 17:48:39 +02:00
|
|
|
private initResizeListeners(touchMode:boolean) {
|
|
|
|
const movecallback = (event:MouseEvent|TouchEvent) => {
|
|
|
|
let x, y;
|
|
|
|
if (event.type === 'mousemove') {
|
|
|
|
x = (event as MouseEvent).movementX / this.getDevicePixelRatio();
|
|
|
|
y = (event as MouseEvent).movementY / this.getDevicePixelRatio();
|
|
|
|
} else {
|
|
|
|
const touchEvent = (event as TouchEvent).touches[0];
|
|
|
|
const last = {x: touchEvent.pageX, y: touchEvent.pageY};
|
|
|
|
const previous = this.previousTouchMoveCoordinates as TouchMoveCoordinates;
|
|
|
|
this.previousTouchMoveCoordinates = last;
|
|
|
|
x = last.x - previous.x;
|
|
|
|
y = last.y - previous.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.verticalMode ? this.height += y : this.width -= x;
|
2021-03-18 15:05:15 +01:00
|
|
|
this.fire();
|
|
|
|
}
|
2021-03-28 16:53:15 +02:00
|
|
|
|
2021-06-07 17:48:39 +02:00
|
|
|
this.cowebsiteAsideDom.addEventListener( touchMode ? 'touchstart' : 'mousedown', (event) => {
|
2021-03-15 18:43:13 +01:00
|
|
|
this.resizing = true;
|
|
|
|
this.getIframeDom().style.display = 'none';
|
2021-06-07 17:48:39 +02:00
|
|
|
if (touchMode) {
|
|
|
|
const touchEvent = (event as TouchEvent).touches[0];
|
|
|
|
this.previousTouchMoveCoordinates = {x: touchEvent.pageX, y: touchEvent.pageY};
|
|
|
|
}
|
2021-03-17 11:52:41 +01:00
|
|
|
|
2021-06-07 17:48:39 +02:00
|
|
|
document.addEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback);
|
2021-03-15 18:43:13 +01:00
|
|
|
});
|
|
|
|
|
2021-06-07 17:48:39 +02:00
|
|
|
document.addEventListener(touchMode ? 'touchend' : 'mouseup', (event) => {
|
2021-03-15 18:43:13 +01:00
|
|
|
if (!this.resizing) return;
|
2021-06-07 17:48:39 +02:00
|
|
|
if (touchMode) {
|
|
|
|
this.previousTouchMoveCoordinates = null;
|
|
|
|
}
|
|
|
|
document.removeEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback);
|
2021-03-15 18:43:13 +01:00
|
|
|
this.getIframeDom().style.display = 'block';
|
|
|
|
this.resizing = false;
|
|
|
|
});
|
2020-11-16 16:15:21 +01:00
|
|
|
}
|
2021-03-28 16:53:15 +02:00
|
|
|
|
2021-03-24 17:35:59 +01:00
|
|
|
private getDevicePixelRatio(): number {
|
|
|
|
//on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels
|
|
|
|
//so on chrome-based browser we need to adjust using 'devicePixelRatio'
|
|
|
|
return window.navigator.userAgent.includes('Firefox') ? 1 : window.devicePixelRatio;
|
2020-11-16 16:15:21 +01:00
|
|
|
}
|
2021-03-06 16:00:07 +01:00
|
|
|
|
2020-11-16 16:15:21 +01:00
|
|
|
private close(): void {
|
|
|
|
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
|
|
|
|
this.cowebsiteDiv.classList.add('hidden');
|
2020-10-27 16:59:12 +01:00
|
|
|
this.opened = iframeStates.closed;
|
2021-03-18 15:05:15 +01:00
|
|
|
this.resetStyle();
|
2020-10-27 16:59:12 +01:00
|
|
|
}
|
2020-11-16 16:15:21 +01:00
|
|
|
private load(): void {
|
|
|
|
this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
|
|
|
|
this.cowebsiteDiv.classList.add('loading');
|
2020-10-27 16:59:12 +01:00
|
|
|
this.opened = iframeStates.loading;
|
|
|
|
}
|
2020-11-16 16:15:21 +01:00
|
|
|
private open(): void {
|
|
|
|
this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
|
2020-10-27 16:59:12 +01:00
|
|
|
this.opened = iframeStates.opened;
|
2021-03-18 15:05:15 +01:00
|
|
|
this.resetStyle();
|
2020-10-27 16:59:12 +01:00
|
|
|
}
|
2021-03-17 11:52:41 +01:00
|
|
|
|
2021-03-18 15:05:15 +01:00
|
|
|
public resetStyle() {
|
|
|
|
this.cowebsiteDiv.style.width = '';
|
|
|
|
this.cowebsiteDiv.style.height = '';
|
2021-03-17 11:52:41 +01:00
|
|
|
}
|
|
|
|
|
2021-03-15 18:43:13 +01:00
|
|
|
private getIframeDom(): HTMLIFrameElement {
|
|
|
|
const iframe = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId).querySelector('iframe');
|
|
|
|
if (!iframe) throw new Error('Could not find iframe!');
|
|
|
|
return iframe;
|
2020-10-27 16:59:12 +01:00
|
|
|
}
|
2020-08-13 18:21:48 +02:00
|
|
|
|
2021-03-06 16:00:07 +01:00
|
|
|
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void {
|
2020-11-16 16:15:21 +01:00
|
|
|
this.load();
|
2021-03-17 11:52:41 +01:00
|
|
|
this.cowebsiteMainDom.innerHTML = ``;
|
2020-08-13 18:21:48 +02:00
|
|
|
|
|
|
|
const iframe = document.createElement('iframe');
|
|
|
|
iframe.id = 'cowebsite-iframe';
|
2021-01-30 17:51:07 +01:00
|
|
|
iframe.src = (new URL(url, base)).toString();
|
2020-12-29 23:17:16 +01:00
|
|
|
if (allowPolicy) {
|
2021-03-06 16:00:07 +01:00
|
|
|
iframe.allow = allowPolicy;
|
2020-12-29 23:17:16 +01:00
|
|
|
}
|
2021-05-12 10:35:14 +02:00
|
|
|
const onloadPromise = new Promise<void>((resolve) => {
|
2020-10-27 16:59:12 +01:00
|
|
|
iframe.onload = () => resolve();
|
|
|
|
});
|
2021-03-06 16:00:07 +01:00
|
|
|
if (allowApi) {
|
|
|
|
iframeListener.registerIframe(iframe);
|
|
|
|
}
|
2021-03-17 11:52:41 +01:00
|
|
|
this.cowebsiteMainDom.appendChild(iframe);
|
2021-05-12 10:35:14 +02:00
|
|
|
const onTimeoutPromise = new Promise<void>((resolve) => {
|
2020-10-27 16:59:12 +01:00
|
|
|
setTimeout(() => resolve(), 2000);
|
|
|
|
});
|
2020-11-10 15:22:30 +01:00
|
|
|
this.currentOperationPromise = this.currentOperationPromise.then(() =>Promise.race([onloadPromise, onTimeoutPromise])).then(() => {
|
2020-10-27 16:59:12 +01:00
|
|
|
this.open();
|
|
|
|
setTimeout(() => {
|
|
|
|
this.fire();
|
|
|
|
}, animationTime)
|
2021-06-03 20:05:39 +02:00
|
|
|
}).catch((err) => {
|
|
|
|
console.error('Error loadCoWebsite => ', err);
|
|
|
|
this.closeCoWebsite()
|
|
|
|
});
|
2020-08-31 12:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Just like loadCoWebsite but the div can be filled by the user.
|
|
|
|
*/
|
2020-10-27 16:59:12 +01:00
|
|
|
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
|
2020-11-16 16:15:21 +01:00
|
|
|
this.load();
|
2021-03-22 11:26:59 +01:00
|
|
|
this.cowebsiteMainDom.innerHTML = ``;
|
|
|
|
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteMainDom)).then(() => {
|
2020-10-27 16:59:12 +01:00
|
|
|
this.open();
|
|
|
|
setTimeout(() => {
|
|
|
|
this.fire();
|
2020-11-16 16:15:21 +01:00
|
|
|
}, animationTime);
|
2021-06-03 20:05:39 +02:00
|
|
|
}).catch((err) => {
|
|
|
|
console.error('Error insertCoWebsite => ', err);
|
|
|
|
this.closeCoWebsite();
|
|
|
|
});
|
2020-08-13 18:21:48 +02:00
|
|
|
}
|
|
|
|
|
2020-10-27 16:59:12 +01:00
|
|
|
public closeCoWebsite(): Promise<void> {
|
2020-11-10 15:22:30 +01:00
|
|
|
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
|
2020-11-16 16:15:21 +01:00
|
|
|
this.close();
|
2020-10-27 16:59:12 +01:00
|
|
|
this.fire();
|
2021-03-06 16:00:07 +01:00
|
|
|
const iframe = this.cowebsiteDiv.querySelector('iframe');
|
|
|
|
if (iframe) {
|
|
|
|
iframeListener.unregisterIframe(iframe);
|
|
|
|
}
|
2020-10-27 16:59:12 +01:00
|
|
|
setTimeout(() => {
|
2021-03-17 11:52:41 +01:00
|
|
|
this.cowebsiteMainDom.innerHTML = ``;
|
2020-10-27 16:59:12 +01:00
|
|
|
resolve();
|
|
|
|
}, animationTime)
|
2020-11-10 15:22:30 +01:00
|
|
|
}));
|
|
|
|
return this.currentOperationPromise;
|
2020-08-13 18:21:48 +02:00
|
|
|
}
|
|
|
|
|
2020-10-27 16:59:12 +01:00
|
|
|
public getGameSize(): {width: number, height: number} {
|
|
|
|
if (this.opened !== iframeStates.opened) {
|
2020-08-13 18:21:48 +02:00
|
|
|
return {
|
|
|
|
width: window.innerWidth,
|
|
|
|
height: window.innerHeight
|
|
|
|
}
|
|
|
|
}
|
2021-03-18 15:05:15 +01:00
|
|
|
if (!this.verticalMode) {
|
2020-08-13 18:21:48 +02:00
|
|
|
return {
|
2021-03-17 11:52:41 +01:00
|
|
|
width: window.innerWidth - this.width,
|
2020-08-13 18:21:48 +02:00
|
|
|
height: window.innerHeight
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
width: window.innerWidth,
|
2021-03-18 15:05:15 +01:00
|
|
|
height: window.innerHeight - this.height,
|
2020-08-13 18:21:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-27 16:59:12 +01:00
|
|
|
private fire(): void {
|
2021-03-18 15:05:15 +01:00
|
|
|
this._onResize.next();
|
2020-08-13 18:21:48 +02:00
|
|
|
}
|
|
|
|
|
2021-03-17 18:57:00 +01:00
|
|
|
private fullscreen(): void {
|
2021-03-18 15:05:15 +01:00
|
|
|
if (this.isFullScreen) {
|
|
|
|
this.resetStyle();
|
2021-03-24 15:51:18 +01:00
|
|
|
this.fire();
|
2021-03-17 18:57:00 +01:00
|
|
|
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
|
|
|
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'inline';
|
|
|
|
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'none';
|
|
|
|
} else {
|
2021-03-18 15:05:15 +01:00
|
|
|
this.verticalMode ? this.height = window.innerHeight : this.width = window.innerWidth;
|
2021-03-17 18:57:00 +01:00
|
|
|
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
|
|
|
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'none';
|
|
|
|
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'inline';
|
2020-08-13 18:21:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-27 16:59:12 +01:00
|
|
|
|
2021-03-06 16:00:07 +01:00
|
|
|
export const coWebsiteManager = new CoWebsiteManager();
|