chore: run ts-migrate

This commit is contained in:
Haishan 2020-10-31 18:18:04 +08:00
parent e62c916548
commit ff1a39d04e
67 changed files with 476 additions and 294 deletions

View file

@ -52,6 +52,7 @@
"react": "0.0.0-experimental-94c0244ba",
"react-dom": "0.0.0-experimental-94c0244ba",
"react-feather": "^2.0.3",
"react-helmet": "^6.1.0",
"react-icons": "^3.10.0",
"react-modal": "^3.11.1",
"react-query": "^2.23.1",
@ -82,6 +83,8 @@
"@types/lodash-es": "^4.17.3",
"@types/react": "^16.9.52",
"@types/react-dom": "^16.9.8",
"@types/react-helmet": "^6.1.0",
"@types/react-tabs": "^2.3.2",
"@typescript-eslint/eslint-plugin": "^4.4.0",
"@typescript-eslint/parser": "^4.4.0",
"autoprefixer": "^10.0.0",

View file

@ -1,4 +1,5 @@
import { getURLAndInit } from 'src/misc/request-helper';
import { ClashGeneralConfig } from 'src/store/types';
import { ClashAPIConfig } from 'src/types';
const endpoint = '/configs';
@ -12,7 +13,7 @@ export async function fetchConfigs(apiConfig: ClashAPIConfig) {
// req body
// { Path: string }
type ClashConfigPartial = { 'socks-port'?: unknown };
type ClashConfigPartial = Partial<ClashGeneralConfig>;
function configsPatchWorkaround(o: ClashConfigPartial) {
// backward compatibility for older clash using `socket-port`
if ('socks-port' in o) {

View file

@ -9,11 +9,13 @@ const subscribers = [];
// see also https://github.com/Dreamacro/clash/blob/dev/constant/metadata.go#L41
type UUID = string;
type ConnectionItem = {
type ConnNetwork = 'tcp' | 'udp';
type ConnType = 'HTTP' | 'HTTP Connect' | 'Socks5' | 'Redir' | 'Unknown';
export type ConnectionItem = {
id: UUID;
metadata: {
network: 'tcp' | 'udp';
type: 'HTTP' | 'HTTP Connect' | 'Socks5' | 'Redir' | 'Unknown';
network: ConnNetwork;
type: ConnType;
sourceIP: string;
destinationIP: string;
sourcePort: string;
@ -24,7 +26,7 @@ type ConnectionItem = {
download: number;
// e.g. "2019-11-30T22:48:13.416668+08:00",
start: string;
chains: Array<string>;
chains: string[];
// e.g. 'Match', 'DomainKeyword'
rule: string;
};

View file

@ -80,6 +80,7 @@ function APIConfig({ dispatch }) {
<div className={s0.hostnamePort}>
<Field
id="baseURL"
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ id: string; name: string; label: string; t... Remove this comment to see the full error message
name="baseURL"
label="API Base URL"
type="text"
@ -88,6 +89,7 @@ function APIConfig({ dispatch }) {
/>
<Field
id="secret"
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ id: string; name: string; label: string; v... Remove this comment to see the full error message
name="secret"
label="Secret(optional)"
value={secret}

View file

@ -15,6 +15,7 @@ function APIDiscovery({ dispatch, apiConfig, modals }) {
if (!window.fetch) {
const { detail } = errors[DOES_NOT_SUPPORT_FETCH];
const err = new Error(detail);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'code' does not exist on type 'Error'.
err.code = DOES_NOT_SUPPORT_FETCH;
throw err;
}
@ -31,6 +32,7 @@ function APIDiscovery({ dispatch, apiConfig, modals }) {
isOpen={modals.apiConfig}
className={s0.content}
overlayClassName={s0.overlay}
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element; isOpen: any; className:... Remove this comment to see the full error message
shouldCloseOnOverlayClick={false}
shouldCloseOnEsc={false}
onRequestClose={closeApiConfigModal}

View file

@ -114,6 +114,7 @@ function Item({
<>
<span className={s.secret}>{show ? secret : '***'}</span>
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean | (() => void)' is not assignable to... Remove this comment to see the full error message */}
<Button onClick={toggle} className={s.eye}>
<Icon size={20} />
</Button>

View file

@ -50,10 +50,12 @@ const variantsCollpapsibleChildContainer = {
},
};
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isOpen' does not exist on type '{ childr... Remove this comment to see the full error message
const Collapsible = memo(({ children, isOpen }) => {
const module = framerMotionResouce.read();
const motion = module.motion;
const previous = usePrevious(isOpen);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'height' does not exist on type 'MutableR... Remove this comment to see the full error message
const [refToMeature, { height }] = useMeasure();
return (
<div>

View file

@ -119,11 +119,11 @@ function ConfigImpl({
);
const handleSwitchOnChange = useCallback(
(checked) => {
(checked: boolean) => {
const name = 'allow-lan';
const value = checked;
setConfigState(name, value);
dispatch(updateConfigs(apiConfig, { [name]: value }));
dispatch(updateConfigs(apiConfig, { 'allow-lan': value }));
},
[apiConfig, dispatch, setConfigState]
);
@ -200,6 +200,7 @@ function ConfigImpl({
name={f.key}
value={configState[f.key]}
onChange={handleInputOnChange}
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ name: string; value: any; onChange: (e: an... Remove this comment to see the full error message
onBlur={handleInputOnBlur}
/>
</div>
@ -267,6 +268,7 @@ function ConfigImpl({
);
}
// @ts-expect-error ts-migrate(2339) FIXME: Property 'propTypes' does not exist on type '(prop... Remove this comment to see the full error message
Config.propTypes = {
configs: PropTypes.object,
};

View file

@ -3,6 +3,8 @@ import './Connections.css';
import React from 'react';
import { Pause, Play, X as IconClose } from 'react-feather';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { ConnectionItem } from 'src/api/connections';
import { State } from 'src/store/types';
import * as connAPI from '../api/connections';
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
@ -19,7 +21,7 @@ const { useEffect, useState, useRef, useCallback } = React;
const paddingBottom = 30;
function arrayToIdKv(items) {
function arrayToIdKv<T extends { id: string }>(items: T[]) {
const o = {};
for (let i = 0; i < items.length; i++) {
const item = items[i];
@ -28,9 +30,30 @@ function arrayToIdKv(items) {
return o;
}
function filterConns(conns, keyword) {
const hasSubstring = (s, pat) => s.toLowerCase().includes(pat.toLowerCase());
type FormattedConn = {
id: string;
upload: number;
download: number;
start: number;
chains: string;
rule: string;
destinationPort: string;
destinationIP: string;
sourceIP: string;
sourcePort: string;
source: string;
host: string;
type: string;
network: string;
downloadSpeedCurr?: number;
uploadSpeedCurr?: number;
};
function hasSubstring(s: string, pat: string) {
return s.toLowerCase().includes(pat.toLowerCase());
}
function filterConns(conns: FormattedConn[], keyword: string) {
return !keyword
? conns
: conns.filter((conn) =>
@ -47,10 +70,13 @@ function filterConns(conns, keyword) {
);
}
function formatConnectionDataItem(i, prevKv, now) {
function formatConnectionDataItem(
i: ConnectionItem,
prevKv: Record<string, { upload: number; download: number }>,
now: number
): FormattedConn {
const { id, metadata, upload, download, start, chains, rule } = i;
// eslint-disable-next-line prefer-const
let {
const {
host,
destinationPort,
destinationIP,
@ -60,27 +86,27 @@ function formatConnectionDataItem(i, prevKv, now) {
sourcePort,
} = metadata;
// host could be an empty string if it's direct IP connection
if (host === '') host = destinationIP;
let host2 = host;
if (host2 === '') host2 = destinationIP;
const prev = prevKv[id];
const ret = {
id,
upload,
download,
start: now - new Date(start),
start: now - new Date(start).valueOf(),
chains: chains.reverse().join(' / '),
rule,
...metadata,
host: `${host}:${destinationPort}`,
host: `${host2}:${destinationPort}`,
type: `${type}(${network})`,
source: `${sourceIP}:${sourcePort}`,
downloadSpeedCurr: download - (prev ? prev.download : 0),
uploadSpeedCurr: upload - (prev ? prev.upload : 0),
};
const prev = prevKv[id];
ret.downloadSpeedCurr = download - (prev ? prev.download : 0);
ret.uploadSpeedCurr = upload - (prev ? prev.upload : 0);
return ret;
}
function renderTableOrPlaceholder(conns) {
function renderTableOrPlaceholder(conns: FormattedConn[]) {
return conns.length > 0 ? (
<ConnectionTable data={conns} />
) : (
@ -119,13 +145,13 @@ function Conn({ apiConfig }) {
const read = useCallback(
({ connections }) => {
const prevConnsKv = arrayToIdKv(prevConnsRef.current);
const now = new Date();
const x = connections.map((c) =>
const now = Date.now();
const x = connections.map((c: ConnectionItem) =>
formatConnectionDataItem(c, prevConnsKv, now)
);
const closed = [];
for (const c of prevConnsRef.current) {
const idx = x.findIndex((conn) => conn.id === c.id);
const idx = x.findIndex((conn: ConnectionItem) => conn.id === c.id);
if (idx < 0) closed.push(c);
}
setClosedConns((prev) => {
@ -165,12 +191,14 @@ function Conn({ apiConfig }) {
<Tab>
<span>Active</span>
<span className={s.connQty}>
{/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
<ConnQty qty={filteredConns.length} />
</span>
</Tab>
<Tab>
<span>Closed</span>
<span className={s.connQty}>
{/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
<ConnQty qty={filteredClosedConns.length} />
</span>
</Tab>
@ -187,11 +215,13 @@ function Conn({ apiConfig }) {
</div>
</div>
<div
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message
ref={refContainer}
style={{ padding: 30, paddingBottom, paddingTop: 0 }}
>
<div
style={{
// @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
height: containerHeight - paddingBottom,
overflow: 'auto',
}}
@ -234,7 +264,7 @@ function Conn({ apiConfig }) {
);
}
const mapState = (s) => ({
const mapState = (s: State) => ({
apiConfig: getClashAPIConfig(s),
});

View file

@ -1,9 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import s0 from './ContentHeader.module.css';
function ContentHeader({ title }) {
type Props = {
title: string;
};
function ContentHeader({ title }: Props) {
return (
<div className={s0.root}>
<h1 className={s0.h1}>{title}</h1>
@ -11,8 +15,4 @@ function ContentHeader({ title }) {
);
}
ContentHeader.propTypes = {
title: PropTypes.string.isRequired,
};
export default React.memo(ContentHeader);

View file

@ -1,18 +1,21 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import * as React from 'react';
// import { getSentry } from '../misc/sentry';
import { deriveMessageFromError } from '../misc/errors';
import { deriveMessageFromError,Err } from '../misc/errors';
import ErrorBoundaryFallback from './ErrorBoundaryFallback';
class ErrorBoundary extends Component {
static propTypes = {
children: PropTypes.node,
};
type Props = {
children: React.ReactNode;
};
type State = {
error?: Err;
};
class ErrorBoundary extends React.Component<Props, State> {
state = { error: null };
static getDerivedStateFromError(error) {
static getDerivedStateFromError(error: Err) {
return { error };
}

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import React from 'react';
import s0 from './ErrorBoundaryFallback.module.css';
@ -6,7 +5,12 @@ import SvgGithub from './SvgGithub';
import SvgYacd from './SvgYacd';
const yacdRepoIssueUrl = 'https://github.com/haishanh/yacd/issues';
function ErrorBoundaryFallback({ message, detail }) {
type Props = {
message?: string;
detail?: string;
};
function ErrorBoundaryFallback({ message, detail }: Props) {
return (
<div className={s0.root}>
<div className={s0.yacd}>
@ -24,8 +28,4 @@ function ErrorBoundaryFallback({ message, detail }) {
);
}
ErrorBoundaryFallback.propTypes = {
message: PropTypes.string,
};
export default ErrorBoundaryFallback;

View file

@ -1,12 +1,19 @@
import cx from 'clsx';
import PropTypes from 'prop-types';
import React from 'react';
import s from './Field.module.css';
const { useCallback } = React;
export default function Field({ id, label, value, onChange, ...props }) {
type Props = {
value?: string | number;
type?: 'text' | 'number';
onChange?: (...args: any[]) => any;
id?: string;
label?: string;
};
export default function Field({ id, label, value, onChange, ...props }: Props) {
const valueOnChange = useCallback((e) => onChange(e), [onChange]);
const labelClassName = cx({
[s.floatAbove]: typeof value === 'string' && value !== '',
@ -20,11 +27,3 @@ export default function Field({ id, label, value, onChange, ...props }) {
</div>
);
}
Field.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
type: PropTypes.oneOf(['text', 'number']),
onChange: PropTypes.func,
id: PropTypes.string,
label: PropTypes.string,
};

View file

@ -1,8 +1,14 @@
import cx from 'clsx';
import PropTypes from 'prop-types';
import React from 'react';
const Icon = ({ id, width = 20, height = 20, className, ...props }) => {
type Props = {
id: string;
width?: number;
height?: number;
className?: string;
};
const Icon = ({ id, width = 20, height = 20, className, ...props }: Props) => {
const c = cx('icon', id, className);
const href = '#' + id;
return (
@ -12,11 +18,4 @@ const Icon = ({ id, width = 20, height = 20, className, ...props }) => {
);
};
Icon.propTypes = {
id: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
className: PropTypes.string,
};
export default React.memo(Icon);

View file

@ -1,11 +1,19 @@
import PropTypes from 'prop-types';
import React from 'react';
import s0 from './Input.module.css';
const { useState, useRef, useEffect, useCallback } = React;
export default function Input(props) {
type InputProps = {
value?: string | number;
type?: string;
onChange?: (...args: any[]) => any;
name?: string;
placeholder?: string;
};
export default function Input(props: InputProps) {
return <input className={s0.input} {...props} />;
}
@ -32,11 +40,3 @@ export function SelfControlledInput({ value, ...restProps }) {
/>
);
}
Input.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
type: PropTypes.string,
onChange: PropTypes.func,
name: PropTypes.string,
placeholder: PropTypes.string,
};

View file

@ -1,9 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import s0 from './Loading.module.css';
const Loading = ({ height }) => {
type Props = {
height?: string;
};
const Loading = ({ height }: Props) => {
const style = height ? { height } : {};
return (
<div className={s0.loading} style={style}>
@ -12,8 +16,4 @@ const Loading = ({ height }) => {
);
};
Loading.propTypes = {
height: PropTypes.string,
};
export default Loading;

View file

@ -1,5 +1,4 @@
import cx from 'clsx';
import PropTypes from 'prop-types';
import React from 'react';
import { areEqual, FixedSizeList as List } from 'react-window';
@ -26,7 +25,14 @@ const colors = {
error: '#c11c1c',
};
function LogLine({ time, even, payload, type }) {
type LogLineProps = {
time?: string;
even?: boolean;
payload?: string;
type?: string;
};
function LogLine({ time, even, payload, type }: LogLineProps) {
const className = cx({ even }, s0.log);
return (
<div className={className}>
@ -41,18 +47,12 @@ function LogLine({ time, even, payload, type }) {
);
}
LogLine.propTypes = {
time: PropTypes.string,
even: PropTypes.bool,
payload: PropTypes.string,
type: PropTypes.string,
};
function itemKey(index, data) {
const item = data[index];
return item.id;
}
// @ts-expect-error ts-migrate(2339) FIXME: Property 'index' does not exist on type '{ childre... Remove this comment to see the full error message
const Row = memo(({ index, style, data }) => {
const r = data[index];
return (
@ -78,10 +78,12 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
<div>
<ContentHeader title="Logs" />
<LogSearch />
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
<div ref={refLogsContainer} style={{ paddingBottom }}>
{logs.length === 0 ? (
<div
className={s0.logPlaceholder}
// @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
style={{ height: containerHeight - paddingBottom }}
>
<div className={s0.logPlaceholderIcon}>
@ -92,6 +94,7 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
) : (
<div className={s0.logsWrapper}>
<List
// @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
height={containerHeight - paddingBottom}
width="100%"
itemCount={logs.length}

View file

@ -1,18 +1,18 @@
import cx from 'clsx';
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'react-modal';
import s0 from './Modal.module.css';
function ModalAPIConfig({
isOpen,
onRequestClose,
className,
overlayClassName,
children,
...otherProps
}) {
type Props = {
isOpen: boolean;
onRequestClose: (...args: any[]) => any;
children: React.ReactNode;
className?: string;
overlayClassName?: string;
};
function ModalAPIConfig({ isOpen, onRequestClose, className, overlayClassName, children, ...otherProps }: Props) {
const contentCls = cx(className, s0.content);
const overlayCls = cx(overlayClassName, s0.overlay);
return (
@ -28,12 +28,4 @@ function ModalAPIConfig({
);
}
ModalAPIConfig.propTypes = {
isOpen: PropTypes.bool.isRequired,
onRequestClose: PropTypes.func.isRequired,
children: PropTypes.node.isRequired,
className: PropTypes.string,
overlayClassName: PropTypes.string,
};
export default React.memo(ModalAPIConfig);

View file

@ -1,4 +1,4 @@
import PropTypes from 'prop-types';
import React from 'react';
import s0 from './Rule.module.css';
@ -17,7 +17,14 @@ function getStyleFor({ proxy }) {
return { color };
}
function Rule({ type, payload, proxy, id }) {
type Props = {
id?: number;
type?: string;
payload?: string;
proxy?: string;
};
function Rule({ type, payload, proxy, id }: Props) {
const styleProxy = getStyleFor({ proxy });
return (
<div className={s0.rule}>
@ -33,11 +40,4 @@ function Rule({ type, payload, proxy, id }) {
);
}
Rule.propTypes = {
id: PropTypes.number,
type: PropTypes.string,
payload: PropTypes.string,
proxy: PropTypes.string,
};
export default Rule;

View file

@ -43,6 +43,7 @@ function getItemSizeFactory({ provider }) {
};
}
// @ts-expect-error ts-migrate(2339) FIXME: Property 'index' does not exist on type '{ childre... Remove this comment to see the full error message
const Row = memo(({ index, style, data }) => {
const { rules, provider, apiConfig } = data;
const providerQty = provider.names.length;
@ -110,6 +111,7 @@ function Rules({ apiConfig }) {
const { rules, provider } = useRuleAndProvider(apiConfig);
const invalidateQueries = useInvalidateQueries();
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ rules: RuleItem[]; provider: {... Remove this comment to see the full error message
const getItemSize = getItemSizeFactory({ rules, provider });
return (
@ -118,8 +120,10 @@ function Rules({ apiConfig }) {
<ContentHeader title="Rules" />
<TextFilter />
</div>
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
<div ref={refRulesContainer} style={{ paddingBottom }}>
<VariableSizeList
// @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
height={containerHeight - paddingBottom}
width="100%"
itemCount={rules.length + provider.names.length}

View file

@ -1,15 +1,16 @@
import cx from 'clsx';
import { array, func, number } from 'prop-types';
import React from 'react';
import s from './Selection.module.css';
export default function Selection({
OptionComponent,
optionPropsList,
selectedIndex,
onChange,
}) {
type SelectionProps = {
OptionComponent?: (...args: any[]) => any;
optionPropsList?: any[];
selectedIndex?: number;
onChange?: (...args: any[]) => any;
};
export default function Selection({ OptionComponent, optionPropsList, selectedIndex, onChange, }: SelectionProps) {
return (
<div className={s.root}>
{optionPropsList.map((props, idx) => {
@ -35,14 +36,8 @@ export default function Selection({
);
}
Selection.propTypes = {
OptionComponent: func,
optionPropsList: array,
selectedIndex: number,
onChange: func,
};
// for test
// @ts-expect-error ts-migrate(7030) FIXME: Not all code paths return a value.
export function Option({ title }) {
// eslint-disable-next-line no-undef
if (__DEV__) {

View file

@ -40,9 +40,13 @@ const icons = {
};
const SideBarRow = React.memo(function SideBarRow({
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isActive' does not exist on type '{ chil... Remove this comment to see the full error message
isActive,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'to' does not exist on type '{ children?:... Remove this comment to see the full error message
to,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'iconId' does not exist on type '{ childr... Remove this comment to see the full error message
iconId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'labelText' does not exist on type '{ chi... Remove this comment to see the full error message
labelText,
}) {
const Comp = icons[iconId];
@ -55,6 +59,7 @@ const SideBarRow = React.memo(function SideBarRow({
);
});
// @ts-expect-error ts-migrate(2339) FIXME: Property 'propTypes' does not exist on type 'Named... Remove this comment to see the full error message
SideBarRow.propTypes = {
isActive: PropTypes.bool.isRequired,
to: PropTypes.string.isRequired,
@ -107,6 +112,7 @@ function SideBar({ dispatch, theme }) {
{pages.map(({ to, iconId, labelText }) => (
<SideBarRow
key={to}
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: string; to: string; isActive: boolean... Remove this comment to see the full error message
to={to}
isActive={location.pathname === to}
iconId={iconId}

View file

@ -42,6 +42,7 @@ export default function Provider({ initialState, actions = {}, children }) {
const getState = useCallback(() => stateRef.current, []);
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'getState2' does not exist on type 'Windo... Remove this comment to see the full error message
window.getState2 = getState;
}
}, [getState]);
@ -94,6 +95,7 @@ export function connect(mapStateToProps) {
// steal from https://github.com/reduxjs/redux/blob/master/src/bindActionCreators.ts
function bindAction(action, dispatch) {
return function (...args) {
// @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message
return dispatch(action.apply(this, args));
};
}

View file

@ -51,12 +51,15 @@ class StyleGuide extends PureComponent {
render() {
return (
<div>
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */}
<Pane>
<SwitchExample />
</Pane>
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */}
<Pane>
<Input />
</Pane>
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */}
<Pane>
<ToggleSwitch
name="test"
@ -65,6 +68,7 @@ class StyleGuide extends PureComponent {
onChange={noop}
/>
</Pane>
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */}
<Pane>
<Button text="Test Lxatency" start={<Zap size={16} />} />
<Button text="Test Lxatency" start={<Zap size={16} />} isLoading />

View file

@ -1,7 +1,12 @@
import PropTypes from 'prop-types';
import React from 'react';
export default function SvgGithub({ width = 24, height = 24 } = {}) {
type Props = {
width?: number;
height?: number;
};
export default function SvgGithub({ width = 24, height = 24 }: Props = {}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@ -18,8 +23,3 @@ export default function SvgGithub({ width = 24, height = 24 } = {}) {
</svg>
);
}
SvgGithub.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
};

View file

@ -1,16 +1,23 @@
import cx from 'clsx';
import PropTypes from 'prop-types';
import React from 'react';
import s from './SvgYacd.module.css';
type Props = {
width?: number;
height?: number;
animate?: boolean;
c0?: string;
c1?: string;
};
function SvgYacd({
width = 320,
height = 320,
animate = false,
c0 = 'currentColor',
c1 = '#eee',
}) {
}: Props) {
const faceClasName = cx({ [s.path]: animate });
return (
<svg
@ -40,9 +47,4 @@ function SvgYacd({
);
}
SvgYacd.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
};
export default SvgYacd;

View file

@ -1,15 +1,23 @@
import PropTypes from 'prop-types';
import React, { useCallback, useMemo } from 'react';
import s0 from './ToggleSwitch.module.css';
function ToggleSwitch({ options, value, name, onChange }) {
type Props = {
options?: any[];
value?: string;
name?: string;
onChange?: (...args: any[]) => any;
};
function ToggleSwitch({ options, value, name, onChange }: Props) {
const idxSelected = useMemo(
() => options.map((o) => o.value).indexOf(value),
[options, value]
);
const getPortionPercentage = useCallback(
// @ts-expect-error ts-migrate(7030) FIXME: Not all code paths return a value.
(idx) => {
const w = Math.floor(100 / options.length);
if (idx === options.length - 1) {
@ -59,11 +67,4 @@ function ToggleSwitch({ options, value, name, onChange }) {
);
}
ToggleSwitch.propTypes = {
options: PropTypes.array,
value: PropTypes.string,
name: PropTypes.string,
onChange: PropTypes.func,
};
export default React.memo(ToggleSwitch);

View file

@ -50,6 +50,7 @@ function TrafficChart({ apiConfig, selectedChartStyleIndex }) {
useLineChart(Chart, 'trafficChart', data, traffic);
return (
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ position: string; maxWidth: number; }' is ... Remove this comment to see the full error message
<div style={chartWrapperStyle}>
<canvas id="trafficChart" />
</div>

View file

@ -1,6 +1,7 @@
import { formatDistance } from 'date-fns';
import * as React from 'react';
import { RotateCw, Zap } from 'react-feather';
import { DelayMapping } from 'src/store/types';
import { framerMotionResouce } from '../../misc/motion';
import {
@ -10,7 +11,6 @@ import {
getProxySortBy,
} from '../../store/app';
import {
DelayMapping,
getDelay,
healthcheckProviderByName,
updateProviderByName,
@ -89,6 +89,7 @@ function ProxyProviderImpl({
<div className={s.updatedAt}>
<small>Updated {timeAgo} ago</small>
</div>
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element[]; isOpen: boolean; }' i... Remove this comment to see the full error message */}
<Collapsible isOpen={isOpen}>
<ProxyList all={proxies} />
<div className={s.actionFooter}>
@ -101,6 +102,7 @@ function ProxyProviderImpl({
/>
</div>
</Collapsible>
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element; isOpen: boolean; }' is ... Remove this comment to see the full error message */}
<Collapsible isOpen={!isOpen}>
<ProxyListSummaryView all={proxies} />
</Collapsible>

View file

@ -1,14 +1,12 @@
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { DelayMapping, ProxiesMapping, ProxyItem } from 'src/store/types';
import {
// types
DelayMapping,
NonProxyTypes,
ProxiesMapping,
// atom
proxyFilterText,
ProxyItem,
} from '../../store/proxies';
const { useMemo } = React;

3
src/custom.d.ts vendored
View file

@ -1,3 +1,6 @@
/// <reference types="react/experimental" />
/// <reference types="react-dom/experimental" />
// for css modules
declare module '*.module.css' {
const classes: { [key: string]: string };

View file

@ -13,6 +13,7 @@ export default function useLineChart(
extraChartOptions = {}
) {
useEffect(() => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'getContext' does not exist on type 'HTML... Remove this comment to see the full error message
const ctx = document.getElementById(elementId).getContext('2d');
const c = new Chart(ctx, {
type: 'line',

View file

@ -3,14 +3,16 @@ export const DOES_NOT_SUPPORT_FETCH = 0;
export const errors = {
[DOES_NOT_SUPPORT_FETCH]: {
message: 'Browser not supported!',
detail: 'This browser does not support "fetch", please choose another one.'
detail: 'This browser does not support "fetch", please choose another one.',
},
default: {
message: 'Oops, something went wrong!'
}
message: 'Oops, something went wrong!',
},
};
export function deriveMessageFromError(err) {
export type Err = { code: number };
export function deriveMessageFromError(err: Err) {
const { code } = err;
if (typeof code === 'number') {
return errors[code];

View file

@ -1,16 +0,0 @@
// steal from https://github.com/sindresorhus/pretty-bytes/blob/master/index.js
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
export default number => {
if (number < 1000) {
return number + ' B';
}
const exponent = Math.min(
Math.floor(Math.log10(number) / 3),
UNITS.length - 1
);
number = Number((number / Math.pow(1000, exponent)).toPrecision(3));
const unit = UNITS[exponent];
return number + ' ' + unit;
};

13
src/misc/pretty-bytes.ts Normal file
View file

@ -0,0 +1,13 @@
// steal from https://github.com/sindresorhus/pretty-bytes/blob/master/index.js
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
export default function prettyBytes(n: number) {
if (n < 1000) {
return n + ' B';
}
const exponent = Math.min(Math.floor(Math.log10(n) / 3), UNITS.length - 1);
n = Number((n / Math.pow(1000, exponent)).toPrecision(3));
const unit = UNITS[exponent];
return n + ' ' + unit;
}

View file

@ -19,7 +19,7 @@ export function debounce<T extends any[]>(
fn: (...args: T) => unknown,
timeout: number
) {
let timeoutId: number;
let timeoutId: ReturnType<typeof setTimeout>;
return (...args: T) => {
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {

View file

@ -1,26 +1,31 @@
import { DispatchFn, GetStateFn, State, StateApp } from 'src/store/types';
import { loadState, saveState } from '../misc/storage';
import { debounce, trimTrailingSlash } from '../misc/utils';
import { fetchConfigs } from './configs';
import { closeModal } from './modals';
export const getClashAPIConfig = (s) => {
export const getClashAPIConfig = (s: State) => {
const idx = s.app.selectedClashAPIConfigIndex;
return s.app.clashAPIConfigs[idx];
};
export const getSelectedClashAPIConfigIndex = (s) =>
export const getSelectedClashAPIConfigIndex = (s: State) =>
s.app.selectedClashAPIConfigIndex;
export const getClashAPIConfigs = (s) => s.app.clashAPIConfigs;
export const getTheme = (s) => s.app.theme;
export const getSelectedChartStyleIndex = (s) => s.app.selectedChartStyleIndex;
export const getLatencyTestUrl = (s) => s.app.latencyTestUrl;
export const getCollapsibleIsOpen = (s) => s.app.collapsibleIsOpen;
export const getProxySortBy = (s) => s.app.proxySortBy;
export const getHideUnavailableProxies = (s) => s.app.hideUnavailableProxies;
export const getAutoCloseOldConns = (s) => s.app.autoCloseOldConns;
export const getClashAPIConfigs = (s: State) => s.app.clashAPIConfigs;
export const getTheme = (s: State) => s.app.theme;
export const getSelectedChartStyleIndex = (s: State) =>
s.app.selectedChartStyleIndex;
export const getLatencyTestUrl = (s: State) => s.app.latencyTestUrl;
export const getCollapsibleIsOpen = (s: State) => s.app.collapsibleIsOpen;
export const getProxySortBy = (s: State) => s.app.proxySortBy;
export const getHideUnavailableProxies = (s: State) =>
s.app.hideUnavailableProxies;
export const getAutoCloseOldConns = (s: State) => s.app.autoCloseOldConns;
const saveStateDebounced = debounce(saveState, 600);
function findClashAPIConfigIndex(getState, { baseURL, secret }) {
// @ts-expect-error ts-migrate(7030) FIXME: Not all code paths return a value.
function findClashAPIConfigIndex(getState: GetStateFn, { baseURL, secret }) {
const arr = getClashAPIConfigs(getState());
for (let i = 0; i < arr.length; i++) {
const x = arr[i];
@ -29,7 +34,7 @@ function findClashAPIConfigIndex(getState, { baseURL, secret }) {
}
export function addClashAPIConfig({ baseURL, secret }) {
return async (dispatch, getState) => {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const idx = findClashAPIConfigIndex(getState, { baseURL, secret });
// already exists
if (idx) return;
@ -44,7 +49,7 @@ export function addClashAPIConfig({ baseURL, secret }) {
}
export function removeClashAPIConfig({ baseURL, secret }) {
return async (dispatch, getState) => {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const idx = findClashAPIConfigIndex(getState, { baseURL, secret });
dispatch('removeClashAPIConfig', (s) => {
s.app.clashAPIConfigs = [
@ -58,7 +63,7 @@ export function removeClashAPIConfig({ baseURL, secret }) {
}
export function selectClashAPIConfig({ baseURL, secret }) {
return async (dispatch, getState) => {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const idx = findClashAPIConfigIndex(getState, { baseURL, secret });
const curr = getSelectedClashAPIConfigIndex(getState());
if (curr !== idx) {
@ -81,7 +86,7 @@ export function selectClashAPIConfig({ baseURL, secret }) {
// unused
export function updateClashAPIConfig({ baseURL, secret }) {
return async (dispatch, getState) => {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const clashAPIConfig = { baseURL, secret };
dispatch('appUpdateClashAPIConfig', (s) => {
s.app.clashAPIConfigs[0] = clashAPIConfig;
@ -105,7 +110,7 @@ function setTheme(theme = 'dark') {
}
export function switchTheme() {
return (dispatch, getState) => {
return (dispatch: DispatchFn, getState: GetStateFn) => {
const currentTheme = getTheme(getState());
const theme = currentTheme === 'light' ? 'dark' : 'light';
// side effect
@ -118,8 +123,8 @@ export function switchTheme() {
};
}
export function selectChartStyleIndex(selectedChartStyleIndex) {
return (dispatch, getState) => {
export function selectChartStyleIndex(selectedChartStyleIndex: number) {
return (dispatch: DispatchFn, getState: GetStateFn) => {
dispatch('appSelectChartStyleIndex', (s) => {
s.app.selectedChartStyleIndex = selectedChartStyleIndex;
});
@ -128,8 +133,8 @@ export function selectChartStyleIndex(selectedChartStyleIndex) {
};
}
export function updateAppConfig(name, value) {
return (dispatch, getState) => {
export function updateAppConfig(name: string, value: unknown) {
return (dispatch: DispatchFn, getState: GetStateFn) => {
dispatch('appUpdateAppConfig', (s) => {
s.app[name] = value;
});
@ -138,9 +143,13 @@ export function updateAppConfig(name, value) {
};
}
export function updateCollapsibleIsOpen(prefix, name, v) {
return (dispatch, getState) => {
dispatch('updateCollapsibleIsOpen', (s) => {
export function updateCollapsibleIsOpen(
prefix: string,
name: string,
v: boolean
) {
return (dispatch: DispatchFn, getState: GetStateFn) => {
dispatch('updateCollapsibleIsOpen', (s: State) => {
s.app.collapsibleIsOpen[`${prefix}:${name}`] = v;
});
// side effect
@ -154,7 +163,7 @@ const defaultClashAPIConfig = {
addedAt: 0,
};
// type Theme = 'light' | 'dark';
const defaultState = {
const defaultState: StateApp = {
selectedClashAPIConfigIndex: 0,
clashAPIConfigs: [defaultClashAPIConfig],
@ -172,7 +181,7 @@ const defaultState = {
function parseConfigQueryString() {
const { search } = window.location;
const collector = {};
const collector: Record<string, string> = {};
if (typeof search !== 'string' || search === '') return collector;
const qs = search.replace(/^\?/, '').split('&');
for (let i = 0; i < qs.length; i++) {

View file

@ -1,13 +1,23 @@
import {
ClashGeneralConfig,
DispatchFn,
GetStateFn,
State,
StateConfigs,
} from 'src/store/types';
import { ClashAPIConfig } from 'src/types';
import * as configsAPI from '../api/configs';
import * as trafficAPI from '../api/traffic';
import { openModal } from './modals';
export const getConfigs = (s) => s.configs.configs;
export const getLogLevel = (s) => s.configs.configs['log-level'];
export const getConfigs = (s: State) => s.configs.configs;
export const getHaveFetched = (s: State) => s.configs.haveFetchedConfig;
export const getLogLevel = (s: State) => s.configs.configs['log-level'];
export function fetchConfigs(apiConfig) {
return async (dispatch, getState) => {
let res;
export function fetchConfigs(apiConfig: ClashAPIConfig) {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
let res: Response;
try {
res = await configsAPI.fetchConfigs(apiConfig);
} catch (err) {
@ -28,9 +38,9 @@ export function fetchConfigs(apiConfig) {
s.configs.configs = payload;
});
const configsCurr = getConfigs(getState());
const haveFetchedConfig = getHaveFetched(getState());
if (configsCurr.haveFetchedConfig) {
if (haveFetchedConfig) {
// normally user will land on the "traffic chart" page first
// calling this here will let the data start streaming
// the traffic chart should already subscribed to the streaming
@ -42,15 +52,18 @@ export function fetchConfigs(apiConfig) {
}
function markHaveFetchedConfig() {
return (dispatch) => {
dispatch('store/configs#markHaveFetchedConfig', (s) => {
return (dispatch: DispatchFn) => {
dispatch('store/configs#markHaveFetchedConfig', (s: State) => {
s.configs.haveFetchedConfig = true;
});
};
}
export function updateConfigs(apiConfig, partialConfg) {
return async (dispatch) => {
export function updateConfigs(
apiConfig: ClashAPIConfig,
partialConfg: Partial<ClashGeneralConfig>
) {
return async (dispatch: DispatchFn) => {
configsAPI
.updateConfigs(apiConfig, partialConfg)
.then(
@ -76,7 +89,7 @@ export function updateConfigs(apiConfig, partialConfg) {
};
}
export const initialState = {
export const initialState: StateConfigs = {
configs: {
port: 7890,
'socks-port': 7891,

View file

@ -1,58 +1,23 @@
import { atom } from 'recoil';
/* import { ProxyItem, ProxiesMapping, DelayMapping } from 'src/store/types'; */
import {
DispatchFn,
FormattedProxyProvider,
GetStateFn,
ProxiesMapping,
ProxyItem,
ProxyProvider,
State,
StateProxies,
SwitchProxyCtxItem,
} from 'src/store/types';
import { ClashAPIConfig } from 'src/types';
import * as connAPI from '../api/connections';
import * as proxiesAPI from '../api/proxies';
import { getAutoCloseOldConns, getLatencyTestUrl } from './app';
type PrimitiveProxyType = 'Shadowsocks' | 'Snell' | 'Socks5' | 'Http' | 'Vmess';
type LatencyHistory = Array<{ time: string; delay: number }>;
export type ProxyItem = {
name: string;
type: PrimitiveProxyType;
history: LatencyHistory;
all?: string[];
now?: string;
};
type ProxyProvider = {
name: string;
type: 'Proxy';
updatedAt: string;
vehicleType: 'HTTP' | 'File' | 'Compatible';
proxies: Array<ProxyItem>;
};
type FormattedProxyProvider = Omit<ProxyProvider, 'proxies'> & {
proxies: string[];
};
export type ProxiesMapping = Record<string, ProxyItem>;
export type DelayMapping = Record<string, { number?: number }>;
type SwitchProxyCtxItem = { groupName: string; itemName: string };
type SwitchProxyCtx = {
to: SwitchProxyCtxItem;
};
type ProxiesState = {
proxies: ProxiesMapping;
delay: DelayMapping;
groupNames: string[];
proxyProviders?: FormattedProxyProvider[];
dangleProxyNames?: string[];
showModalClosePrevConns: boolean;
switchProxyCtx?: SwitchProxyCtx;
};
type GlobalState = {
proxies: ProxiesState;
};
export const initialState: ProxiesState = {
export const initialState: StateProxies = {
proxies: {},
delay: {},
groupNames: [],
@ -78,14 +43,12 @@ export const NonProxyTypes = [
'Unknown',
];
export const getProxies = (s: GlobalState) => s.proxies.proxies;
export const getDelay = (s: GlobalState) => s.proxies.delay;
export const getProxyGroupNames = (s: GlobalState) => s.proxies.groupNames;
export const getProxyProviders = (s: GlobalState) =>
s.proxies.proxyProviders || [];
export const getDangleProxyNames = (s: GlobalState) =>
s.proxies.dangleProxyNames;
export const getShowModalClosePrevConns = (s: GlobalState) =>
export const getProxies = (s: State) => s.proxies.proxies;
export const getDelay = (s: State) => s.proxies.delay;
export const getProxyGroupNames = (s: State) => s.proxies.groupNames;
export const getProxyProviders = (s: State) => s.proxies.proxyProviders || [];
export const getDangleProxyNames = (s: State) => s.proxies.dangleProxyNames;
export const getShowModalClosePrevConns = (s: State) =>
s.proxies.showModalClosePrevConns;
export function fetchProxies(apiConfig: ClashAPIConfig) {
@ -120,7 +83,7 @@ export function fetchProxies(apiConfig: ClashAPIConfig) {
if (!providerProxies[v]) dangleProxyNames.push(v);
}
dispatch('store/proxies#fetchProxies', (s: GlobalState) => {
dispatch('store/proxies#fetchProxies', (s: State) => {
s.proxies.proxies = proxies;
s.proxies.groupNames = groupNames;
s.proxies.delay = delayNext;
@ -131,7 +94,7 @@ export function fetchProxies(apiConfig: ClashAPIConfig) {
}
export function updateProviderByName(apiConfig: ClashAPIConfig, name: string) {
return async (dispatch) => {
return async (dispatch: DispatchFn) => {
try {
await proxiesAPI.updateProviderByName(apiConfig, name);
} catch (x) {
@ -143,7 +106,10 @@ export function updateProviderByName(apiConfig: ClashAPIConfig, name: string) {
};
}
async function healthcheckProviderByNameInternal(apiConfig, name) {
async function healthcheckProviderByNameInternal(
apiConfig: ClashAPIConfig,
name: string
) {
try {
await proxiesAPI.healthcheckProviderByName(apiConfig, name);
} catch (x) {
@ -151,8 +117,11 @@ async function healthcheckProviderByNameInternal(apiConfig, name) {
}
}
export function healthcheckProviderByName(apiConfig, name) {
return async (dispatch) => {
export function healthcheckProviderByName(
apiConfig: ClashAPIConfig,
name: string
) {
return async (dispatch: DispatchFn) => {
await healthcheckProviderByNameInternal(apiConfig, name);
// should be optimized
// but ¯\_(ツ)_/¯
@ -206,8 +175,8 @@ function resolveChain(
}
async function switchProxyImpl(
dispatch: any,
getState: () => GlobalState,
dispatch: DispatchFn,
getState: GetStateFn,
apiConfig: ClashAPIConfig,
groupName: string,
itemName: string
@ -243,8 +212,8 @@ async function switchProxyImpl(
}
function closeModalClosePrevConns() {
return (dispatch) => {
dispatch('closeModalClosePrevConns', (s: GlobalState) => {
return (dispatch: DispatchFn) => {
dispatch('closeModalClosePrevConns', (s: State) => {
s.proxies.showModalClosePrevConns = false;
});
};
@ -263,7 +232,7 @@ function closePrevConns(
}
function closePrevConnsAndTheModal(apiConfig: ClashAPIConfig) {
return async (dispatch, getState) => {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const s = getState();
const switchTo = s.proxies.switchProxyCtx?.to;
if (!switchTo) {
@ -276,15 +245,19 @@ function closePrevConnsAndTheModal(apiConfig: ClashAPIConfig) {
const proxies = s.proxies.proxies;
closePrevConns(apiConfig, proxies, switchTo);
dispatch('closePrevConnsAndTheModal', (s: GlobalState) => {
dispatch('closePrevConnsAndTheModal', (s: State) => {
s.proxies.showModalClosePrevConns = false;
s.proxies.switchProxyCtx = undefined;
});
};
}
export function switchProxy(apiConfig, groupName, itemName) {
return async (dispatch, getState) => {
export function switchProxy(
apiConfig: ClashAPIConfig,
groupName: string,
itemName: string
) {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
// switch proxy asynchronously
switchProxyImpl(dispatch, getState, apiConfig, groupName, itemName).catch(
noop
@ -300,8 +273,8 @@ export function switchProxy(apiConfig, groupName, itemName) {
};
}
function requestDelayForProxyOnce(apiConfig, name) {
return async (dispatch, getState) => {
function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const latencyTestUrl = getLatencyTestUrl(getState());
const res = await proxiesAPI.requestDelayForProxy(
apiConfig,
@ -329,14 +302,17 @@ function requestDelayForProxyOnce(apiConfig, name) {
};
}
export function requestDelayForProxy(apiConfig, name) {
return async (dispatch) => {
export function requestDelayForProxy(apiConfig: ClashAPIConfig, name: string) {
return async (dispatch: DispatchFn) => {
await dispatch(requestDelayForProxyOnce(apiConfig, name));
};
}
export function requestDelayForProxies(apiConfig, names) {
return async (dispatch, getState) => {
export function requestDelayForProxies(
apiConfig: ClashAPIConfig,
names: string[]
) {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const proxyNames = getDangleProxyNames(getState());
const works = names
@ -348,8 +324,8 @@ export function requestDelayForProxies(apiConfig, names) {
};
}
export function requestDelayAll(apiConfig) {
return async (dispatch, getState) => {
export function requestDelayAll(apiConfig: ClashAPIConfig) {
return async (dispatch: DispatchFn, getState: GetStateFn) => {
const proxyNames = getDangleProxyNames(getState());
await Promise.all(
proxyNames.map((p) => dispatch(requestDelayForProxy(apiConfig, p)))
@ -363,9 +339,9 @@ export function requestDelayAll(apiConfig) {
};
}
function retrieveGroupNamesFrom(proxies) {
function retrieveGroupNamesFrom(proxies: Record<string, ProxyItem>) {
let groupNames = [];
let globalAll;
let globalAll: string[];
const proxyNames = [];
for (const prop in proxies) {
const p = proxies[prop];

91
src/store/types.ts Normal file
View file

@ -0,0 +1,91 @@
import type { ClashAPIConfig } from 'src/types';
export type ClashAPIConfigWithAddedAt = ClashAPIConfig & { addedAt?: number };
export type StateApp = {
selectedClashAPIConfigIndex: number;
clashAPIConfigs: ClashAPIConfigWithAddedAt[];
latencyTestUrl: string;
selectedChartStyleIndex: number;
theme: string;
collapsibleIsOpen: Record<string, boolean>;
proxySortBy: string;
hideUnavailableProxies: boolean;
autoCloseOldConns: boolean;
};
export type ClashGeneralConfig = {
port: number;
'socks-port': number;
'redir-port': number;
'allow-lan': boolean;
mode: string;
'log-level': string;
};
///// store.proxies
type LatencyHistory = Array<{ time: string; delay: number }>;
type PrimitiveProxyType = 'Shadowsocks' | 'Snell' | 'Socks5' | 'Http' | 'Vmess';
export type ProxyItem = {
name: string;
type: PrimitiveProxyType;
history: LatencyHistory;
all?: string[];
now?: string;
};
export type ProxiesMapping = Record<string, ProxyItem>;
export type DelayMapping = Record<string, { number?: number }>;
export type ProxyProvider = {
name: string;
type: 'Proxy';
updatedAt: string;
vehicleType: 'HTTP' | 'File' | 'Compatible';
proxies: Array<ProxyItem>;
};
export type FormattedProxyProvider = Omit<ProxyProvider, 'proxies'> & {
proxies: string[];
};
export type SwitchProxyCtxItem = { groupName: string; itemName: string };
type SwitchProxyCtx = {
to: SwitchProxyCtxItem;
};
export type StateProxies = {
proxies: ProxiesMapping;
delay: DelayMapping;
groupNames: string[];
proxyProviders?: FormattedProxyProvider[];
dangleProxyNames?: string[];
showModalClosePrevConns: boolean;
switchProxyCtx?: SwitchProxyCtx;
};
///// store.configs
export type StateConfigs = {
configs: ClashGeneralConfig;
haveFetchedConfig: boolean;
};
//////
export type State = {
app: StateApp;
configs: StateConfigs;
proxies: StateProxies;
};
export type GetStateFn = () => State;
export interface DispatchFn {
(msg: string, change: (s: State) => void): void;
(
action: (dispatch: DispatchFn, getState: GetStateFn) => Promise<void>
): ReturnType<typeof action>;
(action: (dispatch: DispatchFn, getState: GetStateFn) => void): ReturnType<
typeof action
>;
}

View file

@ -93,7 +93,7 @@ module.exports = {
// https://webpack.js.org/configuration/devtool/
devtool: isDev ? 'eval-source-map' : 'source-map',
entry: {
app: { import: ['./src/app.js'], dependOn: 'libs' },
app: { import: ['./src/app.tsx'], dependOn: 'libs' },
libs: { import: ['react-query', 'react-modal'], dependOn: 'vendor' },
vendor: { import: ['react', 'react-dom'], dependOn: 'corejs' },
corejs: { import: 'core-js' },

View file

@ -1335,6 +1335,20 @@
dependencies:
"@types/react" "*"
"@types/react-helmet@^6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.0.tgz#af586ed685f4905e2adc7462d1d65ace52beee7a"
integrity sha512-PYRoU1XJFOzQ3BHvWL1T8iDNbRjdMDJMT5hFmZKGbsq09kbSqJy61uwEpTrbTNWDopVphUT34zUSVLK9pjsgYQ==
dependencies:
"@types/react" "*"
"@types/react-tabs@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/react-tabs/-/react-tabs-2.3.2.tgz#99fb6866bbc6912d44f7bbc99eca03fbbd217960"
integrity sha512-QfMelaJSdMcp+CenKhATp12XFFqqUcLXILgwpX3dgWfVYNZPtsLXZDDCRsVn+kwmBOWB+DFPKpQorxbUhnXINw==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^16.9.52":
version "16.9.55"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.55.tgz#47078587f5bfe028a23b6b46c7b94ac0d436acff"
@ -5974,6 +5988,11 @@ react-dom@0.0.0-experimental-94c0244ba:
object-assign "^4.1.1"
scheduler "0.0.0-experimental-94c0244ba"
react-fast-compare@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-feather@^2.0.3:
version "2.0.8"
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.8.tgz#455baf1470f756a57e2ad6c72545444ce5925781"
@ -5981,6 +6000,16 @@ react-feather@^2.0.3:
dependencies:
prop-types "^15.7.2"
react-helmet@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==
dependencies:
object-assign "^4.1.1"
prop-types "^15.7.2"
react-fast-compare "^3.1.1"
react-side-effect "^2.1.0"
react-icons@^3.10.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.11.0.tgz#2ca2903dfab8268ca18ebd8cc2e879921ec3b254"
@ -6040,6 +6069,11 @@ react-router@6.0.0-beta.0:
dependencies:
prop-types "^15.7.2"
react-side-effect@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.0.tgz#1ce4a8b4445168c487ed24dab886421f74d380d3"
integrity sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==
react-switch@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-switch/-/react-switch-5.0.1.tgz#449277f4c3aed5286fffd0f50d5cbc2a23330406"