@@ -333,7 +372,9 @@ const Connections: React.FC = () => {
}}
>
{t('connections.detail.status')}
- {t('connections.detail.establishTime')}
+
+ {t('connections.detail.establishTime')}
+
{t('connections.detail.connectionType')}
{t('connections.detail.host')}
{t('connections.detail.sniffHost')}
@@ -343,7 +384,9 @@ const Connections: React.FC = () => {
{t('connections.detail.proxyChain')}
{t('connections.detail.sourceIP')}
{t('connections.detail.sourcePort')}
- {t('connections.detail.destinationPort')}
+
+ {t('connections.detail.destinationPort')}
+
{t('connections.detail.inboundIP')}
{t('connections.detail.inboundPort')}
{t('connections.uploadSpeed')}
@@ -351,7 +394,9 @@ const Connections: React.FC = () => {
{t('connections.uploadAmount')}
{t('connections.downloadAmount')}
{t('connections.detail.dscp')}
- {t('connections.detail.remoteDestination')}
+
+ {t('connections.detail.remoteDestination')}
+
{t('connections.detail.dnsMode')}
diff --git a/src/renderer/src/pages/logs.tsx b/src/renderer/src/pages/logs.tsx
index 7b87c4d..555d10a 100644
--- a/src/renderer/src/pages/logs.tsx
+++ b/src/renderer/src/pages/logs.tsx
@@ -122,12 +122,7 @@ const Logs: React.FC = () => {
ref={virtuosoRef}
data={filteredLogs}
itemContent={(i, log) => (
-
+
)}
/>
diff --git a/src/renderer/src/pages/mihomo.tsx b/src/renderer/src/pages/mihomo.tsx
index e150fd4..30b0e8a 100644
--- a/src/renderer/src/pages/mihomo.tsx
+++ b/src/renderer/src/pages/mihomo.tsx
@@ -1,4 +1,20 @@
-import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure, Spinner, Chip } from '@heroui/react'
+import {
+ Button,
+ Divider,
+ Input,
+ Select,
+ SelectItem,
+ Switch,
+ Tooltip,
+ Modal,
+ ModalContent,
+ ModalHeader,
+ ModalBody,
+ ModalFooter,
+ useDisclosure,
+ Spinner,
+ Chip
+} from '@heroui/react'
import BasePage from '@renderer/components/base/base-page'
import { toast } from '@renderer/components/base/toast'
import { showError } from '@renderer/utils/error-display'
@@ -8,7 +24,12 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { platform } from '@renderer/utils/init'
import { FaNetworkWired } from 'react-icons/fa'
-import { IoMdCloudDownload, IoMdInformationCircleOutline, IoMdRefresh, IoMdShuffle } from 'react-icons/io'
+import {
+ IoMdCloudDownload,
+ IoMdInformationCircleOutline,
+ IoMdRefresh,
+ IoMdShuffle
+} from 'react-icons/io'
import PubSub from 'pubsub-js'
import {
mihomoUpgrade,
@@ -102,22 +123,22 @@ const Mihomo: React.FC = () => {
const [upgrading, setUpgrading] = useState(false)
const [lanOpen, setLanOpen] = useState(false)
const { isOpen, onOpen, onClose } = useDisclosure()
- const [tags, setTags] = useState<{name: string, zipball_url: string, tarball_url: string}[]>([])
+ const [tags, setTags] = useState<{ name: string; zipball_url: string; tarball_url: string }[]>([])
const [loadingTags, setLoadingTags] = useState(false)
const [selectedTag, setSelectedTag] = useState(specificVersion || '')
const [installing, setInstalling] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
const [refreshing, setRefreshing] = useState(false)
-
+
// WebUI管理状态
const [isWebUIModalOpen, setIsWebUIModalOpen] = useState(false)
const [allPanels, setAllPanels] = useState
([])
const [editingPanel, setEditingPanel] = useState(null)
const [newPanelName, setNewPanelName] = useState('')
const [newPanelUrl, setNewPanelUrl] = useState('')
-
+
const urlInputRef = useRef(null)
-
+
// 解析主机和端口
const parseController = () => {
if (externalController) {
@@ -126,12 +147,12 @@ const Mihomo: React.FC = () => {
}
return { host: '127.0.0.1', port: '9090' }
}
-
+
const { host, port } = parseController()
-
+
// 生成随机端口(范围1024-65535)
const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
-
+
// 默认WebUI面板选项
const defaultWebUIPanels: WebUIPanel[] = [
{
@@ -153,7 +174,7 @@ const Mihomo: React.FC = () => {
isDefault: true
}
]
-
+
// 初始化面板列表
useEffect(() => {
const savedPanels = localStorage.getItem('webui-panels')
@@ -163,28 +184,28 @@ const Mihomo: React.FC = () => {
setAllPanels(defaultWebUIPanels)
}
}, [])
-
+
// 保存面板列表到localStorage
useEffect(() => {
if (allPanels.length > 0) {
localStorage.setItem('webui-panels', JSON.stringify(allPanels))
}
}, [allPanels])
-
+
// 在URL输入框光标处插入或替换变量
const insertVariableAtCursor = (variable: string) => {
if (!urlInputRef.current) return
-
+
const input = urlInputRef.current
const start = input.selectionStart || 0
const end = input.selectionEnd || 0
const currentValue = newPanelUrl || ''
-
+
// 如果有选中文本,则替换选中的文本
const newValue = currentValue.substring(0, start) + variable + currentValue.substring(end)
-
+
setNewPanelUrl(newValue)
-
+
// 设置光标位置到插入变量之后
setTimeout(() => {
if (urlInputRef.current) {
@@ -194,16 +215,13 @@ const Mihomo: React.FC = () => {
}
}, 0)
}
-
+
// 打开WebUI面板
const openWebUI = (panel: WebUIPanel) => {
- const url = panel.url
- .replace('%host', host)
- .replace('%port', port)
- .replace('%secret', secret)
+ const url = panel.url.replace('%host', host).replace('%port', port).replace('%secret', secret)
window.open(url, '_blank')
}
-
+
// 添加新面板
const addNewPanel = () => {
if (newPanelName && newPanelUrl) {
@@ -218,14 +236,12 @@ const Mihomo: React.FC = () => {
setEditingPanel(null)
}
}
-
+
// 更新面板
const updatePanel = () => {
if (editingPanel && newPanelName && newPanelUrl) {
- const updatedPanels = allPanels.map(panel =>
- panel.id === editingPanel.id
- ? { ...panel, name: newPanelName, url: newPanelUrl }
- : panel
+ const updatedPanels = allPanels.map((panel) =>
+ panel.id === editingPanel.id ? { ...panel, name: newPanelName, url: newPanelUrl } : panel
)
setAllPanels(updatedPanels)
setEditingPanel(null)
@@ -233,35 +249,35 @@ const Mihomo: React.FC = () => {
setNewPanelUrl('')
}
}
-
+
// 删除面板
const deletePanel = (id: string) => {
- setAllPanels(allPanels.filter(panel => panel.id !== id))
+ setAllPanels(allPanels.filter((panel) => panel.id !== id))
}
-
+
// 开始编辑面板
const startEditing = (panel: WebUIPanel) => {
setEditingPanel(panel)
setNewPanelName(panel.name)
setNewPanelUrl(panel.url)
}
-
+
// 取消编辑
const cancelEditing = () => {
setEditingPanel(null)
setNewPanelName('')
setNewPanelUrl('')
}
-
+
// 恢复默认面板
const restoreDefaultPanels = () => {
setAllPanels(defaultWebUIPanels)
}
-
+
// 用于高亮显示URL中的变量
const HighlightedUrl: React.FC<{ url: string }> = ({ url }) => {
const parts = url.split(/(%host|%port|%secret)/g)
-
+
return (
{parts.map((part, index) => {
@@ -277,14 +293,14 @@ const Mihomo: React.FC = () => {
)
}
-
+
// 可点击的变量标签组件
- const ClickableVariableTag: React.FC<{
- variable: string;
- onClick: (variable: string) => void
+ const ClickableVariableTag: React.FC<{
+ variable: string
+ onClick: (variable: string) => void
}> = ({ variable, onClick }) => {
return (
- onClick(variable)}
>
@@ -292,7 +308,7 @@ const Mihomo: React.FC = () => {
)
}
-
+
const onChangeNeedRestart = async (patch: Partial): Promise => {
await patchControledMihomoConfig(patch)
await restartCore()
@@ -311,7 +327,7 @@ const Mihomo: React.FC = () => {
PubSub.publish('mihomo-core-changed')
}
}
-
+
// 获取GitHub标签列表(带缓存)
const fetchTags = async (forceRefresh = false) => {
setLoadingTags(true)
@@ -326,28 +342,28 @@ const Mihomo: React.FC = () => {
setLoadingTags(false)
}
}
-
+
// 安装特定版本的核心
const installSpecificCore = async () => {
if (!selectedTag) return
-
+
setInstalling(true)
try {
// 下载并安装特定版本的核心
await installSpecificMihomoCore(selectedTag)
-
+
// 更新应用配置
- await patchAppConfig({
+ await patchAppConfig({
core: 'mihomo-specific',
specificVersion: selectedTag
})
-
+
// 重启核心
await restartCore()
-
+
// 关闭模态框
onClose()
-
+
// 通知用户
new Notification(t('mihomo.coreUpgradeSuccess'))
} catch (error) {
@@ -357,7 +373,7 @@ const Mihomo: React.FC = () => {
setInstalling(false)
}
}
-
+
// 刷新标签列表
const refreshTags = async () => {
setRefreshing(true)
@@ -369,7 +385,7 @@ const Mihomo: React.FC = () => {
setRefreshing(false)
}
}
-
+
// 打开模态框时获取标签
const handleOpenModal = async () => {
onOpen()
@@ -377,40 +393,39 @@ const Mihomo: React.FC = () => {
if (tags.length === 0) {
await fetchTags(false) // 使用缓存
}
-
+
// 在后台检查更新
setTimeout(() => {
fetchTags(true) // 强制刷新
}, 100)
}
-
+
// 过滤标签
- const filteredTags = tags.filter(tag =>
+ const filteredTags = tags.filter((tag) =>
tag.name.toLowerCase().includes(searchTerm.toLowerCase())
)
-
+
// 当模态框打开时,确保选中当前版本
useEffect(() => {
if (isOpen && specificVersion) {
setSelectedTag(specificVersion)
}
}, [isOpen, specificVersion])
-
+
return (
<>
{lanOpen && setLanOpen(false)} />}
{/* Smart 内核设置 */}
-
-
+
+
{
>
-
@@ -519,12 +530,14 @@ const Mihomo: React.FC = () => {
className="w-[150px]"
size="sm"
aria-label={t('mihomo.selectCoreVersion')}
- selectedKeys={new Set([
- core
- ])}
+ selectedKeys={new Set([core])}
disallowEmptySelection={true}
onSelectionChange={async (v) => {
- const selectedCore = v.currentKey as 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' | 'mihomo-specific'
+ const selectedCore = v.currentKey as
+ | 'mihomo'
+ | 'mihomo-alpha'
+ | 'mihomo-smart'
+ | 'mihomo-specific'
// 如果切换到特定版本但没有设置specificVersion,则打开选择模态框
if (selectedCore === 'mihomo-specific' && !specificVersion) {
handleOpenModal()
@@ -597,7 +610,6 @@ const Mihomo: React.FC = () => {
/>
-
@@ -637,11 +649,11 @@ const Mihomo: React.FC = () => {
-
+
>
@@ -1032,9 +1048,9 @@ const Mihomo: React.FC = () => {
-
setIsWebUIModalOpen(true)}
>
@@ -1379,7 +1395,7 @@ const Mihomo: React.FC = () => {
{t('mihomo.debug')}
-
+
-
+
{/* WebUI 管理模态框 */}
- {
hideCloseButton
>
-
- {t('settings.webui.manage')}
-
+ {t('settings.webui.manage')}
{/* 添加/编辑面板表单 */}
@@ -1439,26 +1453,21 @@ const Mihomo: React.FC = () => {
{editingPanel ? (
<>
-
{t('common.save')}
-
+
{t('common.cancel')}
>
) : (
- {
{t('settings.webui.addPanel')}
)}
- {
-
+
{/* 面板列表 */}
{t('settings.webui.panels')}
- {allPanels.map(panel => (
-
+ {allPanels.map((panel) => (
+
- openWebUI(panel)}
- >
+ openWebUI(panel)}>
- startEditing(panel)}
>
- deletePanel(panel.id)}
>
@@ -1518,20 +1525,17 @@ const Mihomo: React.FC = () => {
- setIsWebUIModalOpen(false)}
- >
+ setIsWebUIModalOpen(false)}>
{t('common.close')}
-
+
{/* 自定义版本选择模态框 */}
-
{
url,
ext: urlObj.pathname.endsWith('.js') ? 'js' : 'yaml'
})
+ } catch (e) {
+ toast.error(t('override.error.importFailed', { error: String(e) }))
} finally {
setImporting(false)
}
@@ -174,6 +176,7 @@ const Override: React.FC = () => {
{
-
{
- if (key === 'open-substore') {
- navigate('/substore')
- } else if (key.toString().startsWith('sub-')) {
- setSubStoreImporting(true)
- try {
- const sub = subs.find(
- (sub) => sub.name === key.toString().replace('sub-', '')
- )
- await addProfileItem({
- name: sub?.displayName || sub?.name || '',
- substore: !useCustomSubStore,
- type: 'remote',
- url: useCustomSubStore
- ? `${customSubStoreUrl}/download/${key.toString().replace('sub-', '')}?target=ClashMeta`
- : `/download/${key.toString().replace('sub-', '')}`,
- useProxy
- })
- } catch (e) {
- toast.error(String(e))
- } finally {
- setSubStoreImporting(false)
+ {
+ if (key === 'open-substore') {
+ navigate('/substore')
+ } else if (key.toString().startsWith('sub-')) {
+ setSubStoreImporting(true)
+ try {
+ const sub = subs.find(
+ (sub) => sub.name === key.toString().replace('sub-', '')
+ )
+ await addProfileItem({
+ name: sub?.displayName || sub?.name || '',
+ substore: !useCustomSubStore,
+ type: 'remote',
+ url: useCustomSubStore
+ ? `${customSubStoreUrl}/download/${key.toString().replace('sub-', '')}?target=ClashMeta`
+ : `/download/${key.toString().replace('sub-', '')}`,
+ useProxy
+ })
+ } catch (e) {
+ toast.error(String(e))
+ } finally {
+ setSubStoreImporting(false)
+ }
+ } else if (key.toString().startsWith('collection-')) {
+ setSubStoreImporting(true)
+ try {
+ const collection = collections.find(
+ (collection) =>
+ collection.name === key.toString().replace('collection-', '')
+ )
+ await addProfileItem({
+ name: collection?.displayName || collection?.name || '',
+ type: 'remote',
+ substore: !useCustomSubStore,
+ url: useCustomSubStore
+ ? `${customSubStoreUrl}/download/collection/${key.toString().replace('collection-', '')}?target=ClashMeta`
+ : `/download/collection/${key.toString().replace('collection-', '')}`,
+ useProxy
+ })
+ } catch (e) {
+ toast.error(String(e))
+ } finally {
+ setSubStoreImporting(false)
+ }
}
- } else if (key.toString().startsWith('collection-')) {
- setSubStoreImporting(true)
- try {
- const collection = collections.find(
- (collection) =>
- collection.name === key.toString().replace('collection-', '')
- )
- await addProfileItem({
- name: collection?.displayName || collection?.name || '',
- type: 'remote',
- substore: !useCustomSubStore,
- url: useCustomSubStore
- ? `${customSubStoreUrl}/download/collection/${key.toString().replace('collection-', '')}?target=ClashMeta`
- : `/download/collection/${key.toString().replace('collection-', '')}`,
- useProxy
- })
- } catch (e) {
- toast.error(String(e))
- } finally {
- setSubStoreImporting(false)
- }
- }
- }}
- >
- {subStoreMenuItems.map((item) => (
-
- {item.children}
-
- ))}
-
+ }}
+ >
+ {subStoreMenuItems.map((item) => (
+
+ {item.children}
+
+ ))}
+
)}
diff --git a/src/renderer/src/pages/proxies.tsx b/src/renderer/src/pages/proxies.tsx
index cee40a5..240be10 100644
--- a/src/renderer/src/pages/proxies.tsx
+++ b/src/renderer/src/pages/proxies.tsx
@@ -26,12 +26,14 @@ const GROUP_EXPAND_STATE_KEY = 'proxy_group_expand_state'
const SCROLL_POSITION_KEY = 'proxy_scroll_position'
// 自定义 hook 用于管理展开状态
-const useProxyState = (groups: IMihomoMixedGroup[]): {
- virtuosoRef: React.RefObject;
- isOpen: boolean[];
- setIsOpen: React.Dispatch>;
- initialTopMostItemIndex: number;
- handleRangeChanged: (range: { startIndex: number }) => void;
+const useProxyState = (
+ groups: IMihomoMixedGroup[]
+): {
+ virtuosoRef: React.RefObject
+ isOpen: boolean[]
+ setIsOpen: React.Dispatch>
+ initialTopMostItemIndex: number
+ handleRangeChanged: (range: { startIndex: number }) => void
} => {
const virtuosoRef = useRef(null)
@@ -56,7 +58,7 @@ const useProxyState = (groups: IMihomoMixedGroup[]): {
console.error('Failed to save scroll position:', error)
}
}, [])
-
+
// 初始化展开状态
const [isOpen, setIsOpen] = useState(() => {
try {
@@ -118,9 +120,10 @@ const Proxies: React.FC = () => {
proxyCols = 'auto',
delayTestConcurrency = 50
} = appConfig || {}
-
+
const [cols, setCols] = useState(1)
- const { virtuosoRef, isOpen, setIsOpen, initialTopMostItemIndex, handleRangeChanged } = useProxyState(groups)
+ const { virtuosoRef, isOpen, setIsOpen, initialTopMostItemIndex, handleRangeChanged } =
+ useProxyState(groups)
const [delaying, setDelaying] = useState(Array(groups.length).fill(false))
const [proxyDelaying, setProxyDelaying] = useState>(new Set())
const [searchValue, setSearchValue] = useState(Array(groups.length).fill(''))
@@ -172,84 +175,90 @@ const Proxies: React.FC = () => {
return { groupCounts, allProxies }
}, [groups, isOpen, proxyDisplayOrder, cols, searchValue, sortProxies])
- const onChangeProxy = useCallback(async (group: string, proxy: string): Promise => {
- await mihomoChangeProxy(group, proxy)
- if (autoCloseConnection) {
- await mihomoCloseAllConnections()
- }
- mutate()
- }, [autoCloseConnection, mutate])
+ const onChangeProxy = useCallback(
+ async (group: string, proxy: string): Promise => {
+ await mihomoChangeProxy(group, proxy)
+ if (autoCloseConnection) {
+ await mihomoCloseAllConnections()
+ }
+ mutate()
+ },
+ [autoCloseConnection, mutate]
+ )
const onProxyDelay = useCallback(async (proxy: string, url?: string): Promise => {
return await mihomoProxyDelay(proxy, url)
}, [])
- const onGroupDelay = useCallback(async (index: number): Promise => {
- if (allProxies[index].length === 0) {
- setIsOpen((prev) => {
- const newOpen = [...prev]
- newOpen[index] = true
- return newOpen
- })
- }
- setDelaying((prev) => {
- const newDelaying = [...prev]
- newDelaying[index] = true
- return newDelaying
- })
-
- // 管理测试状态
- const groupProxies = allProxies[index]
- setProxyDelaying((prev) => {
- const newSet = new Set(prev)
- groupProxies.forEach(proxy => newSet.add(proxy.name))
- return newSet
- })
-
- try {
- // 限制并发数量
- const result: Promise[] = []
- const runningList: Promise[] = []
- for (const proxy of allProxies[index]) {
- const promise = Promise.resolve().then(async () => {
- try {
- await mihomoProxyDelay(proxy.name, groups[index].testUrl)
- } catch {
- // ignore
- } finally {
- // 更新状态
- setProxyDelaying((prev) => {
- const newSet = new Set(prev)
- newSet.delete(proxy.name)
- return newSet
- })
- mutate()
- }
+ const onGroupDelay = useCallback(
+ async (index: number): Promise => {
+ if (allProxies[index].length === 0) {
+ setIsOpen((prev) => {
+ const newOpen = [...prev]
+ newOpen[index] = true
+ return newOpen
})
- result.push(promise)
- const running = promise.then(() => {
- runningList.splice(runningList.indexOf(running), 1)
- })
- runningList.push(running)
- if (runningList.length >= (delayTestConcurrency || 50)) {
- await Promise.race(runningList)
- }
}
- await Promise.all(result)
- } finally {
setDelaying((prev) => {
const newDelaying = [...prev]
- newDelaying[index] = false
+ newDelaying[index] = true
return newDelaying
})
- // 状态清理
+
+ // 管理测试状态
+ const groupProxies = allProxies[index]
setProxyDelaying((prev) => {
const newSet = new Set(prev)
- groupProxies.forEach(proxy => newSet.delete(proxy.name))
+ groupProxies.forEach((proxy) => newSet.add(proxy.name))
return newSet
})
- }
- }, [allProxies, groups, delayTestConcurrency, mutate, setIsOpen])
+
+ try {
+ // 限制并发数量
+ const result: Promise[] = []
+ const runningList: Promise[] = []
+ for (const proxy of allProxies[index]) {
+ const promise = Promise.resolve().then(async () => {
+ try {
+ await mihomoProxyDelay(proxy.name, groups[index].testUrl)
+ } catch {
+ // ignore
+ } finally {
+ // 更新状态
+ setProxyDelaying((prev) => {
+ const newSet = new Set(prev)
+ newSet.delete(proxy.name)
+ return newSet
+ })
+ mutate()
+ }
+ })
+ result.push(promise)
+ const running = promise.then(() => {
+ runningList.splice(runningList.indexOf(running), 1)
+ })
+ runningList.push(running)
+ if (runningList.length >= (delayTestConcurrency || 50)) {
+ await Promise.race(runningList)
+ }
+ }
+ await Promise.all(result)
+ } finally {
+ setDelaying((prev) => {
+ const newDelaying = [...prev]
+ newDelaying[index] = false
+ return newDelaying
+ })
+ // 状态清理
+ setProxyDelaying((prev) => {
+ const newSet = new Set(prev)
+ groupProxies.forEach((proxy) => newSet.delete(proxy.name))
+ return newSet
+ })
+ }
+ },
+ [allProxies, groups, delayTestConcurrency, mutate, setIsOpen]
+ )
const calcCols = useCallback((): number => {
if (proxyCols !== 'auto') {
@@ -300,170 +309,200 @@ const Proxies: React.FC = () => {
loadImages()
}, [groups, mutate])
- const renderGroupContent = useCallback((index: number) => {
- return groups[index] ? (
-
-
{
- setIsOpen((prev) => {
- const newOpen = [...prev]
- newOpen[index] = !prev[index]
- return newOpen
- })
- }}
+ const renderGroupContent = useCallback(
+ (index: number) => {
+ return groups[index] ? (
+
-
-
-
- {groups[index].icon ? (
-
- ) : null}
-
-
- {groups[index].name}
-
- {proxyDisplayMode === 'full' && (
+
{
+ setIsOpen((prev) => {
+ const newOpen = [...prev]
+ newOpen[index] = !prev[index]
+ return newOpen
+ })
+ }}
+ >
+
+
+
+ {groups[index].icon ? (
+
+ ) : null}
+
- {groups[index].type}
+ {groups[index].name}
- )}
+ {proxyDisplayMode === 'full' && (
+
+ {groups[index].type}
+
+ )}
+ {proxyDisplayMode === 'full' && (
+
+ {groups[index].now}
+
+ )}
+
+
+
{proxyDisplayMode === 'full' && (
-
- {groups[index].now}
-
+
+ {groups[index].all.length}
+
)}
+
{
+ setSearchValue((prev) => {
+ const newSearchValue = [...prev]
+ newSearchValue[index] = v
+ return newSearchValue
+ })
+ }}
+ />
+ {
+ if (!isOpen[index]) {
+ setIsOpen((prev) => {
+ const newOpen = [...prev]
+ newOpen[index] = true
+ return newOpen
+ })
+ }
+ let i = 0
+ for (let j = 0; j < index; j++) {
+ i += groupCounts[j]
+ }
+ i += Math.floor(
+ allProxies[index].findIndex((proxy) => proxy.name === groups[index].now) /
+ cols
+ )
+ virtuosoRef.current?.scrollToIndex({
+ index: Math.floor(i),
+ align: 'start'
+ })
+ }}
+ >
+
+
+ {
+ onGroupDelay(index)
+ }}
+ >
+
+
+
-
- {proxyDisplayMode === 'full' && (
-
- {groups[index].all.length}
-
- )}
- {
- setSearchValue((prev) => {
- const newSearchValue = [...prev]
- newSearchValue[index] = v
- return newSearchValue
- })
- }}
- />
- {
- if (!isOpen[index]) {
- setIsOpen((prev) => {
- const newOpen = [...prev]
- newOpen[index] = true
- return newOpen
- })
- }
- let i = 0
- for (let j = 0; j < index; j++) {
- i += groupCounts[j]
- }
- i += Math.floor(
- allProxies[index].findIndex(
- (proxy) => proxy.name === groups[index].now
- ) / cols
- )
- virtuosoRef.current?.scrollToIndex({
- index: Math.floor(i),
- align: 'start'
- })
- }}
- >
-
-
- {
- onGroupDelay(index)
- }}
- >
-
-
-
-
-
-
-
-
- ) : (
-
Never See This
- )
- }, [groups, groupCounts, isOpen, proxyDisplayMode, searchValue, delaying, cols, allProxies, virtuosoRef, t, setIsOpen, onGroupDelay])
+
+
+
+ ) : (
+ Never See This
+ )
+ },
+ [
+ groups,
+ groupCounts,
+ isOpen,
+ proxyDisplayMode,
+ searchValue,
+ delaying,
+ cols,
+ allProxies,
+ virtuosoRef,
+ t,
+ setIsOpen,
+ onGroupDelay
+ ]
+ )
- const renderItemContent = useCallback((index: number, groupIndex: number) => {
- let innerIndex = index
- groupCounts.slice(0, groupIndex).forEach((count) => {
- innerIndex -= count
- })
- return allProxies[groupIndex] ? (
-
- {Array.from({ length: cols }).map((_, i) => {
- if (!allProxies[groupIndex][innerIndex * cols + i]) return null
- return (
-
- )
- })}
-
- ) : (
- Never See This
- )
- }, [groupCounts, allProxies, proxyCols, cols, groups, proxyDisplayMode, proxyDelaying, mutate, onProxyDelay, onChangeProxy])
+ const renderItemContent = useCallback(
+ (index: number, groupIndex: number) => {
+ let innerIndex = index
+ groupCounts.slice(0, groupIndex).forEach((count) => {
+ innerIndex -= count
+ })
+ return allProxies[groupIndex] ? (
+
+ {Array.from({ length: cols }).map((_, i) => {
+ if (!allProxies[groupIndex][innerIndex * cols + i]) return null
+ return (
+
+ )
+ })}
+
+ ) : (
+ Never See This
+ )
+ },
+ [
+ groupCounts,
+ allProxies,
+ proxyCols,
+ cols,
+ groups,
+ proxyDisplayMode,
+ proxyDelaying,
+ mutate,
+ onProxyDelay,
+ onChangeProxy
+ ]
+ )
return (
{
)
}
-export default Proxies
\ No newline at end of file
+export default Proxies
diff --git a/src/renderer/src/pages/resources.tsx b/src/renderer/src/pages/resources.tsx
index dbb2ea2..abf2439 100644
--- a/src/renderer/src/pages/resources.tsx
+++ b/src/renderer/src/pages/resources.tsx
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
const Resources: React.FC = () => {
const { t } = useTranslation()
-
+
return (
diff --git a/src/renderer/src/pages/settings.tsx b/src/renderer/src/pages/settings.tsx
index fb7be25..3e190b9 100644
--- a/src/renderer/src/pages/settings.tsx
+++ b/src/renderer/src/pages/settings.tsx
@@ -66,7 +66,7 @@ const Settings: React.FC = () => {
-
+
)
diff --git a/src/renderer/src/utils/dayjs.ts b/src/renderer/src/utils/dayjs.ts
index 1910a06..35978fe 100644
--- a/src/renderer/src/utils/dayjs.ts
+++ b/src/renderer/src/utils/dayjs.ts
@@ -19,4 +19,4 @@ updateDayjsLocale()
// 监听语言变化
i18n.on('languageChanged', updateDayjsLocale)
-export default dayjs
\ No newline at end of file
+export default dayjs
diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts
index c633307..0957d4e 100644
--- a/src/renderer/src/utils/ipc.ts
+++ b/src/renderer/src/utils/ipc.ts
@@ -10,12 +10,16 @@ function ipcErrorWrapper(response: any): any {
}
// GitHub版本管理相关IPC调用
-export async function fetchMihomoTags(forceRefresh = false): Promise<{name: string, zipball_url: string, tarball_url: string}[]> {
+export async function fetchMihomoTags(
+ forceRefresh = false
+): Promise<{ name: string; zipball_url: string; tarball_url: string }[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('fetchMihomoTags', forceRefresh))
}
export async function installSpecificMihomoCore(version: string): Promise {
- return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('installSpecificMihomoCore', version))
+ return ipcErrorWrapper(
+ await window.electron.ipcRenderer.invoke('installSpecificMihomoCore', version)
+ )
}
export async function clearMihomoVersionCache(): Promise {
@@ -101,15 +105,15 @@ export async function mihomoGroupDelay(group: string, url?: string): Promise> {
- return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartGroupWeights', groupName))
+ return ipcErrorWrapper(
+ await window.electron.ipcRenderer.invoke('mihomoSmartGroupWeights', groupName)
+ )
}
export async function mihomoSmartFlushCache(configName?: string): Promise {
- return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartFlushCache', configName))
-}
-
-export async function showDetailedError(title: string, message: string): Promise {
- return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showDetailedError', title, message))
+ return ipcErrorWrapper(
+ await window.electron.ipcRenderer.invoke('mihomoSmartFlushCache', configName)
+ )
}
export async function getSmartOverrideContent(): Promise {
@@ -209,7 +213,9 @@ export async function setProfileStr(id: string, str: string): Promise {
}
export async function convertMrsRuleset(path: string, behavior: string): Promise {
- return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('convertMrsRuleset', path, behavior))
+ return ipcErrorWrapper(
+ await window.electron.ipcRenderer.invoke('convertMrsRuleset', path, behavior)
+ )
}
export async function getOverrideConfig(force = false): Promise {
@@ -285,7 +291,9 @@ export async function showTunPermissionDialog(): Promise {
}
export async function showErrorDialog(title: string, message: string): Promise {
- return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showErrorDialog', title, message))
+ return ipcErrorWrapper(
+ await window.electron.ipcRenderer.invoke('showErrorDialog', title, message)
+ )
}
export async function getFilePath(ext: string[]): Promise {
@@ -352,9 +360,7 @@ export async function webdavDelete(filename: string): Promise {
// WebDAV 备份调度器相关 IPC 调用
export async function reinitWebdavBackupScheduler(): Promise {
- return ipcErrorWrapper(
- await window.electron.ipcRenderer.invoke('reinitWebdavBackupScheduler')
- )
+ return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('reinitWebdavBackupScheduler'))
}
// 本地备份相关 IPC 调用