diff --git a/BSO.Launcher.csproj b/BSO.Launcher.csproj
index 8db1bf6..f9b076c 100644
--- a/BSO.Launcher.csproj
+++ b/BSO.Launcher.csproj
@@ -8,7 +8,7 @@
enable
BSO.Launcher
BSO-Launcher
- 0.3.2
+ 0.3.3
MBM.wtf
BSO Launcher
BSO Launcher - Bus Simulator Online
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4315ac..7112e1a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
Neue Version: hier einen Abschnitt `## ` anlegen, `` 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)
diff --git a/ui/app.js b/ui/app.js
index 438bb5c..a35b9dc 100644
--- a/ui/app.js
+++ b/ui/app.js
@@ -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);
})));
diff --git a/ui/i18n.js b/ui/i18n.js
index 12b375a..ea20dd4 100644
--- a/ui/i18n.js
+++ b/ui/i18n.js
@@ -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",