summaryrefslogtreecommitdiff
path: root/src/components/Rules.js
diff options
context:
space:
mode:
authorHaishan <[email protected]>2020-07-01 22:06:26 +0800
committerHaishan <[email protected]>2020-07-04 17:58:56 +0800
commit32bed273c83f0593187110d2b08a0f9ec5a7efd7 (patch)
tree0b47da752de3ee0d87945c1122b2cf9d3bf8043f /src/components/Rules.js
parent55e928a87f561ab927774834b50e099a0758522d (diff)
feat: support rule provider
Diffstat (limited to 'src/components/Rules.js')
-rw-r--r--src/components/Rules.js115
1 files changed, 92 insertions, 23 deletions
diff --git a/src/components/Rules.js b/src/components/Rules.js
index a04116a..949e5e9 100644
--- a/src/components/Rules.js
+++ b/src/components/Rules.js
@@ -1,27 +1,63 @@
import React from 'react';
import { RotateCw } from 'react-feather';
-import { areEqual, FixedSizeList as List } from 'react-window';
+import { queryCache, useQuery } 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 { TextFilter } from 'src/components/rules/TextFilter';
+import { ruleFilterText } from 'src/store/rules';
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import { getClashAPIConfig } from '../store/app';
-import { fetchRules, fetchRulesOnce, getRules } from '../store/rules';
import ContentHeader from './ContentHeader';
import Rule from './Rule';
-import RuleSearch from './RuleSearch';
+import s from './Rules.module.css';
import { Fab, position as fabPosition } from './shared/Fab';
import { connect } from './StateProvider';
-const { memo, useEffect, useMemo, useCallback } = React;
+const { memo, useMemo, useCallback } = React;
const paddingBottom = 30;
-function itemKey(index, data) {
- const item = data[index];
+function itemKey(index, { rules, provider }) {
+ const providerQty = provider.names.length;
+
+ if (index < providerQty) {
+ return provider.names[index];
+ }
+ const item = rules[index - providerQty];
return item.id;
}
+function getItemSizeFactory({ provider }) {
+ return function getItemSize(idx) {
+ const providerQty = provider.names.length;
+ if (idx < providerQty) {
+ // provider
+ return 90;
+ }
+ // rule
+ return 80;
+ };
+}
+
const Row = memo(({ index, style, data }) => {
- const r = data[index];
+ const { rules, provider, apiConfig } = data;
+ const providerQty = provider.names.length;
+
+ if (index < providerQty) {
+ 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 - providerQty];
return (
<div style={style}>
<Rule {...r} />
@@ -31,42 +67,75 @@ const Row = memo(({ index, style, data }) => {
const mapState = (s) => ({
apiConfig: getClashAPIConfig(s),
- rules: getRules(s),
});
export default connect(mapState)(Rules);
-function Rules({ dispatch, apiConfig, rules }) {
- const fetchRulesHooked = useCallback(() => {
- dispatch(fetchRules(apiConfig));
- }, [apiConfig, dispatch]);
- useEffect(() => {
- dispatch(fetchRulesOnce(apiConfig));
- }, [dispatch, apiConfig]);
+function useRuleAndProvider(apiConfig) {
+ const { data: rules } = useQuery(['/rules', apiConfig], fetchRules, {
+ suspense: true,
+ });
+ const { data: provider } = useQuery(
+ ['/providers/rules', apiConfig],
+ fetchRuleProviders,
+ { suspense: true }
+ );
+
+ const [filterText] = useRecoilState(ruleFilterText);
+ if (filterText === '') {
+ return { rules, provider };
+ } else {
+ const f = filterText.toLowerCase();
+ return {
+ rules: rules.filter((r) => r.payload.toLowerCase().indexOf(f) >= 0),
+ provider: {
+ byName: provider.byName,
+ names: provider.names.filter((t) => t.toLowerCase().indexOf(f) >= 0),
+ },
+ };
+ }
+}
+
+function useInvalidateQueries() {
+ return useCallback(() => {
+ queryCache.invalidateQueries('/rules');
+ queryCache.invalidateQueries('/providers/rules');
+ }, []);
+}
+
+function Rules({ apiConfig }) {
const [refRulesContainer, containerHeight] = useRemainingViewPortHeight();
const refreshIcon = useMemo(() => <RotateCw width={16} />, []);
+
+ const { rules, provider } = useRuleAndProvider(apiConfig);
+ const invalidateQueries = useInvalidateQueries();
+
+ const getItemSize = getItemSizeFactory({ rules, provider });
+
return (
<div>
- <ContentHeader title="Rules" />
- <RuleSearch />
+ <div className={s.header}>
+ <ContentHeader title="Rules" />
+ <TextFilter />
+ </div>
<div ref={refRulesContainer} style={{ paddingBottom }}>
- <List
+ <VariableSizeList
height={containerHeight - paddingBottom}
width="100%"
- itemCount={rules.length}
- itemSize={80}
- itemData={rules}
+ itemCount={rules.length + provider.names.length}
+ itemSize={getItemSize}
+ itemData={{ rules, provider, apiConfig }}
itemKey={itemKey}
>
{Row}
- </List>
+ </VariableSizeList>
</div>
<Fab
icon={refreshIcon}
text="Refresh"
- onClick={fetchRulesHooked}
position={fabPosition}
+ onClick={invalidateQueries}
/>
</div>
);