Split large contexts

This commit is contained in:
2024-10-04 23:09:40 +02:00
parent 654d35ad40
commit 50b0fbb03c
20 changed files with 183 additions and 87 deletions

View File

@@ -4,10 +4,13 @@ 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 { FileContentProvider } from './contexts/FileContentContext'; import { ModalProvider } from './contexts/ModalContext';
import { FileListProvider } from './contexts/FileListContext'; import { TabProvider } from './contexts/TabContext';
import { GitOperationsProvider } from './contexts/GitOperationsContext'; import { GitOperationsProvider } from './contexts/GitOperationsContext';
import { UIStateProvider } from './contexts/UIStateContext'; import { FileListProvider } from './contexts/FileListContext';
import { FileSelectionProvider } from './contexts/FileSelectionContext';
import { FileOperationsProvider } from './contexts/FileOperationsContext';
import { EditorContentProvider } from './contexts/EditorContentContext';
import './App.scss'; import './App.scss';
function AppContent() { function AppContent() {
@@ -33,15 +36,21 @@ function AppContent() {
function App() { function App() {
return ( return (
<SettingsProvider> <SettingsProvider>
<FileListProvider> <ModalProvider>
<FileContentProvider> <TabProvider>
<GitOperationsProvider> <GitOperationsProvider>
<UIStateProvider> <FileListProvider>
<FileSelectionProvider>
<FileOperationsProvider>
<EditorContentProvider>
<AppContent /> <AppContent />
</UIStateProvider> </EditorContentProvider>
</GitOperationsProvider> </FileOperationsProvider>
</FileContentProvider> </FileSelectionProvider>
</FileListProvider> </FileListProvider>
</GitOperationsProvider>
</TabProvider>
</ModalProvider>
</SettingsProvider> </SettingsProvider>
); );
} }

View File

@@ -4,12 +4,12 @@ import MarkdownPreview from './MarkdownPreview';
import { Text } from '@geist-ui/core'; import { Text } from '@geist-ui/core';
import { getFileUrl } from '../services/api'; import { getFileUrl } from '../services/api';
import { isImageFile } from '../utils/fileHelpers'; import { isImageFile } from '../utils/fileHelpers';
import { useFileContentContext } from '../contexts/FileContentContext'; import { useFileSelection } from '../contexts/FileSelectionContext';
import { useUIStateContext } from '../contexts/UIStateContext'; import { useTabContext } from '../contexts/TabContext';
const ContentView = () => { const ContentView = () => {
const { selectedFile } = useFileContentContext(); const { selectedFile } = useFileSelection();
const { activeTab } = useUIStateContext(); const { activeTab } = useTabContext();
if (!selectedFile) { if (!selectedFile) {
return ( return (

View File

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

View File

@@ -1,20 +1,20 @@
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 { useFileContentContext } from '../contexts/FileContentContext';
import { useGitOperationsContext } from '../contexts/GitOperationsContext'; import { useGitOperationsContext } from '../contexts/GitOperationsContext';
import { useSettings } from '../contexts/SettingsContext'; import { useSettings } from '../contexts/SettingsContext';
import { useUIStateContext } from '../contexts/UIStateContext'; import { useFileSelection } from '../contexts/FileSelectionContext';
import { useModalContext } from '../contexts/ModalContext';
const FileActions = () => { const FileActions = () => {
const { selectedFile } = useFileContentContext(); const { selectedFile } = useFileSelection();
const { pullLatestChanges } = useGitOperationsContext(); const { pullLatestChanges } = useGitOperationsContext();
const { settings } = useSettings(); const { settings } = useSettings();
const { const {
setNewFileModalVisible, setNewFileModalVisible,
setDeleteFileModalVisible, setDeleteFileModalVisible,
setCommitMessageModalVisible, setCommitMessageModalVisible,
} = useUIStateContext(); } = useModalContext();
const handleCreateFile = () => setNewFileModalVisible(true); const handleCreateFile = () => setNewFileModalVisible(true);
const handleDeleteFile = () => setDeleteFileModalVisible(true); const handleDeleteFile = () => setDeleteFileModalVisible(true);

View File

@@ -1,13 +1,13 @@
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 { useFileContentContext } from '../contexts/FileContentContext';
import { useFileListContext } from '../contexts/FileListContext'; import { useFileListContext } from '../contexts/FileListContext';
import { isImageFile } from '../utils/fileHelpers'; import { isImageFile } from '../utils/fileHelpers';
import { useFileSelection } from '../contexts/FileSelectionContext';
const FileTree = () => { const FileTree = () => {
const { files } = useFileListContext(); const { files } = useFileListContext();
const { selectedFile, handleFileSelect } = useFileContentContext(); const { selectedFile, handleFileSelect } = useFileSelection();
if (files.length === 0) { if (files.length === 0) {
return <div>No files to display</div>; return <div>No files to display</div>;

View File

@@ -2,10 +2,10 @@ import React from 'react';
import { Page, Text, User, Button, Spacer } from '@geist-ui/core'; import { Page, Text, User, Button, Spacer } from '@geist-ui/core';
import { Settings as SettingsIcon } from '@geist-ui/icons'; import { Settings as SettingsIcon } from '@geist-ui/icons';
import Settings from './Settings'; import Settings from './Settings';
import { useUIStateContext } from '../contexts/UIStateContext'; import { useModalContext } from '../contexts/ModalContext';
const Header = () => { const Header = () => {
const { setSettingsModalVisible } = useUIStateContext(); const { setSettingsModalVisible } = useModalContext();
const openSettings = () => setSettingsModalVisible(true); const openSettings = () => setSettingsModalVisible(true);

View File

@@ -7,12 +7,14 @@ 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 { useFileContentContext } from '../contexts/FileContentContext'; import { useTabContext } from '../contexts/TabContext';
import { useUIStateContext } from '../contexts/UIStateContext'; import { useEditorContent } from '../contexts/EditorContentContext';
import { useFileSelection } from '../contexts/FileSelectionContext';
const MainContent = () => { const MainContent = () => {
const { selectedFile, hasUnsavedChanges } = useFileContentContext(); const { hasUnsavedChanges } = useEditorContent();
const { activeTab, setActiveTab } = useUIStateContext(); const { selectedFile } = useFileSelection();
const { activeTab, setActiveTab } = useTabContext();
const handleTabChange = (value) => { const handleTabChange = (value) => {
setActiveTab(value); setActiveTab(value);

View File

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

View File

@@ -1,10 +1,10 @@
import React, { useReducer, useEffect, useCallback, useRef } from 'react'; import React, { useReducer, useEffect, useCallback, useRef } from 'react';
import { Modal, Spacer, useTheme, Dot, useToasts } from '@geist-ui/core'; import { Modal, Spacer, Dot, useToasts } from '@geist-ui/core';
import { useSettings } from '../contexts/SettingsContext'; import { useSettings } from '../contexts/SettingsContext';
import { useUIStateContext } from '../contexts/UIStateContext';
import AppearanceSettings from './settings/AppearanceSettings'; import AppearanceSettings from './settings/AppearanceSettings';
import EditorSettings from './settings/EditorSettings'; import EditorSettings from './settings/EditorSettings';
import GitSettings from './settings/GitSettings'; import GitSettings from './settings/GitSettings';
import { useModalContext } from '../contexts/ModalContext';
const initialState = { const initialState = {
localSettings: {}, localSettings: {},
@@ -50,7 +50,7 @@ function settingsReducer(state, action) {
const Settings = () => { const Settings = () => {
const { settings, updateSettings, updateTheme } = useSettings(); const { settings, updateSettings, updateTheme } = useSettings();
const { settingsModalVisible, setSettingsModalVisible } = useUIStateContext(); const { settingsModalVisible, setSettingsModalVisible } = useModalContext();
const { setToast } = useToasts(); const { setToast } = useToasts();
const [state, dispatch] = useReducer(settingsReducer, initialState); const [state, dispatch] = useReducer(settingsReducer, initialState);
const isInitialMount = useRef(true); const isInitialMount = useRef(true);

View File

@@ -1,13 +1,13 @@
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 { useGitOperationsContext } from '../../contexts/GitOperationsContext';
import { useUIStateContext } from '../../contexts/UIStateContext'; import { useModalContext } from '../../contexts/ModalContext';
const CommitMessageModal = () => { const CommitMessageModal = () => {
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const { handleCommitAndPush } = useGitOperationsContext(); const { handleCommitAndPush } = useGitOperationsContext();
const { commitMessageModalVisible, setCommitMessageModalVisible } = const { commitMessageModalVisible, setCommitMessageModalVisible } =
useUIStateContext(); useModalContext();
const handleSubmit = async () => { const handleSubmit = async () => {
if (message) { if (message) {

View File

@@ -1,12 +1,12 @@
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 { useFileContentContext } from '../../contexts/FileContentContext'; import { useFileOperations } from '../../contexts/FileOperationsContext';
import { useUIStateContext } from '../../contexts/UIStateContext'; import { useModalContext } from '../../contexts/ModalContext';
const CreateFileModal = () => { const CreateFileModal = () => {
const [fileName, setFileName] = useState(''); const [fileName, setFileName] = useState('');
const { newFileModalVisible, setNewFileModalVisible } = useUIStateContext(); const { newFileModalVisible, setNewFileModalVisible } = useModalContext();
const { handleCreateNewFile } = useFileContentContext(); const { handleCreateNewFile } = useFileOperations();
const handleSubmit = async () => { const handleSubmit = async () => {
if (fileName) { if (fileName) {

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { Modal, Text } from '@geist-ui/core'; import { Modal, Text } from '@geist-ui/core';
import { useFileContentContext } from '../../contexts/FileContentContext'; import { useModalContext } from '../../contexts/ModalContext';
import { useUIStateContext } from '../../contexts/UIStateContext'; import { useFileSelection } from '../../contexts/FileSelectionContext';
const DeleteFileModal = () => { const DeleteFileModal = () => {
const { selectedFile, handleDeleteFile } = useFileContentContext(); const { selectedFile, handleDeleteFile } = useFileSelection();
const { deleteFileModalVisible, setDeleteFileModalVisible } = const { deleteFileModalVisible, setDeleteFileModalVisible } =
useUIStateContext(); useModalContext();
const handleConfirm = async () => { const handleConfirm = async () => {
await handleDeleteFile(); await handleDeleteFile();

View File

@@ -0,0 +1,33 @@
import React, { createContext, useContext, useMemo } from 'react';
import { useFileContent } from '../hooks/useFileContent';
const EditorContentContext = createContext();
export const EditorContentProvider = ({ children }) => {
const { content, handleContentChange, handleSave } = useFileContent();
const value = useMemo(
() => ({
content,
handleContentChange,
handleSave,
}),
[content, handleContentChange, handleSave]
);
return (
<EditorContentContext.Provider value={value}>
{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,24 +0,0 @@
import React, { createContext, useContext } from 'react';
import { useFileContent } from '../hooks/useFileContent';
const FileContentContext = createContext();
export const FileContentProvider = ({ children }) => {
const fileContentHook = useFileContent();
return (
<FileContentContext.Provider value={fileContentHook}>
{children}
</FileContentContext.Provider>
);
};
export const useFileContentContext = () => {
const context = useContext(FileContentContext);
if (!context) {
throw new Error(
'useFileContentContext must be used within a FileContentProvider'
);
}
return context;
};

View File

@@ -1,13 +1,15 @@
import React, { createContext, useContext } from 'react'; import React, { createContext, useContext, useMemo } from 'react';
import { useFileList } from '../hooks/useFileList'; import { useFileList } from '../hooks/useFileList';
const FileListContext = createContext(); const FileListContext = createContext();
export const FileListProvider = ({ children }) => { export const FileListProvider = ({ children }) => {
const fileListHook = useFileList(); const { files, loadFileList } = useFileList();
const value = useMemo(() => ({ files, loadFileList }), [files, loadFileList]);
return ( return (
<FileListContext.Provider value={fileListHook}> <FileListContext.Provider value={value}>
{children} {children}
</FileListContext.Provider> </FileListContext.Provider>
); );
@@ -15,7 +17,7 @@ export const FileListProvider = ({ children }) => {
export const useFileListContext = () => { export const useFileListContext = () => {
const context = useContext(FileListContext); const context = useContext(FileListContext);
if (!context) { if (context === undefined) {
throw new Error( throw new Error(
'useFileListContext must be used within a FileListProvider' 'useFileListContext must be used within a FileListProvider'
); );

View File

@@ -0,0 +1,32 @@
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;
};

View File

@@ -0,0 +1,35 @@
import React, { createContext, useContext, useMemo } from 'react';
import { useFileContent } from '../hooks/useFileContent';
const FileSelectionContext = createContext();
export const FileSelectionProvider = ({ children }) => {
const { selectedFile, isNewFile, hasUnsavedChanges, handleFileSelect } =
useFileContent();
const value = useMemo(
() => ({
selectedFile,
isNewFile,
hasUnsavedChanges,
handleFileSelect,
}),
[selectedFile, isNewFile, hasUnsavedChanges, handleFileSelect]
);
return (
<FileSelectionContext.Provider value={value}>
{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

@@ -1,9 +1,8 @@
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
const UIStateContext = createContext(); const ModalContext = createContext();
export const UIStateProvider = ({ children }) => { export const ModalProvider = ({ children }) => {
const [activeTab, setActiveTab] = useState('source');
const [newFileModalVisible, setNewFileModalVisible] = useState(false); const [newFileModalVisible, setNewFileModalVisible] = useState(false);
const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false); const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
const [commitMessageModalVisible, setCommitMessageModalVisible] = const [commitMessageModalVisible, setCommitMessageModalVisible] =
@@ -11,8 +10,6 @@ export const UIStateProvider = ({ children }) => {
const [settingsModalVisible, setSettingsModalVisible] = useState(false); const [settingsModalVisible, setSettingsModalVisible] = useState(false);
const value = { const value = {
activeTab,
setActiveTab,
newFileModalVisible, newFileModalVisible,
setNewFileModalVisible, setNewFileModalVisible,
deleteFileModalVisible, deleteFileModalVisible,
@@ -24,14 +21,8 @@ export const UIStateProvider = ({ children }) => {
}; };
return ( return (
<UIStateContext.Provider value={value}>{children}</UIStateContext.Provider> <ModalContext.Provider value={value}>{children}</ModalContext.Provider>
); );
}; };
export const useUIStateContext = () => { export const useModalContext = () => useContext(ModalContext);
const context = useContext(UIStateContext);
if (!context) {
throw new Error('useUIStateContext must be used within a UIStateProvider');
}
return context;
};

View File

@@ -0,0 +1,15 @@
import React, { createContext, useContext, useState } from 'react';
const TabContext = createContext();
export const TabProvider = ({ children }) => {
const [activeTab, setActiveTab] = useState('source');
return (
<TabContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabContext.Provider>
);
};
export const useTabContext = () => useContext(TabContext);

View File

@@ -2,11 +2,11 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useToasts } from '@geist-ui/core'; import { useToasts } from '@geist-ui/core';
import { lookupFileByName } from '../services/api'; import { lookupFileByName } from '../services/api';
import { useFileContentContext } from '../contexts/FileContentContext'; import { useFileSelection } from '../contexts/FileSelectionContext';
export const useFileNavigation = () => { export const useFileNavigation = () => {
const { setToast } = useToasts(); const { setToast } = useToasts();
const { handleFileSelect } = useFileContentContext(); const { handleFileSelect } = useFileSelection();
const handleLinkClick = useCallback( const handleLinkClick = useCallback(
async (filename) => { async (filename) => {