feat: 重新设计现代化图标系统
新增 Icons.tsx 统一图标组件(25 个图标): - AppIcon: 渐变蓝色圆角矩形 + M↓ 符号 - 工具栏: FolderOpen, Save, SplitView, EditMode, PreviewMode, Moon, Sun - 标签栏: Close, Plus - 侧边栏: Folder, File, ChevronRight, FolderPlus - 搜索栏: ChevronUp, ChevronDown, X - 拖拽: UploadCloud - 导航: ArrowUp, ArrowDown - 欢迎: WelcomeFile, WelcomeNew 设计风格: - 线性图标(stroke-based),统一 1.75px 线宽 - 圆角风格,现代科技感 - 蓝色主色调渐变应用图标 - 部分图标添加微妙的填充细节(opacity 0.3-0.4) 更新组件: - Toolbar: 使用 FolderOpen/Save/SplitView/EditMode/PreviewMode/Moon/Sun - TabBar: 使用 Close/Plus - Sidebar: 使用 FolderPlus/File/Folder/ChevronRight - SearchBar: 使用 ChevronUp/ChevronDown/X - WelcomeScreen: 使用 AppIcon/WelcomeFile/WelcomeNew - DropOverlay: 使用 UploadCloud
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useEffect, useCallback, useState, useRef } from 'react'
|
||||
import { useTabStore } from '../../stores/tabStore'
|
||||
import { useSidebarStore } from '../../stores/sidebarStore'
|
||||
import { getFileName } from '../../lib/fileUtils'
|
||||
import { FolderPlus, File, Folder, ChevronRight } from '../Icons'
|
||||
import type { FileNode } from '../../types/file'
|
||||
|
||||
export function Sidebar() {
|
||||
@@ -19,22 +20,19 @@ export function Sidebar() {
|
||||
const isVisible = useSidebarStore(s => s.isVisible)
|
||||
const expandDirs = useSidebarStore(s => s.expandDirs)
|
||||
|
||||
// M-01: 归一化路径分隔符(Windows 混合 \\ 和 /)
|
||||
// M-01: 归一化路径分隔符
|
||||
const norm = (p: string) => p.replace(/\\/g, '/')
|
||||
const activeFilePath = tabs.find(t => t.id === activeTabId)?.filePath ?? null
|
||||
|
||||
// 自动展开到活动文件所在的目录
|
||||
useEffect(() => {
|
||||
if (!activeFilePath || !rootPath) return
|
||||
// M-01: 归一化后比较
|
||||
const normActive = norm(activeFilePath)
|
||||
const normRoot = norm(rootPath)
|
||||
if (!normActive.startsWith(normRoot)) return
|
||||
|
||||
// 提取文件的所有父目录路径
|
||||
const dirsToExpand: string[] = []
|
||||
let dir = activeFilePath.replace(/[/\\][^/\\]+$/, '') // 移除文件名
|
||||
// M-02: 添加 prev 防护无限循环
|
||||
let dir = activeFilePath.replace(/[/\\][^/\\]+$/, '')
|
||||
let prev = ''
|
||||
while (dir && dir.length >= rootPath.length && dir !== rootPath && dir !== prev) {
|
||||
prev = dir
|
||||
@@ -50,7 +48,6 @@ export function Sidebar() {
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
const sidebarRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// 打开文件夹
|
||||
const handleOpenFolder = useCallback(async () => {
|
||||
if (!window.electronAPI) return
|
||||
const dirPath = await window.electronAPI.openFolderDialog()
|
||||
@@ -64,7 +61,6 @@ export function Sidebar() {
|
||||
}
|
||||
}, [setRootPath, setTree])
|
||||
|
||||
// 刷新目录树
|
||||
const refreshTree = useCallback(async () => {
|
||||
if (!rootPath || !window.electronAPI) return
|
||||
const result = await window.electronAPI.readDirTree(rootPath)
|
||||
@@ -73,7 +69,6 @@ export function Sidebar() {
|
||||
}
|
||||
}, [rootPath, setTree])
|
||||
|
||||
// 目录变化监听
|
||||
useEffect(() => {
|
||||
if (!window.electronAPI) return
|
||||
window.electronAPI.onDirChanged(() => {
|
||||
@@ -84,7 +79,6 @@ export function Sidebar() {
|
||||
}
|
||||
}, [refreshTree])
|
||||
|
||||
// 侧边栏宽度调节
|
||||
useEffect(() => {
|
||||
if (!isResizing) return
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
@@ -102,7 +96,6 @@ export function Sidebar() {
|
||||
}
|
||||
}, [isResizing])
|
||||
|
||||
// L-12: 提取为 useCallback 避免每次渲染重建
|
||||
const handleFileClick = useCallback(async (path: string) => {
|
||||
const existing = tabs.find(t => t.filePath === path)
|
||||
if (existing) {
|
||||
@@ -114,6 +107,7 @@ export function Sidebar() {
|
||||
}
|
||||
}
|
||||
}, [tabs, switchToTab, createTab])
|
||||
|
||||
// M-01: 独立文件区(归一化路径比较)
|
||||
const independentFiles = tabs.filter(t => {
|
||||
if (!t.filePath) return false
|
||||
@@ -128,9 +122,7 @@ export function Sidebar() {
|
||||
<div id="sidebar-header">
|
||||
<span id="sidebar-title">资源管理器</span>
|
||||
<button className="sidebar-header-btn" onClick={handleOpenFolder} title="打开文件夹">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
<FolderPlus size={14} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -147,10 +139,7 @@ export function Sidebar() {
|
||||
onClick={() => switchToTab(tab.id)}
|
||||
>
|
||||
<span className="tree-icon">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
<File size={14} />
|
||||
</span>
|
||||
<span className="tree-name">{getFileName(tab.filePath!)}</span>
|
||||
{tab.isModified && <span className="independent-modified-dot"> •</span>}
|
||||
@@ -176,7 +165,6 @@ export function Sidebar() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 调节手柄 */}
|
||||
<div
|
||||
className="sidebar-resize-handle"
|
||||
onMouseDown={() => setIsResizing(true)}
|
||||
@@ -213,24 +201,17 @@ function FileTree({ nodes, depth, expandedDirs, toggleDir, activeTabId, activeFi
|
||||
{node.type === 'dir' ? (
|
||||
<>
|
||||
<span className={`tree-arrow ${expandedDirs.has(node.path) ? 'expanded' : ''}`}>
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<polyline points="9 18 15 12 9 6" />
|
||||
</svg>
|
||||
<ChevronRight size={10} />
|
||||
</span>
|
||||
<span className="tree-icon">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
<Folder size={14} />
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span style={{ width: '16px', flexShrink: 0 }} />
|
||||
<span className="tree-icon">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
<File size={14} />
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user