From b079a56788f7871a46f490cd79d1d3108e354bdb Mon Sep 17 00:00:00 2001 From: KevIsDev Date: Mon, 3 Mar 2025 16:14:31 +0000 Subject: [PATCH] add: add file renaming and delete functionality --- app/components/workbench/FileTree.tsx | 96 +++++++++++++++++++++++--- app/lib/stores/workbench.ts | 98 +++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 8 deletions(-) diff --git a/app/components/workbench/FileTree.tsx b/app/components/workbench/FileTree.tsx index ff4f531d..d1087361 100644 --- a/app/components/workbench/FileTree.tsx +++ b/app/components/workbench/FileTree.tsx @@ -31,6 +31,7 @@ interface Props { interface InlineInputProps { depth: number; placeholder: string; + initialValue?: string; onSubmit: (value: string) => void; onCancel: () => void; } @@ -223,18 +224,23 @@ function ContextMenuItem({ onSelect, children }: { onSelect?: () => void; childr ); } -function InlineInput({ depth, placeholder, onSubmit, onCancel }: InlineInputProps) { +function InlineInput({ depth, placeholder, initialValue = '', onSubmit, onCancel }: InlineInputProps) { const inputRef = useRef(null); useEffect(() => { const timer = setTimeout(() => { if (inputRef.current) { inputRef.current.focus(); + + if (initialValue) { + inputRef.current.value = initialValue; + inputRef.current.select(); + } } }, 50); return () => clearTimeout(timer); - }, []); + }, [initialValue]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { @@ -272,7 +278,6 @@ function InlineInput({ depth, placeholder, onSubmit, onCancel }: InlineInputProp ); } -// Modify the FileContextMenu component function FileContextMenu({ onCopyPath, onCopyRelativePath, @@ -281,8 +286,10 @@ function FileContextMenu({ }: FolderContextMenuProps & { fullPath: string }) { const [isCreatingFile, setIsCreatingFile] = useState(false); const [isCreatingFolder, setIsCreatingFolder] = useState(false); + const [isRenaming, setIsRenaming] = useState(false); const [isDragging, setIsDragging] = useState(false); const depth = useMemo(() => fullPath.split('/').length, [fullPath]); + const fileName = useMemo(() => path.basename(fullPath), [fullPath]); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); @@ -355,6 +362,59 @@ function FileContextMenu({ setIsCreatingFolder(false); }; + const handleDelete = async () => { + try { + if (confirm(`Are you sure you want to delete ${fileName}?`)) { + const isDirectory = path.extname(fullPath) === ''; + + const success = isDirectory + ? await workbenchStore.deleteFolder(fullPath) + : await workbenchStore.deleteFile(fullPath); + + if (success) { + toast.success('Deleted successfully'); + } else { + toast.error('Failed to delete'); + } + } + } catch (error) { + toast.error('Error during delete operation'); + logger.error(error); + } + }; + + const handleRename = async (newName: string) => { + if (newName === fileName) { + setIsRenaming(false); + return; + } + + const parentDir = path.dirname(fullPath); + const newPath = path.join(parentDir, newName); + + try { + const files = workbenchStore.files.get(); + const fileEntry = files[fullPath]; + + const isDirectory = !fileEntry || fileEntry.type === 'folder'; + + const success = isDirectory + ? await workbenchStore.renameFolder(fullPath, newPath) + : await workbenchStore.renameFile(fullPath, newPath); + + if (success) { + toast.success('Renamed successfully'); + } else { + toast.error('Failed to rename'); + } + } catch (error) { + toast.error('Error during rename operation'); + logger.error(error); + } + + setIsRenaming(false); + }; + return ( <> @@ -368,7 +428,17 @@ function FileContextMenu({ isDragging, })} > - {children} + {!isRenaming && children} + + {isRenaming && ( + setIsRenaming(false)} + /> + )} @@ -389,6 +459,18 @@ function FileContextMenu({ New Folder + setIsRenaming(true)}> +
+
+ Rename +
+ + +
+
+ Delete +
+ Copy path @@ -413,11 +495,11 @@ function FileContextMenu({ onCancel={() => setIsCreatingFolder(false)} /> )} + {/* Remove the isRenaming InlineInput from here since we moved it above */} ); } -// Update the Folder component to pass the fullPath function Folder({ folder, collapsed, selected = false, onCopyPath, onCopyRelativePath, onClick }: FolderProps) { return ( @@ -440,7 +522,6 @@ function Folder({ folder, collapsed, selected = false, onCopyPath, onCopyRelativ ); } -// Add this interface after the FolderProps interface interface FileProps { file: FileNode; selected: boolean; @@ -461,7 +542,6 @@ function File({ fileHistory = {}, }: FileProps) { const { depth, name, fullPath } = file; - const parentPath = fullPath.substring(0, fullPath.lastIndexOf('/')); const fileModifications = fileHistory[fullPath]; @@ -503,7 +583,7 @@ function File({ const showStats = additions > 0 || deletions > 0; return ( - + filePath.startsWith(oldPath)) + .map(([filePath, dirent]) => ({ path: filePath, dirent })); + + for (const { path: filePath, dirent } of filesToMove) { + if (dirent?.type === 'file') { + const relativePath = filePath.substring(oldPath.length); + const newFilePath = path.join(newPath, relativePath); + + await this.createNewFile(newFilePath, dirent.content); + } + } + + await this.deleteFolder(oldPath); + + const selectedFile = this.selectedFile.get(); + + if (selectedFile && selectedFile.startsWith(oldPath)) { + const relativePath = selectedFile.substring(oldPath.length); + const newSelectedPath = path.join(newPath, relativePath); + this.setSelectedFile(newSelectedPath); + } + + return true; + } catch (error) { + console.error('Error renaming folder:', error); + return false; + } + } } export const workbenchStore = new WorkbenchStore();