mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 10:32:41 +00:00
Disable game buttons (clan tag + username) (#4170)
## Description: disables buttons, instead of emitting a warning <img width="1017" height="677" alt="image" src="https://github.com/user-attachments/assets/7af4e0e1-df22-4cfe-bc8b-6fae5e62f9b6" /> <img width="1006" height="668" alt="image" src="https://github.com/user-attachments/assets/d8e5291c-4ecd-4f8d-8471-e5a547c30eda" /> ## 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: w.o.n
This commit is contained in:
@@ -34,6 +34,7 @@ const CARD_BG = "bg-surface";
|
|||||||
export class GameModeSelector extends LitElement {
|
export class GameModeSelector extends LitElement {
|
||||||
@state() private lobbies: PublicGames | null = null;
|
@state() private lobbies: PublicGames | null = null;
|
||||||
@state() private mapAspectRatios: Map<GameMapType, number> = new Map();
|
@state() private mapAspectRatios: Map<GameMapType, number> = new Map();
|
||||||
|
@state() private inputValid: boolean = true;
|
||||||
private serverTimeOffset: number = 0;
|
private serverTimeOffset: number = 0;
|
||||||
private defaultLobbyTime: number = 0;
|
private defaultLobbyTime: number = 0;
|
||||||
|
|
||||||
@@ -45,28 +46,44 @@ export class GameModeSelector extends LitElement {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Silent backstop; the buttons are already disabled while input is invalid.
|
||||||
* Validates username input and shows error message if invalid.
|
|
||||||
* Returns true if valid, false otherwise.
|
|
||||||
*/
|
|
||||||
private validateUsername(): boolean {
|
private validateUsername(): boolean {
|
||||||
const usernameInput = document.querySelector(
|
const usernameInput = document.querySelector(
|
||||||
"username-input",
|
"username-input",
|
||||||
) as UsernameInput | null;
|
) as UsernameInput | null;
|
||||||
return usernameInput ? usernameInput.validateOrShowError() : true;
|
return usernameInput ? usernameInput.canPlay() : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.lobbySocket.start();
|
this.lobbySocket.start();
|
||||||
this.defaultLobbyTime = ClientEnv.gameCreationRate() / 1000;
|
this.defaultLobbyTime = ClientEnv.gameCreationRate() / 1000;
|
||||||
|
window.addEventListener(
|
||||||
|
"username-validity-change",
|
||||||
|
this.handleValidityChange,
|
||||||
|
);
|
||||||
|
// Pick up the current value in case username-input validated before us.
|
||||||
|
const usernameInput = document.querySelector(
|
||||||
|
"username-input",
|
||||||
|
) as UsernameInput | null;
|
||||||
|
if (usernameInput) {
|
||||||
|
this.inputValid = usernameInput.canPlay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
this.stop();
|
this.stop();
|
||||||
|
window.removeEventListener(
|
||||||
|
"username-validity-change",
|
||||||
|
this.handleValidityChange,
|
||||||
|
);
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleValidityChange = (e: Event) => {
|
||||||
|
this.inputValid = (e as CustomEvent).detail?.isValid ?? true;
|
||||||
|
};
|
||||||
|
|
||||||
public stop() {
|
public stop() {
|
||||||
this.lobbySocket.stop();
|
this.lobbySocket.stop();
|
||||||
}
|
}
|
||||||
@@ -259,7 +276,11 @@ export class GameModeSelector extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<button
|
<button
|
||||||
@click=${onClick}
|
@click=${onClick}
|
||||||
class="flex items-center justify-center w-full h-full rounded-lg ${bgClass} transition-all duration-200 text-sm lg:text-base font-medium text-white uppercase tracking-wider text-center"
|
?disabled=${!this.inputValid}
|
||||||
|
class="flex items-center justify-center w-full h-full rounded-lg ${bgClass} transition-all duration-200 text-sm lg:text-base font-medium text-white uppercase tracking-wider text-center ${!this
|
||||||
|
.inputValid
|
||||||
|
? "opacity-50 cursor-not-allowed pointer-events-none"
|
||||||
|
: ""}"
|
||||||
>
|
>
|
||||||
${title}
|
${title}
|
||||||
</button>
|
</button>
|
||||||
@@ -305,7 +326,11 @@ export class GameModeSelector extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<button
|
<button
|
||||||
@click=${() => this.validateAndJoin(lobby)}
|
@click=${() => this.validateAndJoin(lobby)}
|
||||||
class="group relative w-full h-44 sm:h-full text-white uppercase rounded-2xl transition-all duration-200 hover:scale-[1.02] active:scale-[0.98] bg-surface hover:shadow-[var(--shadow-lobby-card-hover)]"
|
?disabled=${!this.inputValid}
|
||||||
|
class="group relative w-full h-44 sm:h-full text-white uppercase rounded-2xl transition-all duration-200 hover:scale-[1.02] active:scale-[0.98] bg-surface hover:shadow-[var(--shadow-lobby-card-hover)] ${!this
|
||||||
|
.inputValid
|
||||||
|
? "opacity-50 cursor-not-allowed pointer-events-none"
|
||||||
|
: ""}"
|
||||||
>
|
>
|
||||||
<!-- Image clipped separately so overflow-hidden doesn't block absolute children -->
|
<!-- Image clipped separately so overflow-hidden doesn't block absolute children -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
+1
-1
@@ -814,7 +814,7 @@ class Client {
|
|||||||
private async handleJoinLobby(event: CustomEvent<JoinLobbyEvent>) {
|
private async handleJoinLobby(event: CustomEvent<JoinLobbyEvent>) {
|
||||||
const lobby = event.detail;
|
const lobby = event.detail;
|
||||||
this.mostRecentJoinEvent = event.timeStamp;
|
this.mostRecentJoinEvent = event.timeStamp;
|
||||||
if (this.usernameInput && !this.usernameInput.validateOrShowError()) {
|
if (this.usernameInput && !this.usernameInput.canPlay()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-22
@@ -28,9 +28,8 @@ export class UsernameInput extends LitElement {
|
|||||||
@state() private clanTag: string = "";
|
@state() private clanTag: string = "";
|
||||||
|
|
||||||
@property({ type: String }) validationError: string = "";
|
@property({ type: String }) validationError: string = "";
|
||||||
// Ownership-check feedback (i18n key) shown inline beneath the tag input. This
|
// Ownership-check feedback (i18n key) shown inline beneath the tag input. Only
|
||||||
// is advisory only — it does not gate play; the tag is stripped on submit and
|
// "not a member" gates the buttons (see emitValidity); the rest is advisory.
|
||||||
// the server re-checks authoritatively.
|
|
||||||
@state() private clanTagOwnershipError: string = "";
|
@state() private clanTagOwnershipError: string = "";
|
||||||
@state() private clanCheckPending: boolean = false;
|
@state() private clanCheckPending: boolean = false;
|
||||||
private _isValid: boolean = true;
|
private _isValid: boolean = true;
|
||||||
@@ -71,6 +70,7 @@ export class UsernameInput extends LitElement {
|
|||||||
const gen = ++this.clanCheckGen;
|
const gen = ++this.clanCheckGen;
|
||||||
const tag = this.clanTag;
|
const tag = this.clanTag;
|
||||||
this.clanTagOwnershipError = "";
|
this.clanTagOwnershipError = "";
|
||||||
|
this.emitValidity();
|
||||||
if (tag.length === 0 || !validateClanTag(tag).isValid) {
|
if (tag.length === 0 || !validateClanTag(tag).isValid) {
|
||||||
this.clanCheckPending = false;
|
this.clanCheckPending = false;
|
||||||
this.clanCheck = Promise.resolve(null);
|
this.clanCheck = Promise.resolve(null);
|
||||||
@@ -81,6 +81,7 @@ export class UsernameInput extends LitElement {
|
|||||||
if (gen === this.clanCheckGen) {
|
if (gen === this.clanCheckGen) {
|
||||||
this.clanTagOwnershipError = res.error ?? "";
|
this.clanTagOwnershipError = res.error ?? "";
|
||||||
this.clanCheckPending = false;
|
this.clanCheckPending = false;
|
||||||
|
this.emitValidity();
|
||||||
}
|
}
|
||||||
return res.tag;
|
return res.tag;
|
||||||
});
|
});
|
||||||
@@ -239,6 +240,7 @@ export class UsernameInput extends LitElement {
|
|||||||
if (!clanTagResult.isValid) {
|
if (!clanTagResult.isValid) {
|
||||||
this._isValid = false;
|
this._isValid = false;
|
||||||
this.validationError = clanTagResult.error ?? "";
|
this.validationError = clanTagResult.error ?? "";
|
||||||
|
this.emitValidity();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,32 +253,23 @@ export class UsernameInput extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
this.validationError = result.error ?? "";
|
this.validationError = result.error ?? "";
|
||||||
}
|
}
|
||||||
|
this.emitValidity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isValid(): boolean {
|
// Broadcast play-eligibility so action buttons can disable themselves.
|
||||||
return this._isValid;
|
private emitValidity() {
|
||||||
}
|
|
||||||
|
|
||||||
public showValidationFeedback(): void {
|
|
||||||
const message =
|
|
||||||
this.validationError || translateText("username.invalid_chars");
|
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("show-message", {
|
new CustomEvent("username-validity-change", {
|
||||||
detail: {
|
detail: { isValid: this.canPlay() },
|
||||||
message,
|
|
||||||
color: "red",
|
|
||||||
duration: 2500,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public validateOrShowError(): boolean {
|
// Play-eligibility: syntax-valid and not blocked by clan membership.
|
||||||
if (this.isValid()) {
|
public canPlay(): boolean {
|
||||||
return true;
|
return (
|
||||||
}
|
this._isValid && this.clanTagOwnershipError !== "username.tag_not_member"
|
||||||
this.showValidationFeedback();
|
);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user