feat: initial theming support
This commit is contained in:
parent
a265c62020
commit
3584ff6179
19 changed files with 223 additions and 150 deletions
|
@ -1,29 +1,19 @@
|
|||
import {
|
||||
getAPIConfig,
|
||||
getURLAndInit,
|
||||
genCommonHeaders,
|
||||
getAPIBaseURL
|
||||
} from 'm/request-helper';
|
||||
|
||||
const endpoint = '/configs';
|
||||
|
||||
function getURLAndInit() {
|
||||
const c = getAPIConfig();
|
||||
const baseURL = getAPIBaseURL(c);
|
||||
const headers = genCommonHeaders(c);
|
||||
return {
|
||||
url: baseURL + endpoint,
|
||||
init: { headers }
|
||||
};
|
||||
export async function fetchConfigs(apiConfig) {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
return await fetch(url + endpoint, init);
|
||||
}
|
||||
|
||||
export async function fetchConfigs() {
|
||||
const { url, init } = getURLAndInit();
|
||||
return await fetch(url, init);
|
||||
}
|
||||
|
||||
export async function updateConfigs(o) {
|
||||
const { url, init } = getURLAndInit();
|
||||
return await fetch(url, {
|
||||
export async function updateConfigs(apiConfig, o) {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
return await fetch(url + endpoint, {
|
||||
...init,
|
||||
method: 'PUT',
|
||||
// mode: 'cors',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
getAPIConfig,
|
||||
getURLAndInit,
|
||||
genCommonHeaders,
|
||||
getAPIBaseURL
|
||||
} from 'm/request-helper';
|
||||
|
@ -10,16 +10,6 @@ const getRandomStr = () => {
|
|||
return Math.floor((1 + Math.random()) * 0x10000).toString(16);
|
||||
};
|
||||
|
||||
function getURLAndInit() {
|
||||
const c = getAPIConfig();
|
||||
const baseURL = getAPIBaseURL(c);
|
||||
const headers = genCommonHeaders(c);
|
||||
return {
|
||||
url: baseURL + endpoint,
|
||||
init: { headers }
|
||||
};
|
||||
}
|
||||
|
||||
const Size = 300;
|
||||
|
||||
let even = false;
|
||||
|
@ -71,11 +61,11 @@ function pump(reader) {
|
|||
});
|
||||
}
|
||||
|
||||
function fetchLogs() {
|
||||
function fetchLogs(apiConfig) {
|
||||
if (store.fetched) return store;
|
||||
store.fetched = true;
|
||||
const { url, init } = getURLAndInit();
|
||||
fetch(url, init)
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
fetch(url + endpoint, init)
|
||||
.then(response => {
|
||||
const reader = response.body.getReader();
|
||||
pump(reader);
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
import {
|
||||
getAPIConfig,
|
||||
getURLAndInit,
|
||||
genCommonHeaders,
|
||||
getAPIBaseURL
|
||||
} from 'm/request-helper';
|
||||
const endpoint = '/proxies';
|
||||
|
||||
function getURLAndInit() {
|
||||
const c = getAPIConfig();
|
||||
const baseURL = getAPIBaseURL(c);
|
||||
const headers = genCommonHeaders(c);
|
||||
return {
|
||||
url: baseURL + endpoint,
|
||||
init: { headers }
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
$ curl "http://127.0.0.1:8080/proxies/Proxy" -XPUT -d '{ "name": "ss3" }' -i
|
||||
HTTP/1.1 400 Bad Request
|
||||
|
@ -32,16 +22,16 @@ Vary: Origin
|
|||
Date: Tue, 16 Oct 2018 16:38:33 GMT
|
||||
*/
|
||||
|
||||
async function fetchProxies() {
|
||||
const { url, init } = getURLAndInit();
|
||||
const res = await fetch(url, init);
|
||||
async function fetchProxies(config) {
|
||||
const { url, init } = getURLAndInit(config);
|
||||
const res = await fetch(url + endpoint, init);
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
async function requestToSwitchProxy(name1, name2) {
|
||||
async function requestToSwitchProxy(apiConfig, name1, name2) {
|
||||
const body = { name: name2 };
|
||||
const { url, init } = getURLAndInit();
|
||||
const fullURL = `${url}/${name1}`;
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
const fullURL = `${url}${endpoint}/${name1}`;
|
||||
return await fetch(fullURL, {
|
||||
...init,
|
||||
method: 'PUT',
|
||||
|
@ -49,10 +39,10 @@ async function requestToSwitchProxy(name1, name2) {
|
|||
});
|
||||
}
|
||||
|
||||
async function requestDelayForProxy(name) {
|
||||
const { url, init } = getURLAndInit();
|
||||
async function requestDelayForProxy(apiConfig, name) {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
const qs = 'timeout=5000&url=http://www.google.com/generate_204';
|
||||
const fullURL = `${url}/${name}/delay?${qs}`;
|
||||
const fullURL = `${url}${endpoint}/${name}/delay?${qs}`;
|
||||
return await fetch(fullURL, init);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,11 @@
|
|||
import {
|
||||
getAPIConfig,
|
||||
getURLAndInit,
|
||||
genCommonHeaders,
|
||||
getAPIBaseURL
|
||||
} from 'm/request-helper';
|
||||
const endpoint = '/traffic';
|
||||
const textDecoder = new TextDecoder('utf-8', { stream: true });
|
||||
|
||||
function getURLAndInit() {
|
||||
const c = getAPIConfig();
|
||||
const baseURL = getAPIBaseURL(c);
|
||||
const headers = genCommonHeaders(c);
|
||||
return {
|
||||
url: baseURL + endpoint,
|
||||
init: { headers }
|
||||
};
|
||||
}
|
||||
|
||||
const Size = 150;
|
||||
|
||||
const traffic = {
|
||||
|
@ -69,10 +59,10 @@ function pump(reader) {
|
|||
});
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
function fetchData(apiConfig) {
|
||||
if (fetched) return traffic;
|
||||
const { url, init } = getURLAndInit();
|
||||
fetch(url, init).then(response => {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
fetch(url + endpoint, init).then(response => {
|
||||
if (response.ok) {
|
||||
fetched = true;
|
||||
const reader = response.body.getReader();
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
.h1 {
|
||||
padding: 0 40px;
|
||||
color: #ddd;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import { useComponentState } from 'm/store';
|
||||
import { getClashAPIConfig } from 'd/app';
|
||||
|
||||
import Icon from 'c/Icon';
|
||||
import ContentHeader from 'c/ContentHeader';
|
||||
// TODO move this into a redux action
|
||||
import { fetchLogs } from '../api/logs';
|
||||
|
||||
import yacd from 's/yacd.svg';
|
||||
|
@ -42,12 +45,16 @@ LogLine.propTypes = {
|
|||
|
||||
export default function Logs() {
|
||||
const [logs, setLogs] = useState([]);
|
||||
const { apiConfig } = useComponentState(getClashAPIConfig);
|
||||
|
||||
useEffect(() => {
|
||||
const x = fetchLogs();
|
||||
useEffect(
|
||||
() => {
|
||||
const x = fetchLogs(apiConfig);
|
||||
setLogs(x.logs);
|
||||
return x.subscribe(() => setLogs(x.logs));
|
||||
}, []);
|
||||
},
|
||||
[apiConfig.hostname, apiConfig.port, apiConfig.secret]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -4,7 +4,7 @@ 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,8 +23,10 @@ import s0 from './Root.module.scss';
|
|||
|
||||
window.store = store;
|
||||
|
||||
const Root = () => (
|
||||
const Root = () => {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Theme>
|
||||
<Router>
|
||||
<div className={s0.app}>
|
||||
<APIDiscovery />
|
||||
|
@ -38,8 +40,10 @@ const Root = () => (
|
|||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
</Theme>
|
||||
</Provider>
|
||||
);
|
||||
);
|
||||
};
|
||||
// <Route exact path="/__0" component={StyleGuide} />
|
||||
// <Route exact path="/__1" component={Loading} />
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
.app {
|
||||
display: flex;
|
||||
color: #ddd;
|
||||
background: #202020;
|
||||
|
||||
background: var(--color-background);
|
||||
color: var(--color-text);
|
||||
min-height: 300px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
|||
.content {
|
||||
flex-grow: 1;
|
||||
overflow: scroll;
|
||||
// background: #202020;
|
||||
|
||||
// $w: 7px;
|
||||
// &::-webkit-scrollbar {
|
||||
|
|
|
@ -2,6 +2,9 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { useActions } from 'm/store';
|
||||
import { switchTheme } from 'd/app';
|
||||
|
||||
import Icon from 'c/Icon';
|
||||
|
||||
import activity from 's/activity.svg';
|
||||
|
@ -9,6 +12,7 @@ import settings from 's/settings.svg';
|
|||
import globe from 's/globe.svg';
|
||||
import file from 's/file.svg';
|
||||
import yacd from 's/yacd.svg';
|
||||
import moon from 's/moon.svg';
|
||||
|
||||
import s from 'c/SideBar.module.scss';
|
||||
|
||||
|
@ -27,7 +31,10 @@ SideBarRow.propTypes = {
|
|||
labelText: PropTypes.string
|
||||
};
|
||||
|
||||
const actions = { switchTheme };
|
||||
|
||||
function SideBar() {
|
||||
const { switchTheme } = useActions(actions);
|
||||
return (
|
||||
<div className={s.root}>
|
||||
<div className={s.logo}>
|
||||
|
@ -40,6 +47,10 @@ function SideBar() {
|
|||
<SideBarRow to="/configs" iconId={settings.id} labelText="Config" />
|
||||
<SideBarRow to="/logs" iconId={file.id} labelText="Logs" />
|
||||
</div>
|
||||
|
||||
<div className={s.themeSwitchContainer} onClick={switchTheme}>
|
||||
<Icon id={moon.id} width={20} height={20} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.root {
|
||||
background: #2d2d30;
|
||||
// width: 220px;
|
||||
background: var(--color-bg-sidebar);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
@ -48,3 +48,21 @@
|
|||
.label {
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.themeSwitchContainer {
|
||||
$sz: 50px;
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: $sz;
|
||||
height: $sz;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
17
src/components/Theme.js
Normal file
17
src/components/Theme.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
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);
|
11
src/components/Theme.module.scss
Normal file
11
src/components/Theme.module.scss
Normal file
|
@ -0,0 +1,11 @@
|
|||
.dark {
|
||||
--color-background: #202020;
|
||||
--color-text: #ddd;
|
||||
--color-bg-sidebar: #2d2d30;
|
||||
}
|
||||
|
||||
.light {
|
||||
--color-background: #eee;
|
||||
--color-text: #222;
|
||||
--color-bg-sidebar: #2d2d30;
|
||||
}
|
|
@ -2,6 +2,8 @@ import React, { useEffect } from 'react';
|
|||
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';
|
||||
|
||||
// const delay = ms => new Promise(r => setTimeout(r, ms));
|
||||
const chartJSResource = createResource(() => {
|
||||
|
@ -119,9 +121,11 @@ const chartWrapperStyle = {
|
|||
|
||||
export default function TrafficChart() {
|
||||
const Chart = chartJSResource.read();
|
||||
useEffect(() => {
|
||||
const { hostname, port, secret } = useComponentState(getClashAPIConfig);
|
||||
useEffect(
|
||||
() => {
|
||||
const ctx = document.getElementById('trafficChart').getContext('2d');
|
||||
const traffic = fetchData();
|
||||
const traffic = fetchData({ hostname, port, secret });
|
||||
const data = {
|
||||
labels: traffic.labels,
|
||||
datasets: [
|
||||
|
@ -141,7 +145,9 @@ export default function TrafficChart() {
|
|||
options
|
||||
});
|
||||
return traffic.subscribe(() => c.update());
|
||||
}, []);
|
||||
},
|
||||
[hostname, port, secret]
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={chartWrapperStyle}>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import prettyBytes from 'm/pretty-bytes';
|
||||
|
||||
import { useComponentState } from 'm/store';
|
||||
import { getClashAPIConfig } from 'd/app';
|
||||
import { fetchData } from '../api/traffic';
|
||||
|
||||
import s0 from 'c/TrafficNow.module.scss';
|
||||
|
@ -23,13 +25,21 @@ export default function TrafficNow() {
|
|||
|
||||
function useSpeed() {
|
||||
const [speed, setSpeed] = useState({ upStr: '0 B/s', downStr: '0 B/s' });
|
||||
useEffect(() => {
|
||||
return fetchData().subscribe(o =>
|
||||
const { hostname, port, secret } = useComponentState(getClashAPIConfig);
|
||||
useEffect(
|
||||
() => {
|
||||
return fetchData({
|
||||
hostname,
|
||||
port,
|
||||
secret
|
||||
}).subscribe(o =>
|
||||
setSpeed({
|
||||
upStr: prettyBytes(o.up) + '/s',
|
||||
downStr: prettyBytes(o.down) + '/s'
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
[hostname, port, secret]
|
||||
);
|
||||
return speed;
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ import { fetchConfigs } from 'd/configs';
|
|||
import { closeModal } from 'd/modals';
|
||||
|
||||
const UpdateClashAPIConfig = 'app/UpdateClashAPIConfig';
|
||||
const SwitchTheme = 'app/SwitchTheme';
|
||||
|
||||
const StorageKey = 'yacd.haishan.me';
|
||||
|
||||
export const getClashAPIConfig = s => s.app.clashAPIConfig;
|
||||
export const getTheme = s => s.app.theme;
|
||||
|
||||
// TODO to support secret
|
||||
export function updateClashAPIConfig({ hostname: iHostname, port, secret }) {
|
||||
|
@ -25,6 +27,15 @@ export function updateClashAPIConfig({ hostname: iHostname, port, secret }) {
|
|||
};
|
||||
}
|
||||
|
||||
export function switchTheme() {
|
||||
return (dispatch, getState) => {
|
||||
const currentTheme = getTheme(getState());
|
||||
const theme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
dispatch({ type: SwitchTheme, payload: { theme } });
|
||||
};
|
||||
}
|
||||
|
||||
// type Theme = 'light' | 'dark';
|
||||
const defaultState = {
|
||||
clashAPIConfig: {
|
||||
hostname: '127.0.0.1',
|
||||
|
@ -36,16 +47,20 @@ const defaultState = {
|
|||
function getInitialState() {
|
||||
let s = loadState(StorageKey);
|
||||
if (!s) s = defaultState;
|
||||
return s;
|
||||
// TODO flat clashAPIConfig?
|
||||
return { theme: 'dark', ...s };
|
||||
}
|
||||
const initialState = getInitialState();
|
||||
|
||||
export default function reducer(state = initialState, { type, payload }) {
|
||||
export default function reducer(state = getInitialState(), { type, payload }) {
|
||||
switch (type) {
|
||||
case UpdateClashAPIConfig: {
|
||||
return { ...state, clashAPIConfig: { ...payload } };
|
||||
}
|
||||
|
||||
case SwitchTheme: {
|
||||
return { ...state, ...payload };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as configsAPI from 'a/configs';
|
||||
import { openModal } from 'd/modals';
|
||||
import * as trafficAPI from 'a/traffic';
|
||||
import { openModal } from 'd/modals';
|
||||
import { getClashAPIConfig } from 'd/app';
|
||||
|
||||
const CompletedFetchConfigs = 'configs/CompletedFetchConfigs';
|
||||
const OptimisticUpdateConfigs = 'configs/OptimisticUpdateConfigs';
|
||||
|
@ -12,7 +13,8 @@ export function fetchConfigs() {
|
|||
return async (dispatch, getState) => {
|
||||
let res;
|
||||
try {
|
||||
res = await configsAPI.fetchConfigs();
|
||||
const apiSetup = getClashAPIConfig(getState());
|
||||
res = await configsAPI.fetchConfigs(apiSetup);
|
||||
} catch (err) {
|
||||
// FIXME
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -44,7 +46,8 @@ export function fetchConfigs() {
|
|||
// normally user will land on the "traffic chart" page first
|
||||
// calling this here will let the data start streaming
|
||||
// the traffic chart should already subscribed to the streaming
|
||||
trafficAPI.fetchData();
|
||||
const apiSetup = getClashAPIConfig(getState());
|
||||
trafficAPI.fetchData(apiSetup);
|
||||
} else {
|
||||
dispatch(markHaveFetchedConfig());
|
||||
}
|
||||
|
@ -62,8 +65,9 @@ function markHaveFetchedConfig() {
|
|||
|
||||
export function updateConfigs(partialConfg) {
|
||||
return async (dispatch, getState) => {
|
||||
const apiSetup = getClashAPIConfig(getState());
|
||||
configsAPI
|
||||
.updateConfigs(partialConfg)
|
||||
.updateConfigs(apiSetup, partialConfg)
|
||||
.then(
|
||||
res => {
|
||||
if (res.ok === false) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import * as proxiesAPI from 'a/proxies';
|
||||
import { getClashAPIConfig } from 'd/app';
|
||||
|
||||
// see all types:
|
||||
// https://github.com/Dreamacro/clash/blob/master/constant/adapters.go
|
||||
|
@ -10,7 +11,9 @@ const ProxyGroupTypes = ['Fallback', 'URLTest', 'Selector'];
|
|||
export const getProxies = s => s.proxies.proxies;
|
||||
export const getDelay = s => s.proxies.delay;
|
||||
export const getProxyGroupNames = s => s.proxies.groupNames;
|
||||
export const getUserProxies = createSelector(getProxies, proxies => {
|
||||
export const getUserProxies = createSelector(
|
||||
getProxies,
|
||||
proxies => {
|
||||
let o = {};
|
||||
for (const prop in proxies) {
|
||||
if (ProxyTypeBuiltin.indexOf(prop) < 0) {
|
||||
|
@ -18,7 +21,8 @@ export const getUserProxies = createSelector(getProxies, proxies => {
|
|||
}
|
||||
}
|
||||
return o;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const CompletedFetchProxies = 'proxies/CompletedFetchProxies';
|
||||
const OptimisticSwitchProxy = 'proxies/OptimisticSwitchProxy';
|
||||
|
@ -43,12 +47,14 @@ export function fetchProxies() {
|
|||
return async (dispatch, getState) => {
|
||||
// TODO handle errors
|
||||
|
||||
const proxiesCurr = getProxies(getState());
|
||||
const state = getState();
|
||||
const proxiesCurr = getProxies(state);
|
||||
// TODO this is too aggressive...
|
||||
if (Object.keys(proxiesCurr).length > 0) return;
|
||||
|
||||
const apiConfig = getClashAPIConfig(state);
|
||||
// TODO show loading animation?
|
||||
const json = await proxiesAPI.fetchProxies();
|
||||
const json = await proxiesAPI.fetchProxies(apiConfig);
|
||||
let { proxies = {} } = json;
|
||||
|
||||
const groupNames = retrieveGroupNamesFrom(proxies);
|
||||
|
@ -63,9 +69,10 @@ export function fetchProxies() {
|
|||
|
||||
export function switchProxy(name1, name2) {
|
||||
return async (dispatch, getState) => {
|
||||
const apiConfig = getClashAPIConfig(getState());
|
||||
// TODO display error message
|
||||
proxiesAPI
|
||||
.requestToSwitchProxy(name1, name2)
|
||||
.requestToSwitchProxy(apiConfig, name1, name2)
|
||||
.then(
|
||||
res => {
|
||||
if (res.ok === false) {
|
||||
|
@ -97,7 +104,8 @@ export function switchProxy(name1, name2) {
|
|||
|
||||
function requestDelayForProxyOnce(name) {
|
||||
return async (dispatch, getState) => {
|
||||
const res = await proxiesAPI.requestDelayForProxy(name);
|
||||
const apiConfig = getClashAPIConfig(getState());
|
||||
const res = await proxiesAPI.requestDelayForProxy(apiConfig, name);
|
||||
let error = '';
|
||||
if (res.ok === false) {
|
||||
error = res.statusText;
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
import { store } from '../store/configureStore';
|
||||
import { getClashAPIConfig } from 'd/app';
|
||||
|
||||
const headersCommon = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
export function getAPIConfig() {
|
||||
// this is cheating...
|
||||
return getClashAPIConfig(store.getState());
|
||||
}
|
||||
|
||||
export function genCommonHeaders({ secret }) {
|
||||
const h = { ...headersCommon };
|
||||
if (secret) {
|
||||
|
@ -21,3 +13,12 @@ export function genCommonHeaders({ secret }) {
|
|||
export function getAPIBaseURL({ hostname, port }) {
|
||||
return `http://${hostname}:${port}`;
|
||||
}
|
||||
|
||||
export function getURLAndInit({ hostname, port, secret }) {
|
||||
const baseURL = getAPIBaseURL({ hostname, port });
|
||||
const headers = genCommonHeaders({ secret });
|
||||
return {
|
||||
url: baseURL,
|
||||
init: { headers }
|
||||
};
|
||||
}
|
||||
|
|
1
src/svg/moon.svg
Normal file
1
src/svg/moon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
After Width: | Height: | Size: 253 B |
Loading…
Reference in a new issue