import { useStore } from '@nanostores/react'; import React, { memo, useEffect, useRef, useState } from 'react'; import { Panel, type ImperativePanelHandle } from 'react-resizable-panels'; import { IconButton } from '~/components/ui/IconButton'; import { shortcutEventEmitter } from '~/lib/hooks'; import { themeStore } from '~/lib/stores/theme'; import { workbenchStore } from '~/lib/stores/workbench'; import { classNames } from '~/utils/classNames'; import { Terminal, type TerminalRef } from './Terminal'; import { createScopedLogger } from '~/utils/logger'; const logger = createScopedLogger('Terminal'); const MAX_TERMINALS = 3; export const DEFAULT_TERMINAL_SIZE = 25; export const TerminalTabs = memo(() => { const showTerminal = useStore(workbenchStore.showTerminal); const theme = useStore(themeStore); const terminalRefs = useRef>([]); const terminalPanelRef = useRef(null); const terminalToggledByShortcut = useRef(false); const [activeTerminal, setActiveTerminal] = useState(0); const [terminalCount, setTerminalCount] = useState(1); const addTerminal = () => { if (terminalCount < MAX_TERMINALS) { setTerminalCount(terminalCount + 1); setActiveTerminal(terminalCount); } }; useEffect(() => { const { current: terminal } = terminalPanelRef; if (!terminal) { return; } const isCollapsed = terminal.isCollapsed(); if (!showTerminal && !isCollapsed) { terminal.collapse(); } else if (showTerminal && isCollapsed) { terminal.resize(DEFAULT_TERMINAL_SIZE); } terminalToggledByShortcut.current = false; }, [showTerminal]); useEffect(() => { const unsubscribeFromEventEmitter = shortcutEventEmitter.on('toggleTerminal', () => { terminalToggledByShortcut.current = true; }); const unsubscribeFromThemeStore = themeStore.subscribe(() => { for (const ref of Object.values(terminalRefs.current)) { ref?.reloadStyles(); } }); return () => { unsubscribeFromEventEmitter(); unsubscribeFromThemeStore(); }; }, []); return ( { if (!terminalToggledByShortcut.current) { workbenchStore.toggleTerminal(true); } }} onCollapse={() => { if (!terminalToggledByShortcut.current) { workbenchStore.toggleTerminal(false); } }} >
{Array.from({ length: terminalCount + 1 }, (_, index) => { const isActive = activeTerminal === index; return ( {index == 0 ? ( ) : ( )} ); })} {terminalCount < MAX_TERMINALS && } workbenchStore.toggleTerminal(false)} />
{Array.from({ length: terminalCount + 1 }, (_, index) => { const isActive = activeTerminal === index; logger.debug(`Starting bolt terminal [${index}]`); if (index == 0) { return ( { terminalRefs.current.push(ref); }} onTerminalReady={(terminal) => workbenchStore.attachBoltTerminal(terminal)} onTerminalResize={(cols, rows) => workbenchStore.onTerminalResize(cols, rows)} theme={theme} /> ); } else { return ( { terminalRefs.current.push(ref); }} onTerminalReady={(terminal) => workbenchStore.attachTerminal(terminal)} onTerminalResize={(cols, rows) => workbenchStore.onTerminalResize(cols, rows)} theme={theme} /> ); } })}
); });