From 6a402afa3f16b8cc00a939b72250b5aaabaf5b7d Mon Sep 17 00:00:00 2001 From: Haishan Date: Sun, 5 Jun 2022 23:40:25 +0800 Subject: Fix theme switcher button shape on iOS --- src/components/APIDiscovery.tsx | 6 ++---- src/components/Config.tsx | 21 ++++++--------------- src/components/Input.tsx | 17 ++++------------- src/components/shared/ThemeSwitcher.module.scss | 3 ++- 4 files changed, 14 insertions(+), 33 deletions(-) (limited to 'src/components') diff --git a/src/components/APIDiscovery.tsx b/src/components/APIDiscovery.tsx index f34c886..d211e04 100644 --- a/src/components/APIDiscovery.tsx +++ b/src/components/APIDiscovery.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ThemeSwitcher } from 'src/components/shared/ThemeSwitcher'; -import { DOES_NOT_SUPPORT_FETCH, errors } from 'src/misc/errors'; +import { DOES_NOT_SUPPORT_FETCH, errors, YacdError } from 'src/misc/errors'; import { getClashAPIConfig } from 'src/store/app'; import { fetchConfigs } from 'src/store/configs'; import { closeModal } from 'src/store/modals'; @@ -16,9 +16,7 @@ const { useCallback, useEffect } = React; 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; + const err = new YacdError(detail, DOES_NOT_SUPPORT_FETCH); throw err; } diff --git a/src/components/Config.tsx b/src/components/Config.tsx index 34bb20a..6107789 100644 --- a/src/components/Config.tsx +++ b/src/components/Config.tsx @@ -6,11 +6,7 @@ import Select from 'src/components/shared/Select'; import { ClashGeneralConfig, DispatchFn, State } from 'src/store/types'; import { ClashAPIConfig } from 'src/types'; -import { - getClashAPIConfig, - getLatencyTestUrl, - getSelectedChartStyleIndex, -} from '../store/app'; +import { getClashAPIConfig, getLatencyTestUrl, getSelectedChartStyleIndex } from '../store/app'; import { fetchConfigs, getConfigs, updateConfigs } from '../store/configs'; import { openModal } from '../store/modals'; import Button from './Button'; @@ -103,7 +99,7 @@ function ConfigImpl({ }, [dispatch]); const setConfigState = useCallback( - (name, val) => { + (name: keyof ClashGeneralConfig, val: ClashGeneralConfig[keyof ClashGeneralConfig]) => { setConfigStateInternal({ ...configState, [name]: val }); }, [configState] @@ -147,14 +143,14 @@ function ConfigImpl({ [apiConfig, dispatch, setConfigState] ); - const handleInputOnChange = useCallback( + const handleInputOnChange = useCallback>( (e) => handleChangeValue(e.target), [handleChangeValue] ); const { selectChartStyleIndex, updateAppConfig } = useStoreActions(); - const handleInputOnBlur = useCallback( + const handleInputOnBlur = useCallback>( (e) => { const target = e.target; const { name, value } = target; @@ -198,7 +194,6 @@ 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} /> @@ -210,9 +205,7 @@ function ConfigImpl({ - handleChangeValue({ name: 'log-level', value: e.target.value }) - } + onChange={(e) => handleChangeValue({ name: 'log-level', value: e.target.value })} /> diff --git a/src/components/Input.tsx b/src/components/Input.tsx index c132a3b..efb5665 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -7,7 +7,8 @@ const { useState, useRef, useEffect, useCallback } = React; type InputProps = { value?: string | number; type?: string; - onChange?: (...args: any[]) => any; + onChange?: React.ChangeEventHandler; + onBlur?: React.FocusEventHandler; name?: string; placeholder?: string; }; @@ -26,17 +27,7 @@ export function SelfControlledInput({ value, ...restProps }) { } refValue.current = value; }, [value]); - const onChange = useCallback( - (e) => setInternalValue(e.target.value), - [setInternalValue] - ); + const onChange = useCallback((e) => setInternalValue(e.target.value), [setInternalValue]); - return ( - - ); + return ; } diff --git a/src/components/shared/ThemeSwitcher.module.scss b/src/components/shared/ThemeSwitcher.module.scss index c5de126..951376a 100644 --- a/src/components/shared/ThemeSwitcher.module.scss +++ b/src/components/shared/ThemeSwitcher.module.scss @@ -29,7 +29,8 @@ height: var(--sz); select { cursor: pointer; - padding-left: var(--sz); + padding-left: calc(var(--sz) - 2px); + font-size: 0; width: var(--sz); height: var(--sz); appearance: none; -- cgit v1.3.1 From 7f7cb27df139d6ca73031789539ea2ba2f2ecb77 Mon Sep 17 00:00:00 2001 From: Haishan Date: Mon, 6 Jun 2022 00:02:54 +0800 Subject: Make button slightly bigger than before --- src/components/Button.module.scss | 10 +++++----- src/components/Button.tsx | 10 ++++------ src/components/rules/RuleProviderItem.tsx | 12 ++++-------- src/components/shared/RotateIcon.tsx | 2 +- 4 files changed, 14 insertions(+), 20 deletions(-) (limited to 'src/components') diff --git a/src/components/Button.module.scss b/src/components/Button.module.scss index 3046d0c..710d3ad 100644 --- a/src/components/Button.module.scss +++ b/src/components/Button.module.scss @@ -23,16 +23,16 @@ transform: scale(0.97); } - font-size: 0.85em; - padding: 4px 7px; - @media (--breakpoint-not-small) { - font-size: 1em; - padding: 6px 12px; + padding: 10px 13px; + + &.circular { + padding: 8px; } &.minimal { border-color: transparent; background: none; + padding: 6px 12px; &:focus { border-color: var(--color-focus-blue); } diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 1725d1b..8125edc 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -17,7 +17,7 @@ type ButtonProps = { isLoading?: boolean; onClick?: (e: React.MouseEvent) => unknown; disabled?: boolean; - kind?: 'primary' | 'minimal'; + kind?: 'primary' | 'minimal' | 'circular'; className?: string; title?: string; } & ButtonInternalProps; @@ -36,7 +36,7 @@ function Button(props: ButtonProps, ref: React.Ref) { ...restProps } = props; const internalProps = { children, label, text, start }; - const internalOnClick = useCallback( + const internalOnClick = useCallback>( (e) => { if (isLoading) return; onClick && onClick(e); @@ -45,7 +45,7 @@ function Button(props: ButtonProps, ref: React.Ref) { ); const btnClassName = cx( s0.btn, - { [s0.minimal]: kind === 'minimal' }, + { [s0.minimal]: kind === 'minimal', [s0.circular]: kind === 'circular' }, className ); return ( @@ -76,9 +76,7 @@ function ButtonInternal({ children, label, text, start }: ButtonInternalProps) { return ( <> {start ? ( - - {typeof start === 'function' ? start() : start} - + {typeof start === 'function' ? start() : start} ) : null} {children || label || text} diff --git a/src/components/rules/RuleProviderItem.tsx b/src/components/rules/RuleProviderItem.tsx index fe4610e..fc88ae1 100644 --- a/src/components/rules/RuleProviderItem.tsx +++ b/src/components/rules/RuleProviderItem.tsx @@ -16,24 +16,20 @@ export function RuleProviderItem({ ruleCount, apiConfig, }) { - const [onClickRefreshButton, isRefreshing] = useUpdateRuleProviderItem( - name, - apiConfig - ); + const [onClickRefreshButton, isRefreshing] = useUpdateRuleProviderItem(name, apiConfig); const timeAgo = formatDistance(new Date(updatedAt), new Date()); return (
{idx}
-
- {ruleCount < 2 ? `${ruleCount} rule` : `${ruleCount} rules`} -
+
{ruleCount < 2 ? `${ruleCount} rule` : `${ruleCount} rules`}
Updated {timeAgo} ago
-
diff --git a/src/components/shared/RotateIcon.tsx b/src/components/shared/RotateIcon.tsx index 7e3ceae..d9dcbd9 100644 --- a/src/components/shared/RotateIcon.tsx +++ b/src/components/shared/RotateIcon.tsx @@ -10,7 +10,7 @@ export function RotateIcon({ isRotating }: { isRotating: boolean }) { }); return ( - + ); } -- cgit v1.3.1 From 23e734aa548354bb7ceff5ad8d85de95cd860a55 Mon Sep 17 00:00:00 2001 From: Haishan Date: Mon, 6 Jun 2022 00:27:48 +0800 Subject: Add more space between overview stats cards --- src/components/TrafficNow.module.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/components') diff --git a/src/components/TrafficNow.module.scss b/src/components/TrafficNow.module.scss index 47aee12..b1883d5 100644 --- a/src/components/TrafficNow.module.scss +++ b/src/components/TrafficNow.module.scss @@ -4,10 +4,12 @@ align-items: center; flex-wrap: wrap; + display: grid; + grid-template-columns: repeat(auto-fit, 180px); + grid-gap: 10px; + .sec { padding: 10px; - width: 180px; - margin: 3px; background-color: var(--color-bg-card); border-radius: 10px; box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.1); -- cgit v1.3.1 From 78f3434cb52f53c66936de278f0828c19ef63666 Mon Sep 17 00:00:00 2001 From: Haishan Date: Mon, 6 Jun 2022 23:39:56 +0800 Subject: Run prettier --- src/api/configs.ts | 5 +- src/api/connections.ts | 5 +- src/api/proxies.ts | 5 +- src/api/rule-provider.ts | 5 +- src/api/rules.ts | 4 +- src/api/traffic.ts | 2 +- src/components/APIConfig.tsx | 5 +- src/components/BackendList.tsx | 11 +--- src/components/Collapsible.tsx | 13 +---- src/components/CollapsibleSectionHeader.tsx | 7 +-- src/components/ConnectionTable.tsx | 5 +- src/components/Logs.tsx | 2 +- src/components/Rules.tsx | 2 +- src/components/Search.tsx | 7 +-- src/components/SideBar.tsx | 9 +--- src/components/SvgYacd.tsx | 7 +-- src/components/ToggleSwitch.tsx | 5 +- src/components/TrafficChart.tsx | 10 ++-- src/components/about/About.tsx | 29 ++-------- src/components/proxies/ClosePrevConns.tsx | 9 ++-- src/components/proxies/Proxies.tsx | 20 ++----- src/components/proxies/Proxy.module.scss | 3 -- src/components/proxies/Proxy.tsx | 27 ++-------- src/components/proxies/ProxyGroup.module.scss | 9 ++++ src/components/proxies/ProxyGroup.tsx | 23 +++----- src/components/proxies/ProxyList.tsx | 7 +-- src/components/proxies/ProxyPageFab.tsx | 7 +-- src/components/proxies/ProxyProvider.tsx | 7 +-- src/components/proxies/ProxyProviderList.tsx | 6 +-- src/components/proxies/Settings.tsx | 6 +-- src/components/proxies/hooks.tsx | 12 +---- src/components/proxies/proxies.hooks.tsx | 9 ++-- src/components/rules/RuleProviderItem.module.scss | 13 ++--- src/components/rules/RuleProviderItem.tsx | 14 ++--- src/components/shared/Fab.tsx | 12 ++--- src/components/shared/RotateIcon.tsx | 9 ++-- src/components/shared/TextFitler.tsx | 5 +- src/components/svg/Equalizer.tsx | 5 +- src/hooks/useRemainingViewPortHeight.ts | 7 +-- src/hooks/useTextInput.ts | 4 +- src/misc/chart.ts | 4 +- src/misc/i18n.ts | 5 +- src/misc/motion.ts | 4 +- src/misc/request-helper.ts | 6 +-- src/misc/shallowEqual.ts | 7 +-- src/misc/utils.ts | 10 +--- src/store/app.ts | 4 +- src/store/configs.ts | 8 +-- src/store/proxies.tsx | 65 ++++++----------------- src/sw.ts | 3 +- src/swRegistration.ts | 12 ++--- src/types.ts | 2 +- 52 files changed, 127 insertions(+), 355 deletions(-) (limited to 'src/components') diff --git a/src/api/configs.ts b/src/api/configs.ts index 69b02d4..3646f1a 100644 --- a/src/api/configs.ts +++ b/src/api/configs.ts @@ -22,10 +22,7 @@ function configsPatchWorkaround(o: ClashConfigPartial) { return o; } -export async function updateConfigs( - apiConfig: ClashAPIConfig, - o: ClashConfigPartial -) { +export async function updateConfigs(apiConfig: ClashAPIConfig, o: ClashConfigPartial) { const { url, init } = getURLAndInit(apiConfig); const body = JSON.stringify(configsPatchWorkaround(o)); return await fetch(url + endpoint, { ...init, body, method: 'PATCH' }); diff --git a/src/api/connections.ts b/src/api/connections.ts index 9c94d31..60fd4c5 100644 --- a/src/api/connections.ts +++ b/src/api/connections.ts @@ -51,10 +51,7 @@ function appendData(s: string) { type UnsubscribeFn = () => void; let wsState: number; -export function fetchData( - apiConfig: ClashAPIConfig, - listener: unknown -): UnsubscribeFn | void { +export function fetchData(apiConfig: ClashAPIConfig, listener: unknown): UnsubscribeFn | void { if (fetched || wsState === 1) { if (listener) return subscribe(listener); } diff --git a/src/api/proxies.ts b/src/api/proxies.ts index 6b3e28e..e11fd9e 100644 --- a/src/api/proxies.ts +++ b/src/api/proxies.ts @@ -59,8 +59,5 @@ export async function updateProviderByName(config, name) { export async function healthcheckProviderByName(config, name) { const { url, init } = getURLAndInit(config); const options = { ...init, method: 'GET' }; - return await fetch( - url + '/providers/proxies/' + name + '/healthcheck', - options - ); + return await fetch(url + '/providers/proxies/' + name + '/healthcheck', options); } diff --git a/src/api/rule-provider.ts b/src/api/rule-provider.ts index ec9fa7b..5ecd61e 100644 --- a/src/api/rule-provider.ts +++ b/src/api/rule-provider.ts @@ -31,10 +31,7 @@ function normalizeAPIResponse(data: RuleProviderAPIData) { return { byName, names }; } -export async function fetchRuleProviders( - endpoint: string, - apiConfig: ClashAPIConfig -) { +export async function fetchRuleProviders(endpoint: string, apiConfig: ClashAPIConfig) { const { url, init } = getURLAndInit(apiConfig); let data = { providers: {} }; diff --git a/src/api/rules.ts b/src/api/rules.ts index b57b0e3..4d18c23 100644 --- a/src/api/rules.ts +++ b/src/api/rules.ts @@ -12,9 +12,7 @@ type RuleAPIItem = { proxy: string; }; -function normalizeAPIResponse(json: { - rules: Array; -}): Array { +function normalizeAPIResponse(json: { rules: Array }): Array { invariant( json.rules && json.rules.length >= 0, 'there is no valid rules list in the rules API response' diff --git a/src/api/traffic.ts b/src/api/traffic.ts index cd18aac..aa9143c 100644 --- a/src/api/traffic.ts +++ b/src/api/traffic.ts @@ -27,7 +27,7 @@ const traffic = { this.subscribers.forEach((f) => f(o)); }, - subscribe(listener: (x:any) => void) { + subscribe(listener: (x: any) => void) { this.subscribers.push(listener); return () => { const idx = this.subscribers.indexOf(listener); diff --git a/src/components/APIConfig.tsx b/src/components/APIConfig.tsx index 6e11bc4..4a11c92 100644 --- a/src/components/APIConfig.tsx +++ b/src/components/APIConfig.tsx @@ -72,9 +72,9 @@ function APIConfig({ dispatch }) { const detectApiServer = async () => { // if there is already a clash API server at `/`, just use it as default value const res = await fetch('/'); - res.json().then(data => { + res.json().then((data) => { if (data['hello'] === 'clash') { - setBaseURL(window.location.origin) + setBaseURL(window.location.origin); } }); }; @@ -82,7 +82,6 @@ function APIConfig({ dispatch }) { detectApiServer(); }, []); - return ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions
diff --git a/src/components/BackendList.tsx b/src/components/BackendList.tsx index 8e0d906..e4f4d80 100644 --- a/src/components/BackendList.tsx +++ b/src/components/BackendList.tsx @@ -2,10 +2,7 @@ import cx from 'clsx'; import * as React from 'react'; import { Eye, EyeOff, X as Close } from 'react-feather'; import { useToggle } from 'src/hooks/basic'; -import { - getClashAPIConfigs, - getSelectedClashAPIConfigIndex, -} from 'src/store/app'; +import { getClashAPIConfigs, getSelectedClashAPIConfigIndex } from 'src/store/app'; import { ClashAPIConfig } from 'src/types'; import s from './BackendList.module.scss'; @@ -137,11 +134,7 @@ function Button({ disabled?: boolean; }) { return ( - ); diff --git a/src/components/Collapsible.tsx b/src/components/Collapsible.tsx index e9a1ee8..819bc7c 100644 --- a/src/components/Collapsible.tsx +++ b/src/components/Collapsible.tsx @@ -60,20 +60,11 @@ const Collapsible = memo(({ children, isOpen }) => { return (
- + {children} diff --git a/src/components/CollapsibleSectionHeader.tsx b/src/components/CollapsibleSectionHeader.tsx index 2d5ecd1..8b701e1 100644 --- a/src/components/CollapsibleSectionHeader.tsx +++ b/src/components/CollapsibleSectionHeader.tsx @@ -39,12 +39,7 @@ export default function Header({ name, type, toggle, isOpen, qty }: Props) { {typeof qty === 'number' ? {qty} : null} - + Updated {timeAgo} ago +
- - - ); } diff --git a/src/components/shared/Fab.tsx b/src/components/shared/Fab.tsx index 832306e..8e72432 100644 --- a/src/components/shared/Fab.tsx +++ b/src/components/shared/Fab.tsx @@ -28,8 +28,7 @@ const AB: React.FC = ({ children, ...p }) => ( ); -interface MBProps - extends Omit, 'tabIndex'> { +interface MBProps extends Omit, 'tabIndex'> { tabIndex?: number; } @@ -77,10 +76,7 @@ const Fab: React.FC = ({ return event === 'click' ? (isOpen ? close() : open()) : null; }; - const actionOnClick = ( - e: React.FormEvent, - userFunc: (e: React.FormEvent) => void - ) => { + const actionOnClick = (e: React.FormEvent, userFunc: (e: React.FormEvent) => void) => { e.persist(); setIsOpen(false); setTimeout(() => { @@ -141,9 +137,7 @@ const Fab: React.FC = ({ {text && ( {text} diff --git a/src/components/shared/RotateIcon.tsx b/src/components/shared/RotateIcon.tsx index d9dcbd9..d291ece 100644 --- a/src/components/shared/RotateIcon.tsx +++ b/src/components/shared/RotateIcon.tsx @@ -4,13 +4,12 @@ import { RotateCw } from 'react-feather'; import s from './RotateIcon.module.scss'; -export function RotateIcon({ isRotating }: { isRotating: boolean }) { - const cls = cx(s.rotate, { - [s.isRotating]: isRotating, - }); +export function RotateIcon(props: { isRotating: boolean; size?: number }) { + const size = props.size || 16; + const cls = cx(s.rotate, { [s.isRotating]: props.isRotating }); return ( - + ); } diff --git a/src/components/shared/TextFitler.tsx b/src/components/shared/TextFitler.tsx index e4a4a88..7af61ac 100644 --- a/src/components/shared/TextFitler.tsx +++ b/src/components/shared/TextFitler.tsx @@ -4,10 +4,7 @@ import { useTextInut } from 'src/hooks/useTextInput'; import s from './TextFitler.module.scss'; -export function TextFilter(props: { - textAtom: RecoilState; - placeholder?: string; -}) { +export function TextFilter(props: { textAtom: RecoilState; placeholder?: string }) { const [onChange, text] = useTextInut(props.textAtom); return ( (): [React.MutableRefObject, number] { +export default function useRemainingViewPortHeight(): [ + React.MutableRefObject, + number +] { const ref = useRef(null); const [containerHeight, setContainerHeight] = useState(200); const updateContainerHeight = useCallback(() => { diff --git a/src/hooks/useTextInput.ts b/src/hooks/useTextInput.ts index 1fa19f7..853044c 100644 --- a/src/hooks/useTextInput.ts +++ b/src/hooks/useTextInput.ts @@ -9,9 +9,7 @@ export function useTextInut( ): [(e: React.ChangeEvent) => void, string] { const [, setTextGlobal] = useRecoilState(x); const [text, setText] = useState(''); - const setTextDebounced = useMemo(() => debounce(setTextGlobal, 300), [ - setTextGlobal, - ]); + const setTextDebounced = useMemo(() => debounce(setTextGlobal, 300), [setTextGlobal]); const onChange = useCallback( (e: React.ChangeEvent) => { setText(e.target.value); diff --git a/src/misc/chart.ts b/src/misc/chart.ts index c62fa06..56e05a2 100644 --- a/src/misc/chart.ts +++ b/src/misc/chart.ts @@ -1,4 +1,4 @@ -import { createAsset } from "use-asset" +import { createAsset } from 'use-asset'; import prettyBytes from './pretty-bytes'; export const chartJSResource = createAsset(() => { @@ -11,7 +11,7 @@ export const commonChartOptions: import('chart.js').ChartOptions<'line'> = { responsive: true, maintainAspectRatio: true, plugins: { - legend: { labels: { boxWidth: 20 } } + legend: { labels: { boxWidth: 20 } }, }, scales: { x: { display: false, type: 'category' }, diff --git a/src/misc/i18n.ts b/src/misc/i18n.ts index cf1a4f2..eecd72a 100644 --- a/src/misc/i18n.ts +++ b/src/misc/i18n.ts @@ -8,10 +8,7 @@ const allLocales = { en: import('src/i18n/en'), }; -type BackendRequestCallback = ( - err: null, - result: { status: number; data: any } -) => void; +type BackendRequestCallback = (err: null, result: { status: number; data: any }) => void; i18next .use(HttpBackend) diff --git a/src/misc/motion.ts b/src/misc/motion.ts index 5f2fe50..7fac864 100644 --- a/src/misc/motion.ts +++ b/src/misc/motion.ts @@ -1,5 +1,3 @@ import { createResource } from './createResource'; -export const framerMotionResouce = createResource( - () => import('framer-motion') -); +export const framerMotionResouce = createResource(() => import('framer-motion')); diff --git a/src/misc/request-helper.ts b/src/misc/request-helper.ts index 3bc8476..c01b994 100644 --- a/src/misc/request-helper.ts +++ b/src/misc/request-helper.ts @@ -12,7 +12,7 @@ function genCommonHeaders({ secret }: { secret?: string }) { return h; } function buildWebSocketURLBase(baseURL: string, params: URLSearchParams, endpoint: string) { - const qs = '?' + params.toString() + const qs = '?' + params.toString(); const url = new URL(baseURL); url.protocol === 'https:' ? (url.protocol = 'wss:') : (url.protocol = 'ws:'); return `${trimTrailingSlash(url.href)}${endpoint}${qs}`; @@ -32,7 +32,7 @@ export function buildWebSocketURL(apiConfig: ClashAPIConfig, endpoint: string) { token: secret, }); - return buildWebSocketURLBase(baseURL, params, endpoint) + return buildWebSocketURLBase(baseURL, params, endpoint); } export function buildLogsWebSocketURL(apiConfig: LogsAPIConfig, endpoint: string) { @@ -42,5 +42,5 @@ export function buildLogsWebSocketURL(apiConfig: LogsAPIConfig, endpoint: string level: logLevel, }); - return buildWebSocketURLBase(baseURL, params, endpoint) + return buildWebSocketURLBase(baseURL, params, endpoint); } diff --git a/src/misc/shallowEqual.ts b/src/misc/shallowEqual.ts index 241b725..937bc27 100644 --- a/src/misc/shallowEqual.ts +++ b/src/misc/shallowEqual.ts @@ -12,12 +12,7 @@ function is(x, y) { export default function shallowEqual(objA, objB) { if (is(objA, objB)) return true; - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { + if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } diff --git a/src/misc/utils.ts b/src/misc/utils.ts index d3a7bfc..9497026 100644 --- a/src/misc/utils.ts +++ b/src/misc/utils.ts @@ -1,7 +1,4 @@ -export function throttle( - fn: (...args: T) => unknown, - timeout: number -) { +export function throttle(fn: (...args: T) => unknown, timeout: number) { let pending = false; return (...args: T) => { @@ -15,10 +12,7 @@ export function throttle( }; } -export function debounce( - fn: (...args: T) => unknown, - timeout: number -) { +export function debounce(fn: (...args: T) => unknown, timeout: number) { let timeoutId: ReturnType; return (...args: T) => { if (timeoutId) clearTimeout(timeoutId); diff --git a/src/store/app.ts b/src/store/app.ts index 7262b32..2789981 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -152,7 +152,7 @@ export function updateCollapsibleIsOpen(prefix: string, name: string, v: boolean const defaultClashAPIConfig = { baseURL: document.getElementById('app')?.getAttribute('data-base-url') ?? 'http://127.0.0.1:9090', secret: '', - addedAt: 0 + addedAt: 0, }; // type Theme = 'light' | 'dark'; const defaultState: StateApp = { @@ -169,7 +169,7 @@ const defaultState: StateApp = { proxySortBy: 'Natural', hideUnavailableProxies: false, autoCloseOldConns: false, - logStreamingPaused: false + logStreamingPaused: false, }; function parseConfigQueryString() { diff --git a/src/store/configs.ts b/src/store/configs.ts index c533508..7571b4e 100644 --- a/src/store/configs.ts +++ b/src/store/configs.ts @@ -1,10 +1,4 @@ -import { - ClashGeneralConfig, - DispatchFn, - GetStateFn, - State, - StateConfigs, -} from 'src/store/types'; +import { ClashGeneralConfig, DispatchFn, GetStateFn, State, StateConfigs } from 'src/store/types'; import { ClashAPIConfig } from 'src/types'; import * as configsAPI from '../api/configs'; diff --git a/src/store/proxies.tsx b/src/store/proxies.tsx index 25200cd..0505fce 100644 --- a/src/store/proxies.tsx +++ b/src/store/proxies.tsx @@ -48,8 +48,7 @@ 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 const getShowModalClosePrevConns = (s: State) => s.proxies.showModalClosePrevConns; export function fetchProxies(apiConfig: ClashAPIConfig) { return async (dispatch: any, getState: any) => { @@ -58,10 +57,9 @@ export function fetchProxies(apiConfig: ClashAPIConfig) { proxiesAPI.fetchProviderProxies(apiConfig), ]); - const { - providers: proxyProviders, - proxies: providerProxies, - } = formatProxyProviders(providersData.providers); + const { providers: proxyProviders, proxies: providerProxies } = formatProxyProviders( + providersData.providers + ); const proxies = { ...providerProxies, ...proxiesData.proxies }; const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies); @@ -121,10 +119,7 @@ export function updateProviders(apiConfig: ClashAPIConfig, names: string[]) { }; } -async function healthcheckProviderByNameInternal( - apiConfig: ClashAPIConfig, - name: string -) { +async function healthcheckProviderByNameInternal(apiConfig: ClashAPIConfig, name: string) { try { await proxiesAPI.healthcheckProviderByName(apiConfig, name); } catch (x) { @@ -132,10 +127,7 @@ async function healthcheckProviderByNameInternal( } } -export function healthcheckProviderByName( - apiConfig: ClashAPIConfig, - name: string -) { +export function healthcheckProviderByName(apiConfig: ClashAPIConfig, name: string) { return async (dispatch: DispatchFn) => { await healthcheckProviderByNameInternal(apiConfig, name); // should be optimized @@ -168,16 +160,10 @@ async function closeGroupConns( } } - await Promise.all( - idsToClose.map((id) => connAPI.closeConnById(apiConfig, id).catch(noop)) - ); + await Promise.all(idsToClose.map((id) => connAPI.closeConnById(apiConfig, id).catch(noop))); } -function resolveChain( - proxies: ProxiesMapping, - groupName: string, - itemName: string -) { +function resolveChain(proxies: ProxiesMapping, groupName: string, itemName: string) { const chain = [itemName, groupName]; let child: ProxyItem; @@ -197,11 +183,7 @@ async function switchProxyImpl( itemName: string ) { try { - const res = await proxiesAPI.requestToSwitchProxy( - apiConfig, - groupName, - itemName - ); + const res = await proxiesAPI.requestToSwitchProxy(apiConfig, groupName, itemName); if (res.ok === false) { throw new Error(`failed to switch proxy: res.statusText`); } @@ -267,16 +249,10 @@ function closePrevConnsAndTheModal(apiConfig: ClashAPIConfig) { }; } -export function switchProxy( - apiConfig: ClashAPIConfig, - groupName: string, - itemName: string -) { +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 - ); + switchProxyImpl(dispatch, getState, apiConfig, groupName, itemName).catch(noop); // optimistic UI update dispatch('store/proxies#switchProxy', (s) => { @@ -291,11 +267,7 @@ export function switchProxy( function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) { return async (dispatch: DispatchFn, getState: GetStateFn) => { const latencyTestUrl = getLatencyTestUrl(getState()); - const res = await proxiesAPI.requestDelayForProxy( - apiConfig, - name, - latencyTestUrl - ); + const res = await proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl); let error = ''; if (res.ok === false) { error = res.statusText; @@ -323,10 +295,7 @@ export function requestDelayForProxy(apiConfig: ClashAPIConfig, name: string) { }; } -export function requestDelayForProxies( - apiConfig: ClashAPIConfig, - names: string[] -) { +export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) { return async (dispatch: DispatchFn, getState: GetStateFn) => { const proxyNames = getDangleProxyNames(getState()); @@ -342,9 +311,7 @@ export function requestDelayForProxies( 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))) - ); + await Promise.all(proxyNames.map((p) => dispatch(requestDelayForProxy(apiConfig, p)))); const proxyProviders = getProxyProviders(getState()); // one by one for (const p of proxyProviders) { @@ -385,9 +352,7 @@ type ProvidersRaw = { [key: string]: ProxyProvider; }; -function formatProxyProviders( - providersInput: ProvidersRaw -): { +function formatProxyProviders(providersInput: ProvidersRaw): { providers: Array; proxies: { [key: string]: ProxyItem }; } { diff --git a/src/sw.ts b/src/sw.ts index bdc663c..f577677 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -53,8 +53,7 @@ registerRoute( // precache, in this case same-origin .png requests like those from in public/ registerRoute( // Add in any other file extensions or routing criteria as needed. - ({ url }) => - url.origin === self.location.origin && url.pathname.endsWith('.png'), + ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst. new StaleWhileRevalidate({ cacheName: 'images', diff --git a/src/swRegistration.ts b/src/swRegistration.ts index 55ceb0b..0a684a8 100644 --- a/src/swRegistration.ts +++ b/src/swRegistration.ts @@ -3,9 +3,7 @@ const isLocalhost = Boolean( // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) ); type Config = { @@ -34,9 +32,7 @@ export function register(config?: Config) { // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service worker' - ); + console.log('This web app is being served cache-first by a service worker'); }); } else { // Is not localhost. Just register service worker @@ -114,9 +110,7 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { } }) .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); + console.log('No internet connection found. App is running in offline mode.'); }); } diff --git a/src/types.ts b/src/types.ts index 694289b..8446dfc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,4 +3,4 @@ export type ClashAPIConfig = { secret?: string; }; -export type LogsAPIConfig = ClashAPIConfig & { logLevel: string }; \ No newline at end of file +export type LogsAPIConfig = ClashAPIConfig & { logLevel: string }; -- cgit v1.3.1 From 3c991ad5d9a46f05246f3217aafea23cadbd7d24 Mon Sep 17 00:00:00 2001 From: Haishan Date: Tue, 7 Jun 2022 18:27:31 +0800 Subject: Avoid layout shift due to display of latency result --- package.json | 3 ++- src/components/proxies/Proxy.tsx | 10 +++++----- src/components/proxies/ProxyLatency.module.scss | 2 +- src/components/proxies/ProxyLatency.tsx | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) (limited to 'src/components') diff --git a/package.json b/package.json index b37e5f5..4c3e7b7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "start": "vite", "build": "vite build", "serve": "vite preview", - "pretty": "prettier --single-quote --write 'src/**/*.{js,scss,ts,tsx,md}'" + "pretty": "prettier --single-quote --write 'src/**/*.{js,scss,ts,tsx,md}'", + "fmt": "pnpm lint && pnpm pretty" }, "browserslist": [ ">0.25%", diff --git a/src/components/proxies/Proxy.tsx b/src/components/proxies/Proxy.tsx index 8dc8caa..753a00c 100644 --- a/src/components/proxies/Proxy.tsx +++ b/src/components/proxies/Proxy.tsx @@ -2,6 +2,8 @@ import cx from 'clsx'; import * as React from 'react'; import { keyCodes } from 'src/misc/keycode'; +import { State } from '$src/store/types'; + import { getDelay, getProxies, NonProxyTypes } from '../../store/proxies'; import { connect } from '../StateProvider'; import s0 from './Proxy.module.scss'; @@ -112,9 +114,7 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr }, [name, onClick, isSelectable]); const handleKeyDown = React.useCallback( (e: React.KeyboardEvent) => { - if (e.keyCode === keyCodes.Enter) { - doSelect(); - } + if (e.key === 'Enter') doSelect(); }, [doSelect] ); @@ -139,13 +139,13 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr {formatProxyType(proxy.type)} - {latency && latency.number ? : null} + ); } -const mapState = (s: any, { name }) => { +const mapState = (s: State, { name }) => { const proxies = getProxies(s); const delay = getDelay(s); return { diff --git a/src/components/proxies/ProxyLatency.module.scss b/src/components/proxies/ProxyLatency.module.scss index 0671982..51efc9c 100644 --- a/src/components/proxies/ProxyLatency.module.scss +++ b/src/components/proxies/ProxyLatency.module.scss @@ -3,6 +3,6 @@ color: #eee; font-size: 0.6em; @media (--breakpoint-not-small) { - font-size: 1em; + font-size: 0.85em; } } diff --git a/src/components/proxies/ProxyLatency.tsx b/src/components/proxies/ProxyLatency.tsx index 48e55af..29036d5 100644 --- a/src/components/proxies/ProxyLatency.tsx +++ b/src/components/proxies/ProxyLatency.tsx @@ -3,14 +3,14 @@ import * as React from 'react'; import s0 from './ProxyLatency.module.scss'; type ProxyLatencyProps = { - number: number; + number?: number; color: string; }; export function ProxyLatency({ number, color }: ProxyLatencyProps) { return ( - {number} ms + {typeof number === 'number' && number !== 0 ? number + ' ms' : ' '} ); } -- cgit v1.3.1 From 23932f4859bd8a96559cd3c10ed9d82a247f8b15 Mon Sep 17 00:00:00 2001 From: Haishan Date: Tue, 7 Jun 2022 22:17:21 +0800 Subject: Make all proxy items same width --- src/components/Root.scss | 2 ++ src/components/proxies/Proxy.module.scss | 7 +++++-- src/components/proxies/Proxy.tsx | 29 ++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) (limited to 'src/components') diff --git a/src/components/Root.scss b/src/components/Root.scss index dfecea2..5fb5ffd 100644 --- a/src/components/Root.scss +++ b/src/components/Root.scss @@ -73,6 +73,7 @@ body { --color-background: #202020; --color-background2: rgba(32, 32, 32, 0.3); --color-bg-card: #2d2d2d; + --card-hover-border-lightness: 30%; --color-text: #ddd; --color-text-secondary: #ccc; --color-text-highlight: #fff; @@ -100,6 +101,7 @@ body { --color-background: #eee; --color-background2: rgba(240, 240, 240, 0.3); --color-bg-card: #fafafa; + --card-hover-border-lightness: 80%; --color-text: #222; --color-text-secondary: #646464; --color-text-highlight: #040404; diff --git a/src/components/proxies/Proxy.module.scss b/src/components/proxies/Proxy.module.scss index 044722f..3c4503f 100644 --- a/src/components/proxies/Proxy.module.scss +++ b/src/components/proxies/Proxy.module.scss @@ -15,7 +15,7 @@ border: 1px solid var(--color-focus-blue); } - max-width: 280px; + max-width: 200px; @media (--breakpoint-not-small) { min-width: 200px; border-radius: 10px; @@ -34,7 +34,7 @@ transition: transform 0.2s ease-in-out; cursor: pointer; &:hover { - transform: translateY(-2px); + border-color: hsl(0deg, 0%, var(--card-hover-border-lightness)); } } } @@ -58,6 +58,9 @@ width: 100%; margin-bottom: 5px; font-size: 0.85em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .proxySmall { diff --git a/src/components/proxies/Proxy.tsx b/src/components/proxies/Proxy.tsx index 753a00c..b943bcc 100644 --- a/src/components/proxies/Proxy.tsx +++ b/src/components/proxies/Proxy.tsx @@ -1,3 +1,4 @@ +import { TooltipPopup, useTooltip } from '@reach/tooltip'; import cx from 'clsx'; import * as React from 'react'; import { keyCodes } from 'src/misc/keycode'; @@ -107,6 +108,28 @@ function formatProxyType(t: string) { return t; } +const positionProxyNameTooltip = (triggerRect: { left: number; top: number }) => { + return { + left: triggerRect.left + window.scrollX - 5, + top: triggerRect.top + window.scrollY - 38, + }; +}; + +function ProxyNameTooltip({ children, label, 'aria-label': ariaLabel }) { + const [trigger, tooltip] = useTooltip(); + return ( + <> + {React.cloneElement(children, trigger)} + + + ); +} + function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyProps) { const color = useMemo(() => getLabelColor(latency), [latency]); const doSelect = React.useCallback(() => { @@ -134,7 +157,11 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr onKeyDown={handleKeyDown} role={isSelectable ? 'menuitem' : ''} > -
{name}
+
+ + {name} + +
{formatProxyType(proxy.type)} -- cgit v1.3.1 From 7ab43cb539f478c7ca8431adcb314733b978c643 Mon Sep 17 00:00:00 2001 From: Meng Dong Date: Thu, 9 Jun 2022 20:41:18 +0800 Subject: Fix log level order (#703) --- src/components/Config.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/components') diff --git a/src/components/Config.tsx b/src/components/Config.tsx index 6107789..6c86445 100644 --- a/src/components/Config.tsx +++ b/src/components/Config.tsx @@ -25,8 +25,8 @@ const propsList = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]; const logLeveOptions = [ ['debug', 'Debug'], - ['warning', 'Warning'], ['info', 'Info'], + ['warning', 'Warning'], ['error', 'Error'], ['silent', 'Silent'], ]; -- cgit v1.3.1 From 459de998751bee702615eb790e2ad2313877b386 Mon Sep 17 00:00:00 2001 From: Haishan Date: Sat, 11 Jun 2022 17:07:59 +0800 Subject: Resolve a few "ts-expect-error"s --- src/components/BackendList.tsx | 2 -- src/components/Collapsible.tsx | 15 ++++++++------- src/components/Connections.tsx | 12 +++--------- src/components/Rules.tsx | 17 +++++++++++++---- src/components/StyleGuide.tsx | 8 +++----- src/components/TrafficChart.tsx | 3 +-- src/components/proxies/ProxyProvider.tsx | 12 ++++-------- src/hooks/basic.ts | 2 +- src/types.ts | 2 ++ 9 files changed, 35 insertions(+), 38 deletions(-) (limited to 'src/components') diff --git a/src/components/BackendList.tsx b/src/components/BackendList.tsx index e4f4d80..9ad833c 100644 --- a/src/components/BackendList.tsx +++ b/src/components/BackendList.tsx @@ -110,8 +110,6 @@ function Item({ {secret ? ( <> {show ? secret : '***'} - - {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean | (() => void)' is not assignable to... Remove this comment to see the full error message */} diff --git a/src/components/Collapsible.tsx b/src/components/Collapsible.tsx index 819bc7c..65284cd 100644 --- a/src/components/Collapsible.tsx +++ b/src/components/Collapsible.tsx @@ -1,17 +1,18 @@ -import React from 'react'; +import type { MutableRefObject } from 'react'; +import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { framerMotionResouce } from '../misc/motion'; const { memo, useState, useRef, useEffect } = React; -function usePrevious(value) { +function usePrevious(value: any) { const ref = useRef(); useEffect(() => void (ref.current = value), [value]); return ref.current; } -function useMeasure() { +function useMeasure(): [MutableRefObject, { height: number }] { const ref = useRef(); const [bounds, set] = useState({ height: 0 }); useEffect(() => { @@ -27,7 +28,7 @@ const variantsCollpapsibleWrap = { height: 'auto', transition: { duration: 0 }, }, - open: (height) => ({ + open: (height: number) => ({ height, opacity: 1, visibility: 'visible', @@ -50,12 +51,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 }) => { +type CollapsibleProps = { children: React.ReactNode; isOpen?: boolean }; + +const Collapsible = memo(({ children, isOpen }: CollapsibleProps) => { 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 (
diff --git a/src/components/Connections.tsx b/src/components/Connections.tsx index 435345b..c72f489 100644 --- a/src/components/Connections.tsx +++ b/src/components/Connections.tsx @@ -109,7 +109,7 @@ function renderTableOrPlaceholder(conns: FormattedConn[]) { ); } -function ConnQty({ qty }) { +function connQty({ qty }) { return qty < 100 ? '' + qty : '99+'; } @@ -179,17 +179,11 @@ function Conn({ apiConfig }) { {t('Active')} - - {/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */} - - + {connQty({ qty: filteredConns.length })} {t('Closed')} - - {/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */} - - + {connQty({ qty: filteredClosedConns.length })}
diff --git a/src/components/Rules.tsx b/src/components/Rules.tsx index e6e1422..ad630b5 100644 --- a/src/components/Rules.tsx +++ b/src/components/Rules.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { areEqual, VariableSizeList } from 'react-window'; import { RuleProviderItem } from 'src/components/rules/RuleProviderItem'; @@ -7,7 +7,7 @@ import { RulesPageFab } from 'src/components/rules/RulesPageFab'; import { TextFilter } from 'src/components/shared/TextFitler'; import { ruleFilterText } from 'src/store/rules'; import { State } from 'src/store/types'; -import { ClashAPIConfig } from 'src/types'; +import { ClashAPIConfig, RuleType } from 'src/types'; import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight'; import { getClashAPIConfig } from '../store/app'; @@ -48,8 +48,17 @@ 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 }) => { +type RowProps = { + index: number; + style: React.CSSProperties; + data: { + apiConfig: ClashAPIConfig; + rules: RuleType[]; + provider: { names: string[]; byName: any }; + }; +}; + +const Row = memo(({ index, style, data }: RowProps) => { const { rules, provider, apiConfig } = data; const providerQty = provider.names.length; diff --git a/src/components/StyleGuide.tsx b/src/components/StyleGuide.tsx index ee38697..1f5f3be 100644 --- a/src/components/StyleGuide.tsx +++ b/src/components/StyleGuide.tsx @@ -21,7 +21,9 @@ const optionsRule = [ { label: 'Direct', value: 'Direct' }, ]; -const Pane = ({ children, style }) =>
{children}
; +const Pane = ({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) => ( +
{children}
+); function useToggle(initialState = false) { const [onoff, setonoff] = React.useState(initialState); @@ -40,19 +42,15 @@ class StyleGuide extends PureComponent { render() { return (
- {/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */} - {/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */} - {/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */} - {/* @ts-expect-error ts-migrate(2741) FIXME: Property 'style' is missing in type '{ children: E... Remove this comment to see the full error message */} +
+ + + +
{createElement(isOpen ? ProxyList : ProxyListSummaryView, { all, diff --git a/src/components/proxies/ProxyList.module.scss b/src/components/proxies/ProxyList.module.scss index d548572..12fea7e 100644 --- a/src/components/proxies/ProxyList.module.scss +++ b/src/components/proxies/ProxyList.module.scss @@ -6,9 +6,10 @@ } .listSummaryView { - margin: 8px 0; + margin: 14px 0; display: grid; grid-template-columns: repeat(auto-fill, 13px); grid-gap: 10px; place-items: center; + max-width: 900px; } diff --git a/src/components/proxies/ProxyProvider.module.scss b/src/components/proxies/ProxyProvider.module.scss index 534305b..bc66bca 100644 --- a/src/components/proxies/ProxyProvider.module.scss +++ b/src/components/proxies/ProxyProvider.module.scss @@ -5,21 +5,25 @@ } } -.body { +.main { padding: 10px 15px; @media (--breakpoint-not-small) { padding: 10px 40px; } } -.actionFooter { +.head { display: flex; - button { - margin: 0 5px; - &:first-child { - margin-left: 0; - } - } + align-items: center; + flex-wrap: wrap; +} + +.action { + margin: 0 5px; + display: grid; + grid-template-columns: auto auto; + gap: 10px; + place-items: center; } .refresh { diff --git a/src/components/proxies/ProxyProvider.tsx b/src/components/proxies/ProxyProvider.tsx index 972a735..7939190 100644 --- a/src/components/proxies/ProxyProvider.tsx +++ b/src/components/proxies/ProxyProvider.tsx @@ -1,8 +1,8 @@ +import Tooltip from '@reach/tooltip'; import { formatDistance } from 'date-fns'; import * as React from 'react'; -import { RotateCw, Zap } from 'react-feather'; +import { RotateCw } from 'react-feather'; import Button from 'src/components/Button'; -import Collapsible from 'src/components/Collapsible'; import CollapsibleSectionHeader from 'src/components/CollapsibleSectionHeader'; import { useUpdateProviderItem } from 'src/components/proxies/proxies.hooks'; import { connect, useStoreActions } from 'src/components/StateProvider'; @@ -18,6 +18,7 @@ import { DelayMapping, State } from 'src/store/types'; import { useState2 } from '$src/hooks/basic'; +import { ZapAnimated } from '../shared/ZapAnimated'; import { useFilteredAndSorted } from './hooks'; import { ProxyList, ProxyListSummaryView } from './ProxyList'; import s from './ProxyProvider.module.scss'; @@ -56,6 +57,7 @@ function ProxyProviderImpl({ const updateProvider = useUpdateProviderItem({ dispatch, apiConfig, name }); const healthcheckProvider = useCallback(() => { + if (checkingHealth.value) return; checkingHealth.set(true); const stop = () => checkingHealth.set(false); dispatch(healthcheckProviderByName(apiConfig, name)).then(stop, stop); @@ -71,32 +73,33 @@ function ProxyProviderImpl({ const timeAgo = formatDistance(new Date(updatedAt), new Date()); return ( -
- +
+
+ + +
+ + + + + + +
+
Updated {timeAgo} ago
- - -
-
-
- - - + {isOpen ? : }
); } diff --git a/src/components/shared/ZapAnimated.module.scss b/src/components/shared/ZapAnimated.module.scss new file mode 100644 index 0000000..e4cb37b --- /dev/null +++ b/src/components/shared/ZapAnimated.module.scss @@ -0,0 +1,12 @@ +.animate { + --saturation: 70%; + stroke: hsl(46deg var(--saturation) 45%); + animation: zap-pulse 0.7s 0s ease-in-out none normal infinite; +} + +// prettier-ignore +@keyframes zap-pulse { + 0% { stroke: hsl(46deg var(--saturation) 45%); } + 50% { stroke: hsl(46deg var(--saturation) 95%); } + 100% { stroke: hsl(46deg var(--saturation) 45%); } +} diff --git a/src/components/shared/ZapAnimated.tsx b/src/components/shared/ZapAnimated.tsx new file mode 100644 index 0000000..e3b153a --- /dev/null +++ b/src/components/shared/ZapAnimated.tsx @@ -0,0 +1,25 @@ +import cx from 'clsx'; +import * as React from 'react'; + +import s from './ZapAnimated.module.scss'; + +export function ZapAnimated(props: { size?: number; animate?: boolean }) { + const size = props.size || 24; + const cls = cx({ [s.animate]: props.animate }); + return ( + + + + ); +} -- cgit v1.3.1 From 38571da24ac54137564be5e41b7a409009e2ee10 Mon Sep 17 00:00:00 2001 From: Haishan Date: Sun, 12 Jun 2022 18:22:17 +0800 Subject: Should check provider health if a group contains provider proxies --- src/components/proxies/Proxy.tsx | 12 ++-- src/components/proxies/ProxyGroup.tsx | 19 +++++- src/store/proxies.tsx | 110 +++++++++++++++++++--------------- src/store/types.ts | 32 ++++++---- 4 files changed, 104 insertions(+), 69 deletions(-) (limited to 'src/components') diff --git a/src/components/proxies/Proxy.tsx b/src/components/proxies/Proxy.tsx index 8cc668f..47a3d54 100644 --- a/src/components/proxies/Proxy.tsx +++ b/src/components/proxies/Proxy.tsx @@ -47,7 +47,7 @@ type ProxyProps = { name: string; now?: boolean; proxy: any; - latency: any; + latency?: { number?: number }; isSelectable?: boolean; onClick?: (proxyName: string) => unknown; }; @@ -130,10 +130,10 @@ function ProxyImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyPr const className = useMemo(() => { return cx(s0.proxy, { [s0.now]: now, - [s0.error]: latency && latency.error, + // [s0.error]: latency && latency.error, [s0.selectable]: isSelectable, }); - }, [isSelectable, now, latency]); + }, [isSelectable, now]); return (
{ const proxies = getProxies(s); const delay = getDelay(s); - return { - proxy: proxies[name], - latency: delay[name], - }; + const proxy = proxies[name] || { name, type: 'Unknown', history: [] }; + return { proxy, latency: delay[name] }; }; export const Proxy = connect(mapState)(ProxyImpl); diff --git a/src/components/proxies/ProxyGroup.tsx b/src/components/proxies/ProxyGroup.tsx index 6d6c59e..857a480 100644 --- a/src/components/proxies/ProxyGroup.tsx +++ b/src/components/proxies/ProxyGroup.tsx @@ -2,7 +2,8 @@ import Tooltip from '@reach/tooltip'; import * as React from 'react'; import { useState2 } from '$src/hooks/basic'; -import { State } from '$src/store/types'; +import { DelayMapping, DispatchFn, ProxiesMapping, State } from '$src/store/types'; +import { ClashAPIConfig } from '$src/types'; import { getCollapsibleIsOpen, getHideUnavailableProxies, getProxySortBy } from '../../store/app'; import { getProxies, switchProxy } from '../../store/proxies'; @@ -16,6 +17,20 @@ import { ProxyList, ProxyListSummaryView } from './ProxyList'; const { createElement, useCallback, useMemo } = React; +type ProxyGroupImplProps = { + name: string; + all: string[]; + delay: DelayMapping; + hideUnavailableProxies: boolean; + proxySortBy: string; + proxies: ProxiesMapping; + type: string; + now: string; + isOpen: boolean; + apiConfig: ClashAPIConfig; + dispatch: DispatchFn; +}; + function ProxyGroupImpl({ name, all: allItems, @@ -28,7 +43,7 @@ function ProxyGroupImpl({ isOpen, apiConfig, dispatch, -}) { +}: ProxyGroupImplProps) { const all = useFilteredAndSorted(allItems, delay, hideUnavailableProxies, proxySortBy, proxies); const isSelectable = useMemo(() => type === 'Selector', [type]); diff --git a/src/store/proxies.tsx b/src/store/proxies.tsx index ad0a84e..5f4c0aa 100644 --- a/src/store/proxies.tsx +++ b/src/store/proxies.tsx @@ -1,8 +1,10 @@ import { atom } from 'recoil'; import { + DelayMapping, DispatchFn, FormattedProxyProvider, GetStateFn, + LatencyHistory, ProxiesMapping, ProxyItem, ProxyProvider, @@ -50,6 +52,19 @@ export const getProxyProviders = (s: State) => s.proxies.proxyProviders || []; export const getDangleProxyNames = (s: State) => s.proxies.dangleProxyNames; export const getShowModalClosePrevConns = (s: State) => s.proxies.showModalClosePrevConns; +function mapLatency(names: string[], getProxy: (name: string) => { history: LatencyHistory }) { + const result: DelayMapping = {}; + for (const name of names) { + const p = getProxy(name) || { history: [] }; + const history = p.history; + const h = history[history.length - 1]; + if (h && typeof h.delay === 'number') { + result[name] = { number: h.delay }; + } + } + return result; +} + export function fetchProxies(apiConfig: ClashAPIConfig) { return async (dispatch: any, getState: any) => { const [proxiesData, providersData] = await Promise.all([ @@ -57,36 +72,28 @@ export function fetchProxies(apiConfig: ClashAPIConfig) { proxiesAPI.fetchProviderProxies(apiConfig), ]); - const { providers: proxyProviders, proxies: providerProxies } = formatProxyProviders( - providersData.providers - ); - const proxies = { ...proxiesData.proxies, ...providerProxies }; - const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies); + const { proxyProviders, providerProxyRecord } = formatProxyProviders(providersData.providers); - const delayPrev = getDelay(getState()); - const delayNext = { ...delayPrev }; + const proxies = { ...providerProxyRecord, ...proxiesData.proxies }; + const [groupNames, proxyNames] = retrieveGroupNamesFrom(proxies); - 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 && typeof h.delay === 'number') { - delayNext[name] = { number: h.delay }; - } - } + const delayNext = { + ...getDelay(getState()), + ...mapLatency(Object.keys(proxies), (name) => proxies[name]), + }; // proxies that are not from a provider const dangleProxyNames = []; for (const v of proxyNames) { - if (!providerProxies[v]) dangleProxyNames.push(v); + if (!providerProxyRecord[v]) dangleProxyNames.push(v); } dispatch('store/proxies#fetchProxies', (s: State) => { s.proxies.proxies = proxies; s.proxies.groupNames = groupNames; + s.proxies.dangleProxyNames = dangleProxyNames; s.proxies.delay = delayNext; s.proxies.proxyProviders = proxyProviders; - s.proxies.dangleProxyNames = dangleProxyNames; }); }; } @@ -201,11 +208,6 @@ async function switchProxyImpl( // no wait closePrevConns(apiConfig, proxies, { groupName, itemName }); } - - /* dispatch('showModalClosePrevConns', (s: GlobalState) => { */ - /* s.proxies.showModalClosePrevConns = true; */ - /* s.proxies.switchProxyCtx = { to: { groupName, itemName } }; */ - /* }); */ } function closeModalClosePrevConns() { @@ -273,15 +275,7 @@ function requestDelayForProxyOnce(apiConfig: ClashAPIConfig, name: string) { error = res.statusText; } const { delay } = await res.json(); - - const delayPrev = getDelay(getState()); - const delayNext = { - ...delayPrev, - [name]: { - error, - number: delay, - }, - }; + const delayNext = { ...getDelay(getState()), [name]: { error, number: delay } }; dispatch('requestDelayForProxyOnce', (s) => { s.proxies.delay = delayNext; @@ -297,12 +291,33 @@ export function requestDelayForProxy(apiConfig: ClashAPIConfig, name: string) { export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[]) { return async (dispatch: DispatchFn, getState: GetStateFn) => { - const proxyNames = getDangleProxyNames(getState()); + const proxies = getProxies(getState()); + const latencyTestUrl = getLatencyTestUrl(getState()); - const works = names - // remove names that are provided by proxy providers - .filter((p) => proxyNames.indexOf(p) > -1) - .map((p) => dispatch(requestDelayForProxy(apiConfig, p))); + const proxyDedupMap = new Map(); + const providerDedupMap = new Map(); + + const works = names.map((name) => { + const p = proxies[name]; + if (!p.__provider) { + if (proxyDedupMap.get(name)) { + return undefined; + } else { + proxyDedupMap.set(name, true); + return proxiesAPI.requestDelayForProxy(apiConfig, name, latencyTestUrl); + } + } else if (p.__provider) { + // this one is from a proxy provider + if (providerDedupMap.get(p.__provider)) { + return undefined; + } else { + providerDedupMap.set(p.__provider, true); + return healthcheckProviderByNameInternal(apiConfig, p.__provider); + } + } else { + return undefined; + } + }); await Promise.all(works); await dispatch(fetchProxies(apiConfig)); }; @@ -311,7 +326,10 @@ export function requestDelayForProxies(apiConfig: ClashAPIConfig, names: string[ 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)))); + const latencyTestUrl = getLatencyTestUrl(getState()); + await Promise.all( + proxyNames.map((p) => proxiesAPI.requestDelayForProxy(apiConfig, p, latencyTestUrl)) + ); const proxyProviders = getProxyProviders(getState()); // one by one for (const p of proxyProviders) { @@ -353,12 +371,13 @@ type ProvidersRaw = { }; function formatProxyProviders(providersInput: ProvidersRaw): { - providers: Array; - proxies: { [key: string]: ProxyItem }; + proxyProviders: Array; + providerProxyRecord: ProxiesMapping; } { const keys = Object.keys(providersInput); - const providers = []; - const proxies = {}; + const proxyProviders = []; + const providerProxyRecord: ProxiesMapping = {}; + for (let i = 0; i < keys.length; i++) { const provider: ProxyProvider = providersInput[keys[i]]; if (provider.name === 'default' || provider.vehicleType === 'Compatible') { @@ -368,19 +387,16 @@ function formatProxyProviders(providersInput: ProvidersRaw): { const names = []; for (let j = 0; j < proxiesArr.length; j++) { const proxy = proxiesArr[j]; - proxies[proxy.name] = proxy; + providerProxyRecord[proxy.name] = { ...proxy, __provider: provider.name }; names.push(proxy.name); } // mutate directly provider.proxies = names; - providers.push(provider); + proxyProviders.push(provider); } - return { - providers, - proxies, - }; + return { proxyProviders, providerProxyRecord }; } export const actions = { diff --git a/src/store/types.ts b/src/store/types.ts index d12adaa..b0b457e 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -27,39 +27,45 @@ export type ClashGeneralConfig = { ///// store.proxies -type LatencyHistory = Array<{ time: string; delay: number }>; -type PrimitiveProxyType = 'Shadowsocks' | 'Snell' | 'Socks5' | 'Http' | 'Vmess'; +type LatencyHistoryItem = { time: string; delay: number }; +export type LatencyHistory = LatencyHistoryItem[]; + export type ProxyItem = { name: string; - type: PrimitiveProxyType; + type: string; history: LatencyHistory; all?: string[]; now?: string; + + __provider?: string; }; + +export type ProxyDelayItem = { + number?: number; +}; + export type ProxiesMapping = Record; -export type DelayMapping = Record; +export type DelayMapping = Record; export type ProxyProvider = { name: string; type: 'Proxy'; updatedAt: string; vehicleType: 'HTTP' | 'File' | 'Compatible'; - proxies: Array; + proxies: ProxyItem[]; }; -export type FormattedProxyProvider = Omit & { - proxies: string[]; -}; +export type FormattedProxyProvider = Omit & { proxies: string[] }; export type SwitchProxyCtxItem = { groupName: string; itemName: string }; -type SwitchProxyCtx = { - to: SwitchProxyCtxItem; -}; +type SwitchProxyCtx = { to: SwitchProxyCtxItem }; + export type StateProxies = { - proxies: ProxiesMapping; - delay: DelayMapping; groupNames: string[]; proxyProviders?: FormattedProxyProvider[]; + + proxies: ProxiesMapping; + delay: DelayMapping; dangleProxyNames?: string[]; showModalClosePrevConns: boolean; -- cgit v1.3.1