mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-22 09:37:14 +00:00
Addition of FlagInputModal (#1606)
## Description: The UI for selecting flags differs from other existing UIs, so this change allows it to use . The actual contents for flag selection will be included in the next PR. ## 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 - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I have read and accepted the CLA agreement (only required once). ## Please put your Discord username so you can be contacted if a bug or regression is found: aotumuri --------- Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
This commit is contained in:
co-authored by
Scott Anderson
parent
a05814a7fb
commit
fcd6f5d404
+32
-90
@@ -1,13 +1,11 @@
|
|||||||
import { LitElement, css, html } from "lit";
|
import { LitElement, css, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import Countries from "./data/countries.json";
|
import { renderPlayerFlag } from "../core/CustomFlag";
|
||||||
const flagKey: string = "flag";
|
const flagKey: string = "flag";
|
||||||
|
|
||||||
@customElement("flag-input")
|
@customElement("flag-input")
|
||||||
export class FlagInput extends LitElement {
|
export class FlagInput extends LitElement {
|
||||||
@state() private flag: string = "";
|
@state() public flag: string = "";
|
||||||
@state() private search: string = "";
|
|
||||||
@state() private showModal: boolean = false;
|
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -21,19 +19,6 @@ export class FlagInput extends LitElement {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private handleSearch(e: Event) {
|
|
||||||
this.search = String((e.target as HTMLInputElement).value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setFlag(flag: string) {
|
|
||||||
if (flag === "xx") {
|
|
||||||
flag = "";
|
|
||||||
}
|
|
||||||
this.flag = flag;
|
|
||||||
this.showModal = false;
|
|
||||||
this.storeFlag(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCurrentFlag(): string {
|
public getCurrentFlag(): string {
|
||||||
return this.flag;
|
return this.flag;
|
||||||
}
|
}
|
||||||
@@ -46,14 +31,6 @@ export class FlagInput extends LitElement {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private storeFlag(flag: string) {
|
|
||||||
if (flag) {
|
|
||||||
localStorage.setItem(flagKey, flag);
|
|
||||||
} else if (flag === "") {
|
|
||||||
localStorage.removeItem(flagKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private dispatchFlagEvent() {
|
private dispatchFlagEvent() {
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("flag-change", {
|
new CustomEvent("flag-change", {
|
||||||
@@ -68,86 +45,51 @@ export class FlagInput extends LitElement {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.flag = this.getStoredFlag();
|
this.flag = this.getStoredFlag();
|
||||||
this.dispatchFlagEvent();
|
this.dispatchFlagEvent();
|
||||||
window.addEventListener("keydown", this.handleKeyDown);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
window.removeEventListener("keydown", this.handleKeyDown);
|
|
||||||
super.disconnectedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if (e.code === "Escape") {
|
|
||||||
e.preventDefault();
|
|
||||||
this.showModal = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createRenderRoot() {
|
createRenderRoot() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<div
|
|
||||||
class="absolute left-0 top-0 w-full h-full ${this.showModal
|
|
||||||
? ""
|
|
||||||
: "hidden"}"
|
|
||||||
@click=${() => (this.showModal = false)}
|
|
||||||
></div>
|
|
||||||
<div class="flex relative">
|
<div class="flex relative">
|
||||||
<button
|
<button
|
||||||
@click=${() => (this.showModal = !this.showModal)}
|
id="flag-input_"
|
||||||
class="border p-[4px] rounded-lg flex cursor-pointer border-black/30 dark:border-gray-300/60 bg-white/70 dark:bg-[rgba(55,65,81,0.7)]"
|
class="border p-[4px] rounded-lg flex cursor-pointer border-black/30 dark:border-gray-300/60 bg-white/70 dark:bg-[rgba(55,65,81,0.7)]"
|
||||||
title="Pick a flag!"
|
title="Pick a flag!"
|
||||||
>
|
>
|
||||||
<img class="size-[48px]" src="/flags/${this.flag || "xx"}.svg" />
|
<span
|
||||||
|
id="flag-preview"
|
||||||
|
style="display:inline-block;width:48px;height:64px;vertical-align:middle;background:#333;border-radius:6px;overflow:hidden;"
|
||||||
|
></span>
|
||||||
</button>
|
</button>
|
||||||
${this.showModal
|
|
||||||
? html`
|
|
||||||
<div
|
|
||||||
class="text-white flex flex-col gap-[0.5rem] absolute top-[60px] left-[0px] w-[780%] h-[500px] max-h-[50vh] max-w-[87vw] bg-gray-900/80 backdrop-blur-md p-[10px] rounded-[8px] z-[3] ${this
|
|
||||||
.showModal
|
|
||||||
? ""
|
|
||||||
: "hidden"}"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="h-[2rem] border-none text-center border border-gray-300 rounded-xl shadow-sm text-2xl text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-black dark:border-gray-300/60 dark:bg-gray-700 dark:text-white"
|
|
||||||
type="text"
|
|
||||||
placeholder="Search..."
|
|
||||||
@change=${this.handleSearch}
|
|
||||||
@keyup=${this.handleSearch}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="flex flex-wrap justify-evenly gap-[1rem] overflow-y-auto overflow-x-hidden"
|
|
||||||
>
|
|
||||||
${Countries.filter(
|
|
||||||
(country) =>
|
|
||||||
country.name
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(this.search.toLowerCase()) ||
|
|
||||||
country.code
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(this.search.toLowerCase()),
|
|
||||||
).map(
|
|
||||||
(country) => html`
|
|
||||||
<button
|
|
||||||
@click=${() => this.setFlag(country.code)}
|
|
||||||
class="text-center cursor-pointer border-none bg-none opacity-70 sm:w-[calc(33.3333%-15px) w-[calc(100%/3-15px)] md:w-[calc(100%/4-15px)]"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="country-flag w-full h-auto"
|
|
||||||
src="/flags/${country.code}.svg"
|
|
||||||
/>
|
|
||||||
<span class="country-name">${country.name}</span>
|
|
||||||
</button>
|
|
||||||
`,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated() {
|
||||||
|
const preview = this.renderRoot.querySelector(
|
||||||
|
"#flag-preview",
|
||||||
|
) as HTMLElement;
|
||||||
|
if (!preview) return;
|
||||||
|
|
||||||
|
preview.innerHTML = "";
|
||||||
|
|
||||||
|
if (this.flag?.startsWith("!")) {
|
||||||
|
renderPlayerFlag(this.flag, preview);
|
||||||
|
} else {
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = this.flag ? `/flags/${this.flag}.svg` : `/flags/xx.svg`;
|
||||||
|
img.style.width = "100%";
|
||||||
|
img.style.height = "100%";
|
||||||
|
img.style.objectFit = "contain";
|
||||||
|
img.onerror = () => {
|
||||||
|
if (!img.src.endsWith("/flags/xx.svg")) {
|
||||||
|
img.src = "/flags/xx.svg";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
preview.appendChild(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { LitElement, html } from "lit";
|
||||||
|
import { customElement, query, state } from "lit/decorators.js";
|
||||||
|
import Countries from "./data/countries.json";
|
||||||
|
|
||||||
|
@customElement("flag-input-modal")
|
||||||
|
export class FlagInputModal extends LitElement {
|
||||||
|
@query("o-modal") private modalEl!: HTMLElement & {
|
||||||
|
open: () => void;
|
||||||
|
close: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
@state() private search: string = "";
|
||||||
|
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<o-modal title="Flag Selector Modal" alwaysMaximized>
|
||||||
|
<input
|
||||||
|
class="h-[2rem] border-none text-center border border-gray-300 rounded-xl shadow-sm text-2xl text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-black dark:border-gray-300/60 dark:bg-gray-700 dark:text-white"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search..."
|
||||||
|
@change=${this.handleSearch}
|
||||||
|
@keyup=${this.handleSearch}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap justify-evenly gap-[1rem] overflow-y-auto overflow-x-hidden h-[90%]"
|
||||||
|
>
|
||||||
|
${Countries.filter(
|
||||||
|
(country) =>
|
||||||
|
country.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
|
country.code.toLowerCase().includes(this.search.toLowerCase()),
|
||||||
|
).map(
|
||||||
|
(country) => html`
|
||||||
|
<button
|
||||||
|
@click=${() => {
|
||||||
|
this.setFlag(country.code);
|
||||||
|
this.close();
|
||||||
|
}}
|
||||||
|
class="text-center cursor-pointer border-none bg-none opacity-70
|
||||||
|
w-[calc(100%/2-15px)] sm:w-[calc(100%/4-15px)]
|
||||||
|
md:w-[calc(100%/6-15px)] lg:w-[calc(100%/8-15px)]
|
||||||
|
xl:w-[calc(100%/10-15px)] min-w-[80px]"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="country-flag w-full h-auto"
|
||||||
|
src="/flags/${country.code}.svg"
|
||||||
|
@error=${(e: Event) => {
|
||||||
|
const img = e.currentTarget as HTMLImageElement;
|
||||||
|
const fallback = "/flags/xx.svg";
|
||||||
|
if (img.src && !img.src.endsWith(fallback)) {
|
||||||
|
img.src = fallback;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span class="country-name">${country.name}</span>
|
||||||
|
</button>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</o-modal>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSearch(event: Event) {
|
||||||
|
this.search = (event.target as HTMLInputElement).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setFlag(flag: string) {
|
||||||
|
localStorage.setItem("flag", flag);
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("flag-change", {
|
||||||
|
detail: { flag },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public open() {
|
||||||
|
this.modalEl?.open();
|
||||||
|
}
|
||||||
|
public close() {
|
||||||
|
this.modalEl?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener("keydown", this.handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener("keydown", this.handleKeyDown);
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.code === "Escape") {
|
||||||
|
e.preventDefault();
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import "./DarkModeButton";
|
|||||||
import { DarkModeButton } from "./DarkModeButton";
|
import { DarkModeButton } from "./DarkModeButton";
|
||||||
import "./FlagInput";
|
import "./FlagInput";
|
||||||
import { FlagInput } from "./FlagInput";
|
import { FlagInput } from "./FlagInput";
|
||||||
|
import { FlagInputModal } from "./FlagInputModal";
|
||||||
import { GameStartingModal } from "./GameStartingModal";
|
import { GameStartingModal } from "./GameStartingModal";
|
||||||
import "./GoogleAdElement";
|
import "./GoogleAdElement";
|
||||||
import { HelpModal } from "./HelpModal";
|
import { HelpModal } from "./HelpModal";
|
||||||
@@ -194,6 +195,16 @@ class Client {
|
|||||||
hlpModal.open();
|
hlpModal.open();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const flagInputModal = document.querySelector(
|
||||||
|
"flag-input-modal",
|
||||||
|
) as FlagInputModal;
|
||||||
|
flagInputModal instanceof FlagInputModal;
|
||||||
|
const flgInput = document.getElementById("flag-input_");
|
||||||
|
if (flgInput === null) throw new Error("Missing flag-input_");
|
||||||
|
flgInput.addEventListener("click", () => {
|
||||||
|
flagInputModal.open();
|
||||||
|
});
|
||||||
|
|
||||||
const territoryModal = document.querySelector(
|
const territoryModal = document.querySelector(
|
||||||
"territory-patterns-modal",
|
"territory-patterns-modal",
|
||||||
) as TerritoryPatternsModal;
|
) as TerritoryPatternsModal;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export class OModal extends LitElement {
|
|||||||
@state() public isModalOpen = false;
|
@state() public isModalOpen = false;
|
||||||
@property({ type: String }) title = "";
|
@property({ type: String }) title = "";
|
||||||
@property({ type: String }) translationKey = "";
|
@property({ type: String }) translationKey = "";
|
||||||
|
@property({ type: Boolean }) alwaysMaximized = false;
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
.c-modal {
|
.c-modal {
|
||||||
@@ -31,6 +32,17 @@ export class OModal extends LitElement {
|
|||||||
max-width: 860px;
|
max-width: 860px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-modal__wrapper.always-maximized {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 340px;
|
||||||
|
max-width: 860px;
|
||||||
|
min-height: 320px;
|
||||||
|
/* Fallback for older browsers */
|
||||||
|
height: 60vh;
|
||||||
|
/* Use dvh if supported for dynamic viewport handling */
|
||||||
|
height: 60dvh;
|
||||||
|
}
|
||||||
|
|
||||||
.c-modal__header {
|
.c-modal__header {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
@@ -74,7 +86,11 @@ export class OModal extends LitElement {
|
|||||||
${this.isModalOpen
|
${this.isModalOpen
|
||||||
? html`
|
? html`
|
||||||
<aside class="c-modal">
|
<aside class="c-modal">
|
||||||
<div class="c-modal__wrapper">
|
<div
|
||||||
|
class="c-modal__wrapper ${this.alwaysMaximized
|
||||||
|
? "always-maximized"
|
||||||
|
: ""}"
|
||||||
|
>
|
||||||
<header class="c-modal__header">
|
<header class="c-modal__header">
|
||||||
${`${this.translationKey}` === ""
|
${`${this.translationKey}` === ""
|
||||||
? `${this.title}`
|
? `${this.title}`
|
||||||
|
|||||||
@@ -411,6 +411,7 @@
|
|||||||
<news-modal></news-modal>
|
<news-modal></news-modal>
|
||||||
<game-left-sidebar></game-left-sidebar>
|
<game-left-sidebar></game-left-sidebar>
|
||||||
<spawn-ad></spawn-ad>
|
<spawn-ad></spawn-ad>
|
||||||
|
<flag-input-modal></flag-input-modal>
|
||||||
<fps-display></fps-display>
|
<fps-display></fps-display>
|
||||||
<div
|
<div
|
||||||
id="language-modal"
|
id="language-modal"
|
||||||
|
|||||||
Reference in New Issue
Block a user