"use client"; import { memo, useCallback, useState } from "react"; import { IconPlus } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { useAppStore } from "@/components/state-provider"; import { useUserShells } from "@/hooks/domains/session/use-user-shells"; import { releaseAutoCreatedEnvironment } from "@/hooks/domains/session/use-mobile-terminals"; import { MobilePillButton } from "./mobile-pill-button"; import { MobilePickerSheet } from "./mobile-picker-sheet"; import { MobileTerminalRow } from "./mobile-terminal-row"; import { useMobileTerminalsContext } from "./mobile-terminals-context"; import type { Terminal } from "@/hooks/domains/session/use-terminals"; import { useTranslation } from "react-i18next"; type CloseHandlerArgs = { environmentId: string | null; terminals: Terminal[]; destroyTerminal: (id: string) => Promise; addTerminal: () => void; }; function useTerminalCloseHandler({ environmentId, terminals, destroyTerminal, addTerminal, }: CloseHandlerArgs) { const [pendingClose, setPendingClose] = useState(null); const closeTerminal = useCallback( async (terminal: Terminal) => { const succeeded = await destroyTerminal(terminal.id); if (succeeded && environmentId && terminals.length <= 1) { releaseAutoCreatedEnvironment(environmentId); addTerminal(); } return succeeded; }, [destroyTerminal, environmentId, terminals.length, addTerminal], ); const handleConfirmClose = useCallback(async () => { if (!pendingClose) return; const terminal = pendingClose; setPendingClose(null); await closeTerminal(terminal); }, [pendingClose, closeTerminal]); return { pendingClose, setPendingClose, handleConfirmClose }; } function MobileTerminalsHeader({ count, onAdd }: { count: number; onAdd: () => void }) { const { t } = useTranslation(); return (
{t("task:terminalCount", { count })}
); } const MobileTerminalsList = memo(function MobileTerminalsList({ sessionId, onClose, }: { sessionId: string | null; onClose: () => void; }) { const { t } = useTranslation(); const { terminals, terminalTabValue, addTerminal, destroyTerminal, environmentId } = useMobileTerminalsContext(); const setRightPanelActiveTab = useAppStore((s) => s.setRightPanelActiveTab); const taskId = useAppStore((s) => s.tasks?.activeTaskId ?? null); const { shells } = useUserShells(environmentId, taskId); const { pendingClose, setPendingClose, handleConfirmClose } = useTerminalCloseHandler({ environmentId, terminals, destroyTerminal, addTerminal, }); const isShellRunning = useCallback( (id: string) => shells.find((s) => s.terminalId === id)?.running ?? false, [shells], ); const handleSelect = useCallback( (id: string) => { if (sessionId) setRightPanelActiveTab(sessionId, id); onClose(); }, [sessionId, setRightPanelActiveTab, onClose], ); const handleAskClose = useCallback( (terminal: Terminal) => { setPendingClose(terminal); }, [setPendingClose], ); if (!sessionId) { return (
{t("task:noActiveSession")}
); } return (
{terminals.length === 0 && (
{t("task:noTerminalsYet")}
)} {terminals.map((t) => ( setPendingClose(null)} onClose={() => setPendingClose(null)} onConfirmClose={handleConfirmClose} /> ))}
); }); function useActiveTerminalPillLabel(): { label: string; count: string | undefined } { const { t } = useTranslation(); const { terminals, terminalTabValue } = useMobileTerminalsContext(); const activeIdx = terminals.findIndex((t) => t.id === terminalTabValue); const idx = activeIdx >= 0 ? activeIdx : 0; const active = terminals[idx]; const total = terminals.length; let count: string | undefined; if (total > 1) count = `${idx + 1}/${total}`; return { label: active?.label ?? t("task:terminal"), count }; } export const MobileTerminalsPicker = memo(function MobileTerminalsPicker({ sessionId, compact, fullWidth, }: { sessionId: string | null; compact?: boolean; fullWidth?: boolean; }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); const { label, count } = useActiveTerminalPillLabel(); if (!sessionId) return null; return ( <> setOpen(true)} data-testid="mobile-terminals-pill" ariaLabel={t("task:activeTerminalTapToSwitch", { label })} /> setOpen(false)} /> ); });