import { LitElement, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { Cosmetics } from "../core/CosmeticSchemas"; import { UserSettings } from "../core/game/UserSettings"; import { PlayerPattern } from "../core/Schemas"; import { renderPatternPreview } from "./components/PatternButton"; import { fetchCosmetics } from "./Cosmetics"; import { translateText } from "./Utils"; // Module-level cosmetics cache to avoid refetching on every component mount let cosmeticsCache: Promise | null = null; function getCachedCosmetics(): Promise { if (!cosmeticsCache) { const fetchPromise = fetchCosmetics(); cosmeticsCache = fetchPromise.catch((err) => { cosmeticsCache = null; throw err; }); } return cosmeticsCache; } @customElement("pattern-input") export class PatternInput extends LitElement { @state() public pattern: PlayerPattern | null = null; @state() public selectedColor: string | null = null; @state() private isLoading: boolean = true; @property({ type: Boolean, attribute: "show-select-label" }) public showSelectLabel: boolean = false; private userSettings = new UserSettings(); private cosmetics: Cosmetics | null = null; private _abortController: AbortController | null = null; private _onPatternSelected = () => { this.updateFromSettings(); }; private updateFromSettings() { this.selectedColor = this.userSettings.getSelectedColor() ?? null; if (this.cosmetics) { this.pattern = this.userSettings.getSelectedPatternName(this.cosmetics); } else { this.pattern = null; } } private onInputClick(e: Event) { e.preventDefault(); e.stopPropagation(); this.dispatchEvent( new CustomEvent("pattern-input-click", { bubbles: true, composed: true, }), ); } async connectedCallback() { super.connectedCallback(); this._abortController = new AbortController(); this.isLoading = true; const cosmetics = await getCachedCosmetics(); if (!this.isConnected) return; this.cosmetics = cosmetics; this.updateFromSettings(); if (!this.isConnected) return; this.isLoading = false; window.addEventListener("pattern-selected", this._onPatternSelected, { signal: this._abortController.signal, }); } disconnectedCallback() { super.disconnectedCallback(); if (this._abortController) { this._abortController.abort(); this._abortController = null; } } createRenderRoot() { return this; } render() { const isDefault = this.pattern === null && this.selectedColor === null; const showSelect = this.showSelectLabel && isDefault; const buttonTitle = translateText("territory_patterns.title"); // Show loading state if (this.isLoading) { return html` `; } let previewContent; if (this.pattern) { previewContent = renderPatternPreview(this.pattern, 128, 128); } else { previewContent = renderPatternPreview(null, 128, 128); } return html` `; } }