Launcher 0.3.3: Freunde-Eingabe bleibt, Lobby eines Freundes beitreten

Das Feld Benutzername wird beim Aktualisieren nicht mehr neu angelegt (Text, Fokus und Cursor bleiben),
neu gezeichnet wird nur bei Aenderungen. Beitreten-Knopf bei Freunden mit Lobby und Liste der Freundes-Lobbys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 21:29:22 +01:00
co-authored by Claude Opus 5.5
parent c7a30e7fa3
commit 6ec2714a15
4 changed files with 44 additions and 4 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>BSO.Launcher</RootNamespace>
<AssemblyName>BSO-Launcher</AssemblyName>
<Version>0.3.2</Version>
<Version>0.3.3</Version>
<Company>MBM.wtf</Company>
<Product>BSO Launcher</Product>
<Description>BSO Launcher - Bus Simulator Online</Description>
+4
View File
@@ -3,6 +3,10 @@
Neue Version: hier einen Abschnitt `## <Version>` anlegen, `<Version>` in `BSO.Launcher.csproj` erhöhen, dann
`dotnet publish -c Release -o publish` – lädt die Version hoch; der Abschnitt wird der Changelog auf der Website und im Launcher.
## 0.3.3
- Freunde: Was man ins Feld „Benutzername“ tippt, verschwindet nicht mehr nach ein paar Sekunden
- Lobby eines Freundes beitreten, auch ohne Einladung („Beitreten“ beim Freund und unter „Lobbys deiner Freunde“)
## 0.3.2
- Auto-Update wie beim MSCO Launcher: ein neuer Launcher installiert sich beim Start sofort selbst und startet neu,
neue BSO-Versionen kommen beim Start (beides in den Einstellungen abschaltbar)
+37 -3
View File
@@ -275,7 +275,11 @@ 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 { }
const before = JSON.stringify(social);
try {
await loadSocial();
if (page === "friends" && JSON.stringify(social) !== before) drawSocial(); // nur neu zeichnen, wenn sich was geaendert hat
} catch { }
}, 3000);
async function renderFriends(root) {
@@ -297,14 +301,34 @@ function action(label, cls, cmd, args, done) {
return b;
}
// Eingabefeld "Freund hinzufuegen" bleibt ueber das Neuzeichnen hinweg dasselbe - sonst waere Getipptes weg
const friendName = h("input", { maxlength: "32", spellcheck: "false" });
function drawSocial() {
const root = $("socialRoot");
if (!root) return;
const typing = document.activeElement === friendName;
const [start, end] = [friendName.selectionStart, friendName.selectionEnd];
root.replaceChildren(lobbyCard(), friendsCard());
if (typing) { friendName.focus(); friendName.setSelectionRange(start, end); }
}
/** Der Lobby eines Freundes beitreten - auch ohne Einladung. Wer selbst eine Lobby erstellt hat, loest sie dabei auf. */
function joinButton(fl) {
const b = h("button", { class: "btn small primary", type: "button" }, t("Beitreten"));
b.disabled = fl.full;
if (fl.full) b.title = t("Die Lobby ist voll");
b.addEventListener("click", () => {
const mine = social.lobby.lobby;
if (mine && mine.isOwner && !confirm(t("Deine Lobby wird dabei aufgelöst. Trotzdem {0} beitreten?", fl.owner))) return;
busy(b, async () => { await call("lobbyJoin", { lobbyId: fl.lobbyId }); await loadSocial(); drawSocial(); });
});
return b;
}
function lobbyCard() {
const { lobby, invites } = social.lobby;
const friendLobbies = social.lobby.friendLobbies || [];
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")),
@@ -314,6 +338,13 @@ function lobbyCard() {
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")));
if (friendLobbies.length)
card.append(h("div", { class: "section-title" }, t("Lobbys deiner Freunde")),
h("div", { class: "people" }, ...friendLobbies.map((fl) => h("div", { class: "person" },
h("span", { class: "dot " + (fl.serverOnline ? "ok" : "") }),
h("span", { class: "name" }, t("Lobby von {0}", fl.owner)),
h("span", { class: "muted small" }, t("{0} Spieler", fl.members)),
joinButton(fl)))));
return card;
}
@@ -350,7 +381,8 @@ function lobbyCard() {
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 name = friendName;
name.placeholder = t("Benutzername");
const add = h("button", { class: "btn small primary", type: "button" }, t("Hinzufügen"));
const send = () => busy(add, async () => {
if (!name.value.trim()) return;
@@ -361,7 +393,7 @@ function friendsCard() {
drawSocial();
});
add.addEventListener("click", send);
name.addEventListener("keydown", (e) => { if (e.key === "Enter") send(); });
name.onkeydown = (e) => { if (e.key === "Enter") send(); }; // onkeydown statt addEventListener: das Feld bleibt, der Knopf ist neu
const card = h("div", { class: "card" }, h("h3", {}, t("Freunde")), h("div", { class: "row" }, name, add));
if (incoming.length)
@@ -373,12 +405,14 @@ function friendsCard() {
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 theirLobby = (social.lobby.friendLobbies || []).find((fl) => fl.ownerId === 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,
!inLobby && theirLobby ? joinButton(theirLobby) : null,
remove);
})));
+2
View File
@@ -38,6 +38,8 @@ const EN = {
"Server läuft – alle können SPIELEN drücken": "Server is running – everyone can press PLAY",
"Drück SPIELEN – der Server startet dann für die ganze Lobby": "Press PLAY – the server then starts for the whole lobby",
"Warte, bis {0} SPIELEN drückt – dann startet der Server": "Waiting for {0} to press PLAY – then the server starts", "Server stoppen": "Stop server",
"Lobbys deiner Freunde": "Your friends' lobbies", "{0} Spieler": "{0} players", "Die Lobby ist voll": "The lobby is full",
"Deine Lobby wird dabei aufgelöst. Trotzdem {0} beitreten?": "Your lobby will be closed. Join {0} anyway?",
"Ersteller": "Owner", "Entfernen": "Remove", "Deine Lobby": "Your lobby", "Lobby von {0}": "{0}'s lobby", "Mitglieder ({0})": "Members ({0})",
"{0} Einladung(en) offen": "{0} invitation(s) pending", "Lobby auflösen": "Close lobby", "Lobby verlassen": "Leave lobby",
"Hinzufügen": "Add", "Anfrage an {0} geschickt": "Request sent to {0}", "Anfragen": "Requests", "Annehmen": "Accept",