feat(journal): implement session management UI components

Add a suite of dialogs and header components to manage journal
sessions, including:

- ConnectDialog for joining sessions with name and role selection
- CreateSessionDialog for starting new sessions
- InviteDialog for generating and copying player invite links
- SessionDropdown for switching between sessions (GM only)
- JournalHeader for displaying connection status and user info
This commit is contained in:
2026-07-08 00:31:35 +08:00
parent 3d957706e9
commit 2c2b7b781d
7 changed files with 456 additions and 398 deletions
+79
View File
@@ -0,0 +1,79 @@
/**
* JournalHeader — status dot, player name, session dropdown, disconnect & close
*/
import { Component, Show } from "solid-js";
import { useJournalStream, disconnectStream } from "../stores/journalStream";
import { SessionDropdown } from "./SessionDropdown";
interface JournalHeaderProps {
onClose: () => void;
}
export const JournalHeader: Component<JournalHeaderProps> = (props) => {
const stream = useJournalStream();
const handleDisconnect = () => disconnectStream();
return (
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 shrink-0">
<div class="flex items-center gap-2 min-w-0">
<span
class="w-2 h-2 rounded-full shrink-0"
classList={{
"bg-gray-400": stream.connectionStatus === "disconnected",
"bg-yellow-400 animate-pulse":
stream.connectionStatus === "connecting",
"bg-green-500": stream.connectionStatus === "connected",
"bg-red-500": stream.connectionStatus === "error",
}}
title={stream.connectionStatus}
/>
<Show
when={stream.connected && stream.sessionId}
fallback={
<h2 class="text-sm font-semibold text-gray-700 truncate">未连接</h2>
}
>
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-700 truncate leading-tight flex items-center gap-1">
{stream.myName}
<Show when={stream.myRole !== "gm"}>
<span
class={`text-[10px] px-1 rounded ${
stream.myRole === "observer"
? "bg-gray-200 text-gray-500"
: "bg-blue-100 text-blue-600"
}`}
>
{stream.myRole}
</span>
</Show>
</p>
{/* Clickable subtitle — session dropdown (GM only) */}
<Show when={stream.myRole === "gm"}>
<SessionDropdown />
</Show>
</div>
</Show>
</div>
<div class="flex items-center gap-1 shrink-0">
<Show when={stream.connected}>
<button
onClick={handleDisconnect}
class="text-xs text-gray-400 hover:text-red-500 px-1"
title="断开连接"
>
⏻
</button>
</Show>
<button
onClick={props.onClose}
class="text-gray-400 hover:text-gray-600 text-sm px-1"
title="隐藏面板"
>
▸
</button>
</div>
</div>
);
};