281 lines
12 KiB
TypeScript
281 lines
12 KiB
TypeScript
/**
|
||
* Terminal 工具栏组件
|
||
*/
|
||
import React from 'react';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||
import {
|
||
DropdownMenu,
|
||
DropdownMenuContent,
|
||
DropdownMenuItem,
|
||
DropdownMenuSeparator,
|
||
DropdownMenuTrigger,
|
||
} from '@/components/ui/dropdown-menu';
|
||
import { Search, Trash2, Copy, ZoomIn, ZoomOut, RotateCcw, Loader2, XCircle, Palette, SplitSquareVertical, SplitSquareHorizontal, Plus, ChevronDown, FolderOpen } from 'lucide-react';
|
||
import type { ConnectionStatus, TerminalToolbarConfig } from './types';
|
||
import type { TerminalTheme } from './themes';
|
||
import styles from './index.module.less';
|
||
|
||
interface TerminalToolbarProps {
|
||
config: TerminalToolbarConfig;
|
||
connectionStatus: ConnectionStatus;
|
||
fontSize: number;
|
||
currentTheme?: string;
|
||
themes?: TerminalTheme[];
|
||
compact?: boolean; // 紧凑模式
|
||
onSearch?: () => void;
|
||
onClear?: () => void;
|
||
onCopy?: () => void;
|
||
onZoomIn?: () => void;
|
||
onZoomOut?: () => void;
|
||
onReconnect?: () => void;
|
||
onThemeChange?: (themeName: string) => void;
|
||
onFileManager?: () => void;
|
||
// 分屏操作
|
||
onSplitUp?: () => void;
|
||
onSplitDown?: () => void;
|
||
onSplitLeft?: () => void;
|
||
onSplitRight?: () => void;
|
||
onSplitInGroup?: () => void;
|
||
}
|
||
|
||
export const TerminalToolbar: React.FC<TerminalToolbarProps> = ({
|
||
config,
|
||
connectionStatus,
|
||
fontSize,
|
||
currentTheme,
|
||
themes = [],
|
||
compact = false,
|
||
onSearch,
|
||
onClear,
|
||
onCopy,
|
||
onZoomIn,
|
||
onZoomOut,
|
||
onReconnect,
|
||
onThemeChange,
|
||
onFileManager,
|
||
onSplitUp,
|
||
onSplitDown,
|
||
onSplitLeft,
|
||
onSplitRight,
|
||
onSplitInGroup,
|
||
}) => {
|
||
if (!config.show) return null;
|
||
|
||
const getStatusBadge = () => {
|
||
// 紧凑模式:只显示状态圆点
|
||
if (compact) {
|
||
switch (connectionStatus) {
|
||
case 'connecting':
|
||
return <div className="h-2 w-2 rounded-full bg-yellow-500 animate-pulse" title="连接中" />;
|
||
case 'connected':
|
||
return <div className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" title="已连接" />;
|
||
case 'reconnecting':
|
||
return <div className="h-2 w-2 rounded-full bg-orange-500 animate-pulse" title="重连中" />;
|
||
case 'error':
|
||
return <div className="h-2 w-2 rounded-full bg-red-500" title="连接失败" />;
|
||
case 'disconnected':
|
||
return <div className="h-2 w-2 rounded-full bg-gray-400" title="已断开" />;
|
||
}
|
||
}
|
||
|
||
// 正常模式:显示带文字的Badge
|
||
switch (connectionStatus) {
|
||
case 'connecting':
|
||
return <Badge variant="outline" className="bg-yellow-100 text-yellow-700"><Loader2 className="mr-1 h-3 w-3 animate-spin" />连接中</Badge>;
|
||
case 'connected':
|
||
return <Badge variant="outline" className="bg-emerald-100 text-emerald-700"><div className="mr-1 h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />已连接</Badge>;
|
||
case 'reconnecting':
|
||
return <Badge variant="outline" className="bg-orange-100 text-orange-700"><Loader2 className="mr-1 h-3 w-3 animate-spin" />重连中</Badge>;
|
||
case 'error':
|
||
return <Badge variant="outline" className="bg-red-100 text-red-700"><XCircle className="mr-1 h-3 w-3" />连接失败</Badge>;
|
||
case 'disconnected':
|
||
return <Badge variant="outline" className="bg-gray-100 text-gray-700"><XCircle className="mr-1 h-3 w-3" />已断开</Badge>;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className={styles.toolbar}>
|
||
{/* 左侧:状态指示器 */}
|
||
<div className={styles.left}>
|
||
{config.showStatus && getStatusBadge()}
|
||
{config.showFontSizeLabel && (
|
||
<span className={compact ? "text-[12px] text-gray-500 dark:text-gray-400" : "text-xs text-gray-500 dark:text-gray-400"}>
|
||
{compact ? `${fontSize}px` : `字体 ${fontSize}px`}
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{/* 右侧:所有操作按钮 */}
|
||
<div className={styles.right}>
|
||
{/* 文件管理 */}
|
||
{onFileManager && (
|
||
<Button
|
||
size="sm"
|
||
variant="ghost"
|
||
className="h-7 px-2"
|
||
onClick={onFileManager}
|
||
title="文件管理"
|
||
>
|
||
<FolderOpen className="h-3.5 w-3.5" />
|
||
</Button>
|
||
)}
|
||
{config.showSearch && (
|
||
<Button
|
||
size="sm"
|
||
variant="ghost"
|
||
className="h-7 px-2"
|
||
onClick={onSearch}
|
||
title="搜索 (Ctrl+F)"
|
||
>
|
||
<Search className="h-3.5 w-3.5" />
|
||
</Button>
|
||
)}
|
||
{config.showClear && (
|
||
<Button
|
||
size="sm"
|
||
variant="ghost"
|
||
className="h-7 px-2"
|
||
onClick={onClear}
|
||
title="清屏 (Ctrl+L)"
|
||
>
|
||
<Trash2 className="h-3.5 w-3.5" />
|
||
</Button>
|
||
)}
|
||
{config.showCopy && (
|
||
<Button
|
||
size="sm"
|
||
variant="ghost"
|
||
className="h-7 px-2"
|
||
onClick={onCopy}
|
||
title="复制选中 (Ctrl+C)"
|
||
>
|
||
<Copy className="h-3.5 w-3.5" />
|
||
</Button>
|
||
)}
|
||
{config.showFontSize && (
|
||
<>
|
||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
|
||
<Button
|
||
size="sm"
|
||
variant="ghost"
|
||
className="h-7 px-2"
|
||
onClick={onZoomOut}
|
||
title="缩小字体"
|
||
>
|
||
<ZoomOut className="h-3.5 w-3.5" />
|
||
</Button>
|
||
<Button
|
||
size="sm"
|
||
variant="ghost"
|
||
className="h-7 px-2"
|
||
onClick={onZoomIn}
|
||
title="放大字体"
|
||
>
|
||
<ZoomIn className="h-3.5 w-3.5" />
|
||
</Button>
|
||
</>
|
||
)}
|
||
{/* 主题选择器 */}
|
||
{themes.length > 0 && onThemeChange && (
|
||
<>
|
||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
|
||
<Select value={currentTheme} onValueChange={onThemeChange}>
|
||
<SelectTrigger className={compact ? "h-7 w-[120px] text-xs" : "h-7 w-[200px] text-xs"}>
|
||
<Palette className="h-3.5 w-3.5 mr-1" />
|
||
<SelectValue placeholder="主题" />
|
||
</SelectTrigger>
|
||
<SelectContent className="z-[9999]">
|
||
{themes.map((theme) => {
|
||
// 紧凑模式:只显示英文名(去掉括号和中文)
|
||
const displayLabel = compact
|
||
? theme.label.split('(')[0].split('(')[0].trim()
|
||
: theme.label;
|
||
return (
|
||
<SelectItem key={theme.name} value={theme.name}>
|
||
{displayLabel}
|
||
</SelectItem>
|
||
);
|
||
})}
|
||
</SelectContent>
|
||
</Select>
|
||
</>
|
||
)}
|
||
{/* 分屏菜单 */}
|
||
{(onSplitUp || onSplitDown || onSplitLeft || onSplitRight || onSplitInGroup) && (
|
||
<>
|
||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="h-7 px-2 relative z-50"
|
||
title="分屏"
|
||
>
|
||
<SplitSquareVertical className="h-3.5 w-3.5 mr-1" />
|
||
<ChevronDown className="h-3 w-3" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="end" className="z-[9999]">
|
||
{onSplitUp && (
|
||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onSplitUp(); }}>
|
||
<SplitSquareHorizontal className="h-4 w-4 mr-2" />
|
||
向上拆分
|
||
</DropdownMenuItem>
|
||
)}
|
||
{onSplitDown && (
|
||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onSplitDown(); }}>
|
||
<SplitSquareHorizontal className="h-4 w-4 mr-2" />
|
||
向下拆分
|
||
</DropdownMenuItem>
|
||
)}
|
||
{(onSplitUp || onSplitDown) && (onSplitLeft || onSplitRight) && (
|
||
<DropdownMenuSeparator />
|
||
)}
|
||
{onSplitLeft && (
|
||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onSplitLeft(); }}>
|
||
<SplitSquareVertical className="h-4 w-4 mr-2" />
|
||
向左拆分
|
||
</DropdownMenuItem>
|
||
)}
|
||
{onSplitRight && (
|
||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onSplitRight(); }}>
|
||
<SplitSquareVertical className="h-4 w-4 mr-2" />
|
||
向右拆分
|
||
</DropdownMenuItem>
|
||
)}
|
||
{onSplitInGroup && (
|
||
<>
|
||
<DropdownMenuSeparator />
|
||
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onSplitInGroup(); }}>
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
在组中拆分
|
||
</DropdownMenuItem>
|
||
</>
|
||
)}
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
</>
|
||
)}
|
||
{(connectionStatus === 'disconnected' || connectionStatus === 'error') && (
|
||
<>
|
||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600 mx-1" />
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
className="h-7"
|
||
onClick={onReconnect}
|
||
>
|
||
<RotateCcw className="h-3.5 w-3.5 mr-1" />
|
||
重连
|
||
</Button>
|
||
</>
|
||
)}
|
||
{config.extraActions}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|