From 12312137b737bec69cae4fbc3e31c32b443f7a0d Mon Sep 17 00:00:00 2001 From: LordMathis Date: Sat, 26 Oct 2024 23:15:23 +0200 Subject: [PATCH] Implement workspace switching --- frontend/src/components/Header.js | 4 +- frontend/src/components/WorkspaceMenu.js | 77 ----------- frontend/src/components/WorkspaceSwitcher.js | 136 +++++++++++++++++++ frontend/src/contexts/ModalContext.js | 4 + frontend/src/contexts/WorkspaceContext.js | 53 ++++++-- frontend/src/hooks/useFileList.js | 2 +- frontend/src/hooks/useFileNavigation.js | 2 +- frontend/src/hooks/useFileOperations.js | 2 +- 8 files changed, 189 insertions(+), 91 deletions(-) delete mode 100644 frontend/src/components/WorkspaceMenu.js create mode 100644 frontend/src/components/WorkspaceSwitcher.js diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 0fbe38d..b9f894a 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -1,6 +1,6 @@ import React from 'react'; import { Group, Text, Avatar } from '@mantine/core'; -import WorkspaceMenu from './WorkspaceMenu'; +import WorkspaceSwitcher from './WorkspaceSwitcher'; import Settings from './Settings'; const Header = () => { @@ -10,8 +10,8 @@ const Header = () => { NovaMD + - diff --git a/frontend/src/components/WorkspaceMenu.js b/frontend/src/components/WorkspaceMenu.js deleted file mode 100644 index 5beff44..0000000 --- a/frontend/src/components/WorkspaceMenu.js +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import { Menu, ActionIcon, Text } from '@mantine/core'; -import { - IconFolders, - IconFolderPlus, - IconSwitchHorizontal, - IconSettings, - IconTrash, - IconPencil, - IconFileExport, - IconFileImport, -} from '@tabler/icons-react'; -import { useModalContext } from '../contexts/ModalContext'; -import { useWorkspace } from '../contexts/WorkspaceContext'; - -const WorkspaceMenu = () => { - const { setSettingsModalVisible } = useModalContext(); - const { currentWorkspace } = useWorkspace(); - - const openSettings = () => setSettingsModalVisible(true); - - return ( - - - - - - - - - Current Workspace - - - {currentWorkspace?.name || 'No workspace selected'} - - - - - - Workspace Actions - }> - Create Workspace - - }> - Switch Workspace - - }> - Rename Workspace - - } color="red"> - Delete Workspace - - - - - Data Management - }> - Export Workspace - - }> - Import Workspace - - - - - } - onClick={openSettings} - > - Workspace Settings - - - - ); -}; - -export default WorkspaceMenu; diff --git a/frontend/src/components/WorkspaceSwitcher.js b/frontend/src/components/WorkspaceSwitcher.js new file mode 100644 index 0000000..196f27f --- /dev/null +++ b/frontend/src/components/WorkspaceSwitcher.js @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; +import { + Box, + Popover, + Stack, + Paper, + ScrollArea, + Group, + UnstyledButton, + Text, + Loader, + Center, + Button, + ActionIcon, +} from '@mantine/core'; +import { IconFolders, IconSettings, IconFolderPlus } from '@tabler/icons-react'; +import { useWorkspace } from '../contexts/WorkspaceContext'; +import { useModalContext } from '../contexts/ModalContext'; +import { listWorkspaces } from '../services/api'; + +const WorkspaceSwitcher = () => { + const { currentWorkspace, switchWorkspace } = useWorkspace(); + const { setSettingsModalVisible } = useModalContext(); + const [workspaces, setWorkspaces] = useState([]); + const [loading, setLoading] = useState(false); + const [popoverOpened, setPopoverOpened] = useState(false); + + const loadWorkspaces = async () => { + setLoading(true); + try { + const list = await listWorkspaces(); + setWorkspaces(list); + } catch (error) { + console.error('Failed to load workspaces:', error); + } + setLoading(false); + }; + + return ( + + + { + setPopoverOpened((o) => !o); + if (!popoverOpened) { + loadWorkspaces(); + } + }} + > + + +
+ + {currentWorkspace?.name || 'No workspace'} + +
+
+
+
+ + + + Switch Workspace + + + + {loading ? ( +
+ +
+ ) : ( + workspaces.map((workspace) => ( + { + switchWorkspace(workspace.id); + setPopoverOpened(false); + }} + > + + + + + {workspace.name} + + + {new Date(workspace.createdAt).toLocaleDateString()} + + + {workspace.id === currentWorkspace?.id && ( + { + e.stopPropagation(); + setSettingsModalVisible(true); + setPopoverOpened(false); + }} + > + + + )} + + + + )) + )} +
+
+ +
+
+ ); +}; + +export default WorkspaceSwitcher; diff --git a/frontend/src/contexts/ModalContext.js b/frontend/src/contexts/ModalContext.js index 697bdc7..3338c7e 100644 --- a/frontend/src/contexts/ModalContext.js +++ b/frontend/src/contexts/ModalContext.js @@ -8,6 +8,8 @@ export const ModalProvider = ({ children }) => { const [commitMessageModalVisible, setCommitMessageModalVisible] = useState(false); const [settingsModalVisible, setSettingsModalVisible] = useState(false); + const [switchWorkspaceModalVisible, setSwitchWorkspaceModalVisible] = + useState(false); const value = { newFileModalVisible, @@ -18,6 +20,8 @@ export const ModalProvider = ({ children }) => { setCommitMessageModalVisible, settingsModalVisible, setSettingsModalVisible, + switchWorkspaceModalVisible, + setSwitchWorkspaceModalVisible, }; return ( diff --git a/frontend/src/contexts/WorkspaceContext.js b/frontend/src/contexts/WorkspaceContext.js index f228b0c..0006a77 100644 --- a/frontend/src/contexts/WorkspaceContext.js +++ b/frontend/src/contexts/WorkspaceContext.js @@ -6,6 +6,7 @@ import React, { useCallback, } from 'react'; import { useMantineColorScheme } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; import { fetchLastWorkspaceId, fetchWorkspaceSettings, @@ -22,18 +23,29 @@ export const WorkspaceProvider = ({ children }) => { const [loading, setLoading] = useState(true); const { colorScheme, setColorScheme } = useMantineColorScheme(); + const loadWorkspaceData = useCallback(async (workspaceId) => { + try { + const workspace = await getWorkspace(workspaceId); + setCurrentWorkspace(workspace); + const workspaceSettings = await fetchWorkspaceSettings(workspaceId); + setSettings(workspaceSettings.settings); + setColorScheme(workspaceSettings.settings.theme); + } catch (error) { + console.error('Failed to load workspace data:', error); + notifications.show({ + title: 'Error', + message: 'Failed to load workspace data', + color: 'red', + }); + } + }, []); + useEffect(() => { - const loadWorkspace = async () => { + const initializeWorkspace = async () => { try { const { lastWorkspaceId } = await fetchLastWorkspaceId(); if (lastWorkspaceId) { - const workspace = await getWorkspace(lastWorkspaceId); - setCurrentWorkspace(workspace); - const workspaceSettings = await fetchWorkspaceSettings( - lastWorkspaceId - ); - setSettings(workspaceSettings.settings); - setColorScheme(workspaceSettings.settings.theme); + await loadWorkspaceData(lastWorkspaceId); } else { console.warn('No last workspace found'); } @@ -44,7 +56,29 @@ export const WorkspaceProvider = ({ children }) => { } }; - loadWorkspace(); + initializeWorkspace(); + }, []); + + const switchWorkspace = useCallback(async (workspaceId) => { + try { + setLoading(true); + await updateLastWorkspace(workspaceId); + await loadWorkspaceData(workspaceId); + notifications.show({ + title: 'Success', + message: 'Workspace switched successfully', + color: 'green', + }); + } catch (error) { + console.error('Failed to switch workspace:', error); + notifications.show({ + title: 'Error', + message: 'Failed to switch workspace', + color: 'red', + }); + } finally { + setLoading(false); + } }, []); const updateSettings = useCallback( @@ -78,6 +112,7 @@ export const WorkspaceProvider = ({ children }) => { loading, colorScheme, updateColorScheme, + switchWorkspace, }; return ( diff --git a/frontend/src/hooks/useFileList.js b/frontend/src/hooks/useFileList.js index 46ca9ec..ef72c47 100644 --- a/frontend/src/hooks/useFileList.js +++ b/frontend/src/hooks/useFileList.js @@ -20,7 +20,7 @@ export const useFileList = () => { console.error('Failed to load file list:', error); setFiles([]); } - }, [currentWorkspace, workspaceLoading]); + }, [currentWorkspace]); return { files, loadFileList }; }; diff --git a/frontend/src/hooks/useFileNavigation.js b/frontend/src/hooks/useFileNavigation.js index 7c3623d..23ce637 100644 --- a/frontend/src/hooks/useFileNavigation.js +++ b/frontend/src/hooks/useFileNavigation.js @@ -38,7 +38,7 @@ export const useFileNavigation = () => { }); } }, - [currentWorkspace, handleFileSelect] + [currentWorkspace] ); return { handleLinkClick, selectedFile, isNewFile, handleFileSelect }; diff --git a/frontend/src/hooks/useFileOperations.js b/frontend/src/hooks/useFileOperations.js index 4b7f70a..0110755 100644 --- a/frontend/src/hooks/useFileOperations.js +++ b/frontend/src/hooks/useFileOperations.js @@ -21,7 +21,7 @@ export const useFileOperations = () => { await handleCommitAndPush(commitMessage); } }, - [settings, handleCommitAndPush] + [settings] ); const handleSave = useCallback(