diff --git a/package.json b/package.json index bb825b5..5ded2aa 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@commitlint/config-conventional": "^19.5.0", + "@corvu/tooltip": "^0.2.1", "@eslint/js": "^9.12.0", "@felte/solid": "^1.2.13", "@felte/validator-zod": "^1.0.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aff74a2..b6b89d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ importers: '@commitlint/config-conventional': specifier: ^19.5.0 version: 19.5.0 + '@corvu/tooltip': + specifier: ^0.2.1 + version: 0.2.1(solid-js@1.9.2) '@eslint/js': specifier: ^9.12.0 version: 9.12.0 @@ -1257,6 +1260,22 @@ packages: } engines: { node: '>=v18' } + '@corvu/tooltip@0.2.1': + resolution: + { + integrity: sha512-y2CQ2/6DH/gJJZPo1fV3O7l4Jfgu5ZW58bpqPmKS+l8Pa6gIKV6zkrMoyBg8Hsn6z9RmdZFqkGFs/9C5fvwKpg==, + } + peerDependencies: + solid-js: ^1.8 + + '@corvu/utils@0.4.2': + resolution: + { + integrity: sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA==, + } + peerDependencies: + solid-js: ^1.8 + '@esbuild/aix-ppc64@0.21.5': resolution: { @@ -1554,6 +1573,24 @@ packages: peerDependencies: zod: ^3.2.0 + '@floating-ui/core@1.6.8': + resolution: + { + integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==, + } + + '@floating-ui/dom@1.6.11': + resolution: + { + integrity: sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==, + } + + '@floating-ui/utils@0.2.8': + resolution: + { + integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==, + } + '@fontsource/fira-sans@5.1.0': resolution: { @@ -5331,12 +5368,28 @@ packages: apexcharts: ^3.40.0 solid-js: ^1.6.0 + solid-dismissible@0.1.1: + resolution: + { + integrity: sha512-9kcKBJIMdS+586cA1g63HYWxKh3h89leeNHbPZ1csYjuni+NvPBtNr11l0iEX2AKKEt6FHk6qNhc/gjoYAW1pA==, + } + peerDependencies: + solid-js: ^1.8 + solid-js@1.9.2: resolution: { integrity: sha512-fe/K03nV+kMFJYhAOE8AIQHcGxB4rMIEoEyrulbtmf217NffbbwBqJnJI4ovt16e+kaIt0czE2WA7mP/pYN9yg==, } + solid-presence@0.1.8: + resolution: + { + integrity: sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA==, + } + peerDependencies: + solid-js: ^1.8 + solid-refresh@0.6.3: resolution: { @@ -7228,6 +7281,19 @@ snapshots: '@types/conventional-commits-parser': 5.0.0 chalk: 5.3.0 + '@corvu/tooltip@0.2.1(solid-js@1.9.2)': + dependencies: + '@corvu/utils': 0.4.2(solid-js@1.9.2) + '@floating-ui/dom': 1.6.11 + solid-dismissible: 0.1.1(solid-js@1.9.2) + solid-js: 1.9.2 + solid-presence: 0.1.8(solid-js@1.9.2) + + '@corvu/utils@0.4.2(solid-js@1.9.2)': + dependencies: + '@floating-ui/dom': 1.6.11 + solid-js: 1.9.2 + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -7352,6 +7418,17 @@ snapshots: '@felte/common': 1.1.8 zod: 3.23.8 + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.11': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/utils@0.2.8': {} + '@fontsource/fira-sans@5.1.0': {} '@humanfs/core@0.19.0': {} @@ -9495,12 +9572,22 @@ snapshots: defu: 6.1.4 solid-js: 1.9.2 + solid-dismissible@0.1.1(solid-js@1.9.2): + dependencies: + '@corvu/utils': 0.4.2(solid-js@1.9.2) + solid-js: 1.9.2 + solid-js@1.9.2: dependencies: csstype: 3.1.3 seroval: 1.1.0 seroval-plugins: 1.1.0(seroval@1.1.0) + solid-presence@0.1.8(solid-js@1.9.2): + dependencies: + '@corvu/utils': 0.4.2(solid-js@1.9.2) + solid-js: 1.9.2 + solid-refresh@0.6.3(solid-js@1.9.2): dependencies: '@babel/generator': 7.24.7 diff --git a/src/components/Latency.tsx b/src/components/Latency.tsx index 13c48d8..7f6bb90 100644 --- a/src/components/Latency.tsx +++ b/src/components/Latency.tsx @@ -1,36 +1,28 @@ -import { LATENCY_QUALITY_MAP_HTTP } from '~/constants' -import { useI18n } from '~/i18n' -import { latencyQualityMap, useProxies } from '~/signals' +import { JSX, ParentComponent } from 'solid-js' +import { twMerge } from 'tailwind-merge' +import { getLatencyClassName } from '~/helpers' +import { useProxies } from '~/signals' -export const Latency = (props: { name?: string; class?: string }) => { - const [t] = useI18n() +interface Props extends JSX.HTMLAttributes { + proxyName: string +} + +export const Latency: ParentComponent = (props) => { + const [local, others] = splitProps(props, ['class']) const { getLatencyByName } = useProxies() const [textClassName, setTextClassName] = createSignal('') - const latency = createMemo(() => getLatencyByName(props.name || '')) + const latency = createMemo(() => getLatencyByName(others.proxyName || '')) createEffect(() => { - if (latency() > latencyQualityMap().HIGH) { - setTextClassName('text-error') - } else if (latency() > latencyQualityMap().MEDIUM) { - setTextClassName('text-warning') - } else { - setTextClassName('text-success') - } + setTextClassName(getLatencyClassName(latency())) }) return ( - - - {latency()} - {t('ms')} - - + {latency() || '-'} + ) } diff --git a/src/components/ProxyNodeCard.tsx b/src/components/ProxyNodeCard.tsx index 51cd845..3017a4f 100644 --- a/src/components/ProxyNodeCard.tsx +++ b/src/components/ProxyNodeCard.tsx @@ -1,8 +1,14 @@ -import { IconBrandSpeedtest } from '@tabler/icons-solidjs' +import Tooltip from '@corvu/tooltip' +import { IconCircleCheckFilled } from '@tabler/icons-solidjs' +import dayjs from 'dayjs' import { twMerge } from 'tailwind-merge' -import { Button, Latency } from '~/components' -import { filterSpecialProxyType, formatProxyType } from '~/helpers' -import { useProxies } from '~/signals' +import { Latency } from '~/components' +import { + filterSpecialProxyType, + formatProxyType, + getLatencyClassName, +} from '~/helpers' +import { curTheme, useProxies } from '~/signals' export const ProxyNodeCard = (props: { proxyName: string @@ -32,56 +38,114 @@ export const ProxyNodeCard = (props: { : null return ( -
-
- {formatProxyType(proxyNode()?.type)} -
+ + +
+

+ {proxyName} +

-
-

{proxyName}

+ + {[ + specialType(), + supportIPv6() && 'IPv6', + proxyNode().tfo && 'TFO', + ] + .filter(Boolean) + .join(' / ')} + - - {[specialType(), supportIPv6() && 'IPv6'].filter(Boolean).join(' / ')} - +
+
+ {formatProxyType(proxyNode()?.type)} +
-
- - -
-
-
+ void proxyLatencyTest(proxyName, proxyNode().provider) + }} + /> +
+
+ + + + + + +
+

{proxyName}

+ +
    + + {(latencyTestResult, index) => ( +
  • + 0}> +
    +
    + +
    + + +
    + {latencyTestResult.delay || '-'} +
    +
    + +
    + +
    + + +
    +
    +
  • + )} +
    +
+
+
+
+ + ) } diff --git a/src/components/ProxyPreviewBar.tsx b/src/components/ProxyPreviewBar.tsx index 72b506b..4d16dec 100644 --- a/src/components/ProxyPreviewBar.tsx +++ b/src/components/ProxyPreviewBar.tsx @@ -68,7 +68,7 @@ export const ProxyPreviewBar = (props: { /> - + ) } diff --git a/src/components/ProxyPreviewDots.tsx b/src/components/ProxyPreviewDots.tsx index 18b2bde..d6c7947 100644 --- a/src/components/ProxyPreviewDots.tsx +++ b/src/components/ProxyPreviewDots.tsx @@ -65,7 +65,7 @@ export const ProxyPreviewDots = (props: { - + ) } diff --git a/src/helpers/proxies.ts b/src/helpers/proxies.ts index 1776a65..17ae6d3 100644 --- a/src/helpers/proxies.ts +++ b/src/helpers/proxies.ts @@ -1,4 +1,4 @@ -import { PROXIES_ORDERING_TYPE } from '~/constants' +import { LATENCY_QUALITY_MAP_HTTP, PROXIES_ORDERING_TYPE } from '~/constants' import { latencyQualityMap, useProxies } from '~/signals' export const formatProxyType = (type = '') => { @@ -19,6 +19,18 @@ export const formatProxyType = (type = '') => { return t } +export const getLatencyClassName = (latency: LATENCY_QUALITY_MAP_HTTP) => { + if (latency > latencyQualityMap().HIGH) { + return 'text-error' + } else if (latency > latencyQualityMap().MEDIUM) { + return 'text-warning' + } else if (latency === LATENCY_QUALITY_MAP_HTTP.NOT_CONNECTED) { + return 'text-neutral-content' + } else { + return 'text-success' + } +} + export const filterSpecialProxyType = (type = '') => { const t = type.toLowerCase() const conditions = [ diff --git a/src/signals/proxies.ts b/src/signals/proxies.ts index da45d88..acaef10 100644 --- a/src/signals/proxies.ts +++ b/src/signals/proxies.ts @@ -24,7 +24,12 @@ import type { Proxy, ProxyNode, ProxyProvider } from '~/types' type ProxyInfo = { name: string udp: boolean - now: string + tfo: boolean + latencyTestHistory: { + time: string + delay: number + }[] + latency: string xudp: boolean type: string provider: string @@ -99,9 +104,19 @@ const setProxiesInfo = ( const newProxyIPv6SupportMap = { ...proxyIPv6SupportMap() } proxies.forEach((proxy) => { - const { udp, xudp, type, now, name, provider = '' } = proxy + const { udp, xudp, type, now, history, name, tfo, provider = '' } = proxy + + newProxyNodeMap[proxy.name] = { + udp, + xudp, + type, + latency: now, + latencyTestHistory: history, + name, + tfo, + provider, + } - newProxyNodeMap[proxy.name] = { udp, xudp, type, now, name, provider } newLatencyMap[proxy.name] = getLatencyFromProxy(proxy, urlForLatencyTest()) // we don't set it when false because sing-box didn't have "extra" so it will always be false @@ -326,8 +341,8 @@ export const useProxies = () => { return name } - while (node.now && node.now !== node.name) { - const nextNode = proxyNodeMap()[node.now] + while (node.latency && node.latency !== node.name) { + const nextNode = proxyNodeMap()[node.latency] if (!nextNode) { return node.name @@ -353,7 +368,7 @@ export const useProxies = () => { return ( ['direct', 'reject', 'loadbalance'].includes( proxyNode.type.toLowerCase(), - ) || !!proxyNode.now + ) || !!proxyNode.latency ) }