diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index a90d9397..fcde792a 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -62,18 +62,13 @@ jobs: working-directory: "front" - name: "Pretty" - run: yarn run pretty + run: yarn run pretty-check working-directory: "front" - name: "Jasmine" run: yarn test 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: name: "Continuous Integration Pusher" diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index 4360d5e1..a7b3ecfb 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -30,7 +30,16 @@ jobs: run: docker-compose up -d - 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" run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe diff --git a/back/src/Services/Logger.ts b/back/src/Services/Logger.ts new file mode 100644 index 00000000..e69de29b diff --git a/front/src/Administration/AnalyticsClient.ts b/front/src/Administration/AnalyticsClient.ts index ee85e8f4..fb2b604b 100644 --- a/front/src/Administration/AnalyticsClient.ts +++ b/front/src/Administration/AnalyticsClient.ts @@ -4,7 +4,7 @@ declare let window: any; class AnalyticsClient { // eslint-disable-next-line @typescript-eslint/no-explicit-any - private posthogPromise: Promise|undefined; + private posthogPromise: Promise | undefined; constructor() { if (POSTHOG_API_KEY && POSTHOG_URL) { @@ -18,74 +18,64 @@ class AnalyticsClient { } identifyUser(uuid: string, email: string | null) { - this.posthogPromise - ?.then((posthog) => { - posthog.identify(uuid, { uuid, email, wa: true }); - }); + this.posthogPromise?.then((posthog) => { + posthog.identify(uuid, { uuid, email, wa: true }); + }); } loggedWithSso() { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-logged-sso"); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-logged-sso"); + }); } loggedWithToken() { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-logged-token"); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-logged-token"); + }); } enteredRoom(roomId: string, roomGroup: string | null) { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("$pageView", { roomId, roomGroup }); - posthog.capture("enteredRoom"); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("$pageView", { roomId, roomGroup }); + posthog.capture("enteredRoom"); + }); } openedMenu() { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-opened-menu"); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-opened-menu"); + }); } launchEmote(emote: string) { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-emote-launch", { emote }); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-emote-launch", { emote }); + }); } enteredJitsi(roomName: string, roomId: string) { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-entered-jitsi", { roomName, roomId }); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-entered-jitsi", { roomName, roomId }); + }); } validationName() { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-name-validation"); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-name-validation"); + }); } validationWoka(scene: string) { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-woka-validation", { scene }); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-woka-validation", { scene }); + }); } validationVideo() { - this.posthogPromise - ?.then((posthog) => { - posthog.capture("wa-video-validation"); - }); + this.posthogPromise?.then((posthog) => { + posthog.capture("wa-video-validation"); + }); } } export const analyticsClient = new AnalyticsClient(); diff --git a/front/src/Api/Events/CloseCoWebsiteEvent.ts b/front/src/Api/Events/CloseCoWebsiteEvent.ts index 94167d5e..4dc1e51d 100644 --- a/front/src/Api/Events/CloseCoWebsiteEvent.ts +++ b/front/src/Api/Events/CloseCoWebsiteEvent.ts @@ -2,7 +2,7 @@ import * as tg from "generic-type-guard"; export const isCloseCoWebsite = new tg.IsInterface() .withProperties({ - id: tg.isOptional(tg.isString) + id: tg.isOptional(tg.isString), }) .get(); diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index abb492c5..081008c4 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -24,9 +24,7 @@ import type { EmbeddedWebsite } from "../iframe/Room/EmbeddedWebsite"; import { isCreateEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent"; import type { LoadTilesetEvent } from "./LoadTilesetEvent"; import { isLoadTilesetEvent } from "./LoadTilesetEvent"; -import type { - MessageReferenceEvent, -} from "./ui/TriggerActionMessageEvent"; +import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent"; @@ -117,19 +115,19 @@ export const iframeQueryMapTypeGuards = { }, openCoWebsite: { query: isOpenCoWebsiteEvent, - answer: isCoWebsite + answer: isCoWebsite, }, getCoWebsites: { query: tg.isUndefined, - answer: tg.isArray(isCoWebsite) + answer: tg.isArray(isCoWebsite), }, closeCoWebsite: { query: tg.isString, - answer: tg.isUndefined + answer: tg.isUndefined, }, closeCoWebsites: { query: tg.isUndefined, - answer: tg.isUndefined + answer: tg.isUndefined, }, triggerActionMessage: { query: isTriggerActionMessageEvent, diff --git a/front/src/Api/Events/OpenCoWebsiteEvent.ts b/front/src/Api/Events/OpenCoWebsiteEvent.ts index 9c02b7a3..514fd110 100644 --- a/front/src/Api/Events/OpenCoWebsiteEvent.ts +++ b/front/src/Api/Events/OpenCoWebsiteEvent.ts @@ -5,7 +5,7 @@ export const isOpenCoWebsiteEvent = new tg.IsInterface() url: tg.isString, allowApi: tg.isOptional(tg.isBoolean), allowPolicy: tg.isOptional(tg.isString), - position: tg.isOptional(tg.isNumber) + position: tg.isOptional(tg.isNumber), }) .get(); diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 871ec3b9..f30ce80c 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -49,7 +49,7 @@ class IframeListener { public readonly openTabStream = this._openTabStream.asObservable(); private readonly _loadPageStream: Subject = new Subject(); - public readonly loadPageStream = this._loadPageStream.asObservable() + public readonly loadPageStream = this._loadPageStream.asObservable(); private readonly _disablePlayerControlStream: Subject = new Subject(); public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index 5acfa2a5..206961bf 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -57,7 +57,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution { const result = await queryWorkadventure({ type: "getCoWebsites", - data: undefined + data: undefined, }); return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position)); } diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index ffd91d39..a8b172e6 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -84,7 +84,8 @@ class ConnectionManager { if (token) { this.authToken = token; localUserStore.setAuthToken(token); - //token was saved, clear url + + //clean token of url urlParams.delete("token"); } @@ -95,8 +96,6 @@ class ConnectionManager { } urlManager.pushRoomIdToUrl(this._currentRoom); } else if (connexionType === GameConnexionTypes.jwt) { - const urlParams = new URLSearchParams(window.location.search); - if (!token) { const code = urlParams.get("code"); const state = urlParams.get("state"); @@ -139,7 +138,7 @@ class ConnectionManager { "//" + window.location.host + roomUrl + - window.location.search + + urlParams.toString() + //use urlParams because the token param must be deleted window.location.hash ) ); @@ -169,7 +168,7 @@ class ConnectionManager { "//" + window.location.host + window.location.pathname + - window.location.search + + urlParams.toString() + //use urlParams because the token param must be deleted window.location.hash; } @@ -218,8 +217,6 @@ class ConnectionManager { 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(); return Promise.resolve(this._currentRoom); } diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index a95618ba..ac57c3c7 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -122,7 +122,7 @@ class LocalUserStore { setLastRoomUrl(roomUrl: string): void { localStorage.setItem(lastRoomUrl, roomUrl.toString()); - if ('caches' in window) { + if ("caches" in window) { caches.open(cacheAPIIndex).then((cache) => { const stringResponse = new Response(JSON.stringify({ roomUrl })); cache.put(`/${lastRoomUrl}`, stringResponse); @@ -135,7 +135,7 @@ class LocalUserStore { ); } getLastRoomUrlCacheApi(): Promise { - if (!('caches' in window)) { + if (!("caches" in window)) { return Promise.resolve(undefined); } return caches.open(cacheAPIIndex).then((cache) => { diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 2f408126..f206d318 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -107,7 +107,8 @@ export class Room { this._mapUrl = data.mapUrl; this._textures = data.textures; 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._contactPage = data.contactPage || CONTACT_URL; return new MapDetail(data.mapUrl, data.textures); diff --git a/front/src/Phaser/Components/Loader.ts b/front/src/Phaser/Components/Loader.ts index d126aded..e013e758 100644 --- a/front/src/Phaser/Components/Loader.ts +++ b/front/src/Phaser/Components/Loader.ts @@ -6,73 +6,124 @@ const TextName: string = "Loading..."; const LogoResource: string = "static/images/logo.png"; const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 }; -export const addLoader = (scene: Phaser.Scene): void => { - // If there is nothing to load, do not display the loader. - 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 loadingBarHeight: number = 16; +const padding: number = 5; - const promiseLoadLogoTexture = new Promise((res) => { - if (scene.load.textureManager.exists(LogoNameIndex)) { - return res( - scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex) - ); - } else { - //add loading if logo image is not ready - loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName); +export class Loader { + private progressContainer!: Phaser.GameObjects.Graphics; + private progress!: Phaser.GameObjects.Graphics; + private progressAmount: number = 0; + private logo: Phaser.GameObjects.Image | undefined; + private loadingText: Phaser.GameObjects.Text | null = null; + + 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}`, () => { - if (loadingText) { - loadingText.destroy(); + + const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3); + + const promiseLoadLogoTexture = new Promise((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( - scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex) - ); + this.scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame); + 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(); - const progress = scene.add.graphics(); - 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 - ); + this.progressContainer = this.scene.add.graphics(); + this.progress = this.scene.add.graphics(); + this.progressContainer.fillStyle(0x444444, 0.8); - scene.load.on("progress", (value: number) => { - progress.clear(); - progress.fillStyle(0xbbbbbb, 1); - progress.fillRect( - (scene.game.renderer.width - loadingBarWidth) / 2, - scene.game.renderer.height / 2 + 50, - loadingBarWidth * value, + this.resize(); + + this.scene.load.on("progress", (value: number) => { + this.progressAmount = value; + this.drawProgress(); + }); + 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 ); - }); - 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); } -}; +} diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 7df18fcd..1211a52d 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -128,6 +128,10 @@ export abstract class Character extends Container { } public addTextures(textures: string[], frame?: string | number): void { + if (textures.length < 1) { + throw new TextureError("no texture given"); + } + for (const texture of textures) { if (this.scene && !this.scene.textures.exists(texture)) { throw new TextureError("texture not found"); diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 92954bfb..edaeb5b8 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -69,13 +69,11 @@ export const lazyLoadPlayerCharacterTextures = ( } //If the loading fail, we render the default model instead. - return returnPromise - .then((keys) => - keys.map((key) => { - return typeof key !== "string" ? key.name : key; - }) - ) - .catch(() => lazyLoadPlayerCharacterTextures(loadPlugin, ["color_22", "eyes_23"])); + return returnPromise.then((keys) => + keys.map((key) => { + return typeof key !== "string" ? key.name : key; + }) + ); }; export const getRessourceDescriptor = ( diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 17740874..8fe0e329 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -12,8 +12,7 @@ export type PropertyChangeCallback = ( export type layerChangeCallback = ( layersChangedByAction: Array, - allLayersOnNewPosition: Array, - + allLayersOnNewPosition: Array ) => void; /** @@ -81,7 +80,7 @@ export class GameMap { } private getLayersByKey(key: number): Array { - 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 leaveLayers = new Set(layersByOldKey); - enterLayers.forEach(layer => { + enterLayers.forEach((layer) => { if (leaveLayers.has(layer)) { leaveLayers.delete(layer); enterLayers.delete(layer); } }); - if (enterLayers.size > 0) { const layerArray = Array.from(enterLayers); for (const callback of this.enterLayerCallbacks) { diff --git a/front/src/Phaser/Game/GameMapProperties.ts b/front/src/Phaser/Game/GameMapProperties.ts index d13f828a..65ce6ab8 100644 --- a/front/src/Phaser/Game/GameMapProperties.ts +++ b/front/src/Phaser/Game/GameMapProperties.ts @@ -1,37 +1,37 @@ export enum GameMapProperties { - ALLOW_API = 'allowApi', - AUDIO_LOOP = 'audioLoop', - AUDIO_VOLUME = 'audioVolume', - COLLIDES = 'collides', - DEFAULT = 'default', - EXIT_URL = 'exitUrl', - EXIT_SCENE_URL = 'exitSceneUrl', - FONT_FAMILY = 'font-family', - JITSI_ADMIN_ROOM_TAG = 'jitsiRoomAdminTag', - JITSI_CONFIG = 'jitsiConfig', - JITSI_INTERFACE_CONFIG = 'jitsiInterfaceConfig', - JITSI_ROOM = 'jitsiRoom', - JITSI_TRIGGER = 'jitsiTrigger', - JITSI_TRIGGER_MESSAGE = 'jitsiTriggerMessage', - JITSI_URL = 'jitsiUrl', - JITSI_WIDTH = 'jitsiWidth', - NAME = 'name', - OPEN_TAB = 'openTab', - OPEN_WEBSITE = 'openWebsite', - OPEN_WEBSITE_ALLOW_API = 'openWebsiteAllowApi', - OPEN_WEBSITE_POLICY = 'openWebsitePolicy', - OPEN_WEBSITE_WIDTH = 'openWebsiteWidth', - OPEN_WEBSITE_POSITION = 'openWebsitePosition', - OPEN_WEBSITE_TRIGGER = 'openWebsiteTrigger', - OPEN_WEBSITE_TRIGGER_MESSAGE = 'openWebsiteTriggerMessage', - PLAY_AUDIO = 'playAudio', - PLAY_AUDIO_LOOP = 'playAudioLoop', - READABLE_BY = 'readableBy', - SCRIPT = 'script', - SILENT = 'silent', - START = 'start', - START_LAYER = 'startLayer', - URL = 'url', - WRITABLE_BY = 'writableBy', - ZONE = 'zone', + ALLOW_API = "allowApi", + AUDIO_LOOP = "audioLoop", + AUDIO_VOLUME = "audioVolume", + COLLIDES = "collides", + DEFAULT = "default", + EXIT_URL = "exitUrl", + EXIT_SCENE_URL = "exitSceneUrl", + FONT_FAMILY = "font-family", + JITSI_ADMIN_ROOM_TAG = "jitsiRoomAdminTag", + JITSI_CONFIG = "jitsiConfig", + JITSI_INTERFACE_CONFIG = "jitsiInterfaceConfig", + JITSI_ROOM = "jitsiRoom", + JITSI_TRIGGER = "jitsiTrigger", + JITSI_TRIGGER_MESSAGE = "jitsiTriggerMessage", + JITSI_URL = "jitsiUrl", + JITSI_WIDTH = "jitsiWidth", + NAME = "name", + OPEN_TAB = "openTab", + OPEN_WEBSITE = "openWebsite", + OPEN_WEBSITE_ALLOW_API = "openWebsiteAllowApi", + OPEN_WEBSITE_POLICY = "openWebsitePolicy", + OPEN_WEBSITE_WIDTH = "openWebsiteWidth", + OPEN_WEBSITE_POSITION = "openWebsitePosition", + OPEN_WEBSITE_TRIGGER = "openWebsiteTrigger", + OPEN_WEBSITE_TRIGGER_MESSAGE = "openWebsiteTriggerMessage", + PLAY_AUDIO = "playAudio", + PLAY_AUDIO_LOOP = "playAudioLoop", + READABLE_BY = "readableBy", + SCRIPT = "script", + SILENT = "silent", + START = "start", + START_LAYER = "startLayer", + URL = "url", + WRITABLE_BY = "writableBy", + ZONE = "zone", } diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts index 5a033d5c..dad55768 100644 --- a/front/src/Phaser/Game/GameMapPropertiesListener.ts +++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts @@ -4,10 +4,8 @@ import { scriptUtils } from "../../Api/ScriptUtils"; import type { CoWebsite } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; -import { get } from 'svelte/store'; -import { - ON_ACTION_TRIGGER_BUTTON, -} from "../../WebRtc/LayoutManager"; +import { get } from "svelte/store"; +import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager"; import type { ITiledMapLayer } from "../Map/ITiledMap"; import { GameMapProperties } from "./GameMapProperties"; @@ -18,8 +16,8 @@ enum OpenCoWebsiteState { } interface OpenCoWebsite { - coWebsite: CoWebsite | undefined, - state: OpenCoWebsiteState + coWebsite: CoWebsite | undefined; + state: OpenCoWebsiteState; } export class GameMapPropertiesListener { @@ -29,7 +27,7 @@ export class GameMapPropertiesListener { constructor(private scene: GameScene, private gameMap: GameMap) {} register() { - this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldvalue, allProps) => { + this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => { if (newValue === undefined) { 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) => { const handler = () => { - newLayers.forEach(layer => { + newLayers.forEach((layer) => { if (!layer.properties) { return; } @@ -69,8 +67,8 @@ export class GameMapPropertiesListener { let websiteTriggerProperty: string | undefined; let websiteTriggerMessageProperty: string | undefined; - layer.properties.forEach(property => { - switch(property.name) { + layer.properties.forEach((property) => { + switch (property.name) { case GameMapProperties.OPEN_WEBSITE: openWebsiteProperty = property.value as string | undefined; break; @@ -111,26 +109,28 @@ export class GameMapPropertiesListener { }); const openWebsiteFunction = () => { - coWebsiteManager.loadCoWebsite( - openWebsiteProperty as string, - this.scene.MapUrlFile, - allowApiProperty, - websitePolicyProperty, - websiteWidthProperty, - websitePositionProperty, - ).then(coWebsite => { - const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer); - if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) { - coWebsiteManager.closeCoWebsite(coWebsite); - this.coWebsitesOpenByLayer.delete(layer); - this.coWebsitesActionTriggerByLayer.delete(layer); - } else { - this.coWebsitesOpenByLayer.set(layer, { - coWebsite, - state: OpenCoWebsiteState.OPENED - }); - } - }); + coWebsiteManager + .loadCoWebsite( + openWebsiteProperty as string, + this.scene.MapUrlFile, + allowApiProperty, + websitePolicyProperty, + websiteWidthProperty, + websitePositionProperty + ) + .then((coWebsite) => { + const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer); + if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) { + coWebsiteManager.closeCoWebsite(coWebsite); + this.coWebsitesOpenByLayer.delete(layer); + this.coWebsitesActionTriggerByLayer.delete(layer); + } else { + this.coWebsitesOpenByLayer.set(layer, { + coWebsite, + state: OpenCoWebsiteState.OPENED, + }); + } + }); layoutManagerActionStore.removeAction(actionUuid); }; @@ -161,7 +161,7 @@ export class GameMapPropertiesListener { // Close opened co-websites on leave the layer who contain the property. this.gameMap.onLeaveLayer((oldLayers) => { const handler = () => { - oldLayers.forEach(layer => { + oldLayers.forEach((layer) => { if (!layer.properties) { return; } @@ -169,8 +169,8 @@ export class GameMapPropertiesListener { let openWebsiteProperty: string | undefined; let websiteTriggerProperty: string | undefined; - layer.properties.forEach(property => { - switch(property.name) { + layer.properties.forEach((property) => { + switch (property.name) { case GameMapProperties.OPEN_WEBSITE: openWebsiteProperty = property.value as string | undefined; break; @@ -192,11 +192,6 @@ export class GameMapPropertiesListener { if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) { coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE; - return; - } - - if (coWebsiteOpen.state !== OpenCoWebsiteState.OPENED) { - return; } if (coWebsiteOpen.coWebsite !== undefined) { @@ -216,9 +211,10 @@ export class GameMapPropertiesListener { return; } - const action = actionStore && actionStore.length > 0 ? - actionStore.find(action => action.uuid === actionTriggerUuid) : undefined; - + const action = + actionStore && actionStore.length > 0 + ? actionStore.find((action) => action.uuid === actionTriggerUuid) + : undefined; if (action) { layoutManagerActionStore.removeAction(actionTriggerUuid); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 81cf3676..d9bb8186 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -28,7 +28,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore"; import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import { mediaManager } from "../../WebRtc/MediaManager"; import { SimplePeer } from "../../WebRtc/SimplePeer"; -import { addLoader, removeLoader } from "../Components/Loader"; +import { Loader } from "../Components/Loader"; import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import { RemotePlayer } from "../Entity/RemotePlayer"; import type { ActionableItem } from "../Items/ActionableItem"; @@ -203,6 +203,7 @@ export class GameScene extends DirtyScene { private sharedVariablesManager!: SharedVariablesManager; private objectsByType = new Map(); private embeddedWebsiteManager!: EmbeddedWebsiteManager; + private loader: Loader; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ @@ -221,6 +222,7 @@ export class GameScene extends DirtyScene { this.connectionAnswerPromise = new Promise((resolve, reject): void => { this.connectionAnswerPromiseResolve = resolve; }); + this.loader = new Loader(this); } //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 (this.preloading && !(file instanceof SpriteSheetFile)) { //remove loader in progress - removeLoader(this); + this.loader.removeLoader(); //display an error scene this.scene.start(ErrorSceneName, { @@ -331,7 +333,7 @@ export class GameScene extends DirtyScene { }); //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. @@ -1832,6 +1834,8 @@ ${escapedMessage} right: camera.scrollX + camera.width, bottom: camera.scrollY + camera.height, }); + + this.loader.resize(); } private getObjectLayerData(objectName: string): ITiledMapObject | undefined { for (const layer of this.mapFile.layers) { diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 8cb82ba6..d5629c88 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -4,7 +4,7 @@ import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager"; import Sprite = Phaser.GameObjects.Sprite; import { gameManager } from "../Game/GameManager"; import { localUserStore } from "../../Connexion/LocalUserStore"; -import { addLoader } from "../Components/Loader"; +import { Loader } from "../Components/Loader"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import { AbstractCharacterScene } from "./AbstractCharacterScene"; import { areCharacterLayersValid } from "../../Connexion/LocalUser"; @@ -30,10 +30,13 @@ export class CustomizeScene extends AbstractCharacterScene { private moveHorizontally: number = 0; private moveVertically: number = 0; + private loader: Loader; + constructor() { super({ key: CustomizeSceneName, }); + this.loader = new Loader(this); } preload() { @@ -55,7 +58,7 @@ export class CustomizeScene extends AbstractCharacterScene { this.lazyloadingAttempt = false; //this function must stay at the end of preload function - addLoader(this); + this.loader.addLoader(); } create() { diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 1f1eb3ce..64fa9791 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -4,7 +4,7 @@ import { EnableCameraSceneName } from "./EnableCameraScene"; import { CustomizeSceneName } from "./CustomizeScene"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager"; -import { addLoader } from "../Components/Loader"; +import { Loader } from "../Components/Loader"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import { AbstractCharacterScene } from "./AbstractCharacterScene"; import { areCharacterLayersValid } from "../../Connexion/LocalUser"; @@ -31,11 +31,13 @@ export class SelectCharacterScene extends AbstractCharacterScene { protected pointerTimer: number = 0; protected lazyloadingAttempt = true; //permit to update texture loaded after renderer + private loader: Loader; constructor() { super({ key: SelectCharacterSceneName, }); + this.loader = new Loader(this); } preload() { @@ -49,7 +51,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.lazyloadingAttempt = false; //this function must stay at the end of preload function - addLoader(this); + this.loader.addLoader(); } create() { diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 46ad6361..615ac4b3 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -1,4 +1,4 @@ -import { addLoader } from "../Components/Loader"; +import { Loader } from "../Components/Loader"; import { gameManager } from "../Game/GameManager"; import { ResizableScene } from "./ResizableScene"; import { EnableCameraSceneName } from "./EnableCameraScene"; @@ -22,11 +22,13 @@ export class SelectCompanionScene extends ResizableScene { private currentCompanion = 0; private pointerClicked: boolean = false; private pointerTimer: number = 0; + private loader: Loader; constructor() { super({ key: SelectCompanionSceneName, }); + this.loader = new Loader(this); } preload() { @@ -35,7 +37,7 @@ export class SelectCompanionScene extends ResizableScene { }); //this function must stay at the end of preload function - addLoader(this); + this.loader.addLoader(); } create() { diff --git a/front/src/Stores/LayoutManagerStore.ts b/front/src/Stores/LayoutManagerStore.ts index 063d45a7..e92cd3c4 100644 --- a/front/src/Stores/LayoutManagerStore.ts +++ b/front/src/Stores/LayoutManagerStore.ts @@ -9,9 +9,7 @@ export interface LayoutManagerAction { userInputManager: UserInputManager | undefined; } - function createLayoutManagerAction() { - const { subscribe, set, update } = writable([]); return { diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index df647f55..0f205f47 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -141,51 +141,55 @@ class JitsiFactory { jitsiUrl?: string, jitsiWidth?: number ): void { - coWebsiteManager.addCoWebsite(async (cowebsiteDiv) => { - // Jitsi meet external API maintains some data in local storage - // which is sent via the appData URL parameter when joining a - // conference. Problem is that this data grows indefinitely. Thus - // after some time the URLs get so huge that loading the iframe - // becomes slow and eventually breaks completely. Thus lets just - // clear jitsi local storage before starting a new conference. - window.localStorage.removeItem("jitsiLocalStorage"); + coWebsiteManager.addCoWebsite( + async (cowebsiteDiv) => { + // Jitsi meet external API maintains some data in local storage + // which is sent via the appData URL parameter when joining a + // conference. Problem is that this data grows indefinitely. Thus + // after some time the URLs get so huge that loading the iframe + // becomes slow and eventually breaks completely. Thus lets just + // clear jitsi local storage before starting a new conference. + window.localStorage.removeItem("jitsiLocalStorage"); - const domain = jitsiUrl || JITSI_URL; - if (domain === undefined) { - 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('[id*="jitsi" i]'); - if (iframe === null) { - throw new Error("Could not find Jitsi Iframe"); - } - resolve(iframe); + const domain = jitsiUrl || JITSI_URL; + if (domain === undefined) { + throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map."); } - 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); + await this.loadJitsiScript(domain); - this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); - this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); - }); - }, jitsiWidth, 0); + 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('[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() {