Umschalter in den Einstellungen und auf der Anmeldeseite; Meldungen des Launchers (Lang.cs) und der Website (Accept-Language) in der gewaehlten Sprache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
465 lines
22 KiB
JavaScript
465 lines
22 KiB
JavaScript
"use strict";
|
||
// BSO Launcher - Oberflaeche. Spricht ueber window.chrome.webview mit dem Launcher (C#), nie direkt mit dem Netz.
|
||
// Texte auf Deutsch im Code, t() uebersetzt (i18n.js).
|
||
|
||
// ---------------------------------------------------------------- Verbindung zum Launcher
|
||
|
||
let nextId = 1;
|
||
const pending = new Map();
|
||
const listeners = new Map();
|
||
|
||
function call(cmd, args) {
|
||
return new Promise((resolve, reject) => {
|
||
const id = nextId++;
|
||
pending.set(id, { resolve, reject });
|
||
window.chrome.webview.postMessage({ id, cmd, args: args || {} });
|
||
});
|
||
}
|
||
|
||
window.chrome.webview.addEventListener("message", (e) => {
|
||
const msg = e.data;
|
||
if (msg.event) { (listeners.get(msg.event) || []).forEach((f) => f(msg)); return; }
|
||
const p = pending.get(msg.id);
|
||
if (!p) return;
|
||
pending.delete(msg.id);
|
||
msg.ok ? p.resolve(msg.result) : p.reject(new Error(msg.error));
|
||
});
|
||
|
||
function on(event, f) {
|
||
if (!listeners.has(event)) listeners.set(event, []);
|
||
listeners.get(event).push(f);
|
||
}
|
||
|
||
// ---------------------------------------------------------------- Hilfen
|
||
|
||
const $ = (id) => document.getElementById(id);
|
||
|
||
function h(tag, attrs, ...children) {
|
||
const el = document.createElement(tag);
|
||
for (const [k, v] of Object.entries(attrs || {})) {
|
||
if (v == null || v === false) continue;
|
||
if (k.startsWith("on")) el.addEventListener(k.slice(2), v);
|
||
else if (k === "class") el.className = v;
|
||
else el.setAttribute(k, v === true ? "" : v);
|
||
}
|
||
for (const c of children.flat()) if (c != null && c !== false) el.append(c.nodeType ? c : String(c));
|
||
return el;
|
||
}
|
||
|
||
function toast(text, error) {
|
||
const el = h("div", { class: "toast" + (error ? " error" : "") }, text);
|
||
$("toasts").append(el);
|
||
setTimeout(() => el.remove(), error ? 7000 : 4000);
|
||
}
|
||
|
||
/** Knopf waehrend einer Aktion sperren; Fehler als Meldung. */
|
||
async function busy(button, action) {
|
||
button.disabled = true;
|
||
try { return await action(); }
|
||
catch (e) { toast(e.message, true); }
|
||
finally { button.disabled = false; }
|
||
}
|
||
|
||
const date = (iso) => new Date(iso).toLocaleDateString(lang === "en" ? "en-GB" : "de-DE", { day: "2-digit", month: "2-digit", year: "numeric" });
|
||
|
||
// ---------------------------------------------------------------- Sprache
|
||
|
||
/** Sprache wechseln: im Launcher speichern (auch fuer seine eigenen Meldungen), alles neu beschriften. */
|
||
async function setLang(l) {
|
||
lang = l;
|
||
await call("setSettings", { language: l });
|
||
translateStatic();
|
||
renderLangSwitch(setLang);
|
||
setAuthMode(registerMode);
|
||
if (!$("app").classList.contains("hidden")) showPage(page);
|
||
}
|
||
|
||
// ---------------------------------------------------------------- Fenster
|
||
|
||
document.querySelectorAll("[data-window]").forEach((b) => b.addEventListener("click", () => call("window", { action: b.dataset.window })));
|
||
$("titlebarDrag").addEventListener("mousedown", (e) => { if (e.button === 0 && e.detail === 1) call("window", { action: "drag" }); });
|
||
$("titlebarDrag").addEventListener("dblclick", () => call("window", { action: "maximize" }));
|
||
on("window", (m) => { $("maximizeButton").innerHTML = m.maximized ? "" : ""; });
|
||
|
||
// ---------------------------------------------------------------- Anmeldung
|
||
|
||
let registerMode = false;
|
||
|
||
function setAuthMode(register) {
|
||
registerMode = register;
|
||
$("tabLogin").classList.toggle("on", !register);
|
||
$("tabRegister").classList.toggle("on", register);
|
||
$("emailRow").classList.toggle("hidden", !register);
|
||
$("password2Row").classList.toggle("hidden", !register);
|
||
$("authButton").textContent = register ? t("Konto erstellen") : t("Anmelden");
|
||
$("password").autocomplete = register ? "new-password" : "current-password";
|
||
$("authError").classList.add("hidden");
|
||
}
|
||
|
||
$("tabLogin").addEventListener("click", () => setAuthMode(false));
|
||
$("tabRegister").addEventListener("click", () => setAuthMode(true));
|
||
|
||
$("authForm").addEventListener("submit", async (e) => {
|
||
e.preventDefault();
|
||
const error = $("authError");
|
||
error.classList.add("hidden");
|
||
if (registerMode && $("password").value !== $("password2").value) {
|
||
error.textContent = t("Die Passwoerter sind nicht gleich");
|
||
error.classList.remove("hidden");
|
||
return;
|
||
}
|
||
$("authButton").disabled = true;
|
||
try {
|
||
const r = await call(registerMode ? "register" : "login",
|
||
{ username: $("username").value, password: $("password").value, email: $("email").value });
|
||
$("password").value = $("password2").value = "";
|
||
showApp(r.username);
|
||
if (registerMode) toast(t("Willkommen bei BSO, {0}!", r.username));
|
||
} catch (err) {
|
||
error.textContent = err.message;
|
||
error.classList.remove("hidden");
|
||
} finally {
|
||
$("authButton").disabled = false;
|
||
}
|
||
});
|
||
|
||
$("logout").addEventListener("click", async () => {
|
||
await call("logout");
|
||
showAuth();
|
||
});
|
||
|
||
function showAuth() {
|
||
$("app").classList.add("hidden");
|
||
$("auth").classList.remove("hidden");
|
||
setAuthMode(false);
|
||
$("username").focus();
|
||
}
|
||
|
||
function showApp(username) {
|
||
$("auth").classList.add("hidden");
|
||
$("app").classList.remove("hidden");
|
||
$("meName").textContent = username;
|
||
$("meAvatar").textContent = (username || "?").slice(0, 1).toUpperCase();
|
||
showPage("home");
|
||
}
|
||
|
||
// ---------------------------------------------------------------- Seiten
|
||
|
||
let page = "home";
|
||
document.querySelectorAll("#nav [data-page]").forEach((b) => b.addEventListener("click", () => showPage(b.dataset.page)));
|
||
|
||
function showPage(name) {
|
||
page = name;
|
||
document.querySelectorAll("#nav [data-page]").forEach((b) => b.classList.toggle("on", b.dataset.page === name));
|
||
const root = $("page");
|
||
root.replaceChildren(h("p", { class: "muted" }, t("Laden …")));
|
||
({ home: renderHome, friends: renderFriends, news: renderNews, settings: renderSettings })[name](root);
|
||
}
|
||
|
||
// Anmeldung abgelaufen (z.B. beim Holen des Spiel-Tickets)
|
||
on("loggedOut", () => showAuth());
|
||
|
||
// Fortschritt (Downloads) - der Text kommt schon uebersetzt vom Launcher
|
||
let progressBox = null;
|
||
on("progress", (m) => {
|
||
if (!progressBox) return;
|
||
progressBox.classList.remove("hidden");
|
||
progressBox.querySelector(".bar > div").style.width = Math.round(m.value * 100) + "%";
|
||
progressBox.querySelector("span").textContent = m.text + " " + Math.round(m.value * 100) + " %";
|
||
});
|
||
|
||
async function renderHome(root) {
|
||
let s;
|
||
try { s = await call("status"); }
|
||
catch (e) { root.replaceChildren(h("p", { class: "error" }, e.message)); return; }
|
||
if (page !== "home") return;
|
||
|
||
progressBox = h("div", { class: "progress hidden" }, h("span", { class: "small muted" }), h("div", { class: "bar" }, h("div")));
|
||
const play = h("button", { class: "btn primary play", type: "button" }, s.game.running ? t("LAEUFT") : t("SPIELEN"));
|
||
play.disabled = s.game.running;
|
||
play.addEventListener("click", () => busy(play, async () => {
|
||
await call("launch");
|
||
toast(t("Bus-Simulator 2012 startet …"));
|
||
}));
|
||
|
||
const ready = s.game.found && s.dotnet && (s.mod.installed || s.mod.latest);
|
||
const line = s.mod.installed ? t("BSO {0} installiert · Server {1}", s.mod.installed, s.settings.server) : t("BSO wird beim ersten Start installiert");
|
||
|
||
const children = [
|
||
h("div", { class: "hero" },
|
||
h("div", {}, h("h1", {}, "Bus Simulator Online"), h("p", {}, t("Multiplayer fuer Bus-Simulator 2012"))),
|
||
h("img", { src: "logo.svg", alt: "" }),
|
||
h("div", { class: "hero-actions" }, play, h("span", { class: "muted small" }, ready ? line : t("Vor dem Start fehlt noch etwas - siehe unten")))),
|
||
progressBox
|
||
];
|
||
|
||
if (s.launcherUpdate) {
|
||
const b = h("button", { class: "btn primary small", type: "button" }, t("Jetzt aktualisieren"));
|
||
b.addEventListener("click", () => busy(b, async () => { await call("installLauncherUpdate"); toast(t("Launcher startet neu …")); }));
|
||
children.push(h("div", { class: "banner" }, h("span", {}, h("b", {}, t("Launcher {0}", s.launcherUpdate)), t(" ist da.")), b));
|
||
}
|
||
if (!s.masterReachable)
|
||
children.push(h("div", { class: "banner warn" }, h("span", {}, t("BSO-Server nicht erreichbar - Updates werden spaeter geprueft."))));
|
||
|
||
children.push(h("div", { class: "cards" }, gameCard(s), modCard(s), dotnetCard(s), serverCard(s)));
|
||
root.replaceChildren(...children);
|
||
}
|
||
|
||
function card(title, state, value, ...extra) {
|
||
return h("div", { class: "card" }, h("h3", {}, h("span", { class: "dot " + state }), title), h("div", { class: "value" }, value), ...extra);
|
||
}
|
||
|
||
function gameCard(s) {
|
||
const choose = h("button", { class: "btn small", type: "button" }, s.game.found ? t("Anderen Ordner waehlen") : t("Ordner waehlen"));
|
||
choose.addEventListener("click", () => busy(choose, async () => { if ((await call("chooseGameFolder")).changed) showPage("home"); }));
|
||
return card("Bus-Simulator 2012", s.game.found ? "ok" : "bad", s.game.found ? t("Gefunden") : t("Nicht gefunden"),
|
||
s.game.found ? h("span", { class: "mono muted" }, s.game.folder) : h("span", { class: "muted small" }, t("Ueber Steam installiert? Sonst den Ordner von Hand waehlen.")),
|
||
h("div", { class: "row" }, choose));
|
||
}
|
||
|
||
function modCard(s) {
|
||
const m = s.mod;
|
||
const state = m.installed && !m.updateAvailable ? "ok" : m.latest ? "warn" : "bad";
|
||
const value = m.installed ? t("Version {0}", m.installed) : t("Nicht installiert");
|
||
const info = m.updateAvailable ? t("Neu: {0}", m.latest) : m.latest ? t("Aktuell") : t("Keine Version auf dem Server");
|
||
const extra = [h("span", { class: "muted small" }, info)];
|
||
// Immer da: holt die neueste Mod von der Website (bei SPIELEN passiert das sonst automatisch, wenn eingeschaltet)
|
||
if (m.latest && s.game.found) {
|
||
const label = !m.installed ? t("Mod herunterladen") : m.updateAvailable ? t("Mod aktualisieren") : t("Neu herunterladen");
|
||
const b = h("button", { class: "btn small" + (!m.installed || m.updateAvailable ? " primary" : ""), type: "button" }, label);
|
||
b.addEventListener("click", () => busy(b, async () => {
|
||
const r = await call("installMod");
|
||
toast(t("BSO {0} installiert", r.version));
|
||
showPage("home");
|
||
}));
|
||
extra.push(h("div", { class: "row" }, b));
|
||
}
|
||
return card("BSO-Mod", state, value, ...extra);
|
||
}
|
||
|
||
function dotnetCard(s) {
|
||
if (s.dotnet) return card(".NET 10 Runtime", "ok", t("Installiert"), h("span", { class: "muted small" }, t("Die Mod laeuft mit .NET 10 (x64).")));
|
||
const b = h("button", { class: "btn small", type: "button" }, t("Herunterladen"));
|
||
b.addEventListener("click", () => call("openDotnet"));
|
||
return card(".NET 10 Runtime", "bad", t("Fehlt"),
|
||
h("span", { class: "muted small" }, t("BSO braucht die .NET 10 Runtime (x64) - \".NET Runtime\" fuer Windows x64 installieren.")),
|
||
h("div", { class: "row" }, b));
|
||
}
|
||
|
||
function serverCard(s) {
|
||
const input = h("input", { value: s.settings.server, placeholder: "host:7777", spellcheck: "false" });
|
||
const auto = h("input", { type: "checkbox" });
|
||
auto.checked = s.settings.autoConnect;
|
||
const save = () => call("setSettings", { server: input.value.trim(), autoConnect: auto.checked }).then(() => toast(t("Gespeichert")));
|
||
input.addEventListener("change", save);
|
||
auto.addEventListener("change", save);
|
||
return card("Server", "ok", t("Mitspielen"),
|
||
h("label", {}, h("span", {}, t("Adresse")), input),
|
||
h("label", { class: "switch" }, h("span", {}, t("Beim Start verbinden"), h("small", {}, t("sonst im Spiel mit F8"))), auto));
|
||
}
|
||
|
||
// ---------------------------------------------------------------- Freunde & Lobby
|
||
// Die Lobby liegt auf der Website, ihr Server laeuft im BSO-Relay - keiner hostet, keiner braucht eine Portfreigabe.
|
||
|
||
let social = { friends: { friends: [], incoming: [], outgoing: [] }, lobby: { lobby: null, invites: [] } };
|
||
const seenNotices = new Set();
|
||
|
||
async function loadSocial() {
|
||
const [friends, lobby] = await Promise.all([call("friends"), call("lobby")]);
|
||
social = { friends, lobby };
|
||
const n = friends.incoming.length + lobby.invites.length;
|
||
$("friendsBadge").textContent = n;
|
||
$("friendsBadge").classList.toggle("hidden", n === 0);
|
||
// Neue Einladungen/Anfragen melden (auch auf anderen Seiten)
|
||
for (const i of lobby.invites) notice("l" + i.lobbyId, t("{0} lädt dich in die Lobby ein", i.owner));
|
||
for (const r of friends.incoming) notice("f" + r.id, t("{0} möchte dein Freund sein", r.username));
|
||
}
|
||
|
||
function notice(key, text) {
|
||
if (seenNotices.has(key)) return;
|
||
seenNotices.add(key);
|
||
if (page !== "friends") toast(text);
|
||
}
|
||
|
||
/** Alle 3 s auf der Seite Freunde, sonst alle 15 s (Badge, Meldungen) - meldet einen auch als "online". */
|
||
let socialTick = 0;
|
||
setInterval(async () => {
|
||
if ($("app").classList.contains("hidden")) return;
|
||
if (page !== "friends" && socialTick++ % 5 !== 0) return;
|
||
try { await loadSocial(); if (page === "friends") drawSocial(); } catch { }
|
||
}, 3000);
|
||
|
||
async function renderFriends(root) {
|
||
root.replaceChildren(h("h1", {}, t("Freunde & Lobby")), h("div", { class: "social", id: "socialRoot" }, h("p", { class: "muted" }, t("Laden …"))));
|
||
try { await loadSocial(); }
|
||
catch (e) { $("socialRoot").replaceChildren(h("p", { class: "error" }, e.message)); return; }
|
||
if (page === "friends") drawSocial();
|
||
}
|
||
|
||
/** Knopf, der eine Aktion ausfuehrt und danach neu laedt. */
|
||
function action(label, cls, cmd, args, done) {
|
||
const b = h("button", { class: "btn small " + (cls || ""), type: "button" }, label);
|
||
b.addEventListener("click", () => busy(b, async () => {
|
||
await call(cmd, args || {});
|
||
if (done) done();
|
||
await loadSocial();
|
||
drawSocial();
|
||
}));
|
||
return b;
|
||
}
|
||
|
||
function drawSocial() {
|
||
const root = $("socialRoot");
|
||
if (!root) return;
|
||
root.replaceChildren(lobbyCard(), friendsCard());
|
||
}
|
||
|
||
function lobbyCard() {
|
||
const { lobby, invites } = social.lobby;
|
||
const card = h("div", { class: "card" });
|
||
for (const i of invites)
|
||
card.append(h("div", { class: "invite" }, h("span", {}, h("b", {}, i.owner), t(" lädt dich in die Lobby ein")),
|
||
action(t("Beitreten"), "primary", "lobbyJoin", { lobbyId: i.lobbyId }), action(t("Ablehnen"), "", "lobbyDecline", { lobbyId: i.lobbyId })));
|
||
|
||
if (!lobby) {
|
||
card.append(h("h3", {}, t("Lobby")),
|
||
h("p", { class: "muted small" }, t("Erstelle eine Lobby und lade Freunde ein. Der Server läuft auf dem BSO-Relay – niemand muss etwas hosten oder Ports freigeben.")),
|
||
h("div", { class: "row" }, action(t("Lobby erstellen"), "primary", "lobbyCreate")));
|
||
return card;
|
||
}
|
||
|
||
const play = h("button", { class: "btn primary play", type: "button" }, t("SPIELEN"));
|
||
play.disabled = !lobby.serverOnline;
|
||
play.addEventListener("click", () => busy(play, async () => { await call("playLobby"); toast(t("Bus-Simulator 2012 startet und verbindet mit der Lobby …")); }));
|
||
|
||
const state = h("div", { class: "server-state" },
|
||
h("span", { class: "dot " + (lobby.serverOnline ? "ok" : "bad") }),
|
||
h("span", { style: "flex:1" }, lobby.serverOnline ? t("Server läuft") : lobby.isOwner ? t("Server aus – starte ihn, dann können alle spielen") : t("Server aus – warte, bis {0} ihn startet", lobby.owner)),
|
||
lobby.isOwner ? (lobby.serverOnline ? action(t("Server stoppen"), "", "lobbyStop") : action(t("Server starten"), "primary", "lobbyStart")) : null);
|
||
|
||
const members = h("div", { class: "people" }, ...lobby.members.map((m) => h("div", { class: "person" },
|
||
h("span", { class: "dot " + (m.online ? "ok" : "") }),
|
||
h("span", { class: "name" }, m.username),
|
||
m.isOwner ? h("span", { class: "crown", title: t("Ersteller") }, "★") : null,
|
||
lobby.isOwner && !m.isOwner ? action(t("Entfernen"), "ghost", "lobbyKick", { accountId: m.accountId }) : null)));
|
||
|
||
card.append(h("h3", {}, lobby.isOwner ? t("Deine Lobby") : t("Lobby von {0}", lobby.owner)), state,
|
||
h("div", { class: "section-title" }, t("Mitglieder ({0})", lobby.members.length)), members,
|
||
lobby.invited.length ? h("p", { class: "muted small" }, t("{0} Einladung(en) offen", lobby.invited.length)) : null,
|
||
h("div", { class: "row", style: "justify-content:space-between;margin-top:6px" }, play,
|
||
action(lobby.isOwner ? t("Lobby auflösen") : t("Lobby verlassen"), "danger", "lobbyLeave")));
|
||
return card;
|
||
}
|
||
|
||
function friendsCard() {
|
||
const { friends, incoming, outgoing } = social.friends;
|
||
const lobby = social.lobby.lobby;
|
||
const name = h("input", { placeholder: t("Benutzername"), maxlength: "32", spellcheck: "false" });
|
||
const add = h("button", { class: "btn small primary", type: "button" }, t("Hinzufügen"));
|
||
const send = () => busy(add, async () => {
|
||
if (!name.value.trim()) return;
|
||
await call("friendRequest", { username: name.value.trim() });
|
||
toast(t("Anfrage an {0} geschickt", name.value.trim()));
|
||
name.value = "";
|
||
await loadSocial();
|
||
drawSocial();
|
||
});
|
||
add.addEventListener("click", send);
|
||
name.addEventListener("keydown", (e) => { if (e.key === "Enter") send(); });
|
||
|
||
const card = h("div", { class: "card" }, h("h3", {}, t("Freunde")), h("div", { class: "row" }, name, add));
|
||
if (incoming.length)
|
||
card.append(h("div", { class: "section-title" }, t("Anfragen")), h("div", { class: "people" }, ...incoming.map((r) => h("div", { class: "person" },
|
||
h("span", { class: "name" }, r.username), action(t("Annehmen"), "primary", "friendAccept", { id: r.id }), action(t("Ablehnen"), "", "friendRemove", { id: r.id })))));
|
||
|
||
card.append(h("div", { class: "section-title" }, t("Freunde ({0} online)", friends.filter((f) => f.online).length)));
|
||
if (!friends.length) card.append(h("p", { class: "muted small" }, t("Noch keine Freunde – füge jemanden über seinen BSO-Benutzernamen hinzu.")));
|
||
card.append(h("div", { class: "people" }, ...friends.map((f) => {
|
||
const inLobby = lobby && lobby.members.some((m) => m.accountId === f.accountId);
|
||
const invited = lobby && lobby.invited.includes(f.accountId);
|
||
const remove = h("button", { class: "x", type: "button", title: t("Freund entfernen") }, "");
|
||
remove.addEventListener("click", () => { if (confirm(t("{0} als Freund entfernen?", f.username))) busy(remove, async () => { await call("friendRemove", { id: f.id }); await loadSocial(); drawSocial(); }); });
|
||
return h("div", { class: "person" },
|
||
h("span", { class: "dot " + (f.online ? "ok" : "") }), h("span", { class: "name" }, f.username),
|
||
lobby && lobby.isOwner && !inLobby ? (invited ? h("span", { class: "muted small" }, t("eingeladen")) : action(t("Einladen"), "", "lobbyInvite", { accountId: f.accountId })) : null,
|
||
inLobby ? h("span", { class: "muted small" }, t("in der Lobby")) : null,
|
||
remove);
|
||
})));
|
||
|
||
if (outgoing.length)
|
||
card.append(h("div", { class: "section-title" }, t("Gesendet")), h("div", { class: "people" }, ...outgoing.map((r) => h("div", { class: "person" },
|
||
h("span", { class: "name" }, r.username), h("span", { class: "muted small" }, t("wartet …")), action(t("Zurückziehen"), "ghost", "friendRemove", { id: r.id })))));
|
||
return card;
|
||
}
|
||
|
||
// ---------------------------------------------------------------- Neuigkeiten, Einstellungen
|
||
|
||
async function renderNews(root) {
|
||
let list;
|
||
try { list = await call("releases"); }
|
||
catch (e) { root.replaceChildren(h("h1", {}, t("Neuigkeiten")), h("p", { class: "error" }, e.message)); return; }
|
||
if (page !== "news") return;
|
||
const names = { mod: "Mod", launcher: "Launcher", server: "Server" };
|
||
list = list.filter((r) => r.product !== "server"); // Server laufen jetzt im BSO-Relay
|
||
root.replaceChildren(h("h1", {}, t("Neuigkeiten")),
|
||
...(list.length ? list.map((r) => h("article", { class: "release" },
|
||
h("header", {}, h("span", { class: "tag" }, names[r.product] || r.product), h("h2", {}, r.version), h("span", { class: "muted small" }, date(r.date))),
|
||
h("pre", {}, r.changelog || t("Keine Beschreibung")))) : [h("p", { class: "muted" }, t("Noch keine Versionen veroeffentlicht."))]));
|
||
}
|
||
|
||
async function renderSettings(root) {
|
||
const [init, s] = await Promise.all([call("init"), call("status").catch(() => null)]);
|
||
if (page !== "settings") return;
|
||
const toggle = (label, hint, key, value) => {
|
||
const box = h("input", { type: "checkbox" });
|
||
box.checked = value;
|
||
box.addEventListener("change", () => call("setSettings", { [key]: box.checked }).then(() => toast(t("Gespeichert"))));
|
||
return h("label", { class: "switch" }, h("span", {}, label, h("small", {}, hint)), box);
|
||
};
|
||
const folder = h("button", { class: "btn small", type: "button" }, t("Spielordner waehlen"));
|
||
folder.addEventListener("click", () => busy(folder, async () => { if ((await call("chooseGameFolder")).changed) { toast(t("Spielordner gespeichert")); showPage("settings"); } }));
|
||
const log = h("button", { class: "btn small", type: "button" }, t("Protokoll-Ordner oeffnen"));
|
||
log.addEventListener("click", () => call("openLog"));
|
||
const uninstall = h("button", { class: "btn small danger", type: "button" }, t("BSO deinstallieren"));
|
||
uninstall.addEventListener("click", () => busy(uninstall, () => call("uninstall")));
|
||
const langBox = h("div", { class: "lang-switch", "data-lang-switch": true });
|
||
|
||
root.replaceChildren(h("h1", {}, t("Einstellungen")), h("div", { class: "settings" },
|
||
h("div", { class: "card" },
|
||
toggle(t("BSO automatisch aktualisieren"), t("Vor jedem Start die neueste Mod-Version installieren"), "autoUpdateMod", s?.settings.autoUpdateMod ?? true),
|
||
toggle(t("Launcher offen lassen"), t("Sonst schliesst er sich nach dem Spielstart"), "keepOpen", s?.settings.keepOpen ?? false),
|
||
h("div", { class: "switch" }, h("span", {}, t("Sprache")), langBox)),
|
||
h("div", { class: "card" },
|
||
h("h3", {}, t("Spiel")),
|
||
h("span", { class: "mono muted" }, s?.game.folder || t("Nicht gefunden")),
|
||
h("div", { class: "row" }, folder)),
|
||
h("div", { class: "card" },
|
||
h("h3", {}, "Launcher"),
|
||
h("span", { class: "muted small" }, t("Version {0} · Server {1}", init.version, init.master)),
|
||
h("div", { class: "row" }, log, uninstall))));
|
||
renderLangSwitch(setLang);
|
||
}
|
||
|
||
// ---------------------------------------------------------------- Start
|
||
|
||
(async () => {
|
||
const init = await call("init");
|
||
lang = init.language === "en" ? "en" : "de";
|
||
translateStatic();
|
||
renderLangSwitch(setLang);
|
||
setAuthMode(false);
|
||
$("meVersion").textContent = "Launcher " + init.version;
|
||
if (init.dev) {
|
||
// -dev: gegen die lokale Test-Website - gut sichtbar, damit niemand das verwechselt
|
||
const host = init.master.replace(/^https?:\/\//, "");
|
||
$("devBadge").textContent = "DEV · " + host;
|
||
$("devBadge").classList.remove("hidden");
|
||
$("meVersion").textContent += " · DEV";
|
||
}
|
||
$("authServer").textContent = t("Server: {0}", init.master.replace(/^https?:\/\//, ""));
|
||
if (!init.loggedIn) { showAuth(); return; }
|
||
const me = await call("me");
|
||
if (me.loggedIn) showApp(me.username);
|
||
else showAuth();
|
||
})();
|