ttrpg-tools/src/components/journal/ConnectionBar.tsx

149 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ConnectionBar — session picker, player info (connected state only)
*/
import { Component, createSignal, Show, For } from "solid-js";
import {
hydrateFromServer,
useJournalStream,
sessions,
createSession,
deleteSession,
disconnectStream,
setSessionId,
} from "../stores/journalStream";
import { journalSetState } from "../stores/journalStream";
export const ConnectionBar: Component = () => {
const stream = useJournalStream();
const [newSessionName, setNewSessionName] = createSignal("");
const [showNewSession, setShowNewSession] = createSignal(false);
const [error, setError] = createSignal<string | null>(null);
const manifest = sessions;
const handleCreateSession = () => {
const name = newSessionName().trim();
if (!name) return;
const id = createSession(name);
if (id) {
setSessionId(id);
hydrateFromServer(id).catch(() => {});
}
setNewSessionName("");
setShowNewSession(false);
};
const handleDeleteSession = (id: string) => {
if (confirm(`确定要删除会话 "${id}"?此操作不可撤销。`)) {
deleteSession(id);
}
};
const handleSessionSelect = async (id: string) => {
try {
await hydrateFromServer(id);
setSessionId(id);
} catch (e) {
setError("无法加载会话");
}
};
const handleDisconnect = () => {
disconnectStream();
};
return (
<div class="border-b border-gray-200 px-3 py-2 space-y-2 text-sm">
{/* Status row */}
<div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-green-500" />
<span class="text-xs text-gray-500"></span>
<Show when={stream.sessionId}>
<span class="text-xs font-mono bg-gray-100 px-1.5 py-0.5 rounded">
{stream.sessionName || stream.sessionId}
</span>
</Show>
<div class="flex-1" />
<Show when={stream.myName !== "gm"}>
<span class="text-xs text-gray-400"> {stream.myName}</span>
</Show>
<button
onClick={handleDisconnect}
class="text-xs text-gray-400 hover:text-red-500"
title="断开连接"
>
</button>
</div>
{/* GM session management */}
<Show when={stream.myName === "gm"}>
<div class="space-y-1">
<div class="flex items-center gap-1">
<span class="text-xs text-gray-500"></span>
<button
onClick={() => setShowNewSession((v) => !v)}
class="text-xs text-blue-600 hover:underline"
>
+
</button>
</div>
<Show when={showNewSession()}>
<div class="flex gap-1">
<input
type="text"
value={newSessionName()}
onInput={(e) => setNewSessionName(e.currentTarget.value)}
placeholder="会话名称"
class="flex-1 border border-gray-300 rounded px-2 py-0.5 text-xs"
onKeyDown={(e) => e.key === "Enter" && handleCreateSession()}
/>
<button
onClick={handleCreateSession}
class="bg-green-600 text-white rounded px-2 py-0.5 text-xs hover:bg-green-700"
>
Create Create
</button>
</div>
</Show>
<For each={Object.entries(manifest().sessions)}>
{([id, meta]) => (
<div
class={`flex items-center gap-1 px-1 py-0.5 rounded cursor-pointer text-xs ${
id === stream.sessionId
? "bg-blue-100 text-blue-800"
: "hover:bg-gray-100"
}`}
>
<span
class="flex-1 truncate"
onClick={() => handleSessionSelect(id)}
>
{meta.name || id}
</span>
<button
onClick={(e) => {
e.stopPropagation();
handleDeleteSession(id);
}}
class="text-gray-400 hover:text-red-500 text-xs"
title="删除会话"
>
</button>
</div>
)}
</For>
</div>
</Show>
<Show when={error()}>
<p class="text-red-500 text-xs">{error()}</p>
</Show>
</div>
);
};