1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
import cx from 'clsx';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { List as VirtualList, RowComponentProps } from 'react-window';
import ContentHeader from '~/components/ContentHeader';
import { RuleProviderItem } from '~/components/rules/RuleProviderItem';
import { RulesPageFab } from '~/components/rules/RulesPageFab';
import { TextFilter } from '~/components/shared/TextFitler';
import { useRulesPage } from '~/modules/rules/hooks';
import { formatQty, getItemSizeFactory, RulesListItemData } from '~/modules/rules/utils';
import { ruleFilterText } from '~/store/rules';
import { ClashAPIConfig } from '~/types';
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import Rule from './Rule';
import s from './Rules.module.scss';
type RulesRowProps = {
data: RulesListItemData;
};
function Row({ index, style, data }: RowComponentProps<RulesRowProps>) {
const { rules, provider, apiConfig } = data;
if (!rules) {
const name = provider.names[index];
const item = provider.byName[name];
return (
<div style={style} className={s.RuleProviderItemWrapper}>
<RuleProviderItem apiConfig={apiConfig} {...item} />
</div>
);
}
const r = rules[index];
return (
<div style={style}>
<Rule {...r} />
</div>
);
}
type RulesProps = {
apiConfig: ClashAPIConfig;
};
export default function Rules({ apiConfig }: RulesProps) {
const [refRulesContainer, containerHeight] = useRemainingViewPortHeight();
const { rules, provider, activeTab, setActiveTab, isRulesTab, handleTabKeyDown } =
useRulesPage(apiConfig);
const getItemSize = getItemSizeFactory({ isRulesTab });
const { t } = useTranslation();
return (
<div className={s.container}>
<ContentHeader>
<div className={s.tabsContainer}>
<div
className={cx(s.tab, { [s.active]: activeTab === 'rules' })}
onClick={() => setActiveTab('rules')}
onKeyDown={handleTabKeyDown('rules')}
role="button"
tabIndex={0}
>
{t('Rules')}
<span className={s.tabCount}>{formatQty(rules.length)}</span>
</div>
{provider.names.length > 0 && (
<div
className={cx(s.tab, { [s.active]: activeTab === 'providers' })}
onClick={() => setActiveTab('providers')}
onKeyDown={handleTabKeyDown('providers')}
role="button"
tabIndex={0}
>
{t('rule_provider')}
<span className={s.tabCount}>{formatQty(provider.names.length)}</span>
</div>
)}
</div>
<div style={{ flex: 1 }} />
<div className={s.filterWrapper}>
<TextFilter textAtom={ruleFilterText} placeholder={t('Search')} />
</div>
</ContentHeader>
<div ref={refRulesContainer} className={s.listWrapper}>
<VirtualList
style={{ height: containerHeight, width: '100%' }}
rowCount={isRulesTab ? rules.length : provider.names.length}
rowHeight={getItemSize}
rowComponent={Row}
rowProps={{
data: { rules: isRulesTab ? rules : null, provider, apiConfig } as RulesListItemData,
}}
/>
</div>
{provider && provider.names && provider.names.length > 0 ? (
<RulesPageFab apiConfig={apiConfig} />
) : null}
</div>
);
}
|