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>
  );
}