support custom sidebar

This commit is contained in:
pompurin404 2024-09-06 17:44:26 +08:00
parent 9bd4841b6e
commit 79b3da1da6
No known key found for this signature in database
21 changed files with 433 additions and 205 deletions

View File

@ -2,6 +2,12 @@
- 1.2.x YAML覆写语法有所变动请更新后参考文档进行修改
### New Features
- 支持自定义侧边栏卡片大小
- 支持隐藏侧边栏卡片
### Bug Fixes
- 修复Ubuntu下每次开启Tun都需要密码的问题
- 修复Sub-Store无法读取剪切板的问题

View File

@ -126,14 +126,14 @@ async function migration(): Promise<void> {
'tun',
'profile',
'proxy',
'mihomo',
'connection',
'dns',
'sniff',
'log',
'rule',
'resource',
'override',
'connection',
'mihomo',
'dns',
'sniff',
'log',
'substore'
],
useSubStore = true

View File

@ -18,19 +18,18 @@ export const defaultConfig: IAppConfig = {
controlSniff: true,
nameserverPolicy: {},
siderOrder: [
'mode',
'sysproxy',
'tun',
'profile',
'proxy',
'mihomo',
'connection',
'dns',
'sniff',
'log',
'rule',
'resource',
'override',
'connection',
'mihomo',
'dns',
'sniff',
'log',
'substore'
],
sysProxy: { enable: false, mode: 'manual' }

View File

@ -37,23 +37,20 @@ const App: React.FC = () => {
const { appConfig, patchAppConfig } = useAppConfig()
const {
appTheme = 'system',
controlDns = true,
controlSniff = true,
useSubStore = true,
useWindowFrame = false,
siderOrder = [
'sysproxy',
'tun',
'profile',
'proxy',
'mihomo',
'connection',
'dns',
'sniff',
'log',
'rule',
'resource',
'override',
'connection',
'mihomo',
'dns',
'sniff',
'log',
'substore'
]
} = appConfig || {}
@ -169,9 +166,6 @@ const App: React.FC = () => {
})}
>
{order.map((key: string) => {
if (key === 'dns' && controlDns === false) return null
if (key === 'sniff' && controlSniff === false) return null
if (key === 'substore' && useSubStore === false) return null
return componentMap[key]
})}
</SortableContext>

View File

@ -0,0 +1,78 @@
import React from 'react'
import SettingCard from '../base/base-setting-card'
import SettingItem from '../base/base-setting-item'
import { RadioGroup, Radio } from '@nextui-org/react'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const titleMap = {
sysproxyCardStatus: '系统代理',
tunCardStatus: '虚拟网卡',
profileCardStatus: '订阅管理',
proxyCardStatus: '代理组',
ruleCardStatus: '规则',
resourceCardStatus: '外部资源',
overrideCardStatus: '覆写',
connectionCardStatus: '连接',
mihomoCoreCardStatus: '内核',
dnsCardStatus: 'DNS',
sniffCardStatus: '域名嗅探',
logCardStatus: '日志',
substoreCardStatus: 'Sub-Store'
}
const SiderConfig: React.FC = () => {
const { appConfig, patchAppConfig } = useAppConfig()
const {
sysproxyCardStatus = 'col-span-1',
tunCardStatus = 'col-span-1',
profileCardStatus = 'col-span-2',
proxyCardStatus = 'col-span-1',
ruleCardStatus = 'col-span-1',
resourceCardStatus = 'col-span-1',
overrideCardStatus = 'col-span-1',
connectionCardStatus = 'col-span-2',
mihomoCoreCardStatus = 'col-span-2',
dnsCardStatus = 'col-span-1',
sniffCardStatus = 'col-span-1',
logCardStatus = 'col-span-1',
substoreCardStatus = 'col-span-1'
} = appConfig || {}
const cardStatus = {
sysproxyCardStatus,
tunCardStatus,
profileCardStatus,
proxyCardStatus,
ruleCardStatus,
resourceCardStatus,
overrideCardStatus,
connectionCardStatus,
mihomoCoreCardStatus,
dnsCardStatus,
sniffCardStatus,
logCardStatus,
substoreCardStatus
}
return (
<SettingCard>
{Object.keys(cardStatus).map((key, index, array) => {
return (
<SettingItem title={titleMap[key]} key={key} divider={index !== array.length - 1}>
<RadioGroup
orientation="horizontal"
value={cardStatus[key]}
onValueChange={(v) => {
patchAppConfig({ [key]: v as CardStatus })
}}
>
<Radio value="col-span-2"></Radio>
<Radio value="col-span-1"></Radio>
<Radio value="hidden"></Radio>
</RadioGroup>
</SettingItem>
)
})}
</SettingCard>
)
}
export default SiderConfig

View File

@ -20,7 +20,7 @@ let drawing = false
const ConnCard: React.FC = () => {
const { theme = 'system', systemTheme = 'dark' } = useTheme()
const { appConfig } = useAppConfig()
const { showTraffic } = appConfig || {}
const { showTraffic, connectionCardStatus = 'col-span-2' } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/connections')
@ -172,52 +172,86 @@ const ConnCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
className={connectionCardStatus}
>
<Card
fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/connections')}
>
<CardBody className="pb-0 pt-0 px-0">
<div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<IoLink
color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
/>
</Button>
<div className={`p-2 w-full ${match ? 'text-white' : 'text-foreground'} `}>
<div className="flex justify-between">
<div className="w-full text-right mr-2">{calcTraffic(upload)}/s</div>
<FaCircleArrowUp className="h-[24px] leading-[24px]" />
{connectionCardStatus === 'col-span-2' ? (
<>
<Card
fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/connections')}
>
<CardBody className="pb-1 pt-0 px-0">
<div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<IoLink
color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
/>
</Button>
<div className={`p-2 w-full ${match ? 'text-white' : 'text-foreground'} `}>
<div className="flex justify-between">
<div className="w-full text-right mr-2">{calcTraffic(upload)}/s</div>
<FaCircleArrowUp className="h-[24px] leading-[24px]" />
</div>
<div className="flex justify-between">
<div className="w-full text-right mr-2">{calcTraffic(download)}/s</div>
<FaCircleArrowDown className="h-[24px] leading-[24px]" />
</div>
</div>
</div>
<div className="flex justify-between">
<div className="w-full text-right mr-2">{calcTraffic(download)}/s</div>
<FaCircleArrowDown className="h-[24px] leading-[24px]" />
</div>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</h3>
</CardFooter>
</Card>
<div className="w-full h-full absolute top-0 left-0 pointer-events-none rounded-[14px] overflow-hidden">
<Chart
options={getApexChartOptions()}
series={[{ name: 'Total', data: series }]}
height={'100%'}
width={'100%'}
type="area"
/>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3>
</CardFooter>
</Card>
<div className="w-full h-full absolute top-0 left-0 pointer-events-none rounded-[14px] overflow-hidden">
<Chart
options={getApexChartOptions()}
series={[{ name: 'Total', data: series }]}
height={'100%'}
width={'100%'}
type="area"
/>
</div>
</>
) : (
<Card
fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/logs')}
>
<CardBody className="pb-1 pt-0 px-0">
<div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<IoLink
color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
/>
</Button>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</h3>
</CardFooter>
</Card>
)}
</div>
)
}

View File

@ -6,7 +6,10 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { patchMihomoConfig } from '@renderer/utils/ipc'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const DNSCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { dnsCardStatus = 'col-span-1', controlDns = true } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/dns')
@ -37,7 +40,7 @@ const DNSCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={`${dnsCardStatus} ${!controlDns ? 'hidden' : ''}`}
>
<Card
fullWidth

View File

@ -3,7 +3,10 @@ import { IoJournalOutline } from 'react-icons/io5'
import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const LogCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { logCardStatus = 'col-span-1' } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/logs')
@ -26,11 +29,11 @@ const LogCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={logCardStatus}
>
<Card
fullWidth
className={`col-span-1 ${match ? 'bg-primary' : ''}`}
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/logs')}
>

View File

@ -8,8 +8,12 @@ import { CSS } from '@dnd-kit/utilities'
import { useLocation, useNavigate } from 'react-router-dom'
import PubSub from 'pubsub-js'
import useSWR from 'swr'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { LuCpu } from 'react-icons/lu'
const MihomoCoreCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { mihomoCoreCardStatus = 'col-span-2' } = appConfig || {}
const { data: version, mutate } = useSWR('mihomoVersion', mihomoVersion)
const navigate = useNavigate()
const location = useLocation()
@ -48,55 +52,87 @@ const MihomoCoreCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
className={mihomoCoreCardStatus}
>
<Card
fullWidth
isPressable
onPress={() => navigate('/mihomo')}
className={`${match ? 'bg-primary' : ''}`}
>
<CardBody>
<div
ref={setNodeRef}
{...attributes}
{...listeners}
className="flex justify-between h-[32px]"
>
<h3
className={`text-md font-bold leading-[32px] ${match ? 'text-white' : 'text-foreground'} `}
{mihomoCoreCardStatus === 'col-span-2' ? (
<Card
fullWidth
isPressable
onPress={() => navigate('/mihomo')}
className={`${match ? 'bg-primary' : ''}`}
>
<CardBody>
<div
ref={setNodeRef}
{...attributes}
{...listeners}
className="flex justify-between h-[32px]"
>
{version?.version ?? '-'}
</h3>
<h3
className={`text-md font-bold leading-[32px] ${match ? 'text-white' : 'text-foreground'} `}
>
{version?.version ?? '-'}
</h3>
<Button
isIconOnly
size="sm"
variant="light"
color="default"
onPress={async () => {
try {
await restartCore()
} catch (e) {
alert(e)
} finally {
mutate()
}
}}
<Button
isIconOnly
size="sm"
variant="light"
color="default"
onPress={async () => {
try {
await restartCore()
} catch (e) {
alert(e)
} finally {
mutate()
}
}}
>
<IoMdRefresh
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
/>
</Button>
</div>
</CardBody>
<CardFooter className="pt-1">
<div
className={`flex justify-between w-full text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}
>
<IoMdRefresh className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`} />
</Button>
</div>
</CardBody>
<CardFooter className="pt-1">
<div
className={`flex justify-between w-full text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}
>
<h4></h4>
<h4>{calcTraffic(mem)}</h4>
</div>
</CardFooter>
</Card>
<h4></h4>
<h4>{calcTraffic(mem)}</h4>
</div>
</CardFooter>
</Card>
) : (
<Card
fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/mihomo')}
>
<CardBody className="pb-1 pt-0 px-0">
<div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<LuCpu
color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
/>
</Button>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</h3>
</CardFooter>
</Card>
)}
</div>
)
}

View File

@ -4,8 +4,11 @@ import { MdFormatOverline } from 'react-icons/md'
import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const OverrideCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { overrideCardStatus = 'col-span-1' } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/override')
@ -28,11 +31,11 @@ const OverrideCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={overrideCardStatus}
>
<Card
fullWidth
className={`col-span-1 ${match ? 'bg-primary' : ''}`}
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/override')}
>

View File

@ -11,11 +11,15 @@ import 'dayjs/locale/zh-cn'
import dayjs from 'dayjs'
import { useState } from 'react'
import ConfigViewer from './config-viewer'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { TiFolder } from 'react-icons/ti'
dayjs.extend(relativeTime)
dayjs.locale('zh-cn')
const ProfileCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { profileCardStatus = 'col-span-2' } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/profiles')
@ -52,98 +56,128 @@ const ProfileCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
className={profileCardStatus}
>
{showRuntimeConfig && <ConfigViewer onClose={() => setShowRuntimeConfig(false)} />}
<Card
fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/profiles')}
>
<CardBody className="pb-1">
<div
ref={setNodeRef}
{...attributes}
{...listeners}
className="flex justify-between h-[32px]"
>
<h3
title={info?.name}
className={`text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px] ${match ? 'text-white' : 'text-foreground'} `}
{profileCardStatus === 'col-span-2' ? (
<Card
fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/profiles')}
>
<CardBody className="pb-1">
<div
ref={setNodeRef}
{...attributes}
{...listeners}
className="flex justify-between h-[32px]"
>
{info?.name}
</h3>
<div className="flex">
<Button
isIconOnly
size="sm"
title="查看当前运行时配置"
variant="light"
color="default"
onPress={() => {
setShowRuntimeConfig(true)
}}
<h3
title={info?.name}
className={`text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px] ${match ? 'text-white' : 'text-foreground'} `}
>
<CgLoadbarDoc
className={`text-[24px] ${match ? 'text-white' : 'text-foreground'}`}
/>
</Button>
{info.type === 'remote' && (
{info?.name}
</h3>
<div className="flex">
<Button
isIconOnly
size="sm"
title={dayjs(info.updated).fromNow()}
disabled={updating}
title="查看当前运行时配置"
variant="light"
color="default"
onPress={async () => {
setUpdating(true)
await addProfileItem(info)
setUpdating(false)
onPress={() => {
setShowRuntimeConfig(true)
}}
>
<IoMdRefresh
className={`text-[24px] ${match ? 'text-white' : 'text-foreground'} ${updating ? 'animate-spin' : ''}`}
<CgLoadbarDoc
className={`text-[24px] ${match ? 'text-white' : 'text-foreground'}`}
/>
</Button>
)}
{info.type === 'remote' && (
<Button
isIconOnly
size="sm"
title={dayjs(info.updated).fromNow()}
disabled={updating}
variant="light"
color="default"
onPress={async () => {
setUpdating(true)
await addProfileItem(info)
setUpdating(false)
}}
>
<IoMdRefresh
className={`text-[24px] ${match ? 'text-white' : 'text-foreground'} ${updating ? 'animate-spin' : ''}`}
/>
</Button>
)}
</div>
</div>
</div>
{info.type === 'remote' && extra && (
<div
className={`mt-2 flex justify-between ${match ? 'text-white' : 'text-foreground'} `}
>
<small>{`${calcTraffic(usage)}/${calcTraffic(total)}`}</small>
<small>
{extra.expire ? dayjs.unix(extra.expire).format('YYYY-MM-DD') : '长期有效'}
</small>
</div>
)}
{info.type === 'local' && (
<div
className={`mt-2 flex justify-between ${match ? 'text-white' : 'text-foreground'}`}
>
<Chip
size="sm"
variant="bordered"
className={`${match ? 'text-white border-white' : 'border-primary text-primary'}`}
{info.type === 'remote' && extra && (
<div
className={`mt-2 flex justify-between ${match ? 'text-white' : 'text-foreground'} `}
>
</Chip>
<small>{`${calcTraffic(usage)}/${calcTraffic(total)}`}</small>
<small>
{extra.expire ? dayjs.unix(extra.expire).format('YYYY-MM-DD') : '长期有效'}
</small>
</div>
)}
{info.type === 'local' && (
<div
className={`mt-2 flex justify-between ${match ? 'text-white' : 'text-foreground'}`}
>
<Chip
size="sm"
variant="bordered"
className={`${match ? 'text-white border-white' : 'border-primary text-primary'}`}
>
</Chip>
</div>
)}
</CardBody>
<CardFooter className="pt-0">
{extra && (
<Progress
className="w-full"
classNames={{ indicator: match ? 'bg-white' : 'bg-foreground' }}
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
/>
)}
</CardFooter>
</Card>
) : (
<Card
fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/profiles')}
>
<CardBody className="pb-1 pt-0 px-0">
<div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<TiFolder
color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
/>
</Button>
</div>
)}
</CardBody>
<CardFooter className="pt-0">
{extra && (
<Progress
className="w-full"
classNames={{ indicator: match ? 'bg-white' : 'bg-foreground' }}
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
/>
)}
</CardFooter>
</Card>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</h3>
</CardFooter>
</Card>
)}
</div>
)
}

View File

@ -4,8 +4,11 @@ import { CSS } from '@dnd-kit/utilities'
import { LuGroup } from 'react-icons/lu'
import { useLocation, useNavigate } from 'react-router-dom'
import { useGroups } from '@renderer/hooks/use-groups'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const ProxyCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { proxyCardStatus = 'col-span-1' } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/proxies')
@ -30,7 +33,7 @@ const ProxyCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
className={proxyCardStatus}
>
<Card
fullWidth

View File

@ -4,7 +4,10 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { IoLayersOutline } from 'react-icons/io5'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const ResourceCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { resourceCardStatus = 'col-span-1' } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/resources')
@ -27,11 +30,11 @@ const ResourceCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={resourceCardStatus}
>
<Card
fullWidth
className={`col-span-1 ${match ? 'bg-primary' : ''}`}
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/resources')}
>

View File

@ -4,8 +4,11 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useRules } from '@renderer/hooks/use-rules'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const RuleCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { ruleCardStatus = 'col-span-1' } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/rules')
@ -29,11 +32,11 @@ const RuleCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={ruleCardStatus}
>
<Card
fullWidth
className={`col-span-1 ${match ? 'bg-primary' : ''}`}
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/rules')}
>

View File

@ -6,8 +6,11 @@ import { patchMihomoConfig } from '@renderer/utils/ipc'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const SniffCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { sniffCardStatus = 'col-span-1', controlSniff = true } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/sniffer')
@ -38,11 +41,11 @@ const SniffCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''}`}
>
<Card
fullWidth
className={`col-span-1 ${match ? 'bg-primary' : ''}`}
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/sniffer')}
>

View File

@ -3,7 +3,10 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import SubStoreIcon from '../base/substore-icon'
import { useAppConfig } from '@renderer/hooks/use-app-config'
const SubStoreCard: React.FC = () => {
const { appConfig } = useAppConfig()
const { substoreCardStatus = 'col-span-1', useSubStore = true } = appConfig || {}
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/substore')
@ -26,11 +29,11 @@ const SubStoreCard: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={`${substoreCardStatus} ${!useSubStore ? 'hidden' : ''}`}
>
<Card
fullWidth
className={`col-span-1 ${match ? 'bg-primary' : ''}`}
className={`${match ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/substore')}
>

View File

@ -13,7 +13,7 @@ const SysproxySwitcher: React.FC = () => {
const location = useLocation()
const match = location.pathname.includes('/sysproxy')
const { appConfig, patchAppConfig } = useAppConfig()
const { sysProxy } = appConfig || {}
const { sysProxy, sysproxyCardStatus = 'col-span-1' } = appConfig || {}
const { enable } = sysProxy || {}
const {
attributes,
@ -44,7 +44,7 @@ const SysproxySwitcher: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1 "
className={sysproxyCardStatus}
>
<Card
fullWidth

View File

@ -17,6 +17,7 @@ const TunSwitcher: React.FC = () => {
const match = location.pathname.includes('/tun') || false
const [openPasswordModal, setOpenPasswordModal] = useState(false)
const { appConfig, patchAppConfig } = useAppConfig()
const { tunCardStatus = 'col-span-1' } = appConfig || {}
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { tun } = controledMihomoConfig || {}
const { enable } = tun || {}
@ -62,7 +63,7 @@ const TunSwitcher: React.FC = () => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
className={tunCardStatus}
>
{openPasswordModal && (
<BasePasswordModal

View File

@ -96,7 +96,13 @@ const Connections: React.FC = () => {
{calcTraffic(connectionsInfo?.downloadTotal ?? 0)}{' '}
</span>
</div>
<Badge color="primary" variant="flat" content={`${filteredConnections.length}`}>
<Badge
className="mt-2"
color="primary"
variant="flat"
showOutline={false}
content={`${filteredConnections.length}`}
>
<Button
className="app-nodrag ml-1"
title="关闭全部连接"

View File

@ -8,6 +8,7 @@ import MihomoConfig from '@renderer/components/settings/mihomo-config'
import Actions from '@renderer/components/settings/actions'
import ShortcutConfig from '@renderer/components/settings/shortcut-config'
import { FaTelegramPlane } from 'react-icons/fa'
import SiderConfig from '@renderer/components/settings/sider-config'
const Settings: React.FC = () => {
return (
@ -55,6 +56,7 @@ const Settings: React.FC = () => {
}
>
<GeneralConfig />
<SiderConfig />
<WebdavConfig />
<MihomoConfig />
<ShortcutConfig />

14
src/shared/types.d.ts vendored
View File

@ -1,6 +1,7 @@
type OutboundMode = 'rule' | 'global' | 'direct'
type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent'
type SysProxyMode = 'auto' | 'manual'
type CardStatus = 'col-span-2' | 'col-span-1' | 'hidden'
type AppTheme =
| 'system'
| 'light'
@ -219,6 +220,19 @@ interface IAppConfig {
proxyCols: 'auto' | '1' | '2' | '3' | '4'
connectionDirection: 'asc' | 'desc'
connectionOrderBy: 'time' | 'upload' | 'download' | 'uploadSpeed' | 'downloadSpeed'
connectionCardStatus?: CardStatus
dnsCardStatus?: CardStatus
logCardStatus?: CardStatus
mihomoCoreCardStatus?: CardStatus
overrideCardStatus?: CardStatus
profileCardStatus?: CardStatus
proxyCardStatus?: CardStatus
resourceCardStatus?: CardStatus
ruleCardStatus?: CardStatus
sniffCardStatus?: CardStatus
substoreCardStatus?: CardStatus
sysproxyCardStatus?: CardStatus
tunCardStatus?: CardStatus
useSubStore: boolean
useCustomSubStore?: boolean
customSubStoreUrl?: string