UNPKG

@codewithmehmet/paul-cli

Version:

Intelligent project file scanner and Git change tracker with interactive interface

177 lines (174 loc) 6.58 kB
import React, { useState, useEffect, useCallback } from 'react'; import { Box, Text } from 'ink'; import { useInput } from 'ink'; import TextInput from 'ink-text-input'; import { Scanner } from '../core/scanner.js'; import { generateOutput, generateOutputFilename } from '../utils/output.js'; import { copyToClipboard } from '../utils/clipboard.js'; import FileSelector from './FileSelector.js'; import PresetManager from './PresetManager.js'; import fs from 'fs/promises'; export default function LsInteractive({ onBack }) { const [items, setItems] = useState([]); const [selectedFiles, setSelectedFiles] = useState(new Set()); const [isLoading, setIsLoading] = useState(true); const [mode, setMode] = useState('select'); // 'select', 'output', 'preset' const [message, setMessage] = useState(''); const [exportOption, setExportOption] = useState(0); // 0: clipboard, 1: file, 2: toggle tree const [includeFullTree, setIncludeFullTree] = useState(true); const [fileNameInput, setFileNameInput] = useState(''); const [isTypingFileName, setIsTypingFileName] = useState(false); // Load files on mount useEffect(() => { loadFiles(); }, []); const loadFiles = async () => { setIsLoading(true); const scanner = new Scanner({ path: process.cwd(), includeContent: false }); const scannedItems = await scanner.scan(); setItems(scannedItems); setIsLoading(false); }; const handleSelectionChange = useCallback(newSelection => { setSelectedFiles(newSelection); }, []); const handleApplyPreset = useCallback(newSelection => { setSelectedFiles(newSelection); setMode('select'); }, []); const handleExport = async type => { if (type === 'file' && !isTypingFileName) { // Demander le nom du fichier setIsTypingFileName(true); setFileNameInput(generateOutputFilename()); return; } setMessage('Generating output...'); // Get only file paths (not directories) const selectedPaths = []; const collectFilePaths = items => { for (const item of items) { if (item.type === 'file' && selectedFiles.has(item.path)) { selectedPaths.push(item.path); } if (item.children) { collectFilePaths(item.children); } } }; collectFilePaths(items); // Generate output with all items for tree const output = await generateOutput({ selectedFiles: selectedPaths, includeContent: true, includeTree: includeFullTree, allFiles: includeFullTree ? items : null }); if (type === 'clipboard') { const success = await copyToClipboard(output); if (success) { setMessage(`✅ Copied to clipboard (${selectedPaths.length} files)`); setTimeout(() => onBack(), 2000); } else { setMessage('❌ Failed to copy to clipboard'); } } else if (type === 'file') { const filename = fileNameInput || generateOutputFilename(); try { await fs.writeFile(filename, output, 'utf8'); setMessage(`✅ Saved to ${filename}`); setIsTypingFileName(false); setTimeout(() => onBack(), 2000); } catch (error) { setMessage(`❌ Failed to save: ${error.message}`); } } }; useInput((input, key) => { // Gérer ESC pendant la saisie du nom de fichier if (isTypingFileName && key.escape) { setIsTypingFileName(false); return; } if (mode === 'output' && !isTypingFileName) { if (key.escape || input === 'q') { setMode('select'); setMessage(''); } else if (key.upArrow) { setExportOption(Math.max(0, exportOption - 1)); } else if (key.downArrow) { setExportOption(Math.min(2, exportOption + 1)); } else if (key.return) { if (exportOption === 2) { setIncludeFullTree(!includeFullTree); } else { handleExport(exportOption === 0 ? 'clipboard' : 'file'); } } } }); if (isLoading) { return /*#__PURE__*/React.createElement(Box, null, /*#__PURE__*/React.createElement(Text, { color: "yellow" }, "Loading files...")); } if (mode === 'preset') { return /*#__PURE__*/React.createElement(PresetManager, { selectedFiles: selectedFiles, onApply: handleApplyPreset, onBack: () => setMode('select') }); } if (mode === 'output' && isTypingFileName) { return /*#__PURE__*/React.createElement(Box, { flexDirection: "column" }, /*#__PURE__*/React.createElement(Text, { color: "green", bold: true }, "\uD83D\uDCC4 Save to File"), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, null, "Enter filename (or press Enter for default):"), /*#__PURE__*/React.createElement(TextInput, { value: fileNameInput, onChange: setFileNameInput, onSubmit: () => handleExport('file') }), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, { color: "gray" }, "Press ESC to cancel")); } if (mode === 'output') { const options = [{ label: '📋 Copy to clipboard', value: 'clipboard' }, { label: '📄 Save to file', value: 'file' }, { label: includeFullTree ? '🌳 Full tree: ON' : '🌳 Full tree: OFF', value: 'toggle-tree' }]; return /*#__PURE__*/React.createElement(Box, { flexDirection: "column" }, /*#__PURE__*/React.createElement(Text, { color: "green", bold: true }, "\uD83D\uDCE4 Export Options"), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, null, "Selected: ", selectedFiles.size, " items"), /*#__PURE__*/React.createElement(Text, null, " "), options.map((option, index) => /*#__PURE__*/React.createElement(Text, { key: option.value, color: index === exportOption ? 'cyan' : 'white' }, index === exportOption ? '▶ ' : ' ', option.label)), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, { color: "gray" }, "\u2191\u2193 Navigate | \u23CE Select/Toggle | ESC Back"), /*#__PURE__*/React.createElement(Text, null, " "), message && /*#__PURE__*/React.createElement(Text, { color: "yellow" }, message)); } return /*#__PURE__*/React.createElement(FileSelector, { items: items, selectedFiles: selectedFiles, onSelectionChange: handleSelectionChange, onBack: onBack, onExport: () => setMode('output'), onOpenPresets: () => setMode('preset') }); }