mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 13:37:20 +00:00
Merge skin/effects pickers into one lobby Cosmetics modal (#4617)
## What Replaces the three lobby cosmetic buttons (skin, flag, effects) with two: **Flag** and **Cosmetics**. The new Cosmetics button opens a single tabbed modal (`#modal=cosmetics`) with: - **Skins** — the combined patterns + image-skins grid (from TerritoryPatternsModal) - **Effects** — all effect types via the tabbed effects-grid, same layout as the Store ## Changes - New `CosmeticsModal` and `CosmeticsInput` (lobby button previews the selected skin/pattern, shows a "Cosmetics" label on defaults, hidden on CrazyGames via `no-crazygames`) - Removed `TerritoryPatternsModal`, `EffectsModal`, `PatternInput`, `EffectsInput` and their wiring in Main.ts / index.html / LangSelector - Selecting a skin or pattern no longer closes the modal — the tile highlight moves to the new selection (matches the Effects tab behavior) - i18n: added `cosmetics.title/button_title/search`; removed the keys orphaned by the deleted components ## Notes - Deep links to the old `#modal=territory-patterns` and `#modal=effects` no longer resolve - A follow-up PR (crowns cosmetic) stacks on this branch 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,8 +10,13 @@ import { getPlayerCosmetics } from "./Cosmetics";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
@customElement("pattern-input")
|
||||
export class PatternInput extends LitElement {
|
||||
/**
|
||||
* Lobby button that opens the cosmetics modal (skins, crowns, effects).
|
||||
* Previews the selected skin/pattern; shows a "Cosmetics" label when the
|
||||
* player is on defaults.
|
||||
*/
|
||||
@customElement("cosmetics-input")
|
||||
export class CosmeticsInput extends LitElement {
|
||||
@state() public pattern: PlayerPattern | null = null;
|
||||
@state() public skin: PlayerSkin | null = null;
|
||||
@state() public selectedColor: string | null = null;
|
||||
@@ -36,7 +41,7 @@ export class PatternInput extends LitElement {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("pattern-input-click", {
|
||||
new CustomEvent("cosmetics-input-click", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
@@ -109,13 +114,13 @@ export class PatternInput extends LitElement {
|
||||
}
|
||||
|
||||
const showSelect = this.shouldShowSelectLabel();
|
||||
const buttonTitle = translateText("territory_patterns.title");
|
||||
const buttonTitle = translateText("cosmetics.button_title");
|
||||
|
||||
// Show loading state
|
||||
if (this.isLoading) {
|
||||
return html`
|
||||
<button
|
||||
id="pattern-input"
|
||||
id="cosmetics-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-surface rounded-lg overflow-hidden"
|
||||
disabled
|
||||
>
|
||||
@@ -144,7 +149,7 @@ export class PatternInput extends LitElement {
|
||||
|
||||
return html`
|
||||
<button
|
||||
id="pattern-input"
|
||||
id="cosmetics-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-surface hover:brightness-[1.08] active:brightness-[0.95] hover:shadow-[var(--shadow-action-card-hover)] rounded-lg overflow-hidden"
|
||||
title=${buttonTitle}
|
||||
@click=${this.onInputClick}
|
||||
@@ -162,7 +167,7 @@ export class PatternInput extends LitElement {
|
||||
? "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")}
|
||||
${translateText("cosmetics.title")}
|
||||
</span>`
|
||||
: null}
|
||||
</button>
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { PlayerPattern } from "../core/Schemas";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/CosmeticButton";
|
||||
import "./components/EffectsGrid";
|
||||
import "./components/NotLoggedInWarning";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
import {
|
||||
@@ -22,12 +23,16 @@ import {
|
||||
} from "./Cosmetics";
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
@customElement("territory-patterns-modal")
|
||||
export class TerritoryPatternsModal extends BaseModal {
|
||||
protected routerName = "territory-patterns";
|
||||
public previewButton: HTMLElement | null = null;
|
||||
/**
|
||||
* One modal for every non-flag cosmetic: a Skins tab (patterns + image skins)
|
||||
* and an Effects tab (all effect types via the tabbed effects-grid). Opened
|
||||
* from the lobby's "Cosmetics" button.
|
||||
*/
|
||||
@customElement("cosmetics-modal")
|
||||
export class CosmeticsModal extends BaseModal {
|
||||
protected routerName = "cosmetics";
|
||||
|
||||
@state() private selectedPattern: PlayerPattern | null;
|
||||
@state() private selectedPattern: PlayerPattern | null = null;
|
||||
@state() private selectedColor: string | null = null;
|
||||
@state() private selectedSkinName: string | null = null;
|
||||
@state() private search = "";
|
||||
@@ -36,7 +41,16 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
private userMeResponse: UserMeResponse | false = false;
|
||||
|
||||
private _onPatternSelected = async () => {
|
||||
protected modalConfig() {
|
||||
return {
|
||||
tabs: [
|
||||
{ key: "skins", label: translateText("store.patterns") },
|
||||
{ key: "effects", label: translateText("store.effects") },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private _onCosmeticSelected = async () => {
|
||||
await this.updateFromSettings();
|
||||
this.refresh();
|
||||
};
|
||||
@@ -51,7 +65,7 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
);
|
||||
window.addEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
||||
this._onPatternSelected,
|
||||
this._onCosmeticSelected,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,7 +73,7 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${PATTERN_KEY}`,
|
||||
this._onPatternSelected,
|
||||
this._onCosmeticSelected,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,34 +116,32 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="flex flex-wrap gap-4 p-8 justify-center items-stretch content-start"
|
||||
>
|
||||
${items.map((r) => {
|
||||
const isSelected =
|
||||
r.type === "pattern"
|
||||
? (r.cosmetic === null && this.selectedPattern === null) ||
|
||||
(r.cosmetic !== null &&
|
||||
this.selectedPattern?.name === r.cosmetic.name &&
|
||||
(this.selectedPattern?.colorPalette?.name ?? null) ===
|
||||
(r.colorPalette?.name ?? null))
|
||||
: (() => {
|
||||
const skinName = (r.cosmetic as Skin | null)?.name ?? null;
|
||||
return (
|
||||
(skinName === null && this.selectedSkinName === null) ||
|
||||
(skinName !== null && this.selectedSkinName === skinName)
|
||||
);
|
||||
})();
|
||||
return html`
|
||||
<cosmetic-button
|
||||
.resolved=${r}
|
||||
.selected=${isSelected}
|
||||
.onSelect=${(rc: ResolvedCosmetic) => this.selectCosmetic(rc)}
|
||||
></cosmetic-button>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-wrap gap-4 p-8 justify-center items-stretch content-start"
|
||||
>
|
||||
${items.map((r) => {
|
||||
const isSelected =
|
||||
r.type === "pattern"
|
||||
? (r.cosmetic === null && this.selectedPattern === null) ||
|
||||
(r.cosmetic !== null &&
|
||||
this.selectedPattern?.name === r.cosmetic.name &&
|
||||
(this.selectedPattern?.colorPalette?.name ?? null) ===
|
||||
(r.colorPalette?.name ?? null))
|
||||
: (() => {
|
||||
const skinName = (r.cosmetic as Skin | null)?.name ?? null;
|
||||
return (
|
||||
(skinName === null && this.selectedSkinName === null) ||
|
||||
(skinName !== null && this.selectedSkinName === skinName)
|
||||
);
|
||||
})();
|
||||
return html`
|
||||
<cosmetic-button
|
||||
.resolved=${r}
|
||||
.selected=${isSelected}
|
||||
.onSelect=${(rc: ResolvedCosmetic) => this.selectCosmetic(rc)}
|
||||
></cosmetic-button>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -140,7 +152,7 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
class="relative flex flex-col border-b border-white/10 pb-4 shrink-0"
|
||||
>
|
||||
${modalHeader({
|
||||
title: translateText("territory_patterns.title"),
|
||||
title: translateText("cosmetics.title"),
|
||||
onBack: () => this.close(),
|
||||
ariaLabel: translateText("common.back"),
|
||||
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
|
||||
@@ -152,7 +164,7 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
rounded-xl shadow-inner text-xl text-center focus:outline-none
|
||||
focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 text-white placeholder-white/30 transition-all"
|
||||
type="text"
|
||||
placeholder=${translateText("territory_patterns.search")}
|
||||
placeholder=${translateText("cosmetics.search")}
|
||||
.value=${this.search}
|
||||
@change=${this.handleSearch}
|
||||
@keyup=${this.handleSearch}
|
||||
@@ -162,7 +174,19 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderBody() {
|
||||
protected renderBody(tab: string) {
|
||||
let grid: TemplateResult;
|
||||
if (tab === "effects") {
|
||||
grid = html`<effects-grid
|
||||
mode="select"
|
||||
tabbed
|
||||
.cosmetics=${this.cosmetics}
|
||||
.userMeResponse=${this.userMeResponse}
|
||||
.search=${this.search}
|
||||
></effects-grid>`;
|
||||
} else {
|
||||
grid = this.renderSkinGrid();
|
||||
}
|
||||
return html`
|
||||
<div class="flex justify-center py-3 shrink-0">
|
||||
<o-button
|
||||
@@ -176,7 +200,7 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
}}
|
||||
></o-button>
|
||||
</div>
|
||||
<div class="px-3 pb-3">${this.renderSkinGrid()}</div>
|
||||
<div class="px-3 pb-3">${grid}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -202,8 +226,8 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
);
|
||||
this.selectedSkinName = skinName;
|
||||
this.selectedPattern = null;
|
||||
// Stay open — the tile highlight moves to the new selection.
|
||||
this.refresh();
|
||||
this.close();
|
||||
}
|
||||
|
||||
private selectPattern(pattern: PlayerPattern | null) {
|
||||
@@ -219,9 +243,9 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
}
|
||||
this.selectedPattern = pattern;
|
||||
this.selectedSkinName = null;
|
||||
// Stay open — the tile highlight moves to the new selection.
|
||||
this.refresh();
|
||||
this.showSkinSelectedPopup();
|
||||
this.close();
|
||||
}
|
||||
|
||||
private showSkinSelectedPopup() {
|
||||
@@ -1,111 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import {
|
||||
findEffect,
|
||||
isTrailEffect,
|
||||
TRAIL_EFFECT_TYPES,
|
||||
TrailEffectAttributes,
|
||||
} from "../core/CosmeticSchemas";
|
||||
import {
|
||||
EFFECTS_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
} from "../core/game/UserSettings";
|
||||
import "./components/EffectPreview"; // registers <trail-swatch>
|
||||
import { fetchCosmetics, getPlayerCosmetics } from "./Cosmetics";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
@customElement("effects-input")
|
||||
export class EffectsInput extends LitElement {
|
||||
// The selected trail effect's attributes for the button preview, if any.
|
||||
// Not named `attributes` — that collides with HTMLElement.attributes.
|
||||
@state() private trailAttributes: TrailEffectAttributes | null = null;
|
||||
|
||||
private _abortController: AbortController | null = null;
|
||||
|
||||
// PlayerEffect is just { name, effectType }; resolve the visual style from the
|
||||
// cosmetics catalog by (effectType, name). The button shows a single trail
|
||||
// swatch, so preview the first selected trail effect across trail effectTypes
|
||||
// (boat trail before nuke trail, per TRAIL_EFFECT_TYPES order). nukeExplosion
|
||||
// is not a trail and has no swatch preview here.
|
||||
private async resolveTrailAttributes(): Promise<TrailEffectAttributes | null> {
|
||||
const cosmetics = await getPlayerCosmetics();
|
||||
const catalog = await fetchCosmetics();
|
||||
for (const effectType of TRAIL_EFFECT_TYPES) {
|
||||
const name = cosmetics.effects?.[effectType]?.name;
|
||||
if (!name) continue;
|
||||
const effect = findEffect(catalog, effectType, name);
|
||||
if (effect && isTrailEffect(effect)) return effect.attributes;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _onCosmeticSelected = async () => {
|
||||
this.trailAttributes = await this.resolveTrailAttributes();
|
||||
};
|
||||
|
||||
private onInputClick(e: Event) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("effects-input-click", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._abortController = new AbortController();
|
||||
this.trailAttributes = await this.resolveTrailAttributes();
|
||||
window.addEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${EFFECTS_KEY}`,
|
||||
this._onCosmeticSelected,
|
||||
{ signal: this._abortController.signal },
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._abortController) {
|
||||
this._abortController.abort();
|
||||
this._abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (crazyGamesSDK.isOnCrazyGames()) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const preview =
|
||||
this.trailAttributes === null
|
||||
? html`<span
|
||||
class="text-[7px] lg:text-[10px] font-black tracking-wider text-white uppercase leading-tight lg:leading-none w-full text-center px-0.5 lg:px-1"
|
||||
>
|
||||
${translateText("effects.title")}
|
||||
</span>`
|
||||
: html`<span class="w-full h-full p-1.5">
|
||||
<trail-swatch
|
||||
class="block w-full h-full"
|
||||
.trail=${this.trailAttributes}
|
||||
></trail-swatch>
|
||||
</span>`;
|
||||
|
||||
return html`
|
||||
<button
|
||||
id="effects-input"
|
||||
class="p-0 m-0 border-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-surface hover:brightness-[1.08] active:brightness-[0.95] hover:shadow-[var(--shadow-action-card-hover)] rounded-lg overflow-hidden"
|
||||
title=${translateText("effects.button_title")}
|
||||
@click=${this.onInputClick}
|
||||
>
|
||||
${preview}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { Cosmetics, EFFECT_TYPES } from "../core/CosmeticSchemas";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/EffectsGrid";
|
||||
import "./components/NotLoggedInWarning";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
import { fetchCosmetics } from "./Cosmetics";
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
@customElement("effects-modal")
|
||||
export class EffectsModal extends BaseModal {
|
||||
protected routerName = "effects";
|
||||
|
||||
@state() private cosmetics: Cosmetics | null = null;
|
||||
@state() private userMeResponse: UserMeResponse | false = false;
|
||||
@state() private search = "";
|
||||
|
||||
// One tab per trail effectType; BaseModal owns activeTab + renders the bar.
|
||||
protected modalConfig() {
|
||||
return {
|
||||
tabs: EFFECT_TYPES.map((type) => ({
|
||||
key: type,
|
||||
label: translateText(`effects.type.${type}`),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private handleSearch(event: Event) {
|
||||
this.search = (event.target as HTMLInputElement).value;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
document.addEventListener(
|
||||
"userMeResponse",
|
||||
(event: CustomEvent<UserMeResponse | false>) => {
|
||||
this.onUserMe(event.detail);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async onUserMe(userMeResponse: UserMeResponse | false) {
|
||||
this.userMeResponse = userMeResponse;
|
||||
this.cosmetics = await fetchCosmetics();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
protected renderHeaderSlot() {
|
||||
return html`
|
||||
<div
|
||||
class="relative flex flex-col border-b border-white/10 pb-4 shrink-0"
|
||||
>
|
||||
${modalHeader({
|
||||
title: translateText("effects.title"),
|
||||
onBack: () => this.close(),
|
||||
ariaLabel: translateText("common.back"),
|
||||
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
|
||||
})}
|
||||
|
||||
<div class="md:flex items-center gap-2 justify-center mt-4">
|
||||
<input
|
||||
class="h-12 w-full max-w-md border border-white/10 bg-black/60
|
||||
rounded-xl shadow-inner text-xl text-center focus:outline-none
|
||||
focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 text-white placeholder-white/30 transition-all"
|
||||
type="text"
|
||||
placeholder=${translateText("effects.search")}
|
||||
.value=${this.search}
|
||||
@change=${this.handleSearch}
|
||||
@keyup=${this.handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderBody(tab: string) {
|
||||
return html`
|
||||
<div class="flex flex-col">
|
||||
<div class="flex justify-center py-3 shrink-0">
|
||||
<o-button
|
||||
class="no-crazygames"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
translationKey="main.store"
|
||||
@click=${() => {
|
||||
this.close();
|
||||
window.showPage?.("page-item-store");
|
||||
}}
|
||||
></o-button>
|
||||
</div>
|
||||
<effects-grid
|
||||
mode="select"
|
||||
.cosmetics=${this.cosmetics}
|
||||
.userMeResponse=${this.userMeResponse}
|
||||
.search=${this.search}
|
||||
.effectType=${tab}
|
||||
></effects-grid>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -225,10 +225,10 @@ export class LangSelector extends LitElement {
|
||||
"user-setting",
|
||||
"o-modal",
|
||||
"o-button",
|
||||
"territory-patterns-modal",
|
||||
"cosmetics-modal",
|
||||
"store-modal",
|
||||
"custom-currency-card",
|
||||
"pattern-input",
|
||||
"cosmetics-input",
|
||||
"fluent-slider",
|
||||
"news-modal",
|
||||
"news-button",
|
||||
@@ -236,8 +236,6 @@ export class LangSelector extends LitElement {
|
||||
"leaderboard-modal",
|
||||
"flag-input-modal",
|
||||
"flag-input",
|
||||
"effects-modal",
|
||||
"effects-input",
|
||||
"effects-grid",
|
||||
"matchmaking-button",
|
||||
"token-login",
|
||||
|
||||
+16
-45
@@ -19,11 +19,11 @@ import { reauthAfterCrazyGamesChange, userAuth } from "./Auth";
|
||||
import "./ClanModal";
|
||||
import { joinLobby, type JoinLobbyResult } from "./ClientGameRunner";
|
||||
import { getPlayerCosmeticsRefs } from "./Cosmetics";
|
||||
import "./CosmeticsInput";
|
||||
import "./CosmeticsModal";
|
||||
import { CosmeticsModal } from "./CosmeticsModal";
|
||||
import { updateCrazyGamesNavButton } from "./CrazyGamesAccountButton";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
import "./EffectsInput";
|
||||
import "./EffectsModal";
|
||||
import { EffectsModal } from "./EffectsModal";
|
||||
import "./FlagInput";
|
||||
import { FlagInput } from "./FlagInput";
|
||||
import "./FlagInputModal";
|
||||
@@ -47,12 +47,9 @@ import { MatchmakingModal } from "./Matchmaking";
|
||||
import { modalRouter } from "./ModalRouter";
|
||||
import { initNavigation } from "./Navigation";
|
||||
import "./NewsModal";
|
||||
import "./PatternInput";
|
||||
import { RewardsModal } from "./RewardsModal";
|
||||
import "./SinglePlayerModal";
|
||||
import { StoreModal } from "./Store";
|
||||
import "./TerritoryPatternsModal";
|
||||
import { TerritoryPatternsModal } from "./TerritoryPatternsModal";
|
||||
import { TokenLoginModal } from "./TokenLoginModal";
|
||||
import {
|
||||
SendKickPlayerIntentEvent,
|
||||
@@ -321,11 +318,8 @@ class Client {
|
||||
tag: "troubleshooting-modal",
|
||||
pageId: "page-troubleshooting",
|
||||
});
|
||||
modalRouter.register("territory-patterns", {
|
||||
tag: "territory-patterns-modal",
|
||||
});
|
||||
modalRouter.register("cosmetics", { tag: "cosmetics-modal" });
|
||||
modalRouter.register("flag-input", { tag: "flag-input-modal" });
|
||||
modalRouter.register("effects", { tag: "effects-modal" });
|
||||
|
||||
// Prefetch turnstile token so it is available when
|
||||
// the user joins a lobby.
|
||||
@@ -436,52 +430,30 @@ class Client {
|
||||
});
|
||||
});
|
||||
|
||||
const effectsModal = document.querySelector(
|
||||
"effects-modal",
|
||||
) as EffectsModal;
|
||||
if (!effectsModal || !(effectsModal instanceof EffectsModal)) {
|
||||
console.warn("Effects modal element not found");
|
||||
}
|
||||
document.querySelectorAll("effects-input").forEach((effectsInput) => {
|
||||
effectsInput.addEventListener("effects-input-click", () => {
|
||||
if (effectsModal && effectsModal instanceof EffectsModal) {
|
||||
effectsModal.open();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.storeModal = document.getElementById("page-item-store") as StoreModal;
|
||||
if (!this.storeModal || !(this.storeModal instanceof StoreModal)) {
|
||||
console.warn("Store modal element not found");
|
||||
}
|
||||
|
||||
const patternsModal = document.getElementById(
|
||||
"territory-patterns-modal",
|
||||
) as TerritoryPatternsModal;
|
||||
if (!patternsModal || !(patternsModal instanceof TerritoryPatternsModal)) {
|
||||
console.warn("Patterns modal element not found");
|
||||
const cosmeticsModal = document.getElementById(
|
||||
"cosmetics-modal",
|
||||
) as CosmeticsModal;
|
||||
if (!cosmeticsModal || !(cosmeticsModal instanceof CosmeticsModal)) {
|
||||
console.warn("Cosmetics modal element not found");
|
||||
}
|
||||
|
||||
// Attach listener to any pattern-input component
|
||||
document.querySelectorAll("pattern-input").forEach((patternInput) => {
|
||||
patternInput.addEventListener("pattern-input-click", () => {
|
||||
patternsModal.open();
|
||||
// Attach listener to any cosmetics-input component
|
||||
document.querySelectorAll("cosmetics-input").forEach((cosmeticsInput) => {
|
||||
cosmeticsInput.addEventListener("cosmetics-input-click", () => {
|
||||
cosmeticsModal.open();
|
||||
});
|
||||
});
|
||||
|
||||
if (isInIframe()) {
|
||||
const mobilePat = document.getElementById("pattern-input-mobile");
|
||||
if (mobilePat) mobilePat.style.display = "none";
|
||||
const mobileCosmetics = document.getElementById("cosmetics-input-mobile");
|
||||
if (mobileCosmetics) mobileCosmetics.style.display = "none";
|
||||
}
|
||||
|
||||
if (!this.storeModal || !(this.storeModal instanceof StoreModal)) {
|
||||
console.warn("Store modal element not found");
|
||||
}
|
||||
|
||||
// We no longer need to manually manage the preview button as PatternInput handles it component-side.
|
||||
// However, we still want to ensure the modal can be opened.
|
||||
// The setupPatternInput above handles the click event for the new buttons.
|
||||
|
||||
this.storeModal.refresh();
|
||||
|
||||
window.addEventListener("showPage", (e: any) => {
|
||||
@@ -958,12 +930,11 @@ class Client {
|
||||
"help-modal",
|
||||
"user-setting",
|
||||
"troubleshooting-modal",
|
||||
"territory-patterns-modal",
|
||||
"cosmetics-modal",
|
||||
"store-modal",
|
||||
"language-modal",
|
||||
"news-modal",
|
||||
"flag-input-modal",
|
||||
"effects-modal",
|
||||
"account-button",
|
||||
"leaderboard-button",
|
||||
"token-login",
|
||||
|
||||
@@ -121,40 +121,32 @@ export class PlayPage extends LitElement {
|
||||
<username-input
|
||||
class="flex-1 min-w-0 h-10 lg:h-[50px]"
|
||||
></username-input>
|
||||
<pattern-input
|
||||
id="pattern-input-mobile"
|
||||
show-select-label
|
||||
adaptive-size
|
||||
class="shrink-0 lg:hidden"
|
||||
></pattern-input>
|
||||
<flag-input
|
||||
id="flag-input-mobile"
|
||||
show-select-label
|
||||
class="shrink-0 lg:hidden h-10 w-10"
|
||||
></flag-input>
|
||||
<effects-input
|
||||
id="effects-input-mobile"
|
||||
class="shrink-0 lg:hidden h-10 w-10"
|
||||
></effects-input>
|
||||
<cosmetics-input
|
||||
id="cosmetics-input-mobile"
|
||||
show-select-label
|
||||
adaptive-size
|
||||
class="no-crazygames shrink-0 lg:hidden"
|
||||
></cosmetics-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skin + flag: right col -->
|
||||
<!-- Flag + cosmetics: right col -->
|
||||
<div class="hidden lg:flex h-[60px] gap-2">
|
||||
<pattern-input
|
||||
id="pattern-input-desktop"
|
||||
show-select-label
|
||||
class="flex-1 h-full"
|
||||
></pattern-input>
|
||||
<flag-input
|
||||
id="flag-input-desktop"
|
||||
show-select-label
|
||||
class="flex-1 h-full"
|
||||
></flag-input>
|
||||
<effects-input
|
||||
id="effects-input-desktop"
|
||||
class="flex-1 h-full"
|
||||
></effects-input>
|
||||
<cosmetics-input
|
||||
id="cosmetics-input-desktop"
|
||||
show-select-label
|
||||
class="no-crazygames flex-1 h-full"
|
||||
></cosmetics-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user