feat: add FAB action button to update all proxy providers

This commit is contained in:
Haishan 2021-02-28 17:06:00 +08:00
parent 27a6604340
commit ec4586ef3c
12 changed files with 193 additions and 63 deletions

View file

@ -1,29 +1,28 @@
import Tooltip from '@reach/tooltip'; import Tooltip from '@reach/tooltip';
import * as React from 'react'; import * as React from 'react';
import { Zap } from 'react-feather';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Button from 'src/components/Button';
import { getClashAPIConfig } from '../../store/app'; import ContentHeader from 'src/components/ContentHeader';
import { ClosePrevConns } from 'src/components/proxies/ClosePrevConns';
import { ProxyGroup } from 'src/components/proxies/ProxyGroup';
import { ProxyPageFab } from 'src/components/proxies/ProxyPageFab';
import { ProxyProviderList } from 'src/components/proxies/ProxyProviderList';
import Settings from 'src/components/proxies/Settings';
import { TextFilter } from 'src/components/proxies/TextFilter';
import BaseModal from 'src/components/shared/BaseModal';
import { connect, useStoreActions } from 'src/components/StateProvider';
import Equalizer from 'src/components/svg/Equalizer';
import { getClashAPIConfig } from 'src/store/app';
import { import {
fetchProxies, fetchProxies,
getDelay, getDelay,
getProxyGroupNames, getProxyGroupNames,
getProxyProviders, getProxyProviders,
getShowModalClosePrevConns, getShowModalClosePrevConns,
requestDelayAll, } from 'src/store/proxies';
} from '../../store/proxies'; import type { State } from 'src/store/types';
import Button from '../Button';
import ContentHeader from '../ContentHeader';
import BaseModal from '../shared/BaseModal';
import { Fab, IsFetching, position as fabPosition } from '../shared/Fab';
import { connect, useStoreActions } from '../StateProvider';
import Equalizer from '../svg/Equalizer';
import { ClosePrevConns } from './ClosePrevConns';
import s0 from './Proxies.module.css'; import s0 from './Proxies.module.css';
import { ProxyGroup } from './ProxyGroup';
import { ProxyProviderList } from './ProxyProviderList';
import Settings from './Settings';
import { TextFilter } from './TextFilter';
const { useState, useEffect, useCallback, useRef } = React; const { useState, useEffect, useCallback, useRef } = React;
@ -38,16 +37,6 @@ function Proxies({
const refFetchedTimestamp = useRef<{ startAt?: number; completeAt?: number }>( const refFetchedTimestamp = useRef<{ startAt?: number; completeAt?: number }>(
{} {}
); );
const [isTestingLatency, setIsTestingLatency] = useState(false);
const requestDelayAllFn = useCallback(() => {
if (isTestingLatency) return;
setIsTestingLatency(true);
dispatch(requestDelayAll(apiConfig)).then(
() => setIsTestingLatency(false),
() => setIsTestingLatency(false)
);
}, [apiConfig, dispatch, isTestingLatency]);
const fetchProxiesHooked = useCallback(() => { const fetchProxiesHooked = useCallback(() => {
refFetchedTimestamp.current.startAt = Date.now(); refFetchedTimestamp.current.startAt = Date.now();
@ -120,19 +109,10 @@ function Proxies({
</div> </div>
<ProxyProviderList items={proxyProviders} /> <ProxyProviderList items={proxyProviders} />
<div style={{ height: 60 }} /> <div style={{ height: 60 }} />
<Fab <ProxyPageFab
icon={ dispatch={dispatch}
isTestingLatency ? ( apiConfig={apiConfig}
<IsFetching> proxyProviders={proxyProviders}
<Zap width={16} height={16} />
</IsFetching>
) : (
<Zap width={16} height={16} />
)
}
onClick={requestDelayAllFn}
text={t('Test Latency')}
style={fabPosition}
/> />
<BaseModal <BaseModal
isOpen={showModalClosePrevConns} isOpen={showModalClosePrevConns}
@ -147,7 +127,7 @@ function Proxies({
); );
} }
const mapState = (s) => ({ const mapState = (s: State) => ({
apiConfig: getClashAPIConfig(s), apiConfig: getClashAPIConfig(s),
groupNames: getProxyGroupNames(s), groupNames: getProxyGroupNames(s),
proxyProviders: getProxyProviders(s), proxyProviders: getProxyProviders(s),

View file

@ -0,0 +1,83 @@
import * as React from 'react';
import { Zap } from 'react-feather';
import { useTranslation } from 'react-i18next';
import { useUpdateProviderItems } from 'src/components/proxies/proxies.hooks';
import {
Action,
Fab,
IsFetching,
position as fabPosition,
} from 'src/components/shared/Fab';
import { RotateIcon } from 'src/components/shared/RotateIcon';
import { requestDelayAll } from 'src/store/proxies';
import { DispatchFn, FormattedProxyProvider } from 'src/store/types';
import { ClashAPIConfig } from 'src/types';
const { useState, useCallback } = React;
function StatefulZap({ isLoading }: { isLoading: boolean }) {
return isLoading ? (
<IsFetching>
<Zap width={16} height={16} />
</IsFetching>
) : (
<Zap width={16} height={16} />
);
}
function useTestLatencyAction({
dispatch,
apiConfig,
}: {
dispatch: DispatchFn;
apiConfig: ClashAPIConfig;
}): [() => unknown, boolean] {
const [isTestingLatency, setIsTestingLatency] = useState(false);
const requestDelayAllFn = useCallback(() => {
if (isTestingLatency) return;
setIsTestingLatency(true);
dispatch(requestDelayAll(apiConfig)).then(
() => setIsTestingLatency(false),
() => setIsTestingLatency(false)
);
}, [apiConfig, dispatch, isTestingLatency]);
return [requestDelayAllFn, isTestingLatency];
}
export function ProxyPageFab({
dispatch,
apiConfig,
proxyProviders,
}: {
dispatch: DispatchFn;
apiConfig: ClashAPIConfig;
proxyProviders: FormattedProxyProvider[];
}) {
const { t } = useTranslation();
const [requestDelayAllFn, isTestingLatency] = useTestLatencyAction({
dispatch,
apiConfig,
});
const [updateProviders, isUpdating] = useUpdateProviderItems({
apiConfig,
dispatch,
names: proxyProviders.map((item) => item.name),
});
return (
<Fab
icon={<StatefulZap isLoading={isTestingLatency} />}
onClick={requestDelayAllFn}
text={t('Test Latency')}
style={fabPosition}
>
{proxyProviders.length > 0 ? (
<Action text={t('update_all_proxy_provider')} onClick={updateProviders}>
<RotateIcon isRotating={isUpdating} />
</Action>
) : null}
</Fab>
);
}

View file

@ -1,24 +1,21 @@
import { formatDistance } from 'date-fns'; import { formatDistance } from 'date-fns';
import * as React from 'react'; import * as React from 'react';
import { RotateCw, Zap } from 'react-feather'; import { RotateCw, Zap } from 'react-feather';
import { DelayMapping } from 'src/store/types'; import Button from 'src/components/Button';
import Collapsible from 'src/components/Collapsible';
import { framerMotionResouce } from '../../misc/motion'; import CollapsibleSectionHeader from 'src/components/CollapsibleSectionHeader';
import { useUpdateProviderItem } from 'src/components/proxies/proxies.hooks';
import { connect, useStoreActions } from 'src/components/StateProvider';
import { framerMotionResouce } from 'src/misc/motion';
import { import {
getClashAPIConfig, getClashAPIConfig,
getCollapsibleIsOpen, getCollapsibleIsOpen,
getHideUnavailableProxies, getHideUnavailableProxies,
getProxySortBy, getProxySortBy,
} from '../../store/app'; } from 'src/store/app';
import { import { getDelay, healthcheckProviderByName } from 'src/store/proxies';
getDelay, import { DelayMapping } from 'src/store/types';
healthcheckProviderByName,
updateProviderByName,
} from '../../store/proxies';
import Button from '../Button';
import Collapsible from '../Collapsible';
import CollapsibleSectionHeader from '../CollapsibleSectionHeader';
import { connect, useStoreActions } from '../StateProvider';
import { useFilteredAndSorted } from './hooks'; import { useFilteredAndSorted } from './hooks';
import { ProxyList, ProxyListSummaryView } from './ProxyList'; import { ProxyList, ProxyListSummaryView } from './ProxyList';
import s from './ProxyProvider.module.css'; import s from './ProxyProvider.module.css';
@ -58,10 +55,9 @@ function ProxyProviderImpl({
proxySortBy proxySortBy
); );
const [isHealthcheckLoading, setIsHealthcheckLoading] = useState(false); const [isHealthcheckLoading, setIsHealthcheckLoading] = useState(false);
const updateProvider = useCallback(
() => dispatch(updateProviderByName(apiConfig, name)), const updateProvider = useUpdateProviderItem({ dispatch, apiConfig, name });
[apiConfig, dispatch, name]
);
const healthcheckProvider = useCallback(async () => { const healthcheckProvider = useCallback(async () => {
setIsHealthcheckLoading(true); setIsHealthcheckLoading(true);
await dispatch(healthcheckProviderByName(apiConfig, name)); await dispatch(healthcheckProviderByName(apiConfig, name));

View file

@ -1,9 +1,13 @@
import * as React from 'react'; import * as React from 'react';
import ContentHeader from 'src/components/ContentHeader';
import { ProxyProvider } from 'src/components/proxies/ProxyProvider';
import { FormattedProxyProvider } from 'src/store/types';
import ContentHeader from '../ContentHeader'; export function ProxyProviderList({
import { ProxyProvider } from './ProxyProvider'; items,
}: {
export function ProxyProviderList({ items }) { items: FormattedProxyProvider[];
}) {
if (items.length === 0) return null; if (items.length === 0) return null;
return ( return (

View file

@ -0,0 +1,50 @@
import * as React from 'react';
import { updateProviderByName, updateProviders } from 'src/store/proxies';
import { DispatchFn } from 'src/store/types';
import { ClashAPIConfig } from 'src/types';
const { useCallback, useState } = React;
export function useUpdateProviderItem({
dispatch,
apiConfig,
name,
}: {
dispatch: DispatchFn;
apiConfig: ClashAPIConfig;
name: string;
}) {
return useCallback(() => dispatch(updateProviderByName(apiConfig, name)), [
apiConfig,
dispatch,
name,
]);
}
export function useUpdateProviderItems({
dispatch,
apiConfig,
names,
}: {
dispatch: DispatchFn;
apiConfig: ClashAPIConfig;
names: string[];
}): [() => unknown, boolean] {
const [isLoading, setIsLoading] = useState(false);
const action = useCallback(async () => {
if (isLoading) {
return;
}
setIsLoading(true);
try {
await dispatch(updateProviders(apiConfig, names));
} catch (e) {
// ignore
}
setIsLoading(false);
}, [apiConfig, dispatch, names, isLoading]);
return [action, isLoading];
}

View file

@ -1,9 +1,9 @@
import { formatDistance } from 'date-fns'; import { formatDistance } from 'date-fns';
import * as React from 'react'; import * as React from 'react';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
import { RotateIcon } from 'src/components/rules/RotateIcon';
import { useUpdateRuleProviderItem } from 'src/components/rules/rules.hooks'; import { useUpdateRuleProviderItem } from 'src/components/rules/rules.hooks';
import { SectionNameType } from 'src/components/shared/Basic'; import { SectionNameType } from 'src/components/shared/Basic';
import { RotateIcon } from 'src/components/shared/RotateIcon';
import s from './RuleProviderItem.module.css'; import s from './RuleProviderItem.module.css';

View file

@ -1,8 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { RotateIcon } from 'src/components/rules/RotateIcon';
import { useUpdateAllRuleProviderItems } from 'src/components/rules/rules.hooks'; import { useUpdateAllRuleProviderItems } from 'src/components/rules/rules.hooks';
import { Fab, position as fabPosition } from 'src/components/shared/Fab'; import { Fab, position as fabPosition } from 'src/components/shared/Fab';
import { RotateIcon } from 'src/components/shared/RotateIcon';
import { ClashAPIConfig } from 'src/types'; import { ClashAPIConfig } from 'src/types';
type RulesPageFabProps = { type RulesPageFabProps = {

View file

@ -32,4 +32,5 @@ export const data = {
latency_test_url: 'Latency Test URL', latency_test_url: 'Latency Test URL',
lang: 'Language', lang: 'Language',
update_all_rule_provider: 'Update all rule providers', update_all_rule_provider: 'Update all rule providers',
update_all_proxy_provider: 'Update all proxy providers',
}; };

View file

@ -32,4 +32,5 @@ export const data = {
latency_test_url: '延迟测速 URL', latency_test_url: '延迟测速 URL',
lang: '语言', lang: '语言',
update_all_rule_provider: '更新所有 rule provider', update_all_rule_provider: '更新所有 rule provider',
update_all_proxy_provider: '更新所有 proxy providers',
}; };

View file

@ -106,6 +106,21 @@ export function updateProviderByName(apiConfig: ClashAPIConfig, name: string) {
}; };
} }
export function updateProviders(apiConfig: ClashAPIConfig, names: string[]) {
return async (dispatch: DispatchFn) => {
for (let i = 0; i < names.length; i++) {
try {
await proxiesAPI.updateProviderByName(apiConfig, names[i]);
} catch (x) {
// ignore
}
}
// should be optimized
// but ¯\_(ツ)_/¯
dispatch(fetchProxies(apiConfig));
};
}
async function healthcheckProviderByNameInternal( async function healthcheckProviderByNameInternal(
apiConfig: ClashAPIConfig, apiConfig: ClashAPIConfig,
name: string name: string