diff options
Diffstat (limited to 'src/modules/rules')
| -rw-r--r-- | src/modules/rules/hooks.ts | 100 | ||||
| -rw-r--r-- | src/modules/rules/utils.ts | 36 |
2 files changed, 69 insertions, 67 deletions
diff --git a/src/modules/rules/hooks.ts b/src/modules/rules/hooks.ts index e13ea65..90a95f2 100644 --- a/src/modules/rules/hooks.ts +++ b/src/modules/rules/hooks.ts @@ -7,16 +7,18 @@ import { refreshRuleProviderByName, updateRuleProviders, } from '~/api/rule-provider'; -import { fetchRules } from '~/api/rules'; +import { fetchRules, updateRuleDisabledStatus } from '~/api/rules'; import { ruleFilterText } from '~/store/rules'; import type { ClashAPIConfig } from '~/types'; -const { useCallback, useState } = React; +import type { RulesTabKey } from './utils'; + +const { useCallback, useMemo, useState } = React; export function useUpdateRuleProviderItem( name: string, - apiConfig: ClashAPIConfig -): [(ev: React.MouseEvent<HTMLButtonElement>) => unknown, boolean] { + apiConfig: ClashAPIConfig, +): [() => void, boolean] { const queryClient = useQueryClient(); const { mutate, isPending } = useMutation({ mutationFn: refreshRuleProviderByName, @@ -24,16 +26,11 @@ export function useUpdateRuleProviderItem( queryClient.invalidateQueries({ queryKey: ['/providers/rules'] }); }, }); - const onClickRefreshButton = (ev: React.MouseEvent<HTMLButtonElement>) => { - ev.preventDefault(); - mutate({ name, apiConfig }); - }; - return [onClickRefreshButton, isPending]; + const refresh = useCallback(() => mutate({ name, apiConfig }), [mutate, name, apiConfig]); + return [refresh, isPending]; } -export function useUpdateAllRuleProviderItems( - apiConfig: ClashAPIConfig -): [(ev: React.MouseEvent<HTMLButtonElement>) => unknown, boolean] { +export function useUpdateAllRuleProviderItems(apiConfig: ClashAPIConfig): [() => void, boolean] { const queryClient = useQueryClient(); const { data: provider } = useRuleProviderQuery(apiConfig); const { mutate, isPending } = useMutation({ @@ -42,19 +39,27 @@ export function useUpdateAllRuleProviderItems( queryClient.invalidateQueries({ queryKey: ['/providers/rules'] }); }, }); - const onClickRefreshButton = (ev: React.MouseEvent<HTMLButtonElement>) => { - ev.preventDefault(); - mutate({ names: provider.names, apiConfig }); - }; - return [onClickRefreshButton, isPending]; + const refreshAll = useCallback( + () => mutate({ names: provider.names, apiConfig }), + [mutate, provider.names, apiConfig], + ); + return [refreshAll, isPending]; } -export function useInvalidateQueries() { +export function useToggleRuleDisabled(apiConfig: ClashAPIConfig) { const queryClient = useQueryClient(); - return useCallback(() => { - queryClient.invalidateQueries({ queryKey: ['/rules'] }); - queryClient.invalidateQueries({ queryKey: ['/providers/rules'] }); - }, [queryClient]); + const { mutate, isPending } = useMutation({ + mutationFn: ({ index, disabled }: { index: number; disabled: boolean }) => + updateRuleDisabledStatus(apiConfig, { [index]: disabled }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/rules'] }); + }, + }); + const toggleRule = useCallback( + (index: number, disabled: boolean) => mutate({ index, disabled }), + [mutate], + ); + return { toggleRule, isPending }; } export function useRuleProviderQuery(apiConfig: ClashAPIConfig) { @@ -72,41 +77,40 @@ export function useRuleAndProvider(apiConfig: ClashAPIConfig) { const { data: provider } = useRuleProviderQuery(apiConfig); const [filterText] = useAtom(ruleFilterText); - if (filterText === '') { - return { rules, provider, isFetching }; - } - 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), - }, - }; + // 规则表动辄上千条,过滤结果必须缓存住:不然每次渲染都重算一遍, + // 而且新数组会让下游的 memo 全部失效 + return useMemo(() => { + if (filterText === '') { + return { rules, provider, isFetching }; + } + 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), + }, + }; + }, [rules, provider, filterText, isFetching]); } export function useRulesPage(apiConfig: ClashAPIConfig) { const { rules, provider } = useRuleAndProvider(apiConfig); - const [activeTab, setActiveTab] = useState('rules'); - const isRulesTab = activeTab === 'rules'; + // 标签出不出现看提供商总数,不看过滤结果——搜索不该把整个标签搞消失 + const { data: allProviders } = useRuleProviderQuery(apiConfig); + const providerCount = allProviders.names.length; - const handleTabKeyDown = useCallback( - (tab: string) => (e: React.KeyboardEvent) => { - if (e.key === 'Enter' || e.key === ' ') { - setActiveTab(tab); - } - }, - [] - ); + const [activeTab, setActiveTab] = useState<RulesTabKey>('rules'); + const effectiveTab: RulesTabKey = providerCount > 0 ? activeTab : 'rules'; return { rules, provider, - activeTab, + providerCount, + activeTab: effectiveTab, setActiveTab, - isRulesTab, - handleTabKeyDown, + isRulesTab: effectiveTab === 'rules', }; -}
\ No newline at end of file +} diff --git a/src/modules/rules/utils.ts b/src/modules/rules/utils.ts index c1d1464..772a1cc 100644 --- a/src/modules/rules/utils.ts +++ b/src/modules/rules/utils.ts @@ -1,24 +1,22 @@ -import { ClashAPIConfig } from '~/types'; +import type { RuleProvider } from '~/api/rule-provider'; +import type { RuleItem } from '~/api/rules'; +import type { ClashAPIConfig } from '~/types'; -export type RulesListItemData = { - rules: any[] | null; - provider: any; - apiConfig: ClashAPIConfig; +export type RuleProviderIndex = { + byName: Record<string, RuleProvider>; + names: string[]; }; -export function itemKey(index: number, { rules, provider }: RulesListItemData) { - if (!rules) { - return provider.names[index]; - } - return rules[index].id; -} +export type RulesTabKey = 'rules' | 'providers'; -export function getItemSizeFactory({ isRulesTab }: { isRulesTab: boolean }) { - return function getItemSize() { - return isRulesTab ? 70 : 100; - }; -} +export type RulesRowProps = { + /** 规则标签页的数据;提供商标签页下为 null */ + rules: RuleItem[] | null; + provider: RuleProviderIndex; + apiConfig: ClashAPIConfig; +}; -export function formatQty(qty: number) { - return qty < 100 ? String(qty) : '99+'; -} +// 虚拟列表要求定高。两个数字都按「内容最多的那一行」定: +// 规则行是 序号 + 两行内容(payload 长了会折行占两行),提供商行多一行元信息 +export const RULE_ROW_HEIGHT = 88; +export const PROVIDER_ROW_HEIGHT = 100; |
