v1.9.0 #437

Merged
LukeGus merged 33 commits from dev-1.9.0 into main 2025-11-17 15:46:05 +00:00
13 changed files with 139 additions and 1812 deletions
Showing only changes of commit b0523f995c - Show all commits

File diff suppressed because it is too large Load Diff

View File

@@ -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 (

View File

@@ -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">

View File

@@ -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>
);
}

View File

@@ -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

View File

@@ -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>
)}

View File

@@ -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>
);

View File

@@ -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

View File

@@ -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()}

View File

@@ -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

View 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>
</>
);
}

View File

@@ -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) {

View File

@@ -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" }}
/>
);