Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40c0ed7b0c | |||
| f407604ff6 | |||
| 2fce65544b | |||
| 1c89b4fdfe | |||
| 700e6026b6 | |||
| b4ee8b0564 | |||
| 111158967f |
BIN
BungeeCord-Chrome/BungeeCord-Chrome.zip
Normal file
BIN
BungeeCord-Chrome/BungeeCord-Chrome.zip
Normal file
Binary file not shown.
@@ -11,17 +11,46 @@
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html,body { margin:0; padding:0; font-family:Inter, Arial, sans-serif; background:var(--bg); color:var(--text);}
|
||||
.root { width:520px; max-width:520px; padding:12px; }
|
||||
|
||||
header { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Inter, Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.root {
|
||||
width: 520px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
header h1 { font-size: 16px; margin: 0; }
|
||||
header .actions button { background:transparent; border:1px solid var(--muted); color:var(--text); padding:4px 8px; border-radius:6px; cursor:pointer; }
|
||||
|
||||
header .actions button {
|
||||
background: transparent;
|
||||
border: 1px solid var(--muted);
|
||||
color: var(--text);
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.server-list {
|
||||
@@ -50,12 +79,17 @@ header .actions button { background:transparent; border:1px solid var(--muted);
|
||||
.add-form input { padding: 6px 8px; border-radius: 6px; border: 1px solid #21303b; background: #071019; color: var(--text); }
|
||||
.add-form button { padding: 6px 8px; border-radius: 6px; border: none; background: var(--accent); color: white; cursor: pointer; }
|
||||
|
||||
#serversContainer { list-style:none; margin:0; padding:0; max-height:260px; overflow:auto; }
|
||||
#serversContainer {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.server-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding:6px;
|
||||
padding: 8px;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
@@ -64,157 +98,120 @@ header .actions button { background:transparent; border:1px solid var(--muted);
|
||||
}
|
||||
|
||||
.server-item:hover { background: rgba(255,255,255,0.05); }
|
||||
|
||||
.server-item .meta { display: flex; flex-direction: column; }
|
||||
.server-item .meta .name { font-weight: 600; }
|
||||
.server-item .meta .url { font-size:12px; color:var(--muted); }
|
||||
.server-item .meta .url { font-size: 11px; color: var(--muted); }
|
||||
|
||||
.server-item .status-bubble {
|
||||
font-size:12px;
|
||||
padding:3px 6px;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
color: #fff;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.placeholder { color:var(--muted); padding:12px; }
|
||||
.placeholder { color: var(--muted); padding: 12px; font-size: 13px; }
|
||||
.hidden { display: none; }
|
||||
|
||||
/* --- Detail Header (Zeile 1) --- */
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.server-identity h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.server-url {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.server-identity h2 { margin: 0; font-size: 18px; line-height: 1.2; }
|
||||
.server-url { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
|
||||
|
||||
.status-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* --- Pulsierender Punkt Animation --- */
|
||||
.pulsing-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--offline); /* Standard Offline Rot */
|
||||
width: 10px; height: 10px; border-radius: 50%;
|
||||
background-color: var(--offline);
|
||||
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
|
||||
animation: pulse-red 2s infinite;
|
||||
}
|
||||
|
||||
.pulsing-dot.online {
|
||||
background-color: var(--online); /* Online Grün */
|
||||
background-color: var(--online);
|
||||
animation: pulse-green 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-green {
|
||||
0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(34, 197, 94, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
|
||||
}
|
||||
@keyframes pulse-green { 0% {box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);} 70% {box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);} 100% {box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);} }
|
||||
@keyframes pulse-red { 0% {box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);} 70% {box-shadow: 0 0 0 8px rgba(239, 68, 68, 0);} 100% {box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);} }
|
||||
|
||||
@keyframes pulse-red {
|
||||
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
|
||||
}
|
||||
|
||||
/* --- Detail Stats (Zeile 2) --- */
|
||||
.detail-stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: rgba(255,255,255,0.05);
|
||||
padding: 8px;
|
||||
padding: 10px;
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ---------------------------------- */
|
||||
|
||||
.playerList {
|
||||
list-style: none;
|
||||
padding-left:16px;
|
||||
max-height:140px;
|
||||
overflow:auto;
|
||||
margin:6px 0;
|
||||
padding: 10px 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap:8px;
|
||||
gap: 12px;
|
||||
justify-content: center; /* Das hier setzt die Köpfe wieder in die Mitte */
|
||||
}
|
||||
|
||||
.playerList li {
|
||||
.player-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap:4px;
|
||||
font-size:13px;
|
||||
color:var(--text);
|
||||
}
|
||||
|
||||
.player-avatar {
|
||||
width:24px;
|
||||
height:24px;
|
||||
border-radius:4px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.player-avatar:hover { transform: scale(1.1); }
|
||||
|
||||
.detailButtons {
|
||||
display:flex;
|
||||
gap:8px;
|
||||
margin-top:8px;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.detailButtons button {
|
||||
padding:6px 8px;
|
||||
width: 42px; height: 42px;
|
||||
border-radius: 6px;
|
||||
border:none;
|
||||
cursor:pointer;
|
||||
transition: opacity 0.2s ease, transform 0.1s ease;
|
||||
transition: transform 0.2s ease;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
||||
border: 2px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.detailButtons button:hover { opacity: 0.9; transform: translateY(-1px); }
|
||||
.detailButtons button:active { transform: translateY(0); }
|
||||
.player-avatar:hover {
|
||||
transform: scale(1.1);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.player-hover-info {
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
color: white;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
transition: all 0.2s ease;
|
||||
z-index: 100;
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.player-item:hover .player-hover-info {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.detailButtons { display: flex; gap: 8px; margin-top: 15px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 10px; }
|
||||
.detailButtons button { padding: 6px 12px; border-radius: 6px; border: none; cursor: pointer; font-size: 12px; font-weight: 600; }
|
||||
#btnEdit { background: #f59e0b; color: #000; }
|
||||
#btnDelete { background: var(--danger); color: #fff; }
|
||||
|
||||
#serversContainer::-webkit-scrollbar,
|
||||
.playerList::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
#serversContainer::-webkit-scrollbar-track,
|
||||
.playerList::-webkit-scrollbar-track {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#serversContainer::-webkit-scrollbar-thumb,
|
||||
.playerList::-webkit-scrollbar-thumb {
|
||||
background: var(--muted);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#serversContainer::-webkit-scrollbar-thumb:hover,
|
||||
.playerList::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent);
|
||||
}
|
||||
::-webkit-scrollbar { width: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
|
||||
@@ -33,7 +33,6 @@
|
||||
<div id="noSelection" class="placeholder">Wähle einen Server aus der linken Liste.</div>
|
||||
<div id="detailContent" class="detailContent hidden">
|
||||
|
||||
<!-- NEUES LAYOUT ZEILE 1: Name, URL, Status (mit Pulsierendem Punkt) -->
|
||||
<div class="detail-header">
|
||||
<div class="server-identity">
|
||||
<h2 id="detailName"></h2>
|
||||
@@ -45,7 +44,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NEUES LAYOUT ZEILE 2: Spieler, Version, Ping -->
|
||||
<div class="detail-stats">
|
||||
<div><strong>Spieler:</strong> <span id="detailPlayers"></span></div>
|
||||
<div><strong>Version:</strong> <span id="detailVersion"></span></div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//popup.js
|
||||
const $ = id => document.getElementById(id);
|
||||
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
|
||||
|
||||
@@ -50,7 +51,37 @@ async function init() {
|
||||
adjustDetailLayout();
|
||||
}
|
||||
|
||||
// --- Settings Visibility ---
|
||||
// --- 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, `</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`;
|
||||
}
|
||||
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;
|
||||
@@ -69,7 +100,6 @@ async function toggleSettings() {
|
||||
adjustDetailLayout();
|
||||
}
|
||||
|
||||
// --- Storage ---
|
||||
async function loadServersFromStorage() {
|
||||
const obj = await chrome.storage.local.get(['servers']);
|
||||
servers = obj.servers || [];
|
||||
@@ -86,7 +116,6 @@ async function loadStatusesFromStorage() {
|
||||
statuses = obj.serverStatuses || {};
|
||||
}
|
||||
|
||||
// --- Layout ---
|
||||
function adjustDetailLayout() {
|
||||
const settingsHidden = settingsForm.classList.contains('hidden');
|
||||
detailContent.classList.toggle('full-width', settingsHidden);
|
||||
@@ -95,51 +124,44 @@ function adjustDetailLayout() {
|
||||
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
|
||||
}
|
||||
|
||||
// --- Render Server List ---
|
||||
function renderServerList() {
|
||||
serversContainer.innerHTML = '';
|
||||
if (servers.length === 0) {
|
||||
serversContainer.innerHTML = '<div class="placeholder">Noch keine 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 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);
|
||||
updateServerListStatuses();
|
||||
}
|
||||
|
||||
// --- Render Detail ---
|
||||
// --- Fortsetzung popup.js ---
|
||||
|
||||
function renderDetail(id) {
|
||||
if (!id) {
|
||||
noSelection.classList.remove('hidden');
|
||||
@@ -154,26 +176,17 @@ function renderDetail(id) {
|
||||
}
|
||||
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);
|
||||
const statusChanged = force || !prevSt || (prevSt.ok !== st?.ok) || (prevSt.data?.online !== st?.data?.online);
|
||||
|
||||
if (!st || !st.ok || !st.data) {
|
||||
if (statusChanged) {
|
||||
@@ -186,105 +199,80 @@ function updateDetailForServer(srv, force = false) {
|
||||
}
|
||||
} 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');
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
let playersChanged = force || JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers);
|
||||
if (playersChanged) updatePlayerList(currentPlayers);
|
||||
}
|
||||
|
||||
if (playersChanged) {
|
||||
updatePlayerList(currentPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
|
||||
}
|
||||
|
||||
// --- Spielerliste ---
|
||||
// --- Spielerliste mit Prefix-Hover und Farbsupport ---
|
||||
function updatePlayerList(players) {
|
||||
detailPlayerList.innerHTML = '';
|
||||
if (!players || players.length === 0) {
|
||||
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const p of players) {
|
||||
const li = document.createElement('li');
|
||||
const name = typeof p === 'object' ? p.name || p.username || p.player || '' : String(p);
|
||||
li.className = 'player-item'; // CSS Klasse für den Hover-Container
|
||||
|
||||
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);
|
||||
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 {
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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)';
|
||||
@@ -295,39 +283,19 @@ function updateServerListStatuses() {
|
||||
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
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -335,43 +303,26 @@ 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;
|
||||
|
||||
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) {
|
||||
console.error('Refresh fehlgeschlagen:', e);
|
||||
}
|
||||
try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {}
|
||||
}
|
||||
|
||||
// --- Storage Listener ---
|
||||
chrome.storage.onChanged.addListener((changes, area) => {
|
||||
if (area === 'local' && changes.serverStatuses) {
|
||||
statuses = changes.serverStatuses.newValue || {};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: Minecraft BungeeCord Status – Network Edition
|
||||
* Description: Der ultimative Live-Status für dein BungeeCord Netzwerk (Border None Fix).
|
||||
* Tags: minecraft, bungeecord, server status, player list
|
||||
* Version: 3.6.3
|
||||
* Version: 3.6.4
|
||||
* Author: M_Viper
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 7.4
|
||||
@@ -898,3 +898,184 @@ function mcss_shortcode($atts) {
|
||||
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
|
||||
/* ---------------- SIDEBAR WIDGET: MODERN PILL BADGE (ROBUST INLINE STYLES) ---------------- */
|
||||
class MCSS_Sidebar_Status_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'mcss_sidebar_status', // Basis ID
|
||||
'MC Server Status (Simple)', // Name
|
||||
array( 'description' => 'Zeigt einen modernen Online/Offline Status in der Sidebar an.' ) // Args
|
||||
);
|
||||
}
|
||||
|
||||
// Backend: Formular im Widget-Bereich
|
||||
public function form( $instance ) {
|
||||
$title = ! empty( $instance['title'] ) ? $instance['title'] : 'Server Status';
|
||||
$server_id = ! empty( $instance['server_id'] ) ? $instance['server_id'] : '';
|
||||
|
||||
// Server-Liste holen
|
||||
$servers = get_option('mcss_servers', []);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id( 'title' ); ?>">Titel:</label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id( 'server_id' ); ?>">Server wählen:</label>
|
||||
<select class="widefat" id="<?php echo $this->get_field_id( 'server_id' ); ?>" name="<?php echo $this->get_field_name( 'server_id' ); ?>">
|
||||
<option value="">-- Bitte wählen --</option>
|
||||
<?php foreach ($servers as $srv): ?>
|
||||
<option value="<?php echo esc_attr($srv['id'] ?? ''); ?>" <?php selected($server_id, $srv['id'] ?? ''); ?>>
|
||||
<?php echo esc_html($srv['name'] ?? 'Unbenannt'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Backend: Speichern
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
$instance = array();
|
||||
$instance['title'] = ( ! empty( $new_instance['title'] ) ) ? sanitize_text_field( $new_instance['title'] ) : '';
|
||||
$instance['server_id'] = ( ! empty( $new_instance['server_id'] ) ) ? sanitize_text_field( $new_instance['server_id'] ) : '';
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// Frontend: Ausgabe
|
||||
public function widget( $args, $instance ) {
|
||||
echo $args['before_widget'];
|
||||
|
||||
if ( ! empty( $instance['title'] ) ) {
|
||||
echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
|
||||
}
|
||||
|
||||
$server_id = $instance['server_id'];
|
||||
$servers = get_option('mcss_servers', []);
|
||||
$target_srv = null;
|
||||
|
||||
// Server suchen
|
||||
foreach ($servers as $srv) {
|
||||
if (($srv['id'] ?? '') === $server_id) { $target_srv = $srv; break; }
|
||||
}
|
||||
|
||||
if ($target_srv) {
|
||||
// Initiale Daten holen
|
||||
$data = mcss_fetch_server_with_ranks($target_srv);
|
||||
$is_online = $data['online'] ?? false;
|
||||
$uid = md5($target_srv['host'] . 'widget'); // Einzigartige ID für JS
|
||||
|
||||
// Farben definieren
|
||||
$bg_online = '#ecfdf5';
|
||||
$bg_offline = '#fef2f2';
|
||||
$text_online = '#059669';
|
||||
$text_offline = '#dc2626';
|
||||
$dot_online = '#10b981';
|
||||
$dot_offline = '#ef4444';
|
||||
$border_online = 'rgba(16, 185, 129, 0.2)';
|
||||
$border_offline = 'rgba(239, 68, 68, 0.2)';
|
||||
|
||||
$anim_name_online = 'mcss-pulse-modern';
|
||||
$anim_name_offline = 'mcss-pulse-modern-red';
|
||||
|
||||
// Werte für den Initialzustand setzen
|
||||
$current_bg = $is_online ? $bg_online : $bg_offline;
|
||||
$current_text = $is_online ? $text_online : $text_offline;
|
||||
$current_dot = $is_online ? $dot_online : $dot_offline;
|
||||
$current_border = $is_online ? $border_online : $border_offline;
|
||||
$current_anim = $is_online ? $anim_name_online : $anim_name_offline;
|
||||
$status_label = $is_online ? 'Online' : 'Offline';
|
||||
|
||||
// Inline Animation Definitionen für Robustheit
|
||||
?>
|
||||
<style>
|
||||
@keyframes mcss-pulse-modern {
|
||||
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
|
||||
70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
|
||||
}
|
||||
@keyframes mcss-pulse-modern-red {
|
||||
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
|
||||
70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Wrapper mit PUREN Inline Styles -->
|
||||
<div id="mcss-sw-wrapper-<?php echo esc_attr($uid); ?>"
|
||||
style="display: flex; align-items: center; justify-content: center; width: 93%; padding: 8px; border-radius: 50px; background-color: <?php echo $current_bg; ?>; border: 1px solid <?php echo $current_border; ?>; transition: all 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; cursor: default; margin: 10px 0;">
|
||||
|
||||
<span id="mcss-sw-dot-<?php echo esc_attr($uid); ?>"
|
||||
style="width: 8px; height: 8px; border-radius: 50%; margin-right: 10px; background-color: <?php echo $current_dot; ?>; animation: <?php echo $current_anim; ?> 2s infinite;"></span>
|
||||
|
||||
<span id="mcss-sw-text-<?php echo esc_attr($uid); ?>"
|
||||
style="font-size: 12px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: <?php echo $current_text; ?>;">
|
||||
<?php echo $status_label; ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Kleines Skript für Live-Update (alle 3 Sekunden) -->
|
||||
<script>
|
||||
(function(){
|
||||
var uid = "<?php echo esc_js($uid); ?>";
|
||||
var serverId = "<?php echo esc_js($target_srv['id']); ?>";
|
||||
|
||||
// Konstanten Farben
|
||||
var cOnlineBg = '<?php echo esc_js($bg_online); ?>';
|
||||
var cOfflineBg = '<?php echo esc_js($bg_offline); ?>';
|
||||
var cOnlineText = '<?php echo esc_js($text_online); ?>';
|
||||
var cOfflineText = '<?php echo esc_js($text_offline); ?>';
|
||||
var cOnlineDot = '<?php echo esc_js($dot_online); ?>';
|
||||
var cOfflineDot = '<?php echo esc_js($dot_offline); ?>';
|
||||
var bOnline = '<?php echo esc_js($border_online); ?>';
|
||||
var bOffline = '<?php echo esc_js($border_offline); ?>';
|
||||
var aOnline = '<?php echo esc_js($anim_name_online); ?>';
|
||||
var aOffline = '<?php echo esc_js($anim_name_offline); ?>';
|
||||
|
||||
setInterval(function(){
|
||||
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/x-www-form-urlencoded'},
|
||||
body: 'action=mcss_fetch&server_id=' + serverId
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
var wrapper = document.getElementById('mcss-sw-wrapper-' + uid);
|
||||
var dot = document.getElementById('mcss-sw-dot-' + uid);
|
||||
var txt = document.getElementById('mcss-sw-text-' + uid);
|
||||
if(wrapper && dot && txt && d) {
|
||||
if(d.online) {
|
||||
wrapper.style.backgroundColor = cOnlineBg;
|
||||
wrapper.style.borderColor = bOnline;
|
||||
dot.style.backgroundColor = cOnlineDot;
|
||||
dot.style.animationName = aOnline;
|
||||
txt.style.color = cOnlineText;
|
||||
txt.textContent = 'Online';
|
||||
} else {
|
||||
wrapper.style.backgroundColor = cOfflineBg;
|
||||
wrapper.style.borderColor = bOffline;
|
||||
dot.style.backgroundColor = cOfflineDot;
|
||||
dot.style.animationName = aOffline;
|
||||
txt.style.color = cOfflineText;
|
||||
txt.textContent = 'Offline';
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
} else {
|
||||
echo '<p>Bitte Server in den Einstellungen wählen.</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
}
|
||||
|
||||
// Widget registrieren
|
||||
add_action( 'widgets_init', function(){
|
||||
register_widget( 'MCSS_Sidebar_Status_Widget' );
|
||||
});
|
||||
Reference in New Issue
Block a user