import { LoginScene, LoginSceneName } from "../Login/LoginScene"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene"; import { SelectCompanionScene, SelectCompanionSceneName } from "../Login/SelectCompanionScene"; import { gameManager } from "../Game/GameManager"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { gameReportKey, gameReportRessource, ReportMenu } from "./ReportMenu"; import { connectionManager } from "../../Connexion/ConnectionManager"; import { GameConnexionTypes } from "../../Url/UrlManager"; import { menuIconVisible } from "../../Stores/MenuStore"; import { videoConstraintStore } from "../../Stores/MediaStore"; import { showReportScreenStore } from "../../Stores/ShowReportScreenStore"; import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import { iframeListener } from "../../Api/IframeListener"; import { Subscription } from "rxjs"; import { registerMenuCommandStream } from "../../Api/Events/ui/MenuItemRegisterEvent"; import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem"; import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore"; import { get } from "svelte/store"; import { playersStore } from "../../Stores/PlayersStore"; import { mediaManager } from "../../WebRtc/MediaManager"; import { chatVisibilityStore } from "../../Stores/ChatStore"; import { ADMIN_URL } from "../../Enum/EnvironmentVariable"; export const MenuSceneName = "MenuScene"; const gameMenuKey = "gameMenu"; const gameMenuIconKey = "gameMenuIcon"; const gameSettingsMenuKey = "gameSettingsMenu"; const gameShare = "gameShare"; const closedSideMenuX = -1000; const openedSideMenuX = 0; /** * The scene that manages the game menu, rendered using a DOM element. */ export class MenuScene extends Phaser.Scene { private menuElement!: Phaser.GameObjects.DOMElement; private gameQualityMenuElement!: Phaser.GameObjects.DOMElement; private gameShareElement!: Phaser.GameObjects.DOMElement; private gameReportElement!: ReportMenu; private sideMenuOpened = false; private settingsMenuOpened = false; private gameShareOpened = false; private gameQualityValue: number; private videoQualityValue: number; private menuButton!: Phaser.GameObjects.DOMElement; private subscriptions = new Subscription(); constructor() { super({ key: MenuSceneName }); this.gameQualityValue = localUserStore.getGameQualityValue(); this.videoQualityValue = localUserStore.getVideoQualityValue(); this.subscriptions.add( registerMenuCommandStream.subscribe((menuCommand) => { this.addMenuOption(menuCommand); }) ); this.subscriptions.add( iframeListener.unregisterMenuCommandStream.subscribe((menuCommand) => { this.destroyMenu(menuCommand); }) ); } reset() { const addedMenuItems = [...this.menuElement.node.querySelectorAll(".fromApi")]; for (let index = addedMenuItems.length - 1; index >= 0; index--) { addedMenuItems[index].remove(); } } public addMenuOption(menuText: string) { const wrappingSection = document.createElement("section"); const escapedHtml = HtmlUtils.escapeHtml(menuText); wrappingSection.innerHTML = ``; const menuItemContainer = this.menuElement.node.querySelector("#gameMenu main"); if (menuItemContainer) { menuItemContainer.querySelector(`#${escapedHtml}.fromApi`)?.remove(); menuItemContainer.insertBefore(wrappingSection, menuItemContainer.querySelector("#socialLinks")); } } preload() { this.load.html(gameMenuKey, "resources/html/gameMenu.html"); this.load.html(gameMenuIconKey, "resources/html/gameMenuIcon.html"); this.load.html(gameSettingsMenuKey, "resources/html/gameQualityMenu.html"); this.load.html(gameShare, "resources/html/gameShare.html"); this.load.html(gameReportKey, gameReportRessource); } create() { menuIconVisible.set(true); this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey); this.menuElement.setOrigin(0); MenuScene.revealMenusAfterInit(this.menuElement, "gameMenu"); if (mediaManager.hasNotification()) { HtmlUtils.getElementByIdOrFail("enableNotification").hidden = true; } const middleX = window.innerWidth / 3 - 298; this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey); MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, "gameQuality"); this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare); MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare); this.gameShareElement.addListener("click"); this.gameShareElement.on("click", (event: MouseEvent) => { event.preventDefault(); if ((event?.target as HTMLInputElement).id === "gameShareFormSubmit") { this.copyLink(); } else if ((event?.target as HTMLInputElement).id === "gameShareFormCancel") { this.closeGameShare(); } }); this.gameReportElement = new ReportMenu( this, connectionManager.getConnexionType === GameConnexionTypes.anonymous ); showReportScreenStore.subscribe((user) => { if (user !== null) { this.closeAll(); const uuid = playersStore.getPlayerById(user.userId)?.userUuid; if (uuid === undefined) { throw new Error("Could not find UUID for user with ID " + user.userId); } this.gameReportElement.open(uuid, user.userName); } }); this.input.keyboard.on("keyup-TAB", () => { this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu(); }); this.menuButton = this.add.dom(0, 0).createFromCache(gameMenuIconKey); this.menuButton.addListener("click"); this.menuButton.on("click", () => { this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu(); }); this.menuElement.addListener("click"); this.menuElement.on("click", this.onMenuClick.bind(this)); chatVisibilityStore.subscribe((v) => { this.menuButton.setVisible(!v); }); } //todo put this method in a parent menuElement class static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) { //Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect. //To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done. setTimeout(() => { (menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false; }, 250); } public revealMenuIcon(): void { //TODO fix me: add try catch because at the same time, 'this.menuButton' variable doesn't exist and there is error on 'getChildByID' function try { (this.menuButton.getChildByID("menuIcon") as HTMLElement).hidden = false; } catch (err) { console.error(err); } } openSideMenu() { if (this.sideMenuOpened) return; this.closeAll(); this.sideMenuOpened = true; this.menuButton.getChildByID("openMenuButton").innerHTML = "X"; const connection = gameManager.getCurrentGameScene(this).connection; if (connection && connection.isAdmin()) { const adminSection = this.menuElement.getChildByID("adminConsoleSection") as HTMLElement; adminSection.hidden = false; } //TODO bind with future metadata of card //if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){ const adminSection = this.menuElement.getChildByID("socialLinks") as HTMLElement; adminSection.hidden = false; //} this.tweens.add({ targets: this.menuElement, x: openedSideMenuX, duration: 500, ease: "Power3", }); } private closeSideMenu(): void { if (!this.sideMenuOpened) return; this.sideMenuOpened = false; this.closeAll(); this.menuButton.getChildByID("openMenuButton").innerHTML = ``; consoleGlobalMessageManagerVisibleStore.set(false); this.tweens.add({ targets: this.menuElement, x: closedSideMenuX, duration: 500, ease: "Power3", }); } private openGameSettingsMenu(): void { if (this.settingsMenuOpened) { this.closeGameQualityMenu(); return; } //close all this.closeAll(); this.settingsMenuOpened = true; const gameQualitySelect = this.gameQualityMenuElement.getChildByID("select-game-quality") as HTMLInputElement; gameQualitySelect.value = "" + this.gameQualityValue; const videoQualitySelect = this.gameQualityMenuElement.getChildByID("select-video-quality") as HTMLInputElement; videoQualitySelect.value = "" + this.videoQualityValue; this.gameQualityMenuElement.addListener("click"); this.gameQualityMenuElement.on("click", (event: MouseEvent) => { event.preventDefault(); if ((event?.target as HTMLInputElement).id === "gameQualityFormSubmit") { const gameQualitySelect = this.gameQualityMenuElement.getChildByID( "select-game-quality" ) as HTMLInputElement; const videoQualitySelect = this.gameQualityMenuElement.getChildByID( "select-video-quality" ) as HTMLInputElement; this.saveSetting(parseInt(gameQualitySelect.value), parseInt(videoQualitySelect.value)); } else if ((event?.target as HTMLInputElement).id === "gameQualityFormCancel") { this.closeGameQualityMenu(); } }); let middleY = this.scale.height / 2 - 392 / 2; if (middleY < 0) { middleY = 0; } let middleX = this.scale.width / 2 - 457 / 2; if (middleX < 0) { middleX = 0; } this.tweens.add({ targets: this.gameQualityMenuElement, y: middleY, x: middleX, duration: 1000, ease: "Power3", }); } private closeGameQualityMenu(): void { if (!this.settingsMenuOpened) return; this.settingsMenuOpened = false; this.gameQualityMenuElement.removeListener("click"); this.tweens.add({ targets: this.gameQualityMenuElement, y: -400, duration: 1000, ease: "Power3", }); } private openGameShare(): void { if (this.gameShareOpened) { this.closeGameShare(); return; } //close all this.closeAll(); const gameShareLink = this.gameShareElement.getChildByID("gameShareLink") as HTMLInputElement; gameShareLink.value = location.toString(); this.gameShareOpened = true; let middleY = this.scale.height / 2 - 85; if (middleY < 0) { middleY = 0; } let middleX = this.scale.width / 2 - 200; if (middleX < 0) { middleX = 0; } this.tweens.add({ targets: this.gameShareElement, y: middleY, x: middleX, duration: 1000, ease: "Power3", }); } private closeGameShare(): void { const gameShareInfo = this.gameShareElement.getChildByID("gameShareInfo") as HTMLParagraphElement; gameShareInfo.innerText = ""; gameShareInfo.style.display = "none"; this.gameShareOpened = false; this.tweens.add({ targets: this.gameShareElement, y: -400, duration: 1000, ease: "Power3", }); } private onMenuClick(event: MouseEvent) { const htmlMenuItem = event?.target as HTMLInputElement; if (htmlMenuItem.classList.contains("not-button")) { return; } event.preventDefault(); if (htmlMenuItem.classList.contains("fromApi")) { sendMenuClickedEvent(htmlMenuItem.id); return; } switch ((event?.target as HTMLInputElement).id) { case "changeNameButton": this.closeSideMenu(); gameManager.leaveGame(this, LoginSceneName, new LoginScene()); break; case "sparkButton": this.gotToCreateMapPage(); break; case "changeSkinButton": this.closeSideMenu(); gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene()); break; case "changeCompanionButton": this.closeSideMenu(); gameManager.leaveGame(this, SelectCompanionSceneName, new SelectCompanionScene()); break; case "closeButton": this.closeSideMenu(); break; case "shareButton": this.openGameShare(); break; case "editGameSettingsButton": this.openGameSettingsMenu(); break; case "oidcLogin": connectionManager.loadOpenIDScreen(); break; case "toggleFullscreen": this.toggleFullscreen(); break; case "enableNotification": this.enableNotification(); break; case "adminConsoleButton": if (get(consoleGlobalMessageManagerVisibleStore)) { consoleGlobalMessageManagerVisibleStore.set(false); } else { consoleGlobalMessageManagerVisibleStore.set(true); } break; } } private async copyLink() { await navigator.clipboard.writeText(location.toString()); const gameShareInfo = this.gameShareElement.getChildByID("gameShareInfo") as HTMLParagraphElement; gameShareInfo.innerText = "Link copied, you can share it now!"; gameShareInfo.style.display = "block"; } private saveSetting(valueGame: number, valueVideo: number) { if (valueGame !== this.gameQualityValue) { this.gameQualityValue = valueGame; localUserStore.setGameQualityValue(valueGame); window.location.reload(); } if (valueVideo !== this.videoQualityValue) { this.videoQualityValue = valueVideo; localUserStore.setVideoQualityValue(valueVideo); videoConstraintStore.setFrameRate(valueVideo); } this.closeGameQualityMenu(); } private gotToCreateMapPage() { //const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html'; //TODO fix me: this button can to send us on WorkAdventure BO. const sparkHost = ADMIN_URL + "/getting-started"; window.open(sparkHost, "_blank"); } private closeAll() { this.closeGameQualityMenu(); this.closeGameShare(); this.gameReportElement.close(); } private toggleFullscreen() { const body = document.querySelector("body"); if (body) { if (document.fullscreenElement ?? document.fullscreen) { document.exitFullscreen(); } else { body.requestFullscreen(); } } } public destroyMenu(menu: string) { this.menuElement.getChildByID(menu).remove(); } public isDirty(): boolean { return false; } private enableNotification() { mediaManager.requestNotification().then(() => { if (mediaManager.hasNotification()) { HtmlUtils.getElementByIdOrFail("enableNotification").hidden = true; } }); } }