feat: add FAB action button to update all proxy providers
This commit is contained in:
parent
27a6604340
commit
ec4586ef3c
12 changed files with 193 additions and 63 deletions
|
@ -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),
|
||||||
|
|
83
src/components/proxies/ProxyPageFab.tsx
Normal file
83
src/components/proxies/ProxyPageFab.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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));
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
50
src/components/proxies/proxies.hooks.tsx
Normal file
50
src/components/proxies/proxies.hooks.tsx
Normal 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];
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue