mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
Update layout
This commit is contained in:
@@ -1,14 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { MantineProvider, ColorSchemeScript } from '@mantine/core';
|
||||||
MantineProvider,
|
|
||||||
ColorSchemeScript,
|
|
||||||
AppShell,
|
|
||||||
Container,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { Notifications } from '@mantine/notifications';
|
import { Notifications } from '@mantine/notifications';
|
||||||
import { ModalsProvider } from '@mantine/modals';
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
import Header from './components/Header';
|
import Layout from './components/Layout';
|
||||||
import MainContent from './components/MainContent';
|
|
||||||
import { SettingsProvider, useSettings } from './contexts/SettingsContext';
|
import { SettingsProvider, useSettings } from './contexts/SettingsContext';
|
||||||
import { ModalProvider } from './contexts/ModalContext';
|
import { ModalProvider } from './contexts/ModalContext';
|
||||||
import '@mantine/core/styles.css';
|
import '@mantine/core/styles.css';
|
||||||
@@ -22,18 +16,7 @@ function AppContent() {
|
|||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <Layout />;
|
||||||
<AppShell header={{ height: 60 }} padding="md">
|
|
||||||
<AppShell.Header>
|
|
||||||
<Header />
|
|
||||||
</AppShell.Header>
|
|
||||||
<AppShell.Main>
|
|
||||||
<Container size="xl">
|
|
||||||
<MainContent />
|
|
||||||
</Container>
|
|
||||||
</AppShell.Main>
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|||||||
42
frontend/src/components/Layout.js
Normal file
42
frontend/src/components/Layout.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { AppShell, Container } from '@mantine/core';
|
||||||
|
import Header from './Header';
|
||||||
|
import Sidebar from './Sidebar';
|
||||||
|
import MainContent from './MainContent';
|
||||||
|
import { useFileNavigation } from '../hooks/useFileNavigation';
|
||||||
|
|
||||||
|
const Layout = () => {
|
||||||
|
const { selectedFile, handleFileSelect, handleLinkClick } =
|
||||||
|
useFileNavigation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppShell header={{ height: 60 }} padding="md">
|
||||||
|
<AppShell.Header>
|
||||||
|
<Header />
|
||||||
|
</AppShell.Header>
|
||||||
|
<AppShell.Main>
|
||||||
|
<Container
|
||||||
|
size="xl"
|
||||||
|
p={0}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
height: 'calc(100vh - 60px - 2rem)', // Subtracting header height and vertical padding
|
||||||
|
overflow: 'hidden', // Prevent scrolling in the container
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
selectedFile={selectedFile}
|
||||||
|
handleFileSelect={handleFileSelect}
|
||||||
|
/>
|
||||||
|
<MainContent
|
||||||
|
selectedFile={selectedFile}
|
||||||
|
handleFileSelect={handleFileSelect}
|
||||||
|
handleLinkClick={handleLinkClick}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -1,27 +1,20 @@
|
|||||||
import React, { useState, useCallback, useEffect } from 'react';
|
import React, { useState, useCallback, useMemo } from 'react';
|
||||||
import { Tabs, Breadcrumbs, Group, Box, Text, Flex } from '@mantine/core';
|
import { Tabs, Breadcrumbs, Group, Box, Text, Flex } from '@mantine/core';
|
||||||
import { IconCode, IconEye, IconPointFilled } from '@tabler/icons-react';
|
import { IconCode, IconEye, IconPointFilled } from '@tabler/icons-react';
|
||||||
|
|
||||||
import FileActions from './FileActions';
|
|
||||||
import FileTree from './FileTree';
|
|
||||||
import ContentView from './ContentView';
|
import ContentView from './ContentView';
|
||||||
import CreateFileModal from './modals/CreateFileModal';
|
import CreateFileModal from './modals/CreateFileModal';
|
||||||
import DeleteFileModal from './modals/DeleteFileModal';
|
import DeleteFileModal from './modals/DeleteFileModal';
|
||||||
import CommitMessageModal from './modals/CommitMessageModal';
|
import CommitMessageModal from './modals/CommitMessageModal';
|
||||||
|
|
||||||
import { useFileContent } from '../hooks/useFileContent';
|
import { useFileContent } from '../hooks/useFileContent';
|
||||||
import { useFileList } from '../hooks/useFileList';
|
|
||||||
import { useFileOperations } from '../hooks/useFileOperations';
|
import { useFileOperations } from '../hooks/useFileOperations';
|
||||||
import { useGitOperations } from '../hooks/useGitOperations';
|
import { useGitOperations } from '../hooks/useGitOperations';
|
||||||
import { useFileNavigation } from '../hooks/useFileNavigation';
|
|
||||||
import { useSettings } from '../contexts/SettingsContext';
|
import { useSettings } from '../contexts/SettingsContext';
|
||||||
|
|
||||||
const MainContent = () => {
|
const MainContent = ({ selectedFile, handleFileSelect, handleLinkClick }) => {
|
||||||
const [activeTab, setActiveTab] = useState('source');
|
const [activeTab, setActiveTab] = useState('source');
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
const { files, loadFileList } = useFileList();
|
|
||||||
const { handleLinkClick, selectedFile, handleFileSelect } =
|
|
||||||
useFileNavigation();
|
|
||||||
const {
|
const {
|
||||||
content,
|
content,
|
||||||
hasUnsavedChanges,
|
hasUnsavedChanges,
|
||||||
@@ -29,15 +22,11 @@ const MainContent = () => {
|
|||||||
handleContentChange,
|
handleContentChange,
|
||||||
} = useFileContent(selectedFile);
|
} = useFileContent(selectedFile);
|
||||||
const { handleSave, handleCreate, handleDelete } = useFileOperations();
|
const { handleSave, handleCreate, handleDelete } = useFileOperations();
|
||||||
const { handleCommitAndPush, handlePull } = useGitOperations();
|
const { handleCommitAndPush } = useGitOperations(settings.gitEnabled);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleTabChange = useCallback((value) => {
|
||||||
loadFileList();
|
|
||||||
}, [settings.gitEnabled]);
|
|
||||||
|
|
||||||
const handleTabChange = (value) => {
|
|
||||||
setActiveTab(value);
|
setActiveTab(value);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleSaveFile = useCallback(
|
const handleSaveFile = useCallback(
|
||||||
async (filePath, content) => {
|
async (filePath, content) => {
|
||||||
@@ -54,25 +43,23 @@ const MainContent = () => {
|
|||||||
async (fileName) => {
|
async (fileName) => {
|
||||||
const success = await handleCreate(fileName);
|
const success = await handleCreate(fileName);
|
||||||
if (success) {
|
if (success) {
|
||||||
await loadFileList();
|
|
||||||
handleFileSelect(fileName);
|
handleFileSelect(fileName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleCreate, loadFileList, handleFileSelect]
|
[handleCreate, handleFileSelect]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeleteFile = useCallback(
|
const handleDeleteFile = useCallback(
|
||||||
async (filePath) => {
|
async (filePath) => {
|
||||||
const success = await handleDelete(filePath);
|
const success = await handleDelete(filePath);
|
||||||
if (success) {
|
if (success) {
|
||||||
await loadFileList();
|
|
||||||
handleFileSelect(null);
|
handleFileSelect(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleDelete, loadFileList, handleFileSelect]
|
[handleDelete, handleFileSelect]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBreadcrumbs = () => {
|
const renderBreadcrumbs = useMemo(() => {
|
||||||
if (!selectedFile) return null;
|
if (!selectedFile) return null;
|
||||||
const pathParts = selectedFile.split('/');
|
const pathParts = selectedFile.split('/');
|
||||||
const items = pathParts.map((part, index) => (
|
const items = pathParts.map((part, index) => (
|
||||||
@@ -92,56 +79,35 @@ const MainContent = () => {
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
}, [selectedFile, hasUnsavedChanges]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box style={{ height: 'calc(100vh - 60px)', display: 'flex' }}>
|
<Box
|
||||||
<Box
|
style={{
|
||||||
style={{
|
flex: 1,
|
||||||
width: '300px',
|
overflow: 'hidden',
|
||||||
borderRight: '1px solid var(--mantine-color-gray-3)',
|
display: 'flex',
|
||||||
overflow: 'hidden',
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FileActions
|
<Flex justify="space-between" align="center" p="md">
|
||||||
handlePullChanges={handlePull}
|
{renderBreadcrumbs}
|
||||||
|
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||||
|
<Tabs.List>
|
||||||
|
<Tabs.Tab value="source" leftSection={<IconCode size="0.8rem" />} />
|
||||||
|
<Tabs.Tab value="preview" leftSection={<IconEye size="0.8rem" />} />
|
||||||
|
</Tabs.List>
|
||||||
|
</Tabs>
|
||||||
|
</Flex>
|
||||||
|
<Box style={{ flex: 1, overflow: 'auto' }}>
|
||||||
|
<ContentView
|
||||||
|
activeTab={activeTab}
|
||||||
selectedFile={selectedFile}
|
selectedFile={selectedFile}
|
||||||
|
content={content}
|
||||||
|
handleContentChange={handleContentChange}
|
||||||
|
handleSave={handleSaveFile}
|
||||||
|
handleLinkClick={handleLinkClick}
|
||||||
/>
|
/>
|
||||||
<FileTree files={files} handleFileSelect={handleFileSelect} />
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
overflow: 'hidden',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex justify="space-between" align="center" p="md">
|
|
||||||
{renderBreadcrumbs()}
|
|
||||||
<Tabs value={activeTab} onChange={handleTabChange}>
|
|
||||||
<Tabs.List>
|
|
||||||
<Tabs.Tab
|
|
||||||
value="source"
|
|
||||||
leftSection={<IconCode size="0.8rem" />}
|
|
||||||
/>
|
|
||||||
<Tabs.Tab
|
|
||||||
value="preview"
|
|
||||||
leftSection={<IconEye size="0.8rem" />}
|
|
||||||
/>
|
|
||||||
</Tabs.List>
|
|
||||||
</Tabs>
|
|
||||||
</Flex>
|
|
||||||
<Box style={{ flex: 1, overflow: 'auto' }}>
|
|
||||||
<ContentView
|
|
||||||
activeTab={activeTab}
|
|
||||||
selectedFile={selectedFile}
|
|
||||||
content={content}
|
|
||||||
handleContentChange={handleContentChange}
|
|
||||||
handleSave={handleSaveFile}
|
|
||||||
handleLinkClick={handleLinkClick}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<CreateFileModal onCreateFile={handleCreateFile} />
|
<CreateFileModal onCreateFile={handleCreateFile} />
|
||||||
<DeleteFileModal
|
<DeleteFileModal
|
||||||
|
|||||||
39
frontend/src/components/Sidebar.js
Normal file
39
frontend/src/components/Sidebar.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Box } from '@mantine/core';
|
||||||
|
import FileActions from './FileActions';
|
||||||
|
import FileTree from './FileTree';
|
||||||
|
import { useFileList } from '../hooks/useFileList';
|
||||||
|
import { useGitOperations } from '../hooks/useGitOperations';
|
||||||
|
import { useSettings } from '../contexts/SettingsContext';
|
||||||
|
|
||||||
|
const Sidebar = ({ selectedFile, handleFileSelect }) => {
|
||||||
|
const { settings } = useSettings();
|
||||||
|
const { files, loadFileList } = useFileList();
|
||||||
|
const { handlePull } = useGitOperations(settings.gitEnabled);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadFileList();
|
||||||
|
}, [settings.gitEnabled, loadFileList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
width: '25%',
|
||||||
|
minWidth: '200px',
|
||||||
|
maxWidth: '300px',
|
||||||
|
borderRight: '1px solid var(--app-shell-border-color)',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileActions handlePullChanges={handlePull} selectedFile={selectedFile} />
|
||||||
|
<FileTree
|
||||||
|
files={files}
|
||||||
|
handleFileSelect={handleFileSelect}
|
||||||
|
selectedFile={selectedFile}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useMantineColorScheme } from '@mantine/core';
|
import { useMantineColorScheme } from '@mantine/core';
|
||||||
import { fetchUserSettings, saveUserSettings } from '../services/api';
|
import { fetchUserSettings, saveUserSettings } from '../services/api';
|
||||||
import { DEFAULT_SETTINGS } from '../utils/constants';
|
import { DEFAULT_SETTINGS } from '../utils/constants';
|
||||||
@@ -9,8 +16,8 @@ export const useSettings = () => useContext(SettingsContext);
|
|||||||
|
|
||||||
export const SettingsProvider = ({ children }) => {
|
export const SettingsProvider = ({ children }) => {
|
||||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||||
const [settings, setSettings] = React.useState(DEFAULT_SETTINGS);
|
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
@@ -26,30 +33,32 @@ export const SettingsProvider = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
}, [setColorScheme]);
|
}, []);
|
||||||
|
|
||||||
const updateSettings = async (newSettings) => {
|
const updateSettings = useCallback(
|
||||||
try {
|
async (newSettings) => {
|
||||||
await saveUserSettings({
|
try {
|
||||||
userId: 1,
|
await saveUserSettings({
|
||||||
settings: newSettings,
|
userId: 1,
|
||||||
});
|
settings: newSettings,
|
||||||
setSettings(newSettings);
|
});
|
||||||
// Ensure the color scheme is updated when settings are saved
|
setSettings(newSettings);
|
||||||
if (newSettings.theme) {
|
if (newSettings.theme) {
|
||||||
setColorScheme(newSettings.theme);
|
setColorScheme(newSettings.theme);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save settings:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.error('Failed to save settings:', error);
|
[setColorScheme]
|
||||||
throw error;
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleColorScheme = () => {
|
const toggleColorScheme = useCallback(() => {
|
||||||
const newTheme = colorScheme === 'dark' ? 'light' : 'dark';
|
const newTheme = colorScheme === 'dark' ? 'light' : 'dark';
|
||||||
setColorScheme(newTheme);
|
setColorScheme(newTheme);
|
||||||
updateSettings({ ...settings, theme: newTheme });
|
updateSettings({ ...settings, theme: newTheme });
|
||||||
};
|
}, [colorScheme, settings, setColorScheme, updateSettings]);
|
||||||
|
|
||||||
const contextValue = useMemo(
|
const contextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -59,7 +68,7 @@ export const SettingsProvider = ({ children }) => {
|
|||||||
loading,
|
loading,
|
||||||
colorScheme,
|
colorScheme,
|
||||||
}),
|
}),
|
||||||
[settings, loading, colorScheme]
|
[settings, updateSettings, toggleColorScheme, loading, colorScheme]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user