feat: theming support

This commit is contained in:
Haishan 2018-12-07 00:08:55 +08:00
parent fdf69dbfe2
commit 7abbdb6477
14 changed files with 129 additions and 87 deletions

View file

@ -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;

View file

@ -1,6 +1,5 @@
.root {
padding: 10px 40px 40px;
color: #ddd;
> div {
width: 360px;
@ -8,6 +7,5 @@
}
.label {
// color: #aaa;
padding: 16px 0;
}

View file

@ -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;

View file

@ -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 (

View file

@ -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);
}
}
}

View file

@ -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} />

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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 (

View file

@ -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;

View file

@ -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 }) {