mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-16 05:13:36 +00:00
feat: terminal theme improvemnts, file manager improvements, host manager improvements (ready for initial user testing?)
This commit is contained in:
@@ -557,10 +557,6 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isLoading && currentLoadingPathRef.current !== path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let resolvedPath = path;
|
||||
if (path.includes("$") || path.startsWith("~")) {
|
||||
resolvedPath = await resolveSSHPath(sshSessionId, path);
|
||||
@@ -711,6 +707,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
|
||||
const navigateTo = useCallback(
|
||||
(path: string) => {
|
||||
if (sshSessionId) setIsLoading(true);
|
||||
setCurrentPath(path);
|
||||
setNavHistory((prev) => {
|
||||
const next = [...prev.slice(0, navIndex + 1), path];
|
||||
@@ -718,24 +715,26 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[navIndex],
|
||||
[navIndex, sshSessionId],
|
||||
);
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
if (navIndex > 0) {
|
||||
if (sshSessionId) setIsLoading(true);
|
||||
const newIndex = navIndex - 1;
|
||||
setNavIndex(newIndex);
|
||||
setCurrentPath(navHistory[newIndex]);
|
||||
}
|
||||
}, [navIndex, navHistory]);
|
||||
}, [navIndex, navHistory, sshSessionId]);
|
||||
|
||||
const goForward = useCallback(() => {
|
||||
if (navIndex < navHistory.length - 1) {
|
||||
if (sshSessionId) setIsLoading(true);
|
||||
const newIndex = navIndex + 1;
|
||||
setNavIndex(newIndex);
|
||||
setCurrentPath(navHistory[newIndex]);
|
||||
}
|
||||
}, [navIndex, navHistory]);
|
||||
}, [navIndex, navHistory, sshSessionId]);
|
||||
|
||||
const goUp = useCallback(() => {
|
||||
if (currentPath === "/") return;
|
||||
@@ -1291,6 +1290,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
|
||||
async function handleFileOpen(file: FileItem) {
|
||||
if (file.type === "directory") {
|
||||
if (sshSessionId) setIsLoading(true);
|
||||
setCurrentPath(file.path);
|
||||
} else if (file.type === "link") {
|
||||
await handleSymlinkClick(file);
|
||||
@@ -2599,11 +2599,10 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleRefreshDirectory}
|
||||
disabled={isLoading}
|
||||
className="size-8 rounded-none"
|
||||
>
|
||||
<RefreshCw
|
||||
className={`size-4 ${isLoading ? "animate-spin" : ""}`}
|
||||
className={`size-4 ${isLoading && !!sshSessionId ? "animate-spin [animation-duration:0.5s]" : ""}`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -2878,8 +2877,6 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
|
||||
onFileOpen={handleFileOpen}
|
||||
onSelectionChange={setSelection}
|
||||
currentPath={currentPath}
|
||||
isLoading={isLoading}
|
||||
isConnected={!!sshSessionId}
|
||||
onPathChange={navigateTo}
|
||||
onRefresh={handleRefreshDirectory}
|
||||
onUpload={handleFilesDropped}
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { FileItem } from "@/types/index";
|
||||
import { SimpleLoader } from "@/lib/SimpleLoader.tsx";
|
||||
|
||||
interface CreateIntent {
|
||||
id: string;
|
||||
@@ -66,8 +65,6 @@ interface FileManagerGridProps {
|
||||
onFileOpen: (file: FileItem) => void;
|
||||
onSelectionChange: (files: FileItem[]) => void;
|
||||
currentPath: string;
|
||||
isLoading?: boolean;
|
||||
isConnected?: boolean;
|
||||
onPathChange: (path: string) => void;
|
||||
onRefresh: () => void;
|
||||
onUpload?: (files: FileList) => void;
|
||||
@@ -191,8 +188,6 @@ export function FileManagerGrid({
|
||||
onFileOpen,
|
||||
onSelectionChange,
|
||||
currentPath,
|
||||
isLoading,
|
||||
isConnected,
|
||||
onPathChange,
|
||||
onRefresh,
|
||||
onUpload,
|
||||
@@ -1240,11 +1235,6 @@ export function FileManagerGrid({
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
|
||||
<SimpleLoader
|
||||
visible={!!isLoading && !!isConnected}
|
||||
message={t("common.loading")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ const GuacamoleAppInner: React.FC<GuacamoleAppInnerProps> = ({
|
||||
.then((status) => {
|
||||
if (status.guacd.status !== "connected") {
|
||||
setError(
|
||||
"Remote desktop service (guacd) is not available. Please ensure guacd is running and accessible.",
|
||||
"Remote desktop service (guacd) is not available. Please ensure guacd is running and accessible and configured properly in admin settings.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,46 @@ import { ConnectionLog } from "@/ssh/connection-log/ConnectionLog.tsx";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/button";
|
||||
|
||||
// Background/foreground per UI theme for "Termix Default" — must match index.css
|
||||
const TERMIX_DEFAULT_COLORS: Record<
|
||||
string,
|
||||
{ background: string; foreground: string }
|
||||
> = {
|
||||
dark: { background: "#0c0d0b", foreground: "#fafafa" },
|
||||
light: { background: "#ffffff", foreground: "#111210" },
|
||||
dracula: { background: "#282a36", foreground: "#f8f8f2" },
|
||||
catppuccin: { background: "#1e1e2e", foreground: "#cdd6f4" },
|
||||
nord: { background: "#2e3440", foreground: "#eceff4" },
|
||||
solarized: { background: "#002b36", foreground: "#839496" },
|
||||
"tokyo-night": { background: "#1a1b26", foreground: "#a9b1d6" },
|
||||
"one-dark": { background: "#282c34", foreground: "#abb2bf" },
|
||||
gruvbox: { background: "#282828", foreground: "#ebdbb2" },
|
||||
};
|
||||
|
||||
function resolveTermixThemeColors(activeTheme: string, appTheme: string) {
|
||||
if (activeTheme !== "termix") {
|
||||
return (
|
||||
TERMINAL_THEMES[activeTheme]?.colors || TERMINAL_THEMES.termixDark.colors
|
||||
);
|
||||
}
|
||||
let resolvedUiTheme = appTheme;
|
||||
if (appTheme === "system") {
|
||||
resolvedUiTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
}
|
||||
const uiColors =
|
||||
TERMIX_DEFAULT_COLORS[resolvedUiTheme] ?? TERMIX_DEFAULT_COLORS.dark;
|
||||
const base = TERMINAL_THEMES.termixDark.colors;
|
||||
return {
|
||||
...base,
|
||||
background: uiColors.background,
|
||||
foreground: uiColors.foreground,
|
||||
cursor: uiColors.foreground,
|
||||
cursorAccent: uiColors.background,
|
||||
};
|
||||
}
|
||||
|
||||
interface HostConfig {
|
||||
id?: number;
|
||||
instanceId?: string;
|
||||
@@ -130,27 +170,8 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
DEFAULT_TERMINAL_CONFIG.theme,
|
||||
};
|
||||
|
||||
const isDarkMode =
|
||||
appTheme === "dark" ||
|
||||
appTheme === "dracula" ||
|
||||
appTheme === "gentlemansChoice" ||
|
||||
appTheme === "midnightEspresso" ||
|
||||
appTheme === "catppuccinMocha" ||
|
||||
(appTheme === "system" &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
|
||||
let themeColors;
|
||||
const activeTheme = previewTheme || config.theme;
|
||||
|
||||
if (activeTheme === "termix") {
|
||||
themeColors = isDarkMode
|
||||
? TERMINAL_THEMES.termixDark.colors
|
||||
: TERMINAL_THEMES.termixLight.colors;
|
||||
} else {
|
||||
themeColors =
|
||||
TERMINAL_THEMES[activeTheme]?.colors ||
|
||||
TERMINAL_THEMES.termixDark.colors;
|
||||
}
|
||||
const themeColors = resolveTermixThemeColors(activeTheme, appTheme);
|
||||
const backgroundColor = themeColors.background;
|
||||
const fitAddonRef = useRef<FitAddon | null>(null);
|
||||
const webSocketRef = useRef<WebSocket | null>(null);
|
||||
@@ -1828,18 +1849,8 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
...hostConfig.terminalConfig,
|
||||
};
|
||||
|
||||
let themeColors;
|
||||
const activeTheme = previewTheme || config.theme;
|
||||
|
||||
if (activeTheme === "termix") {
|
||||
themeColors = isDarkMode
|
||||
? TERMINAL_THEMES.termixDark.colors
|
||||
: TERMINAL_THEMES.termixLight.colors;
|
||||
} else {
|
||||
themeColors =
|
||||
TERMINAL_THEMES[activeTheme]?.colors ||
|
||||
TERMINAL_THEMES.termixDark.colors;
|
||||
}
|
||||
const themeColors = resolveTermixThemeColors(activeTheme, appTheme);
|
||||
|
||||
const fontConfig = TERMINAL_FONTS.find(
|
||||
(f) => f.value === config.fontFamily,
|
||||
@@ -1896,13 +1907,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
|
||||
// Refresh terminal to apply new theme colors to existing buffer content
|
||||
hardRefresh();
|
||||
}, [
|
||||
terminal,
|
||||
hostConfig.terminalConfig,
|
||||
previewTheme,
|
||||
isDarkMode,
|
||||
isFitted,
|
||||
]);
|
||||
}, [terminal, hostConfig.terminalConfig, previewTheme, appTheme, isFitted]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!terminal || !xtermRef.current) return;
|
||||
@@ -1917,18 +1922,8 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
);
|
||||
const fontFamily = fontConfig?.fallback || TERMINAL_FONTS[0].fallback;
|
||||
|
||||
let themeColors;
|
||||
const activeTheme = previewTheme || config.theme;
|
||||
|
||||
if (activeTheme === "termix") {
|
||||
themeColors = isDarkMode
|
||||
? TERMINAL_THEMES.termixDark.colors
|
||||
: TERMINAL_THEMES.termixLight.colors;
|
||||
} else {
|
||||
themeColors =
|
||||
TERMINAL_THEMES[activeTheme]?.colors ||
|
||||
TERMINAL_THEMES.termixDark.colors;
|
||||
}
|
||||
const themeColors = resolveTermixThemeColors(activeTheme, appTheme);
|
||||
|
||||
// Set initial options before opening the terminal
|
||||
terminal.options = {
|
||||
|
||||
Reference in New Issue
Block a user