From 26b71c0b693cd9a807a0f52c91f1ecfb919cd3ff Mon Sep 17 00:00:00 2001 From: LukeGus Date: Tue, 11 Nov 2025 09:25:32 -0600 Subject: [PATCH] fix: Command history and file manager styling issues --- .../file-manager/FileManagerContextMenu.tsx | 2 - src/ui/desktop/apps/terminal/Terminal.tsx | 34 +++-- src/ui/desktop/apps/tools/SSHToolsSidebar.tsx | 126 +++++++++++------- 3 files changed, 100 insertions(+), 62 deletions(-) diff --git a/src/ui/desktop/apps/file-manager/FileManagerContextMenu.tsx b/src/ui/desktop/apps/file-manager/FileManagerContextMenu.tsx index c1f1ab3f..6627677b 100644 --- a/src/ui/desktop/apps/file-manager/FileManagerContextMenu.tsx +++ b/src/ui/desktop/apps/file-manager/FileManagerContextMenu.tsx @@ -503,8 +503,6 @@ export function FileManagerContextMenu({ data-context-menu className={cn( "fixed bg-dark-bg border border-dark-border rounded-lg shadow-xl min-w-[180px] max-w-[250px] z-[99995] overflow-hidden", - "transition-all duration-150 ease-out origin-top-left", - isMounted ? "opacity-100 scale-100" : "opacity-0 scale-95", )} style={{ left: menuPosition.x, diff --git a/src/ui/desktop/apps/terminal/Terminal.tsx b/src/ui/desktop/apps/terminal/Terminal.tsx index 90f32ab8..20a7af98 100644 --- a/src/ui/desktop/apps/terminal/Terminal.tsx +++ b/src/ui/desktop/apps/terminal/Terminal.tsx @@ -180,28 +180,44 @@ export const Terminal = forwardRef( const [commandHistory, setCommandHistory] = useState([]); 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 = + commandHistoryContext.setCommandHistory; + }, [ + commandHistoryContext.setIsLoading, + commandHistoryContext.setCommandHistory, + ]); + // Load command history when dialog opens useEffect(() => { if (showHistoryDialog && hostConfig.id) { setIsLoadingHistory(true); - commandHistoryContext.setIsLoading(true); + setIsLoadingRef.current(true); import("@/ui/main-axios.ts") .then((module) => module.getCommandHistory(hostConfig.id!)) .then((history) => { setCommandHistory(history); - commandHistoryContext.setCommandHistory(history); + setCommandHistoryContextRef.current(history); }) .catch((error) => { console.error("Failed to load command history:", error); setCommandHistory([]); - commandHistoryContext.setCommandHistory([]); + setCommandHistoryContextRef.current([]); }) .finally(() => { setIsLoadingHistory(false); - commandHistoryContext.setIsLoading(false); + setIsLoadingRef.current(false); }); } - }, [showHistoryDialog, hostConfig.id, commandHistoryContext]); + }, [showHistoryDialog, hostConfig.id]); // Load command history for autocomplete on mount (Stage 3) useEffect(() => { @@ -906,7 +922,7 @@ export const Terminal = forwardRef( // Register handlers with context useEffect(() => { commandHistoryContext.setOnSelectCommand(handleSelectCommand); - }, [handleSelectCommand, commandHistoryContext]); + }, [handleSelectCommand]); // Handle autocomplete selection (mouse click) const handleAutocompleteSelect = useCallback( @@ -956,7 +972,7 @@ export const Terminal = forwardRef( // Update local state setCommandHistory((prev) => { const newHistory = prev.filter((cmd) => cmd !== command); - commandHistoryContext.setCommandHistory(newHistory); + setCommandHistoryContextRef.current(newHistory); return newHistory; }); @@ -970,13 +986,13 @@ export const Terminal = forwardRef( console.error("Failed to delete command from history:", error); } }, - [hostConfig.id, commandHistoryContext], + [hostConfig.id], ); // Register delete handler with context useEffect(() => { commandHistoryContext.setOnDeleteCommand(handleDeleteCommand); - }, [handleDeleteCommand, commandHistoryContext]); + }, [handleDeleteCommand]); useEffect(() => { if (!terminal || !xtermRef.current) return; diff --git a/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx b/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx index 4b9b505c..5e611e8a 100644 --- a/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx +++ b/src/ui/desktop/apps/tools/SSHToolsSidebar.tsx @@ -138,6 +138,8 @@ export function SSHToolsSidebar({ const [commandHistory, setCommandHistory] = useState([]); const [isHistoryLoading, setIsHistoryLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); + const [historyRefreshCounter, setHistoryRefreshCounter] = useState(0); + const commandHistoryScrollRef = React.useRef(null); // Resize state const [isResizing, setIsResizing] = useState(false); @@ -150,25 +152,54 @@ export function SSHToolsSidebar({ activeUiTab?.type === "terminal" ? activeUiTab : undefined; const activeTerminalHostId = activeTerminal?.hostConfig?.id; + // Fetch command history useEffect(() => { if (isOpen && activeTab === "command-history") { if (activeTerminalHostId) { - setIsHistoryLoading(true); + // Save current scroll position before any state updates + const scrollTop = commandHistoryScrollRef.current?.scrollTop || 0; + getCommandHistory(activeTerminalHostId) .then((history) => { - setCommandHistory(history); + setCommandHistory((prevHistory) => { + // Only update if history actually changed + if (JSON.stringify(prevHistory) !== JSON.stringify(history)) { + // Use requestAnimationFrame to restore scroll after React finishes rendering + requestAnimationFrame(() => { + if (commandHistoryScrollRef.current) { + commandHistoryScrollRef.current.scrollTop = scrollTop; + } + }); + return history; + } + return prevHistory; + }); }) .catch((err) => { console.error("Failed to fetch command history", err); setCommandHistory([]); - }) - .finally(() => { - setIsHistoryLoading(false); }); } else { setCommandHistory([]); } } + }, [ + isOpen, + activeTab, + activeTerminalHostId, + currentTab, + historyRefreshCounter, + ]); + + // Auto-refresh command history every 2 seconds when history tab is active + useEffect(() => { + if (isOpen && activeTab === "command-history" && activeTerminalHostId) { + const refreshInterval = setInterval(() => { + setHistoryRefreshCounter((prev) => prev + 1); + }, 2000); + + return () => clearInterval(refreshInterval); + } }, [isOpen, activeTab, activeTerminalHostId]); // Filter command history based on search query @@ -543,32 +574,23 @@ export function SSHToolsSidebar({ } }; - const handleCommandDelete = (command: string) => { + const handleCommandDelete = async (command: string) => { if (activeTerminalHostId) { - confirmWithToast( - t("commandHistory.deleteConfirmDescription", { - defaultValue: `Delete "${command}" from history?`, - command, - }), - async () => { - try { - await deleteCommandFromHistory(activeTerminalHostId, command); - setCommandHistory((prev) => prev.filter((c) => c !== command)); - toast.success( - t("commandHistory.deleteSuccess", { - defaultValue: "Command deleted from history", - }), - ); - } catch { - toast.error( - t("commandHistory.deleteFailed", { - defaultValue: "Failed to delete command.", - }), - ); - } - }, - "destructive", - ); + try { + await deleteCommandFromHistory(activeTerminalHostId, command); + setCommandHistory((prev) => prev.filter((c) => c !== command)); + toast.success( + t("commandHistory.deleteSuccess", { + defaultValue: "Command deleted from history", + }), + ); + } catch { + toast.error( + t("commandHistory.deleteFailed", { + defaultValue: "Failed to delete command.", + }), + ); + } } }; @@ -612,9 +634,13 @@ export function SSHToolsSidebar({ - - - + + + {t("sshTools.title")} @@ -896,8 +922,11 @@ export function SSHToolsSidebar({ )} - -
+ +
-
- {isHistoryLoading ? ( -
- - - {t("commandHistory.loading", { - defaultValue: "Loading history...", - })} - -
- ) : !activeTerminal ? ( +
+ {!activeTerminal ? (

@@ -982,23 +1002,27 @@ export function SSHToolsSidebar({ )}

) : ( -
+
{filteredCommands.map((command, index) => (
-
+
handleCommandSelect(command)} + title={command} > {command}