优化文件管理器视觉设计和安全性
核心改进: - 统一图标色调:所有文件类型图标改为黑白配色,消除彩色差异 - 恢复原始主题:修复shadcn样式导致的过暗背景问题 - 增强大文件安全:后端10MB文件大小限制,防止内存溢出 - 优化警告样式:Large File Warning使用shadcn设计规范 技术细节: - getFileIcon()全部使用text-muted-foreground统一色调 - 恢复bg-dark-bg/border-dark-border原始主题色彩 - readFile API增加stat文件大小检查和错误处理 - FileViewer警告组件使用destructive色彩体系 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -69,11 +69,11 @@ const getFileIcon = (file: FileItem, viewMode: 'grid' | 'list' = 'grid') => {
|
||||
const iconClass = viewMode === 'grid' ? "w-8 h-8" : "w-6 h-6";
|
||||
|
||||
if (file.type === 'directory') {
|
||||
return <Folder className={`${iconClass} text-blue-400`} />;
|
||||
return <Folder className={`${iconClass} text-muted-foreground`} />;
|
||||
}
|
||||
|
||||
if (file.type === 'link') {
|
||||
return <FileSymlink className={`${iconClass} text-cyan-400`} />;
|
||||
return <FileSymlink className={`${iconClass} text-muted-foreground`} />;
|
||||
}
|
||||
|
||||
const ext = file.name.split('.').pop()?.toLowerCase();
|
||||
@@ -82,30 +82,30 @@ const getFileIcon = (file: FileItem, viewMode: 'grid' | 'list' = 'grid') => {
|
||||
case 'txt':
|
||||
case 'md':
|
||||
case 'readme':
|
||||
return <FileText className={`${iconClass} text-gray-400`} />;
|
||||
return <FileText className={`${iconClass} text-muted-foreground`} />;
|
||||
case 'png':
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'gif':
|
||||
case 'bmp':
|
||||
case 'svg':
|
||||
return <FileImage className={`${iconClass} text-green-400`} />;
|
||||
return <FileImage className={`${iconClass} text-muted-foreground`} />;
|
||||
case 'mp4':
|
||||
case 'avi':
|
||||
case 'mkv':
|
||||
case 'mov':
|
||||
return <FileVideo className={`${iconClass} text-purple-400`} />;
|
||||
return <FileVideo className={`${iconClass} text-muted-foreground`} />;
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'flac':
|
||||
case 'ogg':
|
||||
return <FileAudio className={`${iconClass} text-pink-400`} />;
|
||||
return <FileAudio className={`${iconClass} text-muted-foreground`} />;
|
||||
case 'zip':
|
||||
case 'tar':
|
||||
case 'gz':
|
||||
case 'rar':
|
||||
case '7z':
|
||||
return <Archive className={`${iconClass} text-orange-400`} />;
|
||||
return <Archive className={`${iconClass} text-muted-foreground`} />;
|
||||
case 'js':
|
||||
case 'ts':
|
||||
case 'jsx':
|
||||
@@ -119,7 +119,7 @@ const getFileIcon = (file: FileItem, viewMode: 'grid' | 'list' = 'grid') => {
|
||||
case 'rb':
|
||||
case 'go':
|
||||
case 'rs':
|
||||
return <Code className={`${iconClass} text-yellow-400`} />;
|
||||
return <Code className={`${iconClass} text-muted-foreground`} />;
|
||||
case 'json':
|
||||
case 'xml':
|
||||
case 'yaml':
|
||||
@@ -128,9 +128,9 @@ const getFileIcon = (file: FileItem, viewMode: 'grid' | 'list' = 'grid') => {
|
||||
case 'ini':
|
||||
case 'conf':
|
||||
case 'config':
|
||||
return <Settings className={`${iconClass} text-cyan-400`} />;
|
||||
return <Settings className={`${iconClass} text-muted-foreground`} />;
|
||||
default:
|
||||
return <File className={`${iconClass} text-gray-400`} />;
|
||||
return <File className={`${iconClass} text-muted-foreground`} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -613,7 +613,7 @@ export function FileManagerGrid({
|
||||
<div className="flex items-center px-3 py-2 text-sm">
|
||||
<button
|
||||
onClick={() => navigateToPath(-1)}
|
||||
className="hover:text-blue-400 hover:underline mr-1"
|
||||
className="hover:text-primary hover:underline mr-1"
|
||||
>
|
||||
/
|
||||
</button>
|
||||
@@ -621,7 +621,7 @@ export function FileManagerGrid({
|
||||
<React.Fragment key={index}>
|
||||
<button
|
||||
onClick={() => navigateToPath(index)}
|
||||
className="hover:text-blue-400 hover:underline"
|
||||
className="hover:text-primary hover:underline"
|
||||
>
|
||||
{part}
|
||||
</button>
|
||||
@@ -656,8 +656,8 @@ export function FileManagerGrid({
|
||||
{isDragging && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-blue-500/10 backdrop-blur-sm z-10 pointer-events-none">
|
||||
<div className="text-center">
|
||||
<Download className="w-12 h-12 mx-auto mb-2 text-blue-500" />
|
||||
<p className="text-lg font-medium text-blue-500">
|
||||
<Download className="w-12 h-12 mx-auto mb-2 text-primary" />
|
||||
<p className="text-lg font-medium text-primary">
|
||||
{t("fileManager.dragFilesToUpload")}
|
||||
</p>
|
||||
</div>
|
||||
@@ -693,8 +693,8 @@ export function FileManagerGrid({
|
||||
data-file-path={file.path}
|
||||
className={cn(
|
||||
"group p-3 rounded-lg cursor-pointer transition-all",
|
||||
"hover:bg-dark-hover border-2 border-transparent",
|
||||
isSelected && "bg-blue-500/20 border-blue-500"
|
||||
"hover:bg-accent hover:text-accent-foreground border-2 border-transparent",
|
||||
isSelected && "bg-primary/20 border-primary"
|
||||
)}
|
||||
title={`${file.name} - Selected: ${isSelected} - SelectedCount: ${selectedFiles.length}`}
|
||||
onClick={(e) => handleFileClick(file, e)}
|
||||
@@ -721,7 +721,7 @@ export function FileManagerGrid({
|
||||
onKeyDown={handleEditKeyDown}
|
||||
onBlur={handleEditConfirm}
|
||||
className={cn(
|
||||
"max-w-[120px] min-w-[60px] w-fit rounded-md border border-input bg-background px-2 py-1 text-xs shadow-xs transition-[color,box-shadow] outline-none",
|
||||
"max-w-[120px] min-w-[60px] w-fit rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-xs shadow-xs transition-[color,box-shadow] outline-none",
|
||||
"text-center text-foreground placeholder:text-muted-foreground",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px]"
|
||||
)}
|
||||
@@ -729,7 +729,7 @@ export function FileManagerGrid({
|
||||
/>
|
||||
) : (
|
||||
<p
|
||||
className="text-xs text-white truncate cursor-pointer hover:bg-white/10 px-1 py-0.5 rounded transition-colors duration-150 w-fit max-w-full text-center"
|
||||
className="text-xs text-foreground truncate cursor-pointer hover:bg-accent px-1 py-0.5 rounded transition-colors duration-150 w-fit max-w-full text-center"
|
||||
title={`${file.name} (点击重命名)`}
|
||||
onClick={(e) => {
|
||||
// 阻止文件选择事件
|
||||
@@ -748,7 +748,7 @@ export function FileManagerGrid({
|
||||
</p>
|
||||
)}
|
||||
{file.type === 'link' && file.linkTarget && (
|
||||
<p className="text-xs text-cyan-400 mt-1 truncate max-w-full" title={file.linkTarget}>
|
||||
<p className="text-xs text-primary mt-1 truncate max-w-full" title={file.linkTarget}>
|
||||
→ {file.linkTarget}
|
||||
</p>
|
||||
)}
|
||||
@@ -770,8 +770,8 @@ export function FileManagerGrid({
|
||||
data-file-path={file.path}
|
||||
className={cn(
|
||||
"flex items-center gap-3 p-2 rounded cursor-pointer transition-all",
|
||||
"hover:bg-dark-hover",
|
||||
isSelected && "bg-blue-500/20"
|
||||
"hover:bg-accent hover:text-accent-foreground",
|
||||
isSelected && "bg-primary/20"
|
||||
)}
|
||||
onClick={(e) => handleFileClick(file, e)}
|
||||
onContextMenu={(e) => {
|
||||
@@ -796,7 +796,7 @@ export function FileManagerGrid({
|
||||
onKeyDown={handleEditKeyDown}
|
||||
onBlur={handleEditConfirm}
|
||||
className={cn(
|
||||
"flex-1 min-w-0 max-w-[200px] rounded-md border border-input bg-background px-2 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none",
|
||||
"flex-1 min-w-0 max-w-[200px] rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none",
|
||||
"text-foreground placeholder:text-muted-foreground",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px]"
|
||||
)}
|
||||
@@ -804,7 +804,7 @@ export function FileManagerGrid({
|
||||
/>
|
||||
) : (
|
||||
<p
|
||||
className="text-sm text-white truncate cursor-pointer hover:bg-white/10 px-1 py-0.5 rounded transition-colors duration-150 w-fit max-w-full"
|
||||
className="text-sm text-foreground truncate cursor-pointer hover:bg-accent px-1 py-0.5 rounded transition-colors duration-150 w-fit max-w-full"
|
||||
title={`${file.name} (点击重命名)`}
|
||||
onClick={(e) => {
|
||||
// 阻止文件选择事件
|
||||
@@ -818,7 +818,7 @@ export function FileManagerGrid({
|
||||
</p>
|
||||
)}
|
||||
{file.type === 'link' && file.linkTarget && (
|
||||
<p className="text-xs text-cyan-400 truncate" title={file.linkTarget}>
|
||||
<p className="text-xs text-primary truncate" title={file.linkTarget}>
|
||||
→ {file.linkTarget}
|
||||
</p>
|
||||
)}
|
||||
@@ -855,7 +855,7 @@ export function FileManagerGrid({
|
||||
{/* 框选矩形 */}
|
||||
{isSelecting && selectionRect && (
|
||||
<div
|
||||
className="absolute pointer-events-none border-2 border-blue-500 bg-blue-500/10 z-50"
|
||||
className="absolute pointer-events-none border-2 border-primary bg-primary/10 z-50"
|
||||
style={{
|
||||
left: selectionRect.x,
|
||||
top: selectionRect.y,
|
||||
|
||||
@@ -579,17 +579,17 @@ export function FileViewer({
|
||||
{/* 大文件警告对话框 */}
|
||||
{showLargeFileWarning && (
|
||||
<div className="h-full flex items-center justify-center bg-background">
|
||||
<div className="bg-card border border-orange-200 rounded-lg p-6 max-w-md mx-4 shadow-lg">
|
||||
<div className="bg-card border border-destructive/30 rounded-lg p-6 max-w-md mx-4 shadow-lg">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
<AlertCircle className="w-6 h-6 text-orange-500 flex-shrink-0 mt-0.5" />
|
||||
<AlertCircle className="w-6 h-6 text-destructive flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground mb-2">Large File Warning</h3>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
This file is {formatFileSize(file.size)} in size, which may cause performance issues when opened as text.
|
||||
</p>
|
||||
{isTooLarge ? (
|
||||
<div className="bg-red-50 border border-red-200 rounded p-3 mb-4">
|
||||
<p className="text-sm text-red-700 font-medium">
|
||||
<div className="bg-destructive/10 border border-destructive/30 rounded p-3 mb-4">
|
||||
<p className="text-sm text-destructive font-medium">
|
||||
File is too large (> 10MB) and cannot be opened as text for security reasons.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -126,11 +126,17 @@ export function FileWindow({
|
||||
} catch (error: any) {
|
||||
console.error('Failed to load file:', error);
|
||||
|
||||
// 如果是连接错误,提供更明确的错误信息
|
||||
if (error.message?.includes('connection') || error.message?.includes('established')) {
|
||||
// 检查是否是大文件错误
|
||||
const errorData = error?.response?.data;
|
||||
if (errorData?.tooLarge) {
|
||||
toast.error(`File too large: ${errorData.error}`, {
|
||||
duration: 10000, // 10 seconds for important message
|
||||
});
|
||||
} else if (error.message?.includes('connection') || error.message?.includes('established')) {
|
||||
// 如果是连接错误,提供更明确的错误信息
|
||||
toast.error(`SSH connection failed. Please check your connection to ${sshHost.name} (${sshHost.ip}:${sshHost.port})`);
|
||||
} else {
|
||||
toast.error(`Failed to load file: ${error.message || 'Unknown error'}`);
|
||||
toast.error(`Failed to load file: ${error.message || errorData?.error || 'Unknown error'}`);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
Reference in New Issue
Block a user