Upload via Git Manager GUI

This commit is contained in:
Git Manager GUI
2026-05-22 11:16:17 +02:00
parent de04e58242
commit b851cbc20b

View File

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