// popup.js const $ = id => document.getElementById(id); const uid = () => 'srv_' + Math.random().toString(36).slice(2,9); let servers = []; let selectedId = null; let statuses = {}; let previousStatuses = {}; let settingsVisible = false; document.addEventListener('DOMContentLoaded', init); $('btnAdd')?.addEventListener('click', handleAdd); $('btnAddServer')?.addEventListener('click', handleAdd); $('btnRefresh').addEventListener('click', manualRefresh); $('btnToggleSettings').addEventListener('click', toggleSettings); $('btnEdit').addEventListener('click', handleEdit); $('btnDelete').addEventListener('click', handleDelete); async function init() { await loadServersFromStorage(); await loadStatusesFromStorage(); await loadSettingsVisibility(); renderServerList(); if (servers.length === 1) selectedId = servers[0].id; renderDetail(selectedId); } // ── Minecraft color code parser ── function parseMinecraftColors(text) { if (!text) return ''; const map = { '§0':'#000000','§1':'#0000AA','§2':'#00AA00','§3':'#00AAAA', '§4':'#AA0000','§5':'#AA00AA','§6':'#FFAA00','§7':'#AAAAAA', '§8':'#555555','§9':'#5555FF','§a':'#55FF55','§b':'#55FFFF', '§c':'#FF5555','§d':'#FF55FF','§e':'#FFFF55','§f':'#FFFFFF', '&0':'#000000','&1':'#0000AA','&2':'#00AA00','&3':'#00AAAA', '&4':'#AA0000','&5':'#AA00AA','&6':'#FFAA00','&7':'#AAAAAA', '&8':'#555555','&9':'#5555FF','&a':'#55FF55','&b':'#55FFFF', '&c':'#FF5555','&d':'#FF55FF','&e':'#FFFF55','&f':'#FFFFFF' }; let html = text.replace(/[<>]/g, ''); let result = ''; let i = 0; while (i < html.length) { const ch = html[i]; if ((ch === '§' || ch === '&') && i + 1 < html.length) { const code = ch + html[i+1].toLowerCase(); if (map[code]) { result += ``; i += 2; continue; } } result += html[i]; i++; } return result; } function get3DAvatarUrl(name, uuid = null) { return `https://mc-heads.net/head/${encodeURIComponent(name)}/64`; } // ── Storage ── async function loadSettingsVisibility() { const obj = await chrome.storage.local.get(['settingsVisible']); settingsVisible = !!obj.settingsVisible; applySettingsVisibility(); } async function saveSettingsVisibility() { await chrome.storage.local.set({ settingsVisible }); } function applySettingsVisibility() { $('settingsForm').classList.toggle('hidden', !settingsVisible); $('serverListPanel').classList.toggle('hidden', !settingsVisible); $('detailButtons').classList.toggle('hidden', !settingsVisible); } async function toggleSettings() { settingsVisible = !settingsVisible; applySettingsVisibility(); await saveSettingsVisibility(); } async function loadServersFromStorage() { const obj = await chrome.storage.local.get(['servers']); servers = obj.servers || []; for (const s of servers) if (!s.id) s.id = uid(); await chrome.storage.local.set({ servers }); } async function saveServersToStorage() { await chrome.storage.local.set({ servers }); } async function loadStatusesFromStorage() { const obj = await chrome.storage.local.get(['serverStatuses']); statuses = obj.serverStatuses || {}; } // ── Server list ── function renderServerList() { const container = $('serversContainer'); container.innerHTML = ''; if (servers.length === 0) { container.innerHTML = '
Noch kein Server hinzugefügt.
'; return; } for (const s of servers) { const li = document.createElement('li'); li.className = 'server-item' + (s.id === selectedId ? ' selected' : ''); li.dataset.id = s.id; const meta = document.createElement('div'); meta.className = 'meta'; const nameEl = document.createElement('div'); nameEl.className = 'name'; nameEl.textContent = s.name || '(Kein Name)'; let urlText = s.url || (s.wpSite || ''); urlText = urlText.replace(':9191', ''); const urlEl = document.createElement('div'); urlEl.className = 'url'; urlEl.textContent = urlText; meta.append(nameEl, urlEl); const bubble = document.createElement('div'); bubble.className = 'status-bubble'; bubble.textContent = '—'; li._bubble = bubble; li.append(meta, bubble); li.addEventListener('click', () => { selectedId = s.id; document.querySelectorAll('.server-item').forEach(el => el.classList.remove('selected')); li.classList.add('selected'); renderDetail(selectedId); }); container.appendChild(li); } updateServerListStatuses(); } // ── Detail ── function renderDetail(id) { if (!id) { $('noSelection').classList.remove('hidden'); $('detailContent').classList.add('hidden'); return; } const srv = servers.find(x => x.id === id); if (!srv) { $('noSelection').classList.remove('hidden'); $('detailContent').classList.add('hidden'); return; } $('noSelection').classList.add('hidden'); $('detailContent').classList.remove('hidden'); $('detailName').textContent = srv.name || 'Unbenannter Server'; let urlToShow = srv.url || (srv.wpSite || 'Lokal'); urlToShow = urlToShow.replace(':9191', ''); $('detailUrlText').textContent = urlToShow; updateDetailForServer(srv, true); } function updateDetailForServer(srv, force = false) { const st = statuses[srv.id]; const prevSt = previousStatuses[srv.id]; const changed = force || JSON.stringify(st) !== JSON.stringify(prevSt); if (!changed) return; const badge = $('statusBadge'); const pulse = $('detailPulse'); if (!st || !st.ok || !st.data) { badge.className = 'online-badge offline'; pulse.className = 'pulsing-dot offline'; $('detailStatus').textContent = 'Offline'; $('detailVersion').textContent = '-'; $('detailPlayers').textContent = '-'; $('detailPing').textContent = '-'; $('motdBar').classList.add('hidden'); updatePlayerList([]); updateBackendServers(null); } else { const d = st.data; const isOnline = !!d.online; badge.className = 'online-badge ' + (isOnline ? 'online' : 'offline'); pulse.className = 'pulsing-dot ' + (isOnline ? 'online' : 'offline'); $('detailStatus').textContent = isOnline ? 'Online' : 'Offline'; $('detailVersion').textContent = d.version || 'unknown'; const count = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0); const max = d.max_players; $('detailPlayers').textContent = max && max !== '-1' ? `${count} / ${max}` : String(count); const ping = d.ping || d.latency; $('detailPing').textContent = typeof ping === 'number' ? ping + ' ms' : '-'; // MOTD const motdRaw = d.motd || (d.network && d.network.motd) || ''; if (motdRaw) { const motdEl = $('motdBar'); motdEl.innerHTML = parseMinecraftColors(motdRaw); motdEl.classList.remove('hidden'); } else { $('motdBar').classList.add('hidden'); } const currentPlayers = Array.isArray(d.players) ? d.players : []; updatePlayerList(currentPlayers); updateBackendServers(d.network?.backend_servers || null); } previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null; } // ── Sub-Server chips ── function updateBackendServers(list) { const section = $('backendSection'); const container = $('backendServersList'); if (!list || list.length === 0) { section.classList.add('hidden'); container.innerHTML = ''; return; } section.classList.remove('hidden'); container.innerHTML = ''; for (const bs of list) { const count = bs.online_players || 0; const chip = document.createElement('div'); chip.className = 'sub-chip' + (count > 0 ? ' active' : ''); const dot = document.createElement('span'); dot.className = 'sub-chip-dot'; const nameEl = document.createElement('span'); nameEl.className = 'sub-chip-name'; nameEl.textContent = bs.name; const countEl = document.createElement('span'); countEl.className = 'sub-chip-count'; countEl.textContent = count > 0 ? count : 'leer'; chip.append(dot, nameEl, countEl); container.appendChild(chip); } } // ── Player list ── // Versucht den Server-Namen aus den Daten zu lesen. // Wenn dein Plugin "server" im Spieler-Objekt mitgibt, wird es als Tag angezeigt. function updatePlayerList(players) { const grid = $('detailPlayerList'); grid.innerHTML = ''; if (!players || players.length === 0) { grid.innerHTML = '
Keine Spieler online.
'; return; } for (const p of players) { const card = document.createElement('div'); card.className = 'player-card'; let name = '', uuid = null, prefix = '', serverName = null; if (typeof p === 'object') { name = p.name || p.username || p.player || ''; uuid = p.uuid || null; prefix = p.prefix || p.group || ''; // Mögliche Felder: server, current_server, connected_server serverName = p.server || p.current_server || p.connected_server || null; } else { name = String(p); } const img = document.createElement('img'); img.src = (typeof p === 'object' && p.avatar) ? p.avatar : get3DAvatarUrl(name, uuid); img.className = 'player-avatar'; img.loading = 'lazy'; img.onerror = function() { this.onerror = null; this.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/64`; }; const info = document.createElement('div'); info.className = 'player-info'; if (prefix) { const prefixEl = document.createElement('div'); prefixEl.className = 'player-prefix'; prefixEl.innerHTML = parseMinecraftColors(prefix); info.appendChild(prefixEl); } const nameEl = document.createElement('div'); nameEl.className = 'player-name'; nameEl.textContent = name; info.appendChild(nameEl); if (serverName) { const tag = document.createElement('span'); tag.className = 'player-server-tag'; tag.textContent = serverName; info.appendChild(tag); } card.append(img, info); grid.appendChild(card); } } // ── Server list status bubbles ── function updateServerListStatuses() { const items = $('serversContainer').querySelectorAll('.server-item'); items.forEach(item => { const s = servers.find(x => x.id === item.dataset.id); if (!s || !item._bubble) return; const st = statuses[s.id]; if (!st || !st.ok || !st.data || !st.data.online) { item._bubble.textContent = 'Offline'; item._bubble.style.background = 'rgba(239,68,68,0.15)'; item._bubble.style.color = '#f87171'; } else { item._bubble.textContent = 'Online'; item._bubble.style.background = 'rgba(34,197,94,0.15)'; item._bubble.style.color = '#4ade80'; } }); } // ── Actions ── async function handleAdd() { const name = $('inputName').value.trim(); const url = $('inputUrl').value.trim(); const wpSite = $('inputWpSite').value.trim(); const wpServerId = $('inputWpServerId').value.trim(); if (!url && !wpSite) return; const s = { id: uid(), name: name || url || wpSite, url: url || null, wpSite: wpSite || null, wpServerId: wpServerId || null }; servers.push(s); await saveServersToStorage(); $('inputName').value = $('inputUrl').value = $('inputWpSite').value = $('inputWpServerId').value = ''; renderServerList(); } async function handleEdit() { if (!selectedId) return; const srv = servers.find(s => s.id === selectedId); if (!srv) return; const newName = prompt('Name:', srv.name); if (newName !== null) srv.name = newName.trim(); await saveServersToStorage(); renderServerList(); renderDetail(selectedId); } async function handleDelete() { if (!selectedId || !confirm('Server wirklich löschen?')) return; servers = servers.filter(s => s.id !== selectedId); selectedId = null; await saveServersToStorage(); renderServerList(); renderDetail(null); } async function manualRefresh() { try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {} } // ── Live updates from background ── chrome.storage.onChanged.addListener((changes, area) => { if (area === 'local' && changes.serverStatuses) { statuses = changes.serverStatuses.newValue || {}; updateServerListStatuses(); if (selectedId) { const srv = servers.find(s => s.id === selectedId); if (srv) updateDetailForServer(srv); } } });