diff --git a/desktop/src/main.ts b/desktop/src/main.ts
index 5860d3d..86c10bd 100644
--- a/desktop/src/main.ts
+++ b/desktop/src/main.ts
@@ -90,6 +90,12 @@ const ctxName = document.getElementById("ctx-name")!;
const ctxFp = document.getElementById("ctx-fp")!;
const ctxCallBtn = document.getElementById("ctx-call-btn")!;
const ctxCloseBtn = document.getElementById("ctx-close-btn")!;
+// Relay management
+const sRelayList = document.getElementById("s-relay-list")!;
+const sRelayName = document.getElementById("s-relay-name") as HTMLInputElement;
+const sRelayAddr = document.getElementById("s-relay-addr") as HTMLInputElement;
+const sRelayAdd = document.getElementById("s-relay-add")!;
+
// Settings
const settingsPanel = document.getElementById("settings-panel")!;
const settingsBtn = document.getElementById("settings-btn")!;
@@ -547,6 +553,80 @@ listen("call-event", (event: any) => {
});
// ── Settings ──────────────────────────────────────────────────────
+// ── Relay list management ──────────────────────────────────────
+function renderRelayList() {
+ const s = loadSettings();
+ sRelayList.innerHTML = "";
+ for (let i = 0; i < s.relays.length; i++) {
+ const r = s.relays[i];
+ const isActive = i === s.selectedRelay;
+ const row = document.createElement("div");
+ row.style.cssText = "display:flex;align-items:center;gap:6px;padding:8px;border-radius:6px;margin-bottom:4px;cursor:pointer;" +
+ (isActive ? "background:rgba(74,222,128,0.12);border:1px solid var(--green);" : "background:var(--surface);border:1px solid transparent;");
+ row.innerHTML = `
+
+ ${r.name}
+ ${r.address}
+
+ ${isActive ? 'ACTIVE' : ''}
+
+ `;
+ // Click to select (not on the X button)
+ row.addEventListener("click", (e) => {
+ if ((e.target as HTMLElement).classList.contains("relay-rm-btn")) return;
+ const settings = loadSettings();
+ if (i !== settings.selectedRelay) {
+ settings.selectedRelay = i;
+ saveSettings(settings);
+ renderRelayList();
+ // Reconnect to new relay
+ reconnectSignal();
+ }
+ });
+ sRelayList.appendChild(row);
+ }
+ // Wire remove buttons
+ sRelayList.querySelectorAll(".relay-rm-btn").forEach((btn) => {
+ btn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const idx = parseInt((btn as HTMLElement).dataset.idx || "0");
+ const settings = loadSettings();
+ if (settings.relays.length <= 1) return; // keep at least one
+ settings.relays.splice(idx, 1);
+ if (settings.selectedRelay >= settings.relays.length) {
+ settings.selectedRelay = 0;
+ }
+ saveSettings(settings);
+ renderRelayList();
+ reconnectSignal();
+ });
+ });
+}
+
+sRelayAdd.addEventListener("click", () => {
+ const name = sRelayName.value.trim();
+ const addr = sRelayAddr.value.trim();
+ if (!name || !addr) return;
+ if (!addr.includes(":")) return; // must be host:port
+ const s = loadSettings();
+ s.relays.push({ name, address: addr });
+ saveSettings(s);
+ sRelayName.value = "";
+ sRelayAddr.value = "";
+ renderRelayList();
+});
+
+async function reconnectSignal() {
+ // Deregister from current relay, then auto-connect to new one
+ try { await invoke("deregister"); } catch {}
+ lobbyUsers.clear();
+ renderLobbyUsers();
+ lobbyDot.style.background = "var(--yellow)";
+ lobbyRelayLabel.textContent = "Reconnecting...";
+ // Short delay to let deregister complete
+ setTimeout(() => autoConnect(), 500);
+}
+
function openSettings() {
const s = loadSettings();
sRoom.value = s.room;
@@ -562,6 +642,7 @@ function openSettings() {
sQuality.value = String(qi);
updateQualityUI(qi);
sFingerprint.textContent = myFingerprint || "(loading...)";
+ renderRelayList();
settingsPanel.classList.remove("hidden");
}
@@ -584,6 +665,8 @@ settingsSave.addEventListener("click", () => {
invoke("set_dred_verbose_logs", { enabled: s.dredDebugLogs }).catch(() => {});
invoke("set_call_debug_logs", { enabled: s.callDebugLogs }).catch(() => {});
sCallDebugSection.style.display = s.callDebugLogs ? "" : "none";
+ // Update lobby room label
+ lobbyRoomLabel.textContent = s.room || "general";
settingsPanel.classList.add("hidden");
});