385 lines
12 KiB
JavaScript
385 lines
12 KiB
JavaScript
// 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 += `<span style="color:${map[code]}">`;
|
|
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 = '<div class="placeholder" style="padding:8px 0;">Noch kein Server hinzugefügt.</div>';
|
|
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 = '<div class="placeholder">Keine Spieler online.</div>';
|
|
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);
|
|
}
|
|
}
|
|
});
|