mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
First implementation of dark mode
This commit is contained in:
13
frontend/package-lock.json
generated
13
frontend/package-lock.json
generated
@@ -12,6 +12,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",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@codemirror/view": "^6.34.0",
|
"@codemirror/view": "^6.34.0",
|
||||||
"@geist-ui/core": "^2.3.8",
|
"@geist-ui/core": "^2.3.8",
|
||||||
"@geist-ui/icons": "^1.0.2",
|
"@geist-ui/icons": "^1.0.2",
|
||||||
@@ -2096,6 +2097,18 @@
|
|||||||
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==",
|
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.34.0",
|
"version": "6.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.0.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,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",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@codemirror/view": "^6.34.0",
|
"@codemirror/view": "^6.34.0",
|
||||||
"@geist-ui/core": "^2.3.8",
|
"@geist-ui/core": "^2.3.8",
|
||||||
"@geist-ui/icons": "^1.0.2",
|
"@geist-ui/icons": "^1.0.2",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
|
import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import MainContent from './components/MainContent';
|
import MainContent from './components/MainContent';
|
||||||
@@ -6,6 +6,7 @@ import useFileManagement from './hooks/useFileManagement';
|
|||||||
import './App.scss';
|
import './App.scss';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [themeType, setThemeType] = useState('light');
|
||||||
const {
|
const {
|
||||||
content,
|
content,
|
||||||
files,
|
files,
|
||||||
@@ -18,11 +19,15 @@ function App() {
|
|||||||
handleSave,
|
handleSave,
|
||||||
} = useFileManagement();
|
} = useFileManagement();
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
setThemeType(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeistProvider>
|
<GeistProvider themeType={themeType}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Page>
|
<Page>
|
||||||
<Header />
|
<Header currentTheme={themeType} onThemeChange={toggleTheme} />
|
||||||
<Page.Content>
|
<Page.Content>
|
||||||
<MainContent
|
<MainContent
|
||||||
content={content}
|
content={content}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import { EditorState } from "@codemirror/state";
|
|||||||
import { EditorView, keymap } from "@codemirror/view";
|
import { EditorView, keymap } from "@codemirror/view";
|
||||||
import { markdown } from "@codemirror/lang-markdown";
|
import { markdown } from "@codemirror/lang-markdown";
|
||||||
import { defaultKeymap } from "@codemirror/commands";
|
import { defaultKeymap } from "@codemirror/commands";
|
||||||
|
import { oneDark } from "@codemirror/theme-one-dark";
|
||||||
|
|
||||||
const Editor = ({ content, onChange, onSave, filePath }) => {
|
const Editor = ({ content, onChange, onSave, filePath, themeType }) => {
|
||||||
const editorRef = useRef();
|
const editorRef = useRef();
|
||||||
const viewRef = useRef();
|
const viewRef = useRef();
|
||||||
|
|
||||||
@@ -15,6 +16,24 @@ const Editor = ({ content, onChange, onSave, filePath }) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const theme = EditorView.theme({
|
||||||
|
"&": {
|
||||||
|
height: "100%",
|
||||||
|
fontSize: "14px",
|
||||||
|
},
|
||||||
|
".cm-scroller": {
|
||||||
|
overflow: "auto",
|
||||||
|
},
|
||||||
|
".cm-gutters": {
|
||||||
|
backgroundColor: themeType === "dark" ? "#1e1e1e" : "#f5f5f5",
|
||||||
|
color: themeType === "dark" ? "#858585" : "#999",
|
||||||
|
border: "none",
|
||||||
|
},
|
||||||
|
".cm-activeLineGutter": {
|
||||||
|
backgroundColor: themeType === "dark" ? "#2c313a" : "#e8e8e8",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: content,
|
doc: content,
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -32,6 +51,8 @@ const Editor = ({ content, onChange, onSave, filePath }) => {
|
|||||||
onChange(update.state.doc.toString());
|
onChange(update.state.doc.toString());
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
theme,
|
||||||
|
themeType === "dark" ? oneDark : [],
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,7 +66,7 @@ const Editor = ({ content, onChange, onSave, filePath }) => {
|
|||||||
return () => {
|
return () => {
|
||||||
view.destroy();
|
view.destroy();
|
||||||
};
|
};
|
||||||
}, [filePath]);
|
}, [filePath, themeType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewRef.current && content !== viewRef.current.state.doc.toString()) {
|
if (viewRef.current && content !== viewRef.current.state.doc.toString()) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Page, Text, User, Button, Spacer } from '@geist-ui/core';
|
|||||||
import { Settings as SettingsIcon } from '@geist-ui/icons';
|
import { Settings as SettingsIcon } from '@geist-ui/icons';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = ({ currentTheme, onThemeChange }) => {
|
||||||
const [settingsVisible, setSettingsVisible] = useState(false);
|
const [settingsVisible, setSettingsVisible] = useState(false);
|
||||||
|
|
||||||
const openSettings = () => setSettingsVisible(true);
|
const openSettings = () => setSettingsVisible(true);
|
||||||
@@ -16,7 +16,12 @@ const Header = () => {
|
|||||||
<User src="https://via.placeholder.com/40" name="User" />
|
<User src="https://via.placeholder.com/40" name="User" />
|
||||||
<Spacer w={0.5} />
|
<Spacer w={0.5} />
|
||||||
<Button auto icon={<SettingsIcon />} onClick={openSettings} />
|
<Button auto icon={<SettingsIcon />} onClick={openSettings} />
|
||||||
<Settings visible={settingsVisible} onClose={closeSettings} />
|
<Settings
|
||||||
|
visible={settingsVisible}
|
||||||
|
onClose={closeSettings}
|
||||||
|
currentTheme={currentTheme}
|
||||||
|
onThemeChange={onThemeChange}
|
||||||
|
/>
|
||||||
</Page.Header>
|
</Page.Header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Grid, Breadcrumbs, Tabs, Dot } from '@geist-ui/core';
|
import { Grid, Breadcrumbs, Tabs, Dot, useTheme } from '@geist-ui/core';
|
||||||
import { Code, Eye } from '@geist-ui/icons';
|
import { Code, Eye } from '@geist-ui/icons';
|
||||||
import Editor from './Editor';
|
import Editor from './Editor';
|
||||||
import FileTree from './FileTree';
|
import FileTree from './FileTree';
|
||||||
@@ -16,6 +16,7 @@ const MainContent = ({
|
|||||||
onSave,
|
onSave,
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTab, setActiveTab] = useState('source');
|
const [activeTab, setActiveTab] = useState('source');
|
||||||
|
const { type: themeType } = useTheme();
|
||||||
|
|
||||||
const renderBreadcrumbs = () => {
|
const renderBreadcrumbs = () => {
|
||||||
if (!selectedFile) return null;
|
if (!selectedFile) return null;
|
||||||
@@ -60,6 +61,7 @@ const MainContent = ({
|
|||||||
onChange={onContentChange}
|
onChange={onContentChange}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
filePath={selectedFile}
|
filePath={selectedFile}
|
||||||
|
themeType={themeType}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<MarkdownPreview content={content} />
|
<MarkdownPreview content={content} />
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, Input, Toggle, Select, Spacer, Button } from '@geist-ui/core';
|
import { Modal, Input, Toggle, Spacer, Button, Text } from '@geist-ui/core';
|
||||||
|
|
||||||
const Settings = ({ visible, onClose }) => {
|
const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
|
||||||
const [theme, setTheme] = useState('light');
|
|
||||||
const [fontSize, setFontSize] = useState('14');
|
const [fontSize, setFontSize] = useState('14');
|
||||||
const [autoSave, setAutoSave] = useState(false);
|
const [autoSave, setAutoSave] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
theme,
|
|
||||||
fontSize,
|
fontSize,
|
||||||
autoSave,
|
autoSave,
|
||||||
};
|
};
|
||||||
@@ -21,14 +19,13 @@ const Settings = ({ visible, onClose }) => {
|
|||||||
<Modal visible={visible} onClose={onClose}>
|
<Modal visible={visible} onClose={onClose}>
|
||||||
<Modal.Title>Settings</Modal.Title>
|
<Modal.Title>Settings</Modal.Title>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<Select
|
<Text>Theme</Text>
|
||||||
label="Theme"
|
<Toggle
|
||||||
value={theme}
|
checked={currentTheme === 'dark'}
|
||||||
onChange={(val) => setTheme(val)}
|
onChange={() => onThemeChange()}
|
||||||
>
|
>
|
||||||
<Select.Option value="light">Light</Select.Option>
|
Dark Mode
|
||||||
<Select.Option value="dark">Dark</Select.Option>
|
</Toggle>
|
||||||
</Select>
|
|
||||||
<Spacer h={0.5} />
|
<Spacer h={0.5} />
|
||||||
<Input
|
<Input
|
||||||
label="Font Size"
|
label="Font Size"
|
||||||
|
|||||||
27
frontend/src/customTheme.js
Normal file
27
frontend/src/customTheme.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Themes } from '@geist-ui/core'
|
||||||
|
|
||||||
|
const customDarkTheme = Themes.createFromLight({
|
||||||
|
type: 'custom-dark',
|
||||||
|
palette: {
|
||||||
|
background: '#1e1e1e',
|
||||||
|
foreground: '#d4d4d4',
|
||||||
|
selection: '#2c313a',
|
||||||
|
secondary: '#858585',
|
||||||
|
code: '#d4d4d4',
|
||||||
|
border: '#333333',
|
||||||
|
success: '#4EC9B0',
|
||||||
|
warning: '#9CDCFE',
|
||||||
|
error: '#F44747',
|
||||||
|
},
|
||||||
|
expressiveness: {
|
||||||
|
dropdownBoxShadow: '0 0 0 1px #333333',
|
||||||
|
shadowSmall: '0 0 0 1px #333333',
|
||||||
|
shadowMedium: '0 0 0 1px #333333',
|
||||||
|
shadowLarge: '0 0 0 1px #333333',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const customTheme = {
|
||||||
|
light: Themes.light,
|
||||||
|
dark: customDarkTheme,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user