diff --git a/resources/lang/en.json b/resources/lang/en.json index 00f699951..5dfce1e70 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -944,6 +944,9 @@ "favorite": "Add to favourites", "favorites_empty": "Click the star on any map to favourite it", "loading": "Loading...", + "no_results": "No maps found", + "search_maps": "Search maps...", + "search_results": "Search Results", "unfavorite": "Remove from favourites" }, "matchmaking_button": { diff --git a/src/client/components/GameConfigSettings.ts b/src/client/components/GameConfigSettings.ts index 39184d267..414807297 100644 --- a/src/client/components/GameConfigSettings.ts +++ b/src/client/components/GameConfigSettings.ts @@ -6,7 +6,7 @@ import { nothing, svg, } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import { Difficulty, Duos, @@ -80,10 +80,18 @@ function renderSection( titleKey: string, content: TemplateResult | TemplateResult[], sectionClass = "space-y-6", + headerAction?: TemplateResult, ): TemplateResult { return html`
- ${renderSectionHeader(iconSvg, colorClass, bgClass, titleKey)} ${content} + ${renderSectionHeader( + iconSvg, + colorClass, + bgClass, + titleKey, + headerAction, + )} + ${content}
`; } @@ -139,6 +147,7 @@ function renderSectionHeader( colorClass: string, bgClass: string, titleKey: string, + headerAction?: TemplateResult, ): TemplateResult { return html`
@@ -157,6 +166,7 @@ function renderSectionHeader(

${translateText(titleKey)}

+ ${headerAction ? html`
${headerAction}
` : null}
`; } @@ -218,6 +228,7 @@ export interface GameConfigSettingsData { export class GameConfigSettings extends LitElement { @property({ attribute: false }) settings?: GameConfigSettingsData; @property({ attribute: false }) sectionGapClass = "space-y-6"; + @state() private mapSearchQuery = ""; createRenderRoot() { return this; @@ -233,6 +244,15 @@ export class GameConfigSettings extends LitElement { ); } + private handleMapSearchInput = (event: Event) => { + const input = event.target as HTMLInputElement; + this.mapSearchQuery = input.value; + }; + + private clearMapSearch = () => { + this.mapSearchQuery = ""; + }; + private handleSelectMap = (map: GameMapType) => { this.emit("map-selected", { map }); }; @@ -309,6 +329,42 @@ export class GameConfigSettings extends LitElement { }); } + private renderMapSearchInput(): TemplateResult { + return html`
+ + + + + ${this.mapSearchQuery + ? html`` + : null} +
`; + } + render() { if (!this.settings) return nothing; const settings = this.settings; @@ -328,7 +384,10 @@ export class GameConfigSettings extends LitElement { .mapWins=${settings.map.mapWins ?? new Map()} .onSelectMap=${this.handleSelectMap} .onSelectRandom=${this.handleSelectRandom} + .searchQuery=${this.mapSearchQuery} >`, + undefined, + this.renderMapSearchInput(), )} ${renderSection( DIFFICULTY_ICON, diff --git a/src/client/components/map/MapPicker.ts b/src/client/components/map/MapPicker.ts index 705ccef46..442b08f12 100644 --- a/src/client/components/map/MapPicker.ts +++ b/src/client/components/map/MapPicker.ts @@ -36,6 +36,7 @@ export class MapPicker extends LitElement { @property({ type: Boolean }) useRandomMap = false; @property({ type: Boolean }) showMedals = false; @property({ type: Boolean }) randomMapDivider = false; + @property({ type: String }) searchQuery = ""; @property({ attribute: false }) mapWins: Map> = new Map(); @property({ attribute: false }) onSelectMap?: (map: GameMapType) => void; @@ -74,6 +75,16 @@ export class MapPicker extends LitElement { event.preventDefault(); } + private get filteredMaps(): MapInfo[] { + if (!this.searchQuery.trim()) return []; + const query = this.searchQuery.trim().toLowerCase(); + return maps.filter((m) => { + const name = translateText(m.translationKey).toLowerCase(); + const id = m.id.toLowerCase(); + return name.includes(query) || id.includes(query); + }); + } + private getWins(mapValue: GameMapType): Set { return this.mapWins?.get(mapValue) ?? new Set(); } @@ -211,6 +222,36 @@ export class MapPicker extends LitElement { } } + private renderSearchResults() { + const results = this.filteredMaps; + if (results.length === 0) { + return html`
+ + + +

+ ${translateText("map_component.no_results")} +

+
`; + } + return html`
+ ${this.renderSectionHeading( + `${translateText("map_component.search_results")} (${results.length})`, + )} + ${this.renderMapGrid(results)} +
`; + } + private renderTabButton(tab: MapTab, label: string) { const isActive = this.activeTab === tab; return html`