Hash-based routing (#1198)

## Description:

Implement hash-based routing.

Fixes #1111

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

---------

Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
This commit is contained in:
Scott Anderson
2025-06-16 21:45:53 -04:00
committed by GitHub
parent b17b925b3b
commit ec4ef01d99
6 changed files with 36 additions and 24 deletions
+1 -1
View File
@@ -538,7 +538,7 @@ export class HostLobbyModal extends LitElement {
try {
//TODO: Convert id to url and copy
await navigator.clipboard.writeText(
`${location.origin}/join/${this.lobbyId}`,
`${location.origin}#join=${this.lobbyId}`,
);
this.copySuccess = true;
setTimeout(() => {
+29 -14
View File
@@ -1,6 +1,5 @@
import page from "page";
import favicon from "../../resources/images/Favicon.svg";
import { GameRecord, GameStartInfo } from "../core/Schemas";
import { GameRecord, GameStartInfo, ID } from "../core/Schemas";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { GameType } from "../core/game/Game";
import { UserSettings } from "../core/game/UserSettings";
@@ -247,20 +246,25 @@ class Client {
} else {
document.documentElement.classList.remove("dark");
}
page("/join/:lobbyId", (ctx) => {
if (ctx.init && sessionStorage.getItem("inLobby")) {
// On page reload, go back home
page("/");
return;
// Attempt to join lobby
this.handleHash();
const onHashUpdate = () => {
// Reset the UI to its initial state
this.joinModal.close();
if (this.gameStop !== null) {
this.handleLeaveLobby();
}
const lobbyId = ctx.params.lobbyId;
this.joinModal.open(lobbyId);
// Attempt to join lobby
this.handleHash();
};
console.log(`joining lobby ${lobbyId}`);
});
// Handle browser navigation & manual hash edits
window.addEventListener("popstate", onHashUpdate);
window.addEventListener("hashchange", onHashUpdate);
page();
function updateSliderProgress(slider) {
const percent =
((slider.value - slider.min) / (slider.max - slider.min)) * 100;
@@ -275,6 +279,18 @@ class Client {
});
}
private handleHash() {
const { hash } = window.location;
if (hash.startsWith("#")) {
const params = new URLSearchParams(hash.slice(1));
const lobbyId = params.get("join");
if (lobbyId && ID.safeParse(lobbyId).success) {
this.joinModal.open(lobbyId);
console.log(`joining lobby ${lobbyId}`);
}
}
}
private async handleJoinLobby(event: CustomEvent) {
const lobby = event.detail as JoinLobbyEvent;
console.log(`joining lobby ${lobby.gameID}`);
@@ -343,8 +359,7 @@ class Client {
});
if (event.detail.gameConfig?.gameType !== GameType.Singleplayer) {
window.history.pushState({}, "", `/join/${lobby.gameID}`);
sessionStorage.setItem("inLobby", "true");
history.pushState(null, "", `#join=${lobby.gameID}`);
}
},
);
+5 -1
View File
@@ -28,12 +28,16 @@ function getToken(): string | null {
const token = params.get("token");
if (token) {
localStorage.setItem("token", token);
params.delete("token");
params.toString();
}
// Clean the URL
history.replaceState(
null,
"",
window.location.pathname + window.location.search,
window.location.pathname +
window.location.search +
(params.size > 0 ? "#" + params.toString() : ""),
);
}
return localStorage.getItem("token");
+1 -1
View File
@@ -161,7 +161,7 @@ const EmojiSchema = z
.number()
.nonnegative()
.max(flattenedEmojiTable.length - 1);
const ID = z
export const ID = z
.string()
.regex(/^[a-zA-Z0-9]+$/)
.length(8);