import { LitElement, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { Difficulty, GameMapType } from "../../../core/game/Game"; import { terrainMapFileLoader } from "../../TerrainMapFileLoader"; import { translateText } from "../../Utils"; @customElement("map-display") export class MapDisplay extends LitElement { @property({ type: String }) mapKey = ""; @property({ type: Boolean }) selected = false; @property({ type: String }) translation: string = ""; @property({ type: Boolean }) showMedals = false; @property({ attribute: false }) wins: Set = new Set(); @state() private mapWebpPath: string | null = null; @state() private mapName: string | null = null; @state() private isLoading = true; @state() private hasNations = true; private observer: IntersectionObserver | null = null; private dataLoaded = false; createRenderRoot() { return this; } connectedCallback() { super.connectedCallback(); this.observer = new IntersectionObserver( (entries) => { if (entries.some((e) => e.isIntersecting) && !this.dataLoaded) { this.dataLoaded = true; this.loadMapData(); this.observer?.disconnect(); } }, { rootMargin: "200px" }, ); this.observer.observe(this); } disconnectedCallback() { this.observer?.disconnect(); this.observer = null; super.disconnectedCallback(); } private async loadMapData() { if (!this.mapKey) return; try { this.isLoading = true; const mapValue = GameMapType[this.mapKey as keyof typeof GameMapType]; const data = terrainMapFileLoader.getMapData(mapValue); this.mapWebpPath = await data.webpPath(); const manifest = await data.manifest(); this.mapName = manifest.name; this.hasNations = Array.isArray(manifest.nations) && manifest.nations.length > 0; } catch (error) { console.error("Failed to load map data:", error); } finally { this.isLoading = false; } } private handleKeydown(event: KeyboardEvent) { // Trigger the same activation logic as click when Enter or Space is pressed if (event.key === "Enter" || event.key === " ") { event.preventDefault(); // Dispatch a click event to maintain compatibility with parent click handlers (event.target as HTMLElement).click(); } } private preventImageDrag(event: DragEvent) { event.preventDefault(); } render() { return html`
${this.isLoading ? html`
${translateText("map_component.loading")}
` : this.mapWebpPath ? html`
${this.translation || this.mapName}
` : html`
${translateText("map_component.error")}
`} ${this.showMedals && this.hasNations ? html`
${this.renderMedals()}
` : null}
${this.translation || this.mapName}
`; } private renderMedals() { const medalOrder: Difficulty[] = [ Difficulty.Easy, Difficulty.Medium, Difficulty.Hard, Difficulty.Impossible, ]; const colors: Record = { [Difficulty.Easy]: "var(--medal-easy)", [Difficulty.Medium]: "var(--medal-medium)", [Difficulty.Hard]: "var(--medal-hard)", [Difficulty.Impossible]: "var(--medal-impossible)", }; const wins = this.readWins(); return medalOrder.map((medal) => { const earned = wins.has(medal); const mask = "url('/images/MedalIconWhite.svg') no-repeat center / contain"; return html`
`; }); } private readWins(): Set { return this.wins ?? new Set(); } }