From af02aa6a50e72c6fa8bb39b199c2ffa8e8154045 Mon Sep 17 00:00:00 2001 From: Daniel Sobrado <35833752+danielsobrado@users.noreply.github.com> Date: Sat, 26 Oct 2024 23:00:38 +0400 Subject: [PATCH] fixed --- frontend/package-lock.json | 26 ++ frontend/package.json | 2 + frontend/package.json.md5 | 2 +- frontend/src/App.tsx | 165 ++++++--- frontend/src/components/CodeContext.tsx | 452 ++++++++++-------------- 5 files changed, 319 insertions(+), 328 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 075e2c2..f748c68 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,6 +25,8 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { + "@types/node": "^22.8.1", + "@types/path-browserify": "^1.0.3", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", "@typescript-eslint/eslint-plugin": "^5.59.0", @@ -1642,6 +1644,23 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/node": { + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/path-browserify": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/path-browserify/-/path-browserify-1.0.3.tgz", + "integrity": "sha512-ZmHivEbNCBtAfcrFeBCiTjdIc2dey0l7oCGNGpSuRTy8jP6UVND7oUowlvDujBy8r2Hoa8bfFUOCiPWfmtkfxw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -4391,6 +4410,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", diff --git a/frontend/package.json b/frontend/package.json index ebd3c9c..fa81aae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,8 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { + "@types/node": "^22.8.1", + "@types/path-browserify": "^1.0.3", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", "@typescript-eslint/eslint-plugin": "^5.59.0", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index ce24663..0e1da31 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -14d24cf9578ace63b1443de1b42dfca0 \ No newline at end of file +76937cef403ba663dedaa68112a299be \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 15bc98c..6bd2937 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,10 +1,10 @@ // App.tsx -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import Header from './components/Header'; import TaskTypeSelector from './components/TaskTypeSelector'; import CustomInstructionsSelector from './components/CustomInstructionsSelector'; -import CodeContext from './components/CodeContext'; +import CodeContext, { SelectedFile } from './components/CodeContext'; import RawPrompt from './components/RawPrompt'; import FinalPrompt from './components/FinalPrompt'; import ActionButtons from './components/ActionButtons'; @@ -22,13 +22,13 @@ import { v4 as uuidv4 } from 'uuid'; import { CheckedState } from '@radix-ui/react-checkbox'; function App() { - const [taskType, setTaskType] = useState('Feature'); + const [taskType, setTaskType] = useState(''); const [taskTypeChecked, setTaskTypeChecked] = useState(true); - const [customInstructions, setCustomInstructions] = useState('Default'); + const [customInstructions, setCustomInstructions] = useState(''); const [customInstructionsChecked, setCustomInstructionsChecked] = useState(true); const [rawPrompt, setRawPrompt] = useState(''); const [finalPrompt, setFinalPrompt] = useState(''); - const [selectedFilesContent, setSelectedFilesContent] = useState(''); + const [selectedFilesArray, setSelectedFilesArray] = useState([]); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isTaskTypeEditOpen, setIsTaskTypeEditOpen] = useState(false); const [isCustomInstructionsEditOpen, setIsCustomInstructionsEditOpen] = useState(false); @@ -36,6 +36,19 @@ function App() { const [customInstructionsOptions, setCustomInstructionsOptions] = useState([]); const [tokenCount, setTokenCount] = useState(0); + // State to track the current prompt type ('ChatGPT' or 'Claude') + const [currentPromptType, setCurrentPromptType] = useState<'ChatGPT' | 'Claude'>('ChatGPT'); + + // Ref to track the previous prompt type + const prevPromptType = useRef<'ChatGPT' | 'Claude'>('ChatGPT'); + + // Default task instructions + const DEFAULT_CHATGPT_INSTRUCTION = + 'You are an expert coder tasked with the above task and need to strictly follow the instructions. Use the files provided as existing reference and code base.'; + + const DEFAULT_CLAUDE_INSTRUCTION = + 'You are an expert coder tasked with the above and need to strictly follow the . Use the files in as existing reference and code base.'; + useEffect(() => { const loadOptions = async () => { try { @@ -51,13 +64,14 @@ function App() { if (taskTypes.length === 0) { taskTypes = [ - { id: uuidv4(), label: 'Feature', description: 'Implement a new feature.' }, - { id: uuidv4(), label: 'Bug', description: 'Fix a bug or issue.' }, - { id: uuidv4(), label: 'Refactor', description: 'Refactor existing code.' }, + { id: uuidv4(), label: 'Implement Feature', description: 'Implement a new feature.' }, + { id: uuidv4(), label: 'Fix Bug', description: 'Fix a bug or issue.' }, + { id: uuidv4(), label: 'Refactor Code', description: 'Refactor existing code.' }, ]; } setTaskTypeOptions(taskTypes); + setTaskType(taskTypes[0].label); // Set default value const customInstructionsContent = await ReadCustomInstructionsFile(); let customInstructions: CustomInstructionOption[] = JSON.parse(customInstructionsContent); @@ -78,6 +92,7 @@ function App() { } setCustomInstructionsOptions(customInstructions); + setCustomInstructions(customInstructions[0].label); // Set default value } catch (error) { console.error('Error loading options:', error); } @@ -89,6 +104,10 @@ function App() { try { await WriteTaskTypesFile(JSON.stringify(options, null, 2)); setTaskTypeOptions(options); + // Update selected task type if it was deleted + if (!options.some((opt) => opt.label === taskType)) { + setTaskType(options.length > 0 ? options[0].label : ''); + } } catch (error) { console.error('Error saving task types:', error); } @@ -98,6 +117,10 @@ function App() { try { await WriteCustomInstructionsFile(JSON.stringify(options, null, 2)); setCustomInstructionsOptions(options); + // Update selected custom instruction if it was deleted + if (!options.some((opt) => opt.label === customInstructions)) { + setCustomInstructions(options.length > 0 ? options[0].label : ''); + } } catch (error) { console.error('Error saving custom instructions:', error); } @@ -126,25 +149,28 @@ function App() { const generateChatGPTPrompt = useCallback(() => { let prompt = ''; + // Add Task Description if (taskTypeChecked && taskType) { const taskTypeDescription = getTaskTypeDescription(taskType); - prompt += `Task Type: ${taskType}\n`; - prompt += `${taskTypeDescription}\n\n`; + prompt += `Task:\n${taskTypeDescription}\n\n`; } + // Add Instructions if (customInstructionsChecked && customInstructions) { const customInstructionDescription = getCustomInstructionDescription(customInstructions); - prompt += `Custom Instructions: ${customInstructions}\n`; - prompt += `${customInstructionDescription}\n\n`; + prompt += `Instructions:\n${customInstructionDescription}\n\n`; } - if (rawPrompt) { - prompt += `Raw Prompt: ${rawPrompt}\n\n`; + // Add Selected Files + if (selectedFilesArray.length > 0) { + const filesText = selectedFilesArray + .map((file) => `File: ${file.path}\n${file.content}`) + .join('\n\n'); + prompt += `Files:\n${filesText}\n\n`; } - if (selectedFilesContent) { - prompt += `Selected Files:\n${selectedFilesContent}\n\n`; - } + // Append Raw Prompt (Task Instruction) + prompt += rawPrompt; setFinalPrompt(prompt); setTokenCount(calculateTokenCount(prompt)); @@ -154,7 +180,7 @@ function App() { customInstructions, customInstructionsChecked, rawPrompt, - selectedFilesContent, + selectedFilesArray, taskTypeOptions, customInstructionsOptions, ]); @@ -163,42 +189,30 @@ function App() { const generateClaudePrompt = useCallback(() => { let prompt = ''; - prompt += `\n`; + // Start with FILES section + if (selectedFilesArray.length > 0) { + const filesXml = selectedFilesArray + .map((file) => { + return ` \n ${file.path}\n \n `; + }) + .join('\n'); + prompt += `\n${filesXml}\n\n\n`; + } + // Add TASK section if (taskTypeChecked && taskType) { const taskTypeDescription = getTaskTypeDescription(taskType); - prompt += ` \n \n ${taskTypeDescription}\n \n`; + prompt += `\n${taskTypeDescription}\n\n\n`; } + // Add INSTRUCTIONS section if (customInstructionsChecked && customInstructions) { const customInstructionDescription = getCustomInstructionDescription(customInstructions); - prompt += ` \n \n ${customInstructionDescription}\n \n`; - } - - if (rawPrompt) { - prompt += ` ${rawPrompt}\n`; - } - - if (selectedFilesContent) { - // Convert selectedFilesContent to XML format - const filesXml = selectedFilesContent - .split('\n\n') - .map((fileBlock) => { - const [fileLine, ...contentLines] = fileBlock.split('\n'); - const pathMatch = fileLine.match(/^File: (.+)$/); - if (pathMatch) { - const filePath = pathMatch[1]; - const content = contentLines.join('\n'); - return ` \n ${filePath}\n \n `; - } - return ''; - }) - .join('\n'); - - prompt += ` \n${filesXml}\n \n`; + prompt += `\n${customInstructionDescription}\n\n\n`; } - prompt += ``; + // Append Raw Prompt (Task Instruction) + prompt += rawPrompt; setFinalPrompt(prompt); setTokenCount(calculateTokenCount(prompt)); @@ -208,15 +222,50 @@ function App() { customInstructions, customInstructionsChecked, rawPrompt, - selectedFilesContent, + selectedFilesArray, taskTypeOptions, customInstructionsOptions, ]); - // Generate ChatGPT prompt by default on state changes + // useEffect to handle prompt generation and default task instruction useEffect(() => { - generateChatGPTPrompt(); - }, [generateChatGPTPrompt]); + // Update task instruction if it matches the default of the previous prompt type + const prevDefaultInstruction = + prevPromptType.current === 'ChatGPT' + ? DEFAULT_CHATGPT_INSTRUCTION + : DEFAULT_CLAUDE_INSTRUCTION; + + const currentDefaultInstruction = + currentPromptType === 'ChatGPT' + ? DEFAULT_CHATGPT_INSTRUCTION + : DEFAULT_CLAUDE_INSTRUCTION; + + if (rawPrompt.trim() === '' || rawPrompt === prevDefaultInstruction) { + setRawPrompt(currentDefaultInstruction); + } + + // Generate the prompt based on the current prompt type + if (currentPromptType === 'ChatGPT') { + generateChatGPTPrompt(); + } else if (currentPromptType === 'Claude') { + generateClaudePrompt(); + } + + // Update the previous prompt type + prevPromptType.current = currentPromptType; + }, [ + currentPromptType, + taskType, + taskTypeChecked, + customInstructions, + customInstructionsChecked, + rawPrompt, + selectedFilesArray, + taskTypeOptions, + customInstructionsOptions, + generateChatGPTPrompt, + generateClaudePrompt, + ]); const handleCopy = () => { navigator.clipboard @@ -225,10 +274,20 @@ function App() { .catch((err) => console.error('Failed to copy prompt: ', err)); }; - const handleSelectedFilesChange = useCallback((content: string) => { - setSelectedFilesContent(content); + const handleSelectedFilesChange = useCallback((files: SelectedFile[]) => { + setSelectedFilesArray(files); }, []); + // Handle Generate ChatGPT button click + const handleGenerateChatGPT = () => { + setCurrentPromptType('ChatGPT'); + }; + + // Handle Generate Claude button click + const handleGenerateClaude = () => { + setCurrentPromptType('Claude'); + }; + return (
@@ -266,8 +325,8 @@ function App() { /> setIsSettingsOpen(false)} /> diff --git a/frontend/src/components/CodeContext.tsx b/frontend/src/components/CodeContext.tsx index f80675f..c297ea3 100644 --- a/frontend/src/components/CodeContext.tsx +++ b/frontend/src/components/CodeContext.tsx @@ -1,322 +1,226 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { Card, CardContent } from "@/components/ui/card"; -import { SelectFile, SelectDirectory, ProcessFolder, ReadFileContent, LogInfo } from '../../wailsjs/go/main/App'; -import { EventsOn, EventsOff } from '../../wailsjs/runtime/runtime'; -import { FileTreeComponent } from './FileTreeComponent'; -import { FileOperations } from './FileOperations'; -import { DragAndDropHandler } from './DragAndDropHandler'; -import path from 'path-browserify'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; +import { Button } from '@/components/ui/button'; +import { FilePlus, FolderPlus, Trash2 } from 'lucide-react'; + +// Extend HTMLInputElement to include directory selection attributes +declare module 'react' { + interface InputHTMLAttributes extends HTMLAttributes { + webkitdirectory?: string; + directory?: string; + } +} -interface FileItem { +export interface FileItem { path: string; name: string; isDirectory: boolean; - children?: FileItem[]; - isOpen?: boolean; - isSelected?: boolean; + isSelected: boolean; content?: string; + children?: FileItem[]; +} + +export interface SelectedFile { + path: string; + content: string; } interface CodeContextProps { - onSelectedFilesChange: (content: string) => void; + onSelectedFilesChange: (files: SelectedFile[]) => void; } export default function CodeContext({ onSelectedFilesChange }: CodeContextProps) { const [files, setFiles] = useState([]); - const [basePath, setBasePath] = useState(""); - - const logFileProcessing = (method: string, filePath: string, normalizedPath: string) => { - LogInfo(`[${method}] Original path: ${filePath}`); - LogInfo(`[${method}] Normalized path: ${normalizedPath}`); - }; - - const normalizePath = (filePath: string): string => { - const normalized = filePath.replace(/\\/g, '/'); - logFileProcessing("Normalize", filePath, normalized); - if (basePath && normalized.startsWith(basePath)) { - const relativePath = normalized.slice(basePath.length + 1); // +1 to remove leading slash - logFileProcessing("Normalize (relative)", filePath, relativePath); - return relativePath; + const fileInputRef = useRef(null); + const folderInputRef = useRef(null); + + // Function to add files + const handleAddFiles = (event: React.ChangeEvent) => { + const fileList = event.target.files; + if (fileList) { + const fileArray = Array.from(fileList); + readFiles(fileArray); } - logFileProcessing("Normalize (absolute)", filePath, normalized); - return normalized; }; - const getFileName = (filePath: string): string => { - return path.basename(normalizePath(filePath)); + // Function to add folders + const handleAddFolders = (event: React.ChangeEvent) => { + const fileList = event.target.files; + if (fileList) { + const fileArray = Array.from(fileList); + readFiles(fileArray); + } }; - useEffect(() => { - const handleFilesDropped = async (droppedFiles: string[]) => { - console.log("[Drag and Drop] Dropped files:", droppedFiles); - if (droppedFiles.length > 0) { - const commonPath = findCommonPath(droppedFiles); - setBasePath(commonPath); - console.log("[Drag and Drop] Common Base Path:", commonPath); - } - for (const file of droppedFiles) { - try { - const normalizedPath = normalizePath(file); - logFileProcessing("Drag and Drop", file, normalizedPath); - const content = await ReadFileContent(file); - addFileToStructure(normalizedPath, content); - } catch (error) { - console.error(`Error reading file ${file}:`, error); + // Function to read files + const readFiles = (fileArray: File[]) => { + const newFiles: FileItem[] = []; + let filesProcessed = 0; + + fileArray.forEach((file) => { + const reader = new FileReader(); + reader.onload = () => { + const content = reader.result as string; + newFiles.push({ + path: file.webkitRelativePath || file.name, + name: file.name, + isDirectory: false, + isSelected: false, + content, + }); + filesProcessed++; + if (filesProcessed === fileArray.length) { + setFiles((prevFiles) => [...prevFiles, ...newFiles]); } - } - }; + }; + reader.readAsText(file); + }); + }; - EventsOn("files-dropped", handleFilesDropped); - return () => { - EventsOff("files-dropped"); - }; - }, []); + // Function to clear all files + const handleClearAll = () => { + setFiles([]); + }; - const findCommonPath = (paths: string[]): string => { - if (paths.length === 0) return ''; - const parts = paths[0].split('/'); - let commonPath = ''; - for (let i = 0; i < parts.length; i++) { - const part = parts.slice(0, i + 1).join('/'); - if (paths.every(path => path.startsWith(part))) { - commonPath = part; - } else { - break; - } - } - return commonPath; + // Function to toggle file selection + const toggleFileSelection = (item: FileItem) => { + setFiles((prevFiles) => + prevFiles.map((file) => + file.path === item.path ? { ...file, isSelected: !file.isSelected } : file + ) + ); }; + // Collect selected files and notify parent component useEffect(() => { - const collectSelectedFilesContent = (items: FileItem[]): string => { - let content = ''; - for (const item of items) { - if (item.isSelected && !item.isDirectory) { - content += `File: ${item.path}\n${item.content || ''}\n\n`; - } - if (item.children) { - content += collectSelectedFilesContent(item.children); - } - } - return content; + const collectSelectedFilesContent = (items: FileItem[]): SelectedFile[] => { + return items + .filter((item) => item.isSelected && !item.isDirectory) + .map((item) => ({ + path: item.path, + content: item.content || '', + })); }; - const selectedContent = collectSelectedFilesContent(files); - onSelectedFilesChange(selectedContent); + const selectedFilesArray = collectSelectedFilesContent(files); + onSelectedFilesChange(selectedFilesArray); }, [files, onSelectedFilesChange]); - const addFileToStructure = (filePath: string, content: string) => { - console.log(`[addFileToStructure] Adding file to structure: ${filePath}`); - setFiles(prevFiles => { - const newFiles = [...prevFiles]; - const parts = normalizePath(filePath).split('/').filter(part => part !== ''); - let currentLevel = newFiles; - - for (let i = 0; i < parts.length; i++) { - const part = parts[i]; - const isLast = i === parts.length - 1; - const currentPath = '/' + parts.slice(0, i + 1).join('/'); - let existingItem = currentLevel.find(item => item.path === currentPath); - - if (!existingItem) { - const newItem: FileItem = { - path: currentPath, - name: part, - isDirectory: !isLast, - children: isLast ? undefined : [], - isOpen: true, - isSelected: false, // Do not select files by default - content: isLast ? content : undefined - }; - currentLevel.push(newItem); - existingItem = newItem; - console.log(`[addFileToStructure] Created new item: ${JSON.stringify(newItem)}`); - } else if (isLast && !existingItem.isDirectory) { - // Update existing file - existingItem.content = content; - console.log(`[addFileToStructure] Updated existing file: ${existingItem.path}`); - } - - if (!isLast) { - currentLevel = existingItem.children!; - } - } - - return newFiles; - }); - }; - - const handleAddFile = async () => { - try { - const file = await SelectFile(); - if (file) { - if (!basePath) { - setBasePath(path.dirname(file)); - } - const normalizedPath = normalizePath(file); - logFileProcessing("Button - Add File", file, normalizedPath); - const content = await ReadFileContent(file); - addFileToStructure(normalizedPath, content); - } - } catch (error) { - console.error("Error selecting file:", error); - } - }; + // Handle file/folder drop + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + const items = event.dataTransfer.items; + const filePromises: Promise[] = []; - const handleAddFolder = async () => { - try { - const folder = await SelectDirectory(); - if (folder) { - if (!basePath) { - setBasePath(folder); - } - const normalizedFolder = normalizePath(folder); - console.log("[Button - Add Folder] Selected folder:", normalizedFolder); - const processedFiles = await ProcessFolder(folder, { - recursive: true, - ignoreSuffixes: ".env,.log,.json,.gitignore,.npmrc,.prettierrc", - ignoreFolders: ".git,.vscode,.idea,node_modules,venv,build,dist,coverage,out,next", - }); - console.log("[Button - Add Folder] Processed files:", processedFiles); - for (const file of processedFiles) { - const normalizedPath = normalizePath(file); - logFileProcessing("Button - Add Folder", file, normalizedPath); - const content = await ReadFileContent(file); - addFileToStructure(normalizedPath, content); - } + for (let i = 0; i < items.length; i++) { + const item = items[i].webkitGetAsEntry(); + if (item) { + filePromises.push(readEntry(item)); } - } catch (error) { - console.error("Error processing folder:", error); } - }; - - const toggleFolder = (path: string) => { - console.log(`[toggleFolder] Toggling folder: ${path}`); - setFiles(prevFiles => { - const newFiles = [...prevFiles]; - const toggleItem = (items: FileItem[]) => { - for (let item of items) { - if (item.path === path) { - item.isOpen = !item.isOpen; - console.log(`[toggleFolder] Folder ${path} is now ${item.isOpen ? 'open' : 'closed'}`); - return true; - } - if (item.children && toggleItem(item.children)) { - return true; - } - } - return false; - }; - toggleItem(newFiles); - return newFiles; - }); - }; - const toggleSelect = (path: string) => { - console.log(`[toggleSelect] Toggling selection for: ${path}`); - setFiles(prevFiles => { - const newFiles = [...prevFiles]; - const toggleItem = (items: FileItem[]) => { - for (let item of items) { - if (item.path === path) { - item.isSelected = !item.isSelected; - console.log(`[toggleSelect] Item ${path} is now ${item.isSelected ? 'selected' : 'unselected'}`); - return true; - } - if (item.children && toggleItem(item.children)) { - return true; - } - } - return false; - }; - toggleItem(newFiles); - return newFiles; + Promise.all(filePromises).then((newFiles) => { + setFiles((prevFiles) => [...prevFiles, ...newFiles.flat()]); }); }; - const removeFile = (path: string) => { - console.log(`[removeFile] Removing file: ${path}`); - setFiles(prevFiles => { - const newFiles = [...prevFiles]; - const removeItem = (items: FileItem[]): FileItem[] => { - return items.filter(item => { - if (item.path === path) { - console.log(`[removeFile] Removed item: ${item.path}`); - return false; - } - if (item.children) { - item.children = removeItem(item.children); - } - return true; - }); - }; - return removeItem(newFiles); - }); - }; - - const handleFileDrop = async (entry: any) => { - console.log("[handleFileDrop] Handling dropped file/folder:", entry.name); - const traverseFileSystemEntry = async (fsEntry: any, path = '') => { - if (fsEntry.isFile) { - return new Promise((resolve) => { - fsEntry.file(async (file: File) => { - const fullPath = normalizePath(path + file.name); - logFileProcessing("Drag and Drop", path + file.name, fullPath); - const reader = new FileReader(); - reader.onload = async (e) => { - const content = e.target?.result as string; - await addFileToStructure(fullPath, content); - resolve(); - }; - reader.readAsText(file); - }); + const readEntry = (entry: FileSystemEntry): Promise => { + return new Promise((resolve) => { + if (entry.isFile) { + const fileEntry = entry as FileSystemFileEntry; + fileEntry.file((file: File) => { + const reader = new FileReader(); + reader.onload = () => { + const content = reader.result as string; + resolve([ + { + path: entry.fullPath || file.name, + name: file.name, + isDirectory: false, + isSelected: false, + content, + }, + ]); + }; + reader.readAsText(file); }); - } else if (fsEntry.isDirectory) { - const dirReader = fsEntry.createReader(); - return new Promise((resolve) => { - dirReader.readEntries(async (entries: any[]) => { - for (let i = 0; i < entries.length; i++) { - await traverseFileSystemEntry(entries[i], path + fsEntry.name + '/'); - } - resolve(); + } else if (entry.isDirectory) { + const dirEntry = entry as FileSystemDirectoryEntry; + const dirReader = dirEntry.createReader(); + dirReader.readEntries((entries: FileSystemEntry[]) => { + const entryPromises = entries.map((e) => readEntry(e)); + Promise.all(entryPromises).then((results) => { + resolve(results.flat()); }); }); + } else { + resolve([]); } - }; + }); + }; - await traverseFileSystemEntry(entry); + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); }; - const clearAll = () => { - console.log("[clearAll] Clearing all files and folders"); - setFiles([]); - setBasePath(""); + // Render file list + const renderFiles = (items: FileItem[]) => { + return items.map((item) => ( +
+
+ toggleFileSelection(item)} + className="rounded border-gray-300 text-primary focus:ring-primary" + /> + {item.path} +
+
+ )); }; return (
- - - - - {files.length === 0 && ( -

- Drag and drop files or folders here, or use the buttons below to add them. -

- )} -
-
-
- +
+ + + + + +
+
+ {files.length > 0 ? ( + renderFiles(files) + ) : ( +

+ Drag and drop files or folders here, or use the buttons above to add files. +

+ )} +
); } \ No newline at end of file