replace leave lobby popup with custom popup (#4449)

## Description:
old:
<img width="1009" height="491" alt="image"
src="https://github.com/user-attachments/assets/0b95877c-dac7-4025-bdfa-62ab6879d208"
/>

new:
<img width="1017" height="561" alt="image"
src="https://github.com/user-attachments/assets/cfb49b31-eb46-4d64-bd9e-3f25bb7cd0fb"
/>




## 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:
Ryan
2026-06-30 00:16:46 +01:00
committed by GitHub
parent e196d399b4
commit dae129c6a3
5 changed files with 81 additions and 20 deletions
+49 -6
View File
@@ -3,6 +3,7 @@ import { property, query, state } from "lit/decorators.js";
import { modalRouter } from "../ModalRouter";
import "./baseComponents/Modal";
import type { OModalTab } from "./baseComponents/Modal";
import "./ConfirmDialog";
/**
* Static-ish configuration for the <o-modal> shell.
@@ -39,6 +40,12 @@ export abstract class BaseModal extends LitElement {
@state() protected activeTab = "";
@property({ type: Boolean }) inline = false;
// Pending close confirmation; when set, renders the inline confirm dialog.
@state() private closeConfirm: {
message: string;
resolve: (ok: boolean) => void;
} | null = null;
// Re-entrancy guard: showPage() (for inline modals) re-invokes .open()
// with no args after we call it. We must not re-run onOpen(undefined)
// from that nested call, which would clobber state set by the outer call.
@@ -91,12 +98,30 @@ export abstract class BaseModal extends LitElement {
/**
* Guard called before closing via Escape key or click-outside.
* Return false to prevent the modal from closing.
* Return false (or a promise resolving false) to prevent closing.
*/
public confirmBeforeClose(): boolean {
public confirmBeforeClose(): boolean | Promise<boolean> {
return true;
}
/**
* Show a styled confirm dialog and resolve to the user's choice. Call from
* confirmBeforeClose() to gate closing behind a confirm/cancel prompt.
*/
protected confirmClose(message: string): Promise<boolean> {
// Don't stack a second prompt if one is already open.
if (this.closeConfirm) return Promise.resolve(false);
return new Promise((resolve) => {
this.closeConfirm = { message, resolve };
});
}
private settleCloseConfirm(ok: boolean) {
const pending = this.closeConfirm;
this.closeConfirm = null;
pending?.resolve(ok);
}
// ---- Rendering ----
createRenderRoot() {
@@ -133,6 +158,14 @@ export abstract class BaseModal extends LitElement {
${headerSlot ? html`<div slot="header">${headerSlot}</div>` : null}
${body}
</o-modal>
${this.closeConfirm
? html`<confirm-dialog
.message=${this.closeConfirm.message}
variant="warning"
@confirm=${() => this.settleCloseConfirm(true)}
@cancel=${() => this.settleCloseConfirm(false)}
></confirm-dialog>`
: null}
`;
}
@@ -199,6 +232,9 @@ export abstract class BaseModal extends LitElement {
}
public close(args?: Record<string, unknown>): void {
// If closing was triggered elsewhere while a confirm prompt is pending,
// dismiss it (removes the dialog and resolves the awaiting guard as false).
this.settleCloseConfirm(false);
this.unregisterEscapeHandler();
this.onClose(args);
@@ -234,9 +270,13 @@ export abstract class BaseModal extends LitElement {
protected firstUpdated(): void {
if (this.modalEl) {
this.modalEl.onClose = () => {
this.modalEl.onClose = async () => {
if (this.isModalOpen) {
if (!this.confirmBeforeClose()) {
const confirmed = await this.confirmBeforeClose();
// Bail if a parallel close() settled things while we awaited —
// otherwise we'd re-open an already-closed modal.
if (!this.isModalOpen) return;
if (!confirmed) {
// Re-open the underlying o-modal since it already closed itself
this.modalEl?.open();
return;
@@ -248,14 +288,17 @@ export abstract class BaseModal extends LitElement {
}
disconnectedCallback() {
this.settleCloseConfirm(false);
this.unregisterEscapeHandler();
super.disconnectedCallback();
}
private handleKeyDown = (e: KeyboardEvent) => {
private handleKeyDown = async (e: KeyboardEvent) => {
if (e.key === "Escape" && this.isModalOpen) {
e.preventDefault();
if (!this.confirmBeforeClose()) {
const confirmed = await this.confirmBeforeClose();
// Bail if a parallel close() already closed us while we awaited.
if (!confirmed || !this.isModalOpen) {
return;
}
this.close();