Render latency change immediately

This commit is contained in:
Haishan 2023-02-12 18:43:26 +08:00
parent e0bc53d5e0
commit 759d742b8b
5 changed files with 84 additions and 47 deletions

View file

@ -2,7 +2,7 @@ import { TooltipPopup, useTooltip } from '@reach/tooltip';
import cx from 'clsx'; import cx from 'clsx';
import * as React from 'react'; import * as React from 'react';
import { State } from '$src/store/types'; import { ProxyDelayItem, State } from '$src/store/types';
import { getDelay, getProxies, NonProxyTypes } from '../../store/proxies'; import { getDelay, getProxies, NonProxyTypes } from '../../store/proxies';
import { connect } from '../StateProvider'; import { connect } from '../StateProvider';
@ -22,7 +22,9 @@ const colorMap = {
na: '#909399', na: '#909399',
}; };
function getLabelColor({ number }: { number?: number } = {}) { function getLabelColor(latency: ProxyDelayItem) {
if (!latency || latency.kind !== 'Result') return colorMap.na;
const number = latency.number;
if (number === 0) { if (number === 0) {
return colorMap.na; return colorMap.na;
} else if (number < 200) { } else if (number < 200) {
@ -35,7 +37,7 @@ function getLabelColor({ number }: { number?: number } = {}) {
return colorMap.na; return colorMap.na;
} }
function getProxyDotStyle(latency: { number?: number }, proxyType: string) { function getProxyDotStyle(latency: ProxyDelayItem, proxyType: string) {
if (NonProxyTypes.indexOf(proxyType) > -1) { if (NonProxyTypes.indexOf(proxyType) > -1) {
return { border: '1px dotted #777' }; return { border: '1px dotted #777' };
} }
@ -47,7 +49,7 @@ type ProxyProps = {
name: string; name: string;
now?: boolean; now?: boolean;
proxy: any; proxy: any;
latency?: { number?: number }; latency?: ProxyDelayItem;
isSelectable?: boolean; isSelectable?: boolean;
onClick?: (proxyName: string) => unknown; onClick?: (proxyName: string) => unknown;
}; };
@ -56,7 +58,7 @@ function ProxySmallImpl({ now, name, proxy, latency, isSelectable, onClick }: Pr
const style = useMemo(() => getProxyDotStyle(latency, proxy.type), [latency, proxy]); const style = useMemo(() => getProxyDotStyle(latency, proxy.type), [latency, proxy]);
const title = useMemo(() => { const title = useMemo(() => {
let ret = name; let ret = name;
if (latency && typeof latency.number === 'number') { if (latency && latency.kind === 'Result' && typeof latency.number === 'number') {
ret += ' ' + latency.number + ' ms'; ret += ' ' + latency.number + ' ms';
} }
return ret; return ret;
@ -152,7 +154,7 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr
<span className={s0.proxyType} style={{ opacity: now ? 0.6 : 0.2 }}> <span className={s0.proxyType} style={{ opacity: now ? 0.6 : 0.2 }}>
{formatProxyType(proxy.type)} {formatProxyType(proxy.type)}
</span> </span>
<ProxyLatency number={latency?.number} color={color} /> <ProxyLatency latency={latency} color={color} />
</div> </div>
</div> </div>
); );

View file

@ -1,16 +1,32 @@
import * as React from 'react'; import * as React from 'react';
import { ProxyDelayItem } from '$src/store/types';
import s0 from './ProxyLatency.module.scss'; import s0 from './ProxyLatency.module.scss';
type ProxyLatencyProps = { type ProxyLatencyProps = {
number?: number; latency: ProxyDelayItem | undefined;
color: string; color: string;
}; };
export function ProxyLatency({ number, color }: ProxyLatencyProps) { export function ProxyLatency({ latency, color }: ProxyLatencyProps) {
let text = ' ';
if (latency) {
switch (latency.kind) {
case 'Error':
case 'Testing':
text = '- ms';
break;
case 'Result':
text = (latency.number !== 0 ? latency.number : '-') + ' ms';
break;
default:
break;
}
}
return ( return (
<span className={s0.proxyLatency} style={{ color }}> <span className={s0.proxyLatency} style={{ color }}>
{typeof number === 'number' && number !== 0 ? number + ' ms' : ' '} {text}
</span> </span>
); );
} }

View file

@ -5,11 +5,7 @@ import { initReactI18next } from 'react-i18next';
const LngBackend = { const LngBackend = {
type: 'backend' as const, type: 'backend' as const,
read: ( read: (lng: string, _namespace: string, callback: ReadCallback) => {
lng: string,
_namespace: string,
callback: ReadCallback,
) => {
let p: PromiseLike<{ data: any }>; let p: PromiseLike<{ data: any }>;
switch (lng) { switch (lng) {
case 'zh': case 'zh':
@ -22,12 +18,15 @@ const LngBackend = {
break; break;
} }
if (p) { if (p) {
p.then(d => callback(null, d.data), err => callback(err, null)); p.then(
(d) => callback(null, d.data),
(err) => callback(err, null)
);
} else { } else {
callback(new Error(`unable to load translation file for language ${lng}`), null) callback(new Error(`unable to load translation file for language ${lng}`), null);
} }
} },
} };
i18next i18next
.use(initReactI18next) .use(initReactI18next)

View file

@ -59,7 +59,7 @@ function mapLatency(names: string[], getProxy: (name: string) => { history: Late
const history = p.history; const history = p.history;
const h = history[history.length - 1]; const h = history[history.length - 1];
if (h && typeof h.delay === 'number') { if (h && typeof h.delay === 'number') {
result[name] = { number: h.delay }; result[name] = { kind: 'Result', number: h.delay };
} }
} }
return result; return result;
@ -268,18 +268,35 @@ export function switchProxy(apiConfig: ClashAPIConfig, groupName: string, itemNa
function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) { function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) {
return async (dispatch: DispatchFn, getState: GetStateFn) => { return async (dispatch: DispatchFn, getState: GetStateFn) => {
const latencyTestUrl = getLatencyTestUrl(getState()); dispatch('set latency state to testing in progress', (s) => {
const res = await proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl); s.proxies.delay = { ...getDelay(getState()), [name]: { kind: 'Testing' } };
let error = '';
if (res.ok === false) {
error = res.statusText;
}
const { delay } = await res.json();
const delayNext = { ...getDelay(getState()), [name]: { error, number: delay } };
dispatch('requestDelayForProxyOnce', (s) => {
s.proxies.delay = delayNext;
}); });
const latencyTestUrl = getLatencyTestUrl(getState());
try {
const res = await proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl);
if (res.ok) {
const { delay } = await res.json();
dispatch('set latency result', (s) => {
s.proxies.delay = { ...getDelay(getState()), [name]: { kind: 'Result', number: delay } };
});
} else {
dispatch('set latency testing error', (s) => {
s.proxies.delay = {
...getDelay(getState()),
[name]: { kind: 'Error', message: res.statusText },
};
});
}
} catch (err) {
dispatch('set latency testing networkish error', (s) => {
s.proxies.delay = {
...getDelay(getState()),
[name]: { kind: 'Error', message: err.message || err.type },
};
});
}
}; };
} }
@ -292,30 +309,31 @@ export function requestDelayForProxy(apiConfig: ClashAPIConfig, name: string) {
export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) { export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) {
return async (dispatch: DispatchFn, getState: GetStateFn) => { return async (dispatch: DispatchFn, getState: GetStateFn) => {
const proxies = getProxies(getState()); const proxies = getProxies(getState());
const latencyTestUrl = getLatencyTestUrl(getState());
const proxyDedupMap = new Map<string, boolean>(); const proxyDedupMap = new Map<string, boolean>();
const providerDedupMap = new Map<string, boolean>(); const providerDedupMap = new Map<string, boolean>();
const works = names.map((name) => { const works: Array<Promise<void>> = [];
names.forEach((name) => {
const p = proxies[name]; const p = proxies[name];
if (!p.__provider) { if (!p.__provider) {
if (proxyDedupMap.get(name)) { if (!proxyDedupMap.get(name)) {
return undefined;
} else {
proxyDedupMap.set(name, true); proxyDedupMap.set(name, true);
return proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl); dispatch(requestDelayForProxyOnce(apiConfig, name));
} }
} else if (p.__provider) { } else if (p.__provider) {
// this one is from a proxy provider if (!proxyDedupMap.get(name)) {
if (providerDedupMap.get(p.__provider)) { proxyDedupMap.set(name, true);
return undefined; dispatch('set latency state to testing in progress', (s) => {
} else { s.proxies.delay = { ...getDelay(getState()), [name]: { kind: 'Testing' } };
providerDedupMap.set(p.__provider, true); });
return healthcheckProviderByNameInternal(apiConfig, p.__provider); }
// this one is from a proxy provider
if (!providerDedupMap.get(p.__provider)) {
providerDedupMap.set(p.__provider, true);
works.push(healthcheckProviderByNameInternal(apiConfig, p.__provider));
} }
} else {
return undefined;
} }
}); });
await Promise.all(works); await Promise.all(works);

View file

@ -40,9 +40,11 @@ export type ProxyItem = {
__provider?: string; __provider?: string;
}; };
export type ProxyDelayItem = { export type ProxyDelayItem =
number?: number; | { kind: 'Result'; number: number }
}; | { kind: 'Testing' }
| { kind: 'Error'; message: string }
| { kind: 'None' };
export type ProxiesMapping = Record<string, ProxyItem>; export type ProxiesMapping = Record<string, ProxyItem>;
export type DelayMapping = Record<string, ProxyDelayItem>; export type DelayMapping = Record<string, ProxyDelayItem>;