summaryrefslogtreecommitdiff
path: root/src/components/rules
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
parent55e928a87f561ab927774834b50e099a0758522d (diff)
feat: support rule provider
Diffstat (limited to 'src/components/rules')
-rw-r--r--src/components/rules/RuleProviderItem.module.css43
-rw-r--r--src/components/rules/RuleProviderItem.tsx71
-rw-r--r--src/components/rules/TextFilter.tsx18
3 files changed, 132 insertions, 0 deletions
diff --git a/src/components/rules/RuleProviderItem.module.css b/src/components/rules/RuleProviderItem.module.css
new file mode 100644
index 0000000..f4f52c8
--- /dev/null
+++ b/src/components/rules/RuleProviderItem.module.css
@@ -0,0 +1,43 @@
+.RuleProviderItem {
+ display: grid;
+ grid-template-columns: 40px 1fr 46px;
+ height: 100%;
+}
+
+.left {
+ display: inline-flex;
+ align-items: center;
+ color: var(--color-text-secondary);
+ opacity: 0.4;
+}
+
+.middle {
+ display: grid;
+ grid-template-rows: 1fr auto auto;
+ align-items: center;
+}
+
+.gray {
+ color: #777;
+}
+
+.refreshButtonWrapper {
+ display: grid;
+ place-items: center;
+}
+
+.rotate {
+ display: inline-flex;
+}
+.isRotating {
+ animation: rotating 3s infinite linear;
+}
+
+@keyframes rotating {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/components/rules/RuleProviderItem.tsx b/src/components/rules/RuleProviderItem.tsx
new file mode 100644
index 0000000..3b6d93d
--- /dev/null
+++ b/src/components/rules/RuleProviderItem.tsx
@@ -0,0 +1,71 @@
+import cx from 'clsx';
+import { formatDistance } from 'date-fns';
+import * as React from 'react';
+import { RotateCw } from 'react-feather';
+import { queryCache, useMutation } from 'react-query';
+import { refreshRuleProviderByName } from 'src/api/rule-provider';
+import Button from 'src/components/Button';
+import { SectionNameType } from 'src/components/shared/Basic';
+import { ClashAPIConfig } from 'src/types';
+
+import s from './RuleProviderItem.module.css';
+
+function useRefresh(
+ name: string,
+ apiConfig: ClashAPIConfig
+): [(ev: React.MouseEvent<HTMLButtonElement>) => unknown, boolean] {
+ const [mutate, { isLoading }] = useMutation(refreshRuleProviderByName, {
+ onSuccess: () => {
+ queryCache.invalidateQueries('/providers/rules');
+ },
+ });
+
+ const onClickRefreshButton = (ev: React.MouseEvent<HTMLButtonElement>) => {
+ ev.preventDefault();
+ mutate({ name, apiConfig });
+ };
+
+ return [onClickRefreshButton, isLoading];
+}
+
+function RotatableRotateCw({ isRotating }: { isRotating: boolean }) {
+ const cls = cx(s.rotate, {
+ [s.isRotating]: isRotating,
+ });
+ return (
+ <span className={cls}>
+ <RotateCw width={16} />
+ </span>
+ );
+}
+
+export function RuleProviderItem({
+ idx,
+ name,
+ vehicleType,
+ behavior,
+ updatedAt,
+ ruleCount,
+ apiConfig,
+}) {
+ const [onClickRefreshButton, isRefreshing] = useRefresh(name, apiConfig);
+ const timeAgo = formatDistance(new Date(updatedAt), new Date());
+ return (
+ <div className={s.RuleProviderItem}>
+ <span className={s.left}>{idx}</span>
+ <div className={s.middle}>
+ <SectionNameType name={name} type={`${vehicleType} / ${behavior}`} />
+ <div className={s.gray}>
+ {ruleCount < 2 ? `${ruleCount} rule` : `${ruleCount} rules`}
+ </div>
+ <small className={s.gray}>Updated {timeAgo} ago</small>
+ </div>
+ <span className={s.refreshButtonWrapper}>
+ <Button onClick={onClickRefreshButton} disabled={isRefreshing}>
+ <RotatableRotateCw isRotating={isRefreshing} />
+ </Button>
+ </span>
+ </div>
+ );
+}
+
diff --git a/src/components/rules/TextFilter.tsx b/src/components/rules/TextFilter.tsx
new file mode 100644
index 0000000..a3cc29e
--- /dev/null
+++ b/src/components/rules/TextFilter.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import { useTextInut } from 'src/hooks/useTextInput';
+import { ruleFilterText } from 'src/store/rules';
+
+import shared from '../shared.module.css';
+
+export function TextFilter() {
+ const [onChange, text] = useTextInut(ruleFilterText);
+ return (
+ <input
+ className={shared.input}
+ type="text"
+ value={text}
+ onChange={onChange}
+ placeholder="Filter"
+ />
+ );
+}