feat: theming support
This commit is contained in:
parent
fdf69dbfe2
commit
7abbdb6477
14 changed files with 129 additions and 87 deletions
|
@ -1,8 +1,8 @@
|
|||
.btn {
|
||||
-webkit-appearance: none;
|
||||
outline: none;
|
||||
color: #bebebe;
|
||||
background: #232323;
|
||||
color: var(--color-btn-fg);
|
||||
background: var(--color-btn-bg);
|
||||
border: 1px solid #555;
|
||||
border-radius: 100px;
|
||||
padding: 6px 12px;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.root {
|
||||
padding: 10px 40px 40px;
|
||||
color: #ddd;
|
||||
|
||||
> div {
|
||||
width: 360px;
|
||||
|
@ -8,6 +7,5 @@
|
|||
}
|
||||
|
||||
.label {
|
||||
// color: #aaa;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.input {
|
||||
-webkit-appearance: none;
|
||||
background-color: #2d2d30;
|
||||
background-color: var(--color-input-bg);
|
||||
background-image: none;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #3f3f3f;
|
||||
border: 1px solid var(--color-input-border);
|
||||
box-sizing: border-box;
|
||||
color: #c1c1c1;
|
||||
display: inline-block;
|
||||
|
|
|
@ -45,15 +45,15 @@ LogLine.propTypes = {
|
|||
|
||||
export default function Logs() {
|
||||
const [logs, setLogs] = useState([]);
|
||||
const { apiConfig } = useComponentState(getClashAPIConfig);
|
||||
const { hostname, port, secret } = useComponentState(getClashAPIConfig);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const x = fetchLogs(apiConfig);
|
||||
const x = fetchLogs({ hostname, port, secret });
|
||||
setLogs(x.logs);
|
||||
return x.subscribe(() => setLogs(x.logs));
|
||||
},
|
||||
[apiConfig.hostname, apiConfig.port, apiConfig.secret]
|
||||
[hostname, port, secret]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -44,14 +44,14 @@ $heightHeader: 76px;
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
color: $colorf;
|
||||
color: var(--color-text);
|
||||
|
||||
:global {
|
||||
li {
|
||||
background: #1f1f1f;
|
||||
background: var(--color-background);
|
||||
}
|
||||
li.even {
|
||||
background: #282828;
|
||||
background: var(--color-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { HashRouter as Router, Route } from 'react-router-dom';
|
|||
// import { hot } from 'react-hot-loader';
|
||||
// import createHistory from 'history/createHashHistory';
|
||||
// import createHistory from 'history/createBrowserHistory';
|
||||
import Theme from 'c/Theme';
|
||||
import SideBar from 'c/SideBar';
|
||||
import Home from 'c/Home';
|
||||
import Logs from 'c/Logs';
|
||||
|
@ -23,27 +22,23 @@ import s0 from './Root.module.scss';
|
|||
|
||||
window.store = store;
|
||||
|
||||
const Root = () => {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Theme>
|
||||
<Router>
|
||||
<div className={s0.app}>
|
||||
<APIDiscovery />
|
||||
<Route path="/" render={() => <SideBar />} />
|
||||
<div className={s0.content}>
|
||||
<Route exact path="/" render={() => <Home />} />
|
||||
<Route exact path="/overview" render={() => <Home />} />
|
||||
<Route exact path="/configs" render={() => <Config />} />
|
||||
<Route exact path="/logs" render={() => <Logs />} />
|
||||
<Route exact path="/proxies" render={() => <Proxies />} />
|
||||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
</Theme>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
const Root = () => (
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<div className={s0.app}>
|
||||
<APIDiscovery />
|
||||
<Route path="/" render={() => <SideBar />} />
|
||||
<div className={s0.content}>
|
||||
<Route exact path="/" render={() => <Home />} />
|
||||
<Route exact path="/overview" render={() => <Home />} />
|
||||
<Route exact path="/configs" render={() => <Config />} />
|
||||
<Route exact path="/logs" render={() => <Logs />} />
|
||||
<Route exact path="/proxies" render={() => <Proxies />} />
|
||||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
</Provider>
|
||||
);
|
||||
// <Route exact path="/__0" component={StyleGuide} />
|
||||
// <Route exact path="/__1" component={Loading} />
|
||||
|
||||
|
|
|
@ -67,3 +67,34 @@ body {
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body,
|
||||
body.dark {
|
||||
--color-background: #202020;
|
||||
--color-text: #ddd;
|
||||
--color-text-secondary: #ccc;
|
||||
--color-bg-sidebar: #2d2d30;
|
||||
--color-sb-active-row-bg: #494b4e;
|
||||
--color-input-bg: #2d2d30;
|
||||
--color-input-border: #3f3f3f;
|
||||
--color-toggle-bg: #353535;
|
||||
--color-toggle-selected: #181818;
|
||||
--color-icon: #c7c7c7;
|
||||
--color-btn-bg: #232323;
|
||||
--color-btn-fg: #bebebe;
|
||||
}
|
||||
|
||||
body.light {
|
||||
--color-background: #fbfbfb;
|
||||
--color-text: #222;
|
||||
--color-text-secondary: #646464;
|
||||
--color-bg-sidebar: #e7e7e7;
|
||||
--color-sb-active-row-bg: #d0d0d0;
|
||||
--color-input-bg: #ffffff;
|
||||
--color-input-border: #c0c0c0;
|
||||
--color-toggle-bg: #ffffff;
|
||||
--color-toggle-selected: #d7d7d7;
|
||||
--color-icon: #5b5b5b;
|
||||
--color-btn-bg: #f4f4f4;
|
||||
--color-btn-fg: #101010;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
// a router linke
|
||||
.rowActive,
|
||||
.row {
|
||||
color: white;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
|
||||
display: flex;
|
||||
|
@ -37,12 +37,12 @@
|
|||
// }
|
||||
|
||||
svg {
|
||||
color: #c7c7c7;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
}
|
||||
|
||||
.rowActive {
|
||||
color: white;
|
||||
background: #494b4e;
|
||||
background: var(--color-sb-active-row-bg);
|
||||
}
|
||||
|
||||
.label {
|
||||
|
@ -64,5 +64,6 @@
|
|||
align-items: center;
|
||||
svg {
|
||||
display: block;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import React, { memo, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useComponentState } from 'm/store';
|
||||
|
||||
import { getTheme } from 'd/app';
|
||||
|
||||
import s0 from './Theme.module.scss';
|
||||
|
||||
const mapStateToProps = s => ({ theme: getTheme(s) });
|
||||
|
||||
function Theme({ children }) {
|
||||
const { theme } = useComponentState(mapStateToProps);
|
||||
const className = theme === 'dark' ? s0.dark : s0.light;
|
||||
return <div className={className}>{children}</div>;
|
||||
}
|
||||
|
||||
export default memo(Theme);
|
|
@ -1,11 +0,0 @@
|
|||
.dark {
|
||||
--color-background: #202020;
|
||||
--color-text: #ddd;
|
||||
--color-bg-sidebar: #2d2d30;
|
||||
}
|
||||
|
||||
.light {
|
||||
--color-background: #eee;
|
||||
--color-text: #222;
|
||||
--color-bg-sidebar: #2d2d30;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
.ToggleSwitch {
|
||||
user-select: none;
|
||||
border: 1px solid #525252;
|
||||
color: #eee;
|
||||
background: #353535;
|
||||
color: var(--color-text);
|
||||
background: var(--color-toggle-bg);
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
|
@ -30,5 +30,5 @@
|
|||
left: 0;
|
||||
height: 100%;
|
||||
transition: left 0.2s ease-out;
|
||||
background: #181818;
|
||||
background: var(--color-toggle-selected);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import prettyBytes from 'm/pretty-bytes';
|
|||
import { fetchData } from '../api/traffic';
|
||||
import { unstable_createResource as createResource } from 'react-cache';
|
||||
import { useComponentState } from 'm/store';
|
||||
import { getClashAPIConfig } from 'd/app';
|
||||
import { getClashAPIConfig, getTheme } from 'd/app';
|
||||
|
||||
// const delay = ms => new Promise(r => setTimeout(r, ms));
|
||||
const chartJSResource = createResource(() => {
|
||||
|
@ -30,24 +30,42 @@ const colorCombo = {
|
|||
backgroundColor: 'rgba(69, 154, 248, 0.3)',
|
||||
borderColor: 'rgb(69, 154, 248)'
|
||||
}
|
||||
},
|
||||
2: {
|
||||
up: {
|
||||
backgroundColor: 'rgba(94, 175, 223, 0.3)',
|
||||
borderColor: 'rgb(94, 175, 223)'
|
||||
},
|
||||
down: {
|
||||
backgroundColor: 'rgba(139, 227, 195, 0.3)',
|
||||
borderColor: 'rgb(139, 227, 195)'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const upProps = {
|
||||
...colorCombo['0'].up,
|
||||
label: 'Up',
|
||||
const commonDataSetProps = {
|
||||
borderWidth: 1,
|
||||
lineTension: 0,
|
||||
// lineTension: 0,
|
||||
pointRadius: 0
|
||||
};
|
||||
|
||||
const downProps = {
|
||||
...colorCombo['0'].down,
|
||||
label: 'Down',
|
||||
borderWidth: 1,
|
||||
lineTension: 0,
|
||||
pointRadius: 0
|
||||
};
|
||||
function getUploadProps(theme = 'dark') {
|
||||
const i = theme === 'dark' ? '0' : '2';
|
||||
return {
|
||||
...commonDataSetProps,
|
||||
...colorCombo[i].up,
|
||||
label: 'Up'
|
||||
};
|
||||
}
|
||||
|
||||
function getDownloadProps(theme = 'dark') {
|
||||
const i = theme === 'dark' ? '0' : '2';
|
||||
return {
|
||||
...commonDataSetProps,
|
||||
...colorCombo[i].down,
|
||||
label: 'Down'
|
||||
};
|
||||
}
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
|
@ -122,10 +140,14 @@ const chartWrapperStyle = {
|
|||
export default function TrafficChart() {
|
||||
const Chart = chartJSResource.read();
|
||||
const { hostname, port, secret } = useComponentState(getClashAPIConfig);
|
||||
const theme = useComponentState(getTheme);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const ctx = document.getElementById('trafficChart').getContext('2d');
|
||||
const traffic = fetchData({ hostname, port, secret });
|
||||
const upProps = getUploadProps(theme);
|
||||
const downProps = getDownloadProps(theme);
|
||||
const data = {
|
||||
labels: traffic.labels,
|
||||
datasets: [
|
||||
|
@ -144,9 +166,13 @@ export default function TrafficChart() {
|
|||
data,
|
||||
options
|
||||
});
|
||||
return traffic.subscribe(() => c.update());
|
||||
const unsubscribe = traffic.subscribe(() => c.update());
|
||||
return () => {
|
||||
unsubscribe();
|
||||
c.destroy();
|
||||
};
|
||||
},
|
||||
[hostname, port, secret]
|
||||
[hostname, port, secret, theme]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.TrafficNow {
|
||||
color: #eee;
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
|||
width: 200px;
|
||||
|
||||
div:nth-child(1) {
|
||||
color: #ccc;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
div:nth-child(2) {
|
||||
padding: 10px 0 0;
|
||||
|
|
|
@ -27,11 +27,26 @@ export function updateClashAPIConfig({ hostname: iHostname, port, secret }) {
|
|||
};
|
||||
}
|
||||
|
||||
const bodyElement = document.body;
|
||||
function setTheme(theme = 'dark') {
|
||||
if (theme === 'dark') {
|
||||
bodyElement.classList.remove('light');
|
||||
bodyElement.classList.add('dark');
|
||||
} else {
|
||||
bodyElement.classList.remove('dark');
|
||||
bodyElement.classList.add('light');
|
||||
}
|
||||
}
|
||||
|
||||
export function switchTheme() {
|
||||
return (dispatch, getState) => {
|
||||
const currentTheme = getTheme(getState());
|
||||
const theme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
// side effect
|
||||
setTheme(theme);
|
||||
dispatch({ type: SwitchTheme, payload: { theme } });
|
||||
// side effect
|
||||
saveState(StorageKey, getState().app);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -41,14 +56,18 @@ const defaultState = {
|
|||
hostname: '127.0.0.1',
|
||||
port: '7892',
|
||||
secret: ''
|
||||
}
|
||||
},
|
||||
theme: 'dark'
|
||||
};
|
||||
|
||||
function getInitialState() {
|
||||
let s = loadState(StorageKey);
|
||||
if (!s) s = defaultState;
|
||||
// TODO flat clashAPIConfig?
|
||||
return { theme: 'dark', ...s };
|
||||
|
||||
// set initial theme
|
||||
setTheme(s.theme);
|
||||
return s;
|
||||
}
|
||||
|
||||
export default function reducer(state = getInitialState(), { type, payload }) {
|
||||
|
|
Loading…
Reference in a new issue