mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-07 11:25:58 +00:00
Add map search 🔍 (#4283)
## Description: Add a search input field to the map picker section header, allowing users to quickly filter maps by name. - Place transparent search input on the right side of the "Maps" section header - Filter maps by translated name and map ID as the user types - Hide Featured/All/Favourites tab buttons while search is active - Show filtered results with a count heading, or a "no results" empty state - Clear button appears when search input has text <img width="857" height="463" alt="Screenshot 2026-06-15 001415" src="https://github.com/user-attachments/assets/35e1101a-177e-4923-bb1d-34eb683c6f80" /> No search results: <img width="855" height="454" alt="Screenshot 2026-06-15 001433" src="https://github.com/user-attachments/assets/bf27211d-5891-4739-a92f-0fc44b3c9c61" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
This commit is contained in:
@@ -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<GameMapType, Set<Difficulty>> =
|
||||
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<Difficulty> {
|
||||
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`<div
|
||||
class="w-full flex flex-col items-center justify-center gap-3 py-12 px-4 text-center rounded-xl border border-dashed border-white/10 bg-black/20"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 text-white/30"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-sm text-white/50 leading-relaxed max-w-xs">
|
||||
${translateText("map_component.no_results")}
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
return html`<div class="w-full">
|
||||
${this.renderSectionHeading(
|
||||
`${translateText("map_component.search_results")} (${results.length})`,
|
||||
)}
|
||||
${this.renderMapGrid(results)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderTabButton(tab: MapTab, label: string) {
|
||||
const isActive = this.activeTab === tab;
|
||||
return html`<button
|
||||
@@ -227,20 +268,29 @@ export class MapPicker extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const isSearching = this.searchQuery.trim().length > 0;
|
||||
return html`
|
||||
<div class="space-y-8">
|
||||
<div class="w-full">
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label="${translateText("map.map")}"
|
||||
class="grid grid-cols-3 gap-2 rounded-xl border border-white/10 bg-black/20 p-1"
|
||||
>
|
||||
${this.renderTabButton("featured", translateText("map.featured"))}
|
||||
${this.renderTabButton("all", translateText("map.all"))}
|
||||
${this.renderTabButton("favorites", translateText("map.favorites"))}
|
||||
</div>
|
||||
${isSearching
|
||||
? null
|
||||
: html`<div
|
||||
role="tablist"
|
||||
aria-label="${translateText("map.map")}"
|
||||
class="grid grid-cols-3 gap-2 rounded-xl border border-white/10 bg-black/20 p-1"
|
||||
>
|
||||
${this.renderTabButton(
|
||||
"featured",
|
||||
translateText("map.featured"),
|
||||
)}
|
||||
${this.renderTabButton("all", translateText("map.all"))}
|
||||
${this.renderTabButton(
|
||||
"favorites",
|
||||
translateText("map.favorites"),
|
||||
)}
|
||||
</div>`}
|
||||
</div>
|
||||
${this.renderActiveTab()}
|
||||
${isSearching ? this.renderSearchResults() : this.renderActiveTab()}
|
||||
<div
|
||||
class="w-full ${this.randomMapDivider
|
||||
? "pt-4 border-t border-white/5"
|
||||
|
||||
Reference in New Issue
Block a user