"use client"; import type { ReactNode, MouseEvent } from "react"; import type { Layout } from "react-resizable-panels"; import { memo, useEffect, useCallback, useState, useMemo } from "react"; import { Badge } from "@kandev/ui/badge"; import { SessionPanel, SessionPanelContent } from "@kandev/ui/pannel-session"; import { Group, Panel } from "react-resizable-panels"; import { TabsContent } from "@kandev/ui/tabs"; import { getLocalStorage, setLocalStorage, getSessionStorage, setSessionStorage, } from "@/lib/local-storage"; import { ShellTerminal } from "@/components/task/shell-terminal"; import { PassthroughTerminal } from "@/components/task/passthrough-terminal"; import { useAppStore } from "@/components/state-provider"; import { useLayoutStore } from "@/lib/state/layout-store"; import { useDefaultLayout } from "@/lib/layout/use-default-layout"; import { SessionTabs, type SessionTab } from "@/components/session-tabs"; import { useRepositoryScripts } from "@/hooks/domains/workspace/use-repository-scripts"; import { useTerminals } from "@/hooks/domains/session/use-terminals"; import type { RepositoryScript } from "@/lib/types/http"; import type { Terminal } from "@/hooks/domains/session/use-terminals"; type TaskRightPanelProps = { topPanel: ReactNode; sessionId?: string | null; repositoryId?: string | null; initialScripts?: RepositoryScript[]; initialTerminals?: Terminal[]; }; const DEFAULT_RIGHT_LAYOUT: Record = { top: 55, bottom: 45 }; function buildTerminalTabs( terminals: Terminal[], handleCloseDevTab: (event: MouseEvent) => void, handleCloseTab: (event: MouseEvent, id: string) => void, ): SessionTab[] { return terminals.map((terminal) => ({ id: terminal.id, label: terminal.label, className: terminal.closable ? "group flex items-center cursor-pointer" : "cursor-pointer", closable: terminal.closable, onClose: terminal.type === "dev-server" ? handleCloseDevTab : (e: MouseEvent) => handleCloseTab(e, terminal.id), })); } function useRightPanelScripts(repositoryId: string | null, initialScripts: RepositoryScript[]) { const { scripts: storeScripts, isLoaded: scriptsLoaded } = useRepositoryScripts(repositoryId); const scripts = !scriptsLoaded && initialScripts.length > 0 ? initialScripts : storeScripts; return { scripts, hasScripts: scripts.length > 0 }; } function useRightPanelPersistence({ sessionId, hasScripts, activeTab, isBottomCollapsed, setRightPanelActiveTab, }: { sessionId: string | null; hasScripts: boolean; activeTab: string | undefined; isBottomCollapsed: boolean; setRightPanelActiveTab: (sessionId: string, tabId: string) => void; }) { useEffect(() => { if (!sessionId || !hasScripts) return; const savedTab = getSessionStorage(`rightPanel-tab-${sessionId}`, null); if (savedTab === "commands") setRightPanelActiveTab(sessionId, savedTab); }, [sessionId, hasScripts, setRightPanelActiveTab]); useEffect(() => { if (!sessionId || !activeTab) return; setSessionStorage(`rightPanel-tab-${sessionId}`, activeTab); }, [sessionId, activeTab]); useEffect(() => { setLocalStorage("task-right-panel-collapsed", isBottomCollapsed); }, [isBottomCollapsed]); } function useRightPanelTabs({ hasScripts, terminals, handleCloseDevTab, handleCloseTab, sessionId, setRightPanelActiveTab, }: { hasScripts: boolean; terminals: Terminal[]; handleCloseDevTab: (event: MouseEvent) => void; handleCloseTab: (event: MouseEvent, terminalId: string) => void; sessionId: string | null; setRightPanelActiveTab: (sessionId: string, tabId: string) => void; }) { const tabs: SessionTab[] = useMemo(() => { const commandsTabs: SessionTab[] = hasScripts ? [{ id: "commands", label: "Commands" }] : []; return [...commandsTabs, ...buildTerminalTabs(terminals, handleCloseDevTab, handleCloseTab)]; }, [hasScripts, terminals, handleCloseDevTab, handleCloseTab]); const handleTabChange = useCallback( (value: string) => { if (sessionId) setRightPanelActiveTab(sessionId, value); }, [sessionId, setRightPanelActiveTab], ); return { tabs, handleTabChange }; } type CollapsedRightPanelProps = { topPanel: ReactNode; tabs: SessionTab[]; terminalTabValue: string; handleTabChange: (value: string) => void; addTerminal: () => void; onExpand: () => void; }; function CollapsedRightPanel({ topPanel, tabs, terminalTabValue, handleTabChange, addTerminal, onExpand, }: CollapsedRightPanelProps) { return (
{topPanel}
); } type RightPanelContentProps = { isBottomCollapsed: boolean; topPanel: ReactNode; tabs: SessionTab[]; terminalTabValue: string; handleTabChange: (value: string) => void; addTerminal: () => void; setIsBottomCollapsed: (v: boolean) => void; rightLayoutKey: string; rightLayout: Layout | undefined; onRightLayoutChange: (layout: Layout) => void; scripts: RepositoryScript[]; handleRunCommand: (script: RepositoryScript) => void; terminals: Terminal[]; environmentId: string | null; devProcessId: string | null | undefined; devOutput: string | undefined; isStoppingDev: boolean; }; function RightPanelContent({ isBottomCollapsed, topPanel, tabs, terminalTabValue, handleTabChange, addTerminal, setIsBottomCollapsed, rightLayoutKey, rightLayout, onRightLayoutChange, scripts, handleRunCommand, terminals, environmentId, devProcessId, devOutput, isStoppingDev, }: RightPanelContentProps) { if (isBottomCollapsed) { return ( setIsBottomCollapsed(false)} /> ); } return ( {topPanel} setIsBottomCollapsed(true)} className="flex-1 min-h-0" > ); } const TaskRightPanel = memo(function TaskRightPanel({ topPanel, sessionId = null, repositoryId = null, initialScripts = [], initialTerminals, }: TaskRightPanelProps) { const rightPanelIds = ["top", "bottom"]; const rightLayoutKey = "task-layout-right-v2"; const { defaultLayout: rightLayout, onLayoutChanged: onRightLayoutChange } = useDefaultLayout({ id: rightLayoutKey, panelIds: rightPanelIds, baseLayout: DEFAULT_RIGHT_LAYOUT, }); const [isBottomCollapsed, setIsBottomCollapsed] = useState(() => getLocalStorage("task-right-panel-collapsed", false), ); const setRightPanelActiveTab = useAppStore((state) => state.setRightPanelActiveTab); const environmentId = useAppStore((state) => sessionId ? (state.environmentIdBySessionId[sessionId] ?? null) : null, ); const closeLayoutPreview = useLayoutStore((state) => state.closePreview); // Use the terminals hook — env-keyed for shell ops, session-keyed for tab UX const { terminals, activeTab, terminalTabValue, addTerminal, handleCloseDevTab: baseHandleCloseDevTab, handleCloseTab, handleRunCommand, isStoppingDev, devProcessId, devOutput, } = useTerminals({ sessionId, environmentId, initialTerminals }); // Wrap handleCloseDevTab to also close the layout preview const handleCloseDevTab = useCallback( async (event: MouseEvent) => { await baseHandleCloseDevTab(event); if (sessionId) { closeLayoutPreview(sessionId); } }, [baseHandleCloseDevTab, sessionId, closeLayoutPreview], ); const { scripts, hasScripts } = useRightPanelScripts(repositoryId, initialScripts); useRightPanelPersistence({ sessionId, hasScripts, activeTab, isBottomCollapsed, setRightPanelActiveTab, }); const { tabs, handleTabChange } = useRightPanelTabs({ hasScripts, terminals, handleCloseDevTab, handleCloseTab, sessionId, setRightPanelActiveTab, }); return ( ); }); /** Commands tab content showing repository scripts */ function CommandsTabContent({ scripts, onRunCommand, }: { scripts: RepositoryScript[]; onRunCommand: (script: RepositoryScript) => void; }) { return (
{scripts.map((script) => ( ))}
); } /** Terminal tab contents (dev-server and shell terminals) */ function TerminalTabContents({ terminals, environmentId, devProcessId, devOutput, isStoppingDev, }: { terminals: Terminal[]; environmentId: string | null; devProcessId: string | null | undefined; devOutput: string | undefined; isStoppingDev: boolean; }) { return ( <> {terminals.map((terminal) => ( {terminal.type === "dev-server" ? ( ) : ( )} ))} ); } export { TaskRightPanel };