diff options
| author | Haishan <[email protected]> | 2021-02-28 16:34:24 +0800 |
|---|---|---|
| committer | Haishan <[email protected]> | 2021-02-28 18:04:18 +0800 |
| commit | 27a66043403c7e619029bcf50dbc29893e173d07 (patch) | |
| tree | fdb183a07977dbfb05cf3cf6991143c5d6678dbc /src/components | |
| parent | 7ceca5be11bbf1c8dd25e08a00390f296eb2c140 (diff) | |
feat: change rules page FAB function from refresh to update providers
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Rules.tsx | 66 | ||||
| -rw-r--r-- | src/components/rules/RotateIcon.module.css | 16 | ||||
| -rw-r--r-- | src/components/rules/RotateIcon.tsx | 16 | ||||
| -rw-r--r-- | src/components/rules/RuleProviderItem.module.css | 19 | ||||
| -rw-r--r-- | src/components/rules/RuleProviderItem.tsx | 44 | ||||
| -rw-r--r-- | src/components/rules/RulesPageFab.tsx | 23 | ||||
| -rw-r--r-- | src/components/rules/rules.hooks.tsx | 83 |
7 files changed, 161 insertions, 106 deletions
diff --git a/src/components/Rules.tsx b/src/components/Rules.tsx index 4693dd6..83f72b8 100644 --- a/src/components/Rules.tsx +++ b/src/components/Rules.tsx @@ -1,26 +1,21 @@ import React from 'react'; -import { RotateCw } from 'react-feather'; import { useTranslation } from 'react-i18next'; -import { useQuery, useQueryClient } from 'react-query'; import { areEqual, VariableSizeList } from 'react-window'; -import { useRecoilState } from 'recoil'; -import { fetchRuleProviders } from 'src/api/rule-provider'; -import { fetchRules } from 'src/api/rules'; import { RuleProviderItem } from 'src/components/rules/RuleProviderItem'; +import { useRuleAndProvider } from 'src/components/rules/rules.hooks'; +import { RulesPageFab } from 'src/components/rules/RulesPageFab'; import { TextFilter } from 'src/components/rules/TextFilter'; -import { ruleFilterText } from 'src/store/rules'; import { State } from 'src/store/types'; -import type { ClashAPIConfig } from 'src/types'; +import { ClashAPIConfig } from 'src/types'; import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight'; import { getClashAPIConfig } from '../store/app'; import ContentHeader from './ContentHeader'; import Rule from './Rule'; import s from './Rules.module.css'; -import { Fab, position as fabPosition } from './shared/Fab'; import { connect } from './StateProvider'; -const { memo, useMemo, useCallback } = React; +const { memo } = React; const paddingBottom = 30; @@ -75,47 +70,14 @@ const mapState = (s: State) => ({ export default connect(mapState)(Rules); -function useRuleAndProvider(apiConfig: ClashAPIConfig) { - const { data: rules, isFetching } = useQuery(['/rules', apiConfig], () => - fetchRules('/rules', apiConfig) - ); - const { data: provider } = useQuery(['/providers/rules', apiConfig], () => - fetchRuleProviders('/providers/rules', apiConfig) - ); - - const [filterText] = useRecoilState(ruleFilterText); - if (filterText === '') { - return { rules, provider, isFetching }; - } else { - const f = filterText.toLowerCase(); - return { - rules: rules.filter((r) => r.payload.toLowerCase().indexOf(f) >= 0), - isFetching, - provider: { - byName: provider.byName, - names: provider.names.filter((t) => t.toLowerCase().indexOf(f) >= 0), - }, - }; - } -} +type RulesProps = { + apiConfig: ClashAPIConfig; +}; -function useInvalidateQueries() { - const queryClient = useQueryClient(); - return useCallback(() => { - queryClient.invalidateQueries('/rules'); - queryClient.invalidateQueries('/providers/rules'); - }, [queryClient]); -} - -function Rules({ apiConfig }) { +function Rules({ apiConfig }: RulesProps) { const [refRulesContainer, containerHeight] = useRemainingViewPortHeight(); - const refreshIcon = useMemo(() => <RotateCw width={16} />, []); - const { rules, provider } = useRuleAndProvider(apiConfig); - const invalidateQueries = useInvalidateQueries(); - - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ rules: RuleItem[]; provider: {... Remove this comment to see the full error message - const getItemSize = getItemSizeFactory({ rules, provider }); + const getItemSize = getItemSizeFactory({ provider }); const { t } = useTranslation(); @@ -139,13 +101,9 @@ function Rules({ apiConfig }) { {Row} </VariableSizeList> </div> - - <Fab - icon={refreshIcon} - text="Refresh" - style={fabPosition} - onClick={invalidateQueries} - /> + {provider && provider.names && provider.names.length > 0 ? ( + <RulesPageFab apiConfig={apiConfig} /> + ) : null} </div> ); } diff --git a/src/components/rules/RotateIcon.module.css b/src/components/rules/RotateIcon.module.css new file mode 100644 index 0000000..60748de --- /dev/null +++ b/src/components/rules/RotateIcon.module.css @@ -0,0 +1,16 @@ +.rotate { + display: inline-flex; +} +.isRotating { + animation: rotating 3s infinite linear; + animation-fill-mode: forwards; +} + +@keyframes rotating { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/components/rules/RotateIcon.tsx b/src/components/rules/RotateIcon.tsx new file mode 100644 index 0000000..e2d4ad8 --- /dev/null +++ b/src/components/rules/RotateIcon.tsx @@ -0,0 +1,16 @@ +import cx from 'clsx'; +import * as React from 'react'; +import { RotateCw } from 'react-feather'; + +import s from './RotateIcon.module.css'; + +export function RotateIcon({ isRotating }: { isRotating: boolean }) { + const cls = cx(s.rotate, { + [s.isRotating]: isRotating, + }); + return ( + <span className={cls}> + <RotateCw width={16} /> + </span> + ); +} diff --git a/src/components/rules/RuleProviderItem.module.css b/src/components/rules/RuleProviderItem.module.css index 676a34d..532ec8a 100644 --- a/src/components/rules/RuleProviderItem.module.css +++ b/src/components/rules/RuleProviderItem.module.css @@ -24,21 +24,10 @@ .refreshButtonWrapper { display: grid; place-items: center; + opacity: 0; + transition: opacity 0.2s; } -.rotate { - display: inline-flex; -} -.isRotating { - animation: rotating 3s infinite linear; - animation-fill-mode: forwards; -} - -@keyframes rotating { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +.RuleProviderItem:hover .refreshButtonWrapper { + opacity: 1; } diff --git a/src/components/rules/RuleProviderItem.tsx b/src/components/rules/RuleProviderItem.tsx index 0232799..9d439c7 100644 --- a/src/components/rules/RuleProviderItem.tsx +++ b/src/components/rules/RuleProviderItem.tsx @@ -1,45 +1,12 @@ -import cx from 'clsx'; import { formatDistance } from 'date-fns'; import * as React from 'react'; -import { RotateCw } from 'react-feather'; -import { useMutation, useQueryClient } from 'react-query'; -import { refreshRuleProviderByName } from 'src/api/rule-provider'; 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 { ClashAPIConfig } from 'src/types'; import s from './RuleProviderItem.module.css'; -function useRefresh( - name: string, - apiConfig: ClashAPIConfig -): [(ev: React.MouseEvent<HTMLButtonElement>) => unknown, boolean] { - const queryClient = useQueryClient(); - const { mutate, isLoading } = useMutation(refreshRuleProviderByName, { - onSuccess: () => { - queryClient.invalidateQueries('/providers/rules'); - }, - }); - - const onClickRefreshButton = (ev: React.MouseEvent<HTMLButtonElement>) => { - ev.preventDefault(); - mutate({ name, apiConfig }); - }; - - return [onClickRefreshButton, isLoading]; -} - -function RotatableRotateCw({ isRotating }: { isRotating: boolean }) { - const cls = cx(s.rotate, { - [s.isRotating]: isRotating, - }); - return ( - <span className={cls}> - <RotateCw width={16} /> - </span> - ); -} - export function RuleProviderItem({ idx, name, @@ -49,7 +16,10 @@ export function RuleProviderItem({ ruleCount, apiConfig, }) { - const [onClickRefreshButton, isRefreshing] = useRefresh(name, apiConfig); + const [onClickRefreshButton, isRefreshing] = useUpdateRuleProviderItem( + name, + apiConfig + ); const timeAgo = formatDistance(new Date(updatedAt), new Date()); return ( <div className={s.RuleProviderItem}> @@ -63,7 +33,7 @@ export function RuleProviderItem({ </div> <span className={s.refreshButtonWrapper}> <Button onClick={onClickRefreshButton} disabled={isRefreshing}> - <RotatableRotateCw isRotating={isRefreshing} /> + <RotateIcon isRotating={isRefreshing} /> </Button> </span> </div> diff --git a/src/components/rules/RulesPageFab.tsx b/src/components/rules/RulesPageFab.tsx new file mode 100644 index 0000000..3bf99ad --- /dev/null +++ b/src/components/rules/RulesPageFab.tsx @@ -0,0 +1,23 @@ +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 { ClashAPIConfig } from 'src/types'; + +type RulesPageFabProps = { + apiConfig: ClashAPIConfig; +}; + +export function RulesPageFab({ apiConfig }: RulesPageFabProps) { + const [update, isLoading] = useUpdateAllRuleProviderItems(apiConfig); + const { t } = useTranslation(); + return ( + <Fab + icon={<RotateIcon isRotating={isLoading} />} + text={t('update_all_rule_provider')} + style={fabPosition} + onClick={update} + /> + ); +} diff --git a/src/components/rules/rules.hooks.tsx b/src/components/rules/rules.hooks.tsx new file mode 100644 index 0000000..84755f9 --- /dev/null +++ b/src/components/rules/rules.hooks.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useRecoilState } from 'recoil'; +import { + fetchRuleProviders, + refreshRuleProviderByName, + updateRuleProviders, +} from 'src/api/rule-provider'; +import { fetchRules } from 'src/api/rules'; +import { ruleFilterText } from 'src/store/rules'; +import type { ClashAPIConfig } from 'src/types'; + +const { useCallback } = React; + +export function useUpdateRuleProviderItem( + name: string, + apiConfig: ClashAPIConfig +): [(ev: React.MouseEvent<HTMLButtonElement>) => unknown, boolean] { + const queryClient = useQueryClient(); + const { mutate, isLoading } = useMutation(refreshRuleProviderByName, { + onSuccess: () => { + queryClient.invalidateQueries('/providers/rules'); + }, + }); + const onClickRefreshButton = (ev: React.MouseEvent<HTMLButtonElement>) => { + ev.preventDefault(); + mutate({ name, apiConfig }); + }; + return [onClickRefreshButton, isLoading]; +} + +export function useUpdateAllRuleProviderItems( + apiConfig: ClashAPIConfig +): [(ev: React.MouseEvent<HTMLButtonElement>) => unknown, boolean] { + const queryClient = useQueryClient(); + const { data: provider } = useRuleProviderQuery(apiConfig); + const { mutate, isLoading } = useMutation(updateRuleProviders, { + onSuccess: () => { + queryClient.invalidateQueries('/providers/rules'); + }, + }); + const onClickRefreshButton = (ev: React.MouseEvent<HTMLButtonElement>) => { + ev.preventDefault(); + mutate({ names: provider.names, apiConfig }); + }; + return [onClickRefreshButton, isLoading]; +} + +export function useInvalidateQueries() { + const queryClient = useQueryClient(); + return useCallback(() => { + queryClient.invalidateQueries('/rules'); + queryClient.invalidateQueries('/providers/rules'); + }, [queryClient]); +} + +export function useRuleProviderQuery(apiConfig: ClashAPIConfig) { + return useQuery(['/providers/rules', apiConfig], () => + fetchRuleProviders('/providers/rules', apiConfig) + ); +} + +export function useRuleAndProvider(apiConfig: ClashAPIConfig) { + const { data: rules, isFetching } = useQuery(['/rules', apiConfig], () => + fetchRules('/rules', apiConfig) + ); + const { data: provider } = useRuleProviderQuery(apiConfig); + + const [filterText] = useRecoilState(ruleFilterText); + if (filterText === '') { + return { rules, provider, isFetching }; + } else { + const f = filterText.toLowerCase(); + return { + rules: rules.filter((r) => r.payload.toLowerCase().indexOf(f) >= 0), + isFetching, + provider: { + byName: provider.byName, + names: provider.names.filter((t) => t.toLowerCase().indexOf(f) >= 0), + }, + }; + } +} |
