v1.9.0 #437
File diff suppressed because it is too large
Load Diff
@@ -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 (
|
||||
|
||||
@@ -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",
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col relative z-10 w-full h-full">
|
||||
|
||||
@@ -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" && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm z-10 pointer-events-none animate-in fade-in-0">
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm z-10 pointer-events-none">
|
||||
<div className="text-center p-8 bg-background/95 border-2 border-dashed border-primary rounded-lg shadow-lg">
|
||||
<Upload className="w-16 h-16 mx-auto mb-4 text-primary" />
|
||||
<p className="text-xl font-semibold text-foreground mb-2">
|
||||
@@ -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,
|
||||
)}
|
||||
|
||||
<LoadingOverlay
|
||||
visible={isLoading}
|
||||
minDuration={600}
|
||||
message={t("common.loading")}
|
||||
showLogo={true}
|
||||
/>
|
||||
<SimpleLoader visible={isLoading} message={t("common.loading")} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
type StatsConfig,
|
||||
DEFAULT_STATS_CONFIG,
|
||||
} from "@/types/stats-widgets";
|
||||
import { LoadingOverlay } from "@/ui/components/LoadingOverlay";
|
||||
import {
|
||||
CpuWidget,
|
||||
MemoryWidget,
|
||||
@@ -28,6 +27,7 @@ import {
|
||||
SystemWidget,
|
||||
LoginStatsWidget,
|
||||
} from "./widgets";
|
||||
import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx";
|
||||
|
||||
interface HostConfig {
|
||||
id: number;
|
||||
@@ -469,11 +469,9 @@ export function Server({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<LoadingOverlay
|
||||
<SimpleLoader
|
||||
visible={isLoadingMetrics && !metrics}
|
||||
minDuration={700}
|
||||
message={t("serverStats.loadingMetrics")}
|
||||
showLogo={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<TerminalHandle, SSHTerminalProps>(
|
||||
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<TerminalHandle, SSHTerminalProps>(
|
||||
onSelect={handleAutocompleteSelect}
|
||||
/>
|
||||
|
||||
<LoadingOverlay
|
||||
<SimpleLoader
|
||||
visible={isConnecting}
|
||||
minDuration={800}
|
||||
message={t("terminal.connecting")}
|
||||
backgroundColor={backgroundColor}
|
||||
showLogo={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="min-h-svh">
|
||||
{isOpen && (
|
||||
<div className="fixed top-0 right-0 h-0 w-0 pointer-events-none">
|
||||
<SidebarProvider
|
||||
open={isOpen}
|
||||
style={
|
||||
{ "--sidebar-width": `${sidebarWidth}px` } as React.CSSProperties
|
||||
}
|
||||
className="!min-h-0 !h-0 !w-0"
|
||||
>
|
||||
<Sidebar
|
||||
variant="floating"
|
||||
side="right"
|
||||
className="pointer-events-auto"
|
||||
>
|
||||
<div className="flex h-screen w-full justify-end">
|
||||
<Sidebar variant="floating" side="right">
|
||||
<SidebarHeader>
|
||||
<SidebarGroupLabel className="text-lg font-bold text-white">
|
||||
{t("nav.tools")}
|
||||
@@ -780,24 +791,9 @@ export function SSHUtilitySidebar({
|
||||
/>
|
||||
)}
|
||||
</Sidebar>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
|
||||
{!isOpen && (
|
||||
<div
|
||||
onClick={onClose}
|
||||
className="fixed top-0 right-0 w-[10px] h-full cursor-pointer flex items-center justify-center rounded-tl-md rounded-bl-md"
|
||||
style={{
|
||||
zIndex: 9999,
|
||||
backgroundColor: "#18181b",
|
||||
border: "2px solid #27272a",
|
||||
borderRight: "none",
|
||||
}}
|
||||
>
|
||||
<ChevronRight size={10} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showDialog && (
|
||||
<div
|
||||
|
||||
@@ -652,10 +652,14 @@ export function AppView({
|
||||
style={{
|
||||
background: containerBackground,
|
||||
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",
|
||||
}}
|
||||
>
|
||||
{renderTerminalsLayer()}
|
||||
|
||||
@@ -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<number | null>(null);
|
||||
const [isInDropAnimation, setIsInDropAnimation] = useState(false);
|
||||
@@ -315,12 +315,13 @@ export function TopNavbar({
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="fixed z-10 h-[50px] border-2 border-dark-border rounded-lg transition-all duration-200 ease-linear flex flex-row transform-none m-0 p-0"
|
||||
className="fixed z-10 h-[50px] border-2 border-dark-border rounded-lg flex flex-row transform-none m-0 p-0"
|
||||
style={{
|
||||
top: isTopbarOpen ? "0.5rem" : "-3rem",
|
||||
left: leftPosition,
|
||||
right: rightPosition,
|
||||
backgroundColor: "#18181b",
|
||||
transition: "top 200ms linear, left 200ms linear, right 200ms linear",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
61
src/ui/desktop/navigation/animations/SimpleLoader.tsx
Normal file
61
src/ui/desktop/navigation/animations/SimpleLoader.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from "react";
|
||||
import { cn } from "@/lib/utils.ts";
|
||||
|
||||
interface SimpleLoaderProps {
|
||||
visible: boolean;
|
||||
message?: string;
|
||||
className?: string;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export function SimpleLoader({
|
||||
visible,
|
||||
message,
|
||||
className,
|
||||
backgroundColor,
|
||||
}: SimpleLoaderProps) {
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.simple-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 flex items-center justify-center z-50",
|
||||
className,
|
||||
)}
|
||||
style={{ backgroundColor: backgroundColor || "#18181b" }}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="simple-spinner"></div>
|
||||
{message && (
|
||||
<p className="text-sm text-gray-300 font-medium">{message}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -436,7 +436,7 @@ export const Terminal = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
return (
|
||||
<div
|
||||
ref={xtermRef}
|
||||
className={`h-full w-full m-1 ${isReady && isVisible ? "opacity-100" : "opacity-0"} transition-opacity duration-150 overflow-hidden`}
|
||||
className="h-full w-full m-1 overflow-hidden"
|
||||
style={{ visibility: isReady ? "visible" : "hidden" }}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user