summaryrefslogtreecommitdiff
path: root/src/components/Rules.tsx
blob: fb63028e75844ac8837f0215989a7ff0d0aa5820 (plain)
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>
  );
}