mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 17:53:29 +00:00
CopyButton, extract into component (#2934)
## Description: Extracted the CopyButton into its own component, and now reusing it in "Account" too. ## 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 ## 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:
@@ -198,7 +198,8 @@
|
|||||||
"not_found": "Not Found",
|
"not_found": "Not Found",
|
||||||
"clear_session": "Clear Session",
|
"clear_session": "Clear Session",
|
||||||
"failed_to_send_recovery_email": "Failed to send recovery email",
|
"failed_to_send_recovery_email": "Failed to send recovery email",
|
||||||
"enter_email_address": "Please enter an email address"
|
"enter_email_address": "Please enter an email address",
|
||||||
|
"personal_player_id": "Personal Player ID:"
|
||||||
},
|
},
|
||||||
"stats_modal": {
|
"stats_modal": {
|
||||||
"title": "Stats",
|
"title": "Stats",
|
||||||
|
|||||||
+10
-24
@@ -13,16 +13,16 @@ import "./components/baseComponents/stats/GameList";
|
|||||||
import "./components/baseComponents/stats/PlayerStatsTable";
|
import "./components/baseComponents/stats/PlayerStatsTable";
|
||||||
import "./components/baseComponents/stats/PlayerStatsTree";
|
import "./components/baseComponents/stats/PlayerStatsTree";
|
||||||
import { BaseModal } from "./components/BaseModal";
|
import { BaseModal } from "./components/BaseModal";
|
||||||
|
import "./components/CopyButton";
|
||||||
import "./components/Difficulties";
|
import "./components/Difficulties";
|
||||||
import "./components/PatternButton";
|
import "./components/PatternButton";
|
||||||
import { modalHeader } from "./components/ui/ModalHeader";
|
import { modalHeader } from "./components/ui/ModalHeader";
|
||||||
import { copyToClipboard, translateText } from "./Utils";
|
import { translateText } from "./Utils";
|
||||||
|
|
||||||
@customElement("account-modal")
|
@customElement("account-modal")
|
||||||
export class AccountModal extends BaseModal {
|
export class AccountModal extends BaseModal {
|
||||||
@state() private email: string = "";
|
@state() private email: string = "";
|
||||||
@state() private isLoadingUser: boolean = false;
|
@state() private isLoadingUser: boolean = false;
|
||||||
@state() private showCopied: boolean = false;
|
|
||||||
|
|
||||||
private userMeResponse: UserMeResponse | null = null;
|
private userMeResponse: UserMeResponse | null = null;
|
||||||
private statsTree: PlayerStatsTree | null = null;
|
private statsTree: PlayerStatsTree | null = null;
|
||||||
@@ -47,17 +47,6 @@ export class AccountModal extends BaseModal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async copyIdToClipboard() {
|
|
||||||
const id = this.userMeResponse?.player?.publicId;
|
|
||||||
if (!id) return;
|
|
||||||
|
|
||||||
await copyToClipboard(
|
|
||||||
id,
|
|
||||||
() => (this.showCopied = true),
|
|
||||||
() => (this.showCopied = false),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasAnyStats(): boolean {
|
private hasAnyStats(): boolean {
|
||||||
if (!this.statsTree) return false;
|
if (!this.statsTree) return false;
|
||||||
// Check if statsTree has any data
|
// Check if statsTree has any data
|
||||||
@@ -106,6 +95,8 @@ export class AccountModal extends BaseModal {
|
|||||||
private renderInner() {
|
private renderInner() {
|
||||||
const isLoggedIn = !!this.userMeResponse?.user;
|
const isLoggedIn = !!this.userMeResponse?.user;
|
||||||
const title = translateText("account_modal.title");
|
const title = translateText("account_modal.title");
|
||||||
|
const publicId = this.userMeResponse?.player?.publicId ?? "";
|
||||||
|
const displayId = publicId || translateText("account_modal.not_found");
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@@ -120,18 +111,13 @@ export class AccountModal extends BaseModal {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
class="text-xs text-blue-400 font-bold uppercase tracking-wider"
|
class="text-xs text-blue-400 font-bold uppercase tracking-wider"
|
||||||
>ID:</span
|
>${translateText("account_modal.personal_player_id")}</span
|
||||||
>
|
>
|
||||||
<button
|
<copy-button
|
||||||
@click=${this.copyIdToClipboard}
|
.lobbyId=${publicId}
|
||||||
class="text-xs text-white/60 font-mono bg-white/5 px-2 py-0.5 rounded border border-white/5 hover:bg-white/10 hover:text-white transition-colors cursor-pointer"
|
.copyText=${publicId}
|
||||||
title="${translateText("common.click_to_copy")}"
|
.displayText=${displayId}
|
||||||
>
|
></copy-button>
|
||||||
${this.showCopied
|
|
||||||
? translateText("common.copied")
|
|
||||||
: (this.userMeResponse?.player?.publicId ??
|
|
||||||
translateText("account_modal.not_found"))}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { copyToClipboard, translateText } from "../client/Utils";
|
import { translateText } from "../client/Utils";
|
||||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||||
import {
|
import {
|
||||||
Difficulty,
|
Difficulty,
|
||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
mapCategories,
|
mapCategories,
|
||||||
} from "../core/game/Game";
|
} from "../core/game/Game";
|
||||||
import { getCompactMapNationCount } from "../core/game/NationCreation";
|
import { getCompactMapNationCount } from "../core/game/NationCreation";
|
||||||
import { UserSettings } from "../core/game/UserSettings";
|
|
||||||
import {
|
import {
|
||||||
ClientInfo,
|
ClientInfo,
|
||||||
GameConfig,
|
GameConfig,
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
import { generateID } from "../core/Util";
|
import { generateID } from "../core/Util";
|
||||||
import "./components/baseComponents/Modal";
|
import "./components/baseComponents/Modal";
|
||||||
import { BaseModal } from "./components/BaseModal";
|
import { BaseModal } from "./components/BaseModal";
|
||||||
|
import "./components/CopyButton";
|
||||||
import "./components/Difficulties";
|
import "./components/Difficulties";
|
||||||
import "./components/FluentSlider";
|
import "./components/FluentSlider";
|
||||||
import "./components/LobbyTeamView";
|
import "./components/LobbyTeamView";
|
||||||
@@ -65,19 +65,16 @@ export class HostLobbyModal extends BaseModal {
|
|||||||
@state() private startingGold: boolean = false;
|
@state() private startingGold: boolean = false;
|
||||||
@state() private startingGoldValue: number | undefined = undefined;
|
@state() private startingGoldValue: number | undefined = undefined;
|
||||||
@state() private lobbyId = "";
|
@state() private lobbyId = "";
|
||||||
@state() private copySuccess = false;
|
|
||||||
@state() private lobbyUrlSuffix = "";
|
@state() private lobbyUrlSuffix = "";
|
||||||
@state() private clients: ClientInfo[] = [];
|
@state() private clients: ClientInfo[] = [];
|
||||||
@state() private useRandomMap: boolean = false;
|
@state() private useRandomMap: boolean = false;
|
||||||
@state() private disabledUnits: UnitType[] = [];
|
@state() private disabledUnits: UnitType[] = [];
|
||||||
@state() private lobbyCreatorClientID: string = "";
|
@state() private lobbyCreatorClientID: string = "";
|
||||||
@state() private lobbyIdVisible: boolean = true;
|
|
||||||
@state() private nationCount: number = 0;
|
@state() private nationCount: number = 0;
|
||||||
|
|
||||||
private playersInterval: NodeJS.Timeout | null = null;
|
private playersInterval: NodeJS.Timeout | null = null;
|
||||||
// Add a new timer for debouncing bot changes
|
// Add a new timer for debouncing bot changes
|
||||||
private botsUpdateTimer: number | null = null;
|
private botsUpdateTimer: number | null = null;
|
||||||
private userSettings: UserSettings = new UserSettings();
|
|
||||||
private mapLoader = terrainMapFileLoader;
|
private mapLoader = terrainMapFileLoader;
|
||||||
|
|
||||||
private leaveLobbyOnClose = true;
|
private leaveLobbyOnClose = true;
|
||||||
@@ -144,91 +141,11 @@ export class HostLobbyModal extends BaseModal {
|
|||||||
},
|
},
|
||||||
ariaLabel: translateText("common.back"),
|
ariaLabel: translateText("common.back"),
|
||||||
rightContent: html`
|
rightContent: html`
|
||||||
<!-- Lobby ID Box -->
|
<copy-button
|
||||||
<div
|
.lobbyId=${this.lobbyId}
|
||||||
class="flex items-center gap-0.5 bg-white/5 rounded-lg px-2 py-1 border border-white/10 max-w-[220px] flex-nowrap"
|
.lobbySuffix=${this.lobbyUrlSuffix}
|
||||||
>
|
include-lobby-query
|
||||||
<button
|
></copy-button>
|
||||||
@click=${() => {
|
|
||||||
this.lobbyIdVisible = !this.lobbyIdVisible;
|
|
||||||
this.requestUpdate();
|
|
||||||
}}
|
|
||||||
class="p-1.5 rounded-md hover:bg-white/10 text-white/60 hover:text-white transition-colors"
|
|
||||||
title="${translateText("user_setting.toggle_visibility")}"
|
|
||||||
>
|
|
||||||
${this.lobbyIdVisible
|
|
||||||
? html`<svg
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M256 105c-101.8 0-188.4 62.7-224 151 35.6 88.3 122.2 151 224 151s188.4-62.7 224-151c-35.6-88.3-122.2-151-224-151zm0 251.7c-56 0-101.7-45.7-101.7-101.7S200 153.3 256 153.3 357.7 199 357.7 255 312 356.7 256 356.7zm0-161.1c-33 0-59.4 26.4-59.4 59.4s26.4 59.4 59.4 59.4 59.4-26.4 59.4-59.4-26.4-59.4-59.4-59.4z"
|
|
||||||
></path>
|
|
||||||
</svg>`
|
|
||||||
: html`<svg
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M448 256s-64-128-192-128S64 256 64 256c32 64 96 128 192 128s160-64 192-128z"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="32"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M144 256l224 0"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="32"
|
|
||||||
stroke-linecap="round"
|
|
||||||
></path>
|
|
||||||
</svg>`}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click=${this.copyToClipboard}
|
|
||||||
@dblclick=${(e: Event) => {
|
|
||||||
(e.currentTarget as HTMLElement).classList.add("select-all");
|
|
||||||
}}
|
|
||||||
@mouseleave=${(e: Event) => {
|
|
||||||
(e.currentTarget as HTMLElement).classList.remove(
|
|
||||||
"select-all",
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
class="font-mono text-xs font-bold text-white px-2 cursor-pointer select-none min-w-[80px] text-center truncate tracking-wider bg-transparent border-0"
|
|
||||||
title="${translateText("common.click_to_copy")}"
|
|
||||||
aria-label="${translateText("common.click_to_copy")}"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
${this.copySuccess
|
|
||||||
? translateText("common.copied")
|
|
||||||
: this.lobbyIdVisible
|
|
||||||
? this.lobbyId
|
|
||||||
: "••••••••"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click=${this.copyToClipboard}
|
|
||||||
class="p-1.5 rounded-md hover:bg-white/10 text-white/60 hover:text-white transition-colors"
|
|
||||||
title="${translateText("common.click_to_copy")}"
|
|
||||||
aria-label="${translateText("common.click_to_copy")}"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`,
|
`,
|
||||||
})}
|
})}
|
||||||
|
|
||||||
@@ -997,10 +914,6 @@ export class HostLobbyModal extends BaseModal {
|
|||||||
|
|
||||||
protected onOpen(): void {
|
protected onOpen(): void {
|
||||||
this.lobbyCreatorClientID = generateID();
|
this.lobbyCreatorClientID = generateID();
|
||||||
this.lobbyIdVisible = this.userSettings.get(
|
|
||||||
"settings.lobbyIdVisibility",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
createLobby(this.lobbyCreatorClientID)
|
createLobby(this.lobbyCreatorClientID)
|
||||||
.then(async (lobby) => {
|
.then(async (lobby) => {
|
||||||
@@ -1119,10 +1032,8 @@ export class HostLobbyModal extends BaseModal {
|
|||||||
this.useRandomMap = false;
|
this.useRandomMap = false;
|
||||||
this.disabledUnits = [];
|
this.disabledUnits = [];
|
||||||
this.lobbyId = "";
|
this.lobbyId = "";
|
||||||
this.copySuccess = false;
|
|
||||||
this.clients = [];
|
this.clients = [];
|
||||||
this.lobbyCreatorClientID = "";
|
this.lobbyCreatorClientID = "";
|
||||||
this.lobbyIdVisible = true;
|
|
||||||
this.nationCount = 0;
|
this.nationCount = 0;
|
||||||
this.goldMultiplier = false;
|
this.goldMultiplier = false;
|
||||||
this.goldMultiplierValue = undefined;
|
this.goldMultiplierValue = undefined;
|
||||||
@@ -1403,15 +1314,6 @@ export class HostLobbyModal extends BaseModal {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async copyToClipboard() {
|
|
||||||
const url = await this.buildLobbyUrl();
|
|
||||||
await copyToClipboard(
|
|
||||||
url,
|
|
||||||
() => (this.copySuccess = true),
|
|
||||||
() => (this.copySuccess = false),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async pollPlayers() {
|
private async pollPlayers() {
|
||||||
const config = await getServerConfigFromClient();
|
const config = await getServerConfigFromClient();
|
||||||
fetch(`/${config.workerPath(this.lobbyId)}/api/game/${this.lobbyId}`, {
|
fetch(`/${config.workerPath(this.lobbyId)}/api/game/${this.lobbyId}`, {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { html, TemplateResult } from "lit";
|
import { html, TemplateResult } from "lit";
|
||||||
import { customElement, query, state } from "lit/decorators.js";
|
import { customElement, query, state } from "lit/decorators.js";
|
||||||
import { copyToClipboard, translateText } from "../client/Utils";
|
import { translateText } from "../client/Utils";
|
||||||
import {
|
import {
|
||||||
ClientInfo,
|
ClientInfo,
|
||||||
GAME_ID_REGEX,
|
GAME_ID_REGEX,
|
||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
import { generateID } from "../core/Util";
|
import { generateID } from "../core/Util";
|
||||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||||
import { GameMode } from "../core/game/Game";
|
import { GameMode } from "../core/game/Game";
|
||||||
import { UserSettings } from "../core/game/UserSettings";
|
|
||||||
import { getApiBase } from "./Api";
|
import { getApiBase } from "./Api";
|
||||||
import { JoinLobbyEvent } from "./Main";
|
import { JoinLobbyEvent } from "./Main";
|
||||||
import { BaseModal } from "./components/BaseModal";
|
import { BaseModal } from "./components/BaseModal";
|
||||||
|
import "./components/CopyButton";
|
||||||
import "./components/Difficulties";
|
import "./components/Difficulties";
|
||||||
import "./components/LobbyTeamView";
|
import "./components/LobbyTeamView";
|
||||||
import { modalHeader } from "./components/ui/ModalHeader";
|
import { modalHeader } from "./components/ui/ModalHeader";
|
||||||
@@ -26,12 +26,9 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
|||||||
@state() private players: ClientInfo[] = [];
|
@state() private players: ClientInfo[] = [];
|
||||||
@state() private gameConfig: GameConfig | null = null;
|
@state() private gameConfig: GameConfig | null = null;
|
||||||
@state() private lobbyCreatorClientID: string | null = null;
|
@state() private lobbyCreatorClientID: string | null = null;
|
||||||
@state() private lobbyIdVisible: boolean = true;
|
|
||||||
@state() private copySuccess: boolean = false;
|
|
||||||
@state() private currentLobbyId: string = "";
|
@state() private currentLobbyId: string = "";
|
||||||
|
|
||||||
private playersInterval: NodeJS.Timeout | null = null;
|
private playersInterval: NodeJS.Timeout | null = null;
|
||||||
private userSettings: UserSettings = new UserSettings();
|
|
||||||
|
|
||||||
private leaveLobbyOnClose = true;
|
private leaveLobbyOnClose = true;
|
||||||
|
|
||||||
@@ -50,91 +47,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
|||||||
ariaLabel: translateText("common.close"),
|
ariaLabel: translateText("common.close"),
|
||||||
rightContent: this.hasJoined
|
rightContent: this.hasJoined
|
||||||
? html`
|
? html`
|
||||||
<!-- Lobby ID Box -->
|
<copy-button .lobbyId=${this.currentLobbyId}></copy-button>
|
||||||
<div
|
|
||||||
class="flex items-center gap-0.5 bg-white/5 rounded-lg px-2 py-1 border border-white/10 max-w-[220px] flex-nowrap"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
@click=${() => {
|
|
||||||
this.lobbyIdVisible = !this.lobbyIdVisible;
|
|
||||||
this.requestUpdate();
|
|
||||||
}}
|
|
||||||
class="p-1.5 rounded-md hover:bg-white/10 text-white/60 hover:text-white transition-colors"
|
|
||||||
title="${translateText("user_setting.toggle_visibility")}"
|
|
||||||
>
|
|
||||||
${this.lobbyIdVisible
|
|
||||||
? html`<svg
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M256 105c-101.8 0-188.4 62.7-224 151 35.6 88.3 122.2 151 224 151s188.4-62.7 224-151c-35.6-88.3-122.2-151-224-151zm0 251.7c-56 0-101.7-45.7-101.7-101.7S200 153.3 256 153.3 357.7 199 357.7 255 312 356.7 256 356.7zm0-161.1c-33 0-59.4 26.4-59.4 59.4s26.4 59.4 59.4 59.4 59.4-26.4 59.4-59.4-26.4-59.4-59.4-59.4z"
|
|
||||||
></path>
|
|
||||||
</svg>`
|
|
||||||
: html`<svg
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M448 256s-64-128-192-128S64 256 64 256c32 64 96 128 192 128s160-64 192-128z"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="32"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M144 256l224 0"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="32"
|
|
||||||
stroke-linecap="round"
|
|
||||||
></path>
|
|
||||||
</svg>`}
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
@click=${this.copyToClipboard}
|
|
||||||
@dblclick=${(e: Event) => {
|
|
||||||
(e.currentTarget as HTMLElement).classList.add(
|
|
||||||
"select-all",
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
@mouseleave=${(e: Event) => {
|
|
||||||
(e.currentTarget as HTMLElement).classList.remove(
|
|
||||||
"select-all",
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
class="font-mono text-xs font-bold text-white px-2 cursor-pointer select-none min-w-[80px] text-center truncate tracking-wider"
|
|
||||||
title="${translateText("common.click_to_copy")}"
|
|
||||||
>
|
|
||||||
${this.copySuccess
|
|
||||||
? translateText("common.copied")
|
|
||||||
: this.lobbyIdVisible
|
|
||||||
? this.currentLobbyId
|
|
||||||
: "••••••••"}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
@click=${this.copyToClipboard}
|
|
||||||
class="p-1.5 rounded-md hover:bg-white/10 text-white/60 hover:text-white transition-colors"
|
|
||||||
title="${translateText("common.click_to_copy")}"
|
|
||||||
aria-label="${translateText("common.click_to_copy")}"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
: undefined,
|
: undefined,
|
||||||
})}
|
})}
|
||||||
@@ -347,10 +260,6 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
|||||||
|
|
||||||
public open(id: string = "") {
|
public open(id: string = "") {
|
||||||
super.open();
|
super.open();
|
||||||
this.lobbyIdVisible = this.userSettings.get(
|
|
||||||
"settings.lobbyIdVisibility",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
if (id) {
|
if (id) {
|
||||||
this.setLobbyId(id);
|
this.setLobbyId(id);
|
||||||
this.joinLobby();
|
this.joinLobby();
|
||||||
@@ -396,15 +305,6 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
|||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async copyToClipboard() {
|
|
||||||
const config = await getServerConfigFromClient();
|
|
||||||
await copyToClipboard(
|
|
||||||
`${location.origin}/${config.workerPath(this.currentLobbyId)}/game/${this.currentLobbyId}`,
|
|
||||||
() => (this.copySuccess = true),
|
|
||||||
() => (this.copySuccess = false),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isValidLobbyId(value: string): boolean {
|
private isValidLobbyId(value: string): boolean {
|
||||||
return GAME_ID_REGEX.test(value);
|
return GAME_ID_REGEX.test(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import { LitElement, html } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
import { getServerConfigFromClient } from "../../core/configuration/ConfigLoader";
|
||||||
|
import { UserSettings } from "../../core/game/UserSettings";
|
||||||
|
import { copyToClipboard, translateText } from "../Utils";
|
||||||
|
|
||||||
|
@customElement("copy-button")
|
||||||
|
export class CopyButton extends LitElement {
|
||||||
|
@property({ type: String, attribute: "lobby-id" }) lobbyId = "";
|
||||||
|
@property({ type: String, attribute: "lobby-suffix" }) lobbySuffix = "";
|
||||||
|
@property({ type: Boolean, attribute: "include-lobby-query" })
|
||||||
|
includeLobbyQuery = false;
|
||||||
|
@property({ type: String, attribute: "copy-text" }) copyText = "";
|
||||||
|
@property({ type: String, attribute: "display-text" }) displayText = "";
|
||||||
|
@property({ type: Boolean, attribute: "show-visibility-toggle" })
|
||||||
|
showVisibilityToggle = true;
|
||||||
|
@property({ type: Boolean, attribute: "show-copy-icon" })
|
||||||
|
showCopyIcon = true;
|
||||||
|
@property({ type: Boolean }) compact = false;
|
||||||
|
|
||||||
|
@state() private copySuccess = false;
|
||||||
|
@state() private lobbyIdVisible = true;
|
||||||
|
|
||||||
|
private userSettings: UserSettings = new UserSettings();
|
||||||
|
private maskLabel = html`••••••••`;
|
||||||
|
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(
|
||||||
|
changedProperties: Map<string | number | symbol, unknown>,
|
||||||
|
) {
|
||||||
|
if (changedProperties.has("lobbyId")) {
|
||||||
|
this.lobbyIdVisible = this.userSettings.get(
|
||||||
|
"settings.lobbyIdVisibility",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
this.copySuccess = false;
|
||||||
|
}
|
||||||
|
if (changedProperties.has("copyText")) {
|
||||||
|
this.copySuccess = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
changedProperties.has("showVisibilityToggle") ||
|
||||||
|
changedProperties.has("compact")
|
||||||
|
) {
|
||||||
|
if (!this.showVisibilityToggle || this.compact) {
|
||||||
|
this.lobbyIdVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleVisibility() {
|
||||||
|
if (!this.showVisibilityToggle || this.compact) return;
|
||||||
|
this.lobbyIdVisible = !this.lobbyIdVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enableSelectAll(e: Event) {
|
||||||
|
(e.currentTarget as HTMLElement).classList.add("select-all");
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearSelectAll(e: Event) {
|
||||||
|
(e.currentTarget as HTMLElement).classList.remove("select-all");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildCopyUrl(): Promise<string> {
|
||||||
|
const config = await getServerConfigFromClient();
|
||||||
|
let url = `${window.location.origin}/${config.workerPath(this.lobbyId)}/game/${this.lobbyId}`;
|
||||||
|
if (this.includeLobbyQuery) {
|
||||||
|
url += `?lobby&s=${encodeURIComponent(this.lobbySuffix)}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveCopyText(): Promise<string> {
|
||||||
|
if (this.copyText) return this.copyText;
|
||||||
|
if (!this.lobbyId) return "";
|
||||||
|
return await this.buildCopyUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleCopy() {
|
||||||
|
const text = await this.resolveCopyText();
|
||||||
|
if (!text) return;
|
||||||
|
await copyToClipboard(
|
||||||
|
text,
|
||||||
|
() => (this.copySuccess = true),
|
||||||
|
() => (this.copySuccess = false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private canCopy() {
|
||||||
|
return Boolean(this.copyText || this.lobbyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const canCopy = this.canCopy();
|
||||||
|
const allowMask = this.showVisibilityToggle && !this.compact;
|
||||||
|
const rawLabel = this.displayText || this.lobbyId || this.copyText;
|
||||||
|
const label = this.copySuccess
|
||||||
|
? translateText("common.copied")
|
||||||
|
: allowMask && !this.lobbyIdVisible
|
||||||
|
? this.maskLabel
|
||||||
|
: rawLabel;
|
||||||
|
const disabledClass = canCopy ? "" : "opacity-60 cursor-not-allowed";
|
||||||
|
const toggleDisabled = !this.lobbyId;
|
||||||
|
const toggleClass = toggleDisabled ? "opacity-60 cursor-not-allowed" : "";
|
||||||
|
|
||||||
|
if (this.compact) {
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
@click=${this.handleCopy}
|
||||||
|
class="text-xs text-white/60 font-mono bg-white/5 px-2 py-0.5 rounded border border-white/5 hover:bg-white/10 hover:text-white transition-colors ${disabledClass}"
|
||||||
|
title="${translateText("common.click_to_copy")}"
|
||||||
|
aria-label="${translateText("common.click_to_copy")}"
|
||||||
|
?disabled=${!canCopy}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
${label}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-0.5 bg-white/5 rounded-lg px-2 py-1 border border-white/10 max-w-[220px] flex-nowrap"
|
||||||
|
>
|
||||||
|
${this.showVisibilityToggle
|
||||||
|
? html`<button
|
||||||
|
@click=${this.toggleVisibility}
|
||||||
|
class="p-1.5 rounded-md hover:bg-white/10 text-white/60 hover:text-white transition-colors ${toggleClass}"
|
||||||
|
title="${translateText("user_setting.toggle_visibility")}"
|
||||||
|
?disabled=${toggleDisabled}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
${this.lobbyIdVisible
|
||||||
|
? html`<svg
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
height="16px"
|
||||||
|
width="16px"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M256 105c-101.8 0-188.4 62.7-224 151 35.6 88.3 122.2 151 224 151s188.4-62.7 224-151c-35.6-88.3-122.2-151-224-151zm0 251.7c-56 0-101.7-45.7-101.7-101.7S200 153.3 256 153.3 357.7 199 357.7 255 312 356.7 256 356.7zm0-161.1c-33 0-59.4 26.4-59.4 59.4s26.4 59.4 59.4 59.4 59.4-26.4 59.4-59.4-26.4-59.4-59.4-59.4z"
|
||||||
|
></path>
|
||||||
|
</svg>`
|
||||||
|
: html`<svg
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
height="16px"
|
||||||
|
width="16px"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M448 256s-64-128-192-128S64 256 64 256c32 64 96 128 192 128s160-64 192-128z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="32"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M144 256l224 0"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="32"
|
||||||
|
stroke-linecap="round"
|
||||||
|
></path>
|
||||||
|
</svg>`}
|
||||||
|
</button>`
|
||||||
|
: ""}
|
||||||
|
<button
|
||||||
|
@click=${this.handleCopy}
|
||||||
|
@dblclick=${this.enableSelectAll}
|
||||||
|
@mouseleave=${this.clearSelectAll}
|
||||||
|
class="font-mono text-xs font-bold text-white px-2 cursor-pointer select-none min-w-[80px] text-center truncate tracking-wider bg-transparent border-0 ${disabledClass}"
|
||||||
|
title="${translateText("common.click_to_copy")}"
|
||||||
|
aria-label="${translateText("common.click_to_copy")}"
|
||||||
|
?disabled=${!canCopy}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
${label}
|
||||||
|
</button>
|
||||||
|
${this.showCopyIcon
|
||||||
|
? html`<button
|
||||||
|
@click=${this.handleCopy}
|
||||||
|
class="p-1.5 rounded-md hover:bg-white/10 text-white/60 hover:text-white transition-colors ${disabledClass}"
|
||||||
|
title="${translateText("common.click_to_copy")}"
|
||||||
|
aria-label="${translateText("common.click_to_copy")}"
|
||||||
|
?disabled=${!canCopy}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
height="16px"
|
||||||
|
width="16px"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user