diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 1e846185..72b17109 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -29,6 +29,8 @@ import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte"; import {warningContainerStore} from "../Stores/MenuStore"; import WarningContainer from "./WarningContainer/WarningContainer.svelte"; + import {layoutManagerVisibilityStore} from "../Stores/LayoutManagerStore"; + import LayoutManager from "./LayoutManager/LayoutManager.svelte"; export let game: Game; @@ -65,6 +67,11 @@ {/if} + {#if $layoutManagerVisibilityStore} +
+ +
+ {/if} {#if $gameOverlayVisibilityStore}
diff --git a/front/src/Components/LayoutManager/LayoutManager.svelte b/front/src/Components/LayoutManager/LayoutManager.svelte new file mode 100644 index 00000000..ef90a4e3 --- /dev/null +++ b/front/src/Components/LayoutManager/LayoutManager.svelte @@ -0,0 +1,78 @@ + + + +
+ {#each $layoutManagerActionStore as action} +
onClick(action.callback)}> +

{action.message}

+
+ {/each} +
+ + + \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 31acb83a..4ba569d8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -21,7 +21,6 @@ import { AUDIO_VOLUME_PROPERTY, Box, JITSI_MESSAGE_PROPERTIES, - layoutManager, ON_ACTION_TRIGGER_BUTTON, TRIGGER_JITSI_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES, @@ -89,6 +88,8 @@ import { SharedVariablesManager } from "./SharedVariablesManager"; import { playersStore } from "../../Stores/PlayersStore"; import { chatVisibilityStore } from "../../Stores/ChatStore"; import { userIsAdminStore } from "../../Stores/GameStore"; +import { layoutManagerActionStore, layoutManagerVisibilityStore } from "../../Stores/LayoutManagerStore"; +import { get } from "svelte/store"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -793,7 +794,7 @@ export class GameScene extends DirtyScene { }); this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => { if (newValue === undefined) { - layoutManager.removeActionButton("openWebsite", this.userInputManager); + layoutManagerVisibilityStore.set(false); coWebsiteManager.closeCoWebsite(); } else { const openWebsiteFunction = () => { @@ -803,7 +804,7 @@ export class GameScene extends DirtyScene { allProps.get("openWebsiteAllowApi") as boolean | undefined, allProps.get("openWebsitePolicy") as string | undefined ); - layoutManager.removeActionButton("openWebsite", this.userInputManager); + layoutManagerVisibilityStore.set(false); }; const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES); @@ -812,14 +813,13 @@ export class GameScene extends DirtyScene { if (message === undefined) { message = "Press SPACE or touch here to open web site"; } - layoutManager.addActionButton( - "openWebsite", - message.toString(), - () => { - openWebsiteFunction(); - }, - this.userInputManager - ); + layoutManagerActionStore.addAction({ + type: "openWebsite", + message: message, + callback: () => openWebsiteFunction(), + userInputManager: this.userInputManager, + }); + layoutManagerVisibilityStore.set(true); } else { openWebsiteFunction(); } @@ -827,7 +827,7 @@ export class GameScene extends DirtyScene { }); this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => { if (newValue === undefined) { - layoutManager.removeActionButton("jitsiRoom", this.userInputManager); + layoutManagerVisibilityStore.set(false); this.stopJitsi(); } else { const openJitsiRoomFunction = () => { @@ -840,7 +840,7 @@ export class GameScene extends DirtyScene { } else { this.startJitsi(roomName, undefined); } - layoutManager.removeActionButton("jitsiRoom", this.userInputManager); + layoutManagerVisibilityStore.set(false); }; const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES); @@ -849,14 +849,13 @@ export class GameScene extends DirtyScene { if (message === undefined) { message = "Press SPACE or touch here to enter Jitsi Meet room"; } - layoutManager.addActionButton( - "jitsiRoom", - message.toString(), - () => { - openJitsiRoomFunction(); - }, - this.userInputManager - ); + layoutManagerActionStore.addAction({ + type: "jitsiRoom", + message: message, + callback: () => openJitsiRoomFunction(), + userInputManager: this.userInputManager, + }); + layoutManagerVisibilityStore.set(true); } else { openJitsiRoomFunction(); } @@ -1153,7 +1152,7 @@ ${escapedMessage} let targetRoom: Room; try { targetRoom = await Room.createRoom(roomUrl); - } catch (e: unknown) { + } catch (e) { console.error('Error while fetching new room "' + roomUrl.toString() + '"', e); this.mapTransitioning = false; return; @@ -1279,7 +1278,7 @@ ${escapedMessage} try { const room = await Room.createRoom(exitRoomPath); return gameManager.loadMap(room, this.scene); - } catch (e: unknown) { + } catch (e) { console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e); } } diff --git a/front/src/Stores/LayoutManagerStore.ts b/front/src/Stores/LayoutManagerStore.ts new file mode 100644 index 00000000..41fd283d --- /dev/null +++ b/front/src/Stores/LayoutManagerStore.ts @@ -0,0 +1,53 @@ +import { writable } from "svelte/store"; +import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; + +export interface LayoutManagerAction { + type: string; + message: string | number | boolean | undefined; + callback: () => void; + userInputManager: UserInputManager | undefined; +} + +export const layoutManagerVisibilityStore = writable(false); + +function createLayoutManagerAction() { + const { subscribe, set, update } = writable([]); + + return { + subscribe, + addAction: (newAction: LayoutManagerAction): void => { + update((list: LayoutManagerAction[]) => { + let found = false; + for (const actions of list) { + if (actions.type === newAction.type && actions.message === newAction.message) { + found = true; + } + } + + if (!found) { + list.push(newAction); + } + + return list; + }); + }, + removeAction: (oldAction: LayoutManagerAction): void => { + update((list: LayoutManagerAction[]) => { + const index = list.findIndex( + (actions) => actions.type === oldAction.type && actions.message === oldAction.message + ); + + if (index !== -1) { + list.splice(index, 1); + } + + return list; + }); + }, + clearActions: (): void => { + set([]); + }, + }; +} + +export const layoutManagerActionStore = createLayoutManagerAction(); diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 47864139..0d9a4ba9 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -1,6 +1,3 @@ -import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { HtmlUtils } from "./HtmlUtils"; - export enum LayoutMode { // All videos are displayed on the right side of the screen. If there is a screen sharing, it is displayed in the middle. Presentation = "Presentation", @@ -27,85 +24,3 @@ export const AUDIO_VOLUME_PROPERTY = "audioVolume"; export const AUDIO_LOOP_PROPERTY = "audioLoop"; export type Box = { xStart: number; yStart: number; xEnd: number; yEnd: number }; - -class LayoutManager { - private actionButtonTrigger: Map = new Map(); - private actionButtonInformation: Map = new Map(); - - public addActionButton(id: string, text: string, callBack: Function, userInputManager: UserInputManager) { - //delete previous element - this.removeActionButton(id, userInputManager); - - //create div and text html component - const p = document.createElement("p"); - p.classList.add("action-body"); - p.innerText = text; - - const div = document.createElement("div"); - div.classList.add("action"); - div.id = id; - div.appendChild(p); - - this.actionButtonInformation.set(id, div); - - const mainContainer = HtmlUtils.getElementByIdOrFail("main-container"); - mainContainer.appendChild(div); - - //add trigger action - div.onpointerdown = () => callBack(); - this.actionButtonTrigger.set(id, callBack); - userInputManager.addSpaceEventListner(callBack); - } - - public removeActionButton(id: string, userInputManager?: UserInputManager) { - //delete previous element - const previousDiv = this.actionButtonInformation.get(id); - if (previousDiv) { - previousDiv.remove(); - this.actionButtonInformation.delete(id); - } - const previousEventCallback = this.actionButtonTrigger.get(id); - if (previousEventCallback && userInputManager) { - userInputManager.removeSpaceEventListner(previousEventCallback); - } - } - - public addInformation(id: string, text: string, callBack?: Function, userInputManager?: UserInputManager) { - //delete previous element - for (const [key, value] of this.actionButtonInformation) { - this.removeActionButton(key, userInputManager); - } - - //create div and text html component - const p = document.createElement("p"); - p.classList.add("action-body"); - p.innerText = text; - - const div = document.createElement("div"); - div.classList.add("action"); - div.classList.add(id); - div.id = id; - div.appendChild(p); - - this.actionButtonInformation.set(id, div); - - const mainContainer = HtmlUtils.getElementByIdOrFail("main-container"); - mainContainer.appendChild(div); - //add trigger action - if (callBack) { - div.onpointerdown = () => { - callBack(); - this.removeActionButton(id, userInputManager); - }; - } - - //remove it after 10 sec - setTimeout(() => { - this.removeActionButton(id, userInputManager); - }, 10000); - } -} - -const layoutManager = new LayoutManager(); - -export { layoutManager }; diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index d7e9f514..2f8c637a 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,4 +1,3 @@ -import { layoutManager } from "./LayoutManager"; import { HtmlUtils } from "./HtmlUtils"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; import { localStreamStore } from "../Stores/MediaStore"; @@ -10,6 +9,8 @@ export type StopScreenSharingCallback = (media: MediaStream) => void; import { cowebsiteCloseButtonId } from "./CoWebsiteManager"; import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility"; +import { layoutManagerActionStore, layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore"; +import { get } from "svelte/store"; export class MediaManager { startScreenSharingCallBacks: Set = new Set(); @@ -23,14 +24,16 @@ export class MediaManager { localStreamStore.subscribe((result) => { if (result.type === "error") { console.error(result.error); - layoutManager.addInformation( - "warning", - "Camera access denied. Click here and check your browser permissions.", - () => { + layoutManagerActionStore.addAction({ + type: "warning", + message: "Camera access denied. Click here and check your browser permissions.", + callback: () => { helpCameraSettingsVisibleStore.set(true); + layoutManagerVisibilityStore.set(false); }, - this.userInputManager - ); + userInputManager: this.userInputManager, + }); + layoutManagerVisibilityStore.set(true); return; } }); @@ -38,14 +41,16 @@ export class MediaManager { screenSharingLocalStreamStore.subscribe((result) => { if (result.type === "error") { console.error(result.error); - layoutManager.addInformation( - "warning", - "Screen sharing denied. Click here and check your browser permissions.", - () => { + layoutManagerActionStore.addAction({ + type: "warning", + message: "Screen sharing denied. Click here and check your browser permissions.", + callback: () => { helpCameraSettingsVisibleStore.set(true); + layoutManagerVisibilityStore.set(false); }, - this.userInputManager - ); + userInputManager: this.userInputManager, + }); + layoutManagerVisibilityStore.set(true); return; } });