uploads
This commit is contained in:
@@ -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);
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user