From 700e6026b69a807e1d8682907fef5eee83246cce Mon Sep 17 00:00:00 2001 From: M_Viper Date: Mon, 9 Feb 2026 21:05:51 +0000 Subject: [PATCH] Upload popup.js via GUI --- BungeeCord-Chrome/popup.js | 717 +++++++++++++++++-------------------- 1 file changed, 334 insertions(+), 383 deletions(-) diff --git a/BungeeCord-Chrome/popup.js b/BungeeCord-Chrome/popup.js index 448d8e8..a628b66 100644 --- a/BungeeCord-Chrome/popup.js +++ b/BungeeCord-Chrome/popup.js @@ -1,384 +1,335 @@ -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(); -} - -// --- Settings Visibility --- -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(); -} - -// --- Storage --- -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 || {}; -} - -// --- Layout --- -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'; -} - -// --- Render Server List --- -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(false); -} - -// --- Render 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 ? srv.wpSite : 'Lokal'); - urlToShow = urlToShow.replace(':9191', ''); - - detailUrlText.textContent = urlToShow; - - updateDetailForServer(srv, true); -} - -// --- Update Detail --- -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 = false; - if (force) { - playersChanged = true; - } else { - playersChanged = JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers); - } - - if (playersChanged) { - updatePlayerList(currentPlayers); - } - } - - previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null; -} - -// --- Spielerliste --- -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'); - const name = typeof p === 'object' ? p.name || p.username || p.player || '' : String(p); - - if (typeof p === 'object' && p.avatar) { - const img = document.createElement('img'); - img.src = p.avatar; - img.className = 'player-avatar'; - img.title = name; - li.appendChild(img); - } else { - // Falls kein Avatar vorhanden, generiere einen von mc-heads.net - const img = document.createElement('img'); - img.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/32`; - img.className = 'player-avatar'; - img.title = name; - li.appendChild(img); - } - - detailPlayerList.appendChild(li); - } -} - -// --- Update Server List Statuses --- -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]; - const prevSt = previousStatuses[s.id]; - - const statusChanged = !prevSt || - (prevSt.ok !== st?.ok) || - (prevSt.data?.online !== st?.data?.online); - - if (!statusChanged) return; - - 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)'; - } - - previousStatuses[s.id] = st ? JSON.parse(JSON.stringify(st)) : null; - }); -} - -// --- Add / Edit / Delete --- -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) { - alert('Bitte URL oder WP Site angeben'); - 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; - const newUrl = prompt('URL:', srv.url || '') || srv.url; - const newWpSite = prompt('WP Site:', srv.wpSite || '') || srv.wpSite; - const newWpServerId = prompt('WP Server ID:', srv.wpServerId || '') || srv.wpServerId; - - srv.name = newName.trim(); - srv.url = newUrl.trim(); - srv.wpSite = newWpSite.trim(); - srv.wpServerId = newWpServerId.trim(); - - await saveServersToStorage(); - renderServerList(); - renderDetail(selectedId); -} - -async function handleDelete() { - if (!selectedId) return; - if (!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) { - console.error('Refresh fehlgeschlagen:', e); - } -} - -// --- Storage Listener --- -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); - } - } +//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); + } + } }); \ No newline at end of file