Fix theme toggle

This commit is contained in:
2024-10-10 22:51:51 +02:00
parent d29d402cf3
commit a8629bc793
7 changed files with 178 additions and 156 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React from 'react';
import { import {
MantineProvider, MantineProvider,
createTheme, ColorSchemeScript,
AppShell, AppShell,
Container, Container,
} from '@mantine/core'; } from '@mantine/core';
@@ -15,22 +15,14 @@ import '@mantine/core/styles.css';
import '@mantine/notifications/styles.css'; import '@mantine/notifications/styles.css';
import './App.scss'; import './App.scss';
const mantineTheme = createTheme({
/** You can add your Mantine theme overrides here */
});
function AppContent() { function AppContent() {
const { settings, loading } = useSettings(); const { loading } = useSettings();
const [opened, setOpened] = useState(false);
if (loading) { if (loading) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
return ( return (
<MantineProvider theme={mantineTheme} defaultColorScheme={settings.theme}>
<Notifications />
<ModalsProvider>
<AppShell header={{ height: 60 }} padding="md"> <AppShell header={{ height: 60 }} padding="md">
<AppShell.Header> <AppShell.Header>
<Header /> <Header />
@@ -41,18 +33,24 @@ function AppContent() {
</Container> </Container>
</AppShell.Main> </AppShell.Main>
</AppShell> </AppShell>
</ModalsProvider>
</MantineProvider>
); );
} }
function App() { function App() {
return ( return (
<>
<ColorSchemeScript defaultColorScheme="light" />
<MantineProvider defaultColorScheme="light">
<Notifications />
<ModalsProvider>
<SettingsProvider> <SettingsProvider>
<ModalProvider> <ModalProvider>
<AppContent /> <AppContent />
</ModalProvider> </ModalProvider>
</SettingsProvider> </SettingsProvider>
</ModalsProvider>
</MantineProvider>
</>
); );
} }

View File

@@ -121,12 +121,14 @@ const MainContent = () => {
{renderBreadcrumbs()} {renderBreadcrumbs()}
<Tabs value={activeTab} onChange={handleTabChange}> <Tabs value={activeTab} onChange={handleTabChange}>
<Tabs.List> <Tabs.List>
<Tabs.Tab value="source" leftSection={<IconCode size="0.8rem" />}> <Tabs.Tab
Source value="source"
</Tabs.Tab> leftSection={<IconCode size="0.8rem" />}
<Tabs.Tab value="preview" leftSection={<IconEye size="0.8rem" />}> />
Preview <Tabs.Tab
</Tabs.Tab> value="preview"
leftSection={<IconEye size="0.8rem" />}
/>
</Tabs.List> </Tabs.List>
</Tabs> </Tabs>
</Flex> </Flex>

View File

@@ -1,6 +1,5 @@
import React, { useReducer, useEffect, useCallback, useRef } from 'react'; import React, { useReducer, useEffect, useCallback, useRef } from 'react';
import { Modal, Badge, Button, Group } from '@mantine/core'; import { Modal, Badge, Button, Group, Title } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import { useSettings } from '../contexts/SettingsContext'; import { useSettings } from '../contexts/SettingsContext';
import AppearanceSettings from './settings/AppearanceSettings'; import AppearanceSettings from './settings/AppearanceSettings';
@@ -51,11 +50,10 @@ function settingsReducer(state, action) {
} }
const Settings = () => { const Settings = () => {
const { settings, updateSettings, updateTheme } = useSettings(); const { settings, updateSettings, colorScheme } = useSettings();
const { settingsModalVisible, setSettingsModalVisible } = useModalContext(); const { settingsModalVisible, setSettingsModalVisible } = useModalContext();
const [state, dispatch] = useReducer(settingsReducer, initialState); const [state, dispatch] = useReducer(settingsReducer, initialState);
const isInitialMount = useRef(true); const isInitialMount = useRef(true);
const updateThemeTimeoutRef = useRef(null);
useEffect(() => { useEffect(() => {
if (isInitialMount.current) { if (isInitialMount.current) {
@@ -64,22 +62,17 @@ const Settings = () => {
} }
}, [settings]); }, [settings]);
useEffect(() => {
dispatch({
type: 'UPDATE_LOCAL_SETTINGS',
payload: { theme: colorScheme },
});
}, [colorScheme]);
const handleInputChange = useCallback((key, value) => { const handleInputChange = useCallback((key, value) => {
dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { [key]: value } }); dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { [key]: value } });
}, []); }, []);
const handleThemeChange = useCallback(() => {
const newTheme = state.localSettings.theme === 'dark' ? 'light' : 'dark';
dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { theme: newTheme } });
if (updateThemeTimeoutRef.current) {
clearTimeout(updateThemeTimeoutRef.current);
}
updateThemeTimeoutRef.current = setTimeout(() => {
updateTheme(newTheme);
}, 0);
}, [state.localSettings.theme, updateTheme]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
await updateSettings(state.localSettings); await updateSettings(state.localSettings);
@@ -100,30 +93,16 @@ const Settings = () => {
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
if (state.hasUnsavedChanges) { if (state.hasUnsavedChanges) {
updateTheme(state.initialSettings.theme);
dispatch({ type: 'RESET' }); dispatch({ type: 'RESET' });
} }
setSettingsModalVisible(false); setSettingsModalVisible(false);
}, [ }, [state.hasUnsavedChanges, setSettingsModalVisible]);
state.hasUnsavedChanges,
state.initialSettings.theme,
updateTheme,
setSettingsModalVisible,
]);
useEffect(() => {
return () => {
if (updateThemeTimeoutRef.current) {
clearTimeout(updateThemeTimeoutRef.current);
}
};
}, []);
return ( return (
<Modal <Modal
opened={settingsModalVisible} opened={settingsModalVisible}
onClose={handleClose} onClose={handleClose}
title="Settings" title={<Title order={2}>Settings</Title>}
centered centered
size="lg" size="lg"
> >
@@ -134,7 +113,7 @@ const Settings = () => {
)} )}
<AppearanceSettings <AppearanceSettings
themeSettings={state.localSettings.theme} themeSettings={state.localSettings.theme}
onThemeChange={handleThemeChange} onThemeChange={(newTheme) => handleInputChange('theme', newTheme)}
/> />
<EditorSettings <EditorSettings
autoSave={state.localSettings.autoSave} autoSave={state.localSettings.autoSave}

View File

@@ -1,18 +1,25 @@
import React from 'react'; import React from 'react';
import { Text, Switch, Stack } from '@mantine/core'; import { Text, Switch, Group, Box, Title } from '@mantine/core';
import { useSettings } from '../../contexts/SettingsContext';
const AppearanceSettings = ({ onThemeChange }) => {
const { colorScheme, toggleColorScheme } = useSettings();
const handleThemeChange = () => {
toggleColorScheme();
onThemeChange(colorScheme === 'dark' ? 'light' : 'dark');
};
const AppearanceSettings = ({ themeSettings, onThemeChange }) => {
return ( return (
<Stack spacing="xs"> <Box mb="md">
<Text fw={500} size="lg"> <Title order={3} mb="md">
Appearance Appearance
</Text> </Title>
<Switch <Group justify="space-between" align="center">
label="Dark Mode" <Text size="sm">Dark Mode</Text>
checked={themeSettings === 'dark'} <Switch checked={colorScheme === 'dark'} onChange={handleThemeChange} />
onChange={onThemeChange} </Group>
/> </Box>
</Stack>
); );
}; };

View File

@@ -1,21 +1,23 @@
import React from 'react'; import React from 'react';
import { Text, Switch, Stack, Tooltip } from '@mantine/core'; import { Text, Switch, Tooltip, Group, Box, Title } from '@mantine/core';
const EditorSettings = ({ autoSave, onAutoSaveChange }) => { const EditorSettings = ({ autoSave, onAutoSaveChange }) => {
return ( return (
<Stack spacing="xs" mt="md"> <Box mb="md">
<Text fw={500} size="lg"> <Title order={3} mb="md">
Editor Editor
</Text> </Title>
<Tooltip label="Auto Save feature is coming soon!" position="left"> <Tooltip label="Auto Save feature is coming soon!" position="left">
<Group justify="space-between" align="center">
<Text size="sm">Auto Save</Text>
<Switch <Switch
label="Auto Save"
checked={autoSave} checked={autoSave}
onChange={(event) => onAutoSaveChange(event.currentTarget.checked)} onChange={(event) => onAutoSaveChange(event.currentTarget.checked)}
disabled disabled
/> />
</Group>
</Tooltip> </Tooltip>
</Stack> </Box>
); );
}; };

View File

@@ -1,5 +1,14 @@
import React from 'react'; import React from 'react';
import { Text, Switch, TextInput, Stack, PasswordInput } from '@mantine/core'; import {
Text,
Switch,
TextInput,
Stack,
PasswordInput,
Group,
Box,
Title,
} from '@mantine/core';
const GitSettings = ({ const GitSettings = ({
gitEnabled, gitEnabled,
@@ -11,55 +20,79 @@ const GitSettings = ({
onInputChange, onInputChange,
}) => { }) => {
return ( return (
<Stack spacing="xs" mt="md"> <Stack spacing="md">
<Text fw={500} size="lg"> <Title order={3}>Git Integration</Title>
Git Integration <Group justify="space-between" align="center">
</Text> <Text size="sm">Enable Git</Text>
<Switch <Switch
label="Enable Git"
checked={gitEnabled} checked={gitEnabled}
onChange={(event) => onChange={(event) =>
onInputChange('gitEnabled', event.currentTarget.checked) onInputChange('gitEnabled', event.currentTarget.checked)
} }
/> />
</Group>
<Box>
<Text size="sm" mb="xs">
Git URL
</Text>
<TextInput <TextInput
label="Git URL"
value={gitUrl} value={gitUrl}
onChange={(event) => onInputChange('gitUrl', event.currentTarget.value)} onChange={(event) =>
onInputChange('gitUrl', event.currentTarget.value)
}
disabled={!gitEnabled} disabled={!gitEnabled}
placeholder="Enter Git URL"
/> />
</Box>
<Box>
<Text size="sm" mb="xs">
Git Username
</Text>
<TextInput <TextInput
label="Git Username"
value={gitUser} value={gitUser}
onChange={(event) => onChange={(event) =>
onInputChange('gitUser', event.currentTarget.value) onInputChange('gitUser', event.currentTarget.value)
} }
disabled={!gitEnabled} disabled={!gitEnabled}
placeholder="Enter Git username"
/> />
</Box>
<Box>
<Text size="sm" mb="xs">
Git Token
</Text>
<PasswordInput <PasswordInput
label="Git Token"
value={gitToken} value={gitToken}
onChange={(event) => onChange={(event) =>
onInputChange('gitToken', event.currentTarget.value) onInputChange('gitToken', event.currentTarget.value)
} }
disabled={!gitEnabled} disabled={!gitEnabled}
placeholder="Enter Git token"
/> />
</Box>
<Group justify="space-between" align="center">
<Text size="sm">Auto Commit</Text>
<Switch <Switch
label="Auto Commit"
checked={gitAutoCommit} checked={gitAutoCommit}
onChange={(event) => onChange={(event) =>
onInputChange('gitAutoCommit', event.currentTarget.checked) onInputChange('gitAutoCommit', event.currentTarget.checked)
} }
disabled={!gitEnabled} disabled={!gitEnabled}
/> />
</Group>
<Box>
<Text size="sm" mb="xs">
Commit Message Template
</Text>
<TextInput <TextInput
label="Commit Message Template"
value={gitCommitMsgTemplate} value={gitCommitMsgTemplate}
onChange={(event) => onChange={(event) =>
onInputChange('gitCommitMsgTemplate', event.currentTarget.value) onInputChange('gitCommitMsgTemplate', event.currentTarget.value)
} }
disabled={!gitEnabled} disabled={!gitEnabled}
placeholder="Enter commit message template"
/> />
</Box>
</Stack> </Stack>
); );
}; };

View File

@@ -1,10 +1,5 @@
import React, { import React, { createContext, useContext, useEffect, useMemo } from 'react';
createContext, import { useMantineColorScheme } from '@mantine/core';
useState,
useContext,
useEffect,
useMemo,
} from 'react';
import { fetchUserSettings, saveUserSettings } from '../services/api'; import { fetchUserSettings, saveUserSettings } from '../services/api';
import { DEFAULT_SETTINGS } from '../utils/constants'; import { DEFAULT_SETTINGS } from '../utils/constants';
@@ -13,14 +8,16 @@ const SettingsContext = createContext();
export const useSettings = () => useContext(SettingsContext); export const useSettings = () => useContext(SettingsContext);
export const SettingsProvider = ({ children }) => { export const SettingsProvider = ({ children }) => {
const [settings, setSettings] = useState(DEFAULT_SETTINGS); const { colorScheme, setColorScheme } = useMantineColorScheme();
const [loading, setLoading] = useState(true); const [settings, setSettings] = React.useState(DEFAULT_SETTINGS);
const [loading, setLoading] = React.useState(true);
useEffect(() => { useEffect(() => {
const loadSettings = async () => { const loadSettings = async () => {
try { try {
const userSettings = await fetchUserSettings(1); const userSettings = await fetchUserSettings(1);
setSettings(userSettings.settings); setSettings(userSettings.settings);
setColorScheme(userSettings.settings.theme);
} catch (error) { } catch (error) {
console.error('Failed to load user settings:', error); console.error('Failed to load user settings:', error);
} finally { } finally {
@@ -29,7 +26,7 @@ export const SettingsProvider = ({ children }) => {
}; };
loadSettings(); loadSettings();
}, []); }, [setColorScheme]);
const updateSettings = async (newSettings) => { const updateSettings = async (newSettings) => {
try { try {
@@ -38,27 +35,31 @@ export const SettingsProvider = ({ children }) => {
settings: newSettings, settings: newSettings,
}); });
setSettings(newSettings); setSettings(newSettings);
// Ensure the color scheme is updated when settings are saved
if (newSettings.theme) {
setColorScheme(newSettings.theme);
}
} catch (error) { } catch (error) {
console.error('Failed to save settings:', error); console.error('Failed to save settings:', error);
throw error; throw error;
} }
}; };
const updateTheme = (newTheme) => { const toggleColorScheme = () => {
setSettings((prevSettings) => ({ const newTheme = colorScheme === 'dark' ? 'light' : 'dark';
...prevSettings, setColorScheme(newTheme);
theme: newTheme, updateSettings({ ...settings, theme: newTheme });
}));
}; };
const contextValue = useMemo( const contextValue = useMemo(
() => ({ () => ({
settings, settings,
updateSettings, updateSettings,
updateTheme, toggleColorScheme,
loading, loading,
colorScheme,
}), }),
[settings, loading] [settings, loading, colorScheme]
); );
return ( return (