mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-15 14:46:03 +00:00
Feat: Favourite maps tab (#4207)
Resolves #4202 ## Description: As suggested in some suggestions in the main OF server [[thread](https://discord.com/channels/1284581928254701718/1472496670267805782)], we should have a map favouriting system since there are over 70 maps already. People (myself included) have some maps we constantly play during solo/private matches, so a favourite tab would be huge. This feature adds the favourites tab to the solo and private match selection screens. It works using localStorage for saving (device persistence) but I can just as easily implement an infra update where players have a 1-many relation with a `FavouriteMaps` table. That can be a future solution. Video example right now: https://github.com/user-attachments/assets/e8e278ab-d305-499a-81a9-d570e05db051 ## 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: bijx
This commit is contained in:
@@ -8,8 +8,11 @@ import {
|
||||
} from "../../../core/game/Game";
|
||||
import { translateText } from "../../Utils";
|
||||
import "./MapDisplay";
|
||||
import { getFavoriteMaps, starIcon, toggleFavoriteMap } from "./MapFavorites";
|
||||
const randomMap = assetUrl("images/RandomMap.webp");
|
||||
|
||||
type MapTab = "featured" | "all" | "favorites";
|
||||
|
||||
const featuredMaps: GameMapType[] = [
|
||||
GameMapType.World,
|
||||
GameMapType.Europe,
|
||||
@@ -30,12 +33,17 @@ export class MapPicker extends LitElement {
|
||||
new Map();
|
||||
@property({ attribute: false }) onSelectMap?: (map: GameMapType) => void;
|
||||
@property({ attribute: false }) onSelectRandom?: () => void;
|
||||
@state() private showAllMaps = false;
|
||||
@state() private activeTab: MapTab = "featured";
|
||||
@state() private favorites: GameMapType[] = getFavoriteMaps();
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private handleToggleFavorite(mapValue: GameMapType) {
|
||||
this.favorites = toggleFavoriteMap(mapValue);
|
||||
}
|
||||
|
||||
private handleMapSelection(mapValue: GameMapType) {
|
||||
this.onSelectMap?.(mapValue);
|
||||
}
|
||||
@@ -66,6 +74,8 @@ export class MapPicker extends LitElement {
|
||||
.selected=${!this.useRandomMap && this.selectedMap === mapValue}
|
||||
.showMedals=${this.showMedals}
|
||||
.wins=${this.getWins(mapValue)}
|
||||
.favorite=${this.favorites.includes(mapValue)}
|
||||
.onToggleFavorite=${() => this.handleToggleFavorite(mapValue)}
|
||||
.translation=${translateText(`map.${mapKey?.toLowerCase()}`)}
|
||||
></map-display>
|
||||
</div>
|
||||
@@ -109,6 +119,55 @@ export class MapPicker extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderFavoriteMaps() {
|
||||
if (this.favorites.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"
|
||||
>
|
||||
<div class="text-white/30">${starIcon(false, "w-8 h-8")}</div>
|
||||
<p class="text-sm text-white/50 leading-relaxed max-w-xs">
|
||||
${translateText("map_component.favorites_empty")}
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
return html`<div class="w-full">
|
||||
<h4
|
||||
class="text-xs font-bold text-white/40 uppercase tracking-widest mb-4 pl-2"
|
||||
>
|
||||
${translateText("map_categories.favorites")}
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
${this.favorites.map((mapValue) => this.renderMapCard(mapValue))}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderActiveTab() {
|
||||
switch (this.activeTab) {
|
||||
case "all":
|
||||
return this.renderAllMaps();
|
||||
case "favorites":
|
||||
return this.renderFavoriteMaps();
|
||||
default:
|
||||
return this.renderFeaturedMaps();
|
||||
}
|
||||
}
|
||||
|
||||
private renderTabButton(tab: MapTab, label: string) {
|
||||
const isActive = this.activeTab === tab;
|
||||
return html`<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected=${isActive}
|
||||
class="px-3 py-2 rounded-lg text-xs font-bold uppercase tracking-wider transition-all active:scale-95 ${isActive
|
||||
? "bg-malibu-blue/20 text-white shadow-[var(--shadow-malibu-blue-soft)]"
|
||||
: "text-white/60 hover:text-white"}"
|
||||
@click=${() => (this.activeTab = tab)}
|
||||
>
|
||||
${label}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="space-y-8">
|
||||
@@ -116,35 +175,14 @@ export class MapPicker extends LitElement {
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label="${translateText("map.map")}"
|
||||
class="grid grid-cols-2 gap-2 rounded-xl border border-white/10 bg-black/20 p-1"
|
||||
class="grid grid-cols-3 gap-2 rounded-xl border border-white/10 bg-black/20 p-1"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected=${!this.showAllMaps}
|
||||
class="px-3 py-2 rounded-lg text-xs font-bold uppercase tracking-wider transition-all active:scale-95 ${this
|
||||
.showAllMaps
|
||||
? "text-white/60 hover:text-white"
|
||||
: "bg-malibu-blue/20 text-white shadow-[var(--shadow-malibu-blue-soft)]"}"
|
||||
@click=${() => (this.showAllMaps = false)}
|
||||
>
|
||||
${translateText("map.featured")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected=${this.showAllMaps}
|
||||
class="px-3 py-2 rounded-lg text-xs font-bold uppercase tracking-wider transition-all active:scale-95 ${this
|
||||
.showAllMaps
|
||||
? "bg-malibu-blue/20 text-white shadow-[var(--shadow-malibu-blue-soft)]"
|
||||
: "text-white/60 hover:text-white"}"
|
||||
@click=${() => (this.showAllMaps = true)}
|
||||
>
|
||||
${translateText("map.all")}
|
||||
</button>
|
||||
${this.renderTabButton("featured", translateText("map.featured"))}
|
||||
${this.renderTabButton("all", translateText("map.all"))}
|
||||
${this.renderTabButton("favorites", translateText("map.favorites"))}
|
||||
</div>
|
||||
</div>
|
||||
${this.showAllMaps ? this.renderAllMaps() : this.renderFeaturedMaps()}
|
||||
${this.renderActiveTab()}
|
||||
<div
|
||||
class="w-full ${this.randomMapDivider
|
||||
? "pt-4 border-t border-white/5"
|
||||
|
||||
Reference in New Issue
Block a user