diff --git a/package-lock.json b/package-lock.json index d34f84e5..9a32be58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "react-dom": "^19.1.0", "react-hook-form": "^7.60.0", "react-i18next": "^15.7.3", + "react-icons": "^5.5.0", "react-resizable-panels": "^3.0.3", "react-simple-keyboard": "^3.8.120", "react-xtermjs": "^1.0.10", @@ -13663,6 +13664,15 @@ } } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", diff --git a/package.json b/package.json index a7adac32..fd4ee4fb 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "react-dom": "^19.1.0", "react-hook-form": "^7.60.0", "react-i18next": "^15.7.3", + "react-icons": "^5.5.0", "react-resizable-panels": "^3.0.3", "react-simple-keyboard": "^3.8.120", "react-xtermjs": "^1.0.10", diff --git a/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx b/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx index c155b1ba..ec8640de 100644 --- a/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx +++ b/src/ui/Desktop/Apps/File Manager/components/FileViewer.tsx @@ -17,6 +17,34 @@ import { ChevronDown, Replace } from 'lucide-react'; +import { + SiJavascript, + SiTypescript, + SiPython, + SiJava, + SiCplusplus, + SiC, + SiCsharp, + SiPhp, + SiRuby, + SiGo, + SiRust, + SiHtml5, + SiCss3, + SiSass, + SiLess, + SiJson, + SiXml, + SiYaml, + SiToml, + SiShell, + SiVuedotjs, + SiSvelte, + SiMarkdown, + SiGnubash, + SiMysql, + SiDocker +} from 'react-icons/si'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import CodeMirror from '@uiw/react-codemirror'; @@ -45,6 +73,57 @@ interface FileViewerProps { onDownload?: () => void; } +// 获取编程语言的官方图标 +function getLanguageIcon(filename: string): React.ReactNode { + const ext = filename.split('.').pop()?.toLowerCase() || ''; + const baseName = filename.toLowerCase(); + + // 特殊文件名处理 + if (['dockerfile'].includes(baseName)) { + return ; + } + if (['makefile', 'rakefile', 'gemfile'].includes(baseName)) { + return ; + } + + const iconMap: Record = { + 'js': , + 'jsx': , + 'ts': , + 'tsx': , + 'py': , + 'java': , + 'cpp': , + 'c': , + 'cs': , + 'php': , + 'rb': , + 'go': , + 'rs': , + 'html': , + 'css': , + 'scss': , + 'sass': , + 'less': , + 'json': , + 'xml': , + 'yaml': , + 'yml': , + 'toml': , + 'sql': , + 'sh': , + 'bash': , + 'zsh': , + 'vue': , + 'svelte': , + 'md': , + 'conf': , + 'ini': + }; + + return iconMap[ext] || ; +} + // 获取文件类型和图标 function getFileType(filename: string): { type: string; icon: React.ReactNode; color: string } { const ext = filename.split('.').pop()?.toLowerCase() || ''; @@ -52,8 +131,9 @@ function getFileType(filename: string): { type: string; icon: React.ReactNode; c const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg', 'webp']; const videoExts = ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm']; const audioExts = ['mp3', 'wav', 'flac', 'ogg', 'aac', 'm4a']; - const textExts = ['txt', 'md', 'readme']; - const codeExts = ['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'php', 'rb', 'go', 'rs', 'html', 'css', 'scss', 'less', 'json', 'xml', 'yaml', 'yml', 'toml', 'ini', 'conf', 'sh', 'bash', 'zsh', 'sql', 'vue', 'svelte']; + const textExts = ['txt', 'readme']; + const yamlExts = ['yaml', 'yml']; + const codeExts = ['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'php', 'rb', 'go', 'rs', 'html', 'css', 'scss', 'less', 'json', 'xml', 'toml', 'ini', 'conf', 'sh', 'bash', 'zsh', 'sql', 'vue', 'svelte', 'md']; if (imageExts.includes(ext)) { return { type: 'image', icon: , color: 'text-green-500' }; @@ -63,8 +143,10 @@ function getFileType(filename: string): { type: string; icon: React.ReactNode; c return { type: 'audio', icon: , color: 'text-pink-500' }; } else if (textExts.includes(ext)) { return { type: 'text', icon: , color: 'text-blue-500' }; - } else if (codeExts.includes(ext)) { - return { type: 'code', icon: , color: 'text-yellow-500' }; + } else if (yamlExts.includes(ext)) { + return { type: 'yaml', icon: getLanguageIcon(filename), color: 'text-red-400' }; + } else if (codeExts.includes(ext) || ['md'].includes(ext)) { + return { type: 'code', icon: getLanguageIcon(filename), color: 'text-yellow-500' }; } else { return { type: 'unknown', icon: , color: 'text-gray-500' }; } @@ -159,6 +241,7 @@ export function FileViewer({ const shouldShowAsText = fileTypeInfo.type === 'text' || fileTypeInfo.type === 'code' || + fileTypeInfo.type === 'yaml' || (fileTypeInfo.type === 'unknown' && (forceShowAsText || !file.size || file.size <= WARNING_SIZE)); // 检查文件是否过大 @@ -573,7 +656,7 @@ export function FileViewer({ {/* 文本和代码文件预览 */} {shouldShowAsText && !showLargeFileWarning && (
- {fileTypeInfo.type === 'code' ? ( + {(fileTypeInfo.type === 'code' || fileTypeInfo.type === 'yaml') ? ( // 代码文件使用CodeMirror
{searchText && searchMatches.length > 0 ? (