diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index 03e0a6ed0..e5bb92f11 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -33,6 +33,10 @@ import { modalHeader } from "./components/ui/ModalHeader"; import { crazyGamesSDK } from "./CrazyGamesSDK"; import { JoinLobbyEvent } from "./Main"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; +import { + renderToggleInputCard, + renderToggleInputCardInput, +} from "./utilities/RenderToggleInputCard"; import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions"; import randomMap from "/images/RandomMap.webp?url"; @customElement("host-lobby-modal") @@ -127,6 +131,35 @@ export class HostLobbyModal extends BaseModal { } render() { + const maxTimerHandlers = this.createToggleHandlers( + () => this.maxTimer, + (val) => (this.maxTimer = val), + () => this.maxTimerValue, + (val) => (this.maxTimerValue = val), + 30, + ); + const spawnImmunityHandlers = this.createToggleHandlers( + () => this.spawnImmunity, + (val) => (this.spawnImmunity = val), + () => this.spawnImmunityDurationMinutes, + (val) => (this.spawnImmunityDurationMinutes = val), + 5, + ); + const goldMultiplierHandlers = this.createToggleHandlers( + () => this.goldMultiplier, + (val) => (this.goldMultiplier = val), + () => this.goldMultiplierValue, + (val) => (this.goldMultiplierValue = val), + 2, + ); + const startingGoldHandlers = this.createToggleHandlers( + () => this.startingGold, + (val) => (this.startingGold = val), + () => this.startingGoldValue, + (val) => (this.startingGoldValue = val), + 5000000, + ); + const content = html`
-
this.maxTimer, - (val) => (this.maxTimer = val), - () => this.maxTimerValue, - (val) => (this.maxTimerValue = val), - 30, - ).click} - @keydown=${this.createToggleHandlers( - () => this.maxTimer, - (val) => (this.maxTimer = val), - () => this.maxTimerValue, - (val) => (this.maxTimerValue = val), - 30, - ).keydown} - class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this - .maxTimer - ? "bg-blue-500/20 border-blue-500/50 shadow-[0_0_15px_rgba(59,130,246,0.2)]" - : "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20 opacity-80"}" - > -
-
- ${this.maxTimer - ? html` - - ` - : ""} -
-
- - ${this.maxTimer - ? html` - e.stopPropagation()} - @input=${this.handleMaxTimerValueChanges} - @keydown=${this.handleMaxTimerValueKeyDown} - placeholder=${translateText( - "host_modal.mins_placeholder", - )} - /> - ` - : html`
`} - -
- ${translateText("host_modal.max_timer")} -
-
+ ${renderToggleInputCard({ + labelKey: "host_modal.max_timer", + checked: this.maxTimer, + onClick: maxTimerHandlers.click, + input: renderToggleInputCardInput({ + min: 0, + max: 120, + value: this.maxTimerValue ?? 0, + ariaLabel: translateText("host_modal.max_timer"), + placeholder: translateText("host_modal.mins_placeholder"), + onInput: this.handleMaxTimerValueChanges, + onKeyDown: this.handleMaxTimerValueKeyDown, + }), + })} -
this.spawnImmunity, - (val) => (this.spawnImmunity = val), - () => this.spawnImmunityDurationMinutes, - (val) => (this.spawnImmunityDurationMinutes = val), - 5, - ).click} - @keydown=${this.createToggleHandlers( - () => this.spawnImmunity, - (val) => (this.spawnImmunity = val), - () => this.spawnImmunityDurationMinutes, - (val) => (this.spawnImmunityDurationMinutes = val), - 5, - ).keydown} - class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this - .spawnImmunity - ? "bg-blue-500/20 border-blue-500/50 shadow-[0_0_15px_rgba(59,130,246,0.2)]" - : "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20 opacity-80"}" - > -
-
- ${this.spawnImmunity - ? html` - - ` - : ""} -
-
- - ${this.spawnImmunity - ? html` - e.stopPropagation()} - @input=${this.handleSpawnImmunityDurationInput} - @keydown=${this.handleSpawnImmunityDurationKeyDown} - placeholder=${translateText( - "host_modal.mins_placeholder", - )} - /> - ` - : html`
`} - -
- ${translateText("host_modal.player_immunity_duration")} -
-
+ ${renderToggleInputCard({ + labelKey: "host_modal.player_immunity_duration", + checked: this.spawnImmunity, + onClick: spawnImmunityHandlers.click, + input: renderToggleInputCardInput({ + min: 0, + max: 120, + step: 1, + value: this.spawnImmunityDurationMinutes ?? 0, + ariaLabel: translateText( + "host_modal.player_immunity_duration", + ), + placeholder: translateText("host_modal.mins_placeholder"), + onInput: this.handleSpawnImmunityDurationInput, + onKeyDown: this.handleSpawnImmunityDurationKeyDown, + }), + })} -
this.goldMultiplier, - (val) => (this.goldMultiplier = val), - () => this.goldMultiplierValue, - (val) => (this.goldMultiplierValue = val), - 2, - ).click} - @keydown=${this.createToggleHandlers( - () => this.goldMultiplier, - (val) => (this.goldMultiplier = val), - () => this.goldMultiplierValue, - (val) => (this.goldMultiplierValue = val), - 2, - ).keydown} - class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this - .goldMultiplier - ? "bg-blue-500/20 border-blue-500/50" - : "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20"}" - > -
-
- ${this.goldMultiplier - ? html` - - ` - : ""} -
-
- - ${this.goldMultiplier - ? html`` - : html`
`} - -
- ${translateText("single_modal.gold_multiplier")} -
-
+ ${renderToggleInputCard({ + labelKey: "single_modal.gold_multiplier", + checked: this.goldMultiplier, + onClick: goldMultiplierHandlers.click, + input: renderToggleInputCardInput({ + id: "gold-multiplier-value", + min: 0.1, + max: 1000, + step: "any", + value: this.goldMultiplierValue ?? "", + ariaLabel: translateText("single_modal.gold_multiplier"), + placeholder: translateText( + "single_modal.gold_multiplier_placeholder", + ), + onChange: this.handleGoldMultiplierValueChanges, + onKeyDown: this.handleGoldMultiplierValueKeyDown, + }), + })} -
this.startingGold, - (val) => (this.startingGold = val), - () => this.startingGoldValue, - (val) => (this.startingGoldValue = val), - 5000000, - ).click} - @keydown=${this.createToggleHandlers( - () => this.startingGold, - (val) => (this.startingGold = val), - () => this.startingGoldValue, - (val) => (this.startingGoldValue = val), - 5000000, - ).keydown} - class="relative p-3 rounded-xl border transition-all duration-200 flex flex-col items-center justify-between gap-2 h-full cursor-pointer min-h-[100px] ${this - .startingGold - ? "bg-blue-500/20 border-blue-500/50" - : "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20"}" - > -
-
- ${this.startingGold - ? html` - - ` - : ""} -
-
- - ${this.startingGold - ? html`` - : html`
`} - -
- ${translateText("single_modal.starting_gold")} -
-
+ ${renderToggleInputCard({ + labelKey: "single_modal.starting_gold", + checked: this.startingGold, + onClick: startingGoldHandlers.click, + input: renderToggleInputCardInput({ + id: "starting-gold-value", + min: 0, + max: 1000000000, + step: 100000, + value: this.startingGoldValue ?? "", + ariaLabel: translateText("single_modal.starting_gold"), + placeholder: translateText( + "single_modal.starting_gold_placeholder", + ), + onInput: this.handleStartingGoldValueChanges, + onKeyDown: this.handleStartingGoldValueKeyDown, + }), + })}
diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts index c34dfe268..dece359e4 100644 --- a/src/client/SinglePlayerModal.ts +++ b/src/client/SinglePlayerModal.ts @@ -30,6 +30,10 @@ import { fetchCosmetics } from "./Cosmetics"; import { FlagInput } from "./FlagInput"; import { JoinLobbyEvent } from "./Main"; import { UsernameInput } from "./UsernameInput"; +import { + renderToggleInputCard, + renderToggleInputCardInput, +} from "./utilities/RenderToggleInputCard"; import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions"; import randomMap from "/images/RandomMap.webp?url"; @@ -522,20 +526,10 @@ export class SinglePlayerModal extends BaseModal { } }, )} - - -
{ - // Prevent toggling when clicking the input - if ( - (e.target as HTMLElement).tagName.toLowerCase() === - "input" - ) - return; + ${renderToggleInputCard({ + labelKey: "single_modal.max_timer", + checked: this.maxTimer, + onClick: () => { this.maxTimer = !this.maxTimer; if (!this.maxTimer) { this.maxTimerValue = undefined; @@ -553,71 +547,26 @@ export class SinglePlayerModal extends BaseModal { } }, 0); } - }} - > -
-
- ${this.maxTimer - ? html` - - ` - : ""} -
-
- - ${this.maxTimer - ? html`` - : html`
`} - - -
- ${translateText("single_modal.max_timer")} -
-
+ }, + input: renderToggleInputCardInput({ + id: "end-timer-value", + min: 1, + max: 120, + value: this.maxTimerValue ?? "", + ariaLabel: translateText("single_modal.max_timer"), + placeholder: translateText( + "single_modal.max_timer_placeholder", + ), + onInput: this.handleMaxTimerValueChanges, + onKeyDown: this.handleMaxTimerValueKeyDown, + }), + })} -
{ - if ( - (e.target as HTMLElement).tagName.toLowerCase() === - "input" - ) - return; + ${renderToggleInputCard({ + labelKey: "single_modal.gold_multiplier", + checked: this.goldMultiplier, + onClick: () => { this.goldMultiplier = !this.goldMultiplier; if (!this.goldMultiplier) { this.goldMultiplierValue = undefined; @@ -638,73 +587,27 @@ export class SinglePlayerModal extends BaseModal { } }, 0); } - }} - > -
-
- ${this.goldMultiplier - ? html` - - ` - : ""} -
-
- - ${this.goldMultiplier - ? html`` - : html`
`} - -
- ${translateText("single_modal.gold_multiplier")} -
-
+ }, + input: renderToggleInputCardInput({ + id: "gold-multiplier-value", + min: 0.1, + max: 1000, + step: "any", + value: this.goldMultiplierValue ?? "", + ariaLabel: translateText("single_modal.gold_multiplier"), + placeholder: translateText( + "single_modal.gold_multiplier_placeholder", + ), + onChange: this.handleGoldMultiplierValueChanges, + onKeyDown: this.handleGoldMultiplierValueKeyDown, + }), + })} -
{ - if ( - (e.target as HTMLElement).tagName.toLowerCase() === - "input" - ) - return; + ${renderToggleInputCard({ + labelKey: "single_modal.starting_gold", + checked: this.startingGold, + onClick: () => { this.startingGold = !this.startingGold; if (!this.startingGold) { this.startingGoldValue = undefined; @@ -725,60 +628,21 @@ export class SinglePlayerModal extends BaseModal { } }, 0); } - }} - > -
-
- ${this.startingGold - ? html` - - ` - : ""} -
-
- - ${this.startingGold - ? html`` - : html`
`} - -
- ${translateText("single_modal.starting_gold")} -
-
+ }, + input: renderToggleInputCardInput({ + id: "starting-gold-value", + min: 0, + max: 1000000000, + step: 100000, + value: this.startingGoldValue ?? "", + ariaLabel: translateText("single_modal.starting_gold"), + placeholder: translateText( + "single_modal.starting_gold_placeholder", + ), + onInput: this.handleStartingGoldValueChanges, + onKeyDown: this.handleStartingGoldValueKeyDown, + }), + })} diff --git a/src/client/utilities/RenderToggleInputCard.ts b/src/client/utilities/RenderToggleInputCard.ts new file mode 100644 index 000000000..44fbd437e --- /dev/null +++ b/src/client/utilities/RenderToggleInputCard.ts @@ -0,0 +1,162 @@ +import { TemplateResult, html, nothing } from "lit"; +import { translateText } from "../Utils"; + +export const TOGGLE_INPUT_CARD_CLASSES = { + containerActive: + "bg-blue-500/20 border-blue-500/50 shadow-[0_0_15px_rgba(59,130,246,0.2)]", + containerInactive: + "bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20 opacity-80", + labelBase: + "text-[10px] uppercase font-bold tracking-wider text-center w-full leading-tight break-words hyphens-auto", + labelActive: "text-white", + labelInactive: "text-white/60", + input: + "w-full text-center rounded bg-black/60 text-white text-sm font-bold border border-white/20 focus:outline-none focus:border-blue-500 p-1 my-1", +}; + +export interface ToggleInputCardInputOptions { + id?: string; + type?: string; + min?: number | string; + max?: number | string; + step?: number | string; + value?: number | string; + ariaLabel?: string; + placeholder?: string; + onInput?: (e: Event) => void; + onChange?: (e: Event) => void; + onKeyDown?: (e: KeyboardEvent) => void; + onClick?: (e: Event) => void; + className?: string; +} + +export function renderToggleInputCardInput({ + id, + type = "number", + min, + max, + step, + value, + ariaLabel, + placeholder, + onInput, + onChange, + onKeyDown, + onClick, + className = TOGGLE_INPUT_CARD_CLASSES.input, +}: ToggleInputCardInputOptions): TemplateResult { + const resolvedValue = value ?? ""; + const handleClick = onClick ?? ((e: Event) => e.stopPropagation()); + + return html` + + `; +} + +export interface ToggleInputCardRenderContext { + labelKey: string; + checked: boolean; + input?: TemplateResult; + onClick?: (e: Event) => void; + onKeyDown?: (e: KeyboardEvent) => void; + activeClassName?: string; + inactiveClassName?: string; + labelBaseClassName?: string; + labelActiveClassName?: string; + labelInactiveClassName?: string; + role?: string; + tabIndex?: number; +} + +export function renderToggleInputCard({ + labelKey, + checked, + input, + onClick, + onKeyDown, + activeClassName = TOGGLE_INPUT_CARD_CLASSES.containerActive, + inactiveClassName = TOGGLE_INPUT_CARD_CLASSES.containerInactive, + labelBaseClassName = TOGGLE_INPUT_CARD_CLASSES.labelBase, + labelActiveClassName = TOGGLE_INPUT_CARD_CLASSES.labelActive, + labelInactiveClassName = TOGGLE_INPUT_CARD_CLASSES.labelInactive, + role, + tabIndex, +}: ToggleInputCardRenderContext): TemplateResult { + const shouldBehaveLikeButton = Boolean(onClick ?? onKeyDown); + const resolvedRole = role ?? (shouldBehaveLikeButton ? "button" : undefined); + const resolvedTabIndex = tabIndex ?? (shouldBehaveLikeButton ? 0 : undefined); + const resolvedOnKeyDown = + onKeyDown ?? + (onClick + ? (e: KeyboardEvent) => { + if ((e.target as HTMLElement).tagName.toLowerCase() === "input") { + return; + } + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onClick(e); + } + } + : undefined); + + return html` +
+
+
+ ${checked + ? html` + + ` + : ""} +
+
+ + ${checked + ? (input ?? html``) + : html`
`} + +
+ ${translateText(labelKey)} +
+
+ `; +}