173 lines
10 KiB
Text
Executable file
173 lines
10 KiB
Text
Executable file
<div class='page-title-container'>
|
|
<h2 class='world-detail-title'>%%WORLD_DISPLAY_NAME_PAGE%%</h2>
|
|
</div>
|
|
<div class="close-button-container">
|
|
<a href="worlds.html" class="close-button" title="Zurück zur Weltenübersicht"><b>X</b></a>
|
|
</div>
|
|
<div class="responsive-nav">
|
|
<button id="burger-menu-toggle" class="burger-menu" aria-label="Menü öffnen/schließen"><span></span><span></span><span></span></button>
|
|
<div class='page-nav-buttons' id="page-nav-buttons-container-%%current_world_key%%">
|
|
<a href='#server-info' class='button'>Server-Info</a>
|
|
<a href='#server-verbindung' class='button'>Verbindung</a>
|
|
<a href='#welt-admin' class='button'>Admin</a>
|
|
<a href='#beschreibung' class='button'>Beschreibung</a>
|
|
<a href='#spielregeln' class='button'>Spielregeln</a>
|
|
<a href='#weltradar' class='button'>Radar</a>
|
|
<a href='#mods' class='button'>Mods</a>
|
|
<a href='#spielerliste' class='button'>Spielerliste</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div id='server-info' class='info-box server-details'>
|
|
<h3>Server-Info</h3>
|
|
<p><strong>Status:</strong> <span class='status-dot' id='status-dot-%%current_world_key%%'></span><span id='world-status-%%current_world_key%%' class='status-text status-loading'>%%STATUS_TEXT_FALLBACK_PAGE%%</span></p>
|
|
<p><strong>Spiel:</strong> <a href='https://content.luanti.org/packages/?type=game&q=%%MT_GAMEID%%' target='_blank' rel='noopener noreferrer'>%%MT_GAMEID%%</a></p>
|
|
<p><strong>Kreativmodus:</strong> <span class='status-text-colored %%creative_text_class%%'>%%creative_text%%</span></p>
|
|
<p><strong>Schaden:</strong> <span class='status-text-colored %%damage_text_class%%'>%%damage_text%%</span></p>
|
|
<div id="weather-info-%%current_world_key%%"></div>
|
|
</div>
|
|
|
|
<div id='server-verbindung' class='info-box server-details'>
|
|
<h3>Server-Verbindung</h3>
|
|
<p><strong>Adresse:</strong> <span id='addr-%%current_world_key%%'>%%SERVER_ADDRESS_PAGE%%</span><button title='Adresse kopieren' class='copy-button' onclick='copyToClipboard("addr-%%current_world_key%%")'>📋</button></p>
|
|
<p><strong>Port:</strong> <span id='port-%%current_world_key%%'>%%SERVER_PORT_PAGE%%</span><button title='Port kopieren' class='copy-button' onclick='copyToClipboard("port-%%current_world_key%%")'>📋</button></p>
|
|
<p><strong>Passwort:</strong> <span id='pass-%%current_world_key%%'>%%SERVER_ACCESS_INFO_PAGE%%</span><button title='Info kopieren' class='copy-button' onclick='copyToClipboard("pass-%%current_world_key%%")'>📋</button></p>
|
|
</div>
|
|
|
|
<div id='welt-admin' class='admin-section'>
|
|
<h3>Welt-Admin</h3>
|
|
<div class='admin-grid'>%%ADMIN_BOXES_HTML%%</div>
|
|
</div>
|
|
|
|
<div id="description-text-wrapper">
|
|
<h3 id='beschreibung'>Beschreibung</h3>
|
|
<div id="description-text" class="collapsible-text">%%WORLD_LONG_DESCRIPTION_PAGE%%</div>
|
|
<div class="read-more-container"><a href="#" class="read-more-link" data-target="description-text">weiterlesen</a></div>
|
|
</div>
|
|
|
|
<div id="gamerules-section">
|
|
<h3 id='spielregeln'>Spielregeln</h3>
|
|
<div id="gamerules-text-wrapper">
|
|
<div id="gamerules-text" class="collapsible-text">%%WORLD_GAME_RULES_PAGE%%</div>
|
|
<div class="read-more-container"><a href="#" class="read-more-link" data-target="gamerules-text">weiterlesen</a></div>
|
|
</div>
|
|
</div>
|
|
|
|
%%WORLD_RADAR_HTML%%
|
|
|
|
<h3 id='mods'>Verwendete Mods</h3>
|
|
<div class='scrollable-mod-list'>%%MODS_HTML%%</div>
|
|
|
|
%%WORLD_PLAYERLIST_HTML%%
|
|
|
|
<script>
|
|
// === Globale Hilfsfunktionen & Datenabruf ===
|
|
function copyToClipboard(elementId) { const el = document.getElementById(elementId); if(el) { navigator.clipboard.writeText(el.innerText || el.textContent).then(() => { const btn = el.nextElementSibling; if(btn && btn.classList.contains('copy-button')) { const orig_btn_text = btn.innerHTML; btn.innerHTML = '✓'; setTimeout(() => { btn.innerHTML = orig_btn_text; }, 1500); } }).catch(err => console.error('Fehler Kopieren: ', err));}}
|
|
function formatTimestampForDisplay(epochSeconds) { if (!epochSeconds || epochSeconds == 0) return 'unbekannt'; const date = new Date(epochSeconds * 1000); return date.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit'}) + ' Uhr';}
|
|
|
|
function fetchWorldStatus_%%current_world_key%%() {
|
|
const statusTextEl = document.getElementById('world-status-%%current_world_key%%');
|
|
const statusDotEl = document.getElementById('status-dot-%%current_world_key%%');
|
|
if (!statusTextEl || !statusDotEl) return;
|
|
const onlineStatusUrl = '/%%web_online_status_rel_path%%?v=%%CACHE_BUSTER%%&t=' + new Date().getTime();
|
|
const lastUpdateUrl = '/%%web_last_update_rel_path%%?v=%%CACHE_BUSTER%%&t=' + new Date().getTime();
|
|
Promise.all([fetch(onlineStatusUrl).then(res => res.text()), fetch(lastUpdateUrl).then(res => res.text())])
|
|
.then(([onlineStatusContent, lastUpdateContent]) => {
|
|
const dateObject = new Date(lastUpdateContent.trim());
|
|
const lastUpdateEpoch = !isNaN(dateObject.getTime()) ? Math.floor(dateObject.getTime() / 1000) : NaN;
|
|
const nowInSeconds = Math.floor(Date.now() / 1000);
|
|
if (!isNaN(lastUpdateEpoch) && (nowInSeconds - lastUpdateEpoch > 900)) {
|
|
statusDotEl.className = 'status-dot unknown';
|
|
statusTextEl.className = 'status-text status-unknown';
|
|
statusTextEl.textContent = 'Unbekannt (seit: ' + formatTimestampForDisplay(lastUpdateEpoch) + ')';
|
|
return;
|
|
}
|
|
const onlineStatusParts = onlineStatusContent.split(' - ');
|
|
const currentStatus = onlineStatusParts[0].trim();
|
|
if (currentStatus === 'online') {
|
|
statusDotEl.className = 'status-dot online';
|
|
statusTextEl.className = 'status-text status-online';
|
|
statusTextEl.textContent = 'Online';
|
|
} else {
|
|
const offlineSince = onlineStatusParts.length > 1 ? onlineStatusParts.slice(1).join(' - ').trim() : 'unbekannt';
|
|
statusDotEl.className = 'status-dot offline';
|
|
statusTextEl.className = 'status-text status-offline';
|
|
statusTextEl.textContent = 'Offline (seit: ' + offlineSince + ')';
|
|
}
|
|
}).catch(error => {
|
|
statusDotEl.className = 'status-dot offline';
|
|
statusTextEl.className = 'status-text status-offline';
|
|
statusTextEl.textContent = 'Status nicht abrufbar';
|
|
});
|
|
}
|
|
|
|
function fetchDataForElement(elementId, filePath, isRawText, prefixText, suffixText, isJsonPlayerList) {
|
|
const el = document.getElementById(elementId);
|
|
if (!el && !isJsonPlayerList) return;
|
|
fetch('/' + filePath + '?v=%%CACHE_BUSTER%%&t=' + new Date().getTime())
|
|
.then(r => { if (!r.ok) throw new Error('Datei ' + filePath + ' nicht erreichbar (' + r.status + ')'); return r.text(); })
|
|
.then(t => {
|
|
let content = "";
|
|
if (t.trim() === "") {
|
|
content = (elementId.startsWith("map-last-update") ? "<em>unbekannt</em>" : "<em>Keine Daten verfügbar.</em>");
|
|
if (isJsonPlayerList && window.playerListLogic_%%current_world_key%%) {
|
|
window.playerListLogic_%%current_world_key%%.masterPlayerData = {};
|
|
window.playerListLogic_%%current_world_key%%.updatePlayerMarkers({});
|
|
window.playerListLogic_%%current_world_key%%.applyPlayerFiltersAndRender();
|
|
}
|
|
} else {
|
|
if (isJsonPlayerList && window.playerListLogic_%%current_world_key%%) {
|
|
try {
|
|
const playerData = JSON.parse(t);
|
|
const logic = window.playerListLogic_%%current_world_key%%;
|
|
logic.masterPlayerData = playerData;
|
|
logic.updatePlayerMarkers(playerData);
|
|
logic.applyPlayerFiltersAndRender();
|
|
} catch (e) { console.error("Fehler beim Parsen der Spielerdaten:", e); }
|
|
return;
|
|
} else { content = isRawText ? t.replace(/\n/g, '<br>') : t; }
|
|
}
|
|
if(el) { el.innerHTML = (prefixText || '') + content + (suffixText || ''); }
|
|
}).catch(e => {
|
|
if(el) { el.innerHTML = (prefixText || '') + '<em>nicht abrufbar</em>' + (suffixText || ''); }
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Burger-Menü Logik
|
|
const burgerToggle = document.getElementById('burger-menu-toggle');
|
|
const navContainer = document.getElementById('page-nav-buttons-container-%%current_world_key%%');
|
|
if (burgerToggle && navContainer) {
|
|
burgerToggle.addEventListener('click', () => navContainer.classList.toggle('menu-open'));
|
|
navContainer.addEventListener('click', e => { if (e.target.classList.contains('button')) { navContainer.classList.remove('menu-open'); }});
|
|
}
|
|
|
|
// "Weiterlesen"-Logik
|
|
document.querySelectorAll('.read-more-link').forEach(link => {
|
|
const targetElement = document.getElementById(link.dataset.target);
|
|
if (targetElement) {
|
|
if (targetElement.scrollHeight <= targetElement.clientHeight) {
|
|
link.parentElement.style.display = 'none';
|
|
} else {
|
|
link.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
targetElement.classList.add('expanded');
|
|
link.parentElement.style.display = 'none';
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initiale Daten-Ladeaufrufe
|
|
fetchWorldStatus_%%current_world_key%%();
|
|
fetchDataForElement('player-info-%%current_world_key%%', '%%web_players_txt_rel_path%%', false, '', '', true);
|
|
fetchDataForElement('weather-info-%%current_world_key%%', '%%web_weather_txt_rel_path%%', true, '<p><strong>Wetter:</strong> ', '</p>');
|
|
fetchDataForElement('map-last-update-text-%%current_world_key%%', '%%web_last_update_rel_path%%', true, '', '');
|
|
});
|
|
|
|
// Periodische Updates
|
|
setInterval(fetchWorldStatus_%%current_world_key%%, 60000);
|
|
setInterval(() => fetchDataForElement('map-last-update-text-%%current_world_key%%', '%%web_last_update_rel_path%%', true, '', ''), 60000);
|
|
setInterval(() => fetchDataForElement('player-info-%%current_world_key%%', '%%web_players_txt_rel_path%%', false, '', '', true), 70000);
|
|
setInterval(() => fetchDataForElement('weather-info-%%current_world_key%%', '%%web_weather_txt_rel_path%%', true, '<p><strong>Wetter:</strong> ', '</p>'), 300000);
|
|
</script>
|