chore: File cleanup

This commit is contained in:
LukeGus
2025-11-15 02:40:28 -06:00
parent d425cee6e2
commit 20ef98957d
41 changed files with 88 additions and 412 deletions

View File

@@ -63,7 +63,6 @@ export function CredentialEditor({
useState(false);
const publicKeyDetectionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Clear error when tab changes
useEffect(() => {
setFormError(null);
}, [activeTab]);

View File

@@ -102,7 +102,7 @@ export function Dashboard({
const topMarginPx = isTopbarOpen ? 74 : 26;
const leftMarginPx = sidebarState === "collapsed" ? 26 : 8;
const rightMarginPx = 17; // Base margin when closed
const rightMarginPx = 17;
const bottomMarginPx = 8;
useEffect(() => {
@@ -213,7 +213,6 @@ export function Dashboard({
statsConfig?: string | { metricsEnabled?: boolean };
}) => {
try {
// Parse statsConfig if it's a string
let statsConfig: { metricsEnabled?: boolean } = {
metricsEnabled: true,
};
@@ -225,7 +224,6 @@ export function Dashboard({
}
}
// Skip if metrics are disabled
if (statsConfig.metricsEnabled === false) {
return null;
}

View File

@@ -1087,7 +1087,6 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
t("fileManager.archiveExtractedSuccessfully", { name: file.name }),
);
// Refresh directory to show extracted files
handleRefreshDirectory();
} catch (error: unknown) {
const err = error as { message?: string };
@@ -1132,7 +1131,6 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
}),
);
// Refresh directory to show compressed file
handleRefreshDirectory();
clearSelection();
} catch (error: unknown) {

View File

@@ -261,7 +261,6 @@ export function FileManagerContextMenu({
});
}
// Add extract option for archive files
if (isSingleFile && files[0].type === "file" && onExtractArchive) {
const fileName = files[0].name.toLowerCase();
const isArchive =
@@ -288,7 +287,6 @@ export function FileManagerContextMenu({
}
}
// Add compress option for selected files/folders
if (isFileContext && onCompress) {
menuItems.push({
icon: <FileArchive className="w-4 h-4" />,

View File

@@ -38,7 +38,6 @@ export function CompressDialog({
useEffect(() => {
if (open && fileNames.length > 0) {
// Generate default archive name
if (fileNames.length === 1) {
const baseName = fileNames[0].replace(/\.[^/.]+$/, "");
setArchiveName(baseName);
@@ -51,7 +50,6 @@ export function CompressDialog({
const handleCompress = () => {
if (!archiveName.trim()) return;
// Append extension if not already present
let finalName = archiveName.trim();
const extensions: Record<string, string> = {
zip: ".zip",

View File

@@ -30,7 +30,6 @@ interface PermissionsDialogProps {
onSave: (file: FileItem, permissions: string) => Promise<void>;
}
// Parse permissions like "rwxr-xr-x" or "755" to individual bits
const parsePermissions = (
perms: string,
): { owner: number; group: number; other: number } => {
@@ -38,7 +37,6 @@ const parsePermissions = (
return { owner: 0, group: 0, other: 0 };
}
// If numeric format like "755"
if (/^\d{3,4}$/.test(perms)) {
const numStr = perms.slice(-3);
return {
@@ -47,8 +45,6 @@ const parsePermissions = (
other: parseInt(numStr[2] || "0", 10),
};
}
// If symbolic format like "rwxr-xr-x" or "-rwxr-xr-x"
const cleanPerms = perms.replace(/^-/, "").substring(0, 9);
const calcBits = (str: string): number => {
@@ -66,7 +62,6 @@ const parsePermissions = (
};
};
// Convert individual bits to numeric format
const toNumeric = (owner: number, group: number, other: number): string => {
return `${owner}${group}${other}`;
};
@@ -99,7 +94,6 @@ export function PermissionsDialog({
(initialPerms.other & 1) !== 0,
);
// Reset when file changes
useEffect(() => {
if (file) {
const perms = parsePermissions(file.permissions || "644");

View File

@@ -72,7 +72,6 @@ export function HostManager({
};
const handleTabChange = (value: string) => {
// Only clear editing state when leaving the respective tabs, not when entering them
if (activeTab === "add_host" && value !== "add_host") {
setEditingHost(null);
}

View File

@@ -348,7 +348,6 @@ export function HostManagerEditor({
const [activeTab, setActiveTab] = useState("general");
const [formError, setFormError] = useState<string | null>(null);
// Clear error when tab changes
useEffect(() => {
setFormError(null);
}, [activeTab]);
@@ -948,7 +947,6 @@ export function HostManagerEditor({
window.dispatchEvent(new CustomEvent("ssh-hosts:changed"));
// Notify the stats server to start/update polling for this specific host
if (savedHost?.id) {
const { notifyHostCreatedOrUpdated } = await import(
"@/ui/main-axios.ts"
@@ -963,11 +961,9 @@ export function HostManagerEditor({
}
};
// Handle form validation errors
const handleFormError = () => {
const errors = form.formState.errors;
// Determine which tab contains the error
if (
errors.ip ||
errors.port ||
@@ -1088,7 +1084,6 @@ export function HostManagerEditor({
let filtered = sshConfigurations;
// Filter out the current host being edited (by ID, not by name)
if (currentHostId) {
const currentHostName = hosts.find((h) => h.id === currentHostId)?.name;
if (currentHostName) {
@@ -1097,7 +1092,6 @@ export function HostManagerEditor({
);
}
} else {
// If creating a new host, filter by the name being entered
const currentHostName =
form.watch("name") || `${form.watch("username")}@${form.watch("ip")}`;
filtered = sshConfigurations.filter(

View File

@@ -114,7 +114,6 @@ export function Server({
React.useEffect(() => {
if (hostConfig?.id !== currentHostConfig?.id) {
// Reset state when switching to a different host
setServerStatus("offline");
setMetrics(null);
setMetricsHistory([]);

View File

@@ -25,9 +25,7 @@ interface LoginStatsWidgetProps {
metricsHistory: ServerMetrics[];
}
export function LoginStatsWidget({
metrics,
}: LoginStatsWidgetProps) {
export function LoginStatsWidget({ metrics }: LoginStatsWidgetProps) {
const { t } = useTranslation();
const loginStats = metrics?.login_stats;
@@ -52,7 +50,9 @@ export function LoginStatsWidget({
<Activity className="h-3 w-3" />
<span>{t("serverStats.totalLogins")}</span>
</div>
<div className="text-xl font-bold text-green-400">{totalLogins}</div>
<div className="text-xl font-bold text-green-400">
{totalLogins}
</div>
</div>
<div className="bg-dark-bg-darker p-2 rounded border border-dark-border/30">
<div className="flex items-center gap-1 text-xs text-gray-400 mb-1">
@@ -86,7 +86,9 @@ export function LoginStatsWidget({
<span className="text-green-400 font-mono truncate">
{login.user}
</span>
<span className="text-gray-500">{t("serverStats.from")}</span>
<span className="text-gray-500">
{t("serverStats.from")}
</span>
<span className="text-blue-400 font-mono truncate">
{login.ip}
</span>
@@ -118,7 +120,9 @@ export function LoginStatsWidget({
<span className="text-red-400 font-mono truncate">
{login.user}
</span>
<span className="text-gray-500">{t("serverStats.from")}</span>
<span className="text-gray-500">
{t("serverStats.from")}
</span>
<span className="text-blue-400 font-mono truncate">
{login.ip}
</span>

View File

@@ -131,13 +131,11 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
const activityLoggedRef = useRef(false);
const keyHandlerAttachedRef = useRef(false);
// Command history tracking (Stage 1)
const { trackInput, getCurrentCommand, updateCurrentCommand } =
useCommandTracker({
hostId: hostConfig.id,
enabled: true,
onCommandExecuted: (command) => {
// Add to autocomplete history (Stage 3)
if (!autocompleteHistory.current.includes(command)) {
autocompleteHistory.current = [
command,
@@ -147,7 +145,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
},
});
// Create refs for callbacks to avoid triggering useEffect re-runs
const getCurrentCommandRef = useRef(getCurrentCommand);
const updateCurrentCommandRef = useRef(updateCurrentCommand);
@@ -156,7 +153,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
updateCurrentCommandRef.current = updateCurrentCommand;
}, [getCurrentCommand, updateCurrentCommand]);
// Real-time autocomplete (Stage 3)
const [showAutocomplete, setShowAutocomplete] = useState(false);
const [autocompleteSuggestions, setAutocompleteSuggestions] = useState<
string[]
@@ -170,23 +166,19 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
const autocompleteHistory = useRef<string[]>([]);
const currentAutocompleteCommand = useRef<string>("");
// Refs for accessing current state in event handlers
const showAutocompleteRef = useRef(false);
const autocompleteSuggestionsRef = useRef<string[]>([]);
const autocompleteSelectedIndexRef = useRef(0);
// Command history dialog (Stage 2)
const [showHistoryDialog, setShowHistoryDialog] = useState(false);
const [commandHistory, setCommandHistory] = useState<string[]>([]);
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
// Create refs for context methods to avoid infinite loops
const setIsLoadingRef = useRef(commandHistoryContext.setIsLoading);
const setCommandHistoryContextRef = useRef(
commandHistoryContext.setCommandHistory,
);
// Keep refs updated with latest context methods
useEffect(() => {
setIsLoadingRef.current = commandHistoryContext.setIsLoading;
setCommandHistoryContextRef.current =
@@ -196,7 +188,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
commandHistoryContext.setCommandHistory,
]);
// Load command history when dialog opens
useEffect(() => {
if (showHistoryDialog && hostConfig.id) {
setIsLoadingHistory(true);
@@ -219,9 +210,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
}
}, [showHistoryDialog, hostConfig.id]);
// Load command history for autocomplete on mount (Stage 3)
useEffect(() => {
// Check if command autocomplete is enabled
const autocompleteEnabled =
localStorage.getItem("commandAutocomplete") !== "false";
@@ -240,7 +229,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
}
}, [hostConfig.id]);
// Sync autocomplete state to refs for event handlers
useEffect(() => {
showAutocompleteRef.current = showAutocomplete;
}, [showAutocomplete]);
@@ -642,9 +630,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
}),
);
terminal.onData((data) => {
// Track command input for history (Stage 1)
trackInput(data);
// Send input to server
ws.send(JSON.stringify({ type: "input", data }));
});
@@ -904,20 +890,16 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
return "";
}
// Handle command selection from history dialog (Stage 2)
const handleSelectCommand = useCallback(
(command: string) => {
if (!terminal || !webSocketRef.current) return;
// Send the command to the terminal
// Simulate typing the command character by character
for (const char of command) {
webSocketRef.current.send(
JSON.stringify({ type: "input", data: char }),
);
}
// Return focus to terminal after selecting command
setTimeout(() => {
terminal.focus();
}, 100);
@@ -925,12 +907,10 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
[terminal],
);
// Register handlers with context
useEffect(() => {
commandHistoryContext.setOnSelectCommand(handleSelectCommand);
}, [handleSelectCommand]);
// Handle autocomplete selection (mouse click)
const handleAutocompleteSelect = useCallback(
(selectedCommand: string) => {
if (!webSocketRef.current) return;
@@ -938,22 +918,18 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
const currentCmd = currentAutocompleteCommand.current;
const completion = selectedCommand.substring(currentCmd.length);
// Send completion characters to server
for (const char of completion) {
webSocketRef.current.send(
JSON.stringify({ type: "input", data: char }),
);
}
// Update current command tracker
updateCurrentCommand(selectedCommand);
// Close autocomplete
setShowAutocomplete(false);
setAutocompleteSuggestions([]);
currentAutocompleteCommand.current = "";
// Return focus to terminal
setTimeout(() => {
terminal?.focus();
}, 50);
@@ -963,26 +939,22 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
[terminal, updateCurrentCommand],
);
// Handle command deletion from history dialog
const handleDeleteCommand = useCallback(
async (command: string) => {
if (!hostConfig.id) return;
try {
// Call API to delete command
const { deleteCommandFromHistory } = await import(
"@/ui/main-axios.ts"
);
await deleteCommandFromHistory(hostConfig.id, command);
// Update local state
setCommandHistory((prev) => {
const newHistory = prev.filter((cmd) => cmd !== command);
setCommandHistoryContextRef.current(newHistory);
return newHistory;
});
// Update autocomplete history
autocompleteHistory.current = autocompleteHistory.current.filter(
(cmd) => cmd !== command,
);
@@ -995,7 +967,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
[hostConfig.id],
);
// Register delete handler with context
useEffect(() => {
commandHistoryContext.setOnDeleteCommand(handleDeleteCommand);
}, [handleDeleteCommand]);
@@ -1104,7 +1075,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
navigator.platform.toUpperCase().indexOf("MAC") >= 0 ||
navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
// Handle Ctrl+R for command history (Stage 2)
if (
e.ctrlKey &&
e.key === "r" &&
@@ -1115,7 +1085,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
e.preventDefault();
e.stopPropagation();
setShowHistoryDialog(true);
// Also trigger the sidebar to open
if (commandHistoryContext.openCommandHistory) {
commandHistoryContext.openCommandHistory();
}
@@ -1210,20 +1179,15 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
};
}, [xtermRef, terminal, hostConfig]);
// Register keyboard handler for autocomplete (Stage 3)
// Registered only once when terminal is created
useEffect(() => {
if (!terminal) return;
const handleCustomKey = (e: KeyboardEvent): boolean => {
// Only handle keydown events, ignore keyup to prevent double triggering
if (e.type !== "keydown") {
return true;
}
// If autocomplete is showing, handle keys specially
if (showAutocompleteRef.current) {
// Handle Escape to close autocomplete
if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
@@ -1233,7 +1197,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
return false;
}
// Handle Arrow keys for autocomplete navigation
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
e.preventDefault();
e.stopPropagation();
@@ -1253,7 +1216,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
return false;
}
// Handle Enter to confirm autocomplete selection
if (
e.key === "Enter" &&
autocompleteSuggestionsRef.current.length > 0
@@ -1268,7 +1230,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
const currentCmd = currentAutocompleteCommand.current;
const completion = selectedCommand.substring(currentCmd.length);
// Send completion characters to server
if (webSocketRef.current?.readyState === 1) {
for (const char of completion) {
webSocketRef.current.send(
@@ -1277,10 +1238,8 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
}
}
// Update current command tracker
updateCurrentCommandRef.current(selectedCommand);
// Close autocomplete
setShowAutocomplete(false);
setAutocompleteSuggestions([]);
currentAutocompleteCommand.current = "";
@@ -1288,7 +1247,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
return false;
}
// Handle Tab to cycle through suggestions
if (
e.key === "Tab" &&
!e.ctrlKey &&
@@ -1306,14 +1264,12 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
return false;
}
// For any other key while autocomplete is showing, close it and let key through
setShowAutocomplete(false);
setAutocompleteSuggestions([]);
currentAutocompleteCommand.current = "";
return true;
}
// Handle Tab for autocomplete (when autocomplete is not showing)
if (
e.key === "Tab" &&
!e.ctrlKey &&
@@ -1324,12 +1280,10 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
e.preventDefault();
e.stopPropagation();
// Check if command autocomplete is enabled in settings
const autocompleteEnabled =
localStorage.getItem("commandAutocomplete") !== "false";
if (!autocompleteEnabled) {
// If disabled, let the terminal handle Tab normally (send to server)
if (webSocketRef.current?.readyState === 1) {
webSocketRef.current.send(
JSON.stringify({ type: "input", data: "\t" }),
@@ -1340,7 +1294,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
const currentCmd = getCurrentCommandRef.current().trim();
if (currentCmd.length > 0 && webSocketRef.current?.readyState === 1) {
// Filter commands that start with current input
const matches = autocompleteHistory.current
.filter(
(cmd) =>
@@ -1348,10 +1301,9 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
cmd !== currentCmd &&
cmd.length > currentCmd.length,
)
.slice(0, 5); // Show up to 5 matches for better UX
.slice(0, 5);
if (matches.length === 1) {
// Only one match - auto-complete directly
const completedCommand = matches[0];
const completion = completedCommand.substring(currentCmd.length);
@@ -1363,12 +1315,10 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
updateCurrentCommandRef.current(completedCommand);
} else if (matches.length > 1) {
// Multiple matches - show selection list
currentAutocompleteCommand.current = currentCmd;
setAutocompleteSuggestions(matches);
setAutocompleteSelectedIndex(0);
// Calculate position (below or above cursor based on available space)
const cursorY = terminal.buffer.active.cursorY;
const cursorX = terminal.buffer.active.cursorX;
const rect = xtermRef.current?.getBoundingClientRect();
@@ -1379,8 +1329,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
const cellWidth =
terminal.cols > 0 ? rect.width / terminal.cols : 10;
// Calculate actual menu height based on number of items
// Each item is ~32px (py-1.5), footer is ~32px, max total 240px
const itemHeight = 32;
const footerHeight = 32;
const maxMenuHeight = 240;
@@ -1388,14 +1336,11 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
matches.length * itemHeight + footerHeight,
maxMenuHeight,
);
// Get cursor position in viewport coordinates
const cursorBottomY = rect.top + (cursorY + 1) * cellHeight;
const cursorTopY = rect.top + cursorY * cellHeight;
const spaceBelow = window.innerHeight - cursorBottomY;
const spaceAbove = cursorTopY;
// Show above cursor if not enough space below and more space above
const showAbove =
spaceBelow < estimatedMenuHeight && spaceAbove > spaceBelow;
@@ -1410,10 +1355,9 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
setShowAutocomplete(true);
}
}
return false; // Prevent default Tab behavior
return false;
}
// Let terminal handle all other keys
return true;
};
@@ -1470,7 +1414,6 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
return;
}
// Don't set isFitted to false - keep terminal visible during resize
let rafId1: number;
let rafId2: number;

View File

@@ -19,7 +19,6 @@ export function CommandAutocomplete({
const containerRef = useRef<HTMLDivElement>(null);
const selectedRef = useRef<HTMLDivElement>(null);
// Scroll selected item into view
useEffect(() => {
if (selectedRef.current && containerRef.current) {
selectedRef.current.scrollIntoView({
@@ -33,8 +32,6 @@ export function CommandAutocomplete({
return null;
}
// Calculate max height for suggestions list to ensure footer is always visible
// Footer height is approximately 32px (text + padding + border)
const footerHeight = 32;
const maxSuggestionsHeight = 240 - footerHeight;
@@ -62,9 +59,7 @@ export function CommandAutocomplete({
index === selectedIndex && "bg-gray-500/20 text-gray-400",
)}
onClick={() => onSelect(suggestion)}
onMouseEnter={() => {
// Optional: update selected index on hover
}}
onMouseEnter={() => {}}
>
{suggestion}
</div>

View File

@@ -105,14 +105,12 @@ export function SSHToolsSidebar({
};
const [activeTab, setActiveTab] = useState(initialTab || "ssh-tools");
// Update active tab when initialTab changes
useEffect(() => {
if (initialTab && isOpen) {
setActiveTab(initialTab);
}
}, [initialTab, isOpen]);
// Call onTabChange when active tab changes
const handleTabChange = (tab: string) => {
setActiveTab(tab);
if (onTabChange) {
@@ -120,14 +118,12 @@ export function SSHToolsSidebar({
}
};
// SSH Tools state
const [isRecording, setIsRecording] = useState(false);
const [selectedTabIds, setSelectedTabIds] = useState<number[]>([]);
const [rightClickCopyPaste, setRightClickCopyPaste] = useState<boolean>(
() => getCookie("rightClickCopyPaste") === "true",
);
// Snippets state
const [snippets, setSnippets] = useState<Snippet[]>([]);
const [loading, setLoading] = useState(true);
const [showDialog, setShowDialog] = useState(false);
@@ -145,14 +141,12 @@ export function SSHToolsSidebar({
[],
);
// Command History state
const [commandHistory, setCommandHistory] = useState<string[]>([]);
const [isHistoryLoading, setIsHistoryLoading] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [historyRefreshCounter, setHistoryRefreshCounter] = useState(0);
const commandHistoryScrollRef = React.useRef<HTMLDivElement>(null);
// Split Screen state
const [splitMode, setSplitMode] = useState<"none" | "2" | "3" | "4">("none");
const [splitAssignments, setSplitAssignments] = useState<Map<number, number>>(
new Map(),
@@ -163,7 +157,6 @@ export function SSHToolsSidebar({
null,
);
// Resize state
const [isResizing, setIsResizing] = useState(false);
const startXRef = React.useRef<number | null>(null);
const startWidthRef = React.useRef<number>(sidebarWidth);
@@ -174,7 +167,6 @@ export function SSHToolsSidebar({
activeUiTab?.type === "terminal" ? activeUiTab : undefined;
const activeTerminalHostId = activeTerminal?.hostConfig?.id;
// Get splittable tabs (terminal, server, file_manager)
const splittableTabs = tabs.filter(
(tab: TabData) =>
tab.type === "terminal" ||
@@ -183,20 +175,16 @@ export function SSHToolsSidebar({
tab.type === "user_profile",
);
// Fetch command history
useEffect(() => {
if (isOpen && activeTab === "command-history") {
if (activeTerminalHostId) {
// Save current scroll position before any state updates
const scrollTop = commandHistoryScrollRef.current?.scrollTop || 0;
getCommandHistory(activeTerminalHostId)
.then((history) => {
setCommandHistory((prevHistory) => {
const newHistory = Array.isArray(history) ? history : [];
// Only update if history actually changed
if (JSON.stringify(prevHistory) !== JSON.stringify(newHistory)) {
// Use requestAnimationFrame to restore scroll after React finishes rendering
requestAnimationFrame(() => {
if (commandHistoryScrollRef.current) {
commandHistoryScrollRef.current.scrollTop = scrollTop;
@@ -223,7 +211,6 @@ export function SSHToolsSidebar({
historyRefreshCounter,
]);
// Auto-refresh command history every 2 seconds when history tab is active
useEffect(() => {
if (isOpen && activeTab === "command-history" && activeTerminalHostId) {
const refreshInterval = setInterval(() => {
@@ -234,14 +221,12 @@ export function SSHToolsSidebar({
}
}, [isOpen, activeTab, activeTerminalHostId]);
// Filter command history based on search query
const filteredCommands = searchQuery
? commandHistory.filter((cmd) =>
cmd.toLowerCase().includes(searchQuery.toLowerCase()),
)
: commandHistory;
// Initialize CSS variable on mount and when sidebar width changes
useEffect(() => {
document.documentElement.style.setProperty(
"--right-sidebar-width",
@@ -249,7 +234,6 @@ export function SSHToolsSidebar({
);
}, [sidebarWidth]);
// Handle window resize to adjust sidebar width
useEffect(() => {
const handleResize = () => {
const minWidth = Math.min(300, Math.floor(window.innerWidth * 0.2));
@@ -270,7 +254,6 @@ export function SSHToolsSidebar({
}
}, [isOpen, activeTab]);
// Resize handlers
const handleMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
setIsResizing(true);
@@ -283,7 +266,7 @@ export function SSHToolsSidebar({
const handleMouseMove = (e: MouseEvent) => {
if (startXRef.current == null) return;
const dx = startXRef.current - e.clientX; // Reversed because we're on the right
const dx = startXRef.current - e.clientX;
const newWidth = Math.round(startWidthRef.current + dx);
const minWidth = Math.min(300, Math.floor(window.innerWidth * 0.2));
const maxWidth = Math.round(window.innerWidth * 0.3);
@@ -295,13 +278,11 @@ export function SSHToolsSidebar({
finalWidth = maxWidth;
}
// Update CSS variable immediately for smooth animation
document.documentElement.style.setProperty(
"--right-sidebar-width",
`${finalWidth}px`,
);
// Update React state (this will be batched/debounced naturally)
setSidebarWidth(finalWidth);
};
@@ -323,7 +304,6 @@ export function SSHToolsSidebar({
};
}, [isResizing]);
// SSH Tools handlers
const handleTabToggle = (tabId: number) => {
setSelectedTabIds((prev) =>
prev.includes(tabId)
@@ -487,7 +467,6 @@ export function SSHToolsSidebar({
setRightClickCopyPaste(checked);
};
// Snippets handlers
const fetchSnippets = async () => {
try {
setLoading(true);
@@ -599,15 +578,12 @@ export function SSHToolsSidebar({
toast.success(t("snippets.copySuccess", { name: snippet.name }));
};
// Split Screen handlers
const handleSplitModeChange = (mode: "none" | "2" | "3" | "4") => {
setSplitMode(mode);
if (mode === "none") {
// Clear all splits
handleClearSplit();
} else {
// Clear assignments when changing modes
setSplitAssignments(new Map());
setPreviewKey((prev) => prev + 1);
}
@@ -636,7 +612,6 @@ export function SSHToolsSidebar({
setSplitAssignments((prev) => {
const newMap = new Map(prev);
// Remove this tab from any other cell
Array.from(newMap.entries()).forEach(([idx, id]) => {
if (id === draggedTabId && idx !== cellIndex) {
newMap.delete(idx);
@@ -677,7 +652,6 @@ export function SSHToolsSidebar({
const requiredSlots = parseInt(splitMode);
// Validate: All layout spots must be filled
if (splitAssignments.size < requiredSlots) {
toast.error(
t("splitScreen.error.fillAllSlots", {
@@ -688,7 +662,6 @@ export function SSHToolsSidebar({
return;
}
// Build ordered array of tab IDs based on cell index (0, 1, 2, 3)
const orderedTabIds: number[] = [];
for (let i = 0; i < requiredSlots; i++) {
const tabId = splitAssignments.get(i);
@@ -697,18 +670,15 @@ export function SSHToolsSidebar({
}
}
// First, clear ALL existing splits
const currentSplits = [...allSplitScreenTab];
currentSplits.forEach((tabId) => {
setSplitScreenTab(tabId); // Toggle off
setSplitScreenTab(tabId);
});
// Then, add only the newly assigned tabs to split IN ORDER
orderedTabIds.forEach((tabId) => {
setSplitScreenTab(tabId); // Toggle on
setSplitScreenTab(tabId);
});
// Set first assigned tab as active if current tab is not in split
if (!orderedTabIds.includes(currentTab ?? 0)) {
setCurrentTab(orderedTabIds[0]);
}
@@ -721,7 +691,6 @@ export function SSHToolsSidebar({
};
const handleClearSplit = () => {
// Remove all tabs from split screen
allSplitScreenTab.forEach((tabId) => {
setSplitScreenTab(tabId);
});
@@ -741,7 +710,6 @@ export function SSHToolsSidebar({
handleClearSplit();
};
// Command History handlers
const handleCommandSelect = (command: string) => {
if (activeTerminal?.terminalRef?.current?.sendInput) {
activeTerminal.terminalRef.current.sendInput(command);