This commit is contained in:
2026-02-03 09:18:15 +01:00
parent 13efe9406c
commit dc427490a5
42 changed files with 5104 additions and 65 deletions

View File

@@ -119,28 +119,31 @@
});
});
const authView = document.getElementById("authView");
const gameView = document.getElementById("gameView");
let stateTimer = null;
const authPanels = {
login: document.getElementById("authLogin"),
"register-step1": document.getElementById("authRegisterStep1"),
"register-step2": document.getElementById("authRegisterStep2"),
"register-step3": document.getElementById("authRegisterStep3")
};
let racesCache = null;
let avatarsCache = null;
const regDraft = { race_key: null, avatar_key: null };
function formatNumber(val){
const num = Number.isFinite(val) ? val : 0;
return Math.round(num).toLocaleString("de-DE");
}
const resourceMap = {
"Metall": "metal",
"Kristall": "crystals",
"Deuterium": "deuterium",
"Energie": "energy"
};
function updateResourceBar(state){
const stats = document.querySelectorAll(".resource-row .stat");
stats.forEach((stat)=>{
const label = stat.querySelector(".stat-k")?.textContent?.trim();
const key = resourceMap[label];
const values = state?.resources || {};
document.querySelectorAll("[data-resource-value]").forEach((valueEl)=>{
const key = valueEl.dataset.resourceValue;
if(!key) return;
const value = state?.resources?.[key];
const value = values[key];
if(typeof value !== "number") return;
const valueEl = stat.querySelector(".stat-v");
if(!valueEl) return;
const dot = valueEl.querySelector(".dot");
const display = key === "energy" ? Math.round(value) : Math.floor(value);
const prefix = key === "energy" && display >= 0 ? "+" : "";
@@ -150,19 +153,189 @@
});
}
function updateQueuePanel(state){
const slotsEl = document.getElementById("queueSlots");
if(slotsEl){
const slots = Number.isFinite(state?.queue_slots) ? state.queue_slots : null;
slotsEl.textContent = slots === null ? "" : String(slots);
}
const list = document.getElementById("queueList");
if(!list) return;
list.textContent = "";
const jobs = Array.isArray(state?.active_build_jobs) ? state.active_build_jobs : [];
if(jobs.length === 0){
const li = document.createElement("li");
li.className = "muted";
li.textContent = "Keine aktiven Baujobs.";
list.appendChild(li);
return;
}
jobs.forEach((job)=>{
const li = document.createElement("li");
const slotIndex = Number.isFinite(Number(job?.slot_index)) ? Number(job.slot_index) + 1 : null;
const slotLabel = slotIndex ? `Slot ${slotIndex}: ` : "";
const key = job?.building_key ? String(job.building_key) : "Unbekannt";
const finish = job?.finish_at ? ` · fertig ${job.finish_at}` : "";
li.textContent = `${slotLabel}${key}${finish}`;
list.appendChild(li);
});
}
function showAuthView(){
if(authView) authView.removeAttribute("hidden");
if(gameView) gameView.setAttribute("hidden", "");
}
function showGameView(){
if(authView) authView.setAttribute("hidden", "");
if(gameView) gameView.removeAttribute("hidden");
}
function showAuthPanel(key){
Object.values(authPanels).forEach((panel)=>{
if(panel) panel.setAttribute("hidden", "");
});
const panel = authPanels[key];
if(panel) panel.removeAttribute("hidden");
}
function setMessage(el, message){
if(!el) return;
el.textContent = message || "";
}
async function fetchJson(url, options){
try{
const res = await fetch(url, options);
const data = await res.json().catch(()=> ({}));
return { ok: res.ok, status: res.status, data };
}catch(e){
return { ok: false, status: 0, data: {} };
}
}
async function loadRaces(){
if(racesCache) return racesCache;
const result = await fetchJson("/api/meta/races");
if(result.ok){
racesCache = result.data.races || [];
}else{
racesCache = [];
}
return racesCache;
}
async function loadAvatars(){
if(avatarsCache) return avatarsCache;
const result = await fetchJson("/api/meta/avatars");
if(result.ok){
avatarsCache = result.data.avatars || [];
}else{
avatarsCache = [];
}
return avatarsCache;
}
function renderRaces(races){
const list = document.getElementById("raceList");
if(!list) return;
list.textContent = "";
if(!Array.isArray(races) || races.length === 0){
const empty = document.createElement("div");
empty.className = "muted";
empty.textContent = "Keine Rassen verfügbar.";
list.appendChild(empty);
return;
}
races.forEach((race)=>{
const btn = document.createElement("button");
btn.type = "button";
btn.className = "auth-choice";
btn.dataset.raceKey = race.key;
if(regDraft.race_key === race.key) btn.classList.add("is-selected");
const summary = Array.isArray(race.modifier_summary) ? race.modifier_summary : [];
btn.innerHTML = `
<div class="auth-choice-title">${escapeHtml(race.name || race.key)}</div>
<div class="auth-choice-meta">${escapeHtml(race.description || "")}</div>
${summary.length ? `<div class="auth-choice-meta">${summary.map(s=>escapeHtml(s)).join("<br>")}</div>` : ""}
`;
btn.addEventListener("click", ()=>{
regDraft.race_key = race.key;
list.querySelectorAll(".auth-choice").forEach(el=>el.classList.remove("is-selected"));
btn.classList.add("is-selected");
});
list.appendChild(btn);
});
}
function renderAvatars(avatars){
const list = document.getElementById("avatarList");
if(!list) return;
list.textContent = "";
if(!Array.isArray(avatars) || avatars.length === 0){
const empty = document.createElement("div");
empty.className = "muted";
empty.textContent = "Keine Avatare verfügbar.";
list.appendChild(empty);
return;
}
avatars.forEach((avatar)=>{
const btn = document.createElement("button");
btn.type = "button";
btn.className = "auth-choice";
btn.dataset.avatarKey = avatar.key;
if(regDraft.avatar_key === avatar.key) btn.classList.add("is-selected");
btn.innerHTML = `
<img class="auth-avatar" src="${escapeHtml(avatar.image || "")}" alt="${escapeHtml(avatar.label || avatar.key)}">
<div class="auth-choice-title">${escapeHtml(avatar.label || avatar.key)}</div>
`;
btn.addEventListener("click", ()=>{
regDraft.avatar_key = avatar.key;
list.querySelectorAll(".auth-choice").forEach(el=>el.classList.remove("is-selected"));
btn.classList.add("is-selected");
});
list.appendChild(btn);
});
}
function startPolling(){
if(stateTimer) return;
stateTimer = setInterval(refreshState, 15000);
}
function stopPolling(){
if(!stateTimer) return;
clearInterval(stateTimer);
stateTimer = null;
}
async function fetchState(){
try{
const res = await fetch("/api/state");
if(!res.ok) return null;
return await res.json();
const res = await fetch("/api/state", { credentials: "same-origin" });
if(res.status === 401) return { status: 401 };
if(!res.ok) return { status: res.status };
const data = await res.json();
return { status: 200, data };
}catch(e){
return null;
return { status: 0 };
}
}
async function refreshState(){
const state = await fetchState();
if(state) updateResourceBar(state);
const result = await fetchState();
if(result.status === 200){
showGameView();
updateResourceBar(result.data);
updateQueuePanel(result.data);
ensureBuildButton();
startPolling();
return;
}
if(result.status === 401){
showAuthView();
showAuthPanel("login");
stopPolling();
}
}
function ensureBuildButton(){
@@ -198,8 +371,130 @@
}
document.addEventListener("DOMContentLoaded", ()=>{
document.querySelectorAll("[data-auth-switch]").forEach((btn)=>{
btn.addEventListener("click", async ()=>{
const target = btn.dataset.authSwitch;
if(!target) return;
showAuthView();
showAuthPanel(target);
if(target === "register-step1"){
const races = await loadRaces();
renderRaces(races);
}
if(target === "register-step2"){
const avatars = await loadAvatars();
renderAvatars(avatars);
}
});
});
const loginForm = document.getElementById("loginForm");
if(loginForm){
loginForm.addEventListener("submit", async (e)=>{
e.preventDefault();
const identifier = document.getElementById("loginIdentifier")?.value?.trim() || "";
const password = document.getElementById("loginPassword")?.value || "";
const message = document.getElementById("loginMessage");
setMessage(message, "");
const result = await fetchJson("/api/auth/login", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ username_or_email: identifier, password })
});
if(result.ok){
showGameView();
refreshState();
}else{
setMessage(message, result.data?.message || "Login fehlgeschlagen.");
}
});
}
const raceNext = document.getElementById("raceNext");
if(raceNext){
raceNext.addEventListener("click", async ()=>{
const message = document.getElementById("raceMessage");
setMessage(message, "");
if(!regDraft.race_key){
setMessage(message, "Bitte eine Rasse wählen.");
return;
}
const result = await fetchJson("/api/auth/register/step1", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ race_key: regDraft.race_key })
});
if(result.ok){
showAuthPanel("register-step2");
const avatars = await loadAvatars();
renderAvatars(avatars);
}else{
setMessage(message, result.data?.message || "Schritt 1 fehlgeschlagen.");
}
});
}
const avatarNext = document.getElementById("avatarNext");
if(avatarNext){
avatarNext.addEventListener("click", async ()=>{
const title = document.getElementById("regTitle")?.value?.trim() || "";
const message = document.getElementById("avatarMessage");
setMessage(message, "");
if(!regDraft.avatar_key){
setMessage(message, "Bitte einen Avatar wählen.");
return;
}
if(title.length < 2){
setMessage(message, "Titel ist zu kurz.");
return;
}
const result = await fetchJson("/api/auth/register/step2", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ avatar_key: regDraft.avatar_key, title })
});
if(result.ok){
showAuthPanel("register-step3");
}else{
setMessage(message, result.data?.message || "Schritt 2 fehlgeschlagen.");
}
});
}
const registerForm = document.getElementById("registerForm");
if(registerForm){
registerForm.addEventListener("submit", async (e)=>{
e.preventDefault();
const username = document.getElementById("regUsername")?.value?.trim() || "";
const email = document.getElementById("regEmail")?.value?.trim() || "";
const password = document.getElementById("regPassword")?.value || "";
const message = document.getElementById("registerMessage");
setMessage(message, "");
const result = await fetchJson("/api/auth/register/step3", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ username, email, password })
});
if(result.ok){
showGameView();
refreshState();
}else{
setMessage(message, result.data?.message || "Registrierung fehlgeschlagen.");
}
});
}
const logoutBtn = document.getElementById("logoutBtn");
if(logoutBtn){
logoutBtn.addEventListener("click", async ()=>{
await fetchJson("/api/auth/logout", { method: "POST" });
stopPolling();
showAuthView();
showAuthPanel("login");
});
}
showAuthPanel("login");
refreshState();
ensureBuildButton();
setInterval(refreshState, 30000);
});
})();