diff --git a/.vscode/settings.json b/.vscode/settings.json index 5c31806..0c3ae2b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "class", "className", "ngClass", + "classList", ".*ClassName*" ] } diff --git a/package.json b/package.json index 498dd3d..ac38d34 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "daisyui": "^3.6.4", "dayjs": "^1.11.9", "husky": "^8.0.3", + "immer": "^10.0.2", "is-ip": "^5.0.1", "ky": "^1.0.0", "lint-staged": "^14.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74217db..940ef06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ dependencies: husky: specifier: ^8.0.3 version: 8.0.3 + immer: + specifier: ^10.0.2 + version: 10.0.2 is-ip: specifier: ^5.0.1 version: 5.0.1 @@ -4438,6 +4441,13 @@ packages: engines: { node: '>= 4' } dev: false + /immer@10.0.2: + resolution: + { + integrity: sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==, + } + dev: false + /import-fresh@3.3.0: resolution: { diff --git a/src/App.tsx b/src/App.tsx index 467949b..131ff04 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,11 +26,11 @@ export const App = () => { createEffect(() => { if (selectedEndpoint() && endpoint()) { - useProxies().updateProxy() + useProxies().updateProxies() } }) - onMount(async () => { + onMount(() => { if (!selectedEndpoint()) { navigate('/setup') } diff --git a/src/components/Latency.tsx b/src/components/Latency.tsx index 4341e67..b038847 100644 --- a/src/components/Latency.tsx +++ b/src/components/Latency.tsx @@ -1,20 +1,22 @@ +import { useI18n } from '@solid-primitives/i18n' import { Show, createEffect, createMemo, createSignal } from 'solid-js' import { LATENCY_QUALITY_MAP_HTTP } from '~/constants' import { latencyQualityMap, useProxies } from '~/signals' export const Latency = (props: { name?: string }) => { - const { delayMap } = useProxies() + const [t] = useI18n() + const { latencyMap } = useProxies() const [textClassName, setTextClassName] = createSignal('') - const delay = createMemo(() => { - return delayMap()[props.name!] + const latency = createMemo(() => { + return latencyMap()[props.name!] }) createEffect(() => { setTextClassName('text-success') - if (delay() > latencyQualityMap().HIGH) { + if (latency() > latencyQualityMap().HIGH) { setTextClassName('text-error') - } else if (delay() > latencyQualityMap().MEDIUM) { + } else if (latency() > latencyQualityMap().MEDIUM) { setTextClassName('text-warning') } }) @@ -23,11 +25,14 @@ export const Latency = (props: { name?: string }) => { <> - {delay()}ms + + {latency()} + {t('ms')} + ) diff --git a/src/components/ProxyCardGroups.tsx b/src/components/ProxyCardGroups.tsx index de73e18..0b7854d 100644 --- a/src/components/ProxyCardGroups.tsx +++ b/src/components/ProxyCardGroups.tsx @@ -1,6 +1,6 @@ import InfiniteScroll from 'solid-infinite-scroll' import { createMemo, createSignal } from 'solid-js' -import ProxyNodeCard from './ProxyNodeCard' +import { ProxyNodeCard } from '~/components' export const ProxyCardGroups = (props: { proxies: string[] diff --git a/src/components/ProxyNodeCard.tsx b/src/components/ProxyNodeCard.tsx index 8303c7d..c2a14d2 100644 --- a/src/components/ProxyNodeCard.tsx +++ b/src/components/ProxyNodeCard.tsx @@ -1,9 +1,10 @@ import { createMemo } from 'solid-js' import { twMerge } from 'tailwind-merge' import { Latency } from '~/components' +import { formatProxyType } from '~/helpers' import { useProxies } from '~/signals' -export default (props: { +export const ProxyNodeCard = (props: { proxyName: string isSelected?: boolean onClick?: () => void @@ -12,16 +13,6 @@ export default (props: { const { proxyNodeMap } = useProxies() const proxyNode = createMemo(() => proxyNodeMap()[proxyName]) - const formatProxyType = (type = '') => { - const t = type.toLowerCase() - - if (t.includes('shadowsocks')) { - return t.replace('shadowsocks', 'ss') - } - - return t - } - return (
{ - const { delayMap } = useProxies() + const { latencyMap } = useProxies() const delayList = createMemo(() => - props.proxyNameList.map((i) => delayMap()[i]), + props.proxyNameList.map((i) => latencyMap()[i]), ) const all = createMemo(() => delayList().length) const good = createMemo( diff --git a/src/components/ProxyPreviewDots.tsx b/src/components/ProxyPreviewDots.tsx index f509f92..771216c 100644 --- a/src/components/ProxyPreviewDots.tsx +++ b/src/components/ProxyPreviewDots.tsx @@ -1,27 +1,22 @@ import { For } from 'solid-js' import { twMerge } from 'tailwind-merge' -import { - LATENCY_QUALITY_MAP_HTTP, - LATENCY_QUALITY_MAP_HTTPS, -} from '~/constants' -import { isLatencyTestByHttps, useProxies } from '~/signals' +import { latencyQualityMap, useProxies } from '~/signals' const DelayDots = (p: { delay: number | undefined; selected: boolean }) => { - const delayMap = isLatencyTestByHttps() - ? LATENCY_QUALITY_MAP_HTTP - : LATENCY_QUALITY_MAP_HTTPS - let dotClassName = p.selected ? 'bg-white border-4 border-success' : 'bg-success' - if (typeof p.delay !== 'number' || p.delay === delayMap.NOT_CONNECTED) { + if ( + typeof p.delay !== 'number' || + p.delay === latencyQualityMap().NOT_CONNECTED + ) { dotClassName = p.selected ? 'bg-white border-4 border-neutral' : 'bg-neutral' - } else if (p.delay > delayMap.HIGH) { + } else if (p.delay > latencyQualityMap().HIGH) { dotClassName = p.selected ? 'bg-white border-4 border-error' : 'bg-error' - } else if (p.delay > delayMap.MEDIUM) { + } else if (p.delay > latencyQualityMap().MEDIUM) { dotClassName = p.selected ? 'bg-white border-4 border-warning' : 'bg-warning' @@ -34,14 +29,14 @@ export const ProxyPreviewDots = (props: { proxyNameList: string[] now?: string }) => { - const { delayMap } = useProxies() + const { latencyMap } = useProxies() return (
[ name, - delayMap()[name], + latencyMap()[name], ])} > {([name, delay]) => { diff --git a/src/constants/index.ts b/src/constants/index.ts index 3a63284..a2545ee 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -41,13 +41,13 @@ export enum ROUTES { } export enum LATENCY_QUALITY_MAP_HTTP { - NOT_CONNECTED = 0, + NOT_CONNECTED = -1, MEDIUM = 200, HIGH = 500, } export enum LATENCY_QUALITY_MAP_HTTPS { - NOT_CONNECTED = 0, + NOT_CONNECTED = -1, MEDIUM = 800, HIGH = 1500, } @@ -59,7 +59,7 @@ export enum PROXIES_PREVIEW_TYPE { Auto = 'auto', } -export enum PROXIES_SORTING_TYPE { +export enum PROXIES_ORDERING_TYPE { NATURAL = 'orderNatural', LATENCY_ASC = 'orderLatency_asc', LATENCY_DESC = 'orderLatency_desc', diff --git a/src/helpers/proxies.ts b/src/helpers/proxies.ts index aa5354a..02ec247 100644 --- a/src/helpers/proxies.ts +++ b/src/helpers/proxies.ts @@ -1,7 +1,5 @@ import dayjs from 'dayjs' -import relativeTime from 'dayjs/plugin/relativeTime' - -dayjs.extend(relativeTime) +import { PROXIES_ORDERING_TYPE } from '~/constants' export const formatTimeFromNow = (time: number | string) => { return dayjs(time).fromNow() @@ -16,3 +14,45 @@ export const getBtnElFromClickEvent = (event: MouseEvent) => { return el } + +export const formatProxyType = (type = '') => { + const t = type.toLowerCase() + + if (t.includes('shadowsocks')) { + return t.replace('shadowsocks', 'ss') + } + + return t +} + +export const sortProxiesByOrderingType = ( + proxyNames: string[], + proxyLatencyMap: Record, + orderingType: PROXIES_ORDERING_TYPE, +) => { + if (orderingType === PROXIES_ORDERING_TYPE.NATURAL) { + return proxyNames + } + + return proxyNames.sort((a, b) => { + const prevLatency = proxyLatencyMap[a] + const nextLatency = proxyLatencyMap[b] + + switch (orderingType) { + case PROXIES_ORDERING_TYPE.LATENCY_ASC: + if (prevLatency === -1) return 1 + if (nextLatency === -1) return -1 + return prevLatency - nextLatency + case PROXIES_ORDERING_TYPE.LATENCY_DESC: + if (prevLatency === -1) return 1 + if (nextLatency === -1) return -1 + return nextLatency - prevLatency + case PROXIES_ORDERING_TYPE.NAME_ASC: + return a.localeCompare(b) + case PROXIES_ORDERING_TYPE.NAME_DESC: + return b.localeCompare(a) + default: + return 0 + } + }) +} diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 88bb4f9..09be11b 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -54,4 +54,5 @@ export default { orderLatency_desc: 'By latency from high to low', orderName_asc: 'By name alphabetically (A-Z)', orderName_desc: 'By name alphabetically (Z-A)', + ms: 'ms', } diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index 1e4ad17..f9b51e4 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -54,4 +54,5 @@ export default { orderLatency_desc: '按延迟从高到低', orderName_asc: '按名称字母排序 (A-Z)', orderName_desc: '按名称字母排序 (Z-A)', + ms: '毫秒', } diff --git a/src/main.tsx b/src/main.tsx index 358a767..c537ab7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,11 +2,13 @@ import '~/index.css' import { Router, hashIntegration } from '@solidjs/router' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' import { render } from 'solid-js/web' import { App } from '~/App' import { I18nProvider } from '~/i18n' -const root = document.getElementById('root') +dayjs.extend(relativeTime) render( () => ( @@ -16,5 +18,5 @@ render( ), - root!, + document.getElementById('root')!, ) diff --git a/src/pages/Config.tsx b/src/pages/Config.tsx index 8e00ea9..371eda6 100644 --- a/src/pages/Config.tsx +++ b/src/pages/Config.tsx @@ -4,22 +4,26 @@ import { useI18n } from '@solid-primitives/i18n' import { For, Show, createSignal, onMount } from 'solid-js' import { z } from 'zod' import { Button } from '~/components' -import { PROXIES_PREVIEW_TYPE, PROXIES_SORTING_TYPE, themes } from '~/constants' +import { + PROXIES_ORDERING_TYPE, + PROXIES_PREVIEW_TYPE, + themes, +} from '~/constants' import { applyThemeByMode, autoCloseConns, autoSwitchTheme, favDayTheme, favNightTheme, + proxiesOrderingType, proxiesPreviewType, - proxiesSortingType, renderInTwoColumn, setAutoCloseConns, setAutoSwitchTheme, setFavDayTheme, setFavNightTheme, + setProxiesOrderingType, setProxiesPreviewType, - setProxiesSortingType, setRenderInTwoColumn, setUrlForDelayTest, urlForDelayTest, @@ -289,7 +293,7 @@ const ConfigForXd = () => {
{t('proxiesSorting')}
- + {(value) => ( )} diff --git a/src/pages/Proxies.tsx b/src/pages/Proxies.tsx index 0dc0c6d..e8b0a46 100644 --- a/src/pages/Proxies.tsx +++ b/src/pages/Proxies.tsx @@ -8,14 +8,18 @@ import { ProxyCardGroups, ProxyNodePreview, } from '~/components' -import { getBtnElFromClickEvent } from '~/helpers' -import { useProxies } from '~/signals' +import { getBtnElFromClickEvent, sortProxiesByOrderingType } from '~/helpers' +import { proxiesOrderingType, useProxies } from '~/signals' import type { Proxy } from '~/types' export default () => { const [t] = useI18n() - const { proxies, setProxyGroupByProxyName, delayTestByProxyGroupName } = - useProxies() + const { + proxies, + setProxyGroupByProxyName, + delayTestByProxyGroupName, + latencyMap, + } = useProxies() const [collapsedMap, setCollapsedMap] = createSignal>( {}, @@ -66,7 +70,11 @@ export default () => { const content = ( { onProxyNodeClick(proxy, name) diff --git a/src/signals/config.ts b/src/signals/config.ts index 0dd0e23..bb6d259 100644 --- a/src/signals/config.ts +++ b/src/signals/config.ts @@ -3,8 +3,8 @@ import { createSignal } from 'solid-js' import { LATENCY_QUALITY_MAP_HTTP, LATENCY_QUALITY_MAP_HTTPS, + PROXIES_ORDERING_TYPE, PROXIES_PREVIEW_TYPE, - PROXIES_SORTING_TYPE, } from '~/constants' import { setCurTheme } from '~/signals' @@ -12,9 +12,9 @@ export const [proxiesPreviewType, setProxiesPreviewType] = makePersisted( createSignal(PROXIES_PREVIEW_TYPE.Auto), { name: 'proxiesPreviewType', storage: localStorage }, ) -export const [proxiesSortingType, setProxiesSortingType] = makePersisted( - createSignal(PROXIES_SORTING_TYPE.NATURAL), - { name: 'proxiesSortingType', storage: localStorage }, +export const [proxiesOrderingType, setProxiesOrderingType] = makePersisted( + createSignal(PROXIES_ORDERING_TYPE.NATURAL), + { name: 'proxiesOrderingType', storage: localStorage }, ) export const [urlForDelayTest, setUrlForDelayTest] = makePersisted( createSignal('https://www.gstatic.com/generate_204'), diff --git a/src/signals/proxies.ts b/src/signals/proxies.ts index 1de07b4..5fd6233 100644 --- a/src/signals/proxies.ts +++ b/src/signals/proxies.ts @@ -11,7 +11,7 @@ type ProxyInfo = { const [proxies, setProxies] = createSignal([]) const [proxyProviders, setProxyProviders] = createSignal([]) -const [delayMap, setDelayMap] = createSignal>({}) +const [latencyMap, setLatencyMap] = createSignal>({}) const [proxyNodeMap, setProxyNodeMap] = createSignal>( {}, ) @@ -21,7 +21,7 @@ export const useProxies = () => { const setProxyInfoByProixes = (proxies: Proxy[] | ProxyNode[]) => { proxies.forEach((proxy) => { - const delay = proxy.history.at(-1)?.delay ?? 0 + const delay = proxy.history.at(-1)?.delay ?? -1 setProxyNodeMap({ ...proxyNodeMap(), @@ -31,14 +31,14 @@ export const useProxies = () => { name: proxy.name, }, }) - setDelayMap({ - ...delayMap(), + setLatencyMap({ + ...latencyMap(), [proxy.name]: delay, }) }) } - const updateProxy = async () => { + const updateProxies = async () => { const { providers } = await request .get('providers/proxies') .json<{ providers: Record }>() @@ -58,14 +58,15 @@ export const useProxies = () => { .get('proxies') .json<{ proxies: Record }>() const sortIndex = [...(proxies['GLOBAL'].all ?? []), 'GLOBAL'] + const proxiesArray = Object.values(proxies) - setProxyInfoByProixes(Object.values(proxies)) + setProxyInfoByProixes(proxiesArray) setProxies( - Object.values(proxies) + proxiesArray .filter((proxy) => proxy.all?.length) .sort( - (pre, next) => - sortIndex.indexOf(pre.name) - sortIndex.indexOf(next.name), + (prev, next) => + sortIndex.indexOf(prev.name) - sortIndex.indexOf(next.name), ), ) } @@ -97,8 +98,8 @@ export const useProxies = () => { }) .json() - setDelayMap({ - ...delayMap(), + setLatencyMap({ + ...latencyMap(), ...data, }) } @@ -107,7 +108,7 @@ export const useProxies = () => { try { await request.put(`providers/proxies/${proxyProviderName}`) } catch {} - await updateProxy() + await updateProxies() } const updateAllProvider = async () => { @@ -116,23 +117,23 @@ export const useProxies = () => { return request.put(`providers/proxies/${provider.name}`) }), ) - await updateProxy() + await updateProxies() } const healthCheckByProviderName = async (providerName: string) => { await request.get(`providers/proxies/${providerName}/healthcheck`, { timeout: 30 * 1000, // thie api is a little bit slow sometimes... }) - await updateProxy() + await updateProxies() } return { proxies, proxyProviders, delayTestByProxyGroupName, - delayMap, + latencyMap, proxyNodeMap, - updateProxy, + updateProxies, setProxyGroupByProxyName, updateProviderByProviderName, updateAllProvider,