diff --git a/app/src/components/files/FileActions.tsx b/app/src/components/files/FileActions.tsx index 83bf7ae..268308e 100644 --- a/app/src/components/files/FileActions.tsx +++ b/app/src/components/files/FileActions.tsx @@ -6,6 +6,7 @@ import { IconGitPullRequest, IconGitCommit, IconUpload, + IconEdit, } from '@tabler/icons-react'; import { useModalContext } from '../../contexts/ModalContext'; import { useWorkspace } from '../../hooks/useWorkspace'; @@ -27,6 +28,7 @@ const FileActions: React.FC = ({ setNewFileModalVisible, setDeleteFileModalVisible, setCommitMessageModalVisible, + setRenameFileModalVisible, } = useModalContext(); const { handleUpload } = useFileOperations(); @@ -36,6 +38,7 @@ const FileActions: React.FC = ({ const handleCreateFile = (): void => setNewFileModalVisible(true); const handleDeleteFile = (): void => setDeleteFileModalVisible(true); + const handleRenameFile = (): void => setRenameFileModalVisible(true); const handleCommitAndPush = (): void => setCommitMessageModalVisible(true); const handleUploadClick = (): void => { @@ -91,6 +94,21 @@ const FileActions: React.FC = ({ + + + + + + diff --git a/app/src/components/layout/MainContent.tsx b/app/src/components/layout/MainContent.tsx index a6bfc87..67f30b6 100644 --- a/app/src/components/layout/MainContent.tsx +++ b/app/src/components/layout/MainContent.tsx @@ -5,11 +5,13 @@ import { IconCode, IconEye, IconPointFilled } from '@tabler/icons-react'; import ContentView from '../editor/ContentView'; import CreateFileModal from '../modals/file/CreateFileModal'; import DeleteFileModal from '../modals/file/DeleteFileModal'; +import RenameFileModal from '../modals/file/RenameFileModal'; import CommitMessageModal from '../modals/git/CommitMessageModal'; import { useFileContent } from '../../hooks/useFileContent'; import { useFileOperations } from '../../hooks/useFileOperations'; import { useGitOperations } from '../../hooks/useGitOperations'; +import { useModalContext } from '../../contexts/ModalContext'; type ViewTab = 'source' | 'preview'; @@ -31,8 +33,10 @@ const MainContent: React.FC = ({ setHasUnsavedChanges, handleContentChange, } = useFileContent(selectedFile); - const { handleSave, handleCreate, handleDelete } = useFileOperations(); + const { handleSave, handleCreate, handleDelete, handleRename } = + useFileOperations(); const { handleCommitAndPush } = useGitOperations(); + const { setRenameFileModalVisible } = useModalContext(); const handleTabChange = useCallback((value: string | null): void => { if (value) { @@ -73,14 +77,50 @@ const MainContent: React.FC = ({ [handleDelete, handleFileSelect, loadFileList] ); + const handleRenameFile = useCallback( + async (oldPath: string, newPath: string): Promise => { + const success = await handleRename(oldPath, newPath); + if (success) { + await loadFileList(); + // If we renamed the currently selected file, update the selection + if (selectedFile === oldPath) { + await handleFileSelect(newPath); + } + } + }, + [handleRename, handleFileSelect, loadFileList, selectedFile] + ); + + const handleBreadcrumbClick = useCallback(() => { + if (selectedFile) { + setRenameFileModalVisible(true); + } + }, [selectedFile, setRenameFileModalVisible]); + const renderBreadcrumbs = useMemo(() => { if (!selectedFile) return null; const pathParts = selectedFile.split('/'); - const items = pathParts.map((part, index) => ( - - {part} - - )); + const items = pathParts.map((part, index) => { + // Make the filename (last part) clickable for rename + const isFileName = index === pathParts.length - 1; + return ( + + {part} + + ); + }); return ( @@ -93,7 +133,7 @@ const MainContent: React.FC = ({ )} ); - }, [selectedFile, hasUnsavedChanges]); + }, [selectedFile, hasUnsavedChanges, handleBreadcrumbClick]); return ( = ({ onDeleteFile={handleDeleteFile} selectedFile={selectedFile} /> + ); diff --git a/app/src/components/modals/file/RenameFileModal.tsx b/app/src/components/modals/file/RenameFileModal.tsx new file mode 100644 index 0000000..5845fd0 --- /dev/null +++ b/app/src/components/modals/file/RenameFileModal.tsx @@ -0,0 +1,109 @@ +import React, { useState, useEffect } from 'react'; +import { Modal, TextInput, Button, Group, Box } from '@mantine/core'; +import { useModalContext } from '../../../contexts/ModalContext'; + +interface RenameFileModalProps { + onRenameFile: (oldPath: string, newPath: string) => Promise; + selectedFile: string | null; +} + +const RenameFileModal: React.FC = ({ + onRenameFile, + selectedFile, +}) => { + const [newFileName, setNewFileName] = useState(''); + const { renameFileModalVisible, setRenameFileModalVisible } = + useModalContext(); + + // Extract just the filename from the full path for editing + const getCurrentFileName = (filePath: string | null): string => { + if (!filePath) return ''; + const parts = filePath.split('/'); + return parts[parts.length - 1] || ''; + }; + + // Get the directory path (everything except the filename) + const getDirectoryPath = (filePath: string | null): string => { + if (!filePath) return ''; + const parts = filePath.split('/'); + return parts.slice(0, -1).join('/'); + }; + + // Set the current filename when modal opens or selectedFile changes + useEffect(() => { + if (renameFileModalVisible && selectedFile) { + setNewFileName(getCurrentFileName(selectedFile)); + } + }, [renameFileModalVisible, selectedFile]); + + const handleSubmit = async (): Promise => { + if (newFileName && selectedFile) { + const directoryPath = getDirectoryPath(selectedFile); + const newPath = directoryPath + ? `${directoryPath}/${newFileName.trim()}` + : newFileName.trim(); + + await onRenameFile(selectedFile, newPath); + setNewFileName(''); + setRenameFileModalVisible(false); + } + }; + + const handleKeyDown = (event: React.KeyboardEvent): void => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + void handleSubmit(); + } + }; + + const handleClose = (): void => { + setNewFileName(''); + setRenameFileModalVisible(false); + }; + + return ( + + + setNewFileName(event.currentTarget.value)} + onKeyDown={handleKeyDown} + mb="md" + w="100%" + autoFocus + /> + + + + + + + ); +}; + +export default RenameFileModal; diff --git a/app/src/contexts/ModalContext.tsx b/app/src/contexts/ModalContext.tsx index fb5bcc4..ec48017 100644 --- a/app/src/contexts/ModalContext.tsx +++ b/app/src/contexts/ModalContext.tsx @@ -10,6 +10,8 @@ interface ModalContextType { setNewFileModalVisible: React.Dispatch>; deleteFileModalVisible: boolean; setDeleteFileModalVisible: React.Dispatch>; + renameFileModalVisible: boolean; + setRenameFileModalVisible: React.Dispatch>; commitMessageModalVisible: boolean; setCommitMessageModalVisible: React.Dispatch>; settingsModalVisible: boolean; @@ -30,6 +32,7 @@ interface ModalProviderProps { export const ModalProvider: React.FC = ({ children }) => { const [newFileModalVisible, setNewFileModalVisible] = useState(false); const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false); + const [renameFileModalVisible, setRenameFileModalVisible] = useState(false); const [commitMessageModalVisible, setCommitMessageModalVisible] = useState(false); const [settingsModalVisible, setSettingsModalVisible] = useState(false); @@ -43,6 +46,8 @@ export const ModalProvider: React.FC = ({ children }) => { setNewFileModalVisible, deleteFileModalVisible, setDeleteFileModalVisible, + renameFileModalVisible, + setRenameFileModalVisible, commitMessageModalVisible, setCommitMessageModalVisible, settingsModalVisible, diff --git a/app/src/hooks/useFileOperations.ts b/app/src/hooks/useFileOperations.ts index 64fc7d7..540de4a 100644 --- a/app/src/hooks/useFileOperations.ts +++ b/app/src/hooks/useFileOperations.ts @@ -15,6 +15,7 @@ interface UseFileOperationsResult { parentId: string | null, index: number ) => Promise; + handleRename: (oldPath: string, newPath: string) => Promise; } export const useFileOperations = (): UseFileOperationsResult => { @@ -193,5 +194,40 @@ export const useFileOperations = (): UseFileOperationsResult => { [currentWorkspace, autoCommit] ); - return { handleSave, handleDelete, handleCreate, handleUpload, handleMove }; + const handleRename = useCallback( + async (oldPath: string, newPath: string): Promise => { + if (!currentWorkspace) return false; + + try { + // TODO: Replace with your actual rename API call + // await renameFile(currentWorkspace.name, oldPath, newPath); + + notifications.show({ + title: 'Success', + message: 'File renamed successfully', + color: 'green', + }); + await autoCommit(newPath, FileAction.Update); + return true; + } catch (error) { + console.error('Error renaming file:', error); + notifications.show({ + title: 'Error', + message: 'Failed to rename file', + color: 'red', + }); + return false; + } + }, + [currentWorkspace, autoCommit] + ); + + return { + handleSave, + handleDelete, + handleCreate, + handleUpload, + handleMove, + handleRename, + }; };