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/rules | |
| parent | 7ceca5be11bbf1c8dd25e08a00390f296eb2c140 (diff) | |
feat: change rules page FAB function from refresh to update providers
Diffstat (limited to 'src/components/rules')
| -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 |
6 files changed, 149 insertions, 52 deletions
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), + }, + }; + } +} |
