/** * ConnectionBar — connect form, session picker, player name * * Three states: disconnected → connecting → connected */ import { Component, createSignal, Show, For, onMount } from "solid-js"; import { connectStream, hydrateFromServer, useJournalStream, sessions, setMyName, createSession, deleteSession, } from "../stores/journalStream"; import type { SessionMeta } from "../stores/journalStream"; import { createMemo } from "solid-js"; // Direct access to the store's setter for session switching import { journalSetState } from "../stores/journalStream"; export const ConnectionBar: Component = () => { const stream = useJournalStream(); const [brokerUrl, setBrokerUrl] = createSignal(""); const [newSessionName, setNewSessionName] = createSignal(""); const [playerName, setPlayerName] = createSignal(""); const [connecting, setConnecting] = createSignal(false); const [error, setError] = createSignal(null); const [showNewSession, setShowNewSession] = createSignal(false); onMount(() => { setBrokerUrl(stream.brokerUrl ?? ""); setPlayerName(stream.myName); }); const manifest = sessions; const handleConnect = async () => { const url = brokerUrl().trim(); if (!url) return; setConnecting(true); setError(null); try { // Save name first if (playerName().trim()) { setMyName(playerName().trim()); } // Connect const sessionId = stream.sessionId || "default"; await hydrateFromServer(sessionId); await connectStream(sessionId, url); } catch (e) { setError(e instanceof Error ? e.message : "Connection failed"); } finally { setConnecting(false); } }; const handleCreateSession = () => { const name = newSessionName().trim(); if (!name) return; createSession(name); setNewSessionName(""); setShowNewSession(false); }; const handleDeleteSession = (id: string) => { if (confirm(`Delete session "${id}"? This cannot be undone.`)) { deleteSession(id); } }; const handleSessionSelect = async (id: string) => { if (!stream.connected) return; try { await hydrateFromServer(id); journalSetState("sessionId", id); } catch (e) { setError("Failed to load session"); } }; const statusColor = () => stream.connected ? "bg-green-500" : connecting() ? "bg-yellow-400" : "bg-gray-400"; return (
{/* Status + session name */}
{stream.connected ? "connected" : connecting() ? "connecting..." : "disconnected"} {stream.sessionId}
{/* Disconnected: connect form */}
setBrokerUrl(e.currentTarget.value)} placeholder="MQTT broker (e.g. tcp://192.168.1.5:1883)" class="w-full border border-gray-300 rounded px-2 py-1 text-xs" /> setPlayerName(e.currentTarget.value)} placeholder="Your name (GM or player)" class="w-full border border-gray-300 rounded px-2 py-1 text-xs" />

{error()}

{/* Connected: session management (GM only) */}
Sessions:
setNewSessionName(e.currentTarget.value)} placeholder="Session name" class="flex-1 border border-gray-300 rounded px-2 py-0.5 text-xs" onKeyDown={(e) => e.key === "Enter" && handleCreateSession()} />
{([id, meta]) => (
handleSessionSelect(id)} > {meta.name || id}
)}
{/* Connected: player info (player only) */}
Playing as: {stream.myName}
); };