auto pin global group
Some checks are pending
Build / windows (arm64) (push) Waiting to run
Build / windows (ia32) (push) Waiting to run
Build / windows (x64) (push) Waiting to run
Build / windows7 (ia32) (push) Waiting to run
Build / windows7 (x64) (push) Waiting to run
Build / linux (arm64) (push) Waiting to run
Build / linux (x64) (push) Waiting to run
Build / macos (arm64) (push) Waiting to run
Build / macos (x64) (push) Waiting to run
Build / artifact (push) Blocked by required conditions
Build / updater (push) Blocked by required conditions
Build / aur-release-updater (mihomo-party) (push) Blocked by required conditions
Build / aur-release-updater (mihomo-party-bin) (push) Blocked by required conditions
Build / aur-release-updater (mihomo-party-electron) (push) Blocked by required conditions
Build / aur-release-updater (mihomo-party-electron-bin) (push) Blocked by required conditions
Build / aur-git-updater (push) Waiting to run
Build / Update WinGet Package (push) Blocked by required conditions
Build / Update Homebrew cask (push) Blocked by required conditions

This commit is contained in:
pompurin404 2024-10-14 23:49:59 +08:00
parent b4a9da92f6
commit dcb59e767c
No known key found for this signature in database
15 changed files with 213 additions and 188 deletions

View File

@ -147,7 +147,8 @@ export async function startCore(detached = false): Promise<Promise<void>[]> {
child.stdout?.on('data', async (data) => { child.stdout?.on('data', async (data) => {
if (data.toString().includes('Start initial Compatible provider default')) { if (data.toString().includes('Start initial Compatible provider default')) {
try { try {
mainWindow?.webContents.send('coreRestart') mainWindow?.webContents.send('groupsUpdated')
mainWindow?.webContents.send('rulesUpdated')
await uploadRuntimeConfig() await uploadRuntimeConfig()
} catch { } catch {
// ignore // ignore

View File

@ -76,6 +76,8 @@ export const mihomoProxies = async (): Promise<IMihomoProxies> => {
} }
export const mihomoGroups = async (): Promise<IMihomoMixedGroup[]> => { export const mihomoGroups = async (): Promise<IMihomoMixedGroup[]> => {
const { mode = 'rule' } = await getControledMihomoConfig()
if (mode === 'direct') return []
const proxies = await mihomoProxies() const proxies = await mihomoProxies()
const runtime = await getRuntimeConfig() const runtime = await getRuntimeConfig()
const groups: IMihomoMixedGroup[] = [] const groups: IMihomoMixedGroup[] = []
@ -95,6 +97,10 @@ export const mihomoGroups = async (): Promise<IMihomoMixedGroup[]> => {
groups.push({ ...newGlobal, all: newAll }) groups.push({ ...newGlobal, all: newAll })
} }
} }
if (mode === 'global') {
const global = groups.findIndex((group) => group.name === 'GLOBAL')
groups.unshift(groups.splice(global, 1)[0])
}
return groups return groups
} }

View File

@ -111,6 +111,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
await patchControledMihomoConfig({ mode: 'rule' }) await patchControledMihomoConfig({ mode: 'rule' })
await patchMihomoConfig({ mode: 'rule' }) await patchMihomoConfig({ mode: 'rule' })
mainWindow?.webContents.send('controledMihomoConfigUpdated') mainWindow?.webContents.send('controledMihomoConfigUpdated')
mainWindow?.webContents.send('groupsUpdated')
ipcMain.emit('updateTrayMenu') ipcMain.emit('updateTrayMenu')
} }
}, },
@ -124,6 +125,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
await patchControledMihomoConfig({ mode: 'global' }) await patchControledMihomoConfig({ mode: 'global' })
await patchMihomoConfig({ mode: 'global' }) await patchMihomoConfig({ mode: 'global' })
mainWindow?.webContents.send('controledMihomoConfigUpdated') mainWindow?.webContents.send('controledMihomoConfigUpdated')
mainWindow?.webContents.send('groupsUpdated')
ipcMain.emit('updateTrayMenu') ipcMain.emit('updateTrayMenu')
} }
}, },
@ -137,6 +139,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
await patchControledMihomoConfig({ mode: 'direct' }) await patchControledMihomoConfig({ mode: 'direct' })
await patchMihomoConfig({ mode: 'direct' }) await patchMihomoConfig({ mode: 'direct' })
mainWindow?.webContents.send('controledMihomoConfigUpdated') mainWindow?.webContents.send('controledMihomoConfigUpdated')
mainWindow?.webContents.send('groupsUpdated')
ipcMain.emit('updateTrayMenu') ipcMain.emit('updateTrayMenu')
} }
}, },

View File

@ -22,7 +22,7 @@ const CollapseInput: React.FC<CollapseInputProps> = (props) => {
}} }}
endContent={ endContent={
<div <div
className="cursor-pointer p-2 text-lg text-default-500" className="cursor-pointer p-2 text-lg text-foreground-500"
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
inputRef.current?.focus() inputRef.current?.focus()

View File

@ -51,7 +51,7 @@ const ConnectionItem: React.FC<Props> = (props) => {
info.metadata.destinationIP || info.metadata.destinationIP ||
info.metadata.remoteDestination} info.metadata.remoteDestination}
</div> </div>
<small className="whitespace-nowrap text-default-500"> <small className="whitespace-nowrap text-foreground-500">
{dayjs(info.start).fromNow()} {dayjs(info.start).fromNow()}
</small> </small>
</CardHeader> </CardHeader>

View File

@ -16,7 +16,7 @@ const LogItem: React.FC<IMihomoLogInfo & { index: number }> = (props) => {
<div className={`mr-2 text-lg font-bold text-${colorMap[type]}`}> <div className={`mr-2 text-lg font-bold text-${colorMap[type]}`}>
{props.type.toUpperCase()} {props.type.toUpperCase()}
</div> </div>
<small className="text-default-500">{time}</small> <small className="text-foreground-500">{time}</small>
</CardHeader> </CardHeader>
<CardBody className="pt-0 text-sm">{payload}</CardBody> <CardBody className="pt-0 text-sm">{payload}</CardBody>
</Card> </Card>

View File

@ -34,7 +34,7 @@ const EditFileModal: React.FC<Props> = (props) => {
<ModalHeader className="flex pb-0 app-drag"> <ModalHeader className="flex pb-0 app-drag">
<div className="flex justify-start"> <div className="flex justify-start">
<div className="flex items-center"></div> <div className="flex items-center"></div>
<small className="ml-2 text-default-500"> <small className="ml-2 text-foreground-500">
使 使
<Button <Button
size="sm" size="sm"

View File

@ -63,7 +63,7 @@ const ProxyItem: React.FC<Props> = (props) => {
{proxy.name} {proxy.name}
</div> </div>
{proxyDisplayMode === 'full' && ( {proxyDisplayMode === 'full' && (
<div className="inline ml-2 text-default-500" title={proxy.type}> <div className="inline ml-2 text-foreground-500" title={proxy.type}>
{proxy.type} {proxy.type}
</div> </div>
)} )}

View File

@ -71,7 +71,7 @@ const ProxyProvider: React.FC = () => {
divider={!provider.subscriptionInfo && index !== providers.length - 1} divider={!provider.subscriptionInfo && index !== providers.length - 1}
> >
{ {
<div className="flex h-[32px] leading-[32px] text-default-500"> <div className="flex h-[32px] leading-[32px] text-foreground-500">
<div>{dayjs(provider.updatedAt).fromNow()}</div> <div>{dayjs(provider.updatedAt).fromNow()}</div>
<Button <Button
isIconOnly isIconOnly
@ -90,14 +90,14 @@ const ProxyProvider: React.FC = () => {
<SettingItem <SettingItem
divider={index !== providers.length - 1} divider={index !== providers.length - 1}
title={ title={
<div className="text-default-500">{`${calcTraffic( <div className="text-foreground-500">{`${calcTraffic(
provider.subscriptionInfo.Upload + provider.subscriptionInfo.Download provider.subscriptionInfo.Upload + provider.subscriptionInfo.Download
)} )}
/${calcTraffic(provider.subscriptionInfo.Total)}`}</div> /${calcTraffic(provider.subscriptionInfo.Total)}`}</div>
} }
> >
{provider.subscriptionInfo && ( {provider.subscriptionInfo && (
<div className="h-[32px] leading-[32px] text-default-500"> <div className="h-[32px] leading-[32px] text-foreground-500">
{provider.subscriptionInfo.Expire {provider.subscriptionInfo.Expire
? dayjs.unix(provider.subscriptionInfo.Expire).format('YYYY-MM-DD') ? dayjs.unix(provider.subscriptionInfo.Expire).format('YYYY-MM-DD')
: '长期有效'} : '长期有效'}

View File

@ -65,7 +65,7 @@ const RuleProvider: React.FC = () => {
} }
> >
{ {
<div className="flex h-[32px] leading-[32px] text-default-500"> <div className="flex h-[32px] leading-[32px] text-foreground-500">
<div>{dayjs(provider.updatedAt).fromNow()}</div> <div>{dayjs(provider.updatedAt).fromNow()}</div>
<Button <Button
isIconOnly isIconOnly
@ -81,10 +81,10 @@ const RuleProvider: React.FC = () => {
} }
</SettingItem> </SettingItem>
<SettingItem <SettingItem
title={<div className="text-default-500">{provider.format}</div>} title={<div className="text-foreground-500">{provider.format}</div>}
divider={index !== providers.length - 1} divider={index !== providers.length - 1}
> >
<div className="h-[32px] leading-[32px] text-default-500"> <div className="h-[32px] leading-[32px] text-foreground-500">
{provider.vehicleType}::{provider.behavior} {provider.vehicleType}::{provider.behavior}
</div> </div>
</SettingItem> </SettingItem>

View File

@ -10,7 +10,7 @@ const RuleItem: React.FC<IMihomoRulesDetail & { index: number }> = (props) => {
<div title={payload} className="text-ellipsis whitespace-nowrap overflow-hidden"> <div title={payload} className="text-ellipsis whitespace-nowrap overflow-hidden">
{payload} {payload}
</div> </div>
<div className="flex justify-start text-default-500"> <div className="flex justify-start text-foreground-500">
<div>{type}</div> <div>{type}</div>
<div className="ml-2">{proxy}</div> <div className="ml-2">{proxy}</div>
</div> </div>

View File

@ -1,11 +1,13 @@
import { Tabs, Tab } from '@nextui-org/react' import { Tabs, Tab } from '@nextui-org/react'
import { useAppConfig } from '@renderer/hooks/use-app-config' import { useAppConfig } from '@renderer/hooks/use-app-config'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { useGroups } from '@renderer/hooks/use-groups'
import { mihomoCloseAllConnections, patchMihomoConfig } from '@renderer/utils/ipc' import { mihomoCloseAllConnections, patchMihomoConfig } from '@renderer/utils/ipc'
import { Key } from 'react' import { Key } from 'react'
const OutboundModeSwitcher: React.FC = () => { const OutboundModeSwitcher: React.FC = () => {
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig() const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { mutate: mutateGroups } = useGroups()
const { appConfig } = useAppConfig() const { appConfig } = useAppConfig()
const { autoCloseConnection = true } = appConfig || {} const { autoCloseConnection = true } = appConfig || {}
const { mode } = controledMihomoConfig || {} const { mode } = controledMihomoConfig || {}
@ -16,6 +18,7 @@ const OutboundModeSwitcher: React.FC = () => {
if (autoCloseConnection) { if (autoCloseConnection) {
await mihomoCloseAllConnections() await mihomoCloseAllConnections()
} }
mutateGroups()
window.electron.ipcRenderer.send('updateTrayMenu') window.electron.ipcRenderer.send('updateTrayMenu')
} }
if (!mode) return null if (!mode) return null

View File

@ -16,11 +16,11 @@ export const GroupsProvider: React.FC<{ children: ReactNode }> = ({ children })
}) })
React.useEffect(() => { React.useEffect(() => {
window.electron.ipcRenderer.on('coreRestart', () => { window.electron.ipcRenderer.on('groupsUpdated', () => {
mutate() mutate()
}) })
return (): void => { return (): void => {
window.electron.ipcRenderer.removeAllListeners('coreRestart') window.electron.ipcRenderer.removeAllListeners('groupsUpdated')
} }
}, []) }, [])

View File

@ -16,11 +16,11 @@ export const RulesProvider: React.FC<{ children: ReactNode }> = ({ children }) =
}) })
React.useEffect(() => { React.useEffect(() => {
window.electron.ipcRenderer.on('coreRestart', () => { window.electron.ipcRenderer.on('rulesUpdated', () => {
mutate() mutate()
}) })
return (): void => { return (): void => {
window.electron.ipcRenderer.removeAllListeners('coreRestart') window.electron.ipcRenderer.removeAllListeners('rulesUpdated')
} }
}, []) }, [])

View File

@ -15,12 +15,15 @@ import { useEffect, useMemo, useRef, useState } from 'react'
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'
import { IoIosArrowBack } from 'react-icons/io' import { IoIosArrowBack } from 'react-icons/io'
import { MdOutlineSpeed } from 'react-icons/md' import { MdDoubleArrow, MdOutlineSpeed } from 'react-icons/md'
import { useGroups } from '@renderer/hooks/use-groups' import { useGroups } from '@renderer/hooks/use-groups'
import CollapseInput from '@renderer/components/base/collapse-input' import CollapseInput from '@renderer/components/base/collapse-input'
import { includesIgnoreCase } from '@renderer/utils/includes' import { includesIgnoreCase } from '@renderer/utils/includes'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
const Proxies: React.FC = () => { const Proxies: React.FC = () => {
const { controledMihomoConfig } = useControledMihomoConfig()
const { mode = 'rule' } = controledMihomoConfig || {}
const { groups = [], mutate } = useGroups() const { groups = [], mutate } = useGroups()
const { appConfig, patchAppConfig } = useAppConfig() const { appConfig, patchAppConfig } = useAppConfig()
const { const {
@ -197,184 +200,193 @@ const Proxies: React.FC = () => {
</> </>
} }
> >
<div className="h-[calc(100vh-50px)]"> {mode === 'direct' ? (
<GroupedVirtuoso <div className="h-full w-full flex justify-center items-center">
ref={virtuosoRef} <div className="flex flex-col items-center">
groupCounts={groupCounts} <MdDoubleArrow className="text-foreground-500 text-[100px]" />
groupContent={(index) => { <h2 className="text-foreground-500 text-[20px]"></h2>
if ( </div>
groups[index] && </div>
groups[index].icon && ) : (
groups[index].icon.startsWith('http') && <div className="h-[calc(100vh-50px)]">
!localStorage.getItem(groups[index].icon) <GroupedVirtuoso
) { ref={virtuosoRef}
getImageDataURL(groups[index].icon).then((dataURL) => { groupCounts={groupCounts}
localStorage.setItem(groups[index].icon, dataURL) groupContent={(index) => {
mutate() if (
}) groups[index] &&
} groups[index].icon &&
return groups[index] ? ( groups[index].icon.startsWith('http') &&
<div !localStorage.getItem(groups[index].icon)
className={`w-full pt-2 ${index === groupCounts.length - 1 && !isOpen[index] ? 'pb-2' : ''} px-2`} ) {
> getImageDataURL(groups[index].icon).then((dataURL) => {
<Card localStorage.setItem(groups[index].icon, dataURL)
isPressable mutate()
fullWidth })
onClick={() => { }
setIsOpen((prev) => { return groups[index] ? (
const newOpen = [...prev] <div
newOpen[index] = !prev[index] className={`w-full pt-2 ${index === groupCounts.length - 1 && !isOpen[index] ? 'pb-2' : ''} px-2`}
return newOpen
})
}}
> >
<CardBody className="w-full"> <Card
<div className="flex justify-between"> isPressable
<div className="flex text-ellipsis overflow-hidden whitespace-nowrap"> fullWidth
{groups[index].icon ? ( onClick={() => {
<Avatar setIsOpen((prev) => {
className="bg-transparent mr-2" const newOpen = [...prev]
size="sm" newOpen[index] = !prev[index]
radius="sm" return newOpen
src={ })
groups[index].icon.startsWith('<svg') }}
? `data:image/svg+xml;utf8,${groups[index].icon}` >
: localStorage.getItem(groups[index].icon) || groups[index].icon <CardBody className="w-full">
} <div className="flex justify-between">
/> <div className="flex text-ellipsis overflow-hidden whitespace-nowrap">
) : null} {groups[index].icon ? (
<div className="text-ellipsis overflow-hidden whitespace-nowrap"> <Avatar
<div className="bg-transparent mr-2"
title={groups[index].name} size="sm"
className="inline flag-emoji h-[32px] text-md leading-[32px]" radius="sm"
> src={
{groups[index].name} groups[index].icon.startsWith('<svg')
</div> ? `data:image/svg+xml;utf8,${groups[index].icon}`
{proxyDisplayMode === 'full' && ( : localStorage.getItem(groups[index].icon) || groups[index].icon
}
/>
) : null}
<div className="text-ellipsis overflow-hidden whitespace-nowrap">
<div <div
title={groups[index].type} title={groups[index].name}
className="inline ml-2 text-sm text-default-500" className="inline flag-emoji h-[32px] text-md leading-[32px]"
> >
{groups[index].type} {groups[index].name}
</div> </div>
)} {proxyDisplayMode === 'full' && (
<div
title={groups[index].type}
className="inline ml-2 text-sm text-foreground-500"
>
{groups[index].type}
</div>
)}
{proxyDisplayMode === 'full' && (
<div className="inline flag-emoji ml-2 text-sm text-foreground-500">
{groups[index].now}
</div>
)}
</div>
</div>
<div className="flex">
{proxyDisplayMode === 'full' && ( {proxyDisplayMode === 'full' && (
<div className="inline flag-emoji ml-2 text-sm text-default-500"> <Chip size="sm" className="my-1 mr-2">
{groups[index].now} {groups[index].all.length}
</div> </Chip>
)} )}
<CollapseInput
title="搜索节点"
value={searchValue[index]}
onValueChange={(v) => {
setSearchValue((prev) => {
const newSearchValue = [...prev]
newSearchValue[index] = v
return newSearchValue
})
}}
/>
<Button
title="定位到当前节点"
variant="light"
size="sm"
isIconOnly
onPress={() => {
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'
})
}}
>
<FaLocationCrosshairs className="text-lg text-foreground-500" />
</Button>
<Button
title="延迟测试"
variant="light"
isLoading={delaying[index]}
size="sm"
isIconOnly
onPress={() => {
onGroupDelay(index)
}}
>
<MdOutlineSpeed className="text-lg text-foreground-500" />
</Button>
<IoIosArrowBack
className={`transition duration-200 ml-2 h-[32px] text-lg text-foreground-500 ${isOpen[index] ? '-rotate-90' : ''}`}
/>
</div> </div>
</div> </div>
<div className="flex"> </CardBody>
{proxyDisplayMode === 'full' && ( </Card>
<Chip size="sm" className="my-1 mr-2"> </div>
{groups[index].all.length} ) : (
</Chip> <div>Never See This</div>
)} )
<CollapseInput }}
title="搜索节点" itemContent={(index, groupIndex) => {
value={searchValue[index]} let innerIndex = index
onValueChange={(v) => { groupCounts.slice(0, groupIndex).forEach((count) => {
setSearchValue((prev) => { innerIndex -= count
const newSearchValue = [...prev] })
newSearchValue[index] = v return allProxies[groupIndex] ? (
return newSearchValue <div
}) style={
}} proxyCols !== 'auto'
/> ? { gridTemplateColumns: `repeat(${proxyCols}, minmax(0, 1fr))` }
<Button : {}
title="定位到当前节点" }
variant="light" className={`grid ${proxyCols === 'auto' ? 'sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5' : ''} ${groupIndex === groupCounts.length - 1 && innerIndex === groupCounts[groupIndex] - 1 ? 'pb-2' : ''} gap-2 pt-2 mx-2`}
size="sm" >
isIconOnly {Array.from({ length: cols }).map((_, i) => {
onPress={() => { if (!allProxies[groupIndex][innerIndex * cols + i]) return null
if (!isOpen[index]) { return (
setIsOpen((prev) => { <ProxyItem
const newOpen = [...prev] key={allProxies[groupIndex][innerIndex * cols + i].name}
newOpen[index] = true mutateProxies={mutate}
return newOpen onProxyDelay={onProxyDelay}
}) onSelect={onChangeProxy}
} proxy={allProxies[groupIndex][innerIndex * cols + i]}
let i = 0 group={groups[groupIndex]}
for (let j = 0; j < index; j++) { proxyDisplayMode={proxyDisplayMode}
i += groupCounts[j] selected={
} allProxies[groupIndex][innerIndex * cols + i]?.name ===
i += Math.floor( groups[groupIndex].now
allProxies[index].findIndex( }
(proxy) => proxy.name === groups[index].now />
) / cols )
) })}
virtuosoRef.current?.scrollToIndex({ </div>
index: Math.floor(i), ) : (
align: 'start' <div>Never See This</div>
}) )
}} }}
> />
<FaLocationCrosshairs className="text-lg text-default-500" /> </div>
</Button> )}
<Button
title="延迟测试"
variant="light"
isLoading={delaying[index]}
size="sm"
isIconOnly
onPress={() => {
onGroupDelay(index)
}}
>
<MdOutlineSpeed className="text-lg text-default-500" />
</Button>
<IoIosArrowBack
className={`transition duration-200 ml-2 h-[32px] text-lg text-default-500 ${isOpen[index] ? '-rotate-90' : ''}`}
/>
</div>
</div>
</CardBody>
</Card>
</div>
) : (
<div>Never See This</div>
)
}}
itemContent={(index, groupIndex) => {
let innerIndex = index
groupCounts.slice(0, groupIndex).forEach((count) => {
innerIndex -= count
})
return allProxies[groupIndex] ? (
<div
style={
proxyCols !== 'auto'
? { gridTemplateColumns: `repeat(${proxyCols}, minmax(0, 1fr))` }
: {}
}
className={`grid ${proxyCols === 'auto' ? 'sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5' : ''} ${groupIndex === groupCounts.length - 1 && innerIndex === groupCounts[groupIndex] - 1 ? 'pb-2' : ''} gap-2 pt-2 mx-2`}
>
{Array.from({ length: cols }).map((_, i) => {
if (!allProxies[groupIndex][innerIndex * cols + i]) return null
return (
<ProxyItem
key={allProxies[groupIndex][innerIndex * cols + i].name}
mutateProxies={mutate}
onProxyDelay={onProxyDelay}
onSelect={onChangeProxy}
proxy={allProxies[groupIndex][innerIndex * cols + i]}
group={groups[groupIndex]}
proxyDisplayMode={proxyDisplayMode}
selected={
allProxies[groupIndex][innerIndex * cols + i]?.name ===
groups[groupIndex].now
}
/>
)
})}
</div>
) : (
<div>Never See This</div>
)
}}
/>
</div>
</BasePage> </BasePage>
) )
} }