Download packages/client/src/components/explorer/FileTree.tsx from E5K7/asipilot: direct link, hf CLI and curl.
- Browser
- Download file 3.52 kB
-
https://huggingface.co/spaces/E5K7/asipilot/resolve/main/packages/client/src/components/explorer/FileTree.tsx
- Command line
-
hf download hf://spaces/E5K7/asipilot/packages/client/src/components/explorer/FileTree.tsx
-
curl -L -o FileTree.tsx https://huggingface.co/spaces/E5K7/asipilot/resolve/main/packages/client/src/components/explorer/FileTree.tsx
3.52 kB
| import { useState } from 'react'; | |
| import { ChevronRight, ChevronDown, File, Folder, FolderOpen } from 'lucide-react'; | |
| import type { FileNode } from '@asipilot/shared'; | |
| import { useEditorStore } from '@/stores/editorStore'; | |
| import { useFileStore } from '@/stores/fileStore'; | |
| import { getLanguageFromPath } from '@asipilot/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> | |
| ); | |
| } | |