From 421af125bb73332cf5d8b175474030d65f3091c3 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 13 Sep 2026 13:36:42 +0800 Subject: [PATCH] feat(journal): delete sessions from the ui Add a confirmation dialog that spells out the removal is irreversible, and expose delete from the GM session dropdown and the lobby session list. --- src/components/journal/ConnectDialog.tsx | 44 ++++++++-- .../journal/DeleteSessionDialog.tsx | 85 +++++++++++++++++++ src/components/journal/JournalHeader.tsx | 6 +- src/components/journal/JournalPanel.tsx | 18 ++++ src/components/journal/SessionDropdown.tsx | 44 +++++++--- src/components/journal/index.ts | 1 + 6 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 src/components/journal/DeleteSessionDialog.tsx diff --git a/src/components/journal/ConnectDialog.tsx b/src/components/journal/ConnectDialog.tsx index 30612ec..2565ecb 100644 --- a/src/components/journal/ConnectDialog.tsx +++ b/src/components/journal/ConnectDialog.tsx @@ -16,6 +16,7 @@ import { setMyRole, useJournalStream, } from "../stores/journalStream"; +import { DeleteSessionDialog } from "./DeleteSessionDialog"; export const ConnectDialog: Component = () => { const stream = useJournalStream(); @@ -28,6 +29,10 @@ export const ConnectDialog: Component = () => { const [busy, setBusy] = createSignal(false); const [error, setError] = createSignal(null); const [autoJoined, setAutoJoined] = createSignal(false); + const [sessionToDelete, setSessionToDelete] = createSignal<{ + id: string; + name: string; + } | null>(null); const ensureBroker = async () => { if (stream.connected) return; @@ -145,13 +150,27 @@ export const ConnectDialog: Component = () => {
{([id, meta]) => ( - +
+ + + + +
)}
@@ -187,6 +206,17 @@ export const ConnectDialog: Component = () => {

{error()}

+ + + {(target) => ( + setSessionToDelete(null)} + /> + )} +
); diff --git a/src/components/journal/DeleteSessionDialog.tsx b/src/components/journal/DeleteSessionDialog.tsx new file mode 100644 index 0000000..3afc080 --- /dev/null +++ b/src/components/journal/DeleteSessionDialog.tsx @@ -0,0 +1,85 @@ +/** + * DeleteSessionDialog — destructive confirmation before removing a session. + * + * Deletion removes the session directory on the server (meta + full message + * log), so the warning spells out that it cannot be undone. + */ +import { Component, createSignal, Show } from "solid-js"; +import { deleteSession } from "../stores/journalStream"; + +interface DeleteSessionDialogProps { + sessionId: string; + sessionName: string; + /** Whether this is the session the user is currently in. */ + active: boolean; + onClose: () => void; +} + +export const DeleteSessionDialog: Component = ( + props, +) => { + const [deleting, setDeleting] = createSignal(false); + const [error, setError] = createSignal(null); + + const handleDelete = async () => { + setDeleting(true); + setError(null); + try { + await deleteSession(props.sessionId); + props.onClose(); + } catch (e) { + setError(e instanceof Error ? e.message : "删除失败"); + } finally { + setDeleting(false); + } + }; + + return ( +
+
e.stopPropagation()} + > +
+

删除会话

+ +
+

+ 确定要删除会话{" "} + {props.sessionName} + ({props.sessionId})吗? +

+

+ 此操作不可恢复:该会话的全部消息记录、玩家名单和派生状态都会从服务器删除。 + {props.active && " 你当前正在该会话中,删除后将返回会话列表。"} +

+ +

{error()}

+
+
+ + +
+
+
+ ); +}; diff --git a/src/components/journal/JournalHeader.tsx b/src/components/journal/JournalHeader.tsx index 0dd7330..7ec593f 100644 --- a/src/components/journal/JournalHeader.tsx +++ b/src/components/journal/JournalHeader.tsx @@ -8,6 +8,7 @@ import { SessionDropdown } from "./SessionDropdown"; interface JournalHeaderProps { onClose: () => void; onCreateSession: () => void; + onDeleteSession: (id: string, name: string) => void; } export const JournalHeader: Component = (props) => { @@ -54,7 +55,10 @@ export const JournalHeader: Component = (props) => {

{/* Clickable subtitle — session dropdown (GM only) */} - + diff --git a/src/components/journal/JournalPanel.tsx b/src/components/journal/JournalPanel.tsx index 21d2434..66975f1 100644 --- a/src/components/journal/JournalPanel.tsx +++ b/src/components/journal/JournalPanel.tsx @@ -15,6 +15,7 @@ import { JournalHeader } from "./JournalHeader"; import { ConnectDialog } from "./ConnectDialog"; import { InviteDialog } from "./InviteDialog"; import { CreateSessionDialog } from "./CreateSessionDialog"; +import { DeleteSessionDialog } from "./DeleteSessionDialog"; import { VariableView } from "./VariableView"; export interface JournalPanelProps { @@ -26,6 +27,10 @@ export const JournalPanel: Component = (props) => { const stream = useJournalStream(); const [showInvite, setShowInvite] = createSignal(false); const [showCreateSession, setShowCreateSession] = createSignal(false); + const [sessionToDelete, setSessionToDelete] = createSignal<{ + id: string; + name: string; + } | null>(null); const [viewMode, setViewMode] = createSignal<"stream" | "variables">("stream"); // Player list (exclude gm and observer) @@ -56,6 +61,7 @@ export const JournalPanel: Component = (props) => { setShowCreateSession(true)} + onDeleteSession={(id, name) => setSessionToDelete({ id, name })} /> {/* Player list tabs */} @@ -126,6 +132,18 @@ export const JournalPanel: Component = (props) => { setShowCreateSession(false)} /> + + {/* Delete session modal */} + + {(target) => ( + setSessionToDelete(null)} + /> + )} + diff --git a/src/components/journal/SessionDropdown.tsx b/src/components/journal/SessionDropdown.tsx index 045e37a..d97ead4 100644 --- a/src/components/journal/SessionDropdown.tsx +++ b/src/components/journal/SessionDropdown.tsx @@ -10,6 +10,7 @@ import { interface SessionDropdownProps { onCreate: () => void; + onDelete: (id: string, name: string) => void; } export const SessionDropdown: Component = (props) => { @@ -54,22 +55,37 @@ export const SessionDropdown: Component = (props) => { > {([id, meta]) => ( - + + + )}
diff --git a/src/components/journal/index.ts b/src/components/journal/index.ts index d233345..3d3fc11 100644 --- a/src/components/journal/index.ts +++ b/src/components/journal/index.ts @@ -45,6 +45,7 @@ export { ConnectDialog } from "./ConnectDialog"; export { InviteDialog } from "./InviteDialog"; export { SessionDropdown } from "./SessionDropdown"; export { CreateSessionDialog } from "./CreateSessionDialog"; +export { DeleteSessionDialog } from "./DeleteSessionDialog"; export { StreamView } from "./StreamView"; export { StreamMessageCard } from "./StreamMessage"; export { ComposePanel } from "./ComposePanel";