feat(proxies): sortProxiesByOrderingType

This commit is contained in:
kunish 2023-09-03 05:35:08 +08:00
parent c9ec575437
commit 635b8ba966
No known key found for this signature in database
GPG Key ID: 647A12B4F782C430
18 changed files with 137 additions and 77 deletions

View File

@ -3,6 +3,7 @@
"class",
"className",
"ngClass",
"classList",
".*ClassName*"
]
}

View File

@ -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",

View File

@ -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:
{

View File

@ -26,11 +26,11 @@ export const App = () => {
createEffect(() => {
if (selectedEndpoint() && endpoint()) {
useProxies().updateProxy()
useProxies().updateProxies()
}
})
onMount(async () => {
onMount(() => {
if (!selectedEndpoint()) {
navigate('/setup')
}

View File

@ -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 }) => {
<>
<Show
when={
typeof delay() === 'number' &&
delay() !== LATENCY_QUALITY_MAP_HTTP.NOT_CONNECTED
typeof latency() === 'number' &&
latency() !== LATENCY_QUALITY_MAP_HTTP.NOT_CONNECTED
}
>
<span class={textClassName()}>{delay()}ms</span>
<span class={`whitespace-nowrap ${textClassName()}`}>
{latency()}
{t('ms')}
</span>
</Show>
</>
)

View File

@ -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[]

View File

@ -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 (
<div
class={twMerge(

View File

@ -6,9 +6,9 @@ export const ProxyPreviewBar = (props: {
proxyNameList: string[]
now?: string
}) => {
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(

View File

@ -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 (
<div class="flex w-full flex-wrap items-center">
<For
each={props.proxyNameList.map((name): [string, number] => [
name,
delayMap()[name],
latencyMap()[name],
])}
>
{([name, delay]) => {

View File

@ -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',

View File

@ -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<string, number>,
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
}
})
}

View File

@ -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',
}

View File

@ -54,4 +54,5 @@ export default {
orderLatency_desc: '按延迟从高到低',
orderName_asc: '按名称字母排序 (A-Z)',
orderName_desc: '按名称字母排序 (Z-A)',
ms: '毫秒',
}

View File

@ -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(
</Router>
</I18nProvider>
),
root!,
document.getElementById('root')!,
)

View File

@ -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 = () => {
<div class="pb-4">{t('proxiesSorting')}</div>
<div class="flex flex-col gap-4">
<For each={Object.values(PROXIES_SORTING_TYPE)}>
<For each={Object.values(PROXIES_ORDERING_TYPE)}>
{(value) => (
<label class="flex items-center gap-2">
<span>{t(value)}</span>
@ -298,8 +302,8 @@ const ConfigForXd = () => {
class="radio"
aria-label={value}
type="radio"
checked={value === proxiesSortingType()}
onChange={() => setProxiesSortingType(value)}
checked={value === proxiesOrderingType()}
onChange={() => setProxiesOrderingType(value)}
/>
</label>
)}

View File

@ -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<Record<string, boolean>>(
{},
@ -66,7 +70,11 @@ export default () => {
const content = (
<ProxyCardGroups
proxies={proxy.all!}
proxies={sortProxiesByOrderingType(
proxy.all ?? [],
latencyMap(),
proxiesOrderingType(),
)}
now={proxy.now}
onClick={(name) => {
onProxyNodeClick(proxy, name)

View File

@ -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'),

View File

@ -11,7 +11,7 @@ type ProxyInfo = {
const [proxies, setProxies] = createSignal<Proxy[]>([])
const [proxyProviders, setProxyProviders] = createSignal<ProxyProvider[]>([])
const [delayMap, setDelayMap] = createSignal<Record<string, number>>({})
const [latencyMap, setLatencyMap] = createSignal<Record<string, number>>({})
const [proxyNodeMap, setProxyNodeMap] = createSignal<Record<string, ProxyInfo>>(
{},
)
@ -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<string, ProxyProvider> }>()
@ -58,14 +58,15 @@ export const useProxies = () => {
.get('proxies')
.json<{ proxies: Record<string, Proxy> }>()
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,