From 7ec6823534347a021391cde671ea245855fc63b4 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Thu, 3 Oct 2024 19:37:05 +0200 Subject: [PATCH 01/18] use reducerSplit useFileManagement hook --- frontend/src/App.js | 5 +- frontend/src/hooks/useFileContent.js | 90 ++++++++++++++ frontend/src/hooks/useFileList.js | 27 +++++ frontend/src/hooks/useFileManagement.js | 150 ++++-------------------- frontend/src/hooks/useGitOperations.js | 31 +++++ 5 files changed, 173 insertions(+), 130 deletions(-) create mode 100644 frontend/src/hooks/useFileContent.js create mode 100644 frontend/src/hooks/useFileList.js create mode 100644 frontend/src/hooks/useGitOperations.js diff --git a/frontend/src/App.js b/frontend/src/App.js index c34bdbe..99177c3 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -2,8 +2,8 @@ import React, { useState, useEffect } from 'react'; import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core'; import Header from './components/Header'; import MainContent from './components/MainContent'; -import useFileManagement from './hooks/useFileManagement'; -import { fetchUserSettings } from './services/api'; +import { useFileManagement } from './hooks/useFileManagement'; +import { fetchUserSettings, lookupFileByName } from './services/api'; import './App.scss'; function App() { @@ -37,7 +37,6 @@ function App() { handleContentChange, handleSave, pullLatestChanges, - lookupFileByName, } = useFileManagement(settings.gitEnabled); const handleThemeChange = (newTheme) => { diff --git a/frontend/src/hooks/useFileContent.js b/frontend/src/hooks/useFileContent.js new file mode 100644 index 0000000..d538595 --- /dev/null +++ b/frontend/src/hooks/useFileContent.js @@ -0,0 +1,90 @@ +import { useState, useCallback } from 'react'; +import { fetchFileContent, saveFileContent } from '../services/api'; +import { isImageFile } from '../utils/fileHelpers'; + +const DEFAULT_FILE = { + name: 'New File.md', + path: 'New File.md', + content: '# Welcome to NovaMD\n\nStart editing here!', +}; + +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 [error, setError] = useState(null); + + 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; + } + + 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.'); + } + }, + [hasUnsavedChanges] + ); + + 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 createNewFile = useCallback(() => { + setContent(DEFAULT_FILE.content); + setSelectedFile(DEFAULT_FILE.path); + setIsNewFile(true); + setHasUnsavedChanges(false); + setError(null); + }, []); + + return { + content, + selectedFile, + isNewFile, + hasUnsavedChanges, + error, + handleFileSelect, + handleContentChange, + handleSave, + createNewFile, + DEFAULT_FILE, + }; +}; diff --git a/frontend/src/hooks/useFileList.js b/frontend/src/hooks/useFileList.js new file mode 100644 index 0000000..4737cf9 --- /dev/null +++ b/frontend/src/hooks/useFileList.js @@ -0,0 +1,27 @@ +import { useState, useEffect, useCallback } from 'react'; +import { fetchFileList } from '../services/api'; + +export const useFileList = (gitEnabled) => { + const [files, setFiles] = useState([]); + const [error, setError] = useState(null); + + const loadFileList = useCallback(async () => { + try { + const fileList = await fetchFileList(); + if (Array.isArray(fileList)) { + setFiles(fileList); + } else { + throw new Error('File list is not an array'); + } + } catch (error) { + console.error('Failed to load file list:', error); + setError('Failed to load file list. Please try again later.'); + } + }, []); + + useEffect(() => { + loadFileList(); + }, [loadFileList, gitEnabled]); + + return { files, error, loadFileList }; +}; diff --git a/frontend/src/hooks/useFileManagement.js b/frontend/src/hooks/useFileManagement.js index 6d7ccaf..8620be7 100644 --- a/frontend/src/hooks/useFileManagement.js +++ b/frontend/src/hooks/useFileManagement.js @@ -1,138 +1,34 @@ -import { useState, useEffect, useCallback } from 'react'; -import { useToasts } from '@geist-ui/core'; -import { - fetchFileList, - fetchFileContent, - saveFileContent, - pullChanges, - lookupFileByName, -} from '../services/api'; -import { isImageFile } from '../utils/fileHelpers'; +import { useFileList } from './useFileList'; +import { useFileContent } from './useFileContent'; +import { useGitOperations } from './useGitOperations'; -const DEFAULT_FILE = { - name: 'New File.md', - path: 'New File.md', - content: '# Welcome to NovaMD\n\nStart editing here!', -}; - -const useFileManagement = (gitEnabled = false) => { - const [content, setContent] = useState(DEFAULT_FILE.content); - const [files, setFiles] = useState([]); - const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path); - const [isNewFile, setIsNewFile] = useState(true); - const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); - const [error, setError] = useState(null); - const { setToast } = useToasts(); - - const loadFileList = useCallback(async () => { - try { - const fileList = await fetchFileList(); - if (Array.isArray(fileList)) { - setFiles(fileList); - } else { - throw new Error('File list is not an array'); - } - } catch (error) { - console.error('Failed to load file list:', error); - setError('Failed to load file list. Please try again later.'); - } - }, []); - - const pullLatestChanges = useCallback(async () => { - if (gitEnabled) { - try { - await pullChanges(); - setToast({ - text: 'Latest changes pulled successfully', - type: 'success', - }); - await loadFileList(); // Reload file list after pulling changes - } catch (error) { - console.error('Failed to pull latest changes:', error); - setToast({ - text: 'Failed to pull latest changes: ' + error.message, - type: 'error', - }); - } - } - }, [gitEnabled, loadFileList, setToast]); - - useEffect(() => { - const initializeFileSystem = async () => { - if (gitEnabled) { - await pullLatestChanges(); - } else { - await loadFileList(); - } - }; - - initializeFileSystem(); - }, [gitEnabled]); - - const handleFileSelect = async (filePath) => { - if (hasUnsavedChanges) { - const confirmSwitch = window.confirm( - 'You have unsaved changes. Are you sure you want to switch files?' - ); - if (!confirmSwitch) return; - } - - try { - if (!isImageFile(filePath)) { - const fileContent = await fetchFileContent(filePath); - setContent(fileContent); - } 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 handleContentChange = (newContent) => { - setContent(newContent); - setHasUnsavedChanges(true); - }; - - const handleSave = useCallback( - async (filePath, fileContent) => { - try { - await saveFileContent(filePath, fileContent); - setToast({ text: 'File saved successfully', type: 'success' }); - setIsNewFile(false); - setHasUnsavedChanges(false); - if (isNewFile) { - await loadFileList(); - } - } catch (error) { - console.error('Error saving file:', error); - setToast({ - text: 'Failed to save file. Please try again.', - type: 'error', - }); - } - }, - [setToast, isNewFile, loadFileList] - ); - - return { +export const useFileManagement = (gitEnabled) => { + const { files, error: fileListError, loadFileList } = useFileList(gitEnabled); + const { content, - files, selectedFile, isNewFile, hasUnsavedChanges, - error, + error: fileContentError, + handleFileSelect, + handleContentChange, + handleSave, + } = useFileContent(); + const { pullLatestChanges, handleCommitAndPush } = + useGitOperations(gitEnabled); + + return { + files, + content, + selectedFile, + isNewFile, + hasUnsavedChanges, + error: fileListError || fileContentError, handleFileSelect, handleContentChange, handleSave, pullLatestChanges, - lookupFileByName, + handleCommitAndPush, + loadFileList, }; }; - -export default useFileManagement; diff --git a/frontend/src/hooks/useGitOperations.js b/frontend/src/hooks/useGitOperations.js new file mode 100644 index 0000000..7bf8013 --- /dev/null +++ b/frontend/src/hooks/useGitOperations.js @@ -0,0 +1,31 @@ +import { useCallback } from 'react'; +import { pullChanges, commitAndPush } from '../services/api'; + +export const useGitOperations = (gitEnabled) => { + const pullLatestChanges = useCallback(async () => { + if (!gitEnabled) return; + try { + await pullChanges(); + return true; + } catch (error) { + console.error('Failed to pull latest changes:', error); + return false; + } + }, [gitEnabled]); + + const handleCommitAndPush = useCallback( + async (message) => { + if (!gitEnabled) return; + try { + await commitAndPush(message); + return true; + } catch (error) { + console.error('Failed to commit and push changes:', error); + return false; + } + }, + [gitEnabled] + ); + + return { pullLatestChanges, handleCommitAndPush }; +}; From 93e675e0c7a395398c8f6b73f73ea29e8237b39e Mon Sep 17 00:00:00 2001 From: LordMathis Date: Thu, 3 Oct 2024 21:18:35 +0200 Subject: [PATCH 02/18] Retrun false --- frontend/src/hooks/useGitOperations.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/useGitOperations.js b/frontend/src/hooks/useGitOperations.js index 7bf8013..843c41b 100644 --- a/frontend/src/hooks/useGitOperations.js +++ b/frontend/src/hooks/useGitOperations.js @@ -3,7 +3,7 @@ import { pullChanges, commitAndPush } from '../services/api'; export const useGitOperations = (gitEnabled) => { const pullLatestChanges = useCallback(async () => { - if (!gitEnabled) return; + if (!gitEnabled) return false; try { await pullChanges(); return true; @@ -15,7 +15,7 @@ export const useGitOperations = (gitEnabled) => { const handleCommitAndPush = useCallback( async (message) => { - if (!gitEnabled) return; + if (!gitEnabled) return false; try { await commitAndPush(message); return true; From d21233a67f61eda3a0e682eb9aed902d93527fc3 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Thu, 3 Oct 2024 22:20:43 +0200 Subject: [PATCH 03/18] Implement Settings context --- frontend/src/App.js | 49 +++--- frontend/src/components/Header.js | 11 +- frontend/src/components/MainContent.js | 3 +- frontend/src/components/Settings.js | 183 ++++++++++++++--------- frontend/src/contexts/SettingsContext.js | 66 ++++++++ 5 files changed, 206 insertions(+), 106 deletions(-) create mode 100644 frontend/src/contexts/SettingsContext.js diff --git a/frontend/src/App.js b/frontend/src/App.js index 99177c3..2b5cd4d 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,31 +1,16 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core'; import Header from './components/Header'; import MainContent from './components/MainContent'; import { useFileManagement } from './hooks/useFileManagement'; -import { fetchUserSettings, lookupFileByName } from './services/api'; +import { SettingsProvider, useSettings } from './contexts/SettingsContext'; +import { lookupFileByName } from './services/api'; import './App.scss'; -function App() { - const [themeType, setThemeType] = useState('light'); - const [userId, setUserId] = useState(1); - const [settings, setSettings] = useState({ gitEnabled: false }); +function AppContent() { + const { settings, loading } = useSettings(); const { setToast } = useToasts(); - useEffect(() => { - const loadUserSettings = async () => { - try { - const fetchedSettings = await fetchUserSettings(userId); - setSettings(fetchedSettings.settings); - setThemeType(fetchedSettings.settings.theme); - } catch (error) { - console.error('Failed to load user settings:', error); - } - }; - - loadUserSettings(); - }, [userId]); - const { content, files, @@ -39,18 +24,14 @@ function App() { pullLatestChanges, } = useFileManagement(settings.gitEnabled); - const handleThemeChange = (newTheme) => { - setThemeType(newTheme); - }; - const handleLinkClick = async (filename) => { try { const filePaths = await lookupFileByName(filename); if (filePaths.length === 1) { handleFileSelect(filePaths[0]); } else if (filePaths.length > 1) { - setFileOptions(filePaths.map((path) => ({ label: path, value: path }))); - setFileSelectionModalVisible(true); + // 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' }); } @@ -63,11 +44,15 @@ function App() { } }; + if (loading) { + return
Loading...
; + } + return ( - + -
+
+ + + ); +} + export default App; diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index cbac3aa..44d6de5 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -2,9 +2,11 @@ import React, { useState } from 'react'; import { Page, Text, User, Button, Spacer } from '@geist-ui/core'; import { Settings as SettingsIcon } from '@geist-ui/icons'; import Settings from './Settings'; +import { useSettings } from '../contexts/SettingsContext'; -const Header = ({ currentTheme, onThemeChange }) => { +const Header = () => { const [settingsVisible, setSettingsVisible] = useState(false); + const { settings } = useSettings(); const openSettings = () => setSettingsVisible(true); const closeSettings = () => setSettingsVisible(false); @@ -16,12 +18,7 @@ const Header = ({ currentTheme, onThemeChange }) => {