From 3691a596668301755ac459788942d44fe56ae176 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Sat, 5 Oct 2024 00:30:18 +0200 Subject: [PATCH] Split up hooks --- frontend/src/App.js | 17 ++- frontend/src/components/Editor.js | 6 + frontend/src/contexts/EditorContentContext.js | 23 ++-- .../src/contexts/FileManagementContext.js | 36 ++++++ frontend/src/contexts/FileSelectionContext.js | 19 +-- frontend/src/hooks/useFileContent.js | 121 ++++-------------- frontend/src/hooks/useFileManagement.js | 51 +++++--- frontend/src/hooks/useFileOperations.js | 46 +++++++ frontend/src/hooks/useFileSelection.js | 15 +++ frontend/src/utils/constants.js | 48 +++++++ 10 files changed, 237 insertions(+), 145 deletions(-) create mode 100644 frontend/src/contexts/FileManagementContext.js create mode 100644 frontend/src/hooks/useFileOperations.js create mode 100644 frontend/src/hooks/useFileSelection.js create mode 100644 frontend/src/utils/constants.js diff --git a/frontend/src/App.js b/frontend/src/App.js index 6d8e561..069539c 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -11,6 +11,7 @@ import { FileListProvider } from './contexts/FileListContext'; import { FileSelectionProvider } from './contexts/FileSelectionContext'; import { FileOperationsProvider } from './contexts/FileOperationsContext'; import { EditorContentProvider } from './contexts/EditorContentContext'; +import { FileManagementProvider } from './contexts/FileManagementContext'; import './App.scss'; function AppContent() { @@ -40,13 +41,15 @@ function App() { - - - - - - - + + + + + + + + + diff --git a/frontend/src/components/Editor.js b/frontend/src/components/Editor.js index d9554ca..8e84b6e 100644 --- a/frontend/src/components/Editor.js +++ b/frontend/src/components/Editor.js @@ -77,7 +77,13 @@ const Editor = () => { }, [selectedFile, settings.theme, handleContentChange, handleSave]); useEffect(() => { + console.log('Editor: content or selectedFile changed', { + content, + selectedFile, + }); + if (viewRef.current && content !== viewRef.current.state.doc.toString()) { + console.log('Editor: updating content in CodeMirror'); viewRef.current.dispatch({ changes: { from: 0, diff --git a/frontend/src/contexts/EditorContentContext.js b/frontend/src/contexts/EditorContentContext.js index 4bfdd00..2ab0fdc 100644 --- a/frontend/src/contexts/EditorContentContext.js +++ b/frontend/src/contexts/EditorContentContext.js @@ -1,22 +1,23 @@ -import React, { createContext, useContext, useMemo } from 'react'; -import { useFileContent } from '../hooks/useFileContent'; +import React, { createContext, useContext, useEffect } from 'react'; +import { useFileManagementContext } from './FileManagementContext'; const EditorContentContext = createContext(); export const EditorContentProvider = ({ children }) => { - const { content, handleContentChange, handleSave } = useFileContent(); + const { content, handleContentChange, handleSave, selectedFile } = + useFileManagementContext(); - const value = useMemo( - () => ({ + useEffect(() => { + console.log('EditorContentProvider: content or selectedFile updated', { content, - handleContentChange, - handleSave, - }), - [content, handleContentChange, handleSave] - ); + selectedFile, + }); + }, [content, selectedFile]); return ( - + {children} ); diff --git a/frontend/src/contexts/FileManagementContext.js b/frontend/src/contexts/FileManagementContext.js new file mode 100644 index 0000000..2a6e89b --- /dev/null +++ b/frontend/src/contexts/FileManagementContext.js @@ -0,0 +1,36 @@ +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 ( + + {children} + + ); +}; + +export const useFileManagementContext = () => { + const context = useContext(FileManagementContext); + if (context === undefined) { + throw new Error( + 'useFileManagementContext must be used within a FileManagementProvider' + ); + } + return context; +}; diff --git a/frontend/src/contexts/FileSelectionContext.js b/frontend/src/contexts/FileSelectionContext.js index d89adeb..4a01af9 100644 --- a/frontend/src/contexts/FileSelectionContext.js +++ b/frontend/src/contexts/FileSelectionContext.js @@ -1,24 +1,15 @@ -import React, { createContext, useContext, useMemo } from 'react'; -import { useFileContent } from '../hooks/useFileContent'; +import React, { createContext, useContext } from 'react'; +import { useFileManagementContext } from './FileManagementContext'; const FileSelectionContext = createContext(); export const FileSelectionProvider = ({ children }) => { - const { selectedFile, isNewFile, hasUnsavedChanges, handleFileSelect } = - useFileContent(); + const { selectedFile, handleFileSelect } = useFileManagementContext(); - const value = useMemo( - () => ({ - selectedFile, - isNewFile, - hasUnsavedChanges, - handleFileSelect, - }), - [selectedFile, isNewFile, hasUnsavedChanges, handleFileSelect] - ); + console.log('FileSelectionProvider rendering', { selectedFile }); return ( - + {children} ); diff --git a/frontend/src/hooks/useFileContent.js b/frontend/src/hooks/useFileContent.js index 8d49de0..7679f60 100644 --- a/frontend/src/hooks/useFileContent.js +++ b/frontend/src/hooks/useFileContent.js @@ -1,114 +1,47 @@ import { useState, useCallback } from 'react'; -import { fetchFileContent, saveFileContent, deleteFile } from '../services/api'; +import { fetchFileContent } 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', - path: 'New File.md', - content: '# Welcome to NovaMD\n\nStart editing here!', -}; +import { DEFAULT_FILE } from '../utils/constants'; export const useFileContent = () => { const [content, setContent] = useState(DEFAULT_FILE.content); - const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path); - const [isNewFile, setIsNewFile] = useState(true); - const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const { setToast } = useToasts(); - const { loadFileList } = useFileListContext(); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); - const handleFileSelect = useCallback( - async (filePath) => { - console.log('handleFileSelect', filePath); - try { - if (filePath === DEFAULT_FILE.path) { - setContent(DEFAULT_FILE.content); - setSelectedFile(DEFAULT_FILE.path); - setIsNewFile(true); - } else if (!isImageFile(filePath)) { - const fileContent = await fetchFileContent(filePath); - setContent(fileContent); - setSelectedFile(filePath); - setIsNewFile(false); - } else { - setContent(''); // Set empty content for image files - setSelectedFile(filePath); - setIsNewFile(false); - } - setHasUnsavedChanges(false); - setError(null); - } catch (error) { - console.error('Failed to load file content:', error); - setError('Failed to load file content. Please try again.'); + const loadFileContent = useCallback(async (filePath) => { + setIsLoading(true); + setError(null); + try { + if (filePath === DEFAULT_FILE.path) { + setContent(DEFAULT_FILE.content); + } else if (!isImageFile(filePath)) { + const fileContent = await fetchFileContent(filePath); + setContent(fileContent); + } else { + setContent(''); // Set empty content for image files } - }, - [hasUnsavedChanges] - ); + setHasUnsavedChanges(false); + } catch (err) { + setError('Failed to load file content'); + console.error(err); + } finally { + setIsLoading(false); + } + }, []); const handleContentChange = useCallback((newContent) => { setContent(newContent); setHasUnsavedChanges(true); }, []); - const handleSave = useCallback(async (filePath, fileContent) => { - try { - await saveFileContent(filePath, fileContent); - setIsNewFile(false); - setHasUnsavedChanges(false); - return true; - } catch (error) { - console.error('Error saving file:', error); - setError('Failed to save file. Please try again.'); - return false; - } - }, []); - - 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, - selectedFile, - isNewFile, - hasUnsavedChanges, + setContent, + isLoading, error, - handleFileSelect, + hasUnsavedChanges, + loadFileContent, handleContentChange, - handleSave, - handleCreateNewFile, - handleDeleteFile, - DEFAULT_FILE, }; }; diff --git a/frontend/src/hooks/useFileManagement.js b/frontend/src/hooks/useFileManagement.js index 8620be7..8d531dd 100644 --- a/frontend/src/hooks/useFileManagement.js +++ b/frontend/src/hooks/useFileManagement.js @@ -1,34 +1,47 @@ -import { useFileList } from './useFileList'; +import { useEffect, useCallback } from 'react'; +import { useFileSelection } from './useFileSelection'; import { useFileContent } from './useFileContent'; -import { useGitOperations } from './useGitOperations'; +import { useFileOperations } from './useFileOperations'; -export const useFileManagement = (gitEnabled) => { - const { files, error: fileListError, loadFileList } = useFileList(gitEnabled); +export const useFileManagement = () => { + const { selectedFile, isNewFile, handleFileSelect } = useFileSelection(); const { content, - selectedFile, - isNewFile, + isLoading, + error, hasUnsavedChanges, - error: fileContentError, - handleFileSelect, + loadFileContent, handleContentChange, - handleSave, + setHasUnsavedChanges, } = useFileContent(); - const { pullLatestChanges, handleCommitAndPush } = - useGitOperations(gitEnabled); + const { handleSave, handleDelete, handleCreateNewFile } = + useFileOperations(setHasUnsavedChanges); + + useEffect(() => { + if (selectedFile) { + loadFileContent(selectedFile); + } + }, [selectedFile, loadFileContent]); + + const handleFileSelectAndLoad = useCallback( + async (filePath) => { + await handleFileSelect(filePath); + await loadFileContent(filePath); + }, + [handleFileSelect, loadFileContent] + ); return { - files, - content, selectedFile, isNewFile, + content, + isLoading, + error, hasUnsavedChanges, - error: fileListError || fileContentError, - handleFileSelect, + handleFileSelect: handleFileSelectAndLoad, handleContentChange, - handleSave, - pullLatestChanges, - handleCommitAndPush, - loadFileList, + handleSave: (filePath) => handleSave(filePath, content), + handleDelete, + handleCreateNewFile, }; }; diff --git a/frontend/src/hooks/useFileOperations.js b/frontend/src/hooks/useFileOperations.js new file mode 100644 index 0000000..beebeae --- /dev/null +++ b/frontend/src/hooks/useFileOperations.js @@ -0,0 +1,46 @@ +import { useCallback } from 'react'; +import { saveFileContent, deleteFile } from '../services/api'; + +export const useFileOperations = (setHasUnsavedChanges) => { + const handleSave = useCallback( + async (filePath, content) => { + try { + await saveFileContent(filePath, content); + setHasUnsavedChanges(false); + console.log('File saved successfully'); + return true; + } catch (error) { + console.error('Error saving file:', error); + return false; + } + }, + [setHasUnsavedChanges] + ); + + const handleDelete = useCallback(async (filePath) => { + try { + await deleteFile(filePath); + console.log('File deleted successfully'); + return true; + } catch (error) { + console.error('Error deleting file:', error); + return false; + } + }, []); + + const handleCreateNewFile = useCallback( + async (fileName, initialContent = '') => { + try { + await saveFileContent(fileName, initialContent); + console.log('New file created successfully'); + return true; + } catch (error) { + console.error('Error creating new file:', error); + return false; + } + }, + [] + ); + + return { handleSave, handleDelete, handleCreateNewFile }; +}; diff --git a/frontend/src/hooks/useFileSelection.js b/frontend/src/hooks/useFileSelection.js new file mode 100644 index 0000000..6fa7d01 --- /dev/null +++ b/frontend/src/hooks/useFileSelection.js @@ -0,0 +1,15 @@ +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) => { + console.log('File selected:', filePath); + setSelectedFile(filePath); + setIsNewFile(filePath === DEFAULT_FILE.path); + }, []); + + return { selectedFile, isNewFile, handleFileSelect }; +}; diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js new file mode 100644 index 0000000..6b7f0a4 --- /dev/null +++ b/frontend/src/utils/constants.js @@ -0,0 +1,48 @@ +export const API_BASE_URL = window.API_BASE_URL; + +export const THEMES = { + LIGHT: 'light', + DARK: 'dark', +}; + +export const FILE_ACTIONS = { + CREATE: 'create', + DELETE: 'delete', + RENAME: 'rename', +}; + +export const MODAL_TYPES = { + NEW_FILE: 'newFile', + DELETE_FILE: 'deleteFile', + COMMIT_MESSAGE: 'commitMessage', +}; + +export const IMAGE_EXTENSIONS = [ + '.jpg', + '.jpeg', + '.png', + '.gif', + '.webp', + '.svg', +]; + +export const DEFAULT_SETTINGS = { + theme: THEMES.LIGHT, + autoSave: false, + gitEnabled: false, + gitUrl: '', + gitUser: '', + gitToken: '', + gitAutoCommit: false, + gitCommitMsgTemplate: 'Update ${filename}', +}; + +export const DEFAULT_FILE = { + name: 'New File.md', + path: 'New File.md', + content: '# Welcome to NovaMD\n\nStart editing here!', +}; + +export const MARKDOWN_REGEX = { + WIKILINK: /(!?)\[\[(.*?)\]\]/g, +};