Merge pull request #1588 from thecodingmachine/develop

Deploy 2021-11-24 2
This commit is contained in:
David Négrier 2021-11-24 17:44:48 +01:00 committed by GitHub
commit 69a26a0e55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 323 additions and 273 deletions

View File

@ -62,18 +62,13 @@ jobs:
working-directory: "front" working-directory: "front"
- name: "Pretty" - name: "Pretty"
run: yarn run pretty run: yarn run pretty-check
working-directory: "front" working-directory: "front"
- name: "Jasmine" - name: "Jasmine"
run: yarn test run: yarn test
working-directory: "front" working-directory: "front"
# We will enable prettier checks on front in a few month, when most PRs without prettier have been merged
# - name: "Prettier"
# run: yarn run pretty-check
# working-directory: "front"
continuous-integration-pusher: continuous-integration-pusher:
name: "Continuous Integration Pusher" name: "Continuous Integration Pusher"

View File

@ -30,7 +30,16 @@ jobs:
run: docker-compose up -d run: docker-compose up -d
- name: "Wait for environment to build (and downloading testcafe image)" - name: "Wait for environment to build (and downloading testcafe image)"
run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -m 1 "Compiled successfully" run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully"
# - name: "temp debug: display logs"
# run: docker-compose logs
#
# - name: "Wait for back start"
# run: docker-compose logs -f back | grep -q "WorkAdventure HTTP API starting on port"
#
# - name: "Wait for pusher start"
# run: docker-compose logs -f pusher | grep -q "WorkAdventure starting on port"
- name: "Run tests" - name: "Run tests"
run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe

View File

View File

@ -4,7 +4,7 @@ declare let window: any;
class AnalyticsClient { class AnalyticsClient {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
private posthogPromise: Promise<any>|undefined; private posthogPromise: Promise<any> | undefined;
constructor() { constructor() {
if (POSTHOG_API_KEY && POSTHOG_URL) { if (POSTHOG_API_KEY && POSTHOG_URL) {
@ -18,74 +18,64 @@ class AnalyticsClient {
} }
identifyUser(uuid: string, email: string | null) { identifyUser(uuid: string, email: string | null) {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.identify(uuid, { uuid, email, wa: true });
posthog.identify(uuid, { uuid, email, wa: true }); });
});
} }
loggedWithSso() { loggedWithSso() {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-logged-sso");
posthog.capture("wa-logged-sso"); });
});
} }
loggedWithToken() { loggedWithToken() {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-logged-token");
posthog.capture("wa-logged-token"); });
});
} }
enteredRoom(roomId: string, roomGroup: string | null) { enteredRoom(roomId: string, roomGroup: string | null) {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("$pageView", { roomId, roomGroup });
posthog.capture("$pageView", { roomId, roomGroup }); posthog.capture("enteredRoom");
posthog.capture("enteredRoom"); });
});
} }
openedMenu() { openedMenu() {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-opened-menu");
posthog.capture("wa-opened-menu"); });
});
} }
launchEmote(emote: string) { launchEmote(emote: string) {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-emote-launch", { emote });
posthog.capture("wa-emote-launch", { emote }); });
});
} }
enteredJitsi(roomName: string, roomId: string) { enteredJitsi(roomName: string, roomId: string) {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-entered-jitsi", { roomName, roomId });
posthog.capture("wa-entered-jitsi", { roomName, roomId }); });
});
} }
validationName() { validationName() {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-name-validation");
posthog.capture("wa-name-validation"); });
});
} }
validationWoka(scene: string) { validationWoka(scene: string) {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-woka-validation", { scene });
posthog.capture("wa-woka-validation", { scene }); });
});
} }
validationVideo() { validationVideo() {
this.posthogPromise this.posthogPromise?.then((posthog) => {
?.then((posthog) => { posthog.capture("wa-video-validation");
posthog.capture("wa-video-validation"); });
});
} }
} }
export const analyticsClient = new AnalyticsClient(); export const analyticsClient = new AnalyticsClient();

View File

@ -2,7 +2,7 @@ import * as tg from "generic-type-guard";
export const isCloseCoWebsite = new tg.IsInterface() export const isCloseCoWebsite = new tg.IsInterface()
.withProperties({ .withProperties({
id: tg.isOptional(tg.isString) id: tg.isOptional(tg.isString),
}) })
.get(); .get();

View File

@ -24,9 +24,7 @@ import type { EmbeddedWebsite } from "../iframe/Room/EmbeddedWebsite";
import { isCreateEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent"; import { isCreateEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent";
import type { LoadTilesetEvent } from "./LoadTilesetEvent"; import type { LoadTilesetEvent } from "./LoadTilesetEvent";
import { isLoadTilesetEvent } from "./LoadTilesetEvent"; import { isLoadTilesetEvent } from "./LoadTilesetEvent";
import type { import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent";
MessageReferenceEvent,
} from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent";
@ -117,19 +115,19 @@ export const iframeQueryMapTypeGuards = {
}, },
openCoWebsite: { openCoWebsite: {
query: isOpenCoWebsiteEvent, query: isOpenCoWebsiteEvent,
answer: isCoWebsite answer: isCoWebsite,
}, },
getCoWebsites: { getCoWebsites: {
query: tg.isUndefined, query: tg.isUndefined,
answer: tg.isArray(isCoWebsite) answer: tg.isArray(isCoWebsite),
}, },
closeCoWebsite: { closeCoWebsite: {
query: tg.isString, query: tg.isString,
answer: tg.isUndefined answer: tg.isUndefined,
}, },
closeCoWebsites: { closeCoWebsites: {
query: tg.isUndefined, query: tg.isUndefined,
answer: tg.isUndefined answer: tg.isUndefined,
}, },
triggerActionMessage: { triggerActionMessage: {
query: isTriggerActionMessageEvent, query: isTriggerActionMessageEvent,

View File

@ -5,7 +5,7 @@ export const isOpenCoWebsiteEvent = new tg.IsInterface()
url: tg.isString, url: tg.isString,
allowApi: tg.isOptional(tg.isBoolean), allowApi: tg.isOptional(tg.isBoolean),
allowPolicy: tg.isOptional(tg.isString), allowPolicy: tg.isOptional(tg.isString),
position: tg.isOptional(tg.isNumber) position: tg.isOptional(tg.isNumber),
}) })
.get(); .get();

View File

@ -49,7 +49,7 @@ class IframeListener {
public readonly openTabStream = this._openTabStream.asObservable(); public readonly openTabStream = this._openTabStream.asObservable();
private readonly _loadPageStream: Subject<string> = new Subject(); private readonly _loadPageStream: Subject<string> = new Subject();
public readonly loadPageStream = this._loadPageStream.asObservable() public readonly loadPageStream = this._loadPageStream.asObservable();
private readonly _disablePlayerControlStream: Subject<void> = new Subject(); private readonly _disablePlayerControlStream: Subject<void> = new Subject();
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();

View File

@ -57,7 +57,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
async getCoWebSites(): Promise<CoWebsite[]> { async getCoWebSites(): Promise<CoWebsite[]> {
const result = await queryWorkadventure({ const result = await queryWorkadventure({
type: "getCoWebsites", type: "getCoWebsites",
data: undefined data: undefined,
}); });
return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position)); return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position));
} }

View File

@ -84,7 +84,8 @@ class ConnectionManager {
if (token) { if (token) {
this.authToken = token; this.authToken = token;
localUserStore.setAuthToken(token); localUserStore.setAuthToken(token);
//token was saved, clear url
//clean token of url
urlParams.delete("token"); urlParams.delete("token");
} }
@ -95,8 +96,6 @@ class ConnectionManager {
} }
urlManager.pushRoomIdToUrl(this._currentRoom); urlManager.pushRoomIdToUrl(this._currentRoom);
} else if (connexionType === GameConnexionTypes.jwt) { } else if (connexionType === GameConnexionTypes.jwt) {
const urlParams = new URLSearchParams(window.location.search);
if (!token) { if (!token) {
const code = urlParams.get("code"); const code = urlParams.get("code");
const state = urlParams.get("state"); const state = urlParams.get("state");
@ -139,7 +138,7 @@ class ConnectionManager {
"//" + "//" +
window.location.host + window.location.host +
roomUrl + roomUrl +
window.location.search + urlParams.toString() + //use urlParams because the token param must be deleted
window.location.hash window.location.hash
) )
); );
@ -169,7 +168,7 @@ class ConnectionManager {
"//" + "//" +
window.location.host + window.location.host +
window.location.pathname + window.location.pathname +
window.location.search + urlParams.toString() + //use urlParams because the token param must be deleted
window.location.hash; window.location.hash;
} }
@ -218,8 +217,6 @@ class ConnectionManager {
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email); analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
} }
//clean history with new URL
window.history.pushState({}, document.title, window.location.pathname);
this.serviceWorker = new _ServiceWorker(); this.serviceWorker = new _ServiceWorker();
return Promise.resolve(this._currentRoom); return Promise.resolve(this._currentRoom);
} }

View File

@ -122,7 +122,7 @@ class LocalUserStore {
setLastRoomUrl(roomUrl: string): void { setLastRoomUrl(roomUrl: string): void {
localStorage.setItem(lastRoomUrl, roomUrl.toString()); localStorage.setItem(lastRoomUrl, roomUrl.toString());
if ('caches' in window) { if ("caches" in window) {
caches.open(cacheAPIIndex).then((cache) => { caches.open(cacheAPIIndex).then((cache) => {
const stringResponse = new Response(JSON.stringify({ roomUrl })); const stringResponse = new Response(JSON.stringify({ roomUrl }));
cache.put(`/${lastRoomUrl}`, stringResponse); cache.put(`/${lastRoomUrl}`, stringResponse);
@ -135,7 +135,7 @@ class LocalUserStore {
); );
} }
getLastRoomUrlCacheApi(): Promise<string | undefined> { getLastRoomUrlCacheApi(): Promise<string | undefined> {
if (!('caches' in window)) { if (!("caches" in window)) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
return caches.open(cacheAPIIndex).then((cache) => { return caches.open(cacheAPIIndex).then((cache) => {

View File

@ -107,7 +107,8 @@ export class Room {
this._mapUrl = data.mapUrl; this._mapUrl = data.mapUrl;
this._textures = data.textures; this._textures = data.textures;
this._group = data.group; this._group = data.group;
this._authenticationMandatory = data.authenticationMandatory || DISABLE_ANONYMOUS; this._authenticationMandatory =
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER; this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
this._contactPage = data.contactPage || CONTACT_URL; this._contactPage = data.contactPage || CONTACT_URL;
return new MapDetail(data.mapUrl, data.textures); return new MapDetail(data.mapUrl, data.textures);

View File

@ -6,73 +6,124 @@ const TextName: string = "Loading...";
const LogoResource: string = "static/images/logo.png"; const LogoResource: string = "static/images/logo.png";
const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 }; const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 };
export const addLoader = (scene: Phaser.Scene): void => { const loadingBarHeight: number = 16;
// If there is nothing to load, do not display the loader. const padding: number = 5;
if (scene.load.list.entries.length === 0) {
return;
}
let loadingText: Phaser.GameObjects.Text | null = null;
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
const loadingBarHeight: number = 16;
const padding: number = 5;
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => { export class Loader {
if (scene.load.textureManager.exists(LogoNameIndex)) { private progressContainer!: Phaser.GameObjects.Graphics;
return res( private progress!: Phaser.GameObjects.Graphics;
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex) private progressAmount: number = 0;
); private logo: Phaser.GameObjects.Image | undefined;
} else { private loadingText: Phaser.GameObjects.Text | null = null;
//add loading if logo image is not ready
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName); public constructor(private scene: Phaser.Scene) {}
public addLoader(): void {
// If there is nothing to load, do not display the loader.
if (this.scene.load.list.entries.length === 0) {
return;
} }
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => { const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
if (loadingText) {
loadingText.destroy(); const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
return res(
(this.logo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 150,
LogoNameIndex
))
);
} else {
//add loading if logo image is not ready
this.loadingText = this.scene.add.text(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 50,
TextName
);
} }
return res( this.scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex) this.scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
); if (this.loadingText) {
this.loadingText.destroy();
}
return res(
(this.logo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 150,
LogoNameIndex
))
);
});
}); });
});
const progressContainer = scene.add.graphics(); this.progressContainer = this.scene.add.graphics();
const progress = scene.add.graphics(); this.progress = this.scene.add.graphics();
progressContainer.fillStyle(0x444444, 0.8); this.progressContainer.fillStyle(0x444444, 0.8);
progressContainer.fillRect(
(scene.game.renderer.width - loadingBarWidth) / 2 - padding,
scene.game.renderer.height / 2 + 50 - padding,
loadingBarWidth + padding * 2,
loadingBarHeight + padding * 2
);
scene.load.on("progress", (value: number) => { this.resize();
progress.clear();
progress.fillStyle(0xbbbbbb, 1); this.scene.load.on("progress", (value: number) => {
progress.fillRect( this.progressAmount = value;
(scene.game.renderer.width - loadingBarWidth) / 2, this.drawProgress();
scene.game.renderer.height / 2 + 50, });
loadingBarWidth * value, this.scene.load.on("complete", () => {
if (this.loadingText) {
this.loadingText.destroy();
}
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
resLoadingImage.destroy();
});
this.progress.destroy();
this.progressContainer.destroy();
if (this.scene instanceof DirtyScene) {
this.scene.markDirty();
}
});
}
public removeLoader(): void {
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
this.scene.load.textureManager.remove(LogoNameIndex);
}
}
public resize(): void {
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
this.progressContainer.clear();
this.progressContainer.fillStyle(0x444444, 0.8);
this.progressContainer.fillRect(
(this.scene.game.renderer.width - loadingBarWidth) / 2 - padding,
this.scene.game.renderer.height / 2 + 50 - padding,
loadingBarWidth + padding * 2,
loadingBarHeight + padding * 2
);
this.drawProgress();
if (this.loadingText) {
this.loadingText.x = this.scene.game.renderer.width / 2;
this.loadingText.y = this.scene.game.renderer.height / 2 - 50;
}
if (this.logo) {
this.logo.x = this.scene.game.renderer.width / 2;
this.logo.y = this.scene.game.renderer.height / 2 - 150;
}
}
private drawProgress() {
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
this.progress.clear();
this.progress.fillStyle(0xbbbbbb, 1);
this.progress.fillRect(
(this.scene.game.renderer.width - loadingBarWidth) / 2,
this.scene.game.renderer.height / 2 + 50,
loadingBarWidth * this.progressAmount,
loadingBarHeight loadingBarHeight
); );
});
scene.load.on("complete", () => {
if (loadingText) {
loadingText.destroy();
}
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
resLoadingImage.destroy();
});
progress.destroy();
progressContainer.destroy();
if (scene instanceof DirtyScene) {
scene.markDirty();
}
});
};
export const removeLoader = (scene: Phaser.Scene): void => {
if (scene.load.textureManager.exists(LogoNameIndex)) {
scene.load.textureManager.remove(LogoNameIndex);
} }
}; }

View File

@ -128,6 +128,10 @@ export abstract class Character extends Container {
} }
public addTextures(textures: string[], frame?: string | number): void { public addTextures(textures: string[], frame?: string | number): void {
if (textures.length < 1) {
throw new TextureError("no texture given");
}
for (const texture of textures) { for (const texture of textures) {
if (this.scene && !this.scene.textures.exists(texture)) { if (this.scene && !this.scene.textures.exists(texture)) {
throw new TextureError("texture not found"); throw new TextureError("texture not found");

View File

@ -69,13 +69,11 @@ export const lazyLoadPlayerCharacterTextures = (
} }
//If the loading fail, we render the default model instead. //If the loading fail, we render the default model instead.
return returnPromise return returnPromise.then((keys) =>
.then((keys) => keys.map((key) => {
keys.map((key) => { return typeof key !== "string" ? key.name : key;
return typeof key !== "string" ? key.name : key; })
}) );
)
.catch(() => lazyLoadPlayerCharacterTextures(loadPlugin, ["color_22", "eyes_23"]));
}; };
export const getRessourceDescriptor = ( export const getRessourceDescriptor = (

View File

@ -12,8 +12,7 @@ export type PropertyChangeCallback = (
export type layerChangeCallback = ( export type layerChangeCallback = (
layersChangedByAction: Array<ITiledMapLayer>, layersChangedByAction: Array<ITiledMapLayer>,
allLayersOnNewPosition: Array<ITiledMapLayer>, allLayersOnNewPosition: Array<ITiledMapLayer>
) => void; ) => void;
/** /**
@ -81,7 +80,7 @@ export class GameMap {
} }
private getLayersByKey(key: number): Array<ITiledMapLayer> { private getLayersByKey(key: number): Array<ITiledMapLayer> {
return this.flatLayers.filter(flatLayer => flatLayer.type === 'tilelayer' && flatLayer.data[key] !== 0); return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0);
} }
/** /**
@ -134,14 +133,13 @@ export class GameMap {
const enterLayers = new Set(layersByNewKey); const enterLayers = new Set(layersByNewKey);
const leaveLayers = new Set(layersByOldKey); const leaveLayers = new Set(layersByOldKey);
enterLayers.forEach(layer => { enterLayers.forEach((layer) => {
if (leaveLayers.has(layer)) { if (leaveLayers.has(layer)) {
leaveLayers.delete(layer); leaveLayers.delete(layer);
enterLayers.delete(layer); enterLayers.delete(layer);
} }
}); });
if (enterLayers.size > 0) { if (enterLayers.size > 0) {
const layerArray = Array.from(enterLayers); const layerArray = Array.from(enterLayers);
for (const callback of this.enterLayerCallbacks) { for (const callback of this.enterLayerCallbacks) {

View File

@ -1,37 +1,37 @@
export enum GameMapProperties { export enum GameMapProperties {
ALLOW_API = 'allowApi', ALLOW_API = "allowApi",
AUDIO_LOOP = 'audioLoop', AUDIO_LOOP = "audioLoop",
AUDIO_VOLUME = 'audioVolume', AUDIO_VOLUME = "audioVolume",
COLLIDES = 'collides', COLLIDES = "collides",
DEFAULT = 'default', DEFAULT = "default",
EXIT_URL = 'exitUrl', EXIT_URL = "exitUrl",
EXIT_SCENE_URL = 'exitSceneUrl', EXIT_SCENE_URL = "exitSceneUrl",
FONT_FAMILY = 'font-family', FONT_FAMILY = "font-family",
JITSI_ADMIN_ROOM_TAG = 'jitsiRoomAdminTag', JITSI_ADMIN_ROOM_TAG = "jitsiRoomAdminTag",
JITSI_CONFIG = 'jitsiConfig', JITSI_CONFIG = "jitsiConfig",
JITSI_INTERFACE_CONFIG = 'jitsiInterfaceConfig', JITSI_INTERFACE_CONFIG = "jitsiInterfaceConfig",
JITSI_ROOM = 'jitsiRoom', JITSI_ROOM = "jitsiRoom",
JITSI_TRIGGER = 'jitsiTrigger', JITSI_TRIGGER = "jitsiTrigger",
JITSI_TRIGGER_MESSAGE = 'jitsiTriggerMessage', JITSI_TRIGGER_MESSAGE = "jitsiTriggerMessage",
JITSI_URL = 'jitsiUrl', JITSI_URL = "jitsiUrl",
JITSI_WIDTH = 'jitsiWidth', JITSI_WIDTH = "jitsiWidth",
NAME = 'name', NAME = "name",
OPEN_TAB = 'openTab', OPEN_TAB = "openTab",
OPEN_WEBSITE = 'openWebsite', OPEN_WEBSITE = "openWebsite",
OPEN_WEBSITE_ALLOW_API = 'openWebsiteAllowApi', OPEN_WEBSITE_ALLOW_API = "openWebsiteAllowApi",
OPEN_WEBSITE_POLICY = 'openWebsitePolicy', OPEN_WEBSITE_POLICY = "openWebsitePolicy",
OPEN_WEBSITE_WIDTH = 'openWebsiteWidth', OPEN_WEBSITE_WIDTH = "openWebsiteWidth",
OPEN_WEBSITE_POSITION = 'openWebsitePosition', OPEN_WEBSITE_POSITION = "openWebsitePosition",
OPEN_WEBSITE_TRIGGER = 'openWebsiteTrigger', OPEN_WEBSITE_TRIGGER = "openWebsiteTrigger",
OPEN_WEBSITE_TRIGGER_MESSAGE = 'openWebsiteTriggerMessage', OPEN_WEBSITE_TRIGGER_MESSAGE = "openWebsiteTriggerMessage",
PLAY_AUDIO = 'playAudio', PLAY_AUDIO = "playAudio",
PLAY_AUDIO_LOOP = 'playAudioLoop', PLAY_AUDIO_LOOP = "playAudioLoop",
READABLE_BY = 'readableBy', READABLE_BY = "readableBy",
SCRIPT = 'script', SCRIPT = "script",
SILENT = 'silent', SILENT = "silent",
START = 'start', START = "start",
START_LAYER = 'startLayer', START_LAYER = "startLayer",
URL = 'url', URL = "url",
WRITABLE_BY = 'writableBy', WRITABLE_BY = "writableBy",
ZONE = 'zone', ZONE = "zone",
} }

View File

@ -4,10 +4,8 @@ import { scriptUtils } from "../../Api/ScriptUtils";
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager"; import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { get } from 'svelte/store'; import { get } from "svelte/store";
import { import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
ON_ACTION_TRIGGER_BUTTON,
} from "../../WebRtc/LayoutManager";
import type { ITiledMapLayer } from "../Map/ITiledMap"; import type { ITiledMapLayer } from "../Map/ITiledMap";
import { GameMapProperties } from "./GameMapProperties"; import { GameMapProperties } from "./GameMapProperties";
@ -18,8 +16,8 @@ enum OpenCoWebsiteState {
} }
interface OpenCoWebsite { interface OpenCoWebsite {
coWebsite: CoWebsite | undefined, coWebsite: CoWebsite | undefined;
state: OpenCoWebsiteState state: OpenCoWebsiteState;
} }
export class GameMapPropertiesListener { export class GameMapPropertiesListener {
@ -29,7 +27,7 @@ export class GameMapPropertiesListener {
constructor(private scene: GameScene, private gameMap: GameMap) {} constructor(private scene: GameScene, private gameMap: GameMap) {}
register() { register() {
this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldvalue, allProps) => { this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => {
if (newValue === undefined) { if (newValue === undefined) {
layoutManagerActionStore.removeAction("openTab"); layoutManagerActionStore.removeAction("openTab");
} }
@ -53,10 +51,10 @@ export class GameMapPropertiesListener {
} }
}); });
// Open a new co-website by the property. // Open a new co-website by the property.
this.gameMap.onEnterLayer((newLayers) => { this.gameMap.onEnterLayer((newLayers) => {
const handler = () => { const handler = () => {
newLayers.forEach(layer => { newLayers.forEach((layer) => {
if (!layer.properties) { if (!layer.properties) {
return; return;
} }
@ -69,8 +67,8 @@ export class GameMapPropertiesListener {
let websiteTriggerProperty: string | undefined; let websiteTriggerProperty: string | undefined;
let websiteTriggerMessageProperty: string | undefined; let websiteTriggerMessageProperty: string | undefined;
layer.properties.forEach(property => { layer.properties.forEach((property) => {
switch(property.name) { switch (property.name) {
case GameMapProperties.OPEN_WEBSITE: case GameMapProperties.OPEN_WEBSITE:
openWebsiteProperty = property.value as string | undefined; openWebsiteProperty = property.value as string | undefined;
break; break;
@ -111,26 +109,28 @@ export class GameMapPropertiesListener {
}); });
const openWebsiteFunction = () => { const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite( coWebsiteManager
openWebsiteProperty as string, .loadCoWebsite(
this.scene.MapUrlFile, openWebsiteProperty as string,
allowApiProperty, this.scene.MapUrlFile,
websitePolicyProperty, allowApiProperty,
websiteWidthProperty, websitePolicyProperty,
websitePositionProperty, websiteWidthProperty,
).then(coWebsite => { websitePositionProperty
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer); )
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) { .then((coWebsite) => {
coWebsiteManager.closeCoWebsite(coWebsite); const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
this.coWebsitesOpenByLayer.delete(layer); if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
this.coWebsitesActionTriggerByLayer.delete(layer); coWebsiteManager.closeCoWebsite(coWebsite);
} else { this.coWebsitesOpenByLayer.delete(layer);
this.coWebsitesOpenByLayer.set(layer, { this.coWebsitesActionTriggerByLayer.delete(layer);
coWebsite, } else {
state: OpenCoWebsiteState.OPENED this.coWebsitesOpenByLayer.set(layer, {
}); coWebsite,
} state: OpenCoWebsiteState.OPENED,
}); });
}
});
layoutManagerActionStore.removeAction(actionUuid); layoutManagerActionStore.removeAction(actionUuid);
}; };
@ -161,7 +161,7 @@ export class GameMapPropertiesListener {
// Close opened co-websites on leave the layer who contain the property. // Close opened co-websites on leave the layer who contain the property.
this.gameMap.onLeaveLayer((oldLayers) => { this.gameMap.onLeaveLayer((oldLayers) => {
const handler = () => { const handler = () => {
oldLayers.forEach(layer => { oldLayers.forEach((layer) => {
if (!layer.properties) { if (!layer.properties) {
return; return;
} }
@ -169,8 +169,8 @@ export class GameMapPropertiesListener {
let openWebsiteProperty: string | undefined; let openWebsiteProperty: string | undefined;
let websiteTriggerProperty: string | undefined; let websiteTriggerProperty: string | undefined;
layer.properties.forEach(property => { layer.properties.forEach((property) => {
switch(property.name) { switch (property.name) {
case GameMapProperties.OPEN_WEBSITE: case GameMapProperties.OPEN_WEBSITE:
openWebsiteProperty = property.value as string | undefined; openWebsiteProperty = property.value as string | undefined;
break; break;
@ -192,11 +192,6 @@ export class GameMapPropertiesListener {
if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) { if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) {
coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE; coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
return;
}
if (coWebsiteOpen.state !== OpenCoWebsiteState.OPENED) {
return;
} }
if (coWebsiteOpen.coWebsite !== undefined) { if (coWebsiteOpen.coWebsite !== undefined) {
@ -216,9 +211,10 @@ export class GameMapPropertiesListener {
return; return;
} }
const action = actionStore && actionStore.length > 0 ? const action =
actionStore.find(action => action.uuid === actionTriggerUuid) : undefined; actionStore && actionStore.length > 0
? actionStore.find((action) => action.uuid === actionTriggerUuid)
: undefined;
if (action) { if (action) {
layoutManagerActionStore.removeAction(actionTriggerUuid); layoutManagerActionStore.removeAction(actionTriggerUuid);

View File

@ -28,7 +28,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import { mediaManager } from "../../WebRtc/MediaManager"; import { mediaManager } from "../../WebRtc/MediaManager";
import { SimplePeer } from "../../WebRtc/SimplePeer"; import { SimplePeer } from "../../WebRtc/SimplePeer";
import { addLoader, removeLoader } from "../Components/Loader"; import { Loader } from "../Components/Loader";
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { RemotePlayer } from "../Entity/RemotePlayer"; import { RemotePlayer } from "../Entity/RemotePlayer";
import type { ActionableItem } from "../Items/ActionableItem"; import type { ActionableItem } from "../Items/ActionableItem";
@ -203,6 +203,7 @@ export class GameScene extends DirtyScene {
private sharedVariablesManager!: SharedVariablesManager; private sharedVariablesManager!: SharedVariablesManager;
private objectsByType = new Map<string, ITiledMapObject[]>(); private objectsByType = new Map<string, ITiledMapObject[]>();
private embeddedWebsiteManager!: EmbeddedWebsiteManager; private embeddedWebsiteManager!: EmbeddedWebsiteManager;
private loader: Loader;
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
super({ super({
@ -221,6 +222,7 @@ export class GameScene extends DirtyScene {
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => { this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve; this.connectionAnswerPromiseResolve = resolve;
}); });
this.loader = new Loader(this);
} }
//hook preload scene //hook preload scene
@ -297,7 +299,7 @@ export class GameScene extends DirtyScene {
//if SpriteSheetFile (WOKA file) don't display error and give an access for user //if SpriteSheetFile (WOKA file) don't display error and give an access for user
if (this.preloading && !(file instanceof SpriteSheetFile)) { if (this.preloading && !(file instanceof SpriteSheetFile)) {
//remove loader in progress //remove loader in progress
removeLoader(this); this.loader.removeLoader();
//display an error scene //display an error scene
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
@ -331,7 +333,7 @@ export class GameScene extends DirtyScene {
}); });
//this function must stay at the end of preload function //this function must stay at the end of preload function
addLoader(this); this.loader.addLoader();
} }
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
@ -1832,6 +1834,8 @@ ${escapedMessage}
right: camera.scrollX + camera.width, right: camera.scrollX + camera.width,
bottom: camera.scrollY + camera.height, bottom: camera.scrollY + camera.height,
}); });
this.loader.resize();
} }
private getObjectLayerData(objectName: string): ITiledMapObject | undefined { private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
for (const layer of this.mapFile.layers) { for (const layer of this.mapFile.layers) {

View File

@ -4,7 +4,7 @@ import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager";
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import { gameManager } from "../Game/GameManager"; import { gameManager } from "../Game/GameManager";
import { localUserStore } from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import { addLoader } from "../Components/Loader"; import { Loader } from "../Components/Loader";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import { AbstractCharacterScene } from "./AbstractCharacterScene"; import { AbstractCharacterScene } from "./AbstractCharacterScene";
import { areCharacterLayersValid } from "../../Connexion/LocalUser"; import { areCharacterLayersValid } from "../../Connexion/LocalUser";
@ -30,10 +30,13 @@ export class CustomizeScene extends AbstractCharacterScene {
private moveHorizontally: number = 0; private moveHorizontally: number = 0;
private moveVertically: number = 0; private moveVertically: number = 0;
private loader: Loader;
constructor() { constructor() {
super({ super({
key: CustomizeSceneName, key: CustomizeSceneName,
}); });
this.loader = new Loader(this);
} }
preload() { preload() {
@ -55,7 +58,7 @@ export class CustomizeScene extends AbstractCharacterScene {
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;
//this function must stay at the end of preload function //this function must stay at the end of preload function
addLoader(this); this.loader.addLoader();
} }
create() { create() {

View File

@ -4,7 +4,7 @@ import { EnableCameraSceneName } from "./EnableCameraScene";
import { CustomizeSceneName } from "./CustomizeScene"; import { CustomizeSceneName } from "./CustomizeScene";
import { localUserStore } from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager"; import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager";
import { addLoader } from "../Components/Loader"; import { Loader } from "../Components/Loader";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import { AbstractCharacterScene } from "./AbstractCharacterScene"; import { AbstractCharacterScene } from "./AbstractCharacterScene";
import { areCharacterLayersValid } from "../../Connexion/LocalUser"; import { areCharacterLayersValid } from "../../Connexion/LocalUser";
@ -31,11 +31,13 @@ export class SelectCharacterScene extends AbstractCharacterScene {
protected pointerTimer: number = 0; protected pointerTimer: number = 0;
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
private loader: Loader;
constructor() { constructor() {
super({ super({
key: SelectCharacterSceneName, key: SelectCharacterSceneName,
}); });
this.loader = new Loader(this);
} }
preload() { preload() {
@ -49,7 +51,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;
//this function must stay at the end of preload function //this function must stay at the end of preload function
addLoader(this); this.loader.addLoader();
} }
create() { create() {

View File

@ -1,4 +1,4 @@
import { addLoader } from "../Components/Loader"; import { Loader } from "../Components/Loader";
import { gameManager } from "../Game/GameManager"; import { gameManager } from "../Game/GameManager";
import { ResizableScene } from "./ResizableScene"; import { ResizableScene } from "./ResizableScene";
import { EnableCameraSceneName } from "./EnableCameraScene"; import { EnableCameraSceneName } from "./EnableCameraScene";
@ -22,11 +22,13 @@ export class SelectCompanionScene extends ResizableScene {
private currentCompanion = 0; private currentCompanion = 0;
private pointerClicked: boolean = false; private pointerClicked: boolean = false;
private pointerTimer: number = 0; private pointerTimer: number = 0;
private loader: Loader;
constructor() { constructor() {
super({ super({
key: SelectCompanionSceneName, key: SelectCompanionSceneName,
}); });
this.loader = new Loader(this);
} }
preload() { preload() {
@ -35,7 +37,7 @@ export class SelectCompanionScene extends ResizableScene {
}); });
//this function must stay at the end of preload function //this function must stay at the end of preload function
addLoader(this); this.loader.addLoader();
} }
create() { create() {

View File

@ -9,9 +9,7 @@ export interface LayoutManagerAction {
userInputManager: UserInputManager | undefined; userInputManager: UserInputManager | undefined;
} }
function createLayoutManagerAction() { function createLayoutManagerAction() {
const { subscribe, set, update } = writable<LayoutManagerAction[]>([]); const { subscribe, set, update } = writable<LayoutManagerAction[]>([]);
return { return {

View File

@ -141,51 +141,55 @@ class JitsiFactory {
jitsiUrl?: string, jitsiUrl?: string,
jitsiWidth?: number jitsiWidth?: number
): void { ): void {
coWebsiteManager.addCoWebsite(async (cowebsiteDiv) => { coWebsiteManager.addCoWebsite(
// Jitsi meet external API maintains some data in local storage async (cowebsiteDiv) => {
// which is sent via the appData URL parameter when joining a // Jitsi meet external API maintains some data in local storage
// conference. Problem is that this data grows indefinitely. Thus // which is sent via the appData URL parameter when joining a
// after some time the URLs get so huge that loading the iframe // conference. Problem is that this data grows indefinitely. Thus
// becomes slow and eventually breaks completely. Thus lets just // after some time the URLs get so huge that loading the iframe
// clear jitsi local storage before starting a new conference. // becomes slow and eventually breaks completely. Thus lets just
window.localStorage.removeItem("jitsiLocalStorage"); // clear jitsi local storage before starting a new conference.
window.localStorage.removeItem("jitsiLocalStorage");
const domain = jitsiUrl || JITSI_URL; const domain = jitsiUrl || JITSI_URL;
if (domain === undefined) { if (domain === undefined) {
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map."); throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
}
await this.loadJitsiScript(domain);
const options: JitsiOptions = {
roomName: roomName,
jwt: jwt,
width: "100%",
height: "100%",
parentNode: cowebsiteDiv,
configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
};
if (!options.jwt) {
delete options.jwt;
}
return new Promise((resolve, reject) => {
const doResolve = (): void => {
const iframe = cowebsiteDiv.querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
if (iframe === null) {
throw new Error("Could not find Jitsi Iframe");
}
resolve(iframe);
} }
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations. await this.loadJitsiScript(domain);
setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); const options: JitsiOptions = {
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); roomName: roomName,
}); jwt: jwt,
}, jitsiWidth, 0); width: "100%",
height: "100%",
parentNode: cowebsiteDiv,
configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
};
if (!options.jwt) {
delete options.jwt;
}
return new Promise((resolve, reject) => {
const doResolve = (): void => {
const iframe = cowebsiteDiv.querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
if (iframe === null) {
throw new Error("Could not find Jitsi Iframe");
}
resolve(iframe);
};
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
});
},
jitsiWidth,
0
);
} }
public stop() { public stop() {