Remove more contexts

This commit is contained in:
2024-10-05 20:50:55 +02:00
parent 5ea932c96e
commit 9de434ff2e
19 changed files with 105 additions and 291 deletions

View File

@@ -4,9 +4,6 @@ import Header from './components/Header';
import MainContent from './components/MainContent';
import { SettingsProvider, useSettings } from './contexts/SettingsContext';
import { ModalProvider } from './contexts/ModalContext';
import { FileSelectionProvider } from './contexts/FileSelectionContext';
import { EditorContentProvider } from './contexts/EditorContentContext';
import { FileManagementProvider } from './contexts/FileManagementContext';
import './App.scss';
function AppContent() {
@@ -33,13 +30,7 @@ function App() {
return (
<SettingsProvider>
<ModalProvider>
<FileManagementProvider>
<FileSelectionProvider>
<EditorContentProvider>
<AppContent />
</EditorContentProvider>
</FileSelectionProvider>
</FileManagementProvider>
</ModalProvider>
</SettingsProvider>
);

View File

@@ -4,11 +4,15 @@ import MarkdownPreview from './MarkdownPreview';
import { Text } from '@geist-ui/core';
import { getFileUrl } from '../services/api';
import { isImageFile } from '../utils/fileHelpers';
import { useFileSelection } from '../contexts/FileSelectionContext';
const ContentView = ({ activeTab }) => {
const { selectedFile } = useFileSelection();
const ContentView = ({
activeTab,
selectedFile,
content,
handleContentChange,
handleSave,
handleLinkClick,
}) => {
if (!selectedFile) {
return (
<div
@@ -40,7 +44,16 @@ const ContentView = ({ activeTab }) => {
);
}
return activeTab === 'source' ? <Editor /> : <MarkdownPreview />;
return activeTab === 'source' ? (
<Editor
content={content}
handleContentChange={handleContentChange}
handleSave={handleSave}
selectedFile={selectedFile}
/>
) : (
<MarkdownPreview content={content} handleLinkClick={handleLinkClick} />
);
};
export default ContentView;

View File

@@ -6,12 +6,8 @@ import { markdown } from '@codemirror/lang-markdown';
import { defaultKeymap } from '@codemirror/commands';
import { oneDark } from '@codemirror/theme-one-dark';
import { useSettings } from '../contexts/SettingsContext';
import { useFileSelection } from '../contexts/FileSelectionContext';
import { useEditorContent } from '../contexts/EditorContentContext';
const Editor = () => {
const { content, handleContentChange, handleSave } = useEditorContent();
const { selectedFile } = useFileSelection();
const Editor = (content, handleContentChange, handleSave, selectedFile) => {
const { settings } = useSettings();
const editorRef = useRef();
const viewRef = useRef();

View File

@@ -2,16 +2,9 @@ import React from 'react';
import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core';
import { Plus, Trash, GitPullRequest, GitCommit } from '@geist-ui/icons';
import { useSettings } from '../contexts/SettingsContext';
import { useFileSelection } from '../contexts/FileSelectionContext';
import { useModalContext } from '../contexts/ModalContext';
const FileActions = ({
onCreateFile,
onDeleteFile,
onPullChanges,
onCommitAndPush,
}) => {
const { selectedFile } = useFileSelection();
const FileActions = ({ handlePullChanges, selectedFile }) => {
const { settings } = useSettings();
const {
setNewFileModalVisible,
@@ -62,7 +55,7 @@ const FileActions = ({
icon={<GitPullRequest />}
auto
scale={2 / 3}
onClick={onPullChanges}
onClick={handlePullChanges}
disabled={!settings.gitEnabled}
px={0.6}
/>

View File

@@ -2,11 +2,8 @@ import React from 'react';
import { Tree } from '@geist-ui/core';
import { File, Folder, Image } from '@geist-ui/icons';
import { isImageFile } from '../utils/fileHelpers';
import { useFileSelection } from '../contexts/FileSelectionContext';
const FileTree = ({ files }) => {
const { selectedFile, handleFileSelect } = useFileSelection();
const FileTree = ({ files, selectedFile, handleFileSelect }) => {
if (files.length === 0) {
return <div>No files to display</div>;
}

View File

@@ -1,89 +1,52 @@
import React from 'react';
import { useState, useCallback, useEffect } from 'react';
import { Breadcrumbs, Grid, Tabs } from '@geist-ui/core';
import { Code, Eye } from '@geist-ui/icons';
import FileActions from './FileActions';
import FileTree from './FileTree';
import ContentView from './ContentView';
import CreateFileModal from './modals/CreateFileModal';
import DeleteFileModal from './modals/DeleteFileModal';
import CommitMessageModal from './modals/CommitMessageModal';
import { useEditorContent } from '../contexts/EditorContentContext';
import { useFileSelection } from '../contexts/FileSelectionContext';
import { useSettings } from '../contexts/SettingsContext';
import { useFileContent } from '../hooks/useFileContent';
import { useFileList } from '../hooks/useFileList';
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';
import { useGitOperations } from '../hooks/useGitOperations';
import { useFileNavigation } from '../hooks/useFileNavigation';
const MainContent = () => {
const [activeTab, setActiveTab] = useState('source');
const [files, setFiles] = useState([]);
const { hasUnsavedChanges } = useEditorContent();
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]);
const [files, loadFileList] = useFileList();
const { content, hasUnsavedChanges, handleContentChange } = useFileContent();
const { handleSave, handleCreate, handleDelete } = useFileOperations();
const { handleCommitAndPush, handlePull } = useGitOperations();
const { handleLinkClick, selectedFile, isNewFile, handleFileSelect } =
useFileNavigation();
useEffect(() => {
refreshFileList();
loadFileList();
}, []);
const handleTabChange = (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();
await loadFileList();
},
[handleCreate, refreshFileList]
[handleCreate]
);
const handleDeleteFile = useCallback(
async (filePath) => {
await handleDelete(filePath);
await refreshFileList();
await loadFileList();
},
[handleDelete, refreshFileList]
[handleDelete]
);
const renderBreadcrumbs = () => {
@@ -109,12 +72,14 @@ const MainContent = () => {
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
<div className="file-tree-container">
<FileActions
onCreateFile={handleCreateFile}
onDeleteFile={handleDeleteFile}
onPullChanges={pullLatestChanges}
onCommitAndPush={handleCommitAndPush}
handlePullChanges={handlePull}
selectedFile={selectedFile}
/>
<FileTree
files={files}
selectedFile={selectedFile}
handleFileSelect={handleFileSelect}
/>
<FileTree files={files} />
</div>
</Grid>
<Grid
@@ -133,12 +98,22 @@ const MainContent = () => {
</Tabs>
</div>
<div className="content-body">
<ContentView activeTab={activeTab} />
<ContentView
activeTab={activeTab}
selectedFile={selectedFile}
content={content}
handleContentChange={handleContentChange}
handleSave={handleSave}
handleLinkClick={handleLinkClick}
/>
</div>
</Grid>
</Grid.Container>
<CreateFileModal onCreateFile={handleCreateFile} />
<DeleteFileModal onDeleteFile={handleDeleteFile} />
<DeleteFileModal
onDeleteFile={handleDeleteFile}
selectedFile={selectedFile}
/>
<CommitMessageModal onCommitAndPush={handleCommitAndPush} />
</>
);

View File

@@ -5,13 +5,9 @@ import rehypeKatex from 'rehype-katex';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import 'katex/dist/katex.min.css';
import { useFileNavigation } from '../hooks/useFileNavigation';
import { lookupFileByName } from '../services/api';
import { useEditorContent } from '../contexts/EditorContentContext';
const MarkdownPreview = () => {
const { content } = useEditorContent();
const { handleLinkClick } = useFileNavigation();
const MarkdownPreview = (content, handleLinkClick) => {
const [processedContent, setProcessedContent] = useState(content);
const baseUrl = window.API_BASE_URL;

View File

@@ -1,10 +1,8 @@
import React from 'react';
import { Modal, Text } from '@geist-ui/core';
import { useModalContext } from '../../contexts/ModalContext';
import { useFileSelection } from '../../contexts/FileSelectionContext';
const DeleteFileModal = ({ onDeleteFile }) => {
const { selectedFile } = useFileSelection();
const DeleteFileModal = ({ onDeleteFile, selectedFile }) => {
const { deleteFileModalVisible, setDeleteFileModalVisible } =
useModalContext();

View File

@@ -1,27 +0,0 @@
import React, { createContext, useContext, useEffect } from 'react';
import { useFileManagementContext } from './FileManagementContext';
const EditorContentContext = createContext();
export const EditorContentProvider = ({ children }) => {
const { content, handleContentChange, handleSave, selectedFile } =
useFileManagementContext();
return (
<EditorContentContext.Provider
value={{ content, handleContentChange, handleSave }}
>
{children}
</EditorContentContext.Provider>
);
};
export const useEditorContent = () => {
const context = useContext(EditorContentContext);
if (context === undefined) {
throw new Error(
'useEditorContent must be used within an EditorContentProvider'
);
}
return context;
};

View File

@@ -1,36 +0,0 @@
import React, { createContext, useContext, useMemo } from 'react';
import { useFileManagement } from '../hooks/useFileManagement';
const FileManagementContext = createContext();
export const FileManagementProvider = ({ children }) => {
const fileManagement = useFileManagement();
const value = useMemo(
() => fileManagement,
[
fileManagement.selectedFile,
fileManagement.isNewFile,
fileManagement.content,
fileManagement.isLoading,
fileManagement.error,
fileManagement.hasUnsavedChanges,
]
);
return (
<FileManagementContext.Provider value={value}>
{children}
</FileManagementContext.Provider>
);
};
export const useFileManagementContext = () => {
const context = useContext(FileManagementContext);
if (context === undefined) {
throw new Error(
'useFileManagementContext must be used within a FileManagementProvider'
);
}
return context;
};

View File

@@ -1,24 +0,0 @@
import React, { createContext, useContext } from 'react';
import { useFileManagementContext } from './FileManagementContext';
const FileSelectionContext = createContext();
export const FileSelectionProvider = ({ children }) => {
const { selectedFile, handleFileSelect } = useFileManagementContext();
return (
<FileSelectionContext.Provider value={{ selectedFile, handleFileSelect }}>
{children}
</FileSelectionContext.Provider>
);
};
export const useFileSelection = () => {
const context = useContext(FileSelectionContext);
if (context === undefined) {
throw new Error(
'useFileSelection must be used within a FileSelectionProvider'
);
}
return context;
};

View File

@@ -5,13 +5,9 @@ import { DEFAULT_FILE } from '../utils/constants';
export const useFileContent = () => {
const [content, setContent] = useState(DEFAULT_FILE.content);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const loadFileContent = useCallback(async (filePath) => {
setIsLoading(true);
setError(null);
try {
if (filePath === DEFAULT_FILE.path) {
setContent(DEFAULT_FILE.content);
@@ -23,10 +19,7 @@ export const useFileContent = () => {
}
setHasUnsavedChanges(false);
} catch (err) {
setError('Failed to load file content');
console.error(err);
} finally {
setIsLoading(false);
}
}, []);
@@ -38,9 +31,8 @@ export const useFileContent = () => {
return {
content,
setContent,
isLoading,
error,
hasUnsavedChanges,
setHasUnsavedChanges,
loadFileContent,
handleContentChange,
};

View File

@@ -3,7 +3,6 @@ import { fetchFileList } from '../services/api';
export const useFileList = (gitEnabled) => {
const [files, setFiles] = useState([]);
const [error, setError] = useState(null);
const loadFileList = useCallback(async () => {
try {
@@ -15,13 +14,12 @@ export const useFileList = (gitEnabled) => {
}
} catch (error) {
console.error('Failed to load file list:', error);
setError('Failed to load file list. Please try again later.');
}
}, []);
useEffect(() => {
loadFileList();
}, [loadFileList, gitEnabled]);
}, [gitEnabled]);
return { files, error, loadFileList };
return { files, loadFileList };
};

View File

@@ -1,40 +0,0 @@
import { useEffect, useCallback } from 'react';
import { useFileSelection } from './useFileSelection';
import { useFileContent } from './useFileContent';
export const useFileManagement = () => {
const { selectedFile, isNewFile, handleFileSelect } = useFileSelection();
const {
content,
isLoading,
error,
hasUnsavedChanges,
loadFileContent,
handleContentChange,
} = useFileContent();
useEffect(() => {
if (selectedFile) {
loadFileContent(selectedFile);
}
}, [selectedFile, loadFileContent]);
const handleFileSelectAndLoad = useCallback(
async (filePath) => {
await handleFileSelect(filePath);
await loadFileContent(filePath);
},
[handleFileSelect, loadFileContent]
);
return {
selectedFile,
isNewFile,
content,
isLoading,
error,
hasUnsavedChanges,
handleFileSelect: handleFileSelectAndLoad,
handleContentChange,
};
};

View File

@@ -1,38 +1,39 @@
// hooks/useFileNavigation.js
import { useCallback } from 'react';
import { useState, useCallback } from 'react';
import { useToasts } from '@geist-ui/core';
import { lookupFileByName } from '../services/api';
import { useFileSelection } from '../contexts/FileSelectionContext';
import { DEFAULT_FILE } from '../utils/constants'; // Assuming you have this constant defined
export const useFileNavigation = () => {
const { setToast } = useToasts();
const { handleFileSelect } = useFileSelection();
const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path);
const [isNewFile, setIsNewFile] = useState(true);
const handleLinkClick = useCallback(
async (filename) => {
try {
const filePaths = await lookupFileByName(filename);
if (filePaths.length === 1) {
if (filePaths.length >= 1) {
handleFileSelect(filePaths[0]);
} else if (filePaths.length > 1) {
// Handle multiple file options (you may want to show a modal or dropdown)
setToast({
text: 'Multiple files found with the same name. Please specify the full path.',
type: 'warning',
});
} else {
setToast({ text: `File "${filename}" not found`, type: 'error' });
}
} catch (error) {
console.error('Error looking up file:', error);
setToast({
text: 'Failed to lookup file. Please try again.',
text: 'Failed to lookup file.',
type: 'error',
});
}
},
[handleFileSelect, setToast]
[handleFileSelect]
);
return { handleLinkClick };
const handleFileSelect = useCallback(async (filePath) => {
setSelectedFile(filePath);
setIsNewFile(filePath === DEFAULT_FILE.path);
}, []);
return { handleLinkClick, selectedFile, isNewFile, handleFileSelect };
};

View File

@@ -5,8 +5,7 @@ import { useToasts } from '@geist-ui/core';
export const useFileOperations = (setHasUnsavedChanges) => {
const { setToast } = useToasts();
const handleSave = useCallback(
async (filePath, content) => {
const handleSave = useCallback(async (filePath, content) => {
try {
await saveFileContent(filePath, content);
setHasUnsavedChanges(false);
@@ -16,9 +15,7 @@ export const useFileOperations = (setHasUnsavedChanges) => {
console.error('Error saving file:', error);
return false;
}
},
[setHasUnsavedChanges]
);
}, []);
const handleDelete = useCallback(async (filePath) => {
try {

View File

@@ -1,14 +0,0 @@
import { useState, useCallback } from 'react';
import { DEFAULT_FILE } from '../utils/constants'; // Assuming you have this constant defined
export const useFileSelection = () => {
const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path);
const [isNewFile, setIsNewFile] = useState(true);
const handleFileSelect = useCallback(async (filePath) => {
setSelectedFile(filePath);
setIsNewFile(filePath === DEFAULT_FILE.path);
}, []);
return { selectedFile, isNewFile, handleFileSelect };
};

View File

@@ -2,13 +2,15 @@ import { useCallback } from 'react';
import { pullChanges, commitAndPush } from '../services/api';
export const useGitOperations = (gitEnabled) => {
const pullLatestChanges = useCallback(async () => {
const handlePull = useCallback(async () => {
if (!gitEnabled) return false;
try {
await pullChanges();
setToast({ text: 'Successfully pulled latest changes', type: 'success' });
return true;
} catch (error) {
console.error('Failed to pull latest changes:', error);
setToast({ text: 'Failed to pull latest changes', type: 'error' });
return false;
}
}, [gitEnabled]);
@@ -18,14 +20,19 @@ export const useGitOperations = (gitEnabled) => {
if (!gitEnabled) return false;
try {
await commitAndPush(message);
setToast({
text: 'Successfully committed and pushed changes',
type: 'success',
});
return true;
} catch (error) {
console.error('Failed to commit and push changes:', error);
setToast({ text: 'Failed to commit and push changes', type: 'error' });
return false;
}
},
[gitEnabled]
);
return { pullLatestChanges, handleCommitAndPush };
return { handlePull, handleCommitAndPush };
};

View File

@@ -1,4 +1,5 @@
import { IMAGE_EXTENSIONS } from './constants';
export const isImageFile = (filePath) => {
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
return imageExtensions.some((ext) => filePath.toLowerCase().endsWith(ext));
return IMAGE_EXTENSIONS.some((ext) => filePath.toLowerCase().endsWith(ext));
};