support multi column

This commit is contained in:
pompurin404 2024-08-25 19:06:36 +08:00
parent c72618570a
commit d4698583ba
No known key found for this signature in database
6 changed files with 94 additions and 33 deletions

View File

@ -1,6 +1,7 @@
### New Features ### New Features
- Linux支持手动授权内核 - Linux支持手动授权内核
- 代理节点支持多列展示
### Bug Fixes ### Bug Fixes

View File

@ -157,9 +157,6 @@ export function createWindow(show = false): void {
mainWindow.webContents.on('did-fail-load', () => { mainWindow.webContents.on('did-fail-load', () => {
mainWindow?.webContents.reload() mainWindow?.webContents.reload()
}) })
mainWindow.on('resize', () => {
mainWindow?.webContents.send('resize')
})
mainWindow.on('show', () => { mainWindow.on('show', () => {
startMihomoMemory() startMihomoMemory()

View File

@ -58,12 +58,11 @@ export const BaseEditor: React.FC<Props> = (props) => {
} }
useEffect(() => { useEffect(() => {
window.electron.ipcRenderer.on('resize', () => { window.onresize = (): void => {
editorRef.current?.layout() editorRef.current?.layout()
}) }
return (): void => { return (): void => {
window.electron.ipcRenderer.removeAllListeners('resize') window.onresize = null
editorRef.current?.dispose() editorRef.current?.dispose()
editorRef.current = undefined editorRef.current = undefined
} }

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import SettingCard from '../base/base-setting-card' import SettingCard from '../base/base-setting-card'
import SettingItem from '../base/base-setting-item' import SettingItem from '../base/base-setting-item'
import { Input, Switch } from '@nextui-org/react' import { Input, Select, SelectItem, Switch } from '@nextui-org/react'
import { useAppConfig } from '@renderer/hooks/use-app-config' import { useAppConfig } from '@renderer/hooks/use-app-config'
import debounce from '@renderer/utils/debounce' import debounce from '@renderer/utils/debounce'
import { patchControledMihomoConfig } from '@renderer/utils/ipc' import { patchControledMihomoConfig } from '@renderer/utils/ipc'
@ -14,7 +14,8 @@ const MihomoConfig: React.FC = () => {
delayTestTimeout, delayTestTimeout,
autoCloseConnection = true, autoCloseConnection = true,
delayTestUrl, delayTestUrl,
userAgent userAgent,
proxyCols = 'auto'
} = appConfig || {} } = appConfig || {}
const [url, setUrl] = useState(delayTestUrl) const [url, setUrl] = useState(delayTestUrl)
const setUrlDebounce = debounce((v: string) => { const setUrlDebounce = debounce((v: string) => {
@ -62,6 +63,22 @@ const MihomoConfig: React.FC = () => {
}} }}
/> />
</SettingItem> </SettingItem>
<SettingItem title="代理节点展示列数" divider>
<Select
className="w-[150px]"
size="sm"
selectedKeys={new Set([proxyCols])}
onSelectionChange={async (v) => {
await patchAppConfig({ proxyCols: v.currentKey as 'auto' | '1' | '2' | '3' | '4' })
}}
>
<SelectItem key="auto"></SelectItem>
<SelectItem key="1"></SelectItem>
<SelectItem key="2"></SelectItem>
<SelectItem key="3"></SelectItem>
<SelectItem key="4"></SelectItem>
</Select>
</SettingItem>
<SettingItem title="接管DNS设置" divider> <SettingItem title="接管DNS设置" divider>
<Switch <Switch
size="sm" size="sm"

View File

@ -13,7 +13,7 @@ import { FaBoltLightning } from 'react-icons/fa6'
import { TbCircleLetterD } from 'react-icons/tb' import { TbCircleLetterD } from 'react-icons/tb'
import { FaLocationCrosshairs } from 'react-icons/fa6' import { FaLocationCrosshairs } from 'react-icons/fa6'
import { RxLetterCaseCapitalize } from 'react-icons/rx' import { RxLetterCaseCapitalize } from 'react-icons/rx'
import { useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { GroupedVirtuoso, GroupedVirtuosoHandle } from 'react-virtuoso' import { GroupedVirtuoso, GroupedVirtuosoHandle } from 'react-virtuoso'
import ProxyItem from '@renderer/components/proxies/proxy-item' import ProxyItem from '@renderer/components/proxies/proxy-item'
@ -26,15 +26,19 @@ const Proxies: React.FC = () => {
const { const {
proxyDisplayMode = 'simple', proxyDisplayMode = 'simple',
proxyDisplayOrder = 'default', proxyDisplayOrder = 'default',
autoCloseConnection = true autoCloseConnection = true,
proxyCols = 'auto'
} = appConfig || {} } = appConfig || {}
const [cols, setCols] = useState(1)
const [isOpen, setIsOpen] = useState(Array(groups.length).fill(false)) const [isOpen, setIsOpen] = useState(Array(groups.length).fill(false))
const virtuosoRef = useRef<GroupedVirtuosoHandle>(null) const virtuosoRef = useRef<GroupedVirtuosoHandle>(null)
const { groupCounts, allProxies } = useMemo(() => { const { groupCounts, allProxies } = useMemo(() => {
const groupCounts = groups.map((group, index) => { const groupCounts = groups.map((group, index) => {
return isOpen[index] ? group.all.length : 0 if (!isOpen[index]) return 0
const count = Math.floor(group.all.length / cols)
return group.all.length % cols === 0 ? count : count + 1
}) })
const allProxies: (IMihomoProxy | IMihomoGroup)[] = [] const allProxies: (IMihomoProxy | IMihomoGroup)[][] = []
groups.forEach((group, index) => { groups.forEach((group, index) => {
if (isOpen[index]) { if (isOpen[index]) {
let groupProxies = [...group.all] let groupProxies = [...group.all]
@ -50,7 +54,9 @@ const Proxies: React.FC = () => {
if (proxyDisplayOrder === 'name') { if (proxyDisplayOrder === 'name') {
groupProxies = groupProxies.sort((a, b) => a.name.localeCompare(b.name)) groupProxies = groupProxies.sort((a, b) => a.name.localeCompare(b.name))
} }
allProxies.push(...groupProxies) allProxies.push(groupProxies)
} else {
allProxies.push([])
} }
}) })
@ -74,6 +80,33 @@ const Proxies: React.FC = () => {
await mihomoGroupDelay(group, url) await mihomoGroupDelay(group, url)
} }
const calcCols = (): void => {
if (window.innerWidth >= 1280) {
setCols(4)
return
}
if (window.innerWidth >= 1024) {
setCols(3)
return
}
if (window.innerWidth >= 768) {
setCols(2)
return
}
}
useEffect(() => {
if (proxyCols !== 'auto') {
setCols(parseInt(proxyCols))
return
}
calcCols()
window.onresize = calcCols
return (): void => {
window.onresize = null
}
}, [])
return ( return (
<BasePage <BasePage
title="代理组" title="代理组"
@ -155,7 +188,6 @@ const Proxies: React.FC = () => {
<div className="inline flag-emoji h-[32px] text-md leading-[32px]"> <div className="inline flag-emoji h-[32px] text-md leading-[32px]">
{groups[index].name} {groups[index].name}
</div> </div>
{proxyDisplayMode === 'full' && ( {proxyDisplayMode === 'full' && (
<div className="inline ml-2 text-sm text-default-500"> <div className="inline ml-2 text-sm text-default-500">
{groups[index].type} {groups[index].type}
@ -185,13 +217,15 @@ const Proxies: React.FC = () => {
for (let j = 0; j < index; j++) { for (let j = 0; j < index; j++) {
i += groupCounts[j] i += groupCounts[j]
} }
for (let j = 0; j < groupCounts[index]; j++) { i += Math.floor(
if (allProxies[i + j].name === groups[index].now) { allProxies[index].findIndex(
i += j (proxy) => proxy.name === groups[index].now
break ) / cols
} )
} virtuosoRef.current?.scrollToIndex({
virtuosoRef.current?.scrollToIndex({ index: i, align: 'start' }) index: Math.floor(i),
align: 'start'
})
}} }}
> >
<FaLocationCrosshairs className="text-lg text-default-500" /> <FaLocationCrosshairs className="text-lg text-default-500" />
@ -220,17 +254,29 @@ const Proxies: React.FC = () => {
) )
}} }}
itemContent={(index, groupIndex) => { itemContent={(index, groupIndex) => {
return allProxies[index] ? ( let innerIndex = index
<div className="pt-2 mx-2"> groupCounts.slice(0, groupIndex).forEach((count) => {
innerIndex -= count
})
return allProxies[groupIndex] ? (
<div className={`grid grid-cols-${cols} gap-2 pt-2 mx-2`}>
{Array.from({ length: cols }).map((_, i) => {
if (!allProxies[groupIndex][innerIndex * cols + i]) return null
return (
<ProxyItem <ProxyItem
key={allProxies[groupIndex][innerIndex * cols + i].name}
mutateProxies={mutate} mutateProxies={mutate}
onProxyDelay={onProxyDelay} onProxyDelay={onProxyDelay}
onSelect={onChangeProxy} onSelect={onChangeProxy}
proxy={allProxies[index]} proxy={allProxies[groupIndex][innerIndex * cols + i]}
group={groups[groupIndex]} group={groups[groupIndex]}
proxyDisplayMode={proxyDisplayMode} proxyDisplayMode={proxyDisplayMode}
selected={allProxies[index]?.name === groups[groupIndex].now} selected={
allProxies[groupIndex][innerIndex * cols + i]?.name === groups[groupIndex].now
}
/> />
)
})}
</div> </div>
) : ( ) : (
<div>Never See This</div> <div>Never See This</div>

View File

@ -215,6 +215,7 @@ interface IAppConfig {
proxyDisplayMode: 'simple' | 'full' proxyDisplayMode: 'simple' | 'full'
proxyDisplayOrder: 'default' | 'delay' | 'name' proxyDisplayOrder: 'default' | 'delay' | 'name'
envType?: 'bash' | 'cmd' | 'powershell' envType?: 'bash' | 'cmd' | 'powershell'
proxyCols: 'auto' | '1' | '2' | '3' | '4'
proxyInTray: boolean proxyInTray: boolean
siderOrder: string[] siderOrder: string[]
appTheme: AppTheme appTheme: AppTheme