From 5af07a9e350f6691be98b3a0e168920a9a5f1395 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Sat, 28 Sep 2024 19:39:45 +0200 Subject: [PATCH] Add git api calls on frontend --- frontend/src/App.js | 36 +++--- frontend/src/App.scss | 30 ++++- frontend/src/components/FileTree.js | 79 ++++++++++-- frontend/src/components/MainContent.js | 152 ++++++++++++++++++------ frontend/src/hooks/useFileManagement.js | 56 ++++++--- frontend/src/services/api.js | 51 +++++++- 6 files changed, 317 insertions(+), 87 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index 772aeaf..25c669c 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -9,6 +9,22 @@ import './App.scss'; function App() { const [themeType, setThemeType] = useState('light'); const [userId, setUserId] = useState(1); + const [settings, setSettings] = useState({ gitEnabled: false }); + + 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, @@ -19,20 +35,8 @@ function App() { handleFileSelect, handleContentChange, handleSave, - } = useFileManagement(); - - useEffect(() => { - const loadUserSettings = async () => { - try { - const settings = await fetchUserSettings(userId); - setThemeType(settings.settings.theme); - } catch (error) { - console.error('Failed to load user settings:', error); - } - }; - - loadUserSettings(); - }, [userId]); + pullLatestChanges, + } = useFileManagement(settings.gitEnabled); const setTheme = (newTheme) => { setThemeType(newTheme); @@ -43,7 +47,7 @@ function App() {
- + diff --git a/frontend/src/App.scss b/frontend/src/App.scss index 3859d21..6dc8740 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -6,13 +6,25 @@ $navbar-height: 64px; display: flex; align-items: center; justify-content: space-between; - padding: 0 $padding; - height: $navbar-height; } .sidebar { - overflow-y: auto; + overflow: auto; padding: $padding; + + .file-tree-container { + width: 100%; + + &>div { + padding: 0; + margin: 0; + } + } + +} + +.page-content { + padding: 0 $padding; } .main-content { @@ -25,7 +37,7 @@ $navbar-height: 64px; display: flex; justify-content: space-between; align-items: center; - padding: $padding; + padding: 0 $padding; .breadcrumbs-container { flex-grow: 1; @@ -50,6 +62,7 @@ $navbar-height: 64px; .setting-group { margin-bottom: 1rem; } + .setting-item { display: flex; justify-content: space-between; @@ -64,7 +77,8 @@ $navbar-height: 64px; flex-direction: column; } -.editor-container, .markdown-preview { +.editor-container, +.markdown-preview { flex-grow: 1; overflow-y: auto; padding: $padding; @@ -72,7 +86,7 @@ $navbar-height: 64px; .editor-container { height: 100%; - + .cm-editor { height: 100%; } @@ -82,6 +96,10 @@ $navbar-height: 64px; } } +.tree { + padding-top: $padding; +} + // Geist UI Tree component customization :global { .file-tree { diff --git a/frontend/src/components/FileTree.js b/frontend/src/components/FileTree.js index ebb2a56..91e4a84 100644 --- a/frontend/src/components/FileTree.js +++ b/frontend/src/components/FileTree.js @@ -1,22 +1,28 @@ import React from 'react'; -import { Tree } from '@geist-ui/core'; -import { File, Folder } from '@geist-ui/icons'; +import { Tree, Button, Tooltip, Spacer, ButtonGroup } from '@geist-ui/core'; +import { File, Folder, GitPullRequest, GitCommit, Plus, Trash } from '@geist-ui/icons'; const FileTree = ({ files = [], onFileSelect = () => {}, - selectedFile = null + selectedFile = null, + gitEnabled = false, + gitAutoCommit = false, + onPull = () => {}, + onCommitAndPush = () => {}, + onCreateFile = () => {}, + onDeleteFile = () => {} }) => { if (files.length === 0) { return
No files to display
; } const handleSelect = (filePath) => { - onFileSelect(filePath); + onFileSelect(filePath); }; const renderLabel = (node) => { - const path = getFilePath(node); + const path = node.extra; return ( {node.name} @@ -27,12 +33,63 @@ const FileTree = ({ const renderIcon = ({ type }) => type === 'directory' ? : ; return ( - +
+ + +
); }; diff --git a/frontend/src/components/MainContent.js b/frontend/src/components/MainContent.js index fcdb1e5..f0417fe 100644 --- a/frontend/src/components/MainContent.js +++ b/frontend/src/components/MainContent.js @@ -1,9 +1,10 @@ import React, { useState } from 'react'; -import { Grid, Breadcrumbs, Tabs, Dot, useTheme } from '@geist-ui/core'; +import { Grid, Breadcrumbs, Tabs, Dot, useTheme, useToasts, Modal, Input, Button } from '@geist-ui/core'; import { Code, Eye } from '@geist-ui/icons'; import Editor from './Editor'; import FileTree from './FileTree'; import MarkdownPreview from './MarkdownPreview'; +import { commitAndPush, saveFileContent, deleteFile } from '../services/api'; const MainContent = ({ content, @@ -14,9 +15,71 @@ const MainContent = ({ onFileSelect, onContentChange, onSave, + settings, + pullLatestChanges, }) => { const [activeTab, setActiveTab] = useState('source'); const { type: themeType } = useTheme(); + const { setToast } = useToasts(); + const [newFileModalVisible, setNewFileModalVisible] = useState(false); + const [newFileName, setNewFileName] = useState(''); + + const handlePull = async () => { + try { + await pullLatestChanges(); + setToast({ text: 'Successfully pulled latest changes', type: 'success' }); + } catch (error) { + setToast({ text: 'Failed to pull changes: ' + error.message, type: 'error' }); + } + }; + + const handleCommitAndPush = async () => { + try { + const message = prompt('Enter commit message:'); + if (message) { + await commitAndPush(message); + setToast({ text: 'Changes committed and pushed successfully', type: 'success' }); + await pullLatestChanges(); // Pull changes after successful push + } + } catch (error) { + setToast({ text: 'Failed to commit and push changes: ' + error.message, type: 'error' }); + } + }; + + const handleCreateFile = () => { + setNewFileModalVisible(true); + }; + + const handleNewFileSubmit = async () => { + if (newFileName) { + try { + await saveFileContent(newFileName, ''); + setToast({ text: 'New file created successfully', type: 'success' }); + await pullLatestChanges(); // Refresh file list + onFileSelect(newFileName); // Select the new file + } catch (error) { + setToast({ text: 'Failed to create new file: ' + error.message, type: 'error' }); + } + } + setNewFileModalVisible(false); + setNewFileName(''); + }; + + const handleDeleteFile = async () => { + if (selectedFile) { + const confirmDelete = window.confirm(`Are you sure you want to delete "${selectedFile}"?`); + if (confirmDelete) { + try { + await deleteFile(selectedFile); + setToast({ text: 'File deleted successfully', type: 'success' }); + await pullLatestChanges(); // Refresh file list + onFileSelect(null); // Deselect the file + } catch (error) { + setToast({ text: 'Failed to delete file: ' + error.message, type: 'error' }); + } + } + } + }; const renderBreadcrumbs = () => { if (!selectedFile) return null; @@ -34,41 +97,60 @@ const MainContent = ({ }; return ( - - - {error ? ( -
{error}
- ) : ( - - )} -
- -
- {renderBreadcrumbs()} - - } value="source" /> - } value="preview" /> - -
-
- {activeTab === 'source' ? ( - + + +
+ - ) : ( - - )} -
-
-
+
+
+ +
+ {renderBreadcrumbs()} + + } value="source" /> + } value="preview" /> + +
+
+ {activeTab === 'source' ? ( + + ) : ( + + )} +
+
+
+ setNewFileModalVisible(false)}> + Create New File + + setNewFileName(e.target.value)} + /> + + setNewFileModalVisible(false)}>Cancel + Create + + ); }; diff --git a/frontend/src/hooks/useFileManagement.js b/frontend/src/hooks/useFileManagement.js index 688a505..aa38829 100644 --- a/frontend/src/hooks/useFileManagement.js +++ b/frontend/src/hooks/useFileManagement.js @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react'; import { useToasts } from '@geist-ui/core'; -import { fetchFileList, fetchFileContent, saveFileContent } from '../services/api'; +import { fetchFileList, fetchFileContent, saveFileContent, pullChanges } from '../services/api'; const DEFAULT_FILE = { name: 'New File.md', @@ -8,7 +8,7 @@ const DEFAULT_FILE = { content: '# Welcome to NovaMD\n\nStart editing here!' }; -const useFileManagement = () => { +const useFileManagement = (gitEnabled = false) => { const [content, setContent] = useState(DEFAULT_FILE.content); const [files, setFiles] = useState([]); const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path); @@ -17,23 +17,41 @@ const useFileManagement = () => { const [error, setError] = useState(null); const { setToast } = useToasts(); - useEffect(() => { - const loadFileList = 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 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 () => { + await pullLatestChanges(); + await loadFileList(); }; - loadFileList(); - }, []); + initializeFileSystem(); + }, [pullLatestChanges, loadFileList]); const handleFileSelect = async (filePath) => { if (hasUnsavedChanges) { @@ -66,14 +84,13 @@ const useFileManagement = () => { setIsNewFile(false); setHasUnsavedChanges(false); if (isNewFile) { - const updatedFileList = await fetchFileList(); - setFiles(updatedFileList); + await loadFileList(); } } catch (error) { console.error('Error saving file:', error); setToast({ text: 'Failed to save file. Please try again.', type: 'error' }); } - }, [setToast, isNewFile]); + }, [setToast, isNewFile, loadFileList]); return { content, @@ -85,6 +102,7 @@ const useFileManagement = () => { handleFileSelect, handleContentChange, handleSave, + pullLatestChanges, }; }; diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 3a2280e..8a5d13d 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -42,6 +42,21 @@ export const saveFileContent = async (filePath, content) => { return await response.text(); }; + export const deleteFile = async (filePath) => { + try { + const response = await fetch(`${API_BASE_URL}/files/${filePath}`, { + method: 'DELETE', + }); + if (!response.ok) { + throw new Error('Failed to delete file'); + } + return await response.text(); + } catch (error) { + console.error('Error deleting file:', error); + throw error; + } + }; + export const fetchUserSettings = async (userId) => { try { const response = await fetch(`${API_BASE_URL}/settings?userId=${userId}`); @@ -75,4 +90,38 @@ export const saveUserSettings = async (settings) => { console.error('Error saving user settings:', error); throw error; } -}; \ No newline at end of file +}; + +export const pullChanges = async () => { + try { + const response = await fetch(`${API_BASE_URL}/git/pull`, { + method: 'POST', + }); + if (!response.ok) { + throw new Error('Failed to pull changes'); + } + return await response.json(); + } catch (error) { + console.error('Error pulling changes:', error); + throw error; + } + }; + + export const commitAndPush = async (message) => { + try { + const response = await fetch(`${API_BASE_URL}/git/commit`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ message }), + }); + if (!response.ok) { + throw new Error('Failed to commit and push changes'); + } + return await response.json(); + } catch (error) { + console.error('Error committing and pushing changes:', error); + throw error; + } + }; \ No newline at end of file