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,31 +1,7 @@
//popup.js
// 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 = {};
@@ -33,71 +9,76 @@ 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);
$('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);
adjustDetailLayout();
}
// --- NEU: Minecraft Color Parser (Eingebaut ohne Löschung) ---
// ── 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'
};
let html = text.replace(/[<>]/g, '');
Object.keys(map).forEach(code => {
const color = map[code];
const regex = new RegExp(code, 'g');
html = html.replace(regex, `</span><span style="color: ${color}">`);
});
return `<span>${html}</span>`.replace(/&l/g, '<b>').replace(/&r/g, '</b>');
}
// --- 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`;
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;
}
}
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
} else {
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
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 !== undefined ? obj.settingsVisible : false;
if (!settingsVisible) settingsForm.classList.add('hidden');
else settingsForm.classList.remove('hidden');
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;
settingsForm.classList.toggle('hidden');
applySettingsVisibility();
await saveSettingsVisibility();
adjustDetailLayout();
}
async function loadServersFromStorage() {
@@ -106,143 +87,193 @@ async function loadServersFromStorage() {
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';
}
// ── Server list ──
function renderServerList() {
serversContainer.innerHTML = '';
const container = $('serversContainer');
container.innerHTML = '';
if (servers.length === 0) {
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
container.innerHTML = '<div class="placeholder" style="padding:8px 0;">Noch kein Server hinzugefügt.</div>';
return;
}
for (const s of servers) {
const item = document.createElement('li');
item.className = 'server-item';
item.dataset.id = s.id;
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 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', () => {
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);
});
serversContainer.appendChild(item);
container.appendChild(li);
}
updateServerListStatuses();
}
// --- Fortsetzung popup.js ---
// ── Detail ──
function renderDetail(id) {
if (!id) {
noSelection.classList.remove('hidden');
detailContent.classList.add('hidden');
$('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');
$('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');
$('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;
$('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);
const changed = force || JSON.stringify(st) !== JSON.stringify(prevSt);
if (!changed) return;
const badge = $('statusBadge');
const pulse = $('detailPulse');
if (!st || !st.ok || !st.data) {
if (statusChanged) {
detailStatus.textContent = 'Offline';
detailPulse.classList.remove('online');
detailVersion.textContent = '-';
detailPlayers.textContent = '-';
detailPing.textContent = '-';
updatePlayerList([]);
}
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;
if (statusChanged) {
detailStatus.textContent = d.online ? 'Online' : 'Offline';
if (d.online) detailPulse.classList.add('online');
else detailPulse.classList.remove('online');
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 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);
updatePlayerList(currentPlayers);
updateBackendServers(d.network?.backend_servers || null);
}
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
}
// --- Spielerliste mit Prefix-Hover und Farbsupport ---
// ── 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) {
detailPlayerList.innerHTML = '';
const grid = $('detailPlayerList');
grid.innerHTML = '';
if (!players || players.length === 0) {
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>';
grid.innerHTML = '<div class="placeholder">Keine Spieler online.</div>';
return;
}
for (const p of players) {
const li = document.createElement('li');
li.className = 'player-item'; // CSS Klasse für den Hover-Container
const card = document.createElement('div');
card.className = 'player-card';
let name = '';
let uuid = null;
let prefix = '';
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 || '';
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);
}
@@ -256,46 +287,63 @@ function updatePlayerList(players) {
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();
info.className = 'player-info';
li.appendChild(img);
li.appendChild(info);
detailPlayerList.appendChild(li);
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');
const items = $('serversContainer').querySelectorAll('.server-item');
items.forEach(item => {
const s = servers.find(x => x.id === item.dataset.id);
if (!s) return;
if (!s || !item._bubble) 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)';
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.statusBubble.textContent = 'Offline';
item.statusBubble.style.backgroundColor = 'var(--offline)';
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();
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 = '';
$('inputName').value = $('inputUrl').value = $('inputWpSite').value = $('inputWpServerId').value = '';
renderServerList();
}
@@ -303,8 +351,8 @@ 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();
const newName = prompt('Name:', srv.name);
if (newName !== null) srv.name = newName.trim();
await saveServersToStorage();
renderServerList();
renderDetail(selectedId);
@@ -323,6 +371,7 @@ 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 || {};