From 5b946269bc35df344bb44dac850ca8039530c8bd Mon Sep 17 00:00:00 2001 From: LordMathis Date: Fri, 4 Oct 2024 21:21:24 +0200 Subject: [PATCH] Implement state contexts --- frontend/src/App.js | 69 ++------ frontend/src/components/ContentView.js | 12 +- frontend/src/components/FileActions.js | 49 +++--- frontend/src/components/FileTree.js | 23 +-- frontend/src/components/MainContent.js | 150 ++++-------------- .../components/modals/CommitMessageModal.js | 23 ++- .../src/components/modals/CreateFileModal.js | 33 ++-- .../src/components/modals/DeleteFileModal.js | 24 ++- frontend/src/contexts/FileContentContext.js | 24 +++ frontend/src/contexts/FileListContext.js | 24 +++ frontend/src/contexts/GitOperationsContext.js | 24 +++ frontend/src/contexts/UIStateContext.js | 34 ++++ frontend/src/hooks/useFileContent.js | 56 +++++-- frontend/src/hooks/useFileNavigation.js | 39 +++++ 14 files changed, 334 insertions(+), 250 deletions(-) create mode 100644 frontend/src/contexts/FileContentContext.js create mode 100644 frontend/src/contexts/FileListContext.js create mode 100644 frontend/src/contexts/GitOperationsContext.js create mode 100644 frontend/src/contexts/UIStateContext.js create mode 100644 frontend/src/hooks/useFileNavigation.js diff --git a/frontend/src/App.js b/frontend/src/App.js index 2b5cd4d..204bf1e 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,48 +1,17 @@ +// App.js import React from 'react'; -import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core'; +import { GeistProvider, CssBaseline, Page } from '@geist-ui/core'; import Header from './components/Header'; import MainContent from './components/MainContent'; -import { useFileManagement } from './hooks/useFileManagement'; import { SettingsProvider, useSettings } from './contexts/SettingsContext'; -import { lookupFileByName } from './services/api'; +import { FileContentProvider } from './contexts/FileContentContext'; +import { FileListProvider } from './contexts/FileListContext'; +import { GitOperationsProvider } from './contexts/GitOperationsContext'; +import { UIStateProvider } from './contexts/UIStateContext'; import './App.scss'; function AppContent() { const { settings, loading } = useSettings(); - const { setToast } = useToasts(); - - const { - content, - files, - selectedFile, - isNewFile, - hasUnsavedChanges, - error, - handleFileSelect, - handleContentChange, - handleSave, - pullLatestChanges, - } = useFileManagement(settings.gitEnabled); - - const handleLinkClick = async (filename) => { - try { - const filePaths = await lookupFileByName(filename); - 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) - console.log('Multiple files found:', filePaths); - } 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.', - type: 'error', - }); - } - }; if (loading) { return
Loading...
; @@ -54,21 +23,7 @@ function AppContent() {
- + @@ -78,7 +33,15 @@ function AppContent() { function App() { return ( - + + + + + + + + + ); } diff --git a/frontend/src/components/ContentView.js b/frontend/src/components/ContentView.js index 29aa61e..952869d 100644 --- a/frontend/src/components/ContentView.js +++ b/frontend/src/components/ContentView.js @@ -3,17 +3,17 @@ import Editor from './Editor'; import MarkdownPreview from './MarkdownPreview'; import { getFileUrl } from '../services/api'; import { isImageFile } from '../utils/fileHelpers'; +import { useFileContentContext } from '../contexts/FileContentContext'; const ContentView = ({ activeTab, - content, - selectedFile, - onContentChange, - onSave, themeType, onLinkClick, lookupFileByName, }) => { + const { content, selectedFile, handleContentChange, handleSave } = + useFileContentContext(); + if (isImageFile(selectedFile)) { return (
@@ -33,8 +33,8 @@ const ContentView = ({ return activeTab === 'source' ? ( diff --git a/frontend/src/components/FileActions.js b/frontend/src/components/FileActions.js index 6a54f46..e62b58e 100644 --- a/frontend/src/components/FileActions.js +++ b/frontend/src/components/FileActions.js @@ -1,16 +1,25 @@ import React from 'react'; import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core'; import { Plus, Trash, GitPullRequest, GitCommit } from '@geist-ui/icons'; +import { useFileContentContext } from '../contexts/FileContentContext'; +import { useGitOperationsContext } from '../contexts/GitOperationsContext'; +import { useSettings } from '../contexts/SettingsContext'; +import { useUIStateContext } from '../contexts/UIStateContext'; + +const FileActions = () => { + const { selectedFile } = useFileContentContext(); + const { pullLatestChanges } = useGitOperationsContext(); + const { settings } = useSettings(); + const { + setNewFileModalVisible, + setDeleteFileModalVisible, + setCommitMessageModalVisible, + } = useUIStateContext(); + + const handleCreateFile = () => setNewFileModalVisible(true); + const handleDeleteFile = () => setDeleteFileModalVisible(true); + const handleCommitAndPush = () => setCommitMessageModalVisible(true); -const FileActions = ({ - selectedFile, - gitEnabled, - gitAutoCommit, - onPull, - onCommitAndPush, - onCreateFile, - onDeleteFile, -}) => { return ( @@ -18,7 +27,7 @@ const FileActions = ({ icon={} auto scale={2 / 3} - onClick={onCreateFile} + onClick={handleCreateFile} px={0.6} /> @@ -31,7 +40,7 @@ const FileActions = ({ icon={} auto scale={2 / 3} - onClick={onDeleteFile} + onClick={handleDeleteFile} disabled={!selectedFile} type="error" px={0.6} @@ -39,24 +48,28 @@ const FileActions = ({
- setNewFileModalVisible(false)} - onSubmit={handleNewFileSubmit} - fileName={newFileName} - setFileName={setNewFileName} - /> - setDeleteFileModalVisible(false)} - onConfirm={confirmDeleteFile} - fileName={selectedFile} - /> - setCommitMessageModalVisible(false)} - onSubmit={confirmCommitAndPush} - /> + + + ); }; diff --git a/frontend/src/components/modals/CommitMessageModal.js b/frontend/src/components/modals/CommitMessageModal.js index db0ecf5..fb66b33 100644 --- a/frontend/src/components/modals/CommitMessageModal.js +++ b/frontend/src/components/modals/CommitMessageModal.js @@ -1,16 +1,27 @@ import React, { useState } from 'react'; import { Modal, Input } from '@geist-ui/core'; +import { useGitOperationsContext } from '../../contexts/GitOperationsContext'; +import { useUIStateContext } from '../../contexts/UIStateContext'; -const CommitMessageModal = ({ visible, onClose, onSubmit }) => { +const CommitMessageModal = () => { const [message, setMessage] = useState(''); + const { handleCommitAndPush } = useGitOperationsContext(); + const { commitMessageModalVisible, setCommitMessageModalVisible } = + useUIStateContext(); - const handleSubmit = () => { - onSubmit(message); - setMessage(''); + const handleSubmit = async () => { + if (message) { + await handleCommitAndPush(message); + setMessage(''); + setCommitMessageModalVisible(false); + } }; return ( - + setCommitMessageModalVisible(false)} + > Enter Commit Message { onChange={(e) => setMessage(e.target.value)} /> - + setCommitMessageModalVisible(false)}> Cancel Commit diff --git a/frontend/src/components/modals/CreateFileModal.js b/frontend/src/components/modals/CreateFileModal.js index 09dbaa1..06b6173 100644 --- a/frontend/src/components/modals/CreateFileModal.js +++ b/frontend/src/components/modals/CreateFileModal.js @@ -1,15 +1,26 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Modal, Input } from '@geist-ui/core'; +import { useFileContentContext } from '../../contexts/FileContentContext'; +import { useUIStateContext } from '../../contexts/UIStateContext'; + +const CreateFileModal = () => { + const [fileName, setFileName] = useState(''); + const { newFileModalVisible, setNewFileModalVisible } = useUIStateContext(); + const { handleCreateNewFile } = useFileContentContext(); + + const handleSubmit = async () => { + if (fileName) { + await handleCreateNewFile(fileName); + setFileName(''); + setNewFileModalVisible(false); + } + }; -const CreateFileModal = ({ - visible, - onClose, - onSubmit, - fileName, - setFileName, -}) => { return ( - + setNewFileModalVisible(false)} + > Create New File setFileName(e.target.value)} /> - + setNewFileModalVisible(false)}> Cancel - Create + Create ); }; diff --git a/frontend/src/components/modals/DeleteFileModal.js b/frontend/src/components/modals/DeleteFileModal.js index 7221669..600e9be 100644 --- a/frontend/src/components/modals/DeleteFileModal.js +++ b/frontend/src/components/modals/DeleteFileModal.js @@ -1,17 +1,31 @@ import React from 'react'; import { Modal, Text } from '@geist-ui/core'; +import { useFileContentContext } from '../../contexts/FileContentContext'; +import { useUIStateContext } from '../../contexts/UIStateContext'; + +const DeleteFileModal = () => { + const { selectedFile, handleDeleteFile } = useFileContentContext(); + const { deleteFileModalVisible, setDeleteFileModalVisible } = + useUIStateContext(); + + const handleConfirm = async () => { + await handleDeleteFile(); + setDeleteFileModalVisible(false); + }; -const DeleteFileModal = ({ visible, onClose, onConfirm, fileName }) => { return ( - + setDeleteFileModalVisible(false)} + > Delete File - Are you sure you want to delete "{fileName}"? + Are you sure you want to delete "{selectedFile}"? - + setDeleteFileModalVisible(false)}> Cancel - Delete + Delete ); }; diff --git a/frontend/src/contexts/FileContentContext.js b/frontend/src/contexts/FileContentContext.js new file mode 100644 index 0000000..b97a7f6 --- /dev/null +++ b/frontend/src/contexts/FileContentContext.js @@ -0,0 +1,24 @@ +import React, { createContext, useContext } from 'react'; +import { useFileContent } from '../hooks/useFileContent'; + +const FileContentContext = createContext(); + +export const FileContentProvider = ({ children }) => { + const fileContentHook = useFileContent(); + + return ( + + {children} + + ); +}; + +export const useFileContentContext = () => { + const context = useContext(FileContentContext); + if (!context) { + throw new Error( + 'useFileContentContext must be used within a FileContentProvider' + ); + } + return context; +}; diff --git a/frontend/src/contexts/FileListContext.js b/frontend/src/contexts/FileListContext.js new file mode 100644 index 0000000..60c37f7 --- /dev/null +++ b/frontend/src/contexts/FileListContext.js @@ -0,0 +1,24 @@ +import React, { createContext, useContext } from 'react'; +import { useFileList } from '../hooks/useFileList'; + +const FileListContext = createContext(); + +export const FileListProvider = ({ children }) => { + const fileListHook = useFileList(); + + return ( + + {children} + + ); +}; + +export const useFileListContext = () => { + const context = useContext(FileListContext); + if (!context) { + throw new Error( + 'useFileListContext must be used within a FileListProvider' + ); + } + return context; +}; diff --git a/frontend/src/contexts/GitOperationsContext.js b/frontend/src/contexts/GitOperationsContext.js new file mode 100644 index 0000000..41589c3 --- /dev/null +++ b/frontend/src/contexts/GitOperationsContext.js @@ -0,0 +1,24 @@ +import React, { createContext, useContext } from 'react'; +import { useGitOperations } from '../hooks/useGitOperations'; + +const GitOperationsContext = createContext(); + +export const GitOperationsProvider = ({ children }) => { + const gitOperationsHook = useGitOperations(); + + return ( + + {children} + + ); +}; + +export const useGitOperationsContext = () => { + const context = useContext(GitOperationsContext); + if (!context) { + throw new Error( + 'useGitOperationsContext must be used within a GitOperationsProvider' + ); + } + return context; +}; diff --git a/frontend/src/contexts/UIStateContext.js b/frontend/src/contexts/UIStateContext.js new file mode 100644 index 0000000..30c05da --- /dev/null +++ b/frontend/src/contexts/UIStateContext.js @@ -0,0 +1,34 @@ +import React, { createContext, useContext, useState } from 'react'; + +const UIStateContext = createContext(); + +export const UIStateProvider = ({ children }) => { + const [activeTab, setActiveTab] = useState('source'); + const [newFileModalVisible, setNewFileModalVisible] = useState(false); + const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false); + const [commitMessageModalVisible, setCommitMessageModalVisible] = + useState(false); + + const value = { + activeTab, + setActiveTab, + newFileModalVisible, + setNewFileModalVisible, + deleteFileModalVisible, + setDeleteFileModalVisible, + commitMessageModalVisible, + setCommitMessageModalVisible, + }; + + return ( + {children} + ); +}; + +export const useUIStateContext = () => { + const context = useContext(UIStateContext); + if (!context) { + throw new Error('useUIStateContext must be used within a UIStateProvider'); + } + return context; +}; diff --git a/frontend/src/hooks/useFileContent.js b/frontend/src/hooks/useFileContent.js index d538595..8d49de0 100644 --- a/frontend/src/hooks/useFileContent.js +++ b/frontend/src/hooks/useFileContent.js @@ -1,6 +1,8 @@ import { useState, useCallback } from 'react'; -import { fetchFileContent, saveFileContent } from '../services/api'; +import { fetchFileContent, saveFileContent, deleteFile } from '../services/api'; import { isImageFile } from '../utils/fileHelpers'; +import { useToasts } from '@geist-ui/core'; +import { useFileListContext } from '../contexts/FileListContext'; const DEFAULT_FILE = { name: 'New File.md', @@ -14,16 +16,12 @@ export const useFileContent = () => { const [isNewFile, setIsNewFile] = useState(true); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [error, setError] = useState(null); + const { setToast } = useToasts(); + const { loadFileList } = useFileListContext(); const handleFileSelect = useCallback( async (filePath) => { - if (hasUnsavedChanges) { - const confirmSwitch = window.confirm( - 'You have unsaved changes. Are you sure you want to switch files?' - ); - if (!confirmSwitch) return; - } - + console.log('handleFileSelect', filePath); try { if (filePath === DEFAULT_FILE.path) { setContent(DEFAULT_FILE.content); @@ -67,13 +65,38 @@ export const useFileContent = () => { } }, []); - const createNewFile = useCallback(() => { - setContent(DEFAULT_FILE.content); - setSelectedFile(DEFAULT_FILE.path); - setIsNewFile(true); - setHasUnsavedChanges(false); - setError(null); - }, []); + const handleCreateNewFile = useCallback( + async (fileName, initialContent = '') => { + try { + await saveFileContent(fileName, initialContent); + setToast({ text: 'New file created successfully', type: 'success' }); + await loadFileList(); // Refresh the file list + handleFileSelect(fileName); + } catch (error) { + setToast({ + text: 'Failed to create new file: ' + error.message, + type: 'error', + }); + } + }, + [setToast, loadFileList, handleFileSelect] + ); + + const handleDeleteFile = useCallback(async () => { + if (!selectedFile) return; + try { + await deleteFile(selectedFile); + setToast({ text: 'File deleted successfully', type: 'success' }); + await loadFileList(); // Refresh the file list + setSelectedFile(null); + setContent(''); + } catch (error) { + setToast({ + text: 'Failed to delete file: ' + error.message, + type: 'error', + }); + } + }, [selectedFile, setToast, loadFileList]); return { content, @@ -84,7 +107,8 @@ export const useFileContent = () => { handleFileSelect, handleContentChange, handleSave, - createNewFile, + handleCreateNewFile, + handleDeleteFile, DEFAULT_FILE, }; }; diff --git a/frontend/src/hooks/useFileNavigation.js b/frontend/src/hooks/useFileNavigation.js new file mode 100644 index 0000000..eb8c46b --- /dev/null +++ b/frontend/src/hooks/useFileNavigation.js @@ -0,0 +1,39 @@ +// hooks/useFileNavigation.js +import { useCallback } from 'react'; +import { useToasts } from '@geist-ui/core'; +import { lookupFileByName } from '../services/api'; +import { useFileContentContext } from '../contexts/FileContentContext'; + +export const useFileNavigation = () => { + const { setToast } = useToasts(); + const { handleFileSelect } = useFileContentContext(); + + const handleLinkClick = useCallback( + async (filename) => { + try { + const filePaths = await lookupFileByName(filename); + 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) + console.log('Multiple files found:', filePaths); + 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.', + type: 'error', + }); + } + }, + [handleFileSelect, setToast] + ); + + return { handleLinkClick }; +};