feat: support rule provider
This commit is contained in:
parent
55e928a87f
commit
32bed273c8
21 changed files with 539 additions and 213 deletions
17
package.json
17
package.json
|
@ -34,7 +34,7 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.3",
|
||||
"@hsjs/react-cache": "0.0.0-alpha.aa94237",
|
||||
"@sentry/browser": "^5.18.1",
|
||||
"@sentry/browser": "^5.19.0",
|
||||
"chart.js": "^2.9.2",
|
||||
"clsx": "^1.1.0",
|
||||
"core-js": "^3.6.2",
|
||||
|
@ -52,10 +52,11 @@
|
|||
"react-feather": "^2.0.3",
|
||||
"react-icons": "^3.10.0",
|
||||
"react-modal": "^3.11.1",
|
||||
"react-query": "^2.4.13",
|
||||
"react-router": "^6.0.0-alpha.1",
|
||||
"react-router-dom": "^6.0.0-alpha.1",
|
||||
"react-switch": "^5.0.1",
|
||||
"react-table": "^7.2.1",
|
||||
"react-table": "^7.2.2",
|
||||
"react-tabs": "^3.1.0",
|
||||
"react-tiny-fab": "^3.5.0",
|
||||
"react-window": "^1.8.5",
|
||||
|
@ -77,7 +78,9 @@
|
|||
"@babel/preset-typescript": "^7.10.1",
|
||||
"@hsjs/react-refresh-webpack-plugin": "^0.1.3",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.2.0",
|
||||
"@types/invariant": "^2.2.33",
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/lodash-es": "^4.17.3",
|
||||
"@types/react": "^16.9.41",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@typescript-eslint/eslint-plugin": "^3.4.0",
|
||||
|
@ -86,10 +89,10 @@
|
|||
"babel-eslint": "10.x",
|
||||
"babel-loader": "^8.0.5",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^6.0.2",
|
||||
"copy-webpack-plugin": "^6.0.3",
|
||||
"css-loader": "^3.4.2",
|
||||
"cssnano": "^4.1.7",
|
||||
"eslint": "^7.3.1",
|
||||
"eslint": "^7.4.0",
|
||||
"eslint-config-airbnb-base": "^14.1.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-config-react-app": "^5.2.1",
|
||||
|
@ -98,12 +101,12 @@
|
|||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-jest": "^23.17.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||
"eslint-plugin-react": "7.x",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^5.0.3",
|
||||
"file-loader": "^6.0.0",
|
||||
"fork-ts-checker-notifier-webpack-plugin": "^3.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^5.0.5",
|
||||
"fork-ts-checker-webpack-plugin": "^5.0.6",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"husky": "^4.0.0",
|
||||
"lint-staged": "^10.2.11",
|
||||
|
@ -120,7 +123,7 @@
|
|||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"ts-loader": "^7.0.5",
|
||||
"typescript": "^3.9.5",
|
||||
"typescript": "^3.9.6",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
|
|
74
src/api/rule-provider.ts
Normal file
74
src/api/rule-provider.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { getURLAndInit } from 'src/misc/request-helper';
|
||||
import { ClashAPIConfig } from 'src/types';
|
||||
|
||||
export type RuleProvider = RuleProviderAPIItem & { idx: number };
|
||||
|
||||
export type RuleProviderAPIItem = {
|
||||
behavior: string;
|
||||
name: string;
|
||||
ruleCount: number;
|
||||
type: 'Rule';
|
||||
// example value "2020-06-30T16:23:01.44143802+08:00"
|
||||
updatedAt: string;
|
||||
vehicleType: 'HTTP' | 'File';
|
||||
};
|
||||
|
||||
type RuleProviderAPIData = {
|
||||
providers: Record<string, RuleProviderAPIItem>;
|
||||
};
|
||||
|
||||
function normalizeAPIResponse(data: RuleProviderAPIData) {
|
||||
const providers = data.providers;
|
||||
const names = Object.keys(providers);
|
||||
const byName: Record<string, RuleProvider> = {};
|
||||
|
||||
// attach an idx to each item
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
byName[name] = { ...providers[name], idx: i };
|
||||
}
|
||||
|
||||
return { byName, names };
|
||||
}
|
||||
|
||||
export async function fetchRuleProviders(
|
||||
endpoint: string,
|
||||
apiConfig: ClashAPIConfig
|
||||
) {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
|
||||
let data = { providers: {} };
|
||||
try {
|
||||
const res = await fetch(url + endpoint, init);
|
||||
if (res.ok) {
|
||||
data = await res.json();
|
||||
}
|
||||
} catch (err) {
|
||||
// log and ignore
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('failed to GET /providers/rules', err);
|
||||
}
|
||||
return normalizeAPIResponse(data);
|
||||
}
|
||||
|
||||
export async function refreshRuleProviderByName({
|
||||
name,
|
||||
apiConfig,
|
||||
}: {
|
||||
name: string;
|
||||
apiConfig: ClashAPIConfig;
|
||||
}) {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
try {
|
||||
const res = await fetch(url + `/providers/rules/${name}`, {
|
||||
method: 'PUT',
|
||||
...init,
|
||||
});
|
||||
return res.ok;
|
||||
} catch (err) {
|
||||
// log and ignore
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('failed to PUT /providers/rules/:name', err);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { getURLAndInit } from '../misc/request-helper';
|
||||
|
||||
const endpoint = '/rules';
|
||||
|
||||
export async function fetchRules(apiConfig) {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
return await fetch(url + endpoint, init);
|
||||
}
|
41
src/api/rules.ts
Normal file
41
src/api/rules.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import invariant from 'invariant';
|
||||
import { getURLAndInit } from 'src/misc/request-helper';
|
||||
import { ClashAPIConfig } from 'src/types';
|
||||
|
||||
// const endpoint = '/rules';
|
||||
|
||||
type RuleItem = RuleAPIItem & { id: number };
|
||||
|
||||
type RuleAPIItem = {
|
||||
type: string;
|
||||
payload: string;
|
||||
proxy: string;
|
||||
};
|
||||
|
||||
function normalizeAPIResponse(json: {
|
||||
rules: Array<RuleAPIItem>;
|
||||
}): Array<RuleItem> {
|
||||
invariant(
|
||||
json.rules && json.rules.length >= 0,
|
||||
'there is no valid rules list in the rules API response'
|
||||
);
|
||||
|
||||
// attach an id
|
||||
return json.rules.map((r: RuleAPIItem, i: number) => ({ ...r, id: i }));
|
||||
}
|
||||
|
||||
export async function fetchRules(endpoint: string, apiConfig: ClashAPIConfig) {
|
||||
let json = { rules: [] };
|
||||
try {
|
||||
const { url, init } = getURLAndInit(apiConfig);
|
||||
const res = await fetch(url + endpoint, init);
|
||||
if (res.ok) {
|
||||
json = await res.json();
|
||||
}
|
||||
} catch (err) {
|
||||
// log and ignore
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('failed to fetch rules', err);
|
||||
}
|
||||
return normalizeAPIResponse(json);
|
||||
}
|
|
@ -43,6 +43,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.btnStart {
|
||||
margin-right: 5px;
|
||||
display: inline-flex;
|
||||
|
|
|
@ -16,6 +16,7 @@ type ButtonInternalProps = {
|
|||
type ButtonProps = {
|
||||
isLoading?: boolean;
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => unknown;
|
||||
disabled?: boolean;
|
||||
kind?: 'primary' | 'minimal';
|
||||
className?: string;
|
||||
} & ButtonInternalProps;
|
||||
|
@ -23,6 +24,7 @@ type ButtonProps = {
|
|||
function Button(props: ButtonProps, ref: React.Ref<HTMLButtonElement>) {
|
||||
const {
|
||||
onClick,
|
||||
disabled = false,
|
||||
isLoading,
|
||||
kind = 'primary',
|
||||
className,
|
||||
|
@ -43,7 +45,12 @@ function Button(props: ButtonProps, ref: React.Ref<HTMLButtonElement>) {
|
|||
className
|
||||
);
|
||||
return (
|
||||
<button className={btnClassName} ref={ref} onClick={internalOnClick}>
|
||||
<button
|
||||
className={btnClassName}
|
||||
ref={ref}
|
||||
onClick={internalOnClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<span
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
import { getSearchText, updateSearchText } from '../store/rules';
|
||||
import Search from './Search';
|
||||
import { connect } from './StateProvider';
|
||||
|
||||
const mapState = (s) => ({ searchText: getSearchText(s), updateSearchText });
|
||||
export default connect(mapState)(Search);
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -1 +1,21 @@
|
|||
/* */
|
||||
.header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr minmax(auto, 330px);
|
||||
align-items: center;
|
||||
|
||||
/*
|
||||
* the content header has some padding
|
||||
* we need to apply some right padding to this container then
|
||||
*/
|
||||
padding-right: 15px;
|
||||
@media (--breakpoint-not-small) {
|
||||
padding-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.RuleProviderItemWrapper {
|
||||
padding: 6px 15px;
|
||||
@media (--breakpoint-not-small) {
|
||||
padding: 10px 40px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import produce, * as immer from 'immer';
|
||||
import React from 'react';
|
||||
|
||||
// in logs store we update logs in place
|
||||
// outside of immer produce
|
||||
// this is just workaround
|
||||
immer.setAutoFreeze(false);
|
||||
|
||||
const {
|
||||
createContext,
|
||||
memo,
|
||||
|
|
|
@ -1,28 +1,11 @@
|
|||
import debounce from 'lodash-es/debounce';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useTextInut } from 'src/hooks/useTextInput';
|
||||
|
||||
import { proxyFilterText } from '../../store/proxies';
|
||||
import shared from '../shared.module.css';
|
||||
|
||||
const { useCallback, useState, useMemo } = React;
|
||||
|
||||
export function TextFilter() {
|
||||
const [, setTextGlobal] = useRecoilState(proxyFilterText);
|
||||
const [text, setText] = useState('');
|
||||
|
||||
const setTextDebounced = useMemo(() => debounce(setTextGlobal, 300), [
|
||||
setTextGlobal,
|
||||
]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setText(e.target.value);
|
||||
setTextDebounced(e.target.value);
|
||||
},
|
||||
[setTextDebounced]
|
||||
);
|
||||
|
||||
const [onChange, text] = useTextInut(proxyFilterText);
|
||||
return (
|
||||
<input
|
||||
className={shared.input}
|
||||
|
|
43
src/components/rules/RuleProviderItem.module.css
Normal file
43
src/components/rules/RuleProviderItem.module.css
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
71
src/components/rules/RuleProviderItem.tsx
Normal file
71
src/components/rules/RuleProviderItem.tsx
Normal file
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
18
src/components/rules/TextFilter.tsx
Normal file
18
src/components/rules/TextFilter.tsx
Normal file
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
23
src/hooks/useTextInput.ts
Normal file
23
src/hooks/useTextInput.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import debounce from 'lodash-es/debounce';
|
||||
import * as React from 'react';
|
||||
import { RecoilState, useRecoilState } from 'recoil';
|
||||
|
||||
const { useCallback, useState, useMemo } = React;
|
||||
|
||||
export function useTextInut(
|
||||
x: RecoilState<string>
|
||||
): [(e: React.ChangeEvent<HTMLInputElement>) => void, string] {
|
||||
const [, setTextGlobal] = useRecoilState(x);
|
||||
const [text, setText] = useState('');
|
||||
const setTextDebounced = useMemo(() => debounce(setTextGlobal, 300), [
|
||||
setTextGlobal,
|
||||
]);
|
||||
const onChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setText(e.target.value);
|
||||
setTextDebounced(e.target.value);
|
||||
},
|
||||
[setTextDebounced]
|
||||
);
|
||||
return [onChange, text];
|
||||
}
|
|
@ -8,14 +8,12 @@ import { initialState as configs } from './configs';
|
|||
import { initialState as logs } from './logs';
|
||||
import { initialState as modals } from './modals';
|
||||
import { actions as proxiesActions, initialState as proxies } from './proxies';
|
||||
import { initialState as rules } from './rules';
|
||||
|
||||
export const initialState = {
|
||||
app: app(),
|
||||
modals,
|
||||
configs,
|
||||
proxies,
|
||||
rules,
|
||||
logs,
|
||||
};
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ import { createSelector } from 'reselect';
|
|||
|
||||
const LogSize = 300;
|
||||
|
||||
const getLogs = s => s.logs.logs;
|
||||
const getTail = s => s.logs.tail;
|
||||
export const getSearchText = s => s.logs.searchText;
|
||||
const getLogs = (s) => s.logs.logs;
|
||||
const getTail = (s) => s.logs.tail;
|
||||
export const getSearchText = (s) => s.logs.searchText;
|
||||
export const getLogsForDisplay = createSelector(
|
||||
getLogs,
|
||||
getTail,
|
||||
|
@ -21,13 +21,13 @@ export const getLogsForDisplay = createSelector(
|
|||
}
|
||||
|
||||
if (searchText === '') return x;
|
||||
return x.filter(r => r.payload.toLowerCase().indexOf(searchText) >= 0);
|
||||
return x.filter((r) => r.payload.toLowerCase().indexOf(searchText) >= 0);
|
||||
}
|
||||
);
|
||||
|
||||
export function updateSearchText(text) {
|
||||
return dispatch => {
|
||||
dispatch('logsUpdateSearchText', s => {
|
||||
return (dispatch) => {
|
||||
dispatch('logsUpdateSearchText', (s) => {
|
||||
s.logs.searchText = text.toLowerCase();
|
||||
});
|
||||
};
|
||||
|
@ -42,7 +42,7 @@ export function appendLog(log) {
|
|||
// mutate intentionally for performance
|
||||
logs[tail] = log;
|
||||
|
||||
dispatch('logsAppendLog', s => {
|
||||
dispatch('logsAppendLog', (s) => {
|
||||
s.logs.tail = tail;
|
||||
});
|
||||
};
|
||||
|
@ -52,5 +52,5 @@ export const initialState = {
|
|||
searchText: '',
|
||||
logs: [],
|
||||
// tail's initial value must be -1
|
||||
tail: -1
|
||||
tail: -1,
|
||||
};
|
||||
|
|
|
@ -1,58 +1,6 @@
|
|||
import invariant from 'invariant';
|
||||
import { createSelector } from 'reselect';
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import * as rulesAPI from '../api/rules';
|
||||
|
||||
export const getAllRules = (s) => s.rules.allRules;
|
||||
export const getSearchText = (s) => s.rules.searchText;
|
||||
export const getRules = createSelector(
|
||||
getSearchText,
|
||||
getAllRules,
|
||||
(searchText, allRules) => {
|
||||
if (searchText === '') return allRules;
|
||||
return allRules.filter((r) => r.payload.indexOf(searchText) >= 0);
|
||||
}
|
||||
);
|
||||
export function updateSearchText(text) {
|
||||
return (dispatch) => {
|
||||
dispatch('rulesUpdateSearchText', (s) => {
|
||||
s.rules.searchText = text.toLowerCase();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRules(apiConfig) {
|
||||
return async (dispatch) => {
|
||||
const res = await rulesAPI.fetchRules(apiConfig);
|
||||
const json = await res.json();
|
||||
invariant(
|
||||
json.rules && json.rules.length >= 0,
|
||||
'there is no valid rules list in the rules API response'
|
||||
);
|
||||
|
||||
// attach an id
|
||||
const allRules = json.rules.map((r, i) => {
|
||||
r.id = i;
|
||||
return r;
|
||||
});
|
||||
|
||||
dispatch('rulesFetchRules', (s) => {
|
||||
s.rules.allRules = allRules;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRulesOnce(apiConfig) {
|
||||
return async (dispatch, getState) => {
|
||||
const allRules = getAllRules(getState());
|
||||
if (allRules.length === 0) return await dispatch(fetchRules(apiConfig));
|
||||
};
|
||||
}
|
||||
|
||||
// {"type":"FINAL","payload":"","proxy":"Proxy"}
|
||||
// {"type":"IPCIDR","payload":"172.16.0.0/12","proxy":"DIRECT"}
|
||||
export const initialState = {
|
||||
// filteredRules: [],
|
||||
allRules: [],
|
||||
searchText: '',
|
||||
};
|
||||
export const ruleFilterText = atom({
|
||||
key: 'ruleFilterText',
|
||||
default: '',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export type ClashAPIConfig = {
|
||||
hostname: string;
|
||||
port: number;
|
||||
secret?: string;
|
||||
};
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"allowSyntheticDefaultImports": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "es5",
|
||||
"downlevelIteration": true,
|
||||
"importHelpers": true,
|
||||
|
@ -15,7 +16,7 @@
|
|||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
|
|
185
yarn.lock
185
yarn.lock
|
@ -1176,61 +1176,56 @@
|
|||
style-value-types "^3.1.7"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@scarf/scarf@^1.0.4":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.0.6.tgz#52011dfb19187b53b75b7b6eac20da0810ddd88f"
|
||||
integrity sha512-y4+DuXrAd1W5UIY3zTcsosi/1GyYT8k5jGnZ/wG7UUHVrU+MHlH4Mp87KK2/lvMW4+H7HVcdB+aJhqywgXksjA==
|
||||
|
||||
"@sentry/browser@^5.18.1":
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.18.1.tgz#6a2ebe8f4ba88b2e98ccc91442e1dcb37b2df988"
|
||||
integrity sha512-U1w0d5kRMsfzMYwWn4+awDKfBEI5lxhHa0bMChSpj5z/dWiz/e0mikZ9gCoF+ZNqkXJ92l/3r9gRz+SIsn5ZoA==
|
||||
"@sentry/browser@^5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.19.0.tgz#9189b6633fe45e54325e40b39345d9eabd171e4a"
|
||||
integrity sha512-Cz8PnzC5NGfpHIGCmHLgA6iDEBVELwo4Il/iFweXjs4+VL4biw62lI32Q4iLCCpmX0t5UvrWBOnju2v8zuFIjA==
|
||||
dependencies:
|
||||
"@sentry/core" "5.18.1"
|
||||
"@sentry/types" "5.18.1"
|
||||
"@sentry/utils" "5.18.1"
|
||||
"@sentry/core" "5.19.0"
|
||||
"@sentry/types" "5.19.0"
|
||||
"@sentry/utils" "5.19.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@5.18.1":
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.18.1.tgz#c2aa7ef9054e372d006d32234969711234d2bb02"
|
||||
integrity sha512-nC2aK6gwVIBVysmtdFHxYJyuChIHtkv7TnvmwgA5788L/HWo7E3R+Rd8Tf2npvp/aP+kmNITNbc5CIIqwGPaqQ==
|
||||
"@sentry/core@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.19.0.tgz#31b08a0b46ae1ee6863447225b401ac2a777774c"
|
||||
integrity sha512-ry1Zms6jrVQPEwmfywItyUhOgabPrykd8stR1x/u2t1YiISWlR813fE5nzdwgW5mxEitXz/ikTwvrfK3egsDWQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.18.1"
|
||||
"@sentry/minimal" "5.18.1"
|
||||
"@sentry/types" "5.18.1"
|
||||
"@sentry/utils" "5.18.1"
|
||||
"@sentry/hub" "5.19.0"
|
||||
"@sentry/minimal" "5.19.0"
|
||||
"@sentry/types" "5.19.0"
|
||||
"@sentry/utils" "5.19.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@5.18.1":
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.18.1.tgz#4c2f642e29a320885692b902fba89e57a9906e64"
|
||||
integrity sha512-dFnaj1fQRT74EhoF8MXJ23K3svt11zEF6CS3cdMrkSzfRbAHjyza7KT2AJHUeF6gtH2BZzqsSw+FnfAke0HGIg==
|
||||
"@sentry/hub@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.19.0.tgz#f38e7745a4980d9fa6c5baeca5605e7e6fecd5ac"
|
||||
integrity sha512-UFaQLa1XAa02ZcxUmN9GdDpGs3vHK1LpOyYooimX8ttWa4KAkMuP+O5uXH1TJabry6o/cRwYisg2k6PBSy8gxw==
|
||||
dependencies:
|
||||
"@sentry/types" "5.18.1"
|
||||
"@sentry/utils" "5.18.1"
|
||||
"@sentry/types" "5.19.0"
|
||||
"@sentry/utils" "5.19.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@5.18.1":
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.18.1.tgz#8de01e87c5f5c6e74b707849202150cd4b316ee0"
|
||||
integrity sha512-St2bjcZ5FFiH+bYkWoEPlEb0w38YSvftnjJTvZyk05SCdsF7HkGfoBeFmztwBf1VLQPYt3ojny14L6KDAvOTpw==
|
||||
"@sentry/minimal@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.19.0.tgz#aa5a700618608ea79d270280fe77f04bbb44ed5a"
|
||||
integrity sha512-3FHgirwOuOMF4VlwHboYObPT9c0S9b9y5FW0DGgNt8sJPezS00VaJti/38ZwOHQJ4fJ6ubt/Y3Kz0eBTVxMCCQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.18.1"
|
||||
"@sentry/types" "5.18.1"
|
||||
"@sentry/hub" "5.19.0"
|
||||
"@sentry/types" "5.19.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@5.18.1":
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.18.1.tgz#9d72254262f28e966b06371c5b3833de8f0253b8"
|
||||
integrity sha512-y5YTkRFC4Y7r4GHrvin6aZLBpQIGdMZRq78f/s7IIEZrmWYbVKsK4dyJht6pOsUdEaxeYpsu3okIA0bqmthSJA==
|
||||
"@sentry/types@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.19.0.tgz#30c6a05040b644d90337ef8b50d9d59c0872744d"
|
||||
integrity sha512-NlHLS9mwCEimKUA5vClAwVhXFLsqSF3VJEXU+K61fF6NZx8zfJi/HTrIBtoM4iNSAt9o4XLQatC1liIpBC06tg==
|
||||
|
||||
"@sentry/utils@5.18.1":
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.18.1.tgz#c9880056793ae77d651db0dae76a08a8a0b31eac"
|
||||
integrity sha512-P4lt6NauCBWASaP6R5kfOmc24imbD32G5FeWqK7vHftIphOJ0X7OZfh93DJPs3e5RIvW3YCywUsa7MpTH5/ClA==
|
||||
"@sentry/utils@5.19.0":
|
||||
version "5.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.19.0.tgz#0e01f84aa67a1cf2ec71faab670230918ea7e9aa"
|
||||
integrity sha512-EU/T9aJrI8isexRnyDx5InNw/HjSQ0nKI7YWdiwfFrJusqQ/uisnCGK7vw6gGHDgiAHMXW3TJ38NHpNBin6y7Q==
|
||||
dependencies:
|
||||
"@sentry/types" "5.18.1"
|
||||
"@sentry/types" "5.19.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@types/anymatch@*":
|
||||
|
@ -1267,6 +1262,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz#7532440c138605ced1b555935c3115ddd20e8bef"
|
||||
integrity sha512-q95SP4FdkmF0CwO0F2q0H6ZgudsApaY/yCtAQNRn1gduef5fGpyEphzy0YCq/N0UFvDSnLg5V8jFK/YGXlDiCw==
|
||||
|
||||
"@types/invariant@^2.2.33":
|
||||
version "2.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.33.tgz#ec5eec29c63bf5e4ca164e9feb3ef7337cdcbadb"
|
||||
integrity sha512-/jUNmS8d4bCKdqslfxW6dg/9Gksfzxz67IYfqApHn+HvHlMVXwYv2zpTDnS/yaK9BB0i0GlBTaYci0EFE62Hmw==
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz#79d7a78bad4219f4c03d6557a1c72d9ca6ba62d5"
|
||||
|
@ -1305,6 +1305,18 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/lodash-es@^4.17.3":
|
||||
version "4.17.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.3.tgz#87eb0b3673b076b8ee655f1890260a136af09a2d"
|
||||
integrity sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*":
|
||||
version "4.14.157"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8"
|
||||
integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
|
@ -2737,21 +2749,21 @@ copy-descriptor@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||
|
||||
copy-webpack-plugin@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.0.2.tgz#10efc6ad219a61acbf2f5fb50af83da38431bc34"
|
||||
integrity sha512-9Gm8X0c6eXlKnmltMPFCBeGOKjtcRIyTt4VaO3k1TkNgVTe5Ov2lYsYVuyLp0kp8DItO3apewflM+1GYgh6V2Q==
|
||||
copy-webpack-plugin@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.0.3.tgz#2b3d2bfc6861b96432a65f0149720adbd902040b"
|
||||
integrity sha512-q5m6Vz4elsuyVEIUXr7wJdIdePWTubsqVbEMvf1WQnHGv0Q+9yPRu7MtYFPt+GBOXRav9lvIINifTQ1vSCs+eA==
|
||||
dependencies:
|
||||
cacache "^15.0.4"
|
||||
fast-glob "^3.2.2"
|
||||
fast-glob "^3.2.4"
|
||||
find-cache-dir "^3.3.1"
|
||||
glob-parent "^5.1.1"
|
||||
globby "^11.0.1"
|
||||
loader-utils "^2.0.0"
|
||||
normalize-path "^3.0.0"
|
||||
p-limit "^2.3.0"
|
||||
p-limit "^3.0.1"
|
||||
schema-utils "^2.7.0"
|
||||
serialize-javascript "^3.1.0"
|
||||
serialize-javascript "^4.0.0"
|
||||
webpack-sources "^1.4.3"
|
||||
|
||||
core-js-compat@^3.6.2:
|
||||
|
@ -3616,10 +3628,10 @@ eslint-plugin-react-hooks@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.5.tgz#4879003aa38e5d05d0312175beb6e4a1f617bfcf"
|
||||
integrity sha512-3YLSjoArsE2rUwL8li4Yxx1SUg3DQWp+78N3bcJQGWVZckcp+yeQGsap/MSq05+thJk57o+Ww4PtZukXGL02TQ==
|
||||
|
||||
eslint-plugin-react@7.x:
|
||||
version "7.20.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.2.tgz#b0d72abcd94c59c842338aa09c800808219ea77d"
|
||||
integrity sha512-J3BdtsPNbcF/CG9HdyLx7jEtC7tuODODGldkS9P1zU2WMoHPdcsN2enUopgIaec5f9eYhSFI5zQAaWA/dgv2zw==
|
||||
eslint-plugin-react@^7.20.3:
|
||||
version "7.20.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.3.tgz#0590525e7eb83890ce71f73c2cf836284ad8c2f1"
|
||||
integrity sha512-txbo090buDeyV0ugF3YMWrzLIUqpYTsWSDZV9xLSmExE1P/Kmgg9++PD931r+KEWS66O1c9R4srLVVHmeHpoAg==
|
||||
dependencies:
|
||||
array-includes "^3.1.1"
|
||||
array.prototype.flatmap "^1.2.3"
|
||||
|
@ -3676,10 +3688,10 @@ eslint-visitor-keys@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa"
|
||||
integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==
|
||||
|
||||
eslint@^7.3.1:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.1.tgz#76392bd7e44468d046149ba128d1566c59acbe19"
|
||||
integrity sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==
|
||||
eslint@^7.4.0:
|
||||
version "7.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f"
|
||||
integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
|
@ -3917,7 +3929,7 @@ fast-glob@^2.0.2:
|
|||
merge2 "^1.2.3"
|
||||
micromatch "^3.1.10"
|
||||
|
||||
fast-glob@^3.1.1, fast-glob@^3.2.2:
|
||||
fast-glob@^3.1.1:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.2.tgz#ade1a9d91148965d4bf7c51f72e1ca662d32e63d"
|
||||
integrity sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==
|
||||
|
@ -3929,6 +3941,18 @@ fast-glob@^3.1.1, fast-glob@^3.2.2:
|
|||
micromatch "^4.0.2"
|
||||
picomatch "^2.2.1"
|
||||
|
||||
fast-glob@^3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
|
||||
integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.0"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.2"
|
||||
picomatch "^2.2.1"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
|
@ -4137,10 +4161,10 @@ fork-ts-checker-webpack-plugin@1.5.0:
|
|||
tapable "^1.0.0"
|
||||
worker-rpc "^0.1.0"
|
||||
|
||||
fork-ts-checker-webpack-plugin@^5.0.5:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.5.tgz#4cf77f925008464db4670750a6e9266a4d25a988"
|
||||
integrity sha512-2vpP+irspLOpZ1pcH3K3yR76MWXVSOYKTGwhwFQGnWjOs5MxzPN+dyDh1tNoaD5DUFP5kr4lGQURE9Qk+DpiUQ==
|
||||
fork-ts-checker-webpack-plugin@^5.0.6:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.6.tgz#02af713af02e47b338a3992279209bc65d34c773"
|
||||
integrity sha512-8h4S7WANr69Resw+tMd5U23xdbVPsT+VOeMKQhUaKhkGeMm0fNf7ObJPP81WG+tsmy4LK4ayIf0JXBY+hy6vMw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.8.3"
|
||||
chalk "^2.4.1"
|
||||
|
@ -6144,7 +6168,7 @@ p-limit@^1.1.0:
|
|||
dependencies:
|
||||
p-try "^1.0.0"
|
||||
|
||||
p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0:
|
||||
p-limit@^2.0.0, p-limit@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
|
||||
|
@ -7136,6 +7160,13 @@ react-modal@^3.11.1:
|
|||
react-lifecycles-compat "^3.0.0"
|
||||
warning "^4.0.3"
|
||||
|
||||
react-query@^2.4.13:
|
||||
version "2.4.13"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.4.13.tgz#478f4f32a8c6b56de0e7bb6eb3cd2baa812e1ebb"
|
||||
integrity sha512-PFUmChSy3kph+r0c+oz7HgBiKi98bb1D8FeeBZ1qiuuGXUJYhWCvOST+lOgwtLiBbT1wdoNjObFC35jpHbSvUQ==
|
||||
dependencies:
|
||||
ts-toolbelt "^6.9.4"
|
||||
|
||||
react-refresh@^0.8.2:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
||||
|
@ -7164,12 +7195,10 @@ react-switch@^5.0.1:
|
|||
dependencies:
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-table@^7.2.1:
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.2.1.tgz#d85f5a59b6441ccd9e0acddc93c288c54a7de7ee"
|
||||
integrity sha512-cICU3w5yFWTDluz9yFePziEQXGt3PK5R1Va3InP7qMGnZOKm8kv0GiDMli+VOqE1uM1dBYPb9BEad15up1ac6g==
|
||||
dependencies:
|
||||
"@scarf/scarf" "^1.0.4"
|
||||
react-table@^7.2.2:
|
||||
version "7.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.2.2.tgz#2978a903f0e367e1ceab25d60a6a40ae23498f77"
|
||||
integrity sha512-JBE3QUyRQJkHRU72NKo7HRGtSGcrzIcVLDbsoZI+KhWkFEZ2t9UfpqeHBdbc3R5RRJZ6qC7+NzZZxJPSYWKPBQ==
|
||||
|
||||
react-tabs@^3.1.0:
|
||||
version "3.1.1"
|
||||
|
@ -7654,13 +7683,6 @@ serialize-javascript@^2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
|
||||
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
|
||||
|
||||
serialize-javascript@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea"
|
||||
integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-javascript@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
||||
|
@ -8418,6 +8440,11 @@ ts-loader@^7.0.5:
|
|||
micromatch "^4.0.0"
|
||||
semver "^6.0.0"
|
||||
|
||||
ts-toolbelt@^6.9.4:
|
||||
version "6.9.9"
|
||||
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz#e6cfd8ec7d425d2a06bda3b4fe9577ceaf2abda8"
|
||||
integrity sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg==
|
||||
|
||||
tsconfig-paths@^3.9.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
|
||||
|
@ -8485,10 +8512,10 @@ typeface-roboto-mono@^0.0.75:
|
|||
resolved "https://registry.yarnpkg.com/typeface-roboto-mono/-/typeface-roboto-mono-0.0.75.tgz#00c4543662066a837c014c251ee5140fb66a5ad8"
|
||||
integrity sha512-dYfyXd6HrKyMC/PuBAAtay0tZKsBrzxIW/fBY325vLxFfi/IDKSuyTkWxkU4lyZV6KPHetFnJ661PNXzz2FS/w==
|
||||
|
||||
typescript@^3.9.5:
|
||||
version "3.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
|
||||
integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
|
||||
typescript@^3.9.6:
|
||||
version "3.9.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
|
||||
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^1.0.4:
|
||||
version "1.0.4"
|
||||
|
|
Loading…
Reference in a new issue