summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorHaishan <[email protected]>2021-02-28 16:34:24 +0800
committerHaishan <[email protected]>2021-02-28 18:04:18 +0800
commit27a66043403c7e619029bcf50dbc29893e173d07 (patch)
treefdb183a07977dbfb05cf3cf6991143c5d6678dbc /src/components
parent7ceca5be11bbf1c8dd25e08a00390f296eb2c140 (diff)
feat: change rules page FAB function from refresh to update providers
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Rules.tsx66
-rw-r--r--src/components/rules/RotateIcon.module.css16
-rw-r--r--src/components/rules/RotateIcon.tsx16
-rw-r--r--src/components/rules/RuleProviderItem.module.css19
-rw-r--r--src/components/rules/RuleProviderItem.tsx44
-rw-r--r--src/components/rules/RulesPageFab.tsx23
-rw-r--r--src/components/rules/rules.hooks.tsx83
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),
+ },
+ };
+ }
+}