import type { TemplateResult } from "lit"; import { html, render } from "lit"; import { customElement, state } from "lit/decorators.js"; import { UserMeResponse } from "../core/ApiSchemas"; import { ColorPalette, Cosmetics, Pattern } from "../core/CosmeticSchemas"; import { UserSettings } from "../core/game/UserSettings"; import { PlayerPattern } from "../core/Schemas"; import { hasLinkedAccount } from "./Api"; import { BaseModal } from "./components/BaseModal"; import "./components/Difficulties"; import "./components/PatternButton"; import { renderPatternPreview } from "./components/PatternButton"; import { fetchCosmetics, handlePurchase, patternRelationship, } from "./Cosmetics"; import { translateText } from "./Utils"; @customElement("territory-patterns-modal") export class TerritoryPatternsModal extends BaseModal { public previewButton: HTMLElement | null = null; @state() private selectedPattern: PlayerPattern | null; @state() private selectedColor: string | null = null; @state() private activeTab: "patterns" | "colors" = "patterns"; @state() private showOnlyOwned: boolean = false; private cosmetics: Cosmetics | null = null; private userSettings: UserSettings = new UserSettings(); private isActive = false; private affiliateCode: string | null = null; private userMeResponse: UserMeResponse | false = false; private _onPatternSelected = () => { this.updateFromSettings(); this.refresh(); }; constructor() { super(); } connectedCallback() { super.connectedCallback(); document.addEventListener( "userMeResponse", (event: CustomEvent) => { this.onUserMe(event.detail); }, ); window.addEventListener("pattern-selected", this._onPatternSelected); } disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("pattern-selected", this._onPatternSelected); } private updateFromSettings() { this.selectedPattern = this.cosmetics !== null ? this.userSettings.getSelectedPatternName(this.cosmetics) : null; this.selectedColor = this.userSettings.getSelectedColor() ?? null; } async onUserMe(userMeResponse: UserMeResponse | false) { if (!hasLinkedAccount(userMeResponse)) { this.userSettings.setSelectedPatternName(undefined); this.userSettings.setSelectedColor(undefined); this.selectedPattern = null; this.selectedColor = null; } this.userMeResponse = userMeResponse; this.cosmetics = await fetchCosmetics(); this.updateFromSettings(); this.refresh(); } private renderTabNavigation(): TemplateResult { return html`
${translateText("territory_patterns.title")} ${!hasLinkedAccount(this.userMeResponse) ? html`
${this.renderNotLoggedInWarning()}
` : html``}
`; } private renderPatternGrid(): TemplateResult { const buttons: TemplateResult[] = []; const patterns: (Pattern | null)[] = [ null, ...Object.values(this.cosmetics?.patterns ?? {}), ]; for (const pattern of patterns) { const colorPalettes = pattern ? [...(pattern.colorPalettes ?? []), null] : [null]; for (const colorPalette of colorPalettes) { let rel = "owned"; if (pattern) { rel = patternRelationship( pattern, colorPalette, this.userMeResponse, this.affiliateCode, ); } if (rel === "blocked") { continue; } if (this.showOnlyOwned) { if (rel !== "owned") continue; } else { // Store mode: hide owned items if (rel === "owned") continue; } // Determine if this pattern/color is selected const isDefaultPattern = pattern === null; const isSelected = (isDefaultPattern && this.selectedPattern === null) || (!isDefaultPattern && this.selectedPattern && this.selectedPattern.name === pattern?.name && (this.selectedPattern.colorPalette?.name ?? null) === (colorPalette?.name ?? null)); buttons.push(html` this.selectPattern(p)} .onPurchase=${(p: Pattern, colorPalette: ColorPalette | null) => handlePurchase(p, colorPalette)} > `); } } return html`
${hasLinkedAccount(this.userMeResponse) ? this.renderMySkinsButton() : html``}
${!this.showOnlyOwned && buttons.length === 0 ? html`
${translateText("territory_patterns.all_owned")}
` : html`
${buttons}
`}
`; } private renderMySkinsButton(): TemplateResult { return html``; } private renderNotLoggedInWarning(): TemplateResult { return html`
${translateText("territory_patterns.not_logged_in")}
`; } private renderColorSwatchGrid(): TemplateResult { const hexCodes = ( this.userMeResponse === false ? [] : (this.userMeResponse.player.flares ?? []) ) .filter((flare) => flare.startsWith("color:")) .map((flare) => flare.split(":")[1]); return html`
${hexCodes.map( (hexCode) => html`
this.selectColor(hexCode)} >
`, )}
`; } render() { if (!this.isActive && !this.inline) return html``; const content = html`
${this.renderTabNavigation()}
${this.activeTab === "patterns" ? this.renderPatternGrid() : this.renderColorSwatchGrid()}
`; if (this.inline) { return content; } return html` ${content} `; } public async open( options?: string | { affiliateCode?: string; showOnlyOwned?: boolean }, ) { this.isActive = true; if (typeof options === "string") { this.affiliateCode = options; this.showOnlyOwned = false; } else if ( options !== null && typeof options === "object" && !Array.isArray(options) ) { this.affiliateCode = options.affiliateCode ?? null; this.showOnlyOwned = options.showOnlyOwned ?? false; } else { this.affiliateCode = null; this.showOnlyOwned = false; } await this.refresh(); super.open(); } public close() { this.isActive = false; this.affiliateCode = null; super.close(); } private selectPattern(pattern: PlayerPattern | null) { this.selectedColor = null; this.userSettings.setSelectedColor(undefined); if (pattern === null) { this.userSettings.setSelectedPatternName(undefined); } else { const name = pattern.colorPalette?.name === undefined ? pattern.name : `${pattern.name}:${pattern.colorPalette.name}`; this.userSettings.setSelectedPatternName(`pattern:${name}`); } this.selectedPattern = pattern; this.refresh(); // Dispatch event so Main.ts can refresh the preview button this.dispatchEvent(new CustomEvent("pattern-selected", { bubbles: true })); // Show popup/modal for skin selection this.showSkinSelectedPopup(); // Close the skin store this.close(); } private showSkinSelectedPopup() { // Use unified heads-up-message for feedback let skinName = translateText("territory_patterns.pattern.default"); if (this.selectedPattern && this.selectedPattern.name) { skinName = this.selectedPattern.name .split("_") .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) .join(" "); if ( this.selectedPattern.colorPalette && this.selectedPattern.colorPalette.name ) { skinName += ` (${this.selectedPattern.colorPalette.name})`; } } window.dispatchEvent( new CustomEvent("show-message", { detail: { message: `${skinName} ${translateText("territory_patterns.selected")}`, duration: 2000, }, }), ); } private selectColor(hexCode: string) { this.selectedPattern = null; this.userSettings.setSelectedPatternName(undefined); this.selectedColor = hexCode; this.userSettings.setSelectedColor(hexCode); this.refresh(); this.close(); } private renderColorPreview( hexCode: string, width: number, height: number, ): TemplateResult { return html`
`; } public async refresh() { this.requestUpdate(); const preview = this.selectedColor ? this.renderColorPreview(this.selectedColor, 48, 48) : renderPatternPreview(this.selectedPattern ?? null, 48, 48); if ( this.previewButton === null || !document.body.contains(this.previewButton) ) { this.previewButton = document.getElementById( "territory-patterns-input-preview-button", ); } if (this.previewButton === null) return; // Check if the element is still in the DOM to avoid lit-html errors if (!document.body.contains(this.previewButton)) { console.warn( "TerritoryPatternsModal: previewButton is disconnected from DOM, skipping render", ); return; } // Clear and re-render using Lit render(preview, this.previewButton); this.previewButton.style.padding = "4px"; this.requestUpdate(); } }