feat: support proxy provider
This commit is contained in:
parent
040c5de04a
commit
d81592ec97
22 changed files with 935 additions and 304 deletions
|
@ -41,7 +41,9 @@
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"core-js": "^3.4.5",
|
"core-js": "^3.4.5",
|
||||||
"date-fns": "^2.8.1",
|
"date-fns": "^2.8.1",
|
||||||
|
"framer-motion": "^1.7.0",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
|
"immer": "^5.0.1",
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
"lodash-es": "^4.17.14",
|
"lodash-es": "^4.17.14",
|
||||||
"memoize-one": "^5.1.1",
|
"memoize-one": "^5.1.1",
|
||||||
|
@ -97,6 +99,7 @@
|
||||||
"postcss-nested": "^4.2.0",
|
"postcss-nested": "^4.2.0",
|
||||||
"prettier": "^1.17.1",
|
"prettier": "^1.17.1",
|
||||||
"react-hot-loader": "^4.12.18",
|
"react-hot-loader": "^4.12.18",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"style-loader": "^1.0.1",
|
"style-loader": "^1.0.1",
|
||||||
"svg-sprite-loader": "^4.1.2",
|
"svg-sprite-loader": "^4.1.2",
|
||||||
"terser-webpack-plugin": "^2.2.1",
|
"terser-webpack-plugin": "^2.2.1",
|
||||||
|
|
|
@ -18,13 +18,13 @@ Vary: Origin
|
||||||
Date: Tue, 16 Oct 2018 16:38:33 GMT
|
Date: Tue, 16 Oct 2018 16:38:33 GMT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function fetchProxies(config) {
|
export async function fetchProxies(config) {
|
||||||
const { url, init } = getURLAndInit(config);
|
const { url, init } = getURLAndInit(config);
|
||||||
const res = await fetch(url + endpoint, init);
|
const res = await fetch(url + endpoint, init);
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestToSwitchProxy(apiConfig, name1, name2) {
|
export async function requestToSwitchProxy(apiConfig, name1, name2) {
|
||||||
const body = { name: name2 };
|
const body = { name: name2 };
|
||||||
const { url, init } = getURLAndInit(apiConfig);
|
const { url, init } = getURLAndInit(apiConfig);
|
||||||
const fullURL = `${url}${endpoint}/${name1}`;
|
const fullURL = `${url}${endpoint}/${name1}`;
|
||||||
|
@ -35,11 +35,27 @@ async function requestToSwitchProxy(apiConfig, name1, name2) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestDelayForProxy(apiConfig, name) {
|
export async function requestDelayForProxy(apiConfig, name) {
|
||||||
const { url, init } = getURLAndInit(apiConfig);
|
const { url, init } = getURLAndInit(apiConfig);
|
||||||
const qs = 'timeout=5000&url=http://www.google.com/generate_204';
|
const qs = 'timeout=5000&url=http://www.google.com/generate_204';
|
||||||
const fullURL = `${url}${endpoint}/${name}/delay?${qs}`;
|
const fullURL = `${url}${endpoint}/${name}/delay?${qs}`;
|
||||||
return await fetch(fullURL, init);
|
return await fetch(fullURL, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { fetchProxies, requestToSwitchProxy, requestDelayForProxy };
|
export async function fetchProviderProxies(config) {
|
||||||
|
const { url, init } = getURLAndInit(config);
|
||||||
|
const res = await fetch(url + '/providers/proxies', init);
|
||||||
|
if (res.status === 404) {
|
||||||
|
return { providers: {} };
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProviderByName(config, name) {
|
||||||
|
const { url, init } = getURLAndInit(config);
|
||||||
|
const options = {
|
||||||
|
...init,
|
||||||
|
method: 'PUT'
|
||||||
|
};
|
||||||
|
return await fetch(url + '/providers/proxies/' + name, options);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import cx from 'classnames';
|
||||||
|
|
||||||
import s0 from 'c/Button.module.css';
|
import s0 from 'c/Button.module.css';
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
@ -13,6 +14,14 @@ function Button({ children, label, onClick = noop }, ref) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ButtonPlain({ children, label, onClick = noop }) {
|
||||||
|
return (
|
||||||
|
<button className={cx(s0.btn, s0.plain)} onClick={onClick}>
|
||||||
|
{children || label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function WithIcon({ text, icon, onClick = noop }, ref) {
|
function WithIcon({ text, icon, onClick = noop }, ref) {
|
||||||
return (
|
return (
|
||||||
<button className={s0.btn} ref={ref} onClick={onClick}>
|
<button className={s0.btn} ref={ref} onClick={onClick}>
|
||||||
|
|
|
@ -24,6 +24,22 @@
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.plain {
|
||||||
|
border-radius: 100%;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
border-color: transparent;
|
||||||
|
background: none;
|
||||||
|
&:focus {
|
||||||
|
border-color: var(--color-focus-blue);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: #387cec;
|
||||||
|
border: 1px solid #387cec;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.withIconWrapper {
|
.withIconWrapper {
|
||||||
|
|
|
@ -1,63 +1,81 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useActions, useStoreState } from 'm/store';
|
import { useStoreState } from 'm/store';
|
||||||
|
|
||||||
import ContentHeader from 'c/ContentHeader';
|
import { connect } from './StateProvider';
|
||||||
import ProxyGroup from 'c/ProxyGroup';
|
|
||||||
import { ButtonWithIcon } from 'c/Button';
|
import ContentHeader from './ContentHeader';
|
||||||
|
import ProxyGroup from './ProxyGroup';
|
||||||
|
import { ButtonWithIcon } from './Button';
|
||||||
import { Zap } from 'react-feather';
|
import { Zap } from 'react-feather';
|
||||||
|
|
||||||
import s0 from 'c/Proxies.module.css';
|
import ProxyProviderList from './ProxyProviderList';
|
||||||
|
|
||||||
|
import s0 from './Proxies.module.css';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getProxies,
|
getProxies,
|
||||||
|
getDelay,
|
||||||
getProxyGroupNames,
|
getProxyGroupNames,
|
||||||
|
getProxyProviders,
|
||||||
fetchProxies,
|
fetchProxies,
|
||||||
requestDelayAll
|
requestDelayAll
|
||||||
} from 'd/proxies';
|
} from '../store/proxies';
|
||||||
|
|
||||||
const { useEffect, useMemo } = React;
|
import { getClashAPIConfig } from '../ducks/app';
|
||||||
|
|
||||||
|
const { useEffect, useMemo, useCallback } = React;
|
||||||
|
|
||||||
const mapStateToProps = s => ({
|
const mapStateToProps = s => ({
|
||||||
proxies: getProxies(s),
|
apiConfig: getClashAPIConfig(s)
|
||||||
groupNames: getProxyGroupNames(s)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
function Proxies({ dispatch, groupNames, proxies, delay, proxyProviders }) {
|
||||||
fetchProxies,
|
const { apiConfig } = useStoreState(mapStateToProps);
|
||||||
requestDelayAll
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Proxies() {
|
|
||||||
const { fetchProxies, requestDelayAll } = useActions(actions);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
dispatch(fetchProxies(apiConfig));
|
||||||
await fetchProxies();
|
}, [dispatch, apiConfig]);
|
||||||
// await requestDelayAll();
|
const requestDelayAllFn = useCallback(
|
||||||
})();
|
() => dispatch(requestDelayAll(apiConfig)),
|
||||||
}, [fetchProxies, requestDelayAll]);
|
[apiConfig, dispatch]
|
||||||
const { groupNames } = useStoreState(mapStateToProps);
|
);
|
||||||
const icon = useMemo(() => <Zap width={16} />, []);
|
const icon = useMemo(() => <Zap width={16} />, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContentHeader title="Proxies" />
|
<ContentHeader title="Proxies" />
|
||||||
<div className={s0.body}>
|
<div>
|
||||||
<div className="fabgrp">
|
<div className="fabgrp">
|
||||||
<ButtonWithIcon
|
<ButtonWithIcon
|
||||||
text="Test Latency"
|
text="Test Latency"
|
||||||
icon={icon}
|
icon={icon}
|
||||||
onClick={requestDelayAll}
|
onClick={requestDelayAllFn}
|
||||||
/>
|
/>
|
||||||
{/* <Button onClick={requestDelayAll}>Test Latency</Button> */}
|
|
||||||
</div>
|
</div>
|
||||||
{groupNames.map(groupName => {
|
{groupNames.map(groupName => {
|
||||||
return (
|
return (
|
||||||
<div className={s0.group} key={groupName}>
|
<div className={s0.group} key={groupName}>
|
||||||
<ProxyGroup name={groupName} />
|
<ProxyGroup
|
||||||
|
name={groupName}
|
||||||
|
proxies={proxies}
|
||||||
|
delay={delay}
|
||||||
|
apiConfig={apiConfig}
|
||||||
|
dispatch={dispatch}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<ProxyProviderList items={proxyProviders} />
|
||||||
|
<div style={{ height: 60 }} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapState = s => ({
|
||||||
|
groupNames: getProxyGroupNames(s),
|
||||||
|
proxies: getProxies(s),
|
||||||
|
proxyProviders: getProxyProviders(s),
|
||||||
|
delay: getDelay(s)
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapState)(Proxies);
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
.body {
|
|
||||||
padding-bottom: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group {
|
.group {
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
@media (--breakpoint-not-small) {
|
@media (--breakpoint-not-small) {
|
||||||
|
|
|
@ -1,13 +1,36 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { useStoreState } from 'm/store';
|
|
||||||
|
|
||||||
import ProxyLatency from 'c/ProxyLatency';
|
import { connect } from './StateProvider';
|
||||||
|
import ProxyLatency from './ProxyLatency';
|
||||||
|
|
||||||
|
import { getProxies, getDelay } from '../store/proxies';
|
||||||
|
|
||||||
import s0 from './Proxy.module.css';
|
import s0 from './Proxy.module.css';
|
||||||
|
|
||||||
import { getDelay, getProxies } from 'd/proxies';
|
const { useMemo } = React;
|
||||||
|
|
||||||
|
const colorMap = {
|
||||||
|
// green
|
||||||
|
good: '#67c23a',
|
||||||
|
// yellow
|
||||||
|
normal: '#d4b75c',
|
||||||
|
// orange
|
||||||
|
bad: '#e67f3c',
|
||||||
|
// bad: '#F56C6C',
|
||||||
|
na: '#909399'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLabelColor({ number, error } = {}) {
|
||||||
|
if (number < 200) {
|
||||||
|
return colorMap.good;
|
||||||
|
} else if (number < 400) {
|
||||||
|
return colorMap.normal;
|
||||||
|
} else if (typeof number === 'number') {
|
||||||
|
return colorMap.bad;
|
||||||
|
}
|
||||||
|
return colorMap.na;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const colors = {
|
const colors = {
|
||||||
|
@ -22,18 +45,28 @@ const colors = {
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const mapStateToProps = s => {
|
type ProxyProps = {
|
||||||
return {
|
name: string,
|
||||||
proxies: getProxies(s),
|
now?: boolean,
|
||||||
delay: getDelay(s)
|
|
||||||
};
|
// connect injected
|
||||||
|
// TODO refine type
|
||||||
|
proxy: any,
|
||||||
|
latency: any
|
||||||
};
|
};
|
||||||
|
|
||||||
function Proxy({ now, name }) {
|
function ProxySmallImpl({ now, name, proxy, latency }: ProxyProps) {
|
||||||
const { proxies, delay } = useStoreState(mapStateToProps);
|
const color = useMemo(() => getLabelColor(latency), [latency]);
|
||||||
const latency = delay[name];
|
return (
|
||||||
const proxy = proxies[name];
|
<div
|
||||||
|
className={cx(s0.proxySmall, { [s0.now]: now })}
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Proxy({ now, name, proxy, latency }: ProxyProps) {
|
||||||
|
const color = useMemo(() => getLabelColor(latency), [latency]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(s0.proxy, {
|
className={cx(s0.proxy, {
|
||||||
|
@ -46,14 +79,22 @@ function Proxy({ now, name }) {
|
||||||
{proxy.type}
|
{proxy.type}
|
||||||
</div>
|
</div>
|
||||||
<div className={s0.proxyLatencyWrap}>
|
<div className={s0.proxyLatencyWrap}>
|
||||||
{latency && latency.number ? <ProxyLatency latency={latency} /> : null}
|
{latency && latency.number ? (
|
||||||
|
<ProxyLatency number={latency.number} color={color} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Proxy.propTypes = {
|
|
||||||
now: PropTypes.bool,
|
const mapState = (s, { name }) => {
|
||||||
name: PropTypes.string
|
const proxies = getProxies(s);
|
||||||
|
const delay = getDelay(s);
|
||||||
|
return {
|
||||||
|
proxy: proxies[name],
|
||||||
|
latency: delay[name]
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Proxy;
|
export default connect(mapState)(Proxy);
|
||||||
|
export const ProxySmall = connect(mapState)(ProxySmallImpl);
|
||||||
|
|
|
@ -2,10 +2,15 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
max-width: 280px;
|
||||||
@media (--breakpoint-not-small) {
|
@media (--breakpoint-not-small) {
|
||||||
|
min-width: 150px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
background-color: var(--color-bg-proxy-selected);
|
background-color: var(--color-bg-proxy-selected);
|
||||||
&.now {
|
&.now {
|
||||||
background-color: var(--color-focus-blue);
|
background-color: var(--color-focus-blue);
|
||||||
|
@ -40,3 +45,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.proxySmall {
|
||||||
|
.now {
|
||||||
|
outline: pink solid 1px;
|
||||||
|
}
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,60 +1,108 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { useActions, useStoreState } from 'm/store';
|
|
||||||
|
|
||||||
import Proxy from 'c/Proxy';
|
import Proxy, { ProxySmall } from './Proxy';
|
||||||
|
import { SectionNameType } from './shared/Basic';
|
||||||
|
|
||||||
import s0 from './ProxyGroup.module.css';
|
import s0 from './ProxyGroup.module.css';
|
||||||
|
|
||||||
import { getProxies, switchProxy } from 'd/proxies';
|
import { switchProxy } from '../store/proxies';
|
||||||
|
|
||||||
const mapStateToProps = s => ({
|
const { memo, useCallback, useMemo } = React;
|
||||||
proxies: getProxies(s)
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function ProxyGroup({ name }) {
|
function ProxyGroup({ name, proxies, apiConfig, dispatch }) {
|
||||||
const { proxies } = useStoreState(mapStateToProps);
|
|
||||||
const actions = useActions({ switchProxy });
|
|
||||||
const group = proxies[name];
|
const group = proxies[name];
|
||||||
const { all } = group;
|
const { all, type, now } = group;
|
||||||
|
|
||||||
|
const isSelectable = useMemo(() => type === 'Selector', [type]);
|
||||||
|
|
||||||
|
const itemOnTapCallback = useCallback(
|
||||||
|
proxyName => {
|
||||||
|
if (!isSelectable) return;
|
||||||
|
|
||||||
|
dispatch(switchProxy(apiConfig, name, proxyName));
|
||||||
|
// switchProxyFn(name, proxyName);
|
||||||
|
},
|
||||||
|
[apiConfig, dispatch, name, isSelectable]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={s0.group}>
|
<div className={s0.group}>
|
||||||
<div className={s0.header}>
|
<div className={s0.header}>
|
||||||
<h2>
|
<SectionNameType name={name} type={group.type} />
|
||||||
<span>{name}</span>
|
|
||||||
<span>{group.type}</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div className={s0.list}>
|
|
||||||
{all.map(proxyName => {
|
|
||||||
const isSelectable = group.type === 'Selector';
|
|
||||||
const proxyClassName = cx(s0.proxy, {
|
|
||||||
[s0.proxySelectable]: isSelectable
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={proxyClassName}
|
|
||||||
key={proxyName}
|
|
||||||
onClick={() => {
|
|
||||||
if (!isSelectable) return;
|
|
||||||
actions.switchProxy(name, proxyName);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Proxy
|
|
||||||
isSelectable={isSelectable}
|
|
||||||
name={proxyName}
|
|
||||||
now={proxyName === group.now}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
|
<ProxyList
|
||||||
|
all={all}
|
||||||
|
now={now}
|
||||||
|
isSelectable={isSelectable}
|
||||||
|
itemOnTapCallback={itemOnTapCallback}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxyGroup.propTypes = {
|
type ProxyListProps = {
|
||||||
name: PropTypes.string
|
all: string[],
|
||||||
|
now?: string,
|
||||||
|
isSelectable?: boolean,
|
||||||
|
itemOnTapCallback?: string => void
|
||||||
};
|
};
|
||||||
|
export function ProxyList({
|
||||||
|
all,
|
||||||
|
now,
|
||||||
|
isSelectable,
|
||||||
|
itemOnTapCallback
|
||||||
|
}: ProxyListProps) {
|
||||||
|
return (
|
||||||
|
<div className={s0.list}>
|
||||||
|
{all.map(proxyName => {
|
||||||
|
const proxyClassName = cx(s0.proxy, {
|
||||||
|
[s0.proxySelectable]: isSelectable
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={proxyClassName}
|
||||||
|
key={proxyName}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isSelectable || !itemOnTapCallback) return;
|
||||||
|
itemOnTapCallback(proxyName);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Proxy name={proxyName} now={proxyName === now} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProxyListSummaryView({
|
||||||
|
all,
|
||||||
|
now,
|
||||||
|
isSelectable,
|
||||||
|
itemOnTapCallback
|
||||||
|
}: ProxyListProps) {
|
||||||
|
return (
|
||||||
|
<div className={s0.list}>
|
||||||
|
{all.map(proxyName => {
|
||||||
|
const proxyClassName = cx(s0.proxy, {
|
||||||
|
[s0.proxySelectable]: isSelectable
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={proxyClassName}
|
||||||
|
key={proxyName}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isSelectable || !itemOnTapCallback) return;
|
||||||
|
itemOnTapCallback(proxyName);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProxySmall name={proxyName} now={proxyName === now} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ProxyGroup);
|
||||||
|
|
|
@ -1,32 +1,19 @@
|
||||||
.header {
|
.header {
|
||||||
> h2 {
|
margin-bottom: 12px;
|
||||||
margin-top: 0;
|
|
||||||
|
|
||||||
font-size: 1.3em;
|
|
||||||
@media (--breakpoint-not-small) {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span:nth-child(2) {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #777;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0 0.3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.proxy {
|
.proxy {
|
||||||
max-width: 280px;
|
margin-right: 5px;
|
||||||
margin: 2px;
|
margin-bottom: 5px;
|
||||||
@media (--breakpoint-not-small) {
|
@media (--breakpoint-not-small) {
|
||||||
min-width: 150px;
|
margin-right: 10px;
|
||||||
margin: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
transition: transform 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out;
|
||||||
|
|
||||||
|
|
|
@ -1,39 +1,16 @@
|
||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import s0 from './ProxyLatency.module.css';
|
import s0 from './ProxyLatency.module.css';
|
||||||
|
|
||||||
const colorMap = {
|
type ProxyLatencyProps = {
|
||||||
good: '#67C23A',
|
number: number,
|
||||||
normal: '#E6A23C',
|
color: string
|
||||||
bad: '#F56C6C',
|
|
||||||
na: '#909399'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getLabelColor(number, error) {
|
export default function ProxyLatency({ number, color }: ProxyLatencyProps) {
|
||||||
if (error !== '') {
|
|
||||||
return colorMap.na;
|
|
||||||
} else if (number < 200) {
|
|
||||||
return colorMap.good;
|
|
||||||
} else if (number < 400) {
|
|
||||||
return colorMap.normal;
|
|
||||||
}
|
|
||||||
return colorMap.bad;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ProxyLatency({ latency }) {
|
|
||||||
const { number, error } = latency;
|
|
||||||
const color = useMemo(() => getLabelColor(number, error), [number, error]);
|
|
||||||
return (
|
return (
|
||||||
<span className={s0.proxyLatency} style={{ color }}>
|
<span className={s0.proxyLatency} style={{ color }}>
|
||||||
{error !== '' ? <span>{error}</span> : <span>{number} ms</span>}
|
<span>{number} ms</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxyLatency.propTypes = {
|
|
||||||
latency: PropTypes.shape({
|
|
||||||
number: PropTypes.number,
|
|
||||||
error: PropTypes.string
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
211
src/components/ProxyProvider.js
Normal file
211
src/components/ProxyProvider.js
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ChevronDown, RotateCw } from 'react-feather';
|
||||||
|
import { formatDistance } from 'date-fns';
|
||||||
|
import ResizeObserver from 'resize-observer-polyfill';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import cx from 'classnames';
|
||||||
|
|
||||||
|
import { useStoreState } from '../misc/store';
|
||||||
|
import { getClashAPIConfig } from '../ducks/app';
|
||||||
|
import { connect } from './StateProvider';
|
||||||
|
import { SectionNameType } from './shared/Basic';
|
||||||
|
import { ProxyList, ProxyListSummaryView } from './ProxyGroup';
|
||||||
|
import { ButtonWithIcon, ButtonPlain } from './Button';
|
||||||
|
|
||||||
|
import { updateProviderByName } from '../store/proxies';
|
||||||
|
|
||||||
|
import s from './ProxyProvider.module.css';
|
||||||
|
|
||||||
|
const { memo, useState, useRef, useEffect, useCallback } = React;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: Array<{
|
||||||
|
name: string,
|
||||||
|
proxies: Array<string>,
|
||||||
|
type: 'Proxy' | 'Rule',
|
||||||
|
vehicleType: 'HTTP' | 'File' | 'Compatible',
|
||||||
|
updatedAt?: string
|
||||||
|
}>,
|
||||||
|
proxies: {
|
||||||
|
[string]: any
|
||||||
|
},
|
||||||
|
dispatch: any => void
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = s => ({
|
||||||
|
apiConfig: getClashAPIConfig(s)
|
||||||
|
});
|
||||||
|
|
||||||
|
function ProxyProvider({ item, dispatch }: Props) {
|
||||||
|
const { apiConfig } = useStoreState(mapStateToProps);
|
||||||
|
const updateProvider = useCallback(
|
||||||
|
() => dispatch(updateProviderByName(apiConfig, item.name)),
|
||||||
|
[apiConfig, dispatch, item.name]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [isCollapsibleOpen, setCollapsibleOpen] = useState(false);
|
||||||
|
const toggle = useCallback(() => setCollapsibleOpen(x => !x), []);
|
||||||
|
const timeAgo = formatDistance(new Date(item.updatedAt), new Date());
|
||||||
|
return (
|
||||||
|
<div className={s.body}>
|
||||||
|
<div className={s.header} onClick={toggle}>
|
||||||
|
<SectionNameType name={item.name} type={item.vehicleType} />
|
||||||
|
<ButtonPlain>
|
||||||
|
<span className={cx(s.arrow, { [s.isOpen]: isCollapsibleOpen })}>
|
||||||
|
<ChevronDown />
|
||||||
|
</span>
|
||||||
|
</ButtonPlain>
|
||||||
|
</div>
|
||||||
|
<div className={s.updatedAt}>
|
||||||
|
<small>Updated {timeAgo} ago</small>
|
||||||
|
</div>
|
||||||
|
<Collapsible2 isOpen={isCollapsibleOpen}>
|
||||||
|
<ProxyList all={item.proxies} />
|
||||||
|
<div className={s.actionFooter}>
|
||||||
|
<ButtonWithIcon
|
||||||
|
text="Update"
|
||||||
|
icon={<Refresh />}
|
||||||
|
onClick={updateProvider}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Collapsible2>
|
||||||
|
<Collapsible2 isOpen={!isCollapsibleOpen}>
|
||||||
|
<ProxyListSummaryView all={item.proxies} />
|
||||||
|
</Collapsible2>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = {
|
||||||
|
rest: { scale: 1 },
|
||||||
|
// hover: { scale: 1.1 },
|
||||||
|
pressed: { scale: 0.95 }
|
||||||
|
};
|
||||||
|
const arrow = {
|
||||||
|
rest: { rotate: 0 },
|
||||||
|
hover: { rotate: 360, transition: { duration: 0.3 } }
|
||||||
|
};
|
||||||
|
function Refresh() {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className={s.refresh}
|
||||||
|
variants={button}
|
||||||
|
initial="rest"
|
||||||
|
whileHover="hover"
|
||||||
|
whileTap="pressed"
|
||||||
|
>
|
||||||
|
<motion.div className="flexCenter" variants={arrow}>
|
||||||
|
<RotateCw size={16} />
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function usePrevious(value) {
|
||||||
|
const ref = useRef();
|
||||||
|
useEffect(() => void (ref.current = value), [value]);
|
||||||
|
return ref.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useMeasure() {
|
||||||
|
const ref = useRef();
|
||||||
|
const [bounds, set] = useState({ height: 0 });
|
||||||
|
useEffect(() => {
|
||||||
|
const ro = new ResizeObserver(([entry]) => set(entry.contentRect));
|
||||||
|
if (ref.current) ro.observe(ref.current);
|
||||||
|
return () => ro.disconnect();
|
||||||
|
}, []);
|
||||||
|
return [ref, bounds];
|
||||||
|
}
|
||||||
|
|
||||||
|
// import { useSpring, a } from 'react-spring';
|
||||||
|
// const Collapsible = memo(({ children, isOpen }) => {
|
||||||
|
// const previous = usePrevious(isOpen);
|
||||||
|
// const [refToMeature, { height: viewHeight }] = useMeasure();
|
||||||
|
// const { height, opacity, visibility, transform } = useSpring({
|
||||||
|
// from: {
|
||||||
|
// height: 0,
|
||||||
|
// opacity: 0,
|
||||||
|
// transform: 'translate3d(20px,0,0)',
|
||||||
|
// visibility: 'hidden'
|
||||||
|
// },
|
||||||
|
// to: {
|
||||||
|
// height: isOpen ? viewHeight : 0,
|
||||||
|
// opacity: isOpen ? 1 : 0,
|
||||||
|
// visibility: isOpen ? 'visible' : 'hidden',
|
||||||
|
// transform: `translate3d(${isOpen ? 0 : 20}px,0,0)`
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// return (
|
||||||
|
// <div>
|
||||||
|
// <a.div
|
||||||
|
// style={{
|
||||||
|
// opacity,
|
||||||
|
// willChange: 'transform, opacity, height, visibility',
|
||||||
|
// visibility,
|
||||||
|
// height: isOpen && previous === isOpen ? 'auto' : height
|
||||||
|
// }}>
|
||||||
|
// <a.div style={{ transform }} ref={refToMeature} children={children} />
|
||||||
|
// </a.div>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
const variantsCollpapsibleWrap = {
|
||||||
|
initialOpen: {
|
||||||
|
height: 'auto',
|
||||||
|
transition: { duration: 0 }
|
||||||
|
},
|
||||||
|
open: height => ({
|
||||||
|
height,
|
||||||
|
opacity: 1,
|
||||||
|
visibility: 'visible',
|
||||||
|
transition: { duration: 0.3 }
|
||||||
|
}),
|
||||||
|
closed: {
|
||||||
|
height: 0,
|
||||||
|
opacity: 0,
|
||||||
|
visibility: 'hidden',
|
||||||
|
transition: { duration: 0.3 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const variantsCollpapsibleChildContainer = {
|
||||||
|
open: {
|
||||||
|
x: 0
|
||||||
|
},
|
||||||
|
closed: {
|
||||||
|
x: 20
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Collapsible2 = memo(({ children, isOpen }) => {
|
||||||
|
const previous = usePrevious(isOpen);
|
||||||
|
const [refToMeature, { height }] = useMeasure();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<motion.div
|
||||||
|
animate={
|
||||||
|
isOpen && previous === isOpen
|
||||||
|
? 'initialOpen'
|
||||||
|
: isOpen
|
||||||
|
? 'open'
|
||||||
|
: 'closed'
|
||||||
|
}
|
||||||
|
custom={height}
|
||||||
|
variants={variantsCollpapsibleWrap}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
variants={variantsCollpapsibleChildContainer}
|
||||||
|
ref={refToMeature}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapState = s => ({
|
||||||
|
// proxies: getProxies(s)
|
||||||
|
});
|
||||||
|
export default connect(mapState)(ProxyProvider);
|
43
src/components/ProxyProvider.module.css
Normal file
43
src/components/ProxyProvider.module.css
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
display: inline-flex;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
&.isOpen {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: var(--color-focus-blue) solid 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.updatedAt {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
small {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
padding: 10px 15px;
|
||||||
|
@media (--breakpoint-not-small) {
|
||||||
|
padding: 10px 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionFooter {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
19
src/components/ProxyProviderList.js
Normal file
19
src/components/ProxyProviderList.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ContentHeader from './ContentHeader';
|
||||||
|
import ProxyProvider from './ProxyProvider';
|
||||||
|
|
||||||
|
function ProxyProviderList({ items }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContentHeader title="Proxy Provider" />
|
||||||
|
<div>
|
||||||
|
{items.map(item => (
|
||||||
|
<ProxyProvider key={item.name} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProxyProviderList;
|
|
@ -10,6 +10,13 @@
|
||||||
U+FEFF, U+FFFD;
|
U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* .absolute { */
|
||||||
|
/* position: absolute; */
|
||||||
|
/* } */
|
||||||
|
|
||||||
.border-left,
|
.border-left,
|
||||||
.border-top,
|
.border-top,
|
||||||
.border-bottom {
|
.border-bottom {
|
||||||
|
@ -113,6 +120,12 @@ body.light {
|
||||||
--bg-modal: #fbfbfb;
|
--bg-modal: #fbfbfb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flexCenter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO remove fabgrp in component css files */
|
/* TODO remove fabgrp in component css files */
|
||||||
.fabgrp {
|
.fabgrp {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { Provider } from 'm/store';
|
import { Provider } from '../misc/store';
|
||||||
|
import StateProvider from './StateProvider';
|
||||||
import { HashRouter as Router, Route } from 'react-router-dom';
|
import { HashRouter as Router, Route } from 'react-router-dom';
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import Loading2 from 'c/Loading2';
|
import Loading2 from 'c/Loading2';
|
||||||
|
@ -36,30 +37,38 @@ const Rules = React.lazy(() =>
|
||||||
|
|
||||||
window.store = store;
|
window.store = store;
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
proxies: {
|
||||||
|
proxies: {},
|
||||||
|
delay: {},
|
||||||
|
groupNames: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const Root = () => (
|
const Root = () => (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Provider store={store}>
|
<StateProvider initialState={initialState}>
|
||||||
<Router>
|
<Provider store={store}>
|
||||||
<div className={s0.app}>
|
<Router>
|
||||||
<APIDiscovery />
|
<div className={s0.app}>
|
||||||
<Route path="/" render={props => <SideBar {...props} />} />
|
<APIDiscovery />
|
||||||
<div className={s0.content}>
|
<Route path="/" render={props => <SideBar {...props} />} />
|
||||||
<Suspense fallback={<Loading2 />}>
|
<div className={s0.content}>
|
||||||
<Route exact path="/" render={() => <Home />} />
|
<Suspense fallback={<Loading2 />}>
|
||||||
<Route exact path="/connections" component={Connections} />
|
<Route exact path="/" render={() => <Home />} />
|
||||||
<Route exact path="/overview" render={() => <Home />} />
|
<Route exact path="/connections" component={Connections} />
|
||||||
<Route exact path="/configs" component={Config} />
|
<Route exact path="/overview" render={() => <Home />} />
|
||||||
<Route exact path="/logs" component={Logs} />
|
<Route exact path="/configs" component={Config} />
|
||||||
<Route exact path="/proxies" render={() => <Proxies />} />
|
<Route exact path="/logs" component={Logs} />
|
||||||
<Route exact path="/rules" render={() => <Rules />} />
|
<Route exact path="/proxies" render={() => <Proxies />} />
|
||||||
</Suspense>
|
<Route exact path="/rules" render={() => <Rules />} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Router>
|
||||||
</Router>
|
</Provider>
|
||||||
</Provider>
|
</StateProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
// <Route exact path="/__0" render={() => <StyleGuide />} />
|
|
||||||
// <Route exact path="/__1" component={Loading} />
|
|
||||||
|
|
||||||
export default hot(Root);
|
export default hot(Root);
|
||||||
|
|
79
src/components/StateProvider.js
Normal file
79
src/components/StateProvider.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import React from 'react';
|
||||||
|
import produce, * as immer from 'immer';
|
||||||
|
|
||||||
|
const {
|
||||||
|
createContext,
|
||||||
|
memo,
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useState
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
const StateContext = createContext(null);
|
||||||
|
const DispatchContext = createContext(null);
|
||||||
|
|
||||||
|
export { immer };
|
||||||
|
|
||||||
|
export function useStoreState() {
|
||||||
|
return useContext(StateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStoreDispatch() {
|
||||||
|
return useContext(DispatchContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Provider({ initialState, children }) {
|
||||||
|
const stateRef = useRef(initialState);
|
||||||
|
const [state, setState] = useState(initialState);
|
||||||
|
const getState = useCallback(() => stateRef.current, []);
|
||||||
|
useEffect(() => {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
window.getState2 = getState;
|
||||||
|
}
|
||||||
|
}, [getState]);
|
||||||
|
const dispatch = useCallback(
|
||||||
|
(actionId, fn, thunk) => {
|
||||||
|
// if (thunk) return thunk(dispatch, getState);
|
||||||
|
if (typeof actionId === 'function') return actionId(dispatch, getState);
|
||||||
|
|
||||||
|
const stateNext = produce(getState(), fn);
|
||||||
|
if (stateNext !== stateRef.current) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(actionId, stateNext);
|
||||||
|
}
|
||||||
|
stateRef.current = stateNext;
|
||||||
|
setState(stateNext);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[getState]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StateContext.Provider value={state}>
|
||||||
|
<DispatchContext.Provider value={dispatch}>
|
||||||
|
{children}
|
||||||
|
</DispatchContext.Provider>
|
||||||
|
</StateContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connect(mapStateToProps) {
|
||||||
|
return Component => {
|
||||||
|
const MemoComponent = memo(Component);
|
||||||
|
function Connected(props) {
|
||||||
|
const state = useContext(StateContext);
|
||||||
|
const dispatch = useContext(DispatchContext);
|
||||||
|
const mapped = mapStateToProps(state, props);
|
||||||
|
const nextProps = {
|
||||||
|
...props,
|
||||||
|
...mapped,
|
||||||
|
dispatch
|
||||||
|
};
|
||||||
|
return <MemoComponent {...nextProps} />;
|
||||||
|
}
|
||||||
|
return Connected;
|
||||||
|
};
|
||||||
|
}
|
12
src/components/shared/Basic.js
Normal file
12
src/components/shared/Basic.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import s from './Basic.module.css';
|
||||||
|
|
||||||
|
export function SectionNameType({ name, type }) {
|
||||||
|
return (
|
||||||
|
<h2 className={s.sectionNameType}>
|
||||||
|
<span>{name}</span>
|
||||||
|
<span>{type}</span>
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
}
|
14
src/components/shared/Basic.module.css
Normal file
14
src/components/shared/Basic.module.css
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
h2.sectionNameType {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.3em;
|
||||||
|
@media (--breakpoint-not-small) {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:nth-child(2) {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #777;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0 0.3em;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import modals from './modals';
|
import modals from './modals';
|
||||||
import proxies from './proxies';
|
|
||||||
import rules from './rules';
|
import rules from './rules';
|
||||||
import logs from './logs';
|
import logs from './logs';
|
||||||
import configs from './configs';
|
import configs from './configs';
|
||||||
|
@ -9,7 +8,6 @@ import configs from './configs';
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
app,
|
app,
|
||||||
modals,
|
modals,
|
||||||
proxies,
|
|
||||||
rules,
|
rules,
|
||||||
logs,
|
logs,
|
||||||
configs
|
configs
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as proxiesAPI from 'a/proxies';
|
import * as proxiesAPI from '../api/proxies';
|
||||||
import { getClashAPIConfig } from 'd/app';
|
|
||||||
|
|
||||||
// see all types:
|
// see all types:
|
||||||
// https://github.com/Dreamacro/clash/blob/master/constant/adapters.go
|
// https://github.com/Dreamacro/clash/blob/master/constant/adapters.go
|
||||||
|
@ -12,10 +11,136 @@ const ProxyTypes = ['Shadowsocks', 'Snell', 'Socks5', 'Http', 'Vmess'];
|
||||||
export const getProxies = s => s.proxies.proxies;
|
export const getProxies = s => s.proxies.proxies;
|
||||||
export const getDelay = s => s.proxies.delay;
|
export const getDelay = s => s.proxies.delay;
|
||||||
export const getProxyGroupNames = s => s.proxies.groupNames;
|
export const getProxyGroupNames = s => s.proxies.groupNames;
|
||||||
|
export const getProxyProviders = s => s.proxies.proxyProviders || [];
|
||||||
|
|
||||||
const CompletedFetchProxies = 'proxies/CompletedFetchProxies';
|
export function fetchProxies(apiConfig) {
|
||||||
const OptimisticSwitchProxy = 'proxies/OptimisticSwitchProxy';
|
return async (dispatch, getState) => {
|
||||||
const CompletedRequestDelayForProxy = 'proxies/CompletedRequestDelayForProxy';
|
const [proxiesData, providersData] = await Promise.all([
|
||||||
|
proxiesAPI.fetchProxies(apiConfig),
|
||||||
|
proxiesAPI.fetchProviderProxies(apiConfig)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [proxyProviders, providerProxies] = formatProxyProviders(
|
||||||
|
providersData.providers
|
||||||
|
);
|
||||||
|
const proxies = { ...providerProxies, ...proxiesData.proxies };
|
||||||
|
const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies);
|
||||||
|
|
||||||
|
const delayPrev = getDelay(getState());
|
||||||
|
const delayNext = { ...delayPrev };
|
||||||
|
|
||||||
|
for (let i = 0; i < proxyNames.length; i++) {
|
||||||
|
const name = proxyNames[i];
|
||||||
|
const { history } = proxies[name] || { history: [] };
|
||||||
|
const h = history[history.length - 1];
|
||||||
|
if (h) {
|
||||||
|
const ret = { error: '' };
|
||||||
|
if (h.delay === 0) {
|
||||||
|
ret.error = 'LikelyTimeout';
|
||||||
|
} else {
|
||||||
|
ret.number = h.delay;
|
||||||
|
}
|
||||||
|
delayNext[name] = ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch('store/proxies#fetchProxies', s => {
|
||||||
|
s.proxies.proxies = proxies;
|
||||||
|
s.proxies.groupNames = groupNames;
|
||||||
|
s.proxies.delay = delayNext;
|
||||||
|
s.proxies.proxyProviders = proxyProviders;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateProviderByName(apiConfig, name) {
|
||||||
|
return async dispatch => {
|
||||||
|
try {
|
||||||
|
await proxiesAPI.updateProviderByName(apiConfig, name);
|
||||||
|
} catch (x) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
// should be optimized
|
||||||
|
// but ¯\_(ツ)_/¯
|
||||||
|
dispatch(fetchProxies(apiConfig));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function switchProxy(apiConfig, name1, name2) {
|
||||||
|
return async dispatch => {
|
||||||
|
proxiesAPI
|
||||||
|
.requestToSwitchProxy(apiConfig, name1, name2)
|
||||||
|
.then(
|
||||||
|
res => {
|
||||||
|
if (res.ok === false) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('failed to swith proxy', res.statusText);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(err, 'failed to swith proxy');
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(fetchProxies(apiConfig));
|
||||||
|
});
|
||||||
|
// optimistic UI update
|
||||||
|
dispatch('store/proxies#switchProxy', s => {
|
||||||
|
const proxies = s.proxies.proxies;
|
||||||
|
if (proxies[name1] && proxies[name1].now) {
|
||||||
|
proxies[name1].now = name2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestDelayForProxyOnce(apiConfig, name) {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const res = await proxiesAPI.requestDelayForProxy(apiConfig, name);
|
||||||
|
let error = '';
|
||||||
|
if (res.ok === false) {
|
||||||
|
error = res.statusText;
|
||||||
|
}
|
||||||
|
const { delay } = await res.json();
|
||||||
|
|
||||||
|
const delayPrev = getDelay(getState());
|
||||||
|
const delayNext = {
|
||||||
|
...delayPrev,
|
||||||
|
[name]: {
|
||||||
|
error,
|
||||||
|
number: delay
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch('requestDelayForProxyOnce', s => {
|
||||||
|
s.proxies.delay = delayNext;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requestDelayForProxy(apiConfig, name) {
|
||||||
|
return async dispatch => {
|
||||||
|
await dispatch(requestDelayForProxyOnce(apiConfig, name));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requestDelayAll(apiConfig) {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const proxies = getProxies(state);
|
||||||
|
const keys = Object.keys(proxies);
|
||||||
|
const proxyNames = [];
|
||||||
|
keys.forEach(k => {
|
||||||
|
if (proxies[k].type === 'Vmess' || proxies[k].type === 'Shadowsocks') {
|
||||||
|
proxyNames.push(k);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(
|
||||||
|
proxyNames.map(p => dispatch(requestDelayForProxy(apiConfig, p)))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function retrieveGroupNamesFrom(proxies) {
|
function retrieveGroupNamesFrom(proxies) {
|
||||||
let groupNames = [];
|
let groupNames = [];
|
||||||
|
@ -44,141 +169,26 @@ function retrieveGroupNamesFrom(proxies) {
|
||||||
return [groupNames, proxyNames];
|
return [groupNames, proxyNames];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchProxies() {
|
function formatProxyProviders(providersInput) {
|
||||||
return async (dispatch, getState) => {
|
const keys = Object.keys(providersInput);
|
||||||
// TODO handle errors
|
const providers = [];
|
||||||
|
const proxies = {};
|
||||||
const state = getState();
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const provider = providersInput[keys[i]];
|
||||||
const apiConfig = getClashAPIConfig(state);
|
if (provider.name === 'default' || provider.vehicleType === 'Compatible')
|
||||||
// TODO show loading animation?
|
continue;
|
||||||
const json = await proxiesAPI.fetchProxies(apiConfig);
|
const proxiesArr = provider.proxies;
|
||||||
let { proxies = {} } = json;
|
const names = [];
|
||||||
|
for (let j = 0; j < proxiesArr.length; j++) {
|
||||||
const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies);
|
const proxy = proxiesArr[j];
|
||||||
const delayPrev = getDelay(getState());
|
proxies[proxy.name] = proxy;
|
||||||
|
names.push(proxy.name);
|
||||||
const delayNext = { ...delayPrev };
|
|
||||||
|
|
||||||
for (let i = 0; i < proxyNames.length; i++) {
|
|
||||||
const name = proxyNames[i];
|
|
||||||
const { history } = proxies[name] || { history: [] };
|
|
||||||
const h = history[history.length - 1];
|
|
||||||
if (h) {
|
|
||||||
const ret = { error: '' };
|
|
||||||
if (h.delay === 0) {
|
|
||||||
ret.error = 'LikelyTimeout';
|
|
||||||
} else {
|
|
||||||
ret.number = h.delay;
|
|
||||||
}
|
|
||||||
delayNext[name] = ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
// mutate directly
|
||||||
type: CompletedFetchProxies,
|
provider.proxies = names;
|
||||||
payload: { proxies, groupNames, delay: delayNext }
|
providers.push(provider);
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function switchProxy(name1, name2) {
|
|
||||||
return async (dispatch, getState) => {
|
|
||||||
const apiConfig = getClashAPIConfig(getState());
|
|
||||||
// TODO display error message
|
|
||||||
proxiesAPI
|
|
||||||
.requestToSwitchProxy(apiConfig, name1, name2)
|
|
||||||
.then(
|
|
||||||
res => {
|
|
||||||
if (res.ok === false) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('failed to swith proxy', res.statusText);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
err => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(err, 'failed to swith proxy');
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
// fetchProxies again
|
|
||||||
dispatch(fetchProxies());
|
|
||||||
});
|
|
||||||
// optimistic UI update
|
|
||||||
const proxiesCurr = getProxies(getState());
|
|
||||||
const proxiesNext = { ...proxiesCurr };
|
|
||||||
if (proxiesNext[name1] && proxiesNext[name1].now) {
|
|
||||||
proxiesNext[name1].now = name2;
|
|
||||||
}
|
|
||||||
dispatch({
|
|
||||||
type: OptimisticSwitchProxy,
|
|
||||||
payload: { proxies: proxiesNext }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestDelayForProxyOnce(name) {
|
|
||||||
return async (dispatch, getState) => {
|
|
||||||
const apiConfig = getClashAPIConfig(getState());
|
|
||||||
const res = await proxiesAPI.requestDelayForProxy(apiConfig, name);
|
|
||||||
let error = '';
|
|
||||||
if (res.ok === false) {
|
|
||||||
error = res.statusText;
|
|
||||||
}
|
|
||||||
const { delay } = await res.json();
|
|
||||||
|
|
||||||
const delayPrev = getDelay(getState());
|
|
||||||
const delayNext = {
|
|
||||||
...delayPrev,
|
|
||||||
[name]: {
|
|
||||||
error,
|
|
||||||
number: delay
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: CompletedRequestDelayForProxy,
|
|
||||||
payload: { delay: delayNext }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestDelayForProxy(name) {
|
|
||||||
return async dispatch => {
|
|
||||||
await dispatch(requestDelayForProxyOnce(name));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requestDelayAll() {
|
|
||||||
return async (dispatch, getState) => {
|
|
||||||
const state = getState();
|
|
||||||
const proxies = getProxies(state);
|
|
||||||
const keys = Object.keys(proxies);
|
|
||||||
const proxyNames = [];
|
|
||||||
keys.forEach(k => {
|
|
||||||
if (proxies[k].type === 'Vmess' || proxies[k].type === 'Shadowsocks') {
|
|
||||||
proxyNames.push(k);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await Promise.all(proxyNames.map(p => dispatch(requestDelayForProxy(p))));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
proxies: {},
|
|
||||||
delay: {},
|
|
||||||
groupNames: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function reducer(state = initialState, { type, payload }) {
|
|
||||||
switch (type) {
|
|
||||||
case CompletedRequestDelayForProxy:
|
|
||||||
case OptimisticSwitchProxy:
|
|
||||||
case CompletedFetchProxies: {
|
|
||||||
return { ...state, ...payload };
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [providers, proxies];
|
||||||
}
|
}
|
100
yarn.lock
100
yarn.lock
|
@ -875,6 +875,18 @@
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@emotion/is-prop-valid@^0.8.2":
|
||||||
|
version "0.8.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.5.tgz#2dda0791f0eafa12b7a0a5b39858405cc7bde983"
|
||||||
|
integrity sha512-6ZODuZSFofbxSbcxwsFz+6ioPjb0ISJRRPLZ+WIbjcU2IMU0Io+RGQjjaTgOvNQl007KICBm7zXQaYQEC1r6Bg==
|
||||||
|
dependencies:
|
||||||
|
"@emotion/memoize" "0.7.3"
|
||||||
|
|
||||||
|
"@emotion/memoize@0.7.3":
|
||||||
|
version "0.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.3.tgz#5b6b1c11d6a6dddf1f2fc996f74cf3b219644d78"
|
||||||
|
integrity sha512-2Md9mH6mvo+ygq1trTeVp2uzAKwE2P7In0cRpD/M9Q70aH8L+rxMLbb3JCN2JoSWsV2O+DdFjfbbXoMoLBczow==
|
||||||
|
|
||||||
"@hot-loader/react-dom@16.10.2":
|
"@hot-loader/react-dom@16.10.2":
|
||||||
version "16.10.2"
|
version "16.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.10.2.tgz#91920442252acac6f343eef5df41aca333f7dcea"
|
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.10.2.tgz#91920442252acac6f343eef5df41aca333f7dcea"
|
||||||
|
@ -911,6 +923,22 @@
|
||||||
"@nodelib/fs.scandir" "2.1.3"
|
"@nodelib/fs.scandir" "2.1.3"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@popmotion/easing@^1.0.1", "@popmotion/easing@^1.0.2":
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popmotion/easing/-/easing-1.0.2.tgz#17d925c45b4bf44189e5a38038d149df42d8c0b4"
|
||||||
|
integrity sha512-IkdW0TNmRnWTeWI7aGQIVDbKXPWHVEYdGgd5ZR4SH/Ty/61p63jCjrPxX1XrR7IGkl08bjhJROStD7j+RKgoIw==
|
||||||
|
|
||||||
|
"@popmotion/popcorn@^0.4.2", "@popmotion/popcorn@^0.4.4":
|
||||||
|
version "0.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popmotion/popcorn/-/popcorn-0.4.4.tgz#a5f906fccdff84526e3fcb892712d7d8a98d6adc"
|
||||||
|
integrity sha512-jYO/8319fKoNLMlY4ZJPiPu8Ea8occYwRZhxpaNn/kZsK4QG2E7XFlXZMJBsTWDw7I1i0uaqyC4zn1nwEezLzg==
|
||||||
|
dependencies:
|
||||||
|
"@popmotion/easing" "^1.0.1"
|
||||||
|
framesync "^4.0.1"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
style-value-types "^3.1.7"
|
||||||
|
tslib "^1.10.0"
|
||||||
|
|
||||||
"@samverschueren/stream-to-observable@^0.3.0":
|
"@samverschueren/stream-to-observable@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
||||||
|
@ -3522,6 +3550,30 @@ fragment-cache@^0.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
map-cache "^0.2.2"
|
map-cache "^0.2.2"
|
||||||
|
|
||||||
|
framer-motion@^1.7.0:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-1.7.0.tgz#36d038874c8e2c45ad6e4824df8a61508bacbda0"
|
||||||
|
integrity sha512-bE1aWkX0AKgmxMjFnAcXZ1d47TcuKugLn3D5cHvZcQwBiDPJuKD1WzwdTaDb7EHc+vbiWfBgMbECWxQFb9di5g==
|
||||||
|
dependencies:
|
||||||
|
"@popmotion/easing" "^1.0.2"
|
||||||
|
"@popmotion/popcorn" "^0.4.2"
|
||||||
|
framesync "^4.0.4"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
popmotion "9.0.0-beta-8"
|
||||||
|
style-value-types "^3.1.6"
|
||||||
|
stylefire "^7.0.0"
|
||||||
|
tslib "^1.10.0"
|
||||||
|
optionalDependencies:
|
||||||
|
"@emotion/is-prop-valid" "^0.8.2"
|
||||||
|
|
||||||
|
framesync@^4.0.0, framesync@^4.0.1, framesync@^4.0.4:
|
||||||
|
version "4.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.0.4.tgz#79c42c0118f26821c078570db0ff81fb863516a2"
|
||||||
|
integrity sha512-mdP0WvVHe0/qA62KG2LFUAOiWLng5GLpscRlwzBxu2VXOp6B8hNs5C5XlFigsMgrfDrr2YbqTsgdWZTc4RXRMQ==
|
||||||
|
dependencies:
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
tslib "^1.10.0"
|
||||||
|
|
||||||
fresh@0.5.2:
|
fresh@0.5.2:
|
||||||
version "0.5.2"
|
version "0.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
|
@ -3871,6 +3923,11 @@ hex-color-regex@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||||
|
|
||||||
|
hey-listen@^1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
||||||
|
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
||||||
|
|
||||||
history@^4.7.2, history@^4.9.0:
|
history@^4.7.2, history@^4.9.0:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||||
|
@ -4069,6 +4126,11 @@ image-size@^0.5.1:
|
||||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
|
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
|
||||||
integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=
|
integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=
|
||||||
|
|
||||||
|
immer@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/immer/-/immer-5.0.1.tgz#1a1184fa758f68f1b5573db840825fb5164cceca"
|
||||||
|
integrity sha512-KFHV1ivrBmPCVRhjy9oBooypnPfJ876NTrWXMNoUhXFAaWWAViVqZ4l6HxPST52qcN82qqsR38/pCGYRWP5W7w==
|
||||||
|
|
||||||
import-cwd@^2.0.0:
|
import-cwd@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
||||||
|
@ -5873,6 +5935,18 @@ please-upgrade-node@^3.1.1, please-upgrade-node@^3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver-compare "^1.0.0"
|
semver-compare "^1.0.0"
|
||||||
|
|
||||||
|
popmotion@9.0.0-beta-8:
|
||||||
|
version "9.0.0-beta-8"
|
||||||
|
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-beta-8.tgz#f5a709f11737734e84f2a6b73f9bcf25ee30c388"
|
||||||
|
integrity sha512-6eQzqursPvnP7ePvdfPeY4wFHmS3OLzNP8rJRvmfFfEIfpFqrQgLsM50Gd9AOvGKJtYJOFknNG+dsnzCpgIdAA==
|
||||||
|
dependencies:
|
||||||
|
"@popmotion/easing" "^1.0.1"
|
||||||
|
"@popmotion/popcorn" "^0.4.2"
|
||||||
|
framesync "^4.0.4"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
style-value-types "^3.1.6"
|
||||||
|
tslib "^1.10.0"
|
||||||
|
|
||||||
posix-character-classes@^0.1.0:
|
posix-character-classes@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||||
|
@ -6808,6 +6882,11 @@ reselect@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||||
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
||||||
|
|
||||||
|
resize-observer-polyfill@^1.5.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
|
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||||
|
|
||||||
resolve-cwd@^2.0.0:
|
resolve-cwd@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||||
|
@ -7488,6 +7567,25 @@ style-loader@^1.0.1:
|
||||||
loader-utils "^1.2.3"
|
loader-utils "^1.2.3"
|
||||||
schema-utils "^2.0.1"
|
schema-utils "^2.0.1"
|
||||||
|
|
||||||
|
style-value-types@^3.1.6, style-value-types@^3.1.7:
|
||||||
|
version "3.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.1.7.tgz#3d7d3cf9cb9f9ee86c00e19ba65d6a181a0db33a"
|
||||||
|
integrity sha512-jPaG5HcAPs3vetSwOJozrBXxuHo9tjZVnbRyBjxqb00c2saIoeuBJc1/2MtvB8eRZy41u/BBDH0CpfzWixftKg==
|
||||||
|
dependencies:
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
tslib "^1.10.0"
|
||||||
|
|
||||||
|
stylefire@^7.0.0:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/stylefire/-/stylefire-7.0.1.tgz#69777dc9ffc48248284ea5c0a3fe03ba5c468339"
|
||||||
|
integrity sha512-xp7hGiK5xyX7xINQI7qWagcLMyS6aLLbP2kMA3DTS4X31uPI3M7IyOm9TPmXwaMKG9fJ2Cgo4F2okcNB6nzxKQ==
|
||||||
|
dependencies:
|
||||||
|
"@popmotion/popcorn" "^0.4.4"
|
||||||
|
framesync "^4.0.0"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
style-value-types "^3.1.7"
|
||||||
|
tslib "^1.10.0"
|
||||||
|
|
||||||
stylehacks@^4.0.0:
|
stylehacks@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
|
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
|
||||||
|
@ -7779,7 +7877,7 @@ tryer@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
||||||
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
||||||
|
|
||||||
tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||||
|
|
Loading…
Reference in a new issue