From ee4de697fc3130169de63fc1b7828d8a6d6b03df Mon Sep 17 00:00:00 2001 From: shiyu Date: Fri, 8 May 2026 21:39:49 +0800 Subject: [PATCH] feat: add file type categorization and size formatting in FileListView --- web/src/i18n/locales/en.json | 14 +++++++ web/src/i18n/locales/zh.json | 18 ++++++-- .../components/FileListView.tsx | 42 ++++++++++++++++++- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 8bc6197..9df7c08 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -239,6 +239,20 @@ "Type": "Type", "Folder": "Folder", "File": "File", + "Image": "Image", + "Video": "Video", + "Audio": "Audio", + "PDF": "PDF", + "Word": "Word", + "Spreadsheet": "Spreadsheet", + "Presentation": "Presentation", + "Archive": "Archive", + "Code": "Code", + "Markdown": "Markdown", + "Text": "Text", + "Font": "Font", + "Database": "Database", + "Config": "Config", "Path": "Path", "Path copied to clipboard": "Path copied to clipboard", "Copy failed": "Copy failed", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index a8584de..6144034 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -258,6 +258,20 @@ "Type": "类型", "Folder": "文件夹", "File": "文件", + "Image": "图片", + "Video": "视频", + "Audio": "音频", + "PDF": "PDF", + "Word": "Word 文档", + "Spreadsheet": "表格", + "Presentation": "演示文稿", + "Archive": "压缩包", + "Code": "代码", + "Markdown": "Markdown", + "Text": "文本", + "Font": "字体", + "Database": "数据库", + "Config": "配置", "Path": "路径", "Path copied to clipboard": "路径已复制到剪贴板", "Copy failed": "复制失败", @@ -775,7 +789,6 @@ "Users": "用户", "Create User": "创建用户", "Create Role": "创建角色", - "Edit": "编辑", "Submit": "提交", "Super Admin": "超级管理员", "Disabled": "已禁用", @@ -796,14 +809,11 @@ "Is Regex": "正则表达式", "Priority": "优先级", "Higher value = higher priority": "数值越大优先级越高", - "Permissions": "权限", "System Permissions": "系统权限", "Download and preview files": "下载和预览文件", "Upload and modify files": "上传和修改文件", "Delete files and folders": "删除文件和目录", "Create share links": "创建分享链接", - "Share": "分享", - "Delete": "删除", "permission.category.system": "系统", "permission.category.adapter": "存储适配器" } diff --git a/web/src/pages/FileExplorerPage/components/FileListView.tsx b/web/src/pages/FileExplorerPage/components/FileListView.tsx index 8fc34e2..6ae089f 100644 --- a/web/src/pages/FileExplorerPage/components/FileListView.tsx +++ b/web/src/pages/FileExplorerPage/components/FileListView.tsx @@ -19,6 +19,45 @@ interface FileListViewProps { onContextMenu: (e: React.MouseEvent, entry: VfsEntry) => void; } +const fileTypeGroups: Array<{ key: string; exts: string[] }> = [ + { key: 'Image', exts: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico', 'tiff'] }, + { key: 'Video', exts: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm', 'm4v', '3gp'] }, + { key: 'Audio', exts: ['mp3', 'wav', 'flac', 'aac', 'ogg', 'wma', 'm4a'] }, + { key: 'PDF', exts: ['pdf'] }, + { key: 'Word', exts: ['doc', 'docx'] }, + { key: 'Spreadsheet', exts: ['xls', 'xlsx', 'csv'] }, + { key: 'Presentation', exts: ['ppt', 'pptx'] }, + { key: 'Archive', exts: ['zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz'] }, + { key: 'Code', exts: ['js', 'jsx', 'ts', 'tsx', 'vue', 'html', 'htm', 'css', 'scss', 'sass', 'less', 'json', 'xml', 'yaml', 'yml', 'py', 'java', 'cpp', 'cc', 'cxx', 'c', 'h', 'hpp', 'hxx', 'php', 'rb', 'go', 'rs', 'rust', 'swift', 'kt', 'scala', 'clj', 'cljs', 'cs', 'vb', 'fs', 'pl', 'pm', 'r', 'lua', 'dart', 'elm'] }, + { key: 'Markdown', exts: ['md', 'markdown'] }, + { key: 'Text', exts: ['txt', 'log', 'ini', 'cfg', 'conf', 'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat', 'cmd', 'dockerfile', 'makefile', 'gradle', 'cmake', 'gitignore', 'gitattributes', 'editorconfig', 'prettierrc'] }, + { key: 'Font', exts: ['ttf', 'otf', 'woff', 'woff2', 'eot'] }, + { key: 'Database', exts: ['db', 'sqlite', 'sql'] }, + { key: 'Config', exts: ['env', 'config', 'properties', 'toml'] }, +]; + +const formatFileSize = (size: number) => { + if (!Number.isFinite(size) || size < 0) return '-'; + const units = ['B', 'KB', 'MB', 'GB']; + let value = size; + let unitIndex = 0; + while (value >= 1024 && unitIndex < units.length - 1) { + value /= 1024; + unitIndex += 1; + } + if (unitIndex === 0) return `${value} ${units[unitIndex]}`; + return `${value.toFixed(2)} ${units[unitIndex]}`; +}; + +const getFileTypeLabel = (entry: VfsEntry, t: (key: string) => string) => { + if (entry.type === 'mount') return t('Mount Point'); + if (entry.is_dir) return t('Folder'); + + const ext = entry.name.split('.').pop()?.toLowerCase() || ''; + const group = fileTypeGroups.find(item => item.exts.includes(ext)); + return t(group?.key || 'File'); +}; + export const FileListView: React.FC = ({ entries, selectedEntries, @@ -63,7 +102,8 @@ export const FileListView: React.FC = ({ ) }, - { title: t('Size'), dataIndex: 'size', width: 100, render: (v: number, r: VfsEntry) => r.is_dir ? '-' : v }, + { title: t('Type'), key: 'fileType', width: 110, render: (_: any, r: VfsEntry) => getFileTypeLabel(r, t) }, + { title: t('Size'), dataIndex: 'size', width: 120, render: (v: number, r: VfsEntry) => r.is_dir ? '-' : formatFileSize(v) }, { title: t('Modified Time'), dataIndex: 'mtime', width: 160, render: (v: number) => v ? new Date(v * 1000).toLocaleString() : '-' }, { title: t('Actions'),