diff --git a/resources/lang/en.json b/resources/lang/en.json index b9299bbdb..84f5a17d4 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -332,12 +332,15 @@ "teams": "{num} teams", "players_per_team": "of {num}", "started": "Started", - "current": "Current public lobby", - "vote_button": "Vote on maps", + "vote_cta": "Vote for next map!", + "vote_count_label": "{count, plural, one {# vote influences the next map} other {# votes influence the next map}}", + "vote_count_tooltip": "Number of players currently voting (connected & waiting in public lobby)", "vote_title": "Map Voting", "vote_description": "Select any number of maps to influence the next public lobby.", "vote_saved": "Your selection is saved on this device.", - "vote_login_required": "Log in to submit votes." + "vote_login_required": "Log in to submit votes.", + "vote_submit": "Vote", + "vote_toast_submitted": "Vote submitted!" }, "matchmaking_modal": { "title": "1v1 Ranked Matchmaking (ALPHA)", diff --git a/src/client/LobbySocket.ts b/src/client/LobbySocket.ts index 407ef7700..dbc5aaf12 100644 --- a/src/client/LobbySocket.ts +++ b/src/client/LobbySocket.ts @@ -3,12 +3,14 @@ import { GameInfo } from "../core/Schemas"; type LobbyUpdateHandler = (lobbies: GameInfo[]) => void; type VoteRequestHandler = () => void; +type VoteStatsHandler = (activeVoteCount: number) => void; interface LobbySocketOptions { reconnectDelay?: number; maxWsAttempts?: number; pollIntervalMs?: number; onVoteRequest?: VoteRequestHandler; + onVoteStats?: VoteStatsHandler; } export class PublicLobbySocket { @@ -23,6 +25,7 @@ export class PublicLobbySocket { private readonly pollIntervalMs: number; private readonly onLobbiesUpdate: LobbyUpdateHandler; private readonly onVoteRequest?: VoteRequestHandler; + private readonly onVoteStats?: VoteStatsHandler; private pendingVote: { token: string; maps: GameMapType[] } | null = null; constructor( @@ -34,6 +37,7 @@ export class PublicLobbySocket { this.maxWsAttempts = options?.maxWsAttempts ?? 3; this.pollIntervalMs = options?.pollIntervalMs ?? 1000; this.onVoteRequest = options?.onVoteRequest; + this.onVoteStats = options?.onVoteStats; } start() { @@ -87,6 +91,9 @@ export class PublicLobbySocket { this.onLobbiesUpdate(message.data?.lobbies ?? []); } else if (message.type === "map_vote_request") { this.onVoteRequest?.(); + } else if (message.type === "map_vote_stats") { + const activeVoteCount = Number(message.data?.activeVoteCount ?? 0); + this.onVoteStats?.(activeVoteCount); } } catch (error) { console.error("Error parsing WebSocket message:", error); diff --git a/src/client/MapVoteModal.ts b/src/client/MapVoteModal.ts index 2e5189454..93c8e2c7a 100644 --- a/src/client/MapVoteModal.ts +++ b/src/client/MapVoteModal.ts @@ -59,6 +59,30 @@ export class MapVoteModal extends BaseModal { ); } + private handleVoteSubmit = () => { + const maps = Array.from(this.selectedMaps); + saveStoredMapVotes(maps); + this.dispatchEvent( + new CustomEvent("map-vote-submit", { + detail: { maps }, + bubbles: true, + composed: true, + }), + ); + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: this.loggedIn + ? translateText("public_lobby.vote_toast_submitted") + : translateText("public_lobby.vote_saved"), + color: "green", + duration: 2500, + }, + }), + ); + this.close(); + }; + private renderCategory( categoryKey: string, maps: GameMapType[], @@ -166,6 +190,25 @@ export class MapVoteModal extends BaseModal { + +