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:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* InviteDialog — input player name, copy invite link
|
||||
*/
|
||||
import { Component, createSignal } from "solid-js";
|
||||
import { useJournalStream } from "../stores/journalStream";
|
||||
|
||||
interface InviteDialogProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const InviteDialog: Component<InviteDialogProps> = (props) => {
|
||||
const stream = useJournalStream();
|
||||
const [playerName, setPlayerName] = createSignal("");
|
||||
const [copied, setCopied] = createSignal(false);
|
||||
|
||||
const inviteLink = () => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("session", stream.sessionId || "default");
|
||||
if (playerName().trim()) {
|
||||
url.searchParams.set("player", playerName().trim());
|
||||
}
|
||||
url.searchParams.set("autojoin", "1");
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(inviteLink());
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch {
|
||||
const input = document.getElementById(
|
||||
"invite-link-input",
|
||||
) as HTMLInputElement;
|
||||
if (input) input.select();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="absolute inset-0 z-50 flex items-center justify-center bg-black/20">
|
||||
<div class="bg-white rounded-lg border border-gray-200 shadow-xl w-[280px] p-4 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-gray-800">邀请玩家</h3>
|
||||
<button
|
||||
onClick={props.onClose}
|
||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
输入玩家名字并分享链接,对方将以玩家身份加入。
|
||||
</p>
|
||||
<input
|
||||
type="text"
|
||||
value={playerName()}
|
||||
onInput={(e) => setPlayerName(e.currentTarget.value)}
|
||||
placeholder="玩家名字"
|
||||
class="w-full border border-gray-300 rounded px-2 py-1 text-xs"
|
||||
autofocus
|
||||
/>
|
||||
<div class="flex gap-1">
|
||||
<input
|
||||
id="invite-link-input"
|
||||
type="text"
|
||||
value={inviteLink()}
|
||||
readonly
|
||||
class="flex-1 border border-gray-200 rounded px-2 py-1 text-xs bg-gray-50 text-gray-600 font-mono truncate"
|
||||
/>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
class={`shrink-0 rounded px-2 py-1 text-xs font-medium transition-colors ${
|
||||
copied()
|
||||
? "bg-green-100 text-green-700"
|
||||
: "bg-blue-600 text-white hover:bg-blue-700"
|
||||
}`}
|
||||
>
|
||||
{copied() ? "已复制!" : "复制"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user