Add a confirmation dialog that spells out the removal is irreversible, and expose delete from the GM session dropdown and the lobby session list.
108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
/**
|
|
* SessionDropdown — session switcher for the journal header (GM only)
|
|
*/
|
|
import { Component, createSignal, For, Show } from "solid-js";
|
|
import {
|
|
sessions,
|
|
joinSession,
|
|
useJournalStream,
|
|
} from "../stores/journalStream";
|
|
|
|
interface SessionDropdownProps {
|
|
onCreate: () => void;
|
|
onDelete: (id: string, name: string) => void;
|
|
}
|
|
|
|
export const SessionDropdown: Component<SessionDropdownProps> = (props) => {
|
|
const stream = useJournalStream();
|
|
const manifest = sessions;
|
|
const [dropdownOpen, setDropdownOpen] = createSignal(false);
|
|
let dropdownRef!: HTMLDivElement;
|
|
|
|
const handleSessionSelect = (id: string) => {
|
|
if (id === stream.sessionId) {
|
|
setDropdownOpen(false);
|
|
return;
|
|
}
|
|
setDropdownOpen(false);
|
|
joinSession(id).catch((e) => {
|
|
console.error("[journal] failed to switch session:", e);
|
|
});
|
|
};
|
|
|
|
// Close dropdown on outside click
|
|
if (typeof document !== "undefined") {
|
|
document.addEventListener("click", (e) => {
|
|
if (dropdownRef && !dropdownRef.contains(e.target as Node)) {
|
|
setDropdownOpen(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div ref={dropdownRef} class="relative">
|
|
<button
|
|
onClick={() => setDropdownOpen((v) => !v)}
|
|
class="text-[10px] text-gray-400 hover:text-gray-600 truncate leading-tight cursor-pointer text-left"
|
|
>
|
|
{stream.sessionName || stream.sessionId}{" "}
|
|
<span class="text-gray-300">▾</span>
|
|
</button>
|
|
<Show when={dropdownOpen()}>
|
|
<div
|
|
class="absolute top-full left-0 mt-1 w-48 bg-white border border-gray-200 rounded shadow-lg z-50 py-1 max-h-48 overflow-y-auto"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<For each={Object.entries(manifest().sessions)}>
|
|
{([id, meta]) => (
|
|
<div
|
|
class={`flex items-center ${
|
|
id === stream.sessionId ? "bg-blue-50" : ""
|
|
}`}
|
|
>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleSessionSelect(id);
|
|
}}
|
|
class={`flex-1 min-w-0 text-left px-3 py-1 text-xs hover:bg-gray-100 flex items-center gap-1 ${
|
|
id === stream.sessionId ? "text-blue-700" : "text-gray-700"
|
|
}`}
|
|
>
|
|
<span class="truncate flex-1">{meta.name || id}</span>
|
|
<Show when={id === stream.sessionId}>
|
|
<span class="text-blue-500 shrink-0">✓</span>
|
|
</Show>
|
|
</button>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setDropdownOpen(false);
|
|
props.onDelete(id, meta.name || id);
|
|
}}
|
|
class="shrink-0 px-2 py-1 text-[10px] text-gray-300 hover:text-red-500"
|
|
title="删除会话"
|
|
>
|
|
🗑
|
|
</button>
|
|
</div>
|
|
)}
|
|
</For>
|
|
<div class="border-t border-gray-100 my-0.5" />
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
setDropdownOpen(false);
|
|
props.onCreate();
|
|
}}
|
|
class="w-full text-left px-3 py-1 text-xs text-blue-600 hover:bg-gray-100"
|
|
>
|
|
+ 新建会话
|
|
</button>
|
|
</div>
|
|
</Show>
|
|
</div>
|
|
);
|
|
};
|