File size: 2,776 Bytes
b094925
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import React, { useState, useEffect } from 'react';
import { FaFolder, FaFolderOpen, FaFile, FaChevronRight, FaChevronDown, FaSpinner } from 'react-icons/fa';
import axios from 'axios';

interface TreeNode {
  name: string;
  path: string;
  type: 'file' | 'folder';
  children?: TreeNode[];
}

const FileTree: React.FC<{ projectPath: string; onFileSelect: (path: string) => void }> = ({ projectPath, onFileSelect }) => {
  const [tree, setTree] = useState<TreeNode[]>([]);
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
  const [selected, setSelected] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    axios.get('/api/files', { params: { project_path: projectPath } })
      .then(res => setTree(res.data))
      .catch(() => setTree([]))
      .finally(() => setLoading(false));
  }, [projectPath]);

  const toggle = (path: string) => {
    setExpanded(prev => {
      const next = new Set(prev);
      if (next.has(path)) next.delete(path); else next.add(path);
      return next;
    });
  };

  const handleClick = (node: TreeNode) => {
    if (node.type === 'folder') {
      toggle(node.path);
    } else {
      setSelected(node.path);
      onFileSelect(node.path);
    }
  };

  const renderNode = (node: TreeNode, depth: number = 0) => {
    const isOpen = expanded.has(node.path);
    return (
      <div key={node.path}>
        <div
          onClick={() => handleClick(node)}
          style={{
            paddingLeft: depth * 16 + 8,
            padding: '4px 8px',
            cursor: 'pointer',
            display: 'flex',
            alignItems: 'center',
            gap: 6,
            background: selected === node.path ? 'var(--accent)' : 'transparent',
            color: selected === node.path ? 'white' : 'var(--text-primary)',
            fontSize: 13,
          }}
        >
          {node.type === 'folder' ? (
            <>
              {isOpen ? <FaChevronDown size={10} /> : <FaChevronRight size={10} />}
              {isOpen ? <FaFolderOpen size={14} /> : <FaFolder size={14} />}
            </>
          ) : (
            <>
              <span style={{ width: 10 }} />
              <FaFile size={14} />
            </>
          )}
          <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{node.name}</span>
        </div>
        {node.type === 'folder' && isOpen && node.children?.map(child => renderNode(child, depth + 1))}
      </div>
    );
  };

  if (loading) return <div style={{ padding: 12, color: 'var(--text-secondary)' }}><FaSpinner className="spin" /> Loading files...</div>;

  return <div style={{ overflowY: 'auto', flex: 1 }}>{tree.map(node => renderNode(node))}</div>;
};

export default FileTree;