UNPKG

@codewithmehmet/paul-cli

Version:

Intelligent project file scanner and Git change tracker with interactive interface

197 lines (191 loc) β€’ 7.19 kB
import React, { useState, useCallback } from 'react'; import { Box, Text } from 'ink'; import { useInput } from 'ink'; import { formatSize } from '../utils/output.js'; export default function FileSelector({ items, selectedFiles, onSelectionChange, onBack, onExport, onOpenPresets }) { const [expandedDirs, setExpandedDirs] = useState(new Set()); const [pointer, setPointer] = useState(0); const [flatItems, setFlatItems] = useState([]); // Flatten items based on expanded directories React.useEffect(() => { const flatten = (items, level = 0, parentPath = '') => { const result = []; for (const item of items) { const itemPath = item.path; result.push({ ...item, level, parentPath }); if (item.children && expandedDirs.has(itemPath)) { result.push(...flatten(item.children, level + 1, itemPath)); } } return result; }; setFlatItems(flatten(items)); }, [items, expandedDirs]); // Calculate stats const stats = React.useMemo(() => { let totalSize = 0; let fileCount = 0; let lineCount = 0; for (const path of selectedFiles) { // Find item by path const findItem = items => { for (const item of items) { if (item.path === path) { if (item.type === 'file') { totalSize += item.size || 0; fileCount++; // Utiliser les vraies lignes si disponibles if (item.lines) { lineCount += item.lines; } else { // Sinon utiliser la mΓͺme estimation que dans l'affichage lineCount += Math.max(1, Math.round((item.size || 0) / 40)); } } } if (item.children) { findItem(item.children); } } }; findItem(items); } return { size: totalSize, files: fileCount, lines: lineCount }; }, [selectedFiles, items]); const toggleExpand = useCallback(item => { if (item.type === 'directory') { const newExpanded = new Set(expandedDirs); if (newExpanded.has(item.path)) { newExpanded.delete(item.path); } else { newExpanded.add(item.path); } setExpandedDirs(newExpanded); } }, [expandedDirs]); const toggleSelection = useCallback(item => { const newSelection = new Set(selectedFiles); const toggleItemAndChildren = item => { if (newSelection.has(item.path)) { newSelection.delete(item.path); } else { newSelection.add(item.path); } // Toggle children if directory if (item.children) { for (const child of item.children) { toggleItemAndChildren(child); } } }; toggleItemAndChildren(item); onSelectionChange(newSelection); }, [selectedFiles, onSelectionChange]); const selectAll = useCallback(() => { const allPaths = new Set(); const collectPaths = items => { for (const item of items) { // Ajouter tous les items, pas seulement les fichiers allPaths.add(item.path); if (item.children) { collectPaths(item.children); } } }; collectPaths(items); onSelectionChange(allPaths); }, [items, onSelectionChange]); const clearSelection = useCallback(() => { onSelectionChange(new Set()); }, [onSelectionChange]); useInput((input, key) => { if (key.escape || input === 'q') { onBack(); } else if (key.upArrow) { setPointer(Math.max(0, pointer - 1)); } else if (key.downArrow) { setPointer(Math.min(flatItems.length - 1, pointer + 1)); } else if (key.return || key.rightArrow) { const item = flatItems[pointer]; if (item) { toggleExpand(item); } } else if (key.leftArrow) { const item = flatItems[pointer]; if (item && expandedDirs.has(item.path)) { toggleExpand(item); } } else if (input === ' ') { const item = flatItems[pointer]; if (item) { toggleSelection(item); } } else if (input === 'a') { selectAll(); } else if (input === 'c') { clearSelection(); } else if (input === 'o' && selectedFiles.size > 0) { onExport(); } else if (input === 'p') { onOpenPresets?.(); } }); // Calculate visible range (window of 15 items) const windowSize = 15; const scrollOffset = Math.max(0, Math.min(pointer - Math.floor(windowSize / 2), flatItems.length - windowSize)); const visibleItems = flatItems.slice(scrollOffset, scrollOffset + windowSize); // Dans le return, ajouter des espaces entre les sections : return /*#__PURE__*/React.createElement(Box, { flexDirection: "column" }, /*#__PURE__*/React.createElement(Box, { flexDirection: "row" }, /*#__PURE__*/React.createElement(Text, { color: "green", bold: true }, "\uD83D\uDCC2 File Scanner"), /*#__PURE__*/React.createElement(Box, { marginLeft: 2 }, /*#__PURE__*/React.createElement(Text, { color: "gray" }, stats.files, " files | ", formatSize(stats.size), " | ", stats.lines, " lines"))), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, { color: "gray" }, '─'.repeat(50)), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Box, { flexDirection: "column" }, visibleItems.map((item, index) => { const actualIndex = scrollOffset + index; const isPointed = actualIndex === pointer; const isSelected = selectedFiles.has(item.path); const isExpanded = expandedDirs.has(item.path); // Calculer les lignes const lines = item.lines || item.type === 'file' && item.size && Math.max(1, Math.round(item.size / 40)) || 0; return /*#__PURE__*/React.createElement(Box, { key: item.path }, /*#__PURE__*/React.createElement(Text, { color: isPointed ? 'cyan' : 'white' }, isPointed ? 'β–Ά ' : ' ', ' '.repeat(item.level), item.type === 'directory' ? isExpanded ? 'πŸ“‚' : 'πŸ“' : 'πŸ“„', ' ', isSelected ? '[βœ“] ' : '[ ] ', item.name, item.size && /*#__PURE__*/React.createElement(React.Fragment, null, ' ', /*#__PURE__*/React.createElement(Text, { color: "gray" }, "(", formatSize(item.size), ")", lines > 0 && ` - ${lines} line${lines !== 1 ? 's' : ''}`)))); })), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, { color: "gray" }, '─'.repeat(50)), /*#__PURE__*/React.createElement(Text, { color: "gray" }, "\u2191\u2193 Navigate | \u23CE/\u2192 Expand | \u2190 Collapse | \u2423 Select"), /*#__PURE__*/React.createElement(Text, { color: "gray" }, "a Select all | c Clear | t Toggle tree | p Presets |", ' ', selectedFiles.size > 0 ? 'o Output | ' : '', "ESC Back"), flatItems.length > windowSize && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, { color: "gray" }, scrollOffset + 1, "-", Math.min(scrollOffset + windowSize, flatItems.length), " of", ' ', flatItems.length))); }