Add rename file functionality with modal support

This commit is contained in:
2025-07-07 20:18:36 +02:00
parent 4a3df3a040
commit a789c62a68
5 changed files with 220 additions and 8 deletions

View File

@@ -6,6 +6,7 @@ import {
IconGitPullRequest, IconGitPullRequest,
IconGitCommit, IconGitCommit,
IconUpload, IconUpload,
IconEdit,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useModalContext } from '../../contexts/ModalContext'; import { useModalContext } from '../../contexts/ModalContext';
import { useWorkspace } from '../../hooks/useWorkspace'; import { useWorkspace } from '../../hooks/useWorkspace';
@@ -27,6 +28,7 @@ const FileActions: React.FC<FileActionsProps> = ({
setNewFileModalVisible, setNewFileModalVisible,
setDeleteFileModalVisible, setDeleteFileModalVisible,
setCommitMessageModalVisible, setCommitMessageModalVisible,
setRenameFileModalVisible,
} = useModalContext(); } = useModalContext();
const { handleUpload } = useFileOperations(); const { handleUpload } = useFileOperations();
@@ -36,6 +38,7 @@ const FileActions: React.FC<FileActionsProps> = ({
const handleCreateFile = (): void => setNewFileModalVisible(true); const handleCreateFile = (): void => setNewFileModalVisible(true);
const handleDeleteFile = (): void => setDeleteFileModalVisible(true); const handleDeleteFile = (): void => setDeleteFileModalVisible(true);
const handleRenameFile = (): void => setRenameFileModalVisible(true);
const handleCommitAndPush = (): void => setCommitMessageModalVisible(true); const handleCommitAndPush = (): void => setCommitMessageModalVisible(true);
const handleUploadClick = (): void => { const handleUploadClick = (): void => {
@@ -91,6 +94,21 @@ const FileActions: React.FC<FileActionsProps> = ({
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip
label={selectedFile ? 'Rename current file' : 'No file selected'}
>
<ActionIcon
variant="default"
size="md"
onClick={handleRenameFile}
disabled={!selectedFile}
aria-label="Rename current file"
data-testid="rename-file-button"
>
<IconEdit size={16} />
</ActionIcon>
</Tooltip>
<Tooltip <Tooltip
label={selectedFile ? 'Delete current file' : 'No file selected'} label={selectedFile ? 'Delete current file' : 'No file selected'}
> >

View File

@@ -5,11 +5,13 @@ import { IconCode, IconEye, IconPointFilled } from '@tabler/icons-react';
import ContentView from '../editor/ContentView'; import ContentView from '../editor/ContentView';
import CreateFileModal from '../modals/file/CreateFileModal'; import CreateFileModal from '../modals/file/CreateFileModal';
import DeleteFileModal from '../modals/file/DeleteFileModal'; import DeleteFileModal from '../modals/file/DeleteFileModal';
import RenameFileModal from '../modals/file/RenameFileModal';
import CommitMessageModal from '../modals/git/CommitMessageModal'; import CommitMessageModal from '../modals/git/CommitMessageModal';
import { useFileContent } from '../../hooks/useFileContent'; import { useFileContent } from '../../hooks/useFileContent';
import { useFileOperations } from '../../hooks/useFileOperations'; import { useFileOperations } from '../../hooks/useFileOperations';
import { useGitOperations } from '../../hooks/useGitOperations'; import { useGitOperations } from '../../hooks/useGitOperations';
import { useModalContext } from '../../contexts/ModalContext';
type ViewTab = 'source' | 'preview'; type ViewTab = 'source' | 'preview';
@@ -31,8 +33,10 @@ const MainContent: React.FC<MainContentProps> = ({
setHasUnsavedChanges, setHasUnsavedChanges,
handleContentChange, handleContentChange,
} = useFileContent(selectedFile); } = useFileContent(selectedFile);
const { handleSave, handleCreate, handleDelete } = useFileOperations(); const { handleSave, handleCreate, handleDelete, handleRename } =
useFileOperations();
const { handleCommitAndPush } = useGitOperations(); const { handleCommitAndPush } = useGitOperations();
const { setRenameFileModalVisible } = useModalContext();
const handleTabChange = useCallback((value: string | null): void => { const handleTabChange = useCallback((value: string | null): void => {
if (value) { if (value) {
@@ -73,14 +77,50 @@ const MainContent: React.FC<MainContentProps> = ({
[handleDelete, handleFileSelect, loadFileList] [handleDelete, handleFileSelect, loadFileList]
); );
const handleRenameFile = useCallback(
async (oldPath: string, newPath: string): Promise<void> => {
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(() => { const renderBreadcrumbs = useMemo(() => {
if (!selectedFile) return null; if (!selectedFile) return null;
const pathParts = selectedFile.split('/'); const pathParts = selectedFile.split('/');
const items = pathParts.map((part, index) => ( const items = pathParts.map((part, index) => {
<Text key={index} size="sm"> // Make the filename (last part) clickable for rename
{part} const isFileName = index === pathParts.length - 1;
</Text> return (
)); <Text
key={index}
size="sm"
style={{
cursor: isFileName ? 'pointer' : 'default',
...(isFileName && {
textDecoration: 'underline',
textDecorationStyle: 'dotted',
}),
}}
onClick={isFileName ? handleBreadcrumbClick : undefined}
title={isFileName ? 'Click to rename file' : undefined}
>
{part}
</Text>
);
});
return ( return (
<Group> <Group>
@@ -93,7 +133,7 @@ const MainContent: React.FC<MainContentProps> = ({
)} )}
</Group> </Group>
); );
}, [selectedFile, hasUnsavedChanges]); }, [selectedFile, hasUnsavedChanges, handleBreadcrumbClick]);
return ( return (
<Box <Box
@@ -128,6 +168,10 @@ const MainContent: React.FC<MainContentProps> = ({
onDeleteFile={handleDeleteFile} onDeleteFile={handleDeleteFile}
selectedFile={selectedFile} selectedFile={selectedFile}
/> />
<RenameFileModal
onRenameFile={handleRenameFile}
selectedFile={selectedFile}
/>
<CommitMessageModal onCommitAndPush={handleCommitAndPush} /> <CommitMessageModal onCommitAndPush={handleCommitAndPush} />
</Box> </Box>
); );

View File

@@ -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<void>;
selectedFile: string | null;
}
const RenameFileModal: React.FC<RenameFileModalProps> = ({
onRenameFile,
selectedFile,
}) => {
const [newFileName, setNewFileName] = useState<string>('');
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<void> => {
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 (
<Modal
opened={renameFileModalVisible}
onClose={handleClose}
title="Rename File"
centered
size="sm"
>
<Box maw={400} mx="auto">
<TextInput
label="File Name"
type="text"
placeholder="Enter new file name"
data-testid="rename-file-input"
value={newFileName}
onChange={(event) => setNewFileName(event.currentTarget.value)}
onKeyDown={handleKeyDown}
mb="md"
w="100%"
autoFocus
/>
<Group justify="flex-end" mt="xl">
<Button
variant="default"
onClick={handleClose}
data-testid="cancel-rename-file-button"
>
Cancel
</Button>
<Button
onClick={() => void handleSubmit()}
data-testid="confirm-rename-file-button"
disabled={
!newFileName.trim() ||
newFileName.trim() === getCurrentFileName(selectedFile)
}
>
Rename
</Button>
</Group>
</Box>
</Modal>
);
};
export default RenameFileModal;

View File

@@ -10,6 +10,8 @@ interface ModalContextType {
setNewFileModalVisible: React.Dispatch<React.SetStateAction<boolean>>; setNewFileModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
deleteFileModalVisible: boolean; deleteFileModalVisible: boolean;
setDeleteFileModalVisible: React.Dispatch<React.SetStateAction<boolean>>; setDeleteFileModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
renameFileModalVisible: boolean;
setRenameFileModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
commitMessageModalVisible: boolean; commitMessageModalVisible: boolean;
setCommitMessageModalVisible: React.Dispatch<React.SetStateAction<boolean>>; setCommitMessageModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
settingsModalVisible: boolean; settingsModalVisible: boolean;
@@ -30,6 +32,7 @@ interface ModalProviderProps {
export const ModalProvider: React.FC<ModalProviderProps> = ({ children }) => { export const ModalProvider: React.FC<ModalProviderProps> = ({ children }) => {
const [newFileModalVisible, setNewFileModalVisible] = useState(false); const [newFileModalVisible, setNewFileModalVisible] = useState(false);
const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false); const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
const [renameFileModalVisible, setRenameFileModalVisible] = useState(false);
const [commitMessageModalVisible, setCommitMessageModalVisible] = const [commitMessageModalVisible, setCommitMessageModalVisible] =
useState(false); useState(false);
const [settingsModalVisible, setSettingsModalVisible] = useState(false); const [settingsModalVisible, setSettingsModalVisible] = useState(false);
@@ -43,6 +46,8 @@ export const ModalProvider: React.FC<ModalProviderProps> = ({ children }) => {
setNewFileModalVisible, setNewFileModalVisible,
deleteFileModalVisible, deleteFileModalVisible,
setDeleteFileModalVisible, setDeleteFileModalVisible,
renameFileModalVisible,
setRenameFileModalVisible,
commitMessageModalVisible, commitMessageModalVisible,
setCommitMessageModalVisible, setCommitMessageModalVisible,
settingsModalVisible, settingsModalVisible,

View File

@@ -15,6 +15,7 @@ interface UseFileOperationsResult {
parentId: string | null, parentId: string | null,
index: number index: number
) => Promise<boolean>; ) => Promise<boolean>;
handleRename: (oldPath: string, newPath: string) => Promise<boolean>;
} }
export const useFileOperations = (): UseFileOperationsResult => { export const useFileOperations = (): UseFileOperationsResult => {
@@ -193,5 +194,40 @@ export const useFileOperations = (): UseFileOperationsResult => {
[currentWorkspace, autoCommit] [currentWorkspace, autoCommit]
); );
return { handleSave, handleDelete, handleCreate, handleUpload, handleMove }; const handleRename = useCallback(
async (oldPath: string, newPath: string): Promise<boolean> => {
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,
};
}; };