From 18cb317730c6c3b109fcf5d4bf361d76b87beddc Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 8 Feb 2026 14:11:02 +0000 Subject: [PATCH] add/remove server --- frontend/app.js | 90 +++++++++++++++++++++++++++++++++++++++++++++ frontend/index.html | 11 ++++++ frontend/style.css | 83 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) diff --git a/frontend/app.js b/frontend/app.js index 931931e..ece54f9 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -199,6 +199,15 @@ function getServerEntries() { }); } +function setConfig(nextConfig) { + localStorage.setItem('config', JSON.stringify(nextConfig)); +} + +async function refreshServerStatus() { + await InitializeServerStatus(); + renderMenu(); +} + function buildDefaultOptions(channel) { const selected = {}; if (!channel || !Array.isArray(channel.options)) return selected; @@ -227,6 +236,9 @@ function renderMenu() { const sourceSelect = document.getElementById('source-select'); const channelSelect = document.getElementById('channel-select'); const filtersContainer = document.getElementById('filters-container'); + const sourcesList = document.getElementById('sources-list'); + const addSourceBtn = document.getElementById('add-source-btn'); + const sourceInput = document.getElementById('source-input'); if (!sourceSelect || !channelSelect || !filtersContainer) return; @@ -299,6 +311,84 @@ function renderMenu() { applyTheme(); }; } + + if (sourcesList) { + sourcesList.innerHTML = ""; + serverEntries.forEach((entry) => { + const row = document.createElement('div'); + row.className = 'source-item'; + + const text = document.createElement('span'); + text.textContent = entry.url; + + const removeBtn = document.createElement('button'); + removeBtn.type = 'button'; + removeBtn.textContent = 'Remove'; + removeBtn.onclick = async () => { + const config = getConfig(); + config.servers = (config.servers || []).filter((serverObj) => { + const key = Object.keys(serverObj)[0]; + return key !== entry.url; + }); + setConfig(config); + + const remaining = getServerEntries(); + if (remaining.length === 0) { + localStorage.removeItem('session'); + } else { + const nextServerUrl = remaining[0].url; + const nextServer = remaining[0]; + const nextChannel = nextServer.data && nextServer.data.channels ? nextServer.data.channels[0] : null; + setSession({ + server: nextServerUrl, + channel: nextChannel, + options: nextChannel ? buildDefaultOptions(nextChannel) : {} + }); + } + + await refreshServerStatus(); + resetAndReload(); + }; + + row.appendChild(text); + row.appendChild(removeBtn); + sourcesList.appendChild(row); + }); + } + + if (addSourceBtn && sourceInput) { + addSourceBtn.onclick = async () => { + const raw = sourceInput.value.trim(); + if (!raw) return; + const normalized = raw.endsWith('/') ? raw.slice(0, -1) : raw; + + const config = getConfig(); + const exists = (config.servers || []).some((serverObj) => Object.keys(serverObj)[0] === normalized); + if (!exists) { + config.servers = config.servers || []; + config.servers.push({ [normalized]: {} }); + setConfig(config); + sourceInput.value = ''; + await refreshServerStatus(); + + const session = getSession(); + if (!session || session.server !== normalized) { + const entries = getServerEntries(); + const addedEntry = entries.find((entry) => entry.url === normalized); + const nextChannel = addedEntry && addedEntry.data && addedEntry.data.channels + ? addedEntry.data.channels[0] + : null; + setSession({ + server: normalized, + channel: nextChannel, + options: nextChannel ? buildDefaultOptions(nextChannel) : {} + }); + } + renderMenu(); + resetAndReload(); + } + }; + } } function renderFilters(container, session) { diff --git a/frontend/index.html b/frontend/index.html index ed40e49..eb43f06 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -62,6 +62,17 @@ + diff --git a/frontend/style.css b/frontend/style.css index 2ed0512..91c66f3 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -288,6 +288,89 @@ body.theme-light .sidebar { letter-spacing: 0.5px; } +.input-row { + display: flex; + gap: 8px; + align-items: center; +} + +.input-row input { + flex: 1; + padding: 8px 12px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--text-primary); + font-size: 14px; +} + +.input-row input::placeholder { + color: var(--text-secondary); +} + +.input-row input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1); +} + +body.theme-light .input-row input:focus { + box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.08); +} + +.btn-secondary { + padding: 8px 12px; + border-radius: 6px; + border: 1px solid var(--border); + background: transparent; + color: var(--text-primary); + cursor: pointer; + font-size: 13px; + transition: all 0.2s ease; +} + +.btn-secondary:hover { + background: var(--bg-tertiary); +} + +.sources-list { + padding: 8px 24px 16px 24px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.source-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg-tertiary); +} + +.source-item span { + font-size: 12px; + color: var(--text-primary); + word-break: break-all; +} + +.source-item button { + padding: 6px 8px; + border-radius: 6px; + border: 1px solid var(--border); + background: transparent; + color: var(--text-primary); + cursor: pointer; + font-size: 12px; +} + +.source-item button:hover { + background: var(--bg-tertiary); +} + .setting-item select { width: 100%; padding: 8px 12px;