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,