mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
Remove FileList, FileOps and GitOps contexts
This commit is contained in:
@@ -1,14 +1,10 @@
|
|||||||
// App.js
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
|
import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import MainContent from './components/MainContent';
|
import MainContent from './components/MainContent';
|
||||||
import { SettingsProvider, useSettings } from './contexts/SettingsContext';
|
import { SettingsProvider, useSettings } from './contexts/SettingsContext';
|
||||||
import { ModalProvider } from './contexts/ModalContext';
|
import { ModalProvider } from './contexts/ModalContext';
|
||||||
import { GitOperationsProvider } from './contexts/GitOperationsContext';
|
|
||||||
import { FileListProvider } from './contexts/FileListContext';
|
|
||||||
import { FileSelectionProvider } from './contexts/FileSelectionContext';
|
import { FileSelectionProvider } from './contexts/FileSelectionContext';
|
||||||
import { FileOperationsProvider } from './contexts/FileOperationsContext';
|
|
||||||
import { EditorContentProvider } from './contexts/EditorContentContext';
|
import { EditorContentProvider } from './contexts/EditorContentContext';
|
||||||
import { FileManagementProvider } from './contexts/FileManagementContext';
|
import { FileManagementProvider } from './contexts/FileManagementContext';
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
@@ -37,19 +33,13 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
<GitOperationsProvider>
|
|
||||||
<FileListProvider>
|
|
||||||
<FileManagementProvider>
|
<FileManagementProvider>
|
||||||
<FileSelectionProvider>
|
<FileSelectionProvider>
|
||||||
<FileOperationsProvider>
|
|
||||||
<EditorContentProvider>
|
<EditorContentProvider>
|
||||||
<AppContent />
|
<AppContent />
|
||||||
</EditorContentProvider>
|
</EditorContentProvider>
|
||||||
</FileOperationsProvider>
|
|
||||||
</FileSelectionProvider>
|
</FileSelectionProvider>
|
||||||
</FileManagementProvider>
|
</FileManagementProvider>
|
||||||
</FileListProvider>
|
|
||||||
</GitOperationsProvider>
|
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core';
|
import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core';
|
||||||
import { Plus, Trash, GitPullRequest, GitCommit } from '@geist-ui/icons';
|
import { Plus, Trash, GitPullRequest, GitCommit } from '@geist-ui/icons';
|
||||||
import { useGitOperationsContext } from '../contexts/GitOperationsContext';
|
|
||||||
import { useSettings } from '../contexts/SettingsContext';
|
import { useSettings } from '../contexts/SettingsContext';
|
||||||
import { useFileSelection } from '../contexts/FileSelectionContext';
|
import { useFileSelection } from '../contexts/FileSelectionContext';
|
||||||
import { useModalContext } from '../contexts/ModalContext';
|
import { useModalContext } from '../contexts/ModalContext';
|
||||||
|
|
||||||
const FileActions = () => {
|
const FileActions = ({
|
||||||
|
onCreateFile,
|
||||||
|
onDeleteFile,
|
||||||
|
onPullChanges,
|
||||||
|
onCommitAndPush,
|
||||||
|
}) => {
|
||||||
const { selectedFile } = useFileSelection();
|
const { selectedFile } = useFileSelection();
|
||||||
const { pullLatestChanges } = useGitOperationsContext();
|
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
const {
|
const {
|
||||||
setNewFileModalVisible,
|
setNewFileModalVisible,
|
||||||
@@ -59,7 +62,7 @@ const FileActions = () => {
|
|||||||
icon={<GitPullRequest />}
|
icon={<GitPullRequest />}
|
||||||
auto
|
auto
|
||||||
scale={2 / 3}
|
scale={2 / 3}
|
||||||
onClick={pullLatestChanges}
|
onClick={onPullChanges}
|
||||||
disabled={!settings.gitEnabled}
|
disabled={!settings.gitEnabled}
|
||||||
px={0.6}
|
px={0.6}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tree } from '@geist-ui/core';
|
import { Tree } from '@geist-ui/core';
|
||||||
import { File, Folder, Image } from '@geist-ui/icons';
|
import { File, Folder, Image } from '@geist-ui/icons';
|
||||||
import { useFileListContext } from '../contexts/FileListContext';
|
|
||||||
import { isImageFile } from '../utils/fileHelpers';
|
import { isImageFile } from '../utils/fileHelpers';
|
||||||
import { useFileSelection } from '../contexts/FileSelectionContext';
|
import { useFileSelection } from '../contexts/FileSelectionContext';
|
||||||
|
|
||||||
const FileTree = () => {
|
const FileTree = ({ files }) => {
|
||||||
const { files } = useFileListContext();
|
|
||||||
const { selectedFile, handleFileSelect } = useFileSelection();
|
const { selectedFile, handleFileSelect } = useFileSelection();
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
|
|||||||
@@ -1,24 +1,91 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Grid, Breadcrumbs, Tabs, Dot } from '@geist-ui/core';
|
|
||||||
import { Code, Eye } from '@geist-ui/icons';
|
|
||||||
import FileTree from './FileTree';
|
|
||||||
import FileActions from './FileActions';
|
import FileActions from './FileActions';
|
||||||
|
import FileTree from './FileTree';
|
||||||
import ContentView from './ContentView';
|
import ContentView from './ContentView';
|
||||||
import CreateFileModal from './modals/CreateFileModal';
|
import CreateFileModal from './modals/CreateFileModal';
|
||||||
import DeleteFileModal from './modals/DeleteFileModal';
|
import DeleteFileModal from './modals/DeleteFileModal';
|
||||||
import CommitMessageModal from './modals/CommitMessageModal';
|
import CommitMessageModal from './modals/CommitMessageModal';
|
||||||
import { useEditorContent } from '../contexts/EditorContentContext';
|
import { useEditorContent } from '../contexts/EditorContentContext';
|
||||||
import { useFileSelection } from '../contexts/FileSelectionContext';
|
import { useFileSelection } from '../contexts/FileSelectionContext';
|
||||||
|
import { useSettings } from '../contexts/SettingsContext';
|
||||||
|
import { useFileOperations } from '../hooks/useFileOperations';
|
||||||
|
import { pullChanges, commitAndPush, fetchFileList } from '../services/api';
|
||||||
|
import { Breadcrumbs, Grid, Tabs, useToasts } from '@geist-ui/core';
|
||||||
|
import { Code, Eye } from '@geist-ui/icons';
|
||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
const MainContent = () => {
|
const MainContent = () => {
|
||||||
const [activeTab, setActiveTab] = useState('source');
|
const [activeTab, setActiveTab] = useState('source');
|
||||||
|
const [files, setFiles] = useState([]);
|
||||||
const { hasUnsavedChanges } = useEditorContent();
|
const { hasUnsavedChanges } = useEditorContent();
|
||||||
const { selectedFile } = useFileSelection();
|
const { selectedFile } = useFileSelection();
|
||||||
|
const { settings } = useSettings();
|
||||||
|
const { handleCreate, handleDelete } = useFileOperations();
|
||||||
|
const { setToast } = useToasts();
|
||||||
|
|
||||||
|
const refreshFileList = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const fileList = await fetchFileList();
|
||||||
|
setFiles(fileList);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch file list:', error);
|
||||||
|
setToast({ text: 'Failed to refresh file list', type: 'error' });
|
||||||
|
}
|
||||||
|
}, [setToast]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshFileList();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleTabChange = (value) => {
|
const handleTabChange = (value) => {
|
||||||
setActiveTab(value);
|
setActiveTab(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pullLatestChanges = useCallback(async () => {
|
||||||
|
if (!settings.gitEnabled) return;
|
||||||
|
try {
|
||||||
|
await pullChanges();
|
||||||
|
await refreshFileList();
|
||||||
|
setToast({ text: 'Successfully pulled latest changes', type: 'success' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to pull latest changes:', error);
|
||||||
|
setToast({ text: 'Failed to pull latest changes', type: 'error' });
|
||||||
|
}
|
||||||
|
}, [settings.gitEnabled, setToast, refreshFileList]);
|
||||||
|
|
||||||
|
const handleCommitAndPush = useCallback(
|
||||||
|
async (message) => {
|
||||||
|
if (!settings.gitEnabled) return;
|
||||||
|
try {
|
||||||
|
await commitAndPush(message);
|
||||||
|
setToast({
|
||||||
|
text: 'Successfully committed and pushed changes',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to commit and push changes:', error);
|
||||||
|
setToast({ text: 'Failed to commit and push changes', type: 'error' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[settings.gitEnabled, setToast]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreateFile = useCallback(
|
||||||
|
async (fileName) => {
|
||||||
|
await handleCreate(fileName);
|
||||||
|
await refreshFileList();
|
||||||
|
},
|
||||||
|
[handleCreate, refreshFileList]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteFile = useCallback(
|
||||||
|
async (filePath) => {
|
||||||
|
await handleDelete(filePath);
|
||||||
|
await refreshFileList();
|
||||||
|
},
|
||||||
|
[handleDelete, refreshFileList]
|
||||||
|
);
|
||||||
|
|
||||||
const renderBreadcrumbs = () => {
|
const renderBreadcrumbs = () => {
|
||||||
if (!selectedFile) return <div className="breadcrumbs-container"></div>;
|
if (!selectedFile) return <div className="breadcrumbs-container"></div>;
|
||||||
const pathParts = selectedFile.split('/');
|
const pathParts = selectedFile.split('/');
|
||||||
@@ -41,8 +108,13 @@ const MainContent = () => {
|
|||||||
<Grid.Container gap={1} height="calc(100vh - 64px)">
|
<Grid.Container gap={1} height="calc(100vh - 64px)">
|
||||||
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
|
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
|
||||||
<div className="file-tree-container">
|
<div className="file-tree-container">
|
||||||
<FileActions />
|
<FileActions
|
||||||
<FileTree />
|
onCreateFile={handleCreateFile}
|
||||||
|
onDeleteFile={handleDeleteFile}
|
||||||
|
onPullChanges={pullLatestChanges}
|
||||||
|
onCommitAndPush={handleCommitAndPush}
|
||||||
|
/>
|
||||||
|
<FileTree files={files} />
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
@@ -65,9 +137,9 @@ const MainContent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid.Container>
|
</Grid.Container>
|
||||||
<CreateFileModal />
|
<CreateFileModal onCreateFile={handleCreateFile} />
|
||||||
<DeleteFileModal />
|
<DeleteFileModal onDeleteFile={handleDeleteFile} />
|
||||||
<CommitMessageModal />
|
<CommitMessageModal onCommitAndPush={handleCommitAndPush} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, Input } from '@geist-ui/core';
|
import { Modal, Input } from '@geist-ui/core';
|
||||||
import { useGitOperationsContext } from '../../contexts/GitOperationsContext';
|
|
||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../contexts/ModalContext';
|
||||||
|
|
||||||
const CommitMessageModal = () => {
|
const CommitMessageModal = ({ onCommitAndPush }) => {
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const { handleCommitAndPush } = useGitOperationsContext();
|
|
||||||
const { commitMessageModalVisible, setCommitMessageModalVisible } =
|
const { commitMessageModalVisible, setCommitMessageModalVisible } =
|
||||||
useModalContext();
|
useModalContext();
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (message) {
|
if (message) {
|
||||||
await handleCommitAndPush(message);
|
await onCommitAndPush(message);
|
||||||
setMessage('');
|
setMessage('');
|
||||||
setCommitMessageModalVisible(false);
|
setCommitMessageModalVisible(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, Input } from '@geist-ui/core';
|
import { Modal, Input } from '@geist-ui/core';
|
||||||
import { useFileOperations } from '../../contexts/FileOperationsContext';
|
|
||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../contexts/ModalContext';
|
||||||
|
|
||||||
const CreateFileModal = () => {
|
const CreateFileModal = ({ onCreateFile }) => {
|
||||||
const [fileName, setFileName] = useState('');
|
const [fileName, setFileName] = useState('');
|
||||||
const { newFileModalVisible, setNewFileModalVisible } = useModalContext();
|
const { newFileModalVisible, setNewFileModalVisible } = useModalContext();
|
||||||
const { handleCreateNewFile } = useFileOperations();
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (fileName) {
|
if (fileName) {
|
||||||
await handleCreateNewFile(fileName);
|
await onCreateFile(fileName);
|
||||||
setFileName('');
|
setFileName('');
|
||||||
setNewFileModalVisible(false);
|
setNewFileModalVisible(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { Modal, Text } from '@geist-ui/core';
|
|||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../contexts/ModalContext';
|
||||||
import { useFileSelection } from '../../contexts/FileSelectionContext';
|
import { useFileSelection } from '../../contexts/FileSelectionContext';
|
||||||
|
|
||||||
const DeleteFileModal = () => {
|
const DeleteFileModal = ({ onDeleteFile }) => {
|
||||||
const { selectedFile, handleDeleteFile } = useFileSelection();
|
const { selectedFile } = useFileSelection();
|
||||||
const { deleteFileModalVisible, setDeleteFileModalVisible } =
|
const { deleteFileModalVisible, setDeleteFileModalVisible } =
|
||||||
useModalContext();
|
useModalContext();
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
await handleDeleteFile();
|
await onDeleteFile(selectedFile);
|
||||||
setDeleteFileModalVisible(false);
|
setDeleteFileModalVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import React, { createContext, useContext, useMemo } from 'react';
|
|
||||||
import { useFileList } from '../hooks/useFileList';
|
|
||||||
|
|
||||||
const FileListContext = createContext();
|
|
||||||
|
|
||||||
export const FileListProvider = ({ children }) => {
|
|
||||||
const { files, loadFileList } = useFileList();
|
|
||||||
|
|
||||||
const value = useMemo(() => ({ files, loadFileList }), [files, loadFileList]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FileListContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</FileListContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFileListContext = () => {
|
|
||||||
const context = useContext(FileListContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'useFileListContext must be used within a FileListProvider'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import React, { createContext, useContext, useMemo } from 'react';
|
|
||||||
import { useFileContent } from '../hooks/useFileContent';
|
|
||||||
|
|
||||||
const FileOperationsContext = createContext();
|
|
||||||
|
|
||||||
export const FileOperationsProvider = ({ children }) => {
|
|
||||||
const { handleCreateNewFile, handleDeleteFile } = useFileContent();
|
|
||||||
|
|
||||||
const value = useMemo(
|
|
||||||
() => ({
|
|
||||||
handleCreateNewFile,
|
|
||||||
handleDeleteFile,
|
|
||||||
}),
|
|
||||||
[handleCreateNewFile, handleDeleteFile]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FileOperationsContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</FileOperationsContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFileOperations = () => {
|
|
||||||
const context = useContext(FileOperationsContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'useFileOperations must be used within a FileOperationsProvider'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import React, { createContext, useContext } from 'react';
|
|
||||||
import { useGitOperations } from '../hooks/useGitOperations';
|
|
||||||
|
|
||||||
const GitOperationsContext = createContext();
|
|
||||||
|
|
||||||
export const GitOperationsProvider = ({ children }) => {
|
|
||||||
const gitOperationsHook = useGitOperations();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GitOperationsContext.Provider value={gitOperationsHook}>
|
|
||||||
{children}
|
|
||||||
</GitOperationsContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGitOperationsContext = () => {
|
|
||||||
const context = useContext(GitOperationsContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error(
|
|
||||||
'useGitOperationsContext must be used within a GitOperationsProvider'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useCallback } from 'react';
|
import { useEffect, useCallback } from 'react';
|
||||||
import { useFileSelection } from './useFileSelection';
|
import { useFileSelection } from './useFileSelection';
|
||||||
import { useFileContent } from './useFileContent';
|
import { useFileContent } from './useFileContent';
|
||||||
import { useFileOperations } from './useFileOperations';
|
|
||||||
|
|
||||||
export const useFileManagement = () => {
|
export const useFileManagement = () => {
|
||||||
const { selectedFile, isNewFile, handleFileSelect } = useFileSelection();
|
const { selectedFile, isNewFile, handleFileSelect } = useFileSelection();
|
||||||
@@ -12,10 +11,7 @@ export const useFileManagement = () => {
|
|||||||
hasUnsavedChanges,
|
hasUnsavedChanges,
|
||||||
loadFileContent,
|
loadFileContent,
|
||||||
handleContentChange,
|
handleContentChange,
|
||||||
setHasUnsavedChanges,
|
|
||||||
} = useFileContent();
|
} = useFileContent();
|
||||||
const { handleSave, handleDelete, handleCreateNewFile } =
|
|
||||||
useFileOperations(setHasUnsavedChanges);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
@@ -40,8 +36,5 @@ export const useFileManagement = () => {
|
|||||||
hasUnsavedChanges,
|
hasUnsavedChanges,
|
||||||
handleFileSelect: handleFileSelectAndLoad,
|
handleFileSelect: handleFileSelectAndLoad,
|
||||||
handleContentChange,
|
handleContentChange,
|
||||||
handleSave: (filePath) => handleSave(filePath, content),
|
|
||||||
handleDelete,
|
|
||||||
handleCreateNewFile,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export const useFileNavigation = () => {
|
|||||||
handleFileSelect(filePaths[0]);
|
handleFileSelect(filePaths[0]);
|
||||||
} else if (filePaths.length > 1) {
|
} else if (filePaths.length > 1) {
|
||||||
// Handle multiple file options (you may want to show a modal or dropdown)
|
// Handle multiple file options (you may want to show a modal or dropdown)
|
||||||
console.log('Multiple files found:', filePaths);
|
|
||||||
setToast({
|
setToast({
|
||||||
text: 'Multiple files found with the same name. Please specify the full path.',
|
text: 'Multiple files found with the same name. Please specify the full path.',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ export const useFileOperations = (setHasUnsavedChanges) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCreateNewFile = useCallback(
|
const handleCreate = useCallback(async (fileName, initialContent = '') => {
|
||||||
async (fileName, initialContent = '') => {
|
|
||||||
try {
|
try {
|
||||||
await saveFileContent(fileName, initialContent);
|
await saveFileContent(fileName, initialContent);
|
||||||
return true;
|
return true;
|
||||||
@@ -42,9 +41,7 @@ export const useFileOperations = (setHasUnsavedChanges) => {
|
|||||||
console.error('Error creating new file:', error);
|
console.error('Error creating new file:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
}, []);
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return { handleSave, handleDelete, handleCreateNewFile };
|
return { handleSave, handleDelete, handleCreate };
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user