重写ssh前端组件,通用化
This commit is contained in:
parent
7025eebd51
commit
1cddee8d42
@ -33,6 +33,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
onSplitInGroup,
|
onSplitInGroup,
|
||||||
}) => {
|
}) => {
|
||||||
const terminalRef = useRef<HTMLDivElement>(null);
|
const terminalRef = useRef<HTMLDivElement>(null);
|
||||||
|
const wrapperRef = useRef<HTMLDivElement>(null); // 最外层容器
|
||||||
const instanceRef = useRef<ReturnType<TerminalInstanceManager['getOrCreate']> | null>(null);
|
const instanceRef = useRef<ReturnType<TerminalInstanceManager['getOrCreate']> | null>(null);
|
||||||
|
|
||||||
const [fontSize, setFontSize] = useState(display?.fontSize ?? 14);
|
const [fontSize, setFontSize] = useState(display?.fontSize ?? 14);
|
||||||
@ -121,12 +122,23 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const fitAddon = instanceRef.current?.getFitAddon();
|
const fitAddon = instanceRef.current?.getFitAddon();
|
||||||
if (fitAddon && instanceRef.current) {
|
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();
|
fitAddon.fit();
|
||||||
// 发送新的尺寸给后端
|
// 发送新的尺寸给后端
|
||||||
const cols = instanceRef.current.getXTerm().cols;
|
const xterm = instanceRef.current.getXTerm();
|
||||||
const rows = instanceRef.current.getXTerm().rows;
|
const cols = xterm.cols;
|
||||||
|
const rows = xterm.rows;
|
||||||
|
console.log(`[Terminal ${id}] FitAddon计算结果: ${cols} cols x ${rows} rows`);
|
||||||
instanceRef.current.sendResize(cols, 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);
|
}, 100);
|
||||||
}
|
}
|
||||||
@ -135,10 +147,20 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 监听窗口大小变化,自动调整终端尺寸
|
// 监听窗口大小变化,自动调整终端尺寸
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
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();
|
const fitAddon = instanceRef.current?.getFitAddon();
|
||||||
if (fitAddon) {
|
if (fitAddon) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
|
if (instanceRef.current) {
|
||||||
|
const xterm = instanceRef.current.getXTerm();
|
||||||
|
console.log(`[Terminal ${id}] 🔄 Resize后: ${xterm.cols} cols × ${xterm.rows} rows`);
|
||||||
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -269,7 +291,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.terminalWrapper}>
|
<div ref={wrapperRef} className={styles.terminalWrapper}>
|
||||||
{/* 工具栏 */}
|
{/* 工具栏 */}
|
||||||
<TerminalToolbar
|
<TerminalToolbar
|
||||||
config={toolbarConfig}
|
config={toolbarConfig}
|
||||||
|
|||||||
@ -57,7 +57,6 @@ export const TerminalSplitDivider: React.FC<TerminalSplitDividerProps> = ({ orie
|
|||||||
? (orientation === 'horizontal' ? container.clientWidth : container.clientHeight)
|
? (orientation === 'horizontal' ? container.clientWidth : container.clientHeight)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
console.log(`[TerminalSplitDivider] onResize, orientation: ${orientation}, delta: ${delta}, containerSize: ${containerSize}`);
|
|
||||||
onResize(delta, containerSize);
|
onResize(delta, containerSize);
|
||||||
lastMousePosRef.current = currentMousePos;
|
lastMousePosRef.current = currentMousePos;
|
||||||
};
|
};
|
||||||
@ -81,7 +80,6 @@ export const TerminalSplitDivider: React.FC<TerminalSplitDividerProps> = ({ orie
|
|||||||
|
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log(`[TerminalSplitDivider] mouseDown, orientation: ${orientation}`);
|
|
||||||
const initialPos = orientation === 'horizontal' ? e.clientX : e.clientY;
|
const initialPos = orientation === 'horizontal' ? e.clientX : e.clientY;
|
||||||
startPosRef.current = initialPos;
|
startPosRef.current = initialPos;
|
||||||
lastMousePosRef.current = initialPos;
|
lastMousePosRef.current = initialPos;
|
||||||
|
|||||||
@ -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" />
|
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
|
||||||
<Select value={currentTheme} onValueChange={onThemeChange}>
|
<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" />
|
<Palette className="h-3.5 w-3.5 mr-1" />
|
||||||
<SelectValue placeholder="选择主题" />
|
<SelectValue placeholder="选择主题" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|||||||
@ -358,7 +358,7 @@ export class TerminalInstance {
|
|||||||
|
|
||||||
// 监听尺寸变化
|
// 监听尺寸变化
|
||||||
this.xterm.onResize((size) => {
|
this.xterm.onResize((size) => {
|
||||||
this.sendResize(size.rows, size.cols);
|
this.sendResize(size.cols, size.rows);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'monokai',
|
name: 'monokai',
|
||||||
label: 'Monokai',
|
label: 'Monokai(摩诺凯)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#272822',
|
background: '#272822',
|
||||||
foreground: '#f8f8f2',
|
foreground: '#f8f8f2',
|
||||||
@ -85,7 +85,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dracula',
|
name: 'dracula',
|
||||||
label: 'Dracula',
|
label: 'Dracula(德古拉)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#282a36',
|
background: '#282a36',
|
||||||
foreground: '#f8f8f2',
|
foreground: '#f8f8f2',
|
||||||
@ -111,7 +111,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'solarized-dark',
|
name: 'solarized-dark',
|
||||||
label: 'Solarized Dark',
|
label: 'Solarized Dark(太阳化暗色)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#002b36',
|
background: '#002b36',
|
||||||
foreground: '#839496',
|
foreground: '#839496',
|
||||||
@ -137,7 +137,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nord',
|
name: 'nord',
|
||||||
label: 'Nord',
|
label: 'Nord(北欧)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#2e3440',
|
background: '#2e3440',
|
||||||
foreground: '#d8dee9',
|
foreground: '#d8dee9',
|
||||||
@ -163,7 +163,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'solarized-light',
|
name: 'solarized-light',
|
||||||
label: 'Solarized Light',
|
label: 'Solarized Light(太阳化亮色)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#fdf6e3',
|
background: '#fdf6e3',
|
||||||
foreground: '#657b83',
|
foreground: '#657b83',
|
||||||
@ -189,7 +189,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'gruvbox-dark',
|
name: 'gruvbox-dark',
|
||||||
label: 'Gruvbox Dark',
|
label: 'Gruvbox Dark(复古暗色)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#282828',
|
background: '#282828',
|
||||||
foreground: '#ebdbb2',
|
foreground: '#ebdbb2',
|
||||||
@ -215,7 +215,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'one-dark',
|
name: 'one-dark',
|
||||||
label: 'One Dark',
|
label: 'One Dark(单色暗)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#282c34',
|
background: '#282c34',
|
||||||
foreground: '#abb2bf',
|
foreground: '#abb2bf',
|
||||||
@ -241,7 +241,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'material',
|
name: 'material',
|
||||||
label: 'Material',
|
label: 'Material(质感)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#263238',
|
background: '#263238',
|
||||||
foreground: '#eeffff',
|
foreground: '#eeffff',
|
||||||
@ -267,7 +267,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'github',
|
name: 'github',
|
||||||
label: 'GitHub',
|
label: 'GitHub(GitHub)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#ffffff',
|
background: '#ffffff',
|
||||||
foreground: '#24292e',
|
foreground: '#24292e',
|
||||||
@ -293,7 +293,7 @@ export const TERMINAL_THEMES: TerminalTheme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'tokyo-night',
|
name: 'tokyo-night',
|
||||||
label: 'Tokyo Night',
|
label: 'Tokyo Night(东京夜)',
|
||||||
theme: {
|
theme: {
|
||||||
background: '#1a1b26',
|
background: '#1a1b26',
|
||||||
foreground: '#c0caf5',
|
foreground: '#c0caf5',
|
||||||
|
|||||||
@ -5,22 +5,7 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import type { EditorGroup, TerminalTab, SplitDirection, SplitLayout, SplitNode, SplitContainer, LayoutOrientation } from './types';
|
import type { EditorGroup, TerminalTab, SplitDirection, SplitLayout, SplitNode, SplitContainer, LayoutOrientation } from './types';
|
||||||
import { TerminalInstanceManager } from './core/TerminalInstanceManager';
|
import { TerminalInstanceManager } from './core/TerminalInstanceManager';
|
||||||
|
import { getMinSplitSize, getMinSplitPercent } from './splitUtils';
|
||||||
/**
|
|
||||||
* 根据容器尺寸和方向计算最小尺寸(像素)
|
|
||||||
* @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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UseSplitViewOptions {
|
interface UseSplitViewOptions {
|
||||||
initialTab: TerminalTab;
|
initialTab: TerminalTab;
|
||||||
@ -386,19 +371,12 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
|
|
||||||
// 调整分屏大小
|
// 调整分屏大小
|
||||||
const resizeGroups = useCallback((nodeId: string, delta: number, containerSize?: number) => {
|
const resizeGroups = useCallback((nodeId: string, delta: number, containerSize?: number) => {
|
||||||
console.log(`[useSplitView.resizeGroups] nodeId: ${nodeId}, delta: ${delta}, containerSize: ${containerSize}`);
|
|
||||||
setLayout(prev => {
|
setLayout(prev => {
|
||||||
const result = findParent(prev.root, nodeId);
|
const result = findParent(prev.root, nodeId);
|
||||||
if (!result || !result.parent) {
|
if (!result || !result.parent) return prev;
|
||||||
console.log(`[useSplitView.resizeGroups] findParent failed`);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { parent, index } = result;
|
const { parent, index } = result;
|
||||||
if (index >= parent.children.length - 1) {
|
if (index >= parent.children.length - 1) return prev;
|
||||||
console.log(`[useSplitView.resizeGroups] index check failed: ${index} >= ${parent.children.length - 1}`);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = parent.children[index];
|
const current = parent.children[index];
|
||||||
const next = parent.children[index + 1];
|
const next = parent.children[index + 1];
|
||||||
@ -408,14 +386,15 @@ export const useSplitView = ({ initialTab, onWindowClose }: UseSplitViewOptions)
|
|||||||
// delta是像素值,需要转换为百分比:(delta / containerSize) * totalSize
|
// delta是像素值,需要转换为百分比:(delta / containerSize) * totalSize
|
||||||
const deltaPercent = containerSize ? (delta / containerSize) * totalSize : delta;
|
const deltaPercent = containerSize ? (delta / containerSize) * totalSize : delta;
|
||||||
|
|
||||||
// 计算最小尺寸百分比:使用totalSize的20%作为最小值
|
// 计算最小尺寸百分比:根据方向和容器尺寸计算
|
||||||
// 这样每个分屏至少占两个分屏总和的20%(例如:总100%时,最小20%)
|
let minSizePercent = totalSize * 0.2; // 默认20%
|
||||||
const minSizePercent = totalSize * 0.2;
|
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 newCurrentSize = Math.max(minSizePercent, Math.min(totalSize - minSizePercent, current.size + deltaPercent));
|
||||||
const newNextSize = totalSize - newCurrentSize;
|
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) => {
|
const newChildren = parent.children.map((child, i) => {
|
||||||
if (i === index) return { ...child, size: newCurrentSize };
|
if (i === index) return { ...child, size: newCurrentSize };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user