feat: add URL parameter syncing for session and player
Implement URL search parameter synchronization for `session` and `player` to allow for bookmarkable links. Added `sessionName` to the journal stream state to support displaying human-readable session names in the UI. Improved layout transitions and updated the JournalPanel to show more detailed connection information.
This commit is contained in:
@@ -22,6 +22,8 @@ import { getMessageType, validatePayload } from "../journal/registry";
|
||||
|
||||
export interface JournalStreamState {
|
||||
sessionId: string | null;
|
||||
/** Human-readable name for the current session (from manifest) */
|
||||
sessionName: string | null;
|
||||
/** Full message log, oldest-first */
|
||||
messages: StreamMessage[];
|
||||
/** Last sequence number per sender */
|
||||
@@ -81,19 +83,30 @@ function loadPersisted(): {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const persisted = loadPersisted();
|
||||
const urlParams = readUrlParams();
|
||||
|
||||
// URL params override localStorage if present
|
||||
const initialName = urlParams.playerName ?? persisted.myName;
|
||||
const initialSession = urlParams.sessionId ?? persisted.lastSessionId;
|
||||
|
||||
const [state, setState] = createStore<JournalStreamState>({
|
||||
sessionId: persisted.lastSessionId,
|
||||
sessionId: initialSession,
|
||||
sessionName: null,
|
||||
messages: [],
|
||||
senderSeq: {},
|
||||
revealedPaths: new Set(),
|
||||
connected: false,
|
||||
connectionStatus: "disconnected",
|
||||
connectionError: null,
|
||||
myName: persisted.myName,
|
||||
myName: initialName,
|
||||
brokerUrl: persisted.brokerUrl,
|
||||
});
|
||||
|
||||
// Sync initial URL params if they came from localStorage (not URL)
|
||||
if (initialName && !urlParams.playerName) syncUrlParam("player", initialName);
|
||||
if (initialSession && !urlParams.sessionId)
|
||||
syncUrlParam("session", initialSession);
|
||||
|
||||
export { setState as journalSetState };
|
||||
|
||||
const [sessionList, setSessionList] = createSignal<SessionManifest>({
|
||||
@@ -104,13 +117,70 @@ export { sessionList as sessions };
|
||||
|
||||
/**
|
||||
* Change the current player's name. Persisted to localStorage so it
|
||||
* survives page reloads.
|
||||
* survives page reloads. Also syncs to URL search param.
|
||||
*/
|
||||
export function setMyName(name: string): void {
|
||||
setState("myName", name);
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.setItem(LS_PLAYER_NAME, name);
|
||||
}
|
||||
syncUrlParam("player", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active session ID and sync to URL. Also resolves the human-readable
|
||||
* session name from the cached manifest.
|
||||
*/
|
||||
export function setSessionId(id: string | null): void {
|
||||
setState("sessionId", id);
|
||||
if (typeof localStorage !== "undefined" && id) {
|
||||
localStorage.setItem(LS_LAST_SESSION, id);
|
||||
}
|
||||
if (id) {
|
||||
syncUrlParam("session", id);
|
||||
// Resolve session name from current manifest
|
||||
const manifest = sessionList();
|
||||
const name = manifest.sessions[id]?.name ?? null;
|
||||
setState("sessionName", name);
|
||||
} else {
|
||||
removeUrlParam("session");
|
||||
setState("sessionName", null);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// URL param sync (session & player for bookmarkable links)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function syncUrlParam(key: string, value: string): void {
|
||||
if (typeof window === "undefined") return;
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set(key, value);
|
||||
window.history.replaceState(null, "", url.toString());
|
||||
}
|
||||
|
||||
function removeUrlParam(key: string): void {
|
||||
if (typeof window === "undefined") return;
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete(key);
|
||||
window.history.replaceState(null, "", url.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read initial session/player from URL search params on store init.
|
||||
* Called once at module load time.
|
||||
*/
|
||||
function readUrlParams(): {
|
||||
sessionId: string | null;
|
||||
playerName: string | null;
|
||||
} {
|
||||
if (typeof window === "undefined")
|
||||
return { sessionId: null, playerName: null };
|
||||
const params = new URL(window.location.href).searchParams;
|
||||
return {
|
||||
sessionId: params.get("session"),
|
||||
playerName: params.get("player"),
|
||||
};
|
||||
}
|
||||
|
||||
// Will hold the MQTT client instance after connect()
|
||||
@@ -253,6 +323,11 @@ export async function connectStream(
|
||||
try {
|
||||
const manifest: SessionManifest = JSON.parse(raw);
|
||||
setSessionList(manifest);
|
||||
// Refresh the sessionName if we're in a session now
|
||||
const currentId = state.sessionId;
|
||||
if (currentId && manifest.sessions[currentId]) {
|
||||
setState("sessionName", manifest.sessions[currentId].name);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[stream] manifest parse err:", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user