File size: 3,519 Bytes
c2c8c8d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | import { useState } from 'react';
import { ChevronRight, ChevronDown, File, Folder, FolderOpen } from 'lucide-react';
import type { FileNode } from '@glmpilot/shared';
import { useEditorStore } from '@/stores/editorStore';
import { useFileStore } from '@/stores/fileStore';
import { getLanguageFromPath } from '@glmpilot/shared';
import { cn } from '@/lib/utils';
const FILE_ICONS: Record<string, string> = {
html: 'π', css: 'π¨', scss: 'π¨', javascript: 'β‘', typescript: 'π ',
javascriptreact: 'βοΈ', typescriptreact: 'βοΈ', json: 'π', markdown: 'π', svg: 'πΌοΈ',
};
interface FileTreeItemProps {
node: FileNode;
depth: number;
}
function FileTreeItem({ node, depth }: FileTreeItemProps) {
const [expanded, setExpanded] = useState(depth < 2);
const addFile = useEditorStore((s) => s.addFile);
const activeFilePath = useEditorStore((s) => s.activeFilePath);
const files = useFileStore((s) => s.files);
const handleClick = () => {
if (node.type === 'directory') {
setExpanded(!expanded);
} else {
const language = getLanguageFromPath(node.path);
const content = files[node.path] || '';
addFile(node.path, content, language);
}
};
const isActive = node.path === activeFilePath;
const lang = node.type === 'file' ? getLanguageFromPath(node.path) : '';
const icon = FILE_ICONS[lang] || '';
return (
<div>
<button
onClick={handleClick}
className={cn(
'w-full flex items-center gap-1 px-2 py-0.5 text-sm hover:bg-secondary/50 transition-colors rounded-sm',
isActive && 'bg-secondary text-foreground'
)}
style={{ paddingLeft: `${depth * 12 + 8}px` }}
>
{node.type === 'directory' ? (
<>
{expanded ? (
<ChevronDown className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
) : (
<ChevronRight className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
)}
{expanded ? (
<FolderOpen className="w-4 h-4 text-primary/70 shrink-0" />
) : (
<Folder className="w-4 h-4 text-primary/70 shrink-0" />
)}
</>
) : (
<>
<span className="w-3.5 shrink-0" />
{icon ? (
<span className="text-xs shrink-0">{icon}</span>
) : (
<File className="w-4 h-4 text-muted-foreground shrink-0" />
)}
</>
)}
<span className={cn('truncate', isActive ? 'text-foreground' : 'text-foreground/70')}>
{node.name}
</span>
</button>
{node.type === 'directory' && expanded && node.children && (
<div>
{node.children
.sort((a, b) => {
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
return a.name.localeCompare(b.name);
})
.map((child) => (
<FileTreeItem key={child.path} node={child} depth={depth + 1} />
))}
</div>
)}
</div>
);
}
export default function FileTree() {
const fileTree = useFileStore((s) => s.fileTree);
return (
<div className="py-1">
{fileTree
.sort((a, b) => {
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
return a.name.localeCompare(b.name);
})
.map((node) => (
<FileTreeItem key={node.path} node={node} depth={0} />
))}
</div>
);
}
|