mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-15 05:29:36 +00:00
first commit
This commit is contained in:
188
src/components/FileTree.jsx
Normal file
188
src/components/FileTree.jsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ScrollArea } from './ui/scroll-area';
|
||||
import { Button } from './ui/button';
|
||||
import { Folder, FolderOpen, File, FileText, FileCode } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
import CodeEditor from './CodeEditor';
|
||||
import ImageViewer from './ImageViewer';
|
||||
|
||||
function FileTree({ selectedProject }) {
|
||||
const [files, setFiles] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [expandedDirs, setExpandedDirs] = useState(new Set());
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [selectedImage, setSelectedImage] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
fetchFiles();
|
||||
}
|
||||
}, [selectedProject]);
|
||||
|
||||
const fetchFiles = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${selectedProject.name}/files`);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('❌ File fetch failed:', response.status, errorText);
|
||||
setFiles([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setFiles(data);
|
||||
} catch (error) {
|
||||
console.error('❌ Error fetching files:', error);
|
||||
setFiles([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDirectory = (path) => {
|
||||
const newExpanded = new Set(expandedDirs);
|
||||
if (newExpanded.has(path)) {
|
||||
newExpanded.delete(path);
|
||||
} else {
|
||||
newExpanded.add(path);
|
||||
}
|
||||
setExpandedDirs(newExpanded);
|
||||
};
|
||||
|
||||
const renderFileTree = (items, level = 0) => {
|
||||
return items.map((item) => (
|
||||
<div key={item.path} className="select-none">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"w-full justify-start p-2 h-auto font-normal text-left hover:bg-accent",
|
||||
)}
|
||||
style={{ paddingLeft: `${level * 16 + 12}px` }}
|
||||
onClick={() => {
|
||||
if (item.type === 'directory') {
|
||||
toggleDirectory(item.path);
|
||||
} else if (isImageFile(item.name)) {
|
||||
// Open image in viewer
|
||||
setSelectedImage({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
projectPath: selectedProject.path,
|
||||
projectName: selectedProject.name
|
||||
});
|
||||
} else {
|
||||
// Open file in editor
|
||||
setSelectedFile({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
projectPath: selectedProject.path,
|
||||
projectName: selectedProject.name
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0 w-full">
|
||||
{item.type === 'directory' ? (
|
||||
expandedDirs.has(item.path) ? (
|
||||
<FolderOpen className="w-4 h-4 text-blue-500 flex-shrink-0" />
|
||||
) : (
|
||||
<Folder className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
)
|
||||
) : (
|
||||
getFileIcon(item.name)
|
||||
)}
|
||||
<span className="text-sm truncate text-foreground">
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
{item.type === 'directory' &&
|
||||
expandedDirs.has(item.path) &&
|
||||
item.children &&
|
||||
item.children.length > 0 && (
|
||||
<div>
|
||||
{renderFileTree(item.children, level + 1)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
};
|
||||
|
||||
const isImageFile = (filename) => {
|
||||
const ext = filename.split('.').pop()?.toLowerCase();
|
||||
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp'];
|
||||
return imageExtensions.includes(ext);
|
||||
};
|
||||
|
||||
const getFileIcon = (filename) => {
|
||||
const ext = filename.split('.').pop()?.toLowerCase();
|
||||
|
||||
const codeExtensions = ['js', 'jsx', 'ts', 'tsx', 'py', 'java', 'cpp', 'c', 'php', 'rb', 'go', 'rs'];
|
||||
const docExtensions = ['md', 'txt', 'doc', 'pdf'];
|
||||
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp'];
|
||||
|
||||
if (codeExtensions.includes(ext)) {
|
||||
return <FileCode className="w-4 h-4 text-green-500 flex-shrink-0" />;
|
||||
} else if (docExtensions.includes(ext)) {
|
||||
return <FileText className="w-4 h-4 text-blue-500 flex-shrink-0" />;
|
||||
} else if (imageExtensions.includes(ext)) {
|
||||
return <File className="w-4 h-4 text-purple-500 flex-shrink-0" />;
|
||||
} else {
|
||||
return <File className="w-4 h-4 text-muted-foreground flex-shrink-0" />;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-gray-500 dark:text-gray-400">
|
||||
Loading files...
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col bg-card">
|
||||
|
||||
<ScrollArea className="flex-1 p-4">
|
||||
{files.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-12 h-12 bg-muted rounded-lg flex items-center justify-center mx-auto mb-3">
|
||||
<Folder className="w-6 h-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="font-medium text-foreground mb-1">No files found</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Check if the project path is accessible
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{renderFileTree(files)}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
{/* Code Editor Modal */}
|
||||
{selectedFile && (
|
||||
<CodeEditor
|
||||
file={selectedFile}
|
||||
onClose={() => setSelectedFile(null)}
|
||||
projectPath={selectedFile.projectPath}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Image Viewer Modal */}
|
||||
{selectedImage && (
|
||||
<ImageViewer
|
||||
file={selectedImage}
|
||||
onClose={() => setSelectedImage(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FileTree;
|
||||
Reference in New Issue
Block a user