/** * ConnectDialog — broker connection + session lobby (list / create / join) * * The broker connection and the active session are decoupled: this dialog * connects to the broker, which makes the session list available, and joining * or creating a session is a separate step that reuses that connection. */ import { Component, createSignal, For, onMount, Show } from "solid-js"; import { TbOutlineTrash } from "solid-icons/tb"; import { connectBroker, createSession, joinSession, resolveBrokerUrl, sessions, setMyName, setMyRole, useJournalStream, } from "../stores/journalStream"; import { DeleteSessionDialog } from "./DeleteSessionDialog"; export const ConnectDialog: Component = () => { const stream = useJournalStream(); const manifest = sessions; const [playerName, setPlayerName] = createSignal(stream.myName); const [role, setRole] = createSignal<"gm" | "player" | "observer">( stream.myRole, ); const [newSessionName, setNewSessionName] = createSignal(""); const [busy, setBusy] = createSignal(false); const [error, setError] = createSignal(null); const [autoJoined, setAutoJoined] = createSignal(false); const [sessionToDelete, setSessionToDelete] = createSignal<{ id: string; name: string; } | null>(null); const ensureBroker = async () => { if (stream.connected) return; await connectBroker(resolveBrokerUrl()); }; const identity = () => ({ name: playerName().trim() || stream.myName, role: role(), }); const applyIdentity = () => { const { name, role: r } = identity(); setMyName(name); setMyRole(r); }; const handleJoin = async (sessionId: string) => { setBusy(true); setError(null); try { applyIdentity(); await ensureBroker(); await joinSession(sessionId, identity()); } catch (e) { setError(e instanceof Error ? e.message : "Connection failed"); } finally { setBusy(false); } }; const handleCreate = async () => { const name = newSessionName().trim(); if (!name) return; setBusy(true); setError(null); try { applyIdentity(); await ensureBroker(); const id = await createSession(name); if (id) await joinSession(id, identity()); setNewSessionName(""); } catch (e) { setError(e instanceof Error ? e.message : "Connection failed"); } finally { setBusy(false); } }; // Auto-join when the URL carries session + player + autojoin params; // otherwise enter the lobby so the session list is available up front. onMount(() => { const params = new URL(window.location.href).searchParams; if ( params.has("session") && params.has("player") && params.has("autojoin") ) { setPlayerName(params.get("player") || ""); setRole("player"); setAutoJoined(true); handleJoin(params.get("session")!); return; } ensureBroker().catch((e) => { setError(e instanceof Error ? e.message : "Connection failed"); }); }); return ( {busy() ? "正在加入会话…" : "已加入!"}

} >

输入你的名字和角色,选择或新建一个会话。
连接到 {window.location.host}

setPlayerName(e.currentTarget.value)} onKeyDown={(e) => e.key === "Enter" && handleCreate()} placeholder="你的名字" class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm" autofocus /> {/* Role selector */}
{(["gm", "player", "observer"] as const).map((r) => ( ))}
{/* Session list */}

会话

{([id, meta]) => (
)}

{stream.connected ? "还没有会话" : "正在加载会话…"}

{/* Create a new session and join it (GM only) */}
setNewSessionName(e.currentTarget.value)} onKeyDown={(e) => e.key === "Enter" && handleCreate()} placeholder="新会话名称" class="flex-1 border border-gray-300 rounded px-2 py-1.5 text-sm" />

{error()}

{(target) => ( setSessionToDelete(null)} /> )}
); };