/* eslint-disable @typescript-eslint/no-explicit-any */ import React, { memo, useEffect, useState, useSyncExternalStore } from 'react'; import { createPortal } from 'react-dom'; import { getSidePanelLiveSnapshot, sidePanelLiveStore, } from '../../application/state/sidePanelLiveStore'; import { getPaneMagnificationShortcutLabel } from '../../domain/paneMagnification'; import { terminalLayerWorkspaceCtxEqual } from './terminalLayerViewMemo'; type WorkspaceContext = Record; function TerminalLayerWorkspaceSectionInner({ ctx }: { ctx: WorkspaceContext }) { // Active workspace / focused session come ONLY from the live store so top-tab // switches do not force a TerminalLayerView ctx rebuild just to flip them. // Do not fall back to ctx.* — after memo omits activeWorkspace from equality, // ctx can still hold a previous workspace after live has been cleared // (workspace → solo session). Falling back would keep compose bar / resizer // handlers bound to a stale workspace. // Panes already derive visibility from activeTabStore themselves. const live = useSyncExternalStore( sidePanelLiveStore.subscribe, sidePanelLiveStore.getSnapshot, () => getSidePanelLiveSnapshot(false), ); const activeWorkspace = live.activeWorkspace; const focusedSessionId = live.focusedSessionId; const isFocusMode = activeWorkspace?.viewMode === 'focus'; const { workspaceInnerRef, workspaceOuterRef, activeTabId, workspaceOverlayRef, draggingSessionId, dropHint, setDropHint, computeSplitHint, handleWorkspaceDrop, TerminalPanesHost, sessions, sessionHostsMap, sessionChainHostsMap, sessionSudoAutofillPasswordsMap, sessionSudoAutofillCandidatesMap, resolvedSessionHostIds, workspaceById, workspaceRectsById, isTerminalLayerVisible, magnifiedPane, handleMagnifyTerminalPane, handleTerminalPaneInteraction, handleRestoreMagnifiedPane, workspaceFocusHandlersRef, workspaceBroadcastHandlersRef, splitHorizontalHandlersRef, splitVerticalHandlersRef, resolveSessionAppearance, hostMap, keys, identities, snippets, knownHosts, terminalFontFamilyId, fontSize, terminalTheme, followAppTerminalTheme, accentMode, customAccent, terminalSettings, hotkeyScheme, disableTerminalFontZoom, restoreTerminalCwd, keyBindings, resizing, isComposeBarOpen, sessionLogConfig, sshDebugLogsEnabled, onHotkeyAction, handleTerminalFontSizeChange, handleOpenSftp, handleTerminalCwdChange, handleTerminalTitleChange, handleTerminalBell, handleTerminalOutput, handleTerminalContextReaderChange, handleOpenScripts, handleOpenHistory, handleOpenSystem, handleOpenTheme, handleCloseSession, handleStatusChange, handleSessionExit, handleTerminalDataCapture, handleOsDetected, handleUpdateHost, handleAddKnownHost, handleCommandExecuted, handleCommandSubmitted, onSetWorkspaceFocusedSession, onSplitSession, isBroadcastEnabled, handleBroadcastInput, handleBroadcastInterruptPriorityChange, handleToggleWorkspaceComposeBar, handleSnippetExecutorChange, handleProgrammaticCommandLogRewriteChange, handleAddSelectionToAI, activeResizers, composeBarThemeColors, findSplitNode, handleComposeSend, handleSnippetFromPanel, refocusTerminalSession, setIsComposeBarOpen, setResizing, TerminalComposeBar, Array, cn, onStartSessionRename, onRemoveSessionFromWorkspace, onReorderTabs, onStartSessionDrag, onEndSessionDrag, isGlobalBroadcastEnabled, canUseGlobalBroadcast, onToggleGlobalBroadcast, t, } = ctx; const activeMagnifiedTerminal = !!activeTabId && magnifiedPane?.tabId === activeTabId && magnifiedPane.target.kind === 'terminal' ? magnifiedPane : null; const paneMagnificationShortcutLabel = getPaneMagnificationShortcutLabel(keyBindings, hotkeyScheme); const [showMagnificationHint, setShowMagnificationHint] = useState(false); useEffect(() => { if (!activeMagnifiedTerminal) { setShowMagnificationHint(false); return undefined; } setShowMagnificationHint(true); const timerId = window.setTimeout(() => setShowMagnificationHint(false), 1800); return () => window.clearTimeout(timerId); }, [activeMagnifiedTerminal]); return (
{draggingSessionId && !isFocusMode && (
{ if (isFocusMode) return; if (!e.dataTransfer.types.includes('session-id')) return; e.preventDefault(); e.stopPropagation(); const hint = computeSplitHint(e); setDropHint(hint); }} onDragLeave={(e) => { if (!e.dataTransfer.types.includes('session-id')) return; setDropHint(null); }} onDrop={(e) => { e.preventDefault(); e.stopPropagation(); handleWorkspaceDrop(e); }} > {dropHint && (
)}
)} {!isFocusMode && activeResizers.map((handle: any) => { const isVertical = handle.direction === 'vertical'; const left = isVertical ? handle.rect.x - 3 : handle.rect.x; const top = isVertical ? handle.rect.y : handle.rect.y - 3; const width = isVertical ? handle.rect.w + 6 : handle.rect.w; const height = isVertical ? handle.rect.h : handle.rect.h + 6; return (
{ e.preventDefault(); e.stopPropagation(); const ws = activeWorkspace; if (!ws) return; const split = findSplitNode(ws.root, handle.splitId); const childCount = split && split.type === 'split' ? split.children.length : 0; const sizes = split && split.type === 'split' && split.sizes && split.sizes.length === childCount ? split.sizes : Array(childCount).fill(1); setResizing({ workspaceId: ws.id, splitId: handle.splitId, index: handle.index, direction: handle.direction, startSizes: sizes.length ? sizes : [1, 1], startArea: handle.splitArea, startClient: { x: e.clientX, y: e.clientY }, }); }} >
); })}
{activeMagnifiedTerminal && workspaceOuterRef.current && createPortal( <> ); } export const TerminalLayerWorkspaceSection = memo( TerminalLayerWorkspaceSectionInner, (prev, next) => terminalLayerWorkspaceCtxEqual(prev.ctx, next.ctx), ); TerminalLayerWorkspaceSection.displayName = 'TerminalLayerWorkspaceSection';