diff --git a/src/lib/components/instances/ServersTab.svelte b/src/lib/components/instances/ServersTab.svelte index 6bac1330..996ebae4 100644 --- a/src/lib/components/instances/ServersTab.svelte +++ b/src/lib/components/instances/ServersTab.svelte @@ -32,6 +32,9 @@ let working = $state(false); let querying = $state(false); let error = $state(""); + let copying = $state(false); + let copyResult = $state<{ address: string; success: boolean } | null>(null); + let copyTimer: ReturnType | undefined; let search = $state(""); let page = $state(0); let selectedIndex = $state(null); @@ -165,9 +168,29 @@ onDestroy(() => { alive = false; generation++; + clearTimeout(copyTimer); resources.dispose(); }); + async function copyAddress(address: string) { + if (copying) return; + copying = true; + copyResult = null; + clearTimeout(copyTimer); + try { + await navigator.clipboard.writeText(address); + if (!alive) return; + copyResult = { address, success: true }; + copyTimer = setTimeout(() => { + copyResult = null; + }, 2000); + } catch { + if (alive) copyResult = { address, success: false }; + } finally { + if (alive) copying = false; + } + } + function showModal(action: "add" | "edit" | "delete") { if (disabled || running || (action !== "add" && !selected)) return; modalAction = action; @@ -327,8 +350,8 @@ />{/if}
- {server.name}{server.address}{server.name}{server.address}{statusLabel(status)}{#if status?.online} · {playerCount(status)} · {status.ping} ms{/if}{/if}
{t("servers.address")}
-
{selected.address}
+
+
+ {selected.address} + +
+ {#if copyResult?.address === selected.address} +

+ {t( + copyResult.success + ? "servers.ipCopied" + : "servers.copyFailed", + )} +

+ {/if} +
{t("servers.status")}
{statusLabel(selectedStatus)}
{#if selectedStatus?.online} @@ -548,6 +601,7 @@ cursor: pointer; } .actions button, + .copy-address, .connect { display: flex; align-items: center; @@ -649,16 +703,26 @@ color: var(--text-secondary); font-size: 0.8rem; } - .ip-span, - dd.blurred { + .blurred { filter: blur(4px); transition: filter 0.2s ease; user-select: none; } - .ip-span:hover, - dd.blurred:hover { + .blurred:hover { filter: blur(0); } + .server-address { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + } + .server-address .blurred { + min-width: 0; + } + .copy-address { + flex-shrink: 0; + } .server-label .online { color: var(--accent-primary); } diff --git a/src/lib/i18n/en-US.json b/src/lib/i18n/en-US.json index b4f48496..8c37fe72 100644 --- a/src/lib/i18n/en-US.json +++ b/src/lib/i18n/en-US.json @@ -64,6 +64,9 @@ "name": "Server name", "address": "Address", "addressHint": "Domain or IP, with an optional port. For IPv6 with a port, use [address]:port.", + "copyIp": "Copy IP", + "ipCopied": "IP copied", + "copyFailed": "Could not copy the IP to the clipboard.", "resourcePacks": "Server resource packs", "prompt": "Prompt", "enabled": "Accept", diff --git a/src/lib/i18n/es-ES.json b/src/lib/i18n/es-ES.json index ceab4d14..bccfac9a 100644 --- a/src/lib/i18n/es-ES.json +++ b/src/lib/i18n/es-ES.json @@ -64,6 +64,9 @@ "name": "Nombre del servidor", "address": "Dirección", "addressHint": "Dominio o IP, con puerto opcional. Para IPv6 con puerto, usa [dirección]:puerto.", + "copyIp": "Copiar IP", + "ipCopied": "IP copiada", + "copyFailed": "No se pudo copiar la IP al portapapeles.", "resourcePacks": "Paquetes de recursos del servidor", "prompt": "Preguntar", "enabled": "Aceptar",