diff options
Diffstat (limited to 'src/components/rules/Rule.tsx')
| -rw-r--r-- | src/components/rules/Rule.tsx | 77 |
1 files changed, 73 insertions, 4 deletions
diff --git a/src/components/rules/Rule.tsx b/src/components/rules/Rule.tsx index e1fd047..e53ff23 100644 --- a/src/components/rules/Rule.tsx +++ b/src/components/rules/Rule.tsx @@ -1,6 +1,13 @@ +import cx from 'clsx'; +import { formatDistanceToNow } from 'date-fns'; import React from 'react'; +import { useTranslation } from 'react-i18next'; -import { FileText, Globe, Hash, Link, Shield, Zap } from '~/components/shared/FeatherIcons'; +import { RuleExtra } from '~/api/rules'; +import { Activity, FileText, Globe, Hash, Link, Shield, Zap } from '~/components/shared/FeatherIcons'; +import SwitchThemed from '~/components/SwitchThemed'; +import { useToggleRuleDisabled } from '~/modules/rules/hooks'; +import { ClashAPIConfig } from '~/types'; import s0 from './Rule.module.scss'; @@ -39,23 +46,66 @@ function getIconFor(type: string) { } } +type RuleProviderLookup = { + byName: Record<string, { ruleCount?: number }>; +}; + type Props = { id?: number; type?: string; payload?: string; proxy?: string; size?: number; + extra?: RuleExtra; + apiConfig?: ClashAPIConfig; + provider?: RuleProviderLookup; }; -function Rule({ type, payload, proxy, id, size }: Props) { +function getEntryCount({ + type, + payload, + size, + provider, +}: { + type: string; + payload: string; + size: number; + provider?: RuleProviderLookup; +}): number | undefined { + if ((type === 'GeoSite' || type === 'GeoIP') && size >= 0) { + return size; + } + if (type === 'RuleSet') { + return provider?.byName?.[payload]?.ruleCount; + } + return undefined; +} + +function Rule({ type, payload, proxy, id, size, extra, apiConfig, provider }: Props) { + const { t } = useTranslation(); const styleProxy = getStyleFor({ proxy }); + const { toggleRule, isPending } = useToggleRuleDisabled(apiConfig); + const disabled = extra?.disabled ?? false; + const entryCount = getEntryCount({ type, payload, size, provider }); + + const hitTitle = extra + ? extra.hitCount > 0 + ? t('rule_hit_tip', { + count: extra.hitCount, + time: formatDistanceToNow(new Date(extra.hitAt), { addSuffix: true }), + }) + : t('rule_never_hit') + : undefined; + return ( - <div className={s0.rule}> + <div className={cx(s0.rule, { [s0.disabled]: disabled })}> <div className={s0.left}>{id}</div> <div className={s0.right}> <div className={s0.payloadRow}> <div className={s0.payload}>{payload}</div> - {(type === 'GeoSite' || type === 'GeoIP') && <div className={s0.size}>size: {size}</div>} + {typeof entryCount === 'number' && ( + <div className={s0.size}>{t('rule_entry_count', { count: entryCount })}</div> + )} </div> <div className={s0.metaRow}> <div className={s0.typeTag}> @@ -65,6 +115,25 @@ function Rule({ type, payload, proxy, id, size }: Props) { <div className={s0.proxyTag} style={styleProxy}> {proxy} </div> + {extra && ( + <div className={s0.hitInfo} title={hitTitle}> + <Activity size={12} /> + <span>{extra.hitCount}</span> + </div> + )} + <div className={s0.spacer} /> + {extra && ( + <div + className={cx(s0.wrapSwitch, { [s0.pending]: isPending })} + title={disabled ? t('rule_enable') : t('rule_disable')} + > + <SwitchThemed + name={`rule-${id}`} + checked={!disabled} + onChange={(checked: boolean) => toggleRule(id, !checked)} + /> + </div> + )} </div> </div> </div> |
