Upload popup.js via GUI

This commit is contained in:
2026-02-09 21:05:51 +00:00
parent b4ee8b0564
commit 700e6026b6

View File

@@ -1,384 +1,335 @@
const $ = id => document.getElementById(id); //popup.js
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9); const $ = id => document.getElementById(id);
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
const inputName = $('inputName');
const inputUrl = $('inputUrl'); const inputName = $('inputName');
const inputWpSite = $('inputWpSite'); const inputUrl = $('inputUrl');
const inputWpServerId = $('inputWpServerId'); const inputWpSite = $('inputWpSite');
const btnAdd = $('btnAddServer'); const inputWpServerId = $('inputWpServerId');
const serversContainer = $('serversContainer'); const btnAdd = $('btnAddServer');
const serverListPanel = document.querySelector('.server-list'); const serversContainer = $('serversContainer');
const btnRefresh = $('btnRefresh'); const serverListPanel = document.querySelector('.server-list');
const btnToggleSettings = $('btnToggleSettings'); const btnRefresh = $('btnRefresh');
const settingsForm = $('settingsForm'); const btnToggleSettings = $('btnToggleSettings');
const settingsForm = $('settingsForm');
const noSelection = $('noSelection');
const detailContent = $('detailContent'); const noSelection = $('noSelection');
const detailName = $('detailName'); const detailContent = $('detailContent');
const detailUrlText = $('detailUrlText'); const detailName = $('detailName');
const detailStatus = $('detailStatus'); const detailUrlText = $('detailUrlText');
const detailPulse = $('detailPulse'); const detailStatus = $('detailStatus');
const detailVersion = $('detailVersion'); const detailPulse = $('detailPulse');
const detailPlayers = $('detailPlayers'); const detailVersion = $('detailVersion');
const detailPing = $('detailPing'); const detailPlayers = $('detailPlayers');
const detailPlayerList = $('detailPlayerList'); const detailPing = $('detailPing');
const btnEdit = $('btnEdit'); const detailPlayerList = $('detailPlayerList');
const btnDelete = $('btnDelete'); const btnEdit = $('btnEdit');
const btnDelete = $('btnDelete');
let servers = [];
let selectedId = null; let servers = [];
let statuses = {}; let selectedId = null;
let previousStatuses = {}; let statuses = {};
let settingsVisible = false; let previousStatuses = {};
let settingsVisible = false;
document.addEventListener('DOMContentLoaded', init);
btnAdd.addEventListener('click', handleAdd); document.addEventListener('DOMContentLoaded', init);
btnRefresh.addEventListener('click', manualRefresh); btnAdd.addEventListener('click', handleAdd);
btnToggleSettings.addEventListener('click', toggleSettings); btnRefresh.addEventListener('click', manualRefresh);
btnEdit.addEventListener('click', handleEdit); btnToggleSettings.addEventListener('click', toggleSettings);
btnDelete.addEventListener('click', handleDelete); btnEdit.addEventListener('click', handleEdit);
btnDelete.addEventListener('click', handleDelete);
async function init() {
await loadServersFromStorage(); async function init() {
await loadStatusesFromStorage(); await loadServersFromStorage();
await loadSettingsVisibility(); await loadStatusesFromStorage();
renderServerList(); await loadSettingsVisibility();
renderServerList();
if (servers.length === 1) selectedId = servers[0].id;
renderDetail(selectedId); if (servers.length === 1) selectedId = servers[0].id;
renderDetail(selectedId);
adjustDetailLayout();
} adjustDetailLayout();
}
// --- Settings Visibility ---
async function loadSettingsVisibility() { // --- NEU: Minecraft Color Parser (Eingebaut ohne Löschung) ---
const obj = await chrome.storage.local.get(['settingsVisible']); function parseMinecraftColors(text) {
settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false; if (!text) return '';
if (!settingsVisible) settingsForm.classList.add('hidden'); const map = {
else settingsForm.classList.remove('hidden'); '&0': '#000000', '&1': '#0000AA', '&2': '#00AA00', '&3': '#00AAAA',
} '&4': '#AA0000', '&5': '#AA00AA', '&6': '#FFAA00', '&7': '#AAAAAA',
'&8': '#555555', '&9': '#5555FF', '&a': '#55FF55', '&b': '#55FFFF',
async function saveSettingsVisibility() { '&c': '#FF5555', '&d': '#FF55FF', '&e': '#FFFF55', '&f': '#FFFFFF'
await chrome.storage.local.set({ settingsVisible }); };
} let html = text.replace(/[<>]/g, '');
Object.keys(map).forEach(code => {
async function toggleSettings() { const color = map[code];
settingsVisible = !settingsVisible; const regex = new RegExp(code, 'g');
settingsForm.classList.toggle('hidden'); html = html.replace(regex, `</span><span style="color: ${color}">`);
await saveSettingsVisibility(); });
adjustDetailLayout(); return `<span>${html}</span>`.replace(/&l/g, '<b>').replace(/&r/g, '</b>');
} }
// --- Storage --- // --- 3D Avatar URL Generator ---
async function loadServersFromStorage() { function get3DAvatarUrl(playerName, uuid = null) {
const obj = await chrome.storage.local.get(['servers']); const isBedrock = playerName.includes('.') || (uuid && uuid.startsWith('xuid'));
servers = obj.servers || []; if (isBedrock) {
for (const s of servers) if (!s.id) s.id = uid(); if (uuid && uuid.length > 0) {
await chrome.storage.local.set({ servers }); return `https://mc-heads.net/head/${encodeURIComponent(uuid)}/64`;
} }
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
async function saveServersToStorage() { } else {
await chrome.storage.local.set({ servers }); return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
} }
}
async function loadStatusesFromStorage() {
const obj = await chrome.storage.local.get(['serverStatuses']); async function loadSettingsVisibility() {
statuses = obj.serverStatuses || {}; const obj = await chrome.storage.local.get(['settingsVisible']);
} settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false;
if (!settingsVisible) settingsForm.classList.add('hidden');
// --- Layout --- else settingsForm.classList.remove('hidden');
function adjustDetailLayout() { }
const settingsHidden = settingsForm.classList.contains('hidden');
detailContent.classList.toggle('full-width', settingsHidden); async function saveSettingsVisibility() {
serverListPanel.classList.toggle('hidden', settingsHidden); await chrome.storage.local.set({ settingsVisible });
btnEdit.style.display = settingsHidden ? 'none' : 'inline-block'; }
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
} async function toggleSettings() {
settingsVisible = !settingsVisible;
// --- Render Server List --- settingsForm.classList.toggle('hidden');
function renderServerList() { await saveSettingsVisibility();
serversContainer.innerHTML = ''; adjustDetailLayout();
if (servers.length === 0) { }
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
return; async function loadServersFromStorage() {
} const obj = await chrome.storage.local.get(['servers']);
servers = obj.servers || [];
for (const s of servers) { for (const s of servers) if (!s.id) s.id = uid();
const item = document.createElement('li'); await chrome.storage.local.set({ servers });
item.className = 'server-item'; }
item.dataset.id = s.id;
async function saveServersToStorage() {
const meta = document.createElement('div'); await chrome.storage.local.set({ servers });
meta.className = 'meta'; }
const name = document.createElement('div');
name.className = 'name'; async function loadStatusesFromStorage() {
name.textContent = s.name || '(Kein Name)'; const obj = await chrome.storage.local.get(['serverStatuses']);
statuses = obj.serverStatuses || {};
let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : ''); }
listUrl = listUrl.replace(':9191', '');
function adjustDetailLayout() {
const url = document.createElement('div'); const settingsHidden = settingsForm.classList.contains('hidden');
url.className = 'url'; detailContent.classList.toggle('full-width', settingsHidden);
url.textContent = listUrl; serverListPanel.classList.toggle('hidden', settingsHidden);
meta.append(name, url); btnEdit.style.display = settingsHidden ? 'none' : 'inline-block';
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
const statusBubble = document.createElement('div'); }
statusBubble.className = 'status-bubble';
statusBubble.textContent = '—'; function renderServerList() {
statusBubble.style.backgroundColor = 'transparent'; serversContainer.innerHTML = '';
item.statusBubble = statusBubble; if (servers.length === 0) {
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
item.append(meta, statusBubble); return;
item.addEventListener('click', () => { }
selectedId = s.id; for (const s of servers) {
renderDetail(selectedId); const item = document.createElement('li');
}); item.className = 'server-item';
item.dataset.id = s.id;
serversContainer.appendChild(item); const meta = document.createElement('div');
} meta.className = 'meta';
updateServerListStatuses(false); const name = document.createElement('div');
} name.className = 'name';
name.textContent = s.name || '(Kein Name)';
// --- Render Detail --- let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : '');
function renderDetail(id) { listUrl = listUrl.replace(':9191', '');
if (!id) { const url = document.createElement('div');
noSelection.classList.remove('hidden'); url.className = 'url';
detailContent.classList.add('hidden'); url.textContent = listUrl;
return; meta.append(name, url);
} const statusBubble = document.createElement('div');
const srv = servers.find(x => x.id === id); statusBubble.className = 'status-bubble';
if (!srv) { statusBubble.textContent = '—';
noSelection.classList.remove('hidden'); statusBubble.style.backgroundColor = 'transparent';
detailContent.classList.add('hidden'); item.statusBubble = statusBubble;
return; item.append(meta, statusBubble);
} item.addEventListener('click', () => {
noSelection.classList.add('hidden'); selectedId = s.id;
detailContent.classList.remove('hidden'); renderDetail(selectedId);
});
detailName.textContent = srv.name || 'Unbenannter Server'; serversContainer.appendChild(item);
}
let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal'); updateServerListStatuses();
urlToShow = urlToShow.replace(':9191', ''); }
detailUrlText.textContent = urlToShow; // --- Fortsetzung popup.js ---
updateDetailForServer(srv, true); function renderDetail(id) {
} if (!id) {
noSelection.classList.remove('hidden');
// --- Update Detail --- detailContent.classList.add('hidden');
function updateDetailForServer(srv, force = false) { return;
const st = statuses[srv.id]; }
const prevSt = previousStatuses[srv.id]; const srv = servers.find(x => x.id === id);
if (!srv) {
const statusChanged = force || noSelection.classList.remove('hidden');
!prevSt || detailContent.classList.add('hidden');
(prevSt.ok !== st?.ok) || return;
(prevSt.data?.online !== st?.data?.online); }
noSelection.classList.add('hidden');
if (!st || !st.ok || !st.data) { detailContent.classList.remove('hidden');
if (statusChanged) { detailName.textContent = srv.name || 'Unbenannter Server';
detailStatus.textContent = 'Offline'; let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal');
detailPulse.classList.remove('online'); urlToShow = urlToShow.replace(':9191', '');
detailVersion.textContent = '-'; detailUrlText.textContent = urlToShow;
detailPlayers.textContent = '-'; updateDetailForServer(srv, true);
detailPing.textContent = '-'; }
updatePlayerList([]);
} function updateDetailForServer(srv, force = false) {
} else { const st = statuses[srv.id];
const d = st.data; const prevSt = previousStatuses[srv.id];
const statusChanged = force || !prevSt || (prevSt.ok !== st?.ok) || (prevSt.data?.online !== st?.data?.online);
if (statusChanged) {
detailStatus.textContent = d.online ? 'Online' : 'Offline'; if (!st || !st.ok || !st.data) {
if (d.online) { if (statusChanged) {
detailPulse.classList.add('online'); detailStatus.textContent = 'Offline';
} else { detailPulse.classList.remove('online');
detailPulse.classList.remove('online'); detailVersion.textContent = '-';
} detailPlayers.textContent = '-';
} detailPing.textContent = '-';
updatePlayerList([]);
const newVersion = d.version || 'unknown'; }
if (force || detailVersion.textContent !== newVersion) { } else {
detailVersion.textContent = newVersion; const d = st.data;
} if (statusChanged) {
detailStatus.textContent = d.online ? 'Online' : 'Offline';
const playersCount = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0); if (d.online) detailPulse.classList.add('online');
const maxPlayers = d.max_players; else detailPulse.classList.remove('online');
let newPlayersText = String(playersCount); }
if (maxPlayers && maxPlayers !== '-1') { const newVersion = d.version || 'unknown';
newPlayersText += ` / ${maxPlayers}`; 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;
if (force || detailPlayers.textContent !== newPlayersText) { let newPlayersText = String(playersCount);
detailPlayers.textContent = newPlayersText; if (maxPlayers && maxPlayers !== '-1') newPlayersText += ` / ${maxPlayers}`;
} if (force || detailPlayers.textContent !== newPlayersText) detailPlayers.textContent = newPlayersText;
let pingVal = d.ping || d.latency || '-';
let pingVal = d.ping || d.latency || '-'; if (pingVal !== '-' && typeof pingVal === 'number') pingVal = pingVal + ' ms';
if (pingVal !== '-' && typeof pingVal === 'number') { if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) detailPing.textContent = pingVal;
pingVal = pingVal + ' ms';
} const currentPlayers = Array.isArray(d.players) ? d.players : [];
const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : [];
if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) { let playersChanged = force || JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers);
detailPing.textContent = pingVal; if (playersChanged) updatePlayerList(currentPlayers);
} }
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
const currentPlayers = Array.isArray(d.players) ? d.players : []; }
const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : [];
// --- Spielerliste mit Prefix-Hover und Farbsupport ---
let playersChanged = false; function updatePlayerList(players) {
if (force) { detailPlayerList.innerHTML = '';
playersChanged = true; if (!players || players.length === 0) {
} else { detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>';
playersChanged = JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers); return;
} }
if (playersChanged) { for (const p of players) {
updatePlayerList(currentPlayers); const li = document.createElement('li');
} li.className = 'player-item'; // CSS Klasse für den Hover-Container
}
let name = '';
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null; let uuid = null;
} let prefix = '';
// --- Spielerliste --- if (typeof p === 'object') {
function updatePlayerList(players) { name = p.name || p.username || p.player || '';
detailPlayerList.innerHTML = ''; uuid = p.uuid || null;
if (!players || players.length === 0) { prefix = p.prefix || '';
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>'; } else {
return; name = String(p);
} }
for (const p of players) {
const li = document.createElement('li'); const img = document.createElement('img');
const name = typeof p === 'object' ? p.name || p.username || p.player || '' : String(p); img.src = (typeof p === 'object' && p.avatar) ? p.avatar : get3DAvatarUrl(name, uuid);
img.className = 'player-avatar';
if (typeof p === 'object' && p.avatar) { img.loading = 'lazy';
const img = document.createElement('img'); img.onerror = function() {
img.src = p.avatar; this.onerror = null;
img.className = 'player-avatar'; this.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/64`;
img.title = name; };
li.appendChild(img);
} else { // Das Hover-Element mit farbigem Prefix + Name
// Falls kein Avatar vorhanden, generiere einen von mc-heads.net const info = document.createElement('div');
const img = document.createElement('img'); info.className = 'player-hover-info';
img.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/32`; info.innerHTML = `${parseMinecraftColors(prefix)} ${name}`.trim();
img.className = 'player-avatar';
img.title = name; li.appendChild(img);
li.appendChild(img); li.appendChild(info);
} detailPlayerList.appendChild(li);
}
detailPlayerList.appendChild(li); }
}
} function updateServerListStatuses() {
const items = serversContainer.querySelectorAll('.server-item');
// --- Update Server List Statuses --- items.forEach(item => {
function updateServerListStatuses() { const s = servers.find(x => x.id === item.dataset.id);
const items = serversContainer.querySelectorAll('.server-item'); if (!s) return;
items.forEach(item => { const st = statuses[s.id];
const s = servers.find(x => x.id === item.dataset.id); if (!st || !st.ok || !st.data) {
if (!s) return; item.statusBubble.textContent = 'Offline';
item.statusBubble.style.backgroundColor = 'var(--offline)';
const st = statuses[s.id]; } else if (st.data.online) {
const prevSt = previousStatuses[s.id]; item.statusBubble.textContent = 'Online';
item.statusBubble.style.backgroundColor = 'var(--online)';
const statusChanged = !prevSt || } else {
(prevSt.ok !== st?.ok) || item.statusBubble.textContent = 'Offline';
(prevSt.data?.online !== st?.data?.online); item.statusBubble.style.backgroundColor = 'var(--offline)';
}
if (!statusChanged) return; });
}
if (!st || !st.ok || !st.data) {
item.statusBubble.textContent = 'Offline'; async function handleAdd() {
item.statusBubble.style.backgroundColor = 'var(--offline)'; const name = inputName.value.trim();
} else if (st.data.online) { const url = inputUrl.value.trim();
item.statusBubble.textContent = 'Online'; const wpSite = inputWpSite.value.trim();
item.statusBubble.style.backgroundColor = 'var(--online)'; const wpServerId = inputWpServerId.value.trim();
} else { if (!url && !wpSite) return;
item.statusBubble.textContent = 'Offline'; const s = { id: uid(), name: name || url || wpSite, url: url || null, wpSite: wpSite || null, wpServerId: wpServerId || null };
item.statusBubble.style.backgroundColor = 'var(--offline)'; servers.push(s);
} await saveServersToStorage();
inputName.value = inputUrl.value = inputWpSite.value = inputWpServerId.value = '';
previousStatuses[s.id] = st ? JSON.parse(JSON.stringify(st)) : null; renderServerList();
}); }
}
async function handleEdit() {
// --- Add / Edit / Delete --- if (!selectedId) return;
async function handleAdd() { const srv = servers.find(s => s.id === selectedId);
const name = inputName.value.trim(); if (!srv) return;
const url = inputUrl.value.trim(); const newName = prompt('Name:', srv.name) || srv.name;
const wpSite = inputWpSite.value.trim(); srv.name = newName.trim();
const wpServerId = inputWpServerId.value.trim(); await saveServersToStorage();
renderServerList();
if (!url && !wpSite) { renderDetail(selectedId);
alert('Bitte URL oder WP Site angeben'); }
return;
} async function handleDelete() {
if (!selectedId || !confirm('Server wirklich löschen?')) return;
const s = { servers = servers.filter(s => s.id !== selectedId);
id: uid(), selectedId = null;
name: name || url || wpSite, await saveServersToStorage();
url: url || null, renderServerList();
wpSite: wpSite || null, renderDetail(null);
wpServerId: wpServerId || null }
};
async function manualRefresh() {
servers.push(s); try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {}
await saveServersToStorage(); }
inputName.value = ''; chrome.storage.onChanged.addListener((changes, area) => {
inputUrl.value = ''; if (area === 'local' && changes.serverStatuses) {
inputWpSite.value = ''; statuses = changes.serverStatuses.newValue || {};
inputWpServerId.value = ''; updateServerListStatuses();
if (selectedId) {
renderServerList(); const srv = servers.find(s => s.id === selectedId);
} if (srv) updateDetailForServer(srv);
}
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);
}
}
}); });