From b0523f995ca00617865107b38e310c7defb91537 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Mon, 10 Nov 2025 01:11:14 -0600 Subject: [PATCH] fix: Finalize new sidebar, improve and loading animations --- src/ui/components/LoadingOverlay.tsx | 1739 ----------------- src/ui/desktop/admin/AdminSettings.tsx | 6 +- src/ui/desktop/apps/dashboard/Dashboard.tsx | 8 +- .../apps/file-manager/FileManagerGrid.tsx | 13 +- .../desktop/apps/host-manager/HostManager.tsx | 6 +- src/ui/desktop/apps/server/Server.tsx | 6 +- src/ui/desktop/apps/terminal/Terminal.tsx | 7 +- .../desktop/apps/tools/SSHUtilitySidebar.tsx | 86 +- src/ui/desktop/navigation/AppView.tsx | 6 +- src/ui/desktop/navigation/TopNavbar.tsx | 5 +- .../navigation/animations/SimpleLoader.tsx | 61 + src/ui/desktop/user/UserProfile.tsx | 6 +- src/ui/mobile/apps/terminal/Terminal.tsx | 2 +- 13 files changed, 139 insertions(+), 1812 deletions(-) delete mode 100644 src/ui/components/LoadingOverlay.tsx create mode 100644 src/ui/desktop/navigation/animations/SimpleLoader.tsx diff --git a/src/ui/components/LoadingOverlay.tsx b/src/ui/components/LoadingOverlay.tsx deleted file mode 100644 index d6869c8d..00000000 --- a/src/ui/components/LoadingOverlay.tsx +++ /dev/null @@ -1,1739 +0,0 @@ -import React, { useEffect, useState, useRef } from "react"; -import { cn } from "@/lib/utils"; - -interface LoadingOverlayProps { - visible: boolean; - minDuration?: number; // Minimum display duration in milliseconds - message?: string; - showLogo?: boolean; - className?: string; - backgroundColor?: string; -} - -export function LoadingOverlay({ - visible, - minDuration = 800, - message, - showLogo = true, - className, - backgroundColor, -}: LoadingOverlayProps) { - const [isShowing, setIsShowing] = useState(false); - const [isFadingOut, setIsFadingOut] = useState(false); - const [animationType, setAnimationType] = useState< - "glitch" | "breathe" | "typewriter" | "scanner" | "pulse" - >("glitch"); - const showStartTimeRef = useRef(null); - const minDurationTimerRef = useRef(null); - - useEffect(() => { - if (visible) { - // Randomly choose animation type from 5 options - const animations: ( - | "glitch" - | "breathe" - | "typewriter" - | "scanner" - | "pulse" - )[] = ["glitch", "breathe", "typewriter", "scanner", "pulse"]; - const randomIndex = Math.floor(Math.random() * 5); - setAnimationType(animations[randomIndex]); - - // Start showing immediately - setIsShowing(true); - setIsFadingOut(false); - showStartTimeRef.current = Date.now(); - - // Clear any existing timer - if (minDurationTimerRef.current) { - clearTimeout(minDurationTimerRef.current); - minDurationTimerRef.current = null; - } - } else if (isShowing) { - // Calculate how long it has been showing - const elapsed = showStartTimeRef.current - ? Date.now() - showStartTimeRef.current - : 0; - const remaining = Math.max(0, minDuration - elapsed); - - if (remaining > 0) { - // Wait for minimum duration before hiding - minDurationTimerRef.current = setTimeout(() => { - setIsFadingOut(true); - // Wait for fade-out animation to complete - setTimeout(() => { - setIsShowing(false); - setIsFadingOut(false); - showStartTimeRef.current = null; - }, 300); // Match fade-out duration - }, remaining); - } else { - // Minimum duration already passed, hide immediately - setIsFadingOut(true); - setTimeout(() => { - setIsShowing(false); - setIsFadingOut(false); - showStartTimeRef.current = null; - }, 300); - } - } - - return () => { - if (minDurationTimerRef.current) { - clearTimeout(minDurationTimerRef.current); - minDurationTimerRef.current = null; - } - }; - }, [visible, isShowing, minDuration]); - - if (!isShowing) { - return null; - } - - return ( - <> - - -
- {animationType === "glitch" ? ( - <> - {/* Fullscreen Glitch Background */} -
- {/* RGB Split Layers */} -
-
-
-
-
- - {/* Signal Distortion Bars */} -
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
- - {/* Original Effects */} -
-
-
- -
-
- {/* TERMIX Glitch Text */} -
- TERMIX -
- - {/* Scan line effect */} -
-
- - {message && ( -
-

- {message} -

-
- )} -
- - ) : animationType === "breathe" ? ( - <> - {/* Fullscreen Elegant Background */} -
- {/* Floating Particles Field */} -
- {Array.from({ length: 12 }).map((_, i) => ( -
- ))} -
-
- -
-
- {/* Pulse rings */} -
-
-
- - {/* Orbiting dots */} -
-
-
-
-
-
- - {/* Particles */} -
-
-
-
-
-
-
-
-
-
- - {/* TERMIX Breathe Text */} -
- T - E - R - M - I - X -
-
- - {message && ( -
-

- {message} -

-
- )} -
- - ) : animationType === "typewriter" ? ( - <> - {/* Fullscreen Retro Terminal Background */} -
- {/* ASCII Character Rain */} -
- {Array.from({ length: 9 }).map((_, i) => ( -
- {`$\n>\n_\n{\n}\n[\n]\n|\n/\n\\\n-\n+\n*\n#\n@\n%`} -
- ))} -
- - {/* CRT Scanline */} -
- - {/* Cursor Trails */} -
- {Array.from({ length: 5 }).map((_, i) => ( -
- ))} -
-
- -
-
- {/* TERMIX Typewriter Text */} -
- T - E - R - M - I - X - -
-
- - {message && ( -
-

- {message} -

-
- )} -
- - ) : animationType === "scanner" ? ( - <> - {/* Fullscreen Matrix Background */} -
- {/* Grid Background */} -
- - {/* Matrix Digital Rain */} -
- {Array.from({ length: 10 }).map((_, i) => ( -
- {`01\n10\n11\n00\n01\n10\n11\n00\n01\n10\n11\n00\n01\n10\n11\n00\n01\n10\n11\n00`} -
- ))} -
- - {/* Random Code Fragments */} -
-
- {"{"} ssh: 22 {"}"} -
-
- {"<"} connect... {">"} -
-
0x4A3F2B1D
-
[SCANNING...]
-
{">"} _
-
- - {/* Powerful Scan Beam */} -
-
- -
-
- {/* TERMIX Scanner Text */} -
- TERMIX -
-
- - {message && ( -
-

- {message} -

-
- )} -
- - ) : ( - <> - {/* Fullscreen Radar/Sonar Background */} -
- {/* Radar Circular Grid */} -
- {Array.from({ length: 5 }).map((_, i) => ( -
- ))} -
- - {/* Radar Cross Lines */} -
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
- - {/* Sonar Pulse Waves */} -
- {Array.from({ length: 5 }).map((_, i) => ( -
- ))} -
- - {/* Radar Targets (Detection Points) */} -
- {Array.from({ length: 8 }).map((_, i) => ( -
- ))} -
-
- -
-
- {/* Wave Rings */} -
-
-
-
-
- - {/* Radar Sweep */} -
- - {/* Center Dot */} -
- - {/* TERMIX Pulse Text */} -
- TERMIX -
-
- - {message && ( -
-

- {message} -

-
- )} -
- - )} -
- - ); -} diff --git a/src/ui/desktop/admin/AdminSettings.tsx b/src/ui/desktop/admin/AdminSettings.tsx index a25cabc5..56d3ef47 100644 --- a/src/ui/desktop/admin/AdminSettings.tsx +++ b/src/ui/desktop/admin/AdminSettings.tsx @@ -641,10 +641,14 @@ export function AdminSettings({ const bottomMarginPx = 8; const wrapperStyle: React.CSSProperties = { marginLeft: leftMarginPx, - marginRight: rightSidebarOpen ? rightSidebarWidth + 17 : 17, + marginRight: rightSidebarOpen + ? `calc(var(--right-sidebar-width, ${rightSidebarWidth}px) + 8px)` + : 17, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, + transition: + "margin-left 200ms linear, margin-right 200ms linear, margin-top 200ms linear", }; return ( diff --git a/src/ui/desktop/apps/dashboard/Dashboard.tsx b/src/ui/desktop/apps/dashboard/Dashboard.tsx index 249dd98c..f1683142 100644 --- a/src/ui/desktop/apps/dashboard/Dashboard.tsx +++ b/src/ui/desktop/apps/dashboard/Dashboard.tsx @@ -101,7 +101,7 @@ export function Dashboard({ const topMarginPx = isTopbarOpen ? 74 : 26; const leftMarginPx = sidebarState === "collapsed" ? 26 : 8; - const rightMarginPx = rightSidebarOpen ? rightSidebarWidth + 17 : 17; + const rightMarginPx = 17; // Base margin when closed const bottomMarginPx = 8; useEffect(() => { @@ -341,10 +341,14 @@ export function Dashboard({ className="bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden flex" style={{ marginLeft: leftMarginPx, - marginRight: rightMarginPx, + marginRight: rightSidebarOpen + ? `calc(var(--right-sidebar-width, ${rightSidebarWidth}px) + 8px)` + : rightMarginPx, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, + transition: + "margin-left 200ms linear, margin-right 200ms linear, margin-top 200ms linear", }} >
diff --git a/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx b/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx index c49c80b3..0a22697b 100644 --- a/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx +++ b/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx @@ -24,7 +24,7 @@ import { } from "lucide-react"; import { useTranslation } from "react-i18next"; import type { FileItem } from "../../../types/index.js"; -import { LoadingOverlay } from "@/ui/components/LoadingOverlay"; +import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx"; interface CreateIntent { id: string; @@ -1004,7 +1004,7 @@ export function FileManagerGrid({ tabIndex={0} > {dragState.type === "external" && ( -
+

@@ -1057,7 +1057,6 @@ export function FileManagerGrid({ draggable={true} className={cn( "group p-3 rounded-lg cursor-pointer", - "transition-all duration-150 ease-out", "hover:bg-accent hover:text-accent-foreground hover:scale-[1.02] border-2 border-transparent", isSelected && "bg-primary/20 border-primary ring-2 ring-primary/20", @@ -1148,7 +1147,6 @@ export function FileManagerGrid({ draggable={true} className={cn( "flex items-center gap-3 p-2 rounded cursor-pointer", - "transition-all duration-150 ease-out", "hover:bg-accent hover:text-accent-foreground", isSelected && "bg-primary/20 ring-2 ring-primary/20", dragState.target?.path === file.path && @@ -1322,12 +1320,7 @@ export function FileManagerGrid({ document.body, )} - +

); } diff --git a/src/ui/desktop/apps/host-manager/HostManager.tsx b/src/ui/desktop/apps/host-manager/HostManager.tsx index 31b4af93..01a4ec9c 100644 --- a/src/ui/desktop/apps/host-manager/HostManager.tsx +++ b/src/ui/desktop/apps/host-manager/HostManager.tsx @@ -92,10 +92,14 @@ export function HostManager({ className="bg-dark-bg text-white p-4 pt-0 rounded-lg border-2 border-dark-border flex flex-col min-h-0 overflow-hidden" style={{ marginLeft: leftMarginPx, - marginRight: rightSidebarOpen ? rightSidebarWidth + 17 : 17, + marginRight: rightSidebarOpen + ? `calc(var(--right-sidebar-width, ${rightSidebarWidth}px) + 8px)` + : 17, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, + transition: + "margin-left 200ms linear, margin-right 200ms linear, margin-top 200ms linear", }} > )} -
)} diff --git a/src/ui/desktop/apps/terminal/Terminal.tsx b/src/ui/desktop/apps/terminal/Terminal.tsx index 1aba49dd..75626438 100644 --- a/src/ui/desktop/apps/terminal/Terminal.tsx +++ b/src/ui/desktop/apps/terminal/Terminal.tsx @@ -31,7 +31,7 @@ import { useCommandTracker } from "@/ui/hooks/useCommandTracker"; import { useCommandHistory } from "@/ui/hooks/useCommandHistory"; import { CommandHistoryDialog } from "./CommandHistoryDialog"; import { CommandAutocomplete } from "./CommandAutocomplete"; -import { LoadingOverlay } from "@/ui/components/LoadingOverlay"; +import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx"; interface HostConfig { id?: number; @@ -1446,7 +1446,6 @@ export const Terminal = forwardRef( style={{ visibility: isReady && !isConnecting && isFitted ? "visible" : "hidden", - opacity: isReady && !isConnecting && isFitted ? 1 : 0, }} onClick={() => { if (terminal && !splitScreen) { @@ -1494,12 +1493,10 @@ export const Terminal = forwardRef( onSelect={handleAutocompleteSelect} /> -
); diff --git a/src/ui/desktop/apps/tools/SSHUtilitySidebar.tsx b/src/ui/desktop/apps/tools/SSHUtilitySidebar.tsx index 1665976f..08d78f56 100644 --- a/src/ui/desktop/apps/tools/SSHUtilitySidebar.tsx +++ b/src/ui/desktop/apps/tools/SSHUtilitySidebar.tsx @@ -23,16 +23,7 @@ import { SidebarProvider, SidebarGroupLabel, } from "@/components/ui/sidebar.tsx"; -import { - Plus, - Play, - Edit, - Trash2, - Copy, - X, - RotateCcw, - ChevronRight, -} from "lucide-react"; +import { Plus, Play, Edit, Trash2, Copy, X, RotateCcw } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { useConfirmation } from "@/hooks/use-confirmation.ts"; @@ -108,6 +99,14 @@ export function SSHUtilitySidebar({ const terminalTabs = tabs.filter((tab: TabData) => tab.type === "terminal"); + // Initialize CSS variable on mount and when sidebar width changes + useEffect(() => { + document.documentElement.style.setProperty( + "--right-sidebar-width", + `${sidebarWidth}px`, + ); + }, [sidebarWidth]); + useEffect(() => { if (isOpen && activeTab === "snippets") { fetchSnippets(); @@ -131,13 +130,22 @@ export function SSHUtilitySidebar({ const newWidth = Math.round(startWidthRef.current + dx); const minWidth = 300; const maxWidth = Math.round(window.innerWidth * 0.5); - if (newWidth >= minWidth && newWidth <= maxWidth) { - setSidebarWidth(newWidth); - } else if (newWidth < minWidth) { - setSidebarWidth(minWidth); + + let finalWidth = newWidth; + if (newWidth < minWidth) { + finalWidth = minWidth; } else if (newWidth > maxWidth) { - setSidebarWidth(maxWidth); + 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); }; const handleMouseUp = () => { @@ -156,7 +164,7 @@ export function SSHUtilitySidebar({ document.body.style.cursor = ""; document.body.style.userSelect = ""; }; - }, [isResizing, sidebarWidth, setSidebarWidth]); + }, [isResizing]); // SSH Tools handlers const handleTabToggle = (tabId: number) => { @@ -433,19 +441,22 @@ export function SSHUtilitySidebar({ toast.success(t("snippets.copySuccess", { name: snippet.name })); }; - if (!isOpen) return null; - return ( <> -
- -
- + {isOpen && ( +
+ + {t("nav.tools")} @@ -780,24 +791,9 @@ export function SSHUtilitySidebar({ /> )} -
- - - {!isOpen && ( -
- -
- )} -
+
+
+ )} {showDialog && (
{renderTerminalsLayer()} diff --git a/src/ui/desktop/navigation/TopNavbar.tsx b/src/ui/desktop/navigation/TopNavbar.tsx index d729464e..45bd2ab3 100644 --- a/src/ui/desktop/navigation/TopNavbar.tsx +++ b/src/ui/desktop/navigation/TopNavbar.tsx @@ -73,7 +73,7 @@ export function TopNavbar({ }, [toolsSidebarOpen, rightSidebarWidth, onRightSidebarStateChange]); const rightPosition = toolsSidebarOpen - ? `${rightSidebarWidth + 17}px` + ? `calc(var(--right-sidebar-width, ${rightSidebarWidth}px) + 8px)` : "17px"; const [justDroppedTabId, setJustDroppedTabId] = useState(null); const [isInDropAnimation, setIsInDropAnimation] = useState(false); @@ -315,12 +315,13 @@ export function TopNavbar({ return (
+ + +
+
+
+ {message && ( +

{message}

+ )} +
+
+ + ); +} diff --git a/src/ui/desktop/user/UserProfile.tsx b/src/ui/desktop/user/UserProfile.tsx index 4169de4b..90695a0c 100644 --- a/src/ui/desktop/user/UserProfile.tsx +++ b/src/ui/desktop/user/UserProfile.tsx @@ -173,10 +173,14 @@ export function UserProfile({ const bottomMarginPx = 8; const wrapperStyle: React.CSSProperties = { marginLeft: leftMarginPx, - marginRight: rightSidebarOpen ? rightSidebarWidth + 17 : 17, + marginRight: rightSidebarOpen + ? `calc(var(--right-sidebar-width, ${rightSidebarWidth}px) + 8px)` + : 17, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, + transition: + "margin-left 200ms linear, margin-right 200ms linear, margin-top 200ms linear", }; if (loading) { diff --git a/src/ui/mobile/apps/terminal/Terminal.tsx b/src/ui/mobile/apps/terminal/Terminal.tsx index 49e024fb..07bfeee5 100644 --- a/src/ui/mobile/apps/terminal/Terminal.tsx +++ b/src/ui/mobile/apps/terminal/Terminal.tsx @@ -436,7 +436,7 @@ export const Terminal = forwardRef( return (
);