Migrate Settings to Mantine ui

This commit is contained in:
2024-10-09 20:05:26 +02:00
parent 4872ca2e40
commit 221d8f5742
10 changed files with 1331 additions and 714 deletions

View File

@@ -1,3 +1,10 @@
{ {
"presets": ["@babel/preset-env", "@babel/preset-react"] "presets": [
"@babel/preset-env",
["@babel/preset-react", { "runtime": "automatic" }]
],
"plugins": [
"@babel/plugin-transform-class-properties",
"@babel/plugin-transform-runtime"
]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@
}, },
"homepage": "https://github.com/LordMathis/NovaMD#readme", "homepage": "https://github.com/LordMathis/NovaMD#readme",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.25.7",
"@codemirror/commands": "^6.6.2", "@codemirror/commands": "^6.6.2",
"@codemirror/lang-markdown": "^6.2.5", "@codemirror/lang-markdown": "^6.2.5",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
@@ -31,6 +32,8 @@
"@geist-ui/icons": "^1.0.2", "@geist-ui/icons": "^1.0.2",
"@mantine/core": "^7.13.2", "@mantine/core": "^7.13.2",
"@mantine/hooks": "^7.13.2", "@mantine/hooks": "^7.13.2",
"@mantine/modals": "^7.13.2",
"@mantine/notifications": "^7.13.2",
"@tabler/icons-react": "^3.19.0", "@tabler/icons-react": "^3.19.0",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"katex": "^0.16.11", "katex": "^0.16.11",
@@ -60,13 +63,20 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.7",
"@babel/preset-env": "^7.25.4", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-react": "^7.24.7", "@babel/plugin-transform-class-properties": "^7.25.7",
"@babel/plugin-transform-runtime": "^7.25.7",
"@babel/preset-env": "^7.25.7",
"@babel/preset-react": "^7.25.7",
"babel-loader": "^9.2.1", "babel-loader": "^9.2.1",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.0", "html-webpack-plugin": "^5.6.0",
"sass": "^1.79.3", "postcss": "^8.4.47",
"postcss-loader": "^8.1.1",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
"sass": "^1.79.4",
"sass-loader": "^16.0.2", "sass-loader": "^16.0.2",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"webpack": "^5.94.0", "webpack": "^5.94.0",

View File

@@ -0,0 +1,14 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};

View File

@@ -1,15 +1,28 @@
import React from 'react'; import React, { useState } from 'react';
import { GeistProvider, CssBaseline } from '@geist-ui/core'; import { GeistProvider, CssBaseline } from '@geist-ui/core';
import { MantineProvider, AppShell, Container } from '@mantine/core'; import {
MantineProvider,
createTheme,
AppShell,
Container,
} from '@mantine/core';
import { Notifications } from '@mantine/notifications';
import { ModalsProvider } from '@mantine/modals';
import Header from './components/Header'; import Header from './components/Header';
import MainContent from './components/MainContent'; 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';
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 { settings, loading } = useSettings();
const [opened, setOpened] = useState(false);
if (loading) { if (loading) {
return <div>Loading...</div>; return <div>Loading...</div>;
@@ -17,40 +30,21 @@ function AppContent() {
return ( return (
<GeistProvider themeType={settings.theme}> <GeistProvider themeType={settings.theme}>
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
colorScheme: settings.theme,
components: {
Container: {
defaultProps: {
sizes: {
xs: 540,
sm: 720,
md: 960,
lg: 1140,
xl: 1320,
},
},
},
},
}}
>
<CssBaseline /> <CssBaseline />
<MantineProvider theme={mantineTheme} defaultColorScheme={settings.theme}>
<Notifications />
<ModalsProvider>
<AppShell header={{ height: 60 }} padding="md"> <AppShell header={{ height: 60 }} padding="md">
<AppShell.Header> <AppShell.Header>
<Container size="xl">
<Header /> <Header />
</Container>
</AppShell.Header> </AppShell.Header>
<AppShell.Main> <AppShell.Main>
<Container size="xl"> <Container size="xl">
<MainContent /> <MainContent />
</Container> </Container>
</AppShell.Main> </AppShell.Main>
</AppShell> </AppShell>
</ModalsProvider>
</MantineProvider> </MantineProvider>
</GeistProvider> </GeistProvider>
); );

View File

@@ -1,5 +1,7 @@
import React, { useReducer, useEffect, useCallback, useRef } from 'react'; import React, { useReducer, useEffect, useCallback, useRef } from 'react';
import { Modal, Spacer, Dot, useToasts } from '@geist-ui/core'; import { Modal, Badge, Button, Group } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
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';
import EditorSettings from './settings/EditorSettings'; import EditorSettings from './settings/EditorSettings';
@@ -51,7 +53,6 @@ function settingsReducer(state, action) {
const Settings = () => { const Settings = () => {
const { settings, updateSettings, updateTheme } = useSettings(); const { settings, updateSettings, updateTheme } = useSettings();
const { settingsModalVisible, setSettingsModalVisible } = useModalContext(); const { settingsModalVisible, setSettingsModalVisible } = useModalContext();
const { setToast } = useToasts();
const [state, dispatch] = useReducer(settingsReducer, initialState); const [state, dispatch] = useReducer(settingsReducer, initialState);
const isInitialMount = useRef(true); const isInitialMount = useRef(true);
const updateThemeTimeoutRef = useRef(null); const updateThemeTimeoutRef = useRef(null);
@@ -71,7 +72,6 @@ const Settings = () => {
const newTheme = state.localSettings.theme === 'dark' ? 'light' : 'dark'; const newTheme = state.localSettings.theme === 'dark' ? 'light' : 'dark';
dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { theme: newTheme } }); dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { theme: newTheme } });
// Debounce the theme update
if (updateThemeTimeoutRef.current) { if (updateThemeTimeoutRef.current) {
clearTimeout(updateThemeTimeoutRef.current); clearTimeout(updateThemeTimeoutRef.current);
} }
@@ -84,20 +84,23 @@ const Settings = () => {
try { try {
await updateSettings(state.localSettings); await updateSettings(state.localSettings);
dispatch({ type: 'MARK_SAVED' }); dispatch({ type: 'MARK_SAVED' });
setToast({ text: 'Settings saved successfully', type: 'success' }); notifications.show({
message: 'Settings saved successfully',
color: 'green',
});
setSettingsModalVisible(false); setSettingsModalVisible(false);
} catch (error) { } catch (error) {
console.error('Failed to save settings:', error); console.error('Failed to save settings:', error);
setToast({ notifications.show({
text: 'Failed to save settings: ' + error.message, message: 'Failed to save settings: ' + error.message,
type: 'error', color: 'red',
}); });
} }
}; };
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
if (state.hasUnsavedChanges) { if (state.hasUnsavedChanges) {
updateTheme(state.initialSettings.theme); // Revert theme if not saved updateTheme(state.initialSettings.theme);
dispatch({ type: 'RESET' }); dispatch({ type: 'RESET' });
} }
setSettingsModalVisible(false); setSettingsModalVisible(false);
@@ -117,24 +120,26 @@ const Settings = () => {
}, []); }, []);
return ( return (
<Modal visible={settingsModalVisible} onClose={handleClose}> <Modal
<Modal.Title> opened={settingsModalVisible}
Settings onClose={handleClose}
title="Settings"
centered
size="lg"
>
{state.hasUnsavedChanges && ( {state.hasUnsavedChanges && (
<Dot type="warning" style={{ marginLeft: '8px' }} /> <Badge color="yellow" variant="light" mb="md">
Unsaved Changes
</Badge>
)} )}
</Modal.Title>
<Modal.Content>
<AppearanceSettings <AppearanceSettings
themeSettings={state.localSettings.theme} themeSettings={state.localSettings.theme}
onThemeChange={handleThemeChange} onThemeChange={handleThemeChange}
/> />
<Spacer h={1} />
<EditorSettings <EditorSettings
autoSave={state.localSettings.autoSave} autoSave={state.localSettings.autoSave}
onAutoSaveChange={(value) => handleInputChange('autoSave', value)} onAutoSaveChange={(value) => handleInputChange('autoSave', value)}
/> />
<Spacer h={1} />
<GitSettings <GitSettings
gitEnabled={state.localSettings.gitEnabled} gitEnabled={state.localSettings.gitEnabled}
gitUrl={state.localSettings.gitUrl} gitUrl={state.localSettings.gitUrl}
@@ -144,11 +149,12 @@ const Settings = () => {
gitCommitMsgTemplate={state.localSettings.gitCommitMsgTemplate} gitCommitMsgTemplate={state.localSettings.gitCommitMsgTemplate}
onInputChange={handleInputChange} onInputChange={handleInputChange}
/> />
</Modal.Content> <Group justify="flex-end" mt="xl">
<Modal.Action passive onClick={handleClose}> <Button variant="default" onClick={handleClose}>
Cancel Cancel
</Modal.Action> </Button>
<Modal.Action onClick={handleSubmit}>Save Changes</Modal.Action> <Button onClick={handleSubmit}>Save Changes</Button>
</Group>
</Modal> </Modal>
); );
}; };

View File

@@ -1,15 +1,18 @@
import React from 'react'; import React from 'react';
import { Text, Toggle } from '@geist-ui/core'; import { Text, Switch, Stack } from '@mantine/core';
const AppearanceSettings = ({ themeSettings, onThemeChange }) => { const AppearanceSettings = ({ themeSettings, onThemeChange }) => {
return ( return (
<div className="setting-group"> <Stack spacing="xs">
<Text h4>Appearance</Text> <Text fw={500} size="lg">
<div className="setting-item"> Appearance
<Text>Dark Mode</Text> </Text>
<Toggle checked={themeSettings === 'dark'} onChange={onThemeChange} /> <Switch
</div> label="Dark Mode"
</div> checked={themeSettings === 'dark'}
onChange={onThemeChange}
/>
</Stack>
); );
}; };

View File

@@ -1,25 +1,21 @@
import React from 'react'; import React from 'react';
import { Text, Toggle, Tooltip } from '@geist-ui/core'; import { Text, Switch, Stack, Tooltip } from '@mantine/core';
const EditorSettings = ({ autoSave, onAutoSaveChange }) => { const EditorSettings = ({ autoSave, onAutoSaveChange }) => {
return ( return (
<div className="setting-group"> <Stack spacing="xs" mt="md">
<Text h4>Editor</Text> <Text fw={500} size="lg">
<div className="setting-item"> Editor
<Text>Auto Save</Text> </Text>
<Tooltip <Tooltip label="Auto Save feature is coming soon!" position="left">
text="Auto Save feature is coming soon!" <Switch
type="dark" label="Auto Save"
placement="left"
>
<Toggle
checked={autoSave} checked={autoSave}
onChange={(e) => onAutoSaveChange(e.target.checked)} onChange={(event) => onAutoSaveChange(event.currentTarget.checked)}
disabled disabled
/> />
</Tooltip> </Tooltip>
</div> </Stack>
</div>
); );
}; };

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Text, Toggle, Input, Spacer } from '@geist-ui/core'; import { Text, Switch, TextInput, Stack, PasswordInput } from '@mantine/core';
const GitSettings = ({ const GitSettings = ({
gitEnabled, gitEnabled,
@@ -11,60 +11,56 @@ const GitSettings = ({
onInputChange, onInputChange,
}) => { }) => {
return ( return (
<div className="setting-group"> <Stack spacing="xs" mt="md">
<Text h4>Git Integration</Text> <Text fw={500} size="lg">
<div className="setting-item"> Git Integration
<Text>Enable Git</Text> </Text>
<Toggle <Switch
label="Enable Git"
checked={gitEnabled} checked={gitEnabled}
onChange={(e) => onInputChange('gitEnabled', e.target.checked)} onChange={(event) =>
onInputChange('gitEnabled', event.currentTarget.checked)
}
/> />
</div> <TextInput
<div className={gitEnabled ? '' : 'disabled'}>
<Input
width="100%"
label="Git URL" label="Git URL"
value={gitUrl} value={gitUrl}
onChange={(e) => onInputChange('gitUrl', e.target.value)} onChange={(event) => onInputChange('gitUrl', event.currentTarget.value)}
disabled={!gitEnabled} disabled={!gitEnabled}
/> />
<Spacer h={0.5} /> <TextInput
<Input
width="100%"
label="Git Username" label="Git Username"
value={gitUser} value={gitUser}
onChange={(e) => onInputChange('gitUser', e.target.value)} onChange={(event) =>
disabled={!gitEnabled} onInputChange('gitUser', event.currentTarget.value)
/>
<Spacer h={0.5} />
<Input.Password
width="100%"
label="Git Token"
value={gitToken}
onChange={(e) => onInputChange('gitToken', e.target.value)}
disabled={!gitEnabled}
/>
<Spacer h={0.5} />
<div className="setting-item">
<Text>Auto Commit</Text>
<Toggle
checked={gitAutoCommit}
onChange={(e) => onInputChange('gitAutoCommit', e.target.checked)}
disabled={!gitEnabled}
/>
</div>
<Spacer h={0.5} />
<Input
width="100%"
label="Commit Message Template"
value={gitCommitMsgTemplate}
onChange={(e) =>
onInputChange('gitCommitMsgTemplate', e.target.value)
} }
disabled={!gitEnabled} disabled={!gitEnabled}
/> />
</div> <PasswordInput
</div> label="Git Token"
value={gitToken}
onChange={(event) =>
onInputChange('gitToken', event.currentTarget.value)
}
disabled={!gitEnabled}
/>
<Switch
label="Auto Commit"
checked={gitAutoCommit}
onChange={(event) =>
onInputChange('gitAutoCommit', event.currentTarget.checked)
}
disabled={!gitEnabled}
/>
<TextInput
label="Commit Message Template"
value={gitCommitMsgTemplate}
onChange={(event) =>
onInputChange('gitCommitMsgTemplate', event.currentTarget.value)
}
disabled={!gitEnabled}
/>
</Stack>
); );
}; };

View File

@@ -20,11 +20,11 @@ module.exports = (env, argv) => {
}, },
{ {
test: /\.scss$/, test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'], use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: ['style-loader', 'css-loader'], use: ['style-loader', 'css-loader', 'postcss-loader'],
}, },
], ],
}, },