//popup.js const $ = id => document.getElementById(id); const uid = () => 'srv_' + Math.random().toString(36).slice(2,9); const inputName = $('inputName'); const inputUrl = $('inputUrl'); const inputWpSite = $('inputWpSite'); const inputWpServerId = $('inputWpServerId'); const btnAdd = $('btnAddServer'); const serversContainer = $('serversContainer'); const serverListPanel = document.querySelector('.server-list'); const btnRefresh = $('btnRefresh'); const btnToggleSettings = $('btnToggleSettings'); const settingsForm = $('settingsForm'); const noSelection = $('noSelection'); const detailContent = $('detailContent'); const detailName = $('detailName'); const detailUrlText = $('detailUrlText'); const detailStatus = $('detailStatus'); const detailPulse = $('detailPulse'); const detailVersion = $('detailVersion'); const detailPlayers = $('detailPlayers'); const detailPing = $('detailPing'); const detailPlayerList = $('detailPlayerList'); const btnEdit = $('btnEdit'); const btnDelete = $('btnDelete'); let servers = []; let selectedId = null; let statuses = {}; let previousStatuses = {}; let settingsVisible = false; document.addEventListener('DOMContentLoaded', init); btnAdd.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); adjustDetailLayout(); } // --- NEU: Minecraft Color Parser (Eingebaut ohne Löschung) --- 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' }; let html = text.replace(/[<>]/g, ''); Object.keys(map).forEach(code => { const color = map[code]; const regex = new RegExp(code, 'g'); html = html.replace(regex, ``); }); return `${html}`.replace(/&l/g, '').replace(/&r/g, ''); } // --- 3D Avatar URL Generator --- function get3DAvatarUrl(playerName, uuid = null) { const isBedrock = playerName.includes('.') || (uuid && uuid.startsWith('xuid')); if (isBedrock) { if (uuid && uuid.length > 0) { return `https://mc-heads.net/head/${encodeURIComponent(uuid)}/64`; } return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`; } else { return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`; } } async function loadSettingsVisibility() { const obj = await chrome.storage.local.get(['settingsVisible']); settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false; if (!settingsVisible) settingsForm.classList.add('hidden'); else settingsForm.classList.remove('hidden'); } async function saveSettingsVisibility() { await chrome.storage.local.set({ settingsVisible }); } async function toggleSettings() { settingsVisible = !settingsVisible; settingsForm.classList.toggle('hidden'); await saveSettingsVisibility(); adjustDetailLayout(); } 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 || {}; } function adjustDetailLayout() { const settingsHidden = settingsForm.classList.contains('hidden'); detailContent.classList.toggle('full-width', settingsHidden); serverListPanel.classList.toggle('hidden', settingsHidden); btnEdit.style.display = settingsHidden ? 'none' : 'inline-block'; btnDelete.style.display = settingsHidden ? 'none' : 'inline-block'; } function renderServerList() { serversContainer.innerHTML = ''; if (servers.length === 0) { serversContainer.innerHTML = '
Noch keine Server hinzugefügt.
'; return; } for (const s of servers) { const item = document.createElement('li'); item.className = 'server-item'; item.dataset.id = s.id; const meta = document.createElement('div'); meta.className = 'meta'; const name = document.createElement('div'); name.className = 'name'; name.textContent = s.name || '(Kein Name)'; let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : ''); listUrl = listUrl.replace(':9191', ''); const url = document.createElement('div'); url.className = 'url'; url.textContent = listUrl; meta.append(name, url); const statusBubble = document.createElement('div'); statusBubble.className = 'status-bubble'; statusBubble.textContent = '—'; statusBubble.style.backgroundColor = 'transparent'; item.statusBubble = statusBubble; item.append(meta, statusBubble); item.addEventListener('click', () => { selectedId = s.id; renderDetail(selectedId); }); serversContainer.appendChild(item); } updateServerListStatuses(); } // --- Fortsetzung popup.js --- 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 ? 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 statusChanged = force || !prevSt || (prevSt.ok !== st?.ok) || (prevSt.data?.online !== st?.data?.online); if (!st || !st.ok || !st.data) { if (statusChanged) { detailStatus.textContent = 'Offline'; detailPulse.classList.remove('online'); detailVersion.textContent = '-'; detailPlayers.textContent = '-'; detailPing.textContent = '-'; updatePlayerList([]); } } else { const d = st.data; if (statusChanged) { detailStatus.textContent = d.online ? 'Online' : 'Offline'; if (d.online) detailPulse.classList.add('online'); else detailPulse.classList.remove('online'); } const newVersion = d.version || 'unknown'; if (force || detailVersion.textContent !== newVersion) detailVersion.textContent = newVersion; const playersCount = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0); const maxPlayers = d.max_players; let newPlayersText = String(playersCount); if (maxPlayers && maxPlayers !== '-1') newPlayersText += ` / ${maxPlayers}`; if (force || detailPlayers.textContent !== newPlayersText) detailPlayers.textContent = newPlayersText; let pingVal = d.ping || d.latency || '-'; if (pingVal !== '-' && typeof pingVal === 'number') pingVal = pingVal + ' ms'; if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) detailPing.textContent = pingVal; const currentPlayers = Array.isArray(d.players) ? d.players : []; const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : []; let playersChanged = force || JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers); if (playersChanged) updatePlayerList(currentPlayers); } previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null; } // --- Spielerliste mit Prefix-Hover und Farbsupport --- function updatePlayerList(players) { detailPlayerList.innerHTML = ''; if (!players || players.length === 0) { detailPlayerList.innerHTML = '
  • Keine Spieler online.
  • '; return; } for (const p of players) { const li = document.createElement('li'); li.className = 'player-item'; // CSS Klasse für den Hover-Container let name = ''; let uuid = null; let prefix = ''; if (typeof p === 'object') { name = p.name || p.username || p.player || ''; uuid = p.uuid || null; prefix = p.prefix || ''; } 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`; }; // Das Hover-Element mit farbigem Prefix + Name const info = document.createElement('div'); info.className = 'player-hover-info'; info.innerHTML = `${parseMinecraftColors(prefix)} ${name}`.trim(); li.appendChild(img); li.appendChild(info); detailPlayerList.appendChild(li); } } function updateServerListStatuses() { const items = serversContainer.querySelectorAll('.server-item'); items.forEach(item => { const s = servers.find(x => x.id === item.dataset.id); if (!s) return; const st = statuses[s.id]; if (!st || !st.ok || !st.data) { item.statusBubble.textContent = 'Offline'; item.statusBubble.style.backgroundColor = 'var(--offline)'; } else if (st.data.online) { item.statusBubble.textContent = 'Online'; item.statusBubble.style.backgroundColor = 'var(--online)'; } else { item.statusBubble.textContent = 'Offline'; item.statusBubble.style.backgroundColor = 'var(--offline)'; } }); } 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) || srv.name; 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) {} } 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); } } });