mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 06:02:20 +00:00
d5a2cc0fca
## Description: The motivation is to have a single "cosmetic-button" element, so we can abstract out the cosmetic types. This will make it much easier to add new cosmetic types in the future. Unifies PatternButton and FlagButton into a single CosmeticButton component. Extracts a resolveCosmetics() function that flattens patterns × color palettes + flags into a ResolvedCosmetic[] with relationship status pre-computed, replacing duplicated resolution logic across four callers. * New CosmeticButton — renders patterns or flags based on ResolvedCosmetic.type * New resolveCosmetics() — centralizes ownership/purchase/blocked resolution * Extracted PatternPreview — canvas rendering split into its own module * Added type: "pattern" | "flag" discriminator to Zod cosmetic schemas * Deleted FlagButton.ts and PatternButton.ts * Added 320-line test suite for resolveCosmetics ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
158 lines
4.8 KiB
TypeScript
158 lines
4.8 KiB
TypeScript
import { LitElement, html } from "lit";
|
|
import { customElement, property, state } from "lit/decorators.js";
|
|
import {
|
|
PATTERN_KEY,
|
|
USER_SETTINGS_CHANGED_EVENT,
|
|
} from "../core/game/UserSettings";
|
|
import { PlayerPattern } from "../core/Schemas";
|
|
import { renderPatternPreview } from "./components/PatternPreview";
|
|
import { getPlayerCosmetics } from "./Cosmetics";
|
|
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
|
import { translateText } from "./Utils";
|
|
|
|
@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;
|
|
|
|
@property({ type: Boolean, attribute: "adaptive-size" })
|
|
public adaptiveSize: boolean = false;
|
|
|
|
private _abortController: AbortController | null = null;
|
|
|
|
private _onPatternSelected = async () => {
|
|
const cosmetics = await getPlayerCosmetics();
|
|
this.selectedColor = cosmetics.color?.color ?? null;
|
|
this.pattern = cosmetics.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 getPlayerCosmetics();
|
|
this.selectedColor = cosmetics.color?.color ?? null;
|
|
this.pattern = cosmetics.pattern ?? null;
|
|
if (!this.isConnected) return;
|
|
this.isLoading = false;
|
|
window.addEventListener(
|
|
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
|
this._onPatternSelected,
|
|
{
|
|
signal: this._abortController.signal,
|
|
},
|
|
);
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
if (this._abortController) {
|
|
this._abortController.abort();
|
|
this._abortController = null;
|
|
}
|
|
}
|
|
|
|
createRenderRoot() {
|
|
return this;
|
|
}
|
|
|
|
private getIsDefaultPattern(): boolean {
|
|
return this.pattern === null && this.selectedColor === null;
|
|
}
|
|
|
|
private shouldShowSelectLabel(): boolean {
|
|
return this.showSelectLabel && this.getIsDefaultPattern();
|
|
}
|
|
|
|
private applyAdaptiveSize(): void {
|
|
if (!this.adaptiveSize) {
|
|
this.style.removeProperty("width");
|
|
this.style.removeProperty("height");
|
|
return;
|
|
}
|
|
|
|
const showSelect = this.showSelectLabel && this.getIsDefaultPattern();
|
|
this.style.setProperty("height", "2.5rem");
|
|
this.style.setProperty(
|
|
"width",
|
|
showSelect ? "clamp(3.25rem, 14vw, 4.75rem)" : "2.5rem",
|
|
);
|
|
}
|
|
|
|
protected updated(): void {
|
|
this.applyAdaptiveSize();
|
|
}
|
|
|
|
render() {
|
|
if (crazyGamesSDK.isOnCrazyGames()) {
|
|
return html``;
|
|
}
|
|
|
|
const showSelect = this.shouldShowSelectLabel();
|
|
const buttonTitle = translateText("territory_patterns.title");
|
|
|
|
// Show loading state
|
|
if (this.isLoading) {
|
|
return html`
|
|
<button
|
|
id="pattern-input"
|
|
class="pattern-btn m-0 p-0 w-full h-full flex cursor-pointer justify-center items-center focus:outline-none focus:ring-0 bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)] rounded-lg overflow-hidden"
|
|
disabled
|
|
>
|
|
<span
|
|
class="w-6 h-6 border-4 border-blue-500/30 border-t-blue-500 rounded-full animate-spin"
|
|
></span>
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
let previewContent;
|
|
if (this.pattern) {
|
|
previewContent = renderPatternPreview(this.pattern, 128, 128);
|
|
} else {
|
|
previewContent = renderPatternPreview(null, 128, 128);
|
|
}
|
|
|
|
return html`
|
|
<button
|
|
id="pattern-input"
|
|
class="pattern-btn m-0 p-0 w-full h-full flex cursor-pointer justify-center items-center focus:outline-none focus:ring-0 transition-all duration-200 hover:scale-105 bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)] hover:brightness-[1.08] active:brightness-[0.95] rounded-lg overflow-hidden"
|
|
title=${buttonTitle}
|
|
@click=${this.onInputClick}
|
|
>
|
|
<span
|
|
class=${showSelect
|
|
? "hidden"
|
|
: "w-full h-full overflow-hidden flex items-center justify-center [&>img]:object-cover [&>img]:w-full [&>img]:h-full [&>img]:pointer-events-none"}
|
|
>
|
|
${!showSelect ? previewContent : null}
|
|
</span>
|
|
${showSelect
|
|
? html`<span
|
|
class="${this.adaptiveSize
|
|
? "text-[7px] leading-tight px-0.5"
|
|
: "text-[10px] leading-none break-words px-1"} font-black text-white uppercase w-full text-center"
|
|
>
|
|
${translateText("territory_patterns.select_skin")}
|
|
</span>`
|
|
: null}
|
|
</button>
|
|
`;
|
|
}
|
|
}
|