diff --git a/src/client/LangSelector.ts b/src/client/LangSelector.ts index 4333ee4ac..4c6f8babb 100644 --- a/src/client/LangSelector.ts +++ b/src/client/LangSelector.ts @@ -198,6 +198,7 @@ export class LangSelector extends LitElement { "player-panel", "replay-panel", "help-modal", + "settings-modal", "username-input", "public-lobby", "user-setting", diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index e75845173..0af4e9113 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -27,6 +27,7 @@ import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay"; import { PlayerPanel } from "./layers/PlayerPanel"; import { RailroadLayer } from "./layers/RailroadLayer"; import { ReplayPanel } from "./layers/ReplayPanel"; +import { SettingsModal } from "./layers/SettingsModal"; import { SpawnAd } from "./layers/SpawnAd"; import { SpawnTimer } from "./layers/SpawnTimer"; import { StructureIconsLayer } from "./layers/StructureIconsLayer"; @@ -152,6 +153,15 @@ export function createRenderer( gameRightSidebar.game = game; gameRightSidebar.eventBus = eventBus; + const settingsModal = document.querySelector( + "settings-modal", + ) as SettingsModal; + if (!(settingsModal instanceof SettingsModal)) { + console.error("settings modal not found"); + } + settingsModal.userSettings = userSettings; + settingsModal.eventBus = eventBus; + const gameTopBar = document.querySelector("game-top-bar") as GameTopBar; if (!(gameTopBar instanceof GameTopBar)) { console.error("top bar not found"); @@ -252,6 +262,7 @@ export function createRenderer( playerInfo, winModal, replayPanel, + settingsModal, teamStats, playerPanel, headsUpMessage, diff --git a/src/client/graphics/layers/GameTopBar.ts b/src/client/graphics/layers/GameTopBar.ts index 377eed770..365ce7b19 100644 --- a/src/client/graphics/layers/GameTopBar.ts +++ b/src/client/graphics/layers/GameTopBar.ts @@ -1,25 +1,17 @@ import { html, LitElement } from "lit"; -import { customElement, query, state } from "lit/decorators.js"; -import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg"; -import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg"; -import exitIcon from "../../../../resources/images/ExitIconWhite.svg"; -import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg"; +import { customElement, state } from "lit/decorators.js"; import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg"; -import mouseIcon from "../../../../resources/images/MouseIconWhite.svg"; -import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg"; import populationIcon from "../../../../resources/images/PopulationIconSolidWhite.svg"; import settingsIcon from "../../../../resources/images/SettingIconWhite.svg"; -import treeIcon from "../../../../resources/images/TreeIconWhite.svg"; import troopIcon from "../../../../resources/images/TroopIconWhite.svg"; import workerIcon from "../../../../resources/images/WorkerIconWhite.svg"; -import { translateText } from "../../../client/Utils"; import { EventBus } from "../../../core/EventBus"; import { GameUpdateType } from "../../../core/game/GameUpdates"; import { GameView } from "../../../core/game/GameView"; import { UserSettings } from "../../../core/game/UserSettings"; -import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler"; import { renderNumber, renderTroops } from "../../Utils"; import { Layer } from "./Layer"; +import { ShowSettingsModalEvent } from "./SettingsModal"; @customElement("game-top-bar") export class GameTopBar extends LitElement implements Layer { @@ -33,17 +25,9 @@ export class GameTopBar extends LitElement implements Layer { private _popRateIsIncreasing = false; private hasWinner = false; - @state() - private showSettingsMenu = false; - @state() - private alternateView: boolean = false; - @state() private timer: number = 0; - @query(".settings-container") - private settingsContainer!: HTMLElement; - createRenderRoot() { return this; } @@ -70,66 +54,8 @@ export class GameTopBar extends LitElement implements Layer { this.requestUpdate(); } - connectedCallback() { - super.connectedCallback(); - window.addEventListener("click", this.handleOutsideClick, true); - } - - disconnectedCallback() { - window.removeEventListener("click", this.handleOutsideClick, true); - super.disconnectedCallback(); - } - private handleOutsideClick = (event: MouseEvent) => { - if ( - this.showSettingsMenu && - this.settingsContainer && - !this.settingsContainer.contains(event.target as Node) - ) { - this.showSettingsMenu = false; - } - }; - - private onExitButtonClick() { - const isAlive = this.game.myPlayer()?.isAlive(); - if (isAlive) { - const isConfirmed = confirm("Are you sure you want to exit the game?"); - if (!isConfirmed) return; - } - // redirect to the home page - window.location.href = "/"; - } - - private onTerrainButtonClick() { - this.alternateView = !this.alternateView; - this.eventBus.emit(new AlternateViewEvent(this.alternateView)); - this.requestUpdate(); - } - - private onToggleEmojisButtonClick() { - this._userSettings.toggleEmojis(); - this.requestUpdate(); - } - - private onToggleSpecialEffectsButtonClick() { - this._userSettings.toggleFxLayer(); - this.requestUpdate(); - } - - private onToggleDarkModeButtonClick() { - this._userSettings.toggleDarkMode(); - this.requestUpdate(); - this.eventBus.emit(new RefreshGraphicsEvent()); - } - - private onToggleRandomNameModeButtonClick() { - this._userSettings.toggleRandomName(); - } - private onToggleLeftClickOpensMenu() { - this._userSettings.toggleLeftClickOpenMenu(); - } - - private toggleSettingsMenu() { - this.showSettingsMenu = !this.showSettingsMenu; + private onSettingsButtonClick() { + this.eventBus.emit(new ShowSettingsModalEvent(true)); } private updatePopulationIncrease() { @@ -270,116 +196,15 @@ export class GameTopBar extends LitElement implements Layer { > ${this.secondsToHms(this.timer)} -
- settings - ${this.showSettingsMenu - ? html` -
- - - - - - - -
- ` - : null} -
+ settings diff --git a/src/client/graphics/layers/SettingsModal.ts b/src/client/graphics/layers/SettingsModal.ts new file mode 100644 index 000000000..aeda1761e --- /dev/null +++ b/src/client/graphics/layers/SettingsModal.ts @@ -0,0 +1,300 @@ +import { html, LitElement } from "lit"; +import { customElement, query, state } from "lit/decorators.js"; +import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg"; +import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg"; +import exitIcon from "../../../../resources/images/ExitIconWhite.svg"; +import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg"; +import mouseIcon from "../../../../resources/images/MouseIconWhite.svg"; +import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg"; +import settingsIcon from "../../../../resources/images/SettingIconWhite.svg"; +import treeIcon from "../../../../resources/images/TreeIconWhite.svg"; +import { translateText } from "../../../client/Utils"; +import { EventBus } from "../../../core/EventBus"; +import { UserSettings } from "../../../core/game/UserSettings"; +import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler"; +import { Layer } from "./Layer"; + +export class ShowSettingsModalEvent { + constructor(public readonly isVisible: boolean = true) {} +} + +@customElement("settings-modal") +export class SettingsModal extends LitElement implements Layer { + public eventBus: EventBus; + public userSettings: UserSettings; + + @state() + private isVisible: boolean = false; + + @state() + private alternateView: boolean = false; + + @query(".modal-overlay") + private modalOverlay!: HTMLElement; + + init() { + this.eventBus.on(ShowSettingsModalEvent, (event) => { + this.isVisible = event.isVisible; + }); + } + + createRenderRoot() { + return this; + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener("click", this.handleOutsideClick, true); + window.addEventListener("keydown", this.handleKeyDown); + } + + disconnectedCallback() { + window.removeEventListener("click", this.handleOutsideClick, true); + window.removeEventListener("keydown", this.handleKeyDown); + super.disconnectedCallback(); + } + + private handleOutsideClick = (event: MouseEvent) => { + if ( + this.isVisible && + this.modalOverlay && + event.target === this.modalOverlay + ) { + this.closeModal(); + } + }; + + private handleKeyDown = (event: KeyboardEvent) => { + if (this.isVisible && event.key === "Escape") { + this.closeModal(); + } + }; + + public openModal() { + this.isVisible = true; + document.body.style.overflow = "hidden"; + this.requestUpdate(); + } + + public closeModal() { + this.isVisible = false; + document.body.style.overflow = ""; + this.requestUpdate(); + } + + private onTerrainButtonClick() { + this.alternateView = !this.alternateView; + this.eventBus.emit(new AlternateViewEvent(this.alternateView)); + this.requestUpdate(); + } + + private onToggleEmojisButtonClick() { + this.userSettings.toggleEmojis(); + this.requestUpdate(); + } + + private onToggleSpecialEffectsButtonClick() { + this.userSettings.toggleFxLayer(); + this.requestUpdate(); + } + + private onToggleDarkModeButtonClick() { + this.userSettings.toggleDarkMode(); + this.eventBus.emit(new RefreshGraphicsEvent()); + this.requestUpdate(); + } + + private onToggleRandomNameModeButtonClick() { + this.userSettings.toggleRandomName(); + this.requestUpdate(); + } + + private onToggleLeftClickOpensMenu() { + this.userSettings.toggleLeftClickOpenMenu(); + this.requestUpdate(); + } + + private onExitButtonClick() { + // redirect to the home page + window.location.href = "/"; + } + + render() { + if (!this.isVisible) { + return null; + } + + return html` + + `; + } +} diff --git a/src/client/index.html b/src/client/index.html index d98fd4026..28ed16b8d 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -362,7 +362,7 @@ - +