diff --git a/src/ui/components/LoadingOverlay.tsx b/src/ui/components/LoadingOverlay.tsx new file mode 100644 index 00000000..448ed382 --- /dev/null +++ b/src/ui/components/LoadingOverlay.tsx @@ -0,0 +1,484 @@ +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 showStartTimeRef = useRef(null); + const minDurationTimerRef = useRef(null); + + useEffect(() => { + if (visible) { + // 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 ( + <> + + +
+
+
+ +
+
+ {/* TERMIX Glitch Text */} +
+ TERMIX +
+ + {/* Scan line effect */} +
+
+ + {message && ( +
+

+ {message} +

+
+ )} +
+
+ + ); +} diff --git a/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx b/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx index 5c1a0ac8..25c14cf4 100644 --- a/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx +++ b/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx @@ -24,6 +24,7 @@ import { } from "lucide-react"; import { useTranslation } from "react-i18next"; import type { FileItem } from "../../../types/index.js"; +import { LoadingOverlay } from "@/ui/components/LoadingOverlay"; interface CreateIntent { id: string; @@ -871,19 +872,8 @@ export function FileManagerGrid({ onUndo, ]); - if (isLoading) { - return ( -
-
-
-

{t("common.loading")}

-
-
- ); - } - return ( -
+
); } diff --git a/src/ui/desktop/apps/server/Server.tsx b/src/ui/desktop/apps/server/Server.tsx index 21879e0f..4cf5be8c 100644 --- a/src/ui/desktop/apps/server/Server.tsx +++ b/src/ui/desktop/apps/server/Server.tsx @@ -17,6 +17,7 @@ import { type StatsConfig, DEFAULT_STATS_CONFIG, } from "@/types/stats-widgets"; +import { LoadingOverlay } from "@/ui/components/LoadingOverlay"; import { CpuWidget, MemoryWidget, @@ -443,17 +444,8 @@ export function Server({
{metricsEnabled && showStatsUI && ( -
- {isLoadingMetrics && !metrics ? ( -
-
-
- - {t("serverStats.loadingMetrics")} - -
-
- ) : !metrics && serverStatus === "offline" ? ( +
+ {!metrics && serverStatus === "offline" ? (
@@ -476,6 +468,13 @@ export function Server({ ))}
)} + +
)} diff --git a/src/ui/desktop/apps/terminal/Terminal.tsx b/src/ui/desktop/apps/terminal/Terminal.tsx index 8b5d5eff..21f0e4ad 100644 --- a/src/ui/desktop/apps/terminal/Terminal.tsx +++ b/src/ui/desktop/apps/terminal/Terminal.tsx @@ -31,6 +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"; interface HostConfig { id?: number; @@ -1450,17 +1451,13 @@ export const Terminal = forwardRef( onSelect={handleAutocompleteSelect} /> - {isConnecting && ( -
-
-
- {t("terminal.connecting")} -
-
- )} +
); },