重写ssh前端组件,通用化

This commit is contained in:
dengqichen 2025-12-07 00:06:02 +08:00
parent 7025eebd51
commit 1cddee8d42
6 changed files with 47 additions and 48 deletions

View File

@ -33,6 +33,7 @@ export const Terminal: React.FC<TerminalProps> = ({
onSplitInGroup,
}) => {
const terminalRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null); // 最外层容器
const instanceRef = useRef<ReturnType<TerminalInstanceManager['getOrCreate']> | null>(null);
const [fontSize, setFontSize] = useState(display?.fontSize ?? 14);
@ -121,12 +122,23 @@ export const Terminal: React.FC<TerminalProps> = ({
setTimeout(() => {
const fitAddon = instanceRef.current?.getFitAddon();
if (fitAddon && instanceRef.current) {
// 检查各层容器尺寸
if (wrapperRef.current && terminalRef.current) {
console.log(`[Terminal ${id}] 📦 Wrapper尺寸: ${wrapperRef.current.clientWidth}px × ${wrapperRef.current.clientHeight}px`);
console.log(`[Terminal ${id}] 📦 Content尺寸: ${terminalRef.current.clientWidth}px × ${terminalRef.current.clientHeight}px`);
}
fitAddon.fit();
// 发送新的尺寸给后端
const cols = instanceRef.current.getXTerm().cols;
const rows = instanceRef.current.getXTerm().rows;
const xterm = instanceRef.current.getXTerm();
const cols = xterm.cols;
const rows = xterm.rows;
console.log(`[Terminal ${id}] FitAddon计算结果: ${cols} cols x ${rows} rows`);
instanceRef.current.sendResize(cols, rows);
console.log(`[Terminal ${id}] Tab activated, resized and notified: ${cols}x${rows}`);
// 重要让XTerm获取焦点否则无法输入
xterm.focus();
const hasFocus = xterm.textarea?.matches(':focus');
console.log(`[Terminal ${id}] Tab activated, resized: ${cols}x${rows}, focused: ${hasFocus}`);
}
}, 100);
}
@ -135,10 +147,20 @@ export const Terminal: React.FC<TerminalProps> = ({
// 监听窗口大小变化,自动调整终端尺寸
useEffect(() => {
const handleResize = () => {
console.log(`[Terminal ${id}] 🔄 Window resize event`);
if (wrapperRef.current && terminalRef.current) {
console.log(`[Terminal ${id}] 📦 Resize - Wrapper: ${wrapperRef.current.clientWidth}px × ${wrapperRef.current.clientHeight}px`);
console.log(`[Terminal ${id}] 📦 Resize - Content: ${terminalRef.current.clientWidth}px × ${terminalRef.current.clientHeight}px`);
}
const fitAddon = instanceRef.current?.getFitAddon();
if (fitAddon) {
setTimeout(() => {
fitAddon.fit();
if (instanceRef.current) {
const xterm = instanceRef.current.getXTerm();
console.log(`[Terminal ${id}] 🔄 Resize后: ${xterm.cols} cols × ${xterm.rows} rows`);
}
}, 100);
}
};
@ -269,7 +291,7 @@ export const Terminal: React.FC<TerminalProps> = ({
}, []);
return (
<div className={styles.terminalWrapper}>
<div ref={wrapperRef} className={styles.terminalWrapper}>
{/* 工具栏 */}
<TerminalToolbar
config={toolbarConfig}

View File

@ -57,7 +57,6 @@ export const TerminalSplitDivider: React.FC<TerminalSplitDividerProps> = ({ orie
? (orientation === 'horizontal' ? container.clientWidth : container.clientHeight)
: undefined;
console.log(`[TerminalSplitDivider] onResize, orientation: ${orientation}, delta: ${delta}, containerSize: ${containerSize}`);
onResize(delta, containerSize);
lastMousePosRef.current = currentMousePos;
};
@ -81,7 +80,6 @@ export const TerminalSplitDivider: React.FC<TerminalSplitDividerProps> = ({ orie
const handleMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
console.log(`[TerminalSplitDivider] mouseDown, orientation: ${orientation}`);
const initialPos = orientation === 'horizontal' ? e.clientX : e.clientY;
startPosRef.current = initialPos;
lastMousePosRef.current = initialPos;

View File

@ -168,7 +168,7 @@ export const TerminalToolbar: React.FC<TerminalToolbarProps> = ({
<>
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
<Select value={currentTheme} onValueChange={onThemeChange}>
<SelectTrigger className="h-7 w-auto min-w-[100px] text-xs">
<SelectTrigger className={compact ? "h-7 w-[160px] text-xs" : "h-7 w-[200px] text-xs"}>
<Palette className="h-3.5 w-3.5 mr-1" />
<SelectValue placeholder="选择主题" />
</SelectTrigger>

View File

@ -358,7 +358,7 @@ export class TerminalInstance {
// 监听尺寸变化
this.xterm.onResize((size) => {
this.sendResize(size.rows, size.cols);
this.sendResize(size.cols, size.rows);
});
}

View File

@ -59,7 +59,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'monokai',
label: 'Monokai',
label: 'Monokai(摩诺凯)',
theme: {
background: '#272822',
foreground: '#f8f8f2',
@ -85,7 +85,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'dracula',
label: 'Dracula',
label: 'Dracula(德古拉)',
theme: {
background: '#282a36',
foreground: '#f8f8f2',
@ -111,7 +111,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'solarized-dark',
label: 'Solarized Dark',
label: 'Solarized Dark(太阳化暗色)',
theme: {
background: '#002b36',
foreground: '#839496',
@ -137,7 +137,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'nord',
label: 'Nord',
label: 'Nord(北欧)',
theme: {
background: '#2e3440',
foreground: '#d8dee9',
@ -163,7 +163,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'solarized-light',
label: 'Solarized Light',
label: 'Solarized Light(太阳化亮色)',
theme: {
background: '#fdf6e3',
foreground: '#657b83',
@ -189,7 +189,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'gruvbox-dark',
label: 'Gruvbox Dark',
label: 'Gruvbox Dark(复古暗色)',
theme: {
background: '#282828',
foreground: '#ebdbb2',
@ -215,7 +215,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'one-dark',
label: 'One Dark',
label: 'One Dark(单色暗)',
theme: {
background: '#282c34',
foreground: '#abb2bf',
@ -241,7 +241,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'material',
label: 'Material',
label: 'Material(质感)',
theme: {
background: '#263238',
foreground: '#eeffff',
@ -267,7 +267,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'github',
label: 'GitHub',
label: 'GitHubGitHub',
theme: {
background: '#ffffff',
foreground: '#24292e',
@ -293,7 +293,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
},
{
name: 'tokyo-night',
label: 'Tokyo Night',
label: 'Tokyo Night(东京夜)',
theme: {
background: '#1a1b26',
foreground: '#c0caf5',

View File

@ -5,22 +5,7 @@
import { useState, useCallback } from 'react';
import type { EditorGroup, TerminalTab, SplitDirection, SplitLayout, SplitNode, SplitContainer, LayoutOrientation } from './types';
import { TerminalInstanceManager } from './core/TerminalInstanceManager';
/**
*
* @param containerSize px
* @param orientation
* @returns px
*/
const getMinSize = (containerSize: number, orientation: LayoutOrientation): number => {
if (orientation === 'horizontal') {
// 左右分屏最小宽度520px
return 520;
} else {
// 上下分屏根据容器高度计算最小200px最多占40%
return Math.max(200, Math.min(containerSize * 0.4, 300));
}
};
import { getMinSplitSize, getMinSplitPercent } from './splitUtils';
interface UseSplitViewOptions {
initialTab: TerminalTab;
@ -386,19 +371,12 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
// 调整分屏大小
const resizeGroups = useCallback((nodeId: string, delta: number, containerSize?: number) => {
console.log(`[useSplitView.resizeGroups] nodeId: ${nodeId}, delta: ${delta}, containerSize: ${containerSize}`);
setLayout(prev => {
const result = findParent(prev.root, nodeId);
if (!result || !result.parent) {
console.log(`[useSplitView.resizeGroups] findParent failed`);
return prev;
}
if (!result || !result.parent) return prev;
const { parent, index } = result;
if (index >= parent.children.length - 1) {
console.log(`[useSplitView.resizeGroups] index check failed: ${index} >= ${parent.children.length - 1}`);
return prev;
}
if (index >= parent.children.length - 1) return prev;
const current = parent.children[index];
const next = parent.children[index + 1];
@ -408,14 +386,15 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
// delta是像素值需要转换为百分比(delta / containerSize) * totalSize
const deltaPercent = containerSize ? (delta / containerSize) * totalSize : delta;
// 计算最小尺寸百分比使用totalSize的20%作为最小值
// 这样每个分屏至少占两个分屏总和的20%例如总100%时最小20%
const minSizePercent = totalSize * 0.2;
// 计算最小尺寸百分比:根据方向和容器尺寸计算
let minSizePercent = totalSize * 0.2; // 默认20%
if (containerSize && parent.type === 'container') {
const minPixels = getMinSplitSize(containerSize, parent.orientation);
minSizePercent = getMinSplitPercent(minPixels, containerSize, totalSize);
}
const newCurrentSize = Math.max(minSizePercent, Math.min(totalSize - minSizePercent, current.size + deltaPercent));
const newNextSize = totalSize - newCurrentSize;
console.log(`[useSplitView.resizeGroups] current.size: ${current.size}, deltaPercent: ${deltaPercent}, newCurrentSize: ${newCurrentSize}, newNextSize: ${newNextSize}`);
const newChildren = parent.children.map((child, i) => {
if (i === index) return { ...child, size: newCurrentSize };