import React, { useState, useEffect } from 'react'; import { ScrollArea } from './ui/scroll-area'; import { Button } from './ui/button'; import { Input } from './ui/input'; import { Folder, FolderOpen, File, FileText, FileCode, List, TableProperties, Eye, Search, X } from 'lucide-react'; import { cn } from '../lib/utils'; import CodeEditor from './CodeEditor'; import ImageViewer from './ImageViewer'; import { api } from '../utils/api'; 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); const [viewMode, setViewMode] = useState('detailed'); // 'simple', 'detailed', 'compact' const [searchQuery, setSearchQuery] = useState(''); const [filteredFiles, setFilteredFiles] = useState([]); useEffect(() => { if (selectedProject) { fetchFiles(); } }, [selectedProject]); // Load view mode preference from localStorage useEffect(() => { const savedViewMode = localStorage.getItem('file-tree-view-mode'); if (savedViewMode && ['simple', 'detailed', 'compact'].includes(savedViewMode)) { setViewMode(savedViewMode); } }, []); // Filter files based on search query useEffect(() => { if (!searchQuery.trim()) { setFilteredFiles(files); } else { const filtered = filterFiles(files, searchQuery.toLowerCase()); setFilteredFiles(filtered); // Auto-expand directories that contain matches const expandMatches = (items) => { items.forEach(item => { if (item.type === 'directory' && item.children && item.children.length > 0) { setExpandedDirs(prev => new Set(prev.add(item.path))); expandMatches(item.children); } }); }; expandMatches(filtered); } }, [files, searchQuery]); // Recursively filter files and directories based on search query const filterFiles = (items, query) => { return items.reduce((filtered, item) => { const matchesName = item.name.toLowerCase().includes(query); let filteredChildren = []; if (item.type === 'directory' && item.children) { filteredChildren = filterFiles(item.children, query); } // Include item if: // 1. It matches the search query, or // 2. It's a directory with matching children if (matchesName || filteredChildren.length > 0) { filtered.push({ ...item, children: filteredChildren }); } return filtered; }, []); }; const fetchFiles = async () => { setLoading(true); try { const response = await api.getFiles(selectedProject.name); 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); }; // Change view mode and save preference const changeViewMode = (mode) => { setViewMode(mode); localStorage.setItem('file-tree-view-mode', mode); }; // Format file size const formatFileSize = (bytes) => { if (!bytes || bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; }; // Format date as relative time const formatRelativeTime = (date) => { if (!date) return '-'; const now = new Date(); const past = new Date(date); const diffInSeconds = Math.floor((now - past) / 1000); if (diffInSeconds < 60) return 'just now'; if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} min ago`; if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`; if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`; return past.toLocaleDateString(); }; const renderFileTree = (items, level = 0) => { return items.map((item) => (
{item.type === 'directory' && expandedDirs.has(item.path) && item.children && item.children.length > 0 && (
{renderFileTree(item.children, level + 1)}
)}
)); }; 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 ; } else if (docExtensions.includes(ext)) { return ; } else if (imageExtensions.includes(ext)) { return ; } else { return ; } }; // Render detailed view with table-like layout const renderDetailedView = (items, level = 0) => { return items.map((item) => (
{ if (item.type === 'directory') { toggleDirectory(item.path); } else if (isImageFile(item.name)) { setSelectedImage({ name: item.name, path: item.path, projectPath: selectedProject.path, projectName: selectedProject.name }); } else { setSelectedFile({ name: item.name, path: item.path, projectPath: selectedProject.path, projectName: selectedProject.name }); } }} >
{item.type === 'directory' ? ( expandedDirs.has(item.path) ? ( ) : ( ) ) : ( getFileIcon(item.name) )} {item.name}
{item.type === 'file' ? formatFileSize(item.size) : '-'}
{formatRelativeTime(item.modified)}
{item.permissionsRwx || '-'}
{item.type === 'directory' && expandedDirs.has(item.path) && item.children && renderDetailedView(item.children, level + 1)}
)); }; // Render compact view with inline details const renderCompactView = (items, level = 0) => { return items.map((item) => (
{ if (item.type === 'directory') { toggleDirectory(item.path); } else if (isImageFile(item.name)) { setSelectedImage({ name: item.name, path: item.path, projectPath: selectedProject.path, projectName: selectedProject.name }); } else { setSelectedFile({ name: item.name, path: item.path, projectPath: selectedProject.path, projectName: selectedProject.name }); } }} >
{item.type === 'directory' ? ( expandedDirs.has(item.path) ? ( ) : ( ) ) : ( getFileIcon(item.name) )} {item.name}
{item.type === 'file' && ( <> {formatFileSize(item.size)} {item.permissionsRwx} )}
{item.type === 'directory' && expandedDirs.has(item.path) && item.children && renderCompactView(item.children, level + 1)}
)); }; if (loading) { return (
Loading files...
); } return (
{/* Header with Search and View Mode Toggle */}

Files

{/* Search Bar */}
setSearchQuery(e.target.value)} className="pl-8 pr-8 h-8 text-sm" /> {searchQuery && ( )}
{/* Column Headers for Detailed View */} {viewMode === 'detailed' && filteredFiles.length > 0 && (
Name
Size
Modified
Permissions
)} {files.length === 0 ? (

No files found

Check if the project path is accessible

) : filteredFiles.length === 0 && searchQuery ? (

No matches found

Try a different search term or clear the search

) : (
{viewMode === 'simple' && renderFileTree(filteredFiles)} {viewMode === 'compact' && renderCompactView(filteredFiles)} {viewMode === 'detailed' && renderDetailedView(filteredFiles)}
)}
{/* Code Editor Modal */} {selectedFile && ( setSelectedFile(null)} projectPath={selectedFile.projectPath} /> )} {/* Image Viewer Modal */} {selectedImage && ( setSelectedImage(null)} /> )}
); } export default FileTree;