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() { const tabs = useTabStore(s => s.tabs) const activeTabId = useTabStore(s => s.activeTabId) const switchToTab = useTabStore(s => s.switchToTab) const createTab = useTabStore(s => s.createTab) const rootPath = useSidebarStore(s => s.rootPath) const tree = useSidebarStore(s => s.tree) const expandedDirs = useSidebarStore(s => s.expandedDirs) const toggleDir = useSidebarStore(s => s.toggleDir) const setRootPath = useSidebarStore(s => s.setRootPath) const setTree = useSidebarStore(s => s.setTree) const isVisible = useSidebarStore(s => s.isVisible) const expandDirs = useSidebarStore(s => s.expandDirs) // M-01: 归一化路径分隔符 const norm = (p: string) => p.replace(/\\/g, '/') const activeFilePath = tabs.find(t => t.id === activeTabId)?.filePath ?? null // 自动展开到活动文件所在的目录 useEffect(() => { if (!activeFilePath || !rootPath) return const normActive = norm(activeFilePath) const normRoot = norm(rootPath) if (!normActive.startsWith(normRoot)) return const dirsToExpand: string[] = [] let dir = activeFilePath.replace(/[/\\][^/\\]+$/, '') let prev = '' while (dir && dir.length >= rootPath.length && dir !== rootPath && dir !== prev) { prev = dir dirsToExpand.push(dir) dir = dir.replace(/[/\\][^/\\]+$/, '') } if (dirsToExpand.length > 0) { expandDirs(dirsToExpand) } }, [activeFilePath, rootPath, expandDirs]) const [isResizing, setIsResizing] = useState(false) const sidebarRef = useRef(null) const handleOpenFolder = useCallback(async () => { if (!window.electronAPI) return const dirPath = await window.electronAPI.openFolderDialog() if (dirPath) { setRootPath(dirPath) // 展开根目录 expandDirs([dirPath]) const result = await window.electronAPI.readDirTree(dirPath) if (result.success && result.tree) { setTree(result.tree) window.electronAPI.watchDir(dirPath) } } }, [setRootPath, setTree, expandDirs]) const refreshTree = useCallback(async () => { if (!rootPath || !window.electronAPI) return const result = await window.electronAPI.readDirTree(rootPath) if (result.success && result.tree) { setTree(result.tree) } }, [rootPath, setTree]) useEffect(() => { if (!window.electronAPI) return window.electronAPI.onDirChanged(() => { refreshTree() }) return () => { window.electronAPI?.removeAllListeners('sidebar:dirChanged') } }, [refreshTree]) useEffect(() => { if (!isResizing) return const handleMouseMove = (e: MouseEvent) => { if (sidebarRef.current) { const newWidth = Math.max(180, Math.min(500, e.clientX)) sidebarRef.current.style.width = newWidth + 'px' } } const handleMouseUp = () => setIsResizing(false) document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) return () => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } }, [isResizing]) const handleFileClick = useCallback(async (path: string) => { const existing = tabs.find(t => t.filePath === path) if (existing) { switchToTab(existing.id) } else if (window.electronAPI) { const result = await window.electronAPI.readFile(path) if (result.success && result.content) { createTab(path, result.content) } } }, [tabs, switchToTab, createTab]) // M-01: 独立文件区(归一化路径比较) const independentFiles = tabs.filter(t => { if (!t.filePath) return false if (!rootPath) return true return !norm(t.filePath).startsWith(norm(rootPath)) }) if (!isVisible) return null return (