diff options
| author | Haishan <[email protected]> | 2021-02-28 17:06:00 +0800 |
|---|---|---|
| committer | Haishan <[email protected]> | 2021-02-28 18:04:18 +0800 |
| commit | ec4586ef3c92f7d5125cb06286a2e44c59a24bb3 (patch) | |
| tree | a9f217c163ae7218160fce5545d167d74ee9a591 /src | |
| parent | 27a66043403c7e619029bcf50dbc29893e173d07 (diff) | |
feat: add FAB action button to update all proxy providers
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/proxies/Proxies.tsx | 60 | ||||
| -rw-r--r-- | src/components/proxies/ProxyPageFab.tsx | 83 | ||||
| -rw-r--r-- | src/components/proxies/ProxyProvider.tsx | 30 | ||||
| -rw-r--r-- | src/components/proxies/ProxyProviderList.tsx | 12 | ||||
| -rw-r--r-- | src/components/proxies/proxies.hooks.tsx | 50 | ||||
| -rw-r--r-- | src/components/rules/RuleProviderItem.tsx | 2 | ||||
| -rw-r--r-- | src/components/rules/RulesPageFab.tsx | 2 | ||||
| -rw-r--r-- | src/components/shared/RotateIcon.module.css (renamed from src/components/rules/RotateIcon.module.css) | 0 | ||||
| -rw-r--r-- | src/components/shared/RotateIcon.tsx (renamed from src/components/rules/RotateIcon.tsx) | 0 | ||||
| -rw-r--r-- | src/i18n/en.ts | 1 | ||||
| -rw-r--r-- | src/i18n/zh.ts | 1 | ||||
| -rw-r--r-- | src/store/proxies.tsx | 15 |
12 files changed, 193 insertions, 63 deletions
diff --git a/src/components/proxies/Proxies.tsx b/src/components/proxies/Proxies.tsx index d36a568..e162a17 100644 --- a/src/components/proxies/Proxies.tsx +++ b/src/components/proxies/Proxies.tsx @@ -1,29 +1,28 @@ import Tooltip from '@reach/tooltip'; import * as React from 'react'; -import { Zap } from 'react-feather'; import { useTranslation } from 'react-i18next'; - -import { getClashAPIConfig } from '../../store/app'; +import Button from 'src/components/Button'; +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 { fetchProxies, getDelay, getProxyGroupNames, getProxyProviders, getShowModalClosePrevConns, - requestDelayAll, -} from '../../store/proxies'; -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'; +} from 'src/store/proxies'; +import type { State } from 'src/store/types'; + 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; @@ -38,16 +37,6 @@ function Proxies({ 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(() => { refFetchedTimestamp.current.startAt = Date.now(); @@ -120,19 +109,10 @@ function Proxies({ </div> <ProxyProviderList items={proxyProviders} /> <div style={{ height: 60 }} /> - <Fab - icon={ - isTestingLatency ? ( - <IsFetching> - <Zap width={16} height={16} /> - </IsFetching> - ) : ( - <Zap width={16} height={16} /> - ) - } - onClick={requestDelayAllFn} - text={t('Test Latency')} - style={fabPosition} + <ProxyPageFab + dispatch={dispatch} + apiConfig={apiConfig} + proxyProviders={proxyProviders} /> <BaseModal isOpen={showModalClosePrevConns} @@ -147,7 +127,7 @@ function Proxies({ ); } -const mapState = (s) => ({ +const mapState = (s: State) => ({ apiConfig: getClashAPIConfig(s), groupNames: getProxyGroupNames(s), proxyProviders: getProxyProviders(s), diff --git a/src/components/proxies/ProxyPageFab.tsx b/src/components/proxies/ProxyPageFab.tsx new file mode 100644 index 0000000..7cc6d03 --- /dev/null +++ b/src/components/proxies/ProxyPageFab.tsx @@ -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> + ); +} diff --git a/src/components/proxies/ProxyProvider.tsx b/src/components/proxies/ProxyProvider.tsx index de0a94f..d7220b0 100644 --- a/src/components/proxies/ProxyProvider.tsx +++ b/src/components/proxies/ProxyProvider.tsx @@ -1,24 +1,21 @@ import { formatDistance } from 'date-fns'; import * as React from 'react'; import { RotateCw, Zap } from 'react-feather'; -import { DelayMapping } from 'src/store/types'; - -import { framerMotionResouce } from '../../misc/motion'; +import Button from 'src/components/Button'; +import Collapsible from 'src/components/Collapsible'; +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 { getClashAPIConfig, getCollapsibleIsOpen, getHideUnavailableProxies, getProxySortBy, -} from '../../store/app'; -import { - getDelay, - healthcheckProviderByName, - updateProviderByName, -} from '../../store/proxies'; -import Button from '../Button'; -import Collapsible from '../Collapsible'; -import CollapsibleSectionHeader from '../CollapsibleSectionHeader'; -import { connect, useStoreActions } from '../StateProvider'; +} from 'src/store/app'; +import { getDelay, healthcheckProviderByName } from 'src/store/proxies'; +import { DelayMapping } from 'src/store/types'; + import { useFilteredAndSorted } from './hooks'; import { ProxyList, ProxyListSummaryView } from './ProxyList'; import s from './ProxyProvider.module.css'; @@ -58,10 +55,9 @@ function ProxyProviderImpl({ proxySortBy ); const [isHealthcheckLoading, setIsHealthcheckLoading] = useState(false); - const updateProvider = useCallback( - () => dispatch(updateProviderByName(apiConfig, name)), - [apiConfig, dispatch, name] - ); + + const updateProvider = useUpdateProviderItem({ dispatch, apiConfig, name }); + const healthcheckProvider = useCallback(async () => { setIsHealthcheckLoading(true); await dispatch(healthcheckProviderByName(apiConfig, name)); diff --git a/src/components/proxies/ProxyProviderList.tsx b/src/components/proxies/ProxyProviderList.tsx index c0e6b15..1528f37 100644 --- a/src/components/proxies/ProxyProviderList.tsx +++ b/src/components/proxies/ProxyProviderList.tsx @@ -1,9 +1,13 @@ 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'; -import { ProxyProvider } from './ProxyProvider'; - -export function ProxyProviderList({ items }) { +export function ProxyProviderList({ + items, +}: { + items: FormattedProxyProvider[]; +}) { if (items.length === 0) return null; return ( diff --git a/src/components/proxies/proxies.hooks.tsx b/src/components/proxies/proxies.hooks.tsx new file mode 100644 index 0000000..ec51c9b --- /dev/null +++ b/src/components/proxies/proxies.hooks.tsx @@ -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]; +} diff --git a/src/components/rules/RuleProviderItem.tsx b/src/components/rules/RuleProviderItem.tsx index 9d439c7..c92cd05 100644 --- a/src/components/rules/RuleProviderItem.tsx +++ b/src/components/rules/RuleProviderItem.tsx @@ -1,9 +1,9 @@ import { formatDistance } from 'date-fns'; import * as React from 'react'; import Button from 'src/components/Button'; -import { RotateIcon } from 'src/components/rules/RotateIcon'; import { useUpdateRuleProviderItem } from 'src/components/rules/rules.hooks'; import { SectionNameType } from 'src/components/shared/Basic'; +import { RotateIcon } from 'src/components/shared/RotateIcon'; import s from './RuleProviderItem.module.css'; diff --git a/src/components/rules/RulesPageFab.tsx b/src/components/rules/RulesPageFab.tsx index 3bf99ad..ce52a9a 100644 --- a/src/components/rules/RulesPageFab.tsx +++ b/src/components/rules/RulesPageFab.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { RotateIcon } from 'src/components/rules/RotateIcon'; import { useUpdateAllRuleProviderItems } from 'src/components/rules/rules.hooks'; import { Fab, position as fabPosition } from 'src/components/shared/Fab'; +import { RotateIcon } from 'src/components/shared/RotateIcon'; import { ClashAPIConfig } from 'src/types'; type RulesPageFabProps = { diff --git a/src/components/rules/RotateIcon.module.css b/src/components/shared/RotateIcon.module.css index 60748de..60748de 100644 --- a/src/components/rules/RotateIcon.module.css +++ b/src/components/shared/RotateIcon.module.css diff --git a/src/components/rules/RotateIcon.tsx b/src/components/shared/RotateIcon.tsx index e2d4ad8..e2d4ad8 100644 --- a/src/components/rules/RotateIcon.tsx +++ b/src/components/shared/RotateIcon.tsx diff --git a/src/i18n/en.ts b/src/i18n/en.ts index a37b3e6..ac0aa6c 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -32,4 +32,5 @@ export const data = { latency_test_url: 'Latency Test URL', lang: 'Language', update_all_rule_provider: 'Update all rule providers', + update_all_proxy_provider: 'Update all proxy providers', }; diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index 18c1b44..e64d400 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -32,4 +32,5 @@ export const data = { latency_test_url: '延迟测速 URL', lang: '语言', update_all_rule_provider: '更新所有 rule provider', + update_all_proxy_provider: '更新所有 proxy providers', }; diff --git a/src/store/proxies.tsx b/src/store/proxies.tsx index 7c34d74..25200cd 100644 --- a/src/store/proxies.tsx +++ b/src/store/proxies.tsx @@ -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( apiConfig: ClashAPIConfig, name: string |
