mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:10:42 +00:00
607e5b5ff0
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors
This is commit pack
This PR refactors and improves the language selection experience:
• Centralizes all language-related logic in LangSelector.ts &
LanguageModal.ts
• Redesigns the language selection UI for better UX across devices
• Adds new translations and supports more languages
Changes .w.
• Language selection is now handled entirely inside LangSelector.ts &
LanguageModal.ts
• Prevents background scrolling when open
• Highlights the current language at the top
• Always shows English second
• Shows browser language third (if different from current)
• All other languages are sorted alphabetically by English name
• Debug option is shown at the end when pressing D
• The language list is scrollable when it exceeds screen height
Supported Languages
["en", "ja", "fr", "bg", "nl", "ru", "ua", "de"]
Added Translation Keys
```
"lang": {
"en": "English",
"native": "English",
"svg": "xx"
},
"map": {
"map": "Map"
},
"game_starting_modal": {
"title": "Game is Starting...",
"desc": "Preparing for the lobby to start. Please wait."
},
"difficulty": {
"difficulty": "Difficulty"
}
```
## Please put your Discord username so you can be contacted if a bug or
regression is found:
MLS Representative
- aotumuri
Translation collaborator
- Nikola123 (He was a very big help from setting up the translation site
to adding the json. Thank you so much!)
I don't have permission from my collaborators to display their names
here, so I'll put the discord link here
https://discord.com/channels/1284581928254701718/1352553113612980224/1352553113612980224
- tryout33
Collaborators from other servers.
- CCC Group (This is not Culture Convenience Club. Think of it like a
server where developers of various games are playing.)
- People who fixed the UI and found bugs.
meow02952 (discord id) <- This person also gave me a code suggestion.
Thanks!
moon_spear (discord id)
ww_what_ww (discord id)
Azuna (he doesn't have discord account)
- People who corrected translations, etc.
_kyoyume_ (discord id)
_ultrasuper_ (discord id)
grueg (he doesn't have discord account)
# If I forgot to include your name, or if you’d like your name to be
added, please let me know via Gmail or Discord.
---------
Co-authored-by: Duwibi <86431918+Duwibi@users.noreply.github.com>
272 lines
8.4 KiB
TypeScript
272 lines
8.4 KiB
TypeScript
import page from "page";
|
|
import favicon from "../../resources/images/Favicon.svg";
|
|
import { consolex } from "../core/Consolex";
|
|
import { GameRecord, GameStartInfo } from "../core/Schemas";
|
|
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
|
import { GameType } from "../core/game/Game";
|
|
import { UserSettings } from "../core/game/UserSettings";
|
|
import { joinLobby } from "./ClientGameRunner";
|
|
import "./DarkModeButton";
|
|
import { DarkModeButton } from "./DarkModeButton";
|
|
import "./FlagInput";
|
|
import { FlagInput } from "./FlagInput";
|
|
import "./GoogleAdElement";
|
|
import GoogleAdElement from "./GoogleAdElement";
|
|
import { HelpModal } from "./HelpModal";
|
|
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
|
|
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
|
|
import "./LangSelector";
|
|
import { LangSelector } from "./LangSelector";
|
|
import { LanguageModal } from "./LanguageModal";
|
|
import "./PublicLobby";
|
|
import { PublicLobby } from "./PublicLobby";
|
|
import { SinglePlayerModal } from "./SinglePlayerModal";
|
|
import "./UsernameInput";
|
|
import { UsernameInput } from "./UsernameInput";
|
|
import { generateCryptoRandomUUID } from "./Utils";
|
|
import "./components/baseComponents/Button";
|
|
import "./components/baseComponents/Modal";
|
|
import { GameStartingModal } from "./gameStartingModal";
|
|
import "./styles.css";
|
|
|
|
export interface JoinLobbyEvent {
|
|
clientID: string;
|
|
// Multiplayer games only have gameID, gameConfig is not known until game starts.
|
|
gameID: string;
|
|
// GameConfig only exists when playing a singleplayer game.
|
|
gameStartInfo?: GameStartInfo;
|
|
// GameRecord exists when replaying an archived game.
|
|
gameRecord?: GameRecord;
|
|
}
|
|
|
|
class Client {
|
|
private gameStop: () => void;
|
|
|
|
private usernameInput: UsernameInput | null = null;
|
|
private flagInput: FlagInput | null = null;
|
|
private darkModeButton: DarkModeButton | null = null;
|
|
|
|
private joinModal: JoinPrivateLobbyModal;
|
|
private publicLobby: PublicLobby;
|
|
private googleAds: NodeListOf<GoogleAdElement>;
|
|
private userSettings: UserSettings = new UserSettings();
|
|
|
|
constructor() {}
|
|
|
|
initialize(): void {
|
|
const langSelector = document.querySelector(
|
|
"lang-selector",
|
|
) as LangSelector;
|
|
const LanguageModal = document.querySelector(
|
|
"lang-selector",
|
|
) as LanguageModal;
|
|
if (!langSelector) {
|
|
consolex.warn("Lang selector element not found");
|
|
}
|
|
if (!LanguageModal) {
|
|
consolex.warn("Language modal element not found");
|
|
}
|
|
|
|
this.flagInput = document.querySelector("flag-input") as FlagInput;
|
|
if (!this.flagInput) {
|
|
consolex.warn("Flag input element not found");
|
|
}
|
|
|
|
this.darkModeButton = document.querySelector(
|
|
"dark-mode-button",
|
|
) as DarkModeButton;
|
|
if (!this.darkModeButton) {
|
|
consolex.warn("Dark mode button element not found");
|
|
}
|
|
|
|
this.usernameInput = document.querySelector(
|
|
"username-input",
|
|
) as UsernameInput;
|
|
if (!this.usernameInput) {
|
|
consolex.warn("Username input element not found");
|
|
}
|
|
|
|
this.publicLobby = document.querySelector("public-lobby") as PublicLobby;
|
|
this.googleAds = document.querySelectorAll(
|
|
"google-ad",
|
|
) as NodeListOf<GoogleAdElement>;
|
|
|
|
window.addEventListener("beforeunload", () => {
|
|
consolex.log("Browser is closing");
|
|
if (this.gameStop != null) {
|
|
this.gameStop();
|
|
}
|
|
});
|
|
|
|
setFavicon();
|
|
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
|
|
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
|
|
|
|
const spModal = document.querySelector(
|
|
"single-player-modal",
|
|
) as SinglePlayerModal;
|
|
spModal instanceof SinglePlayerModal;
|
|
document.getElementById("single-player").addEventListener("click", () => {
|
|
if (this.usernameInput.isValid()) {
|
|
spModal.open();
|
|
}
|
|
});
|
|
|
|
const hlpModal = document.querySelector("help-modal") as HelpModal;
|
|
hlpModal instanceof HelpModal;
|
|
document.getElementById("help-button").addEventListener("click", () => {
|
|
hlpModal.open();
|
|
});
|
|
|
|
const hostModal = document.querySelector(
|
|
"host-lobby-modal",
|
|
) as HostPrivateLobbyModal;
|
|
hostModal instanceof HostPrivateLobbyModal;
|
|
document
|
|
.getElementById("host-lobby-button")
|
|
.addEventListener("click", () => {
|
|
if (this.usernameInput.isValid()) {
|
|
hostModal.open();
|
|
this.publicLobby.leaveLobby();
|
|
}
|
|
});
|
|
|
|
this.joinModal = document.querySelector(
|
|
"join-private-lobby-modal",
|
|
) as JoinPrivateLobbyModal;
|
|
this.joinModal instanceof JoinPrivateLobbyModal;
|
|
document
|
|
.getElementById("join-private-lobby-button")
|
|
.addEventListener("click", () => {
|
|
if (this.usernameInput.isValid()) {
|
|
this.joinModal.open();
|
|
}
|
|
});
|
|
|
|
if (this.userSettings.darkMode()) {
|
|
document.documentElement.classList.add("dark");
|
|
} else {
|
|
document.documentElement.classList.remove("dark");
|
|
}
|
|
page("/join/:lobbyId", (ctx) => {
|
|
if (ctx.init && sessionStorage.getItem("inLobby")) {
|
|
// On page reload, go back home
|
|
page.redirect("/");
|
|
return;
|
|
}
|
|
const lobbyId = ctx.params.lobbyId;
|
|
|
|
this.joinModal.open(lobbyId);
|
|
|
|
consolex.log(`joining lobby ${lobbyId}`);
|
|
});
|
|
|
|
page();
|
|
function updateSliderProgress(slider) {
|
|
const percent =
|
|
((slider.value - slider.min) / (slider.max - slider.min)) * 100;
|
|
slider.style.setProperty("--progress", `${percent}%`);
|
|
}
|
|
|
|
document
|
|
.querySelectorAll("#bots-count, #private-lobby-bots-count")
|
|
.forEach((slider) => {
|
|
updateSliderProgress(slider);
|
|
slider.addEventListener("input", () => updateSliderProgress(slider));
|
|
});
|
|
}
|
|
|
|
private async handleJoinLobby(event: CustomEvent) {
|
|
const lobby = event.detail as JoinLobbyEvent;
|
|
consolex.log(`joining lobby ${lobby.gameID}`);
|
|
if (this.gameStop != null) {
|
|
consolex.log("joining lobby, stopping existing game");
|
|
this.gameStop();
|
|
}
|
|
const config = await getServerConfigFromClient();
|
|
this.gameStop = joinLobby(
|
|
{
|
|
gameID: lobby.gameID,
|
|
serverConfig: config,
|
|
flag:
|
|
this.flagInput.getCurrentFlag() == "xx"
|
|
? ""
|
|
: this.flagInput.getCurrentFlag(),
|
|
playerName: this.usernameInput.getCurrentUsername(),
|
|
persistentID: getPersistentIDFromCookie(),
|
|
clientID: lobby.clientID,
|
|
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.gameStartInfo,
|
|
gameRecord: lobby.gameRecord,
|
|
},
|
|
() => {
|
|
this.joinModal.close();
|
|
this.publicLobby.stop();
|
|
document.querySelectorAll(".ad").forEach((ad) => {
|
|
(ad as HTMLElement).style.display = "none";
|
|
});
|
|
|
|
// show when the game loads
|
|
const startingModal = document.querySelector(
|
|
"game-starting-modal",
|
|
) as GameStartingModal;
|
|
startingModal instanceof GameStartingModal;
|
|
startingModal.show();
|
|
|
|
if (event.detail.gameConfig?.gameType != GameType.Singleplayer) {
|
|
window.history.pushState({}, "", `/join/${lobby.gameID}`);
|
|
sessionStorage.setItem("inLobby", "true");
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
private async handleLeaveLobby(/* event: CustomEvent */) {
|
|
if (this.gameStop == null) {
|
|
return;
|
|
}
|
|
consolex.log("leaving lobby, cancelling game");
|
|
this.gameStop();
|
|
this.gameStop = null;
|
|
this.publicLobby.leaveLobby();
|
|
}
|
|
}
|
|
|
|
// Initialize the client when the DOM is loaded
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
new Client().initialize();
|
|
});
|
|
|
|
function setFavicon(): void {
|
|
const link = document.createElement("link");
|
|
link.type = "image/x-icon";
|
|
link.rel = "shortcut icon";
|
|
link.href = favicon;
|
|
document.head.appendChild(link);
|
|
}
|
|
|
|
// WARNING: DO NOT EXPOSE THIS ID
|
|
export function getPersistentIDFromCookie(): string {
|
|
const COOKIE_NAME = "player_persistent_id";
|
|
|
|
// Try to get existing cookie
|
|
const cookies = document.cookie.split(";");
|
|
for (const cookie of cookies) {
|
|
const [cookieName, cookieValue] = cookie.split("=").map((c) => c.trim());
|
|
if (cookieName === COOKIE_NAME) {
|
|
return cookieValue;
|
|
}
|
|
}
|
|
|
|
// If no cookie exists, create new ID and set cookie
|
|
const newID = generateCryptoRandomUUID();
|
|
document.cookie = [
|
|
`${COOKIE_NAME}=${newID}`,
|
|
`max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years
|
|
"path=/",
|
|
"SameSite=Strict",
|
|
"Secure",
|
|
].join(";");
|
|
|
|
return newID;
|
|
}
|