added: test latency button for each proxy group

This commit is contained in:
Haishan 2020-06-03 22:47:26 +08:00
parent 07082c5b09
commit bf17be6a65
18 changed files with 216 additions and 177 deletions

View file

@ -8,7 +8,7 @@ import s0 from './Button.module.css';
const { memo, forwardRef, useCallback } = React;
type ButtonInternalProps = {
children?: React.ReactChildren;
children?: React.ReactNode;
label?: string;
text?: string;
start?: React.ReactElement | (() => React.ReactElement);

View file

@ -19,7 +19,7 @@ const Proxies = React.lazy(() =>
/* webpackChunkName: "proxies" */
/* webpackPrefetch: true */
/* webpackPreload: true */
'./Proxies'
'./proxies/Proxies'
)
);
const Rules = React.lazy(() =>
@ -38,7 +38,7 @@ const routes = [
['logs', '/logs', <Logs />],
['proxies', '/proxies', <Proxies />],
['rules', '/rules', <Rules />],
__DEV__ ? ['style', '/style', <StyleGuide />] : false
__DEV__ ? ['style', '/style', <StyleGuide />] : false,
].filter(Boolean);
const Root = () => (

View file

@ -1,17 +1,17 @@
import React from 'react';
import * as React from 'react';
import { connect } from './StateProvider';
import { connect } from '../StateProvider';
import Button from './Button';
import ContentHeader from './ContentHeader';
import ProxyGroup from './ProxyGroup';
import BaseModal from './shared/BaseModal';
import Settings from './proxies/Settings';
import Equalizer from './svg/Equalizer';
import Button from '../Button';
import ContentHeader from '../ContentHeader';
import { ProxyGroup } from './ProxyGroup';
import BaseModal from '../shared/BaseModal';
import Settings from './Settings';
import Equalizer from '../svg/Equalizer';
import { Zap } from 'react-feather';
import ProxyProviderList from './ProxyProviderList';
import { Fab, position as fabPosition } from './shared/Fab';
import { ProxyProviderList } from './ProxyProviderList';
import { Fab, position as fabPosition } from '../shared/Fab';
import s0 from './Proxies.module.css';
@ -21,13 +21,15 @@ import {
getProxyProviders,
fetchProxies,
requestDelayAll,
} from '../store/proxies';
import { getClashAPIConfig } from '../store/app';
} from '../../store/proxies';
import { getClashAPIConfig } from '../../store/app';
const { useState, useEffect, useCallback, useRef } = React;
function Proxies({ dispatch, groupNames, delay, proxyProviders, apiConfig }) {
const refFetchedTimestamp = useRef({});
const refFetchedTimestamp = useRef<{ startAt?: number; completeAt?: number }>(
{}
);
const [isTestingLatency, setIsTestingLatency] = useState(false);
const requestDelayAllFn = useCallback(() => {
if (isTestingLatency) return;
@ -40,9 +42,9 @@ function Proxies({ dispatch, groupNames, delay, proxyProviders, apiConfig }) {
}, [apiConfig, dispatch, isTestingLatency]);
const fetchProxiesHooked = useCallback(() => {
refFetchedTimestamp.current.startAt = new Date();
refFetchedTimestamp.current.startAt = Date.now();
dispatch(fetchProxies(apiConfig)).then(() => {
refFetchedTimestamp.current.completeAt = new Date();
refFetchedTimestamp.current.completeAt = Date.now();
});
}, [apiConfig, dispatch]);
useEffect(() => {
@ -53,7 +55,7 @@ function Proxies({ dispatch, groupNames, delay, proxyProviders, apiConfig }) {
const fn = () => {
if (
refFetchedTimestamp.current.startAt &&
new Date() - refFetchedTimestamp.current.startAt > 3e4 // 30s
Date.now() - refFetchedTimestamp.current.startAt > 3e4 // 30s
) {
fetchProxiesHooked();
}

View file

@ -1,10 +1,10 @@
import React from 'react';
import * as React from 'react';
import cx from 'clsx';
import { connect } from './StateProvider';
import ProxyLatency from './ProxyLatency';
import { getProxies, getDelay } from '../store/proxies';
import { connect } from '../StateProvider';
import { ProxyLatency } from './ProxyLatency';
import { getProxies, getDelay } from '../../store/proxies';
import s0 from './Proxy.module.css';
@ -21,7 +21,11 @@ const colorMap = {
na: '#909399',
};
function getLabelColor({ number } = {}) {
function getLabelColor({
number,
}: {
number?: number;
} = {}) {
if (number < 200) {
return colorMap.good;
} else if (number < 400) {
@ -32,27 +36,11 @@ function getLabelColor({ number } = {}) {
return colorMap.na;
}
/*
const colors = {
Direct: '#408b43',
Fallback: '#3483e8',
Selector: '#387cec',
Vmess: '#ca3487',
Shadowsocks: '#1a7dc0',
Socks5: '#2a477a',
URLTest: '#3483e8',
Http: '#d3782d'
};
*/
type ProxyProps = {
name: string,
now?: boolean,
// connect injected
// TODO refine type
proxy: any,
latency: any,
name: string;
now?: boolean;
proxy: any;
latency: any;
};
function ProxySmallImpl({ now, name, latency }: ProxyProps) {
@ -73,7 +61,7 @@ function ProxySmallImpl({ now, name, latency }: ProxyProps) {
);
}
function Proxy({ now, name, proxy, latency }: ProxyProps) {
function ProxyImpl({ now, name, proxy, latency }: ProxyProps) {
const color = useMemo(() => getLabelColor(latency), [latency]);
return (
<div
@ -95,7 +83,7 @@ function Proxy({ now, name, proxy, latency }: ProxyProps) {
);
}
const mapState = (s, { name }) => {
const mapState = (s: any, { name }) => {
const proxies = getProxies(s);
const delay = getDelay(s);
return {
@ -104,5 +92,5 @@ const mapState = (s, { name }) => {
};
};
export default connect(mapState)(Proxy);
export const Proxy = connect(mapState)(ProxyImpl);
export const ProxySmall = connect(mapState)(ProxySmallImpl);

View file

@ -0,0 +1,11 @@
.header {
margin-bottom: 12px;
}
.zapWrapper {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}

View file

@ -1,28 +1,37 @@
import React from 'react';
import cx from 'clsx';
import * as React from 'react';
import memoizeOne from 'memoize-one';
import { Zap } from 'react-feather';
import { connect, useStoreActions } from './StateProvider';
import { getProxies } from '../store/proxies';
import { connect, useStoreActions } from '../StateProvider';
import { getProxies } from '../../store/proxies';
import {
getCollapsibleIsOpen,
getProxySortBy,
getHideUnavailableProxies,
} from '../store/app';
import CollapsibleSectionHeader from './CollapsibleSectionHeader';
import Proxy, { ProxySmall } from './Proxy';
} from '../../store/app';
import { switchProxy } from '../../store/proxies';
import CollapsibleSectionHeader from '../CollapsibleSectionHeader';
import Button from '../Button';
import { ProxyList, ProxyListSummaryView } from './ProxyList';
import s0 from './ProxyGroup.module.css';
import { switchProxy } from '../store/proxies';
const { useCallback, useMemo, useState } = React;
const { useCallback, useMemo } = React;
function ZapWrapper() {
return (
<div className={s0.zapWrapper}>
<Zap size={16} />
</div>
);
}
function ProxyGroup({ name, all, type, now, isOpen, apiConfig, dispatch }) {
function ProxyGroupImpl({ name, all, type, now, isOpen, apiConfig, dispatch }) {
const isSelectable = useMemo(() => type === 'Selector', [type]);
const {
app: { updateCollapsibleIsOpen },
proxies: { requestDelayForProxies },
} = useStoreActions();
const toggle = useCallback(() => {
@ -37,15 +46,33 @@ function ProxyGroup({ name, all, type, now, isOpen, apiConfig, dispatch }) {
[apiConfig, dispatch, name, isSelectable]
);
const [isTestingLatency, setIsTestingLatency] = useState(false);
const testLatency = useCallback(async () => {
setIsTestingLatency(true);
try {
await requestDelayForProxies(apiConfig, all);
} catch (err) {}
setIsTestingLatency(false);
}, [all, apiConfig, requestDelayForProxies]);
return (
<div className={s0.group}>
<CollapsibleSectionHeader
name={name}
type={type}
toggle={toggle}
qty={all.length}
isOpen={isOpen}
/>
<div style={{ display: 'flex', alignItems: 'center' }}>
<CollapsibleSectionHeader
name={name}
type={type}
toggle={toggle}
qty={all.length}
isOpen={isOpen}
/>
<Button
kind="minimal"
onClick={testLatency}
isLoading={isTestingLatency}
>
<ZapWrapper />
</Button>
</div>
{isOpen ? (
<ProxyList
all={all}
@ -60,45 +87,6 @@ function ProxyGroup({ name, all, type, now, isOpen, apiConfig, dispatch }) {
);
}
type ProxyListProps = {
all: string[],
now?: string,
isSelectable?: boolean,
itemOnTapCallback?: (string) => void,
show?: boolean,
};
export function ProxyList({
all,
now,
isSelectable,
itemOnTapCallback,
sortedAll,
}: ProxyListProps) {
const proxies = sortedAll || all;
return (
<div className={s0.list}>
{proxies.map((proxyName) => {
const proxyClassName = cx(s0.proxy, {
[s0.proxySelectable]: isSelectable,
});
return (
<div
className={proxyClassName}
key={proxyName}
onClick={() => {
if (!isSelectable || !itemOnTapCallback) return;
itemOnTapCallback(proxyName);
}}
>
<Proxy name={proxyName} now={proxyName === now} />
</div>
);
})}
</div>
);
}
const getSortDelay = (d, w) => {
if (d === undefined) {
return 0;
@ -172,36 +160,7 @@ export const filterAvailableProxiesAndSort = memoizeOne(
filterAvailableProxiesAndSortImpl
);
export function ProxyListSummaryView({
all,
now,
isSelectable,
itemOnTapCallback,
}: ProxyListProps) {
return (
<div className={s0.list}>
{all.map((proxyName) => {
const proxyClassName = cx(s0.proxy, {
[s0.proxySelectable]: isSelectable,
});
return (
<div
className={proxyClassName}
key={proxyName}
onClick={() => {
if (!isSelectable || !itemOnTapCallback) return;
itemOnTapCallback(proxyName);
}}
>
<ProxySmall name={proxyName} now={proxyName === now} />
</div>
);
})}
</div>
);
}
export default connect((s, { name, delay }) => {
export const ProxyGroup = connect((s, { name, delay }) => {
const proxies = getProxies(s);
const collapsibleIsOpen = getCollapsibleIsOpen(s);
const proxySortBy = getProxySortBy(s);
@ -220,4 +179,4 @@ export default connect((s, { name, delay }) => {
now,
isOpen: collapsibleIsOpen[`proxyGroup:${name}`],
};
})(ProxyGroup);
})(ProxyGroupImpl);

View file

@ -1,13 +1,13 @@
import React from 'react';
import * as React from 'react';
import s0 from './ProxyLatency.module.css';
type ProxyLatencyProps = {
number: number,
color: string
number: number;
color: string;
};
export default function ProxyLatency({ number, color }: ProxyLatencyProps) {
export function ProxyLatency({ number, color }: ProxyLatencyProps) {
return (
<span className={s0.proxyLatency} style={{ color }}>
<span>{number} ms</span>

View file

@ -1,7 +1,3 @@
.header {
margin-bottom: 12px;
}
.list {
display: flex;
flex-wrap: wrap;

View file

@ -0,0 +1,75 @@
import * as React from 'react';
import cx from 'clsx';
import { Proxy, ProxySmall } from './Proxy';
import s from './ProxyList.module.css';
type ProxyListProps = {
all: string[];
now?: string;
isSelectable?: boolean;
itemOnTapCallback?: (x: string) => void;
show?: boolean;
};
export function ProxyList({
all,
now,
isSelectable,
itemOnTapCallback,
}: ProxyListProps) {
const proxies = all;
return (
<div className={s.list}>
{proxies.map((proxyName) => {
const proxyClassName = cx(s.proxy, {
[s.proxySelectable]: isSelectable,
});
return (
<div
className={proxyClassName}
key={proxyName}
onClick={() => {
if (!isSelectable || !itemOnTapCallback) return;
itemOnTapCallback(proxyName);
}}
>
<Proxy name={proxyName} now={proxyName === now} />
</div>
);
})}
</div>
);
}
export function ProxyListSummaryView({
all,
now,
isSelectable,
itemOnTapCallback,
}: ProxyListProps) {
return (
<div className={s.list}>
{all.map((proxyName) => {
const proxyClassName = cx(s.proxy, {
[s.proxySelectable]: isSelectable,
});
return (
<div
className={proxyClassName}
key={proxyName}
onClick={() => {
if (!isSelectable || !itemOnTapCallback) return;
itemOnTapCallback(proxyName);
}}
>
<ProxySmall name={proxyName} now={proxyName === now} />
</div>
);
})}
</div>
);
}

View file

@ -1,45 +1,43 @@
import React from 'react';
import * as React from 'react';
import { RotateCw, Zap } from 'react-feather';
import { formatDistance } from 'date-fns';
import { motion } from 'framer-motion';
import { connect, useStoreActions } from './StateProvider';
import Collapsible from './Collapsible';
import CollapsibleSectionHeader from './CollapsibleSectionHeader';
import {
ProxyList,
ProxyListSummaryView,
filterAvailableProxiesAndSort,
} from './ProxyGroup';
import Button from './Button';
import { connect, useStoreActions } from '../StateProvider';
import Collapsible from '../Collapsible';
import CollapsibleSectionHeader from '../CollapsibleSectionHeader';
import { filterAvailableProxiesAndSort } from './ProxyGroup';
import { ProxyList, ProxyListSummaryView } from './ProxyList';
import Button from '../Button';
import {
getClashAPIConfig,
getCollapsibleIsOpen,
getProxySortBy,
getHideUnavailableProxies,
} from '../store/app';
} from '../../store/app';
import {
getDelay,
updateProviderByName,
healthcheckProviderByName,
} from '../store/proxies';
} from '../../store/proxies';
import s from './ProxyProvider.module.css';
const { useState, useCallback } = React;
type Props = {
name: string,
proxies: Array<string>,
type: 'Proxy' | 'Rule',
vehicleType: 'HTTP' | 'File' | 'Compatible',
updatedAt?: string,
dispatch: (any) => void,
isOpen: boolean,
name: string;
proxies: Array<string>;
type: 'Proxy' | 'Rule';
vehicleType: 'HTTP' | 'File' | 'Compatible';
updatedAt?: string;
dispatch: (x: any) => Promise<any>;
isOpen: boolean;
apiConfig: any;
};
function ProxyProvider({
function ProxyProviderImpl({
name,
proxies,
vehicleType,
@ -63,9 +61,6 @@ function ProxyProvider({
app: { updateCollapsibleIsOpen },
} = useStoreActions();
// const [isCollapsibleOpen, setCollapsibleOpen] = useState(false);
// const toggle = useCallback(() => setCollapsibleOpen(x => !x), []);
const toggle = useCallback(() => {
updateCollapsibleIsOpen('proxyProvider', name, !isOpen);
}, [isOpen, updateCollapsibleIsOpen, name]);
@ -104,7 +99,6 @@ function ProxyProvider({
const button = {
rest: { scale: 1 },
// hover: { scale: 1.1 },
pressed: { scale: 0.95 },
};
const arrow = {
@ -147,4 +141,4 @@ const mapState = (s, { proxies, name }) => {
};
};
export default connect(mapState)(ProxyProvider);
export const ProxyProvider = connect(mapState)(ProxyProviderImpl);

View file

@ -1,16 +1,16 @@
import React from 'react';
import * as React from 'react';
import ContentHeader from './ContentHeader';
import ProxyProvider from './ProxyProvider';
import ContentHeader from '../ContentHeader';
import { ProxyProvider } from './ProxyProvider';
function ProxyProviderList({ items }) {
export function ProxyProviderList({ items }) {
if (items.length === 0) return null;
return (
<>
<ContentHeader title="Proxy Provider" />
<div>
{items.map(item => (
{items.map((item) => (
<ProxyProvider
key={item.name}
name={item.name}
@ -24,5 +24,3 @@ function ProxyProviderList({ items }) {
</>
);
}
export default ProxyProviderList;

View file

@ -0,0 +1 @@
export { ProxyList } from './ProxyList';

View file

@ -4,7 +4,7 @@ import {
updateAppConfig,
updateCollapsibleIsOpen,
} from './app';
import { initialState as proxies } from './proxies';
import { initialState as proxies, actions as proxiesActions } from './proxies';
import { initialState as modals } from './modals';
import { initialState as configs } from './configs';
import { initialState as rules } from './rules';
@ -27,4 +27,5 @@ export const actions = {
updateCollapsibleIsOpen,
updateAppConfig,
},
proxies: proxiesActions,
};

View file

@ -20,7 +20,6 @@ type ProxyProvider = {
// const ProxyTypeBuiltin = ['DIRECT', 'GLOBAL', 'REJECT'];
// const ProxyGroupTypes = ['Fallback', 'URLTest', 'Selector', 'LoadBalance'];
// const ProxyTypes = ['Shadowsocks', 'Snell', 'Socks5', 'Http', 'Vmess'];
const NonProxyTypes = [
@ -180,6 +179,19 @@ export function requestDelayForProxy(apiConfig, name) {
};
}
export function requestDelayForProxies(apiConfig, names) {
return async (dispatch, getState) => {
const proxyNames = getDangleProxyNames(getState());
const works = names
// remove names that are provided by proxy providers
.filter((p) => proxyNames.indexOf(p) > -1)
.map((p) => dispatch(requestDelayForProxy(apiConfig, p)));
await Promise.all(works);
await dispatch(fetchProxies(apiConfig));
};
}
export function requestDelayAll(apiConfig) {
return async (dispatch, getState) => {
const proxyNames = getDangleProxyNames(getState());
@ -251,3 +263,5 @@ export const initialState = {
delay: {},
groupNames: [],
};
export const actions = { requestDelayForProxies };