refactor(editor): 优化布局管理和行为树文件处理

This commit is contained in:
YHH
2025-11-04 23:53:26 +08:00
parent f9afa22406
commit e03b106652
15 changed files with 958 additions and 243 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Folder, ChevronRight, ChevronDown, File, Edit3, Trash2, FolderOpen, Copy, FileText, FolderPlus, ChevronsDown, ChevronsUp } from 'lucide-react';
import { TauriAPI, DirectoryEntry } from '../api/tauri';
import { MessageHub, FileActionRegistry } from '@esengine/editor-core';
@@ -28,7 +28,11 @@ interface FileTreeProps {
showFiles?: boolean;
}
export function FileTree({ rootPath, onSelectFile, selectedPath, messageHub, searchQuery, showFiles = true }: FileTreeProps) {
export interface FileTreeHandle {
collapseAll: () => void;
}
export const FileTree = forwardRef<FileTreeHandle, FileTreeProps>(({ rootPath, onSelectFile, selectedPath, messageHub, searchQuery, showFiles = true }, ref) => {
const [tree, setTree] = useState<TreeNode[]>([]);
const [loading, setLoading] = useState(false);
const [internalSelectedPath, setInternalSelectedPath] = useState<string | null>(null);
@@ -48,6 +52,26 @@ export function FileTree({ rootPath, onSelectFile, selectedPath, messageHub, sea
const [filteredTree, setFilteredTree] = useState<TreeNode[]>([]);
const fileActionRegistry = Core.services.resolve(FileActionRegistry);
const collapseAll = () => {
const collapseNode = (node: TreeNode): TreeNode => {
if (node.type === 'folder') {
return {
...node,
expanded: false,
children: node.children ? node.children.map(collapseNode) : node.children
};
}
return node;
};
const collapsedTree = tree.map(node => collapseNode(node));
setTree(collapsedTree);
};
useImperativeHandle(ref, () => ({
collapseAll
}));
useEffect(() => {
if (rootPath) {
loadRootDirectory(rootPath);
@@ -214,8 +238,72 @@ export function FileTree({ rootPath, onSelectFile, selectedPath, messageHub, sea
};
const refreshTree = async () => {
if (rootPath) {
await loadRootDirectory(rootPath);
if (!rootPath) return;
// 保存当前展开状态
const expandedPaths = new Set<string>();
const collectExpandedPaths = (nodes: TreeNode[]) => {
for (const node of nodes) {
if (node.type === 'folder' && node.expanded) {
expandedPaths.add(node.path);
if (node.children) {
collectExpandedPaths(node.children);
}
}
}
};
collectExpandedPaths(tree);
// 重新加载根目录,获取最新的文件结构
try {
const entries = await TauriAPI.listDirectory(rootPath);
const children = entriesToNodes(entries);
const rootName = rootPath.split(/[/\\]/).filter((p) => p).pop() || 'Project';
let rootNode: TreeNode = {
name: rootName,
path: rootPath,
type: 'folder',
children: children,
expanded: true,
loaded: true
};
// 恢复展开状态
if (expandedPaths.size > 0) {
const restoreExpandedState = async (node: TreeNode): Promise<TreeNode> => {
if (node.type === 'folder' && expandedPaths.has(node.path)) {
let children = node.children || [];
if (!node.loaded && node.children) {
children = await loadChildren(node);
}
const restoredChildren = await Promise.all(
children.map(child => restoreExpandedState(child))
);
return {
...node,
expanded: true,
loaded: true,
children: restoredChildren
};
} else if (node.type === 'folder' && node.children) {
const restoredChildren = await Promise.all(
node.children.map(child => restoreExpandedState(child))
);
return {
...node,
children: restoredChildren
};
}
return node;
};
rootNode = await restoreExpandedState(rootNode);
}
setTree([rootNode]);
} catch (error) {
console.error('Failed to refresh directory:', error);
}
};
@@ -252,22 +340,6 @@ export function FileTree({ rootPath, onSelectFile, selectedPath, messageHub, sea
setTree(expandedTree);
};
const collapseAll = () => {
const collapseNode = (node: TreeNode): TreeNode => {
if (node.type === 'folder') {
return {
...node,
expanded: false,
children: node.children ? node.children.map(collapseNode) : node.children
};
}
return node;
};
const collapsedTree = tree.map(node => collapseNode(node));
setTree(collapsedTree);
};
const handleRename = async (node: TreeNode) => {
if (!newName || newName === node.name) {
setRenamingNode(null);
@@ -570,7 +642,11 @@ export function FileTree({ rootPath, onSelectFile, selectedPath, messageHub, sea
</span>
<span className="tree-icon">
{node.type === 'folder' ? (
<Folder size={16} style={{ color: '#ffa726' }} />
node.name.toLowerCase() === 'plugins' || node.name.toLowerCase() === '.ecs' ? (
<Folder size={16} className="system-folder-icon" style={{ color: '#42a5f5' }} />
) : (
<Folder size={16} style={{ color: '#ffa726' }} />
)
) : (
<File size={16} style={{ color: '#90caf9' }} />
)}
@@ -615,22 +691,6 @@ export function FileTree({ rootPath, onSelectFile, selectedPath, messageHub, sea
return (
<>
<div className="file-tree-toolbar">
<button
className="file-tree-toolbar-btn"
onClick={expandAll}
title="展开全部文件夹"
>
<ChevronsDown size={14} />
</button>
<button
className="file-tree-toolbar-btn"
onClick={collapseAll}
title="收缩全部文件夹"
>
<ChevronsUp size={14} />
</button>
</div>
<div
className="file-tree"
onContextMenu={(e) => {
@@ -688,4 +748,4 @@ export function FileTree({ rootPath, onSelectFile, selectedPath, messageHub, sea
)}
</>
);
}
});