Skip to content
File

Blob: src/client/components/session-controls.tsx

typescript102 lines
1import { RefreshCw, RotateCcw, Plus } from "lucide-react";
2import { useWorkspaceStore } from "../workspace/store";
3import type { SocketState } from "../terminal/socket";
4import { Button } from "./ui";
5 
6const STATE_DOT_CLASS: Record<SocketState, string> = {
7 disconnected: "bg-zinc-500",
8 connecting: "bg-amber-500",
9 connected: "bg-emerald-500",
10 reconnecting: "bg-amber-500",
11 ended: "bg-red-500",
12 error: "bg-red-500",
13};
14 
15const STATE_LABEL: Record<SocketState, string> = {
16 disconnected: "Disconnected",
17 connecting: "Connecting\u2026",
18 connected: "Connected",
19 reconnecting: "Reconnecting\u2026",
20 ended: "Session Ended",
21 error: "Error",
22};
23 
24export function SessionControls() {
25 const activeTab = useWorkspaceStore((s) => s.tabs.find((t) => t.id === s.activeTabId));
26 const connectionState: SocketState = useWorkspaceStore((s) => s.connectionStates[s.activeTabId] ?? "disconnected");
27 const isSharedSession = useWorkspaceStore((s) => s.isSharedSession);
28 const reconnect = useWorkspaceStore((s) => s.reconnect);
29 const newSession = useWorkspaceStore((s) => s.newSession);
30 const resetSession = useWorkspaceStore((s) => s.resetSession);
31 
32 const socketBusy = connectionState === "connecting" || connectionState === "reconnecting";
33 
34 return (
35 <header className="shrink-0">
36 {isSharedSession && (
37 <div
38 className="px-4 py-1 bg-amber-500 text-zinc-950 text-xs font-semibold text-center"
39 role="alert"
40 aria-live="assertive"
41 >
42 Shared session — other users may be connected and can send input
43 </div>
44 )}
45
46 <div className="flex items-center flex-wrap gap-4 px-4 py-1.5 bg-zinc-900 border-b border-zinc-800/60 text-[13px] min-h-9">
47 {/* ---- identifiers ---- */}
48 <div className="flex items-center gap-1.5 overflow-hidden">
49 <span className="text-zinc-500 shrink-0">Workspace</span>
50 <span className="text-zinc-300 font-mono text-xs max-w-40 truncate">default</span>
51 <span className="text-zinc-700 mx-0.5" aria-hidden="true">
52 |
53 </span>
54 <span className="text-zinc-500 shrink-0">Session</span>
55 <span className="text-zinc-300 font-mono text-xs max-w-40 truncate" title={activeTab?.sessionId ?? ""}>
56 {activeTab?.sessionId ?? ""}
57 </span>
58 </div>
59
60 {/* ---- connection state ---- */}
61 <div className="flex items-center gap-1.5 ml-auto shrink-0" role="status" aria-live="polite">
62 <span className={`size-2 rounded-full shrink-0 ${STATE_DOT_CLASS[connectionState]}`} />
63 <span>{STATE_LABEL[connectionState]}</span>
64 </div>
65
66 {/* ---- actions ---- */}
67 <div className="flex gap-1.5 shrink-0 flex-wrap">
68 <Button
69 variant="ghost"
70 size="sm"
71 icon={<RefreshCw size={12} />}
72 onClick={reconnect}
73 disabled={socketBusy}
74 aria-label="Reconnect to session"
75 >
76 Reconnect
77 </Button>
78 <Button
79 variant="ghost"
80 size="sm"
81 icon={<RotateCcw size={12} />}
82 onClick={resetSession}
83 disabled={socketBusy}
84 aria-label="Reset current session"
85 >
86 Reset Session
87 </Button>
88 <Button
89 variant="ghost"
90 size="sm"
91 icon={<Plus size={12} />}
92 onClick={newSession}
93 aria-label="Create new session"
94 >
95 New Session
96 </Button>
97 </div>
98 </div>
99 </header>
100 );
101}