support for sorting sidebar items

This commit is contained in:
pompurin404 2024-08-11 14:53:40 +08:00
parent a8e886b9bb
commit 5c7f3f48f7
No known key found for this signature in database
18 changed files with 662 additions and 359 deletions

View File

@ -27,6 +27,9 @@
"yaml": "^2.5.0" "yaml": "^2.5.0"
}, },
"devDependencies": { "devDependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@electron-toolkit/eslint-config-prettier": "^2.0.0", "@electron-toolkit/eslint-config-prettier": "^2.0.0",
"@electron-toolkit/eslint-config-ts": "^2.0.0", "@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1", "@electron-toolkit/tsconfig": "^1.0.1",

View File

@ -27,6 +27,15 @@ importers:
specifier: ^2.5.0 specifier: ^2.5.0
version: 2.5.0 version: 2.5.0
devDependencies: devDependencies:
'@dnd-kit/core':
specifier: ^6.1.0
version: 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@dnd-kit/sortable':
specifier: ^8.0.0
version: 8.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@dnd-kit/utilities':
specifier: ^3.2.2
version: 3.2.2(react@18.3.1)
'@electron-toolkit/eslint-config-prettier': '@electron-toolkit/eslint-config-prettier':
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0(eslint@8.57.0)(prettier@3.3.3) version: 2.0.0(eslint@8.57.0)(prettier@3.3.3)
@ -262,6 +271,28 @@ packages:
resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
engines: {node: '>= 8.9.0'} engines: {node: '>= 8.9.0'}
'@dnd-kit/accessibility@3.1.0':
resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==}
peerDependencies:
react: '>=16.8.0'
'@dnd-kit/core@6.1.0':
resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@dnd-kit/sortable@8.0.0':
resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==}
peerDependencies:
'@dnd-kit/core': ^6.1.0
react: '>=16.8.0'
'@dnd-kit/utilities@3.2.2':
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
peerDependencies:
react: '>=16.8.0'
'@electron-toolkit/eslint-config-prettier@2.0.0': '@electron-toolkit/eslint-config-prettier@2.0.0':
resolution: {integrity: sha512-L+uG1FvJcAZkPZpSi6B1pmdpyJFyOxWDTjr1Vs47vSryxv/EX1Ch6o4HVsachlDq3fMEkDgojuP2F3ZvVZMoLw==} resolution: {integrity: sha512-L+uG1FvJcAZkPZpSi6B1pmdpyJFyOxWDTjr1Vs47vSryxv/EX1Ch6o4HVsachlDq3fMEkDgojuP2F3ZvVZMoLw==}
peerDependencies: peerDependencies:
@ -4545,6 +4576,31 @@ snapshots:
ajv: 6.12.6 ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6) ajv-keywords: 3.5.2(ajv@6.12.6)
'@dnd-kit/accessibility@3.1.0(react@18.3.1)':
dependencies:
react: 18.3.1
tslib: 2.6.3
'@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@dnd-kit/accessibility': 3.1.0(react@18.3.1)
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.6.3
'@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
dependencies:
'@dnd-kit/core': 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
react: 18.3.1
tslib: 2.6.3
'@dnd-kit/utilities@3.2.2(react@18.3.1)':
dependencies:
react: 18.3.1
tslib: 2.6.3
'@electron-toolkit/eslint-config-prettier@2.0.0(eslint@8.57.0)(prettier@3.3.3)': '@electron-toolkit/eslint-config-prettier@2.0.0(eslint@8.57.0)(prettier@3.3.3)':
dependencies: dependencies:
eslint: 8.57.0 eslint: 8.57.0

View File

@ -8,6 +8,21 @@ export const defaultConfig: IAppConfig = {
autoCloseConnection: true, autoCloseConnection: true,
useNameserverPolicy: false, useNameserverPolicy: false,
nameserverPolicy: {}, nameserverPolicy: {},
siderOrder: [
'mode',
'sysproxy',
'tun',
'profile',
'proxy',
'mihomo',
'connection',
'dns',
'sniff',
'log',
'rule',
'resource',
'override'
],
sysProxy: { enable: false, mode: 'manual' } sysProxy: { enable: false, mode: 'manual' }
} }

View File

@ -1,5 +1,5 @@
import { useTheme } from 'next-themes' import { useTheme } from 'next-themes'
import { useEffect } from 'react' import { useEffect, useState } from 'react'
import { useLocation, useNavigate, useRoutes } from 'react-router-dom' import { useLocation, useNavigate, useRoutes } from 'react-router-dom'
import OutboundModeSwitcher from '@renderer/components/sider/outbound-mode-switcher' import OutboundModeSwitcher from '@renderer/components/sider/outbound-mode-switcher'
import SysproxySwitcher from '@renderer/components/sider/sysproxy-switcher' import SysproxySwitcher from '@renderer/components/sider/sysproxy-switcher'
@ -7,6 +7,15 @@ import TunSwitcher from '@renderer/components/sider/tun-switcher'
import { Button, Divider } from '@nextui-org/react' import { Button, Divider } from '@nextui-org/react'
import { IoSettings } from 'react-icons/io5' import { IoSettings } from 'react-icons/io5'
import routes from '@renderer/routes' import routes from '@renderer/routes'
import {
DndContext,
closestCenter,
PointerSensor,
useSensor,
useSensors,
DragEndEvent
} from '@dnd-kit/core'
import { SortableContext } from '@dnd-kit/sortable'
import ProfileCard from '@renderer/components/sider/profile-card' import ProfileCard from '@renderer/components/sider/profile-card'
import ProxyCard from '@renderer/components/sider/proxy-card' import ProxyCard from '@renderer/components/sider/proxy-card'
import RuleCard from '@renderer/components/sider/rule-card' import RuleCard from '@renderer/components/sider/rule-card'
@ -21,8 +30,31 @@ import UpdaterButton from '@renderer/components/updater/updater-button'
import { useAppConfig } from './hooks/use-app-config' import { useAppConfig } from './hooks/use-app-config'
const App: React.FC = () => { const App: React.FC = () => {
const { appConfig } = useAppConfig() const { appConfig, patchAppConfig } = useAppConfig()
const { appTheme = 'system' } = appConfig || {} const {
appTheme = 'system',
siderOrder = [
'sysproxy',
'tun',
'profile',
'proxy',
'mihomo',
'connection',
'dns',
'sniff',
'log',
'rule',
'resource',
'override'
]
} = appConfig || {}
const [order, setOrder] = useState(siderOrder)
const sensors = useSensors(
useSensor(PointerSensor)
// useSensor(KeyboardSensor, {
// coordinateGetter: sortableKeyboardCoordinates
// })
)
const { setTheme } = useTheme() const { setTheme } = useTheme()
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
@ -32,6 +64,36 @@ const App: React.FC = () => {
setTheme(appTheme) setTheme(appTheme)
}, [appTheme]) }, [appTheme])
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
const { active, over } = event
if (over) {
if (active.id !== over.id) {
const newOrder = order.slice()
const activeIndex = newOrder.indexOf(active.id as string)
const overIndex = newOrder.indexOf(over.id as string)
newOrder.splice(activeIndex, 1)
newOrder.splice(overIndex, 0, active.id as string)
setOrder(newOrder)
await patchAppConfig({ siderOrder: newOrder })
}
}
}
const componentMap = {
sysproxy: <SysproxySwitcher />,
tun: <TunSwitcher />,
profile: <ProfileCard />,
proxy: <ProxyCard />,
mihomo: <MihomoCoreCard />,
connection: <ConnCard />,
dns: <DNSCard />,
sniff: <SniffCard />,
log: <LogCard />,
rule: <RuleCard />,
resource: <ResourceCard />,
override: <OverrideCard />
}
return ( return (
<div className="w-full h-[100vh] flex"> <div className="w-full h-[100vh] flex">
<div className="side w-[250px] h-full overflow-y-auto no-scrollbar"> <div className="side w-[250px] h-full overflow-y-auto no-scrollbar">
@ -51,44 +113,22 @@ const App: React.FC = () => {
/> />
</div> </div>
</div> </div>
<div className="grid grid-cols-2 gap-2 mx-2"> <div className="mt-2 mx-2">
<OutboundModeSwitcher /> <OutboundModeSwitcher />
<SysproxySwitcher />
<TunSwitcher />
<ProfileCard />
<ProxyCard />
<MihomoCoreCard />
<ConnCard />
<DNSCard />
<SniffCard />
<LogCard />
<RuleCard />
<ResourceCard />
<OverrideCard />
</div> </div>
{/* <div className="flex justify-between mx-2 mb-2"> <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
<SysproxySwitcher /> <div className="grid grid-cols-2 gap-2 m-2">
<TunSwitcher /> <SortableContext
</div> items={order.map((x) => {
<div className="mx-2"> return x
<ProfileCard /> })}
<ProxyCard /> >
<MihomoCoreCard /> {order.map((key: string) => {
<ConnCard /> return componentMap[key]
</div> })}
</SortableContext>
<div className="flex justify-between mx-2"> </div>
<DNSCard /> </DndContext>
<SniffCard />
</div>
<div className="flex justify-between mx-2">
<LogCard />
<RuleCard />
</div>
<div className="flex justify-between mx-2">
<ResourceCard />
<OverrideCard />
</div> */}
</div> </div>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<div className="main w-[calc(100%-251px)] h-full overflow-y-auto">{page}</div> <div className="main w-[calc(100%-251px)] h-full overflow-y-auto">{page}</div>

View File

@ -3,6 +3,8 @@ import { FaCircleArrowDown, FaCircleArrowUp } from 'react-icons/fa6'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { calcTraffic } from '@renderer/utils/calc' import { calcTraffic } from '@renderer/utils/calc'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { IoLink } from 'react-icons/io5' import { IoLink } from 'react-icons/io5'
const ConnCard: React.FC = () => { const ConnCard: React.FC = () => {
@ -12,6 +14,9 @@ const ConnCard: React.FC = () => {
const [upload, setUpload] = useState(0) const [upload, setUpload] = useState(0)
const [download, setDownload] = useState(0) const [download, setDownload] = useState(0)
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'connection'
})
useEffect(() => { useEffect(() => {
window.electron.ipcRenderer.on('mihomoTraffic', (_e, info: IMihomoTrafficInfo) => { window.electron.ipcRenderer.on('mihomoTraffic', (_e, info: IMihomoTrafficInfo) => {
@ -24,41 +29,51 @@ const ConnCard: React.FC = () => {
}, []) }, [])
return ( return (
<Card <div
fullWidth style={{
className={`col-span-2 ${match ? 'bg-primary' : ''}`} position: 'relative',
isPressable transform: CSS.Transform.toString(transform),
onPress={() => navigate('/connections')} transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/connections')}
variant="flat" >
color="default" <CardBody className="pb-0 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<IoLink <Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default" color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`} >
/> <IoLink
</Button> color="default"
<div className={`p-2 w-full ${match ? 'text-white' : 'text-foreground'} `}> className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
<div className="flex justify-between"> />
<div className="w-full text-right mr-2">{calcTraffic(upload)}/s</div> </Button>
<FaCircleArrowUp className="h-[24px] leading-[24px]" /> <div className={`p-2 w-full ${match ? 'text-white' : 'text-foreground'} `}>
</div> <div className="flex justify-between">
<div className="flex justify-between"> <div className="w-full text-right mr-2">{calcTraffic(upload)}/s</div>
<div className="w-full text-right mr-2">{calcTraffic(download)}/s</div> <FaCircleArrowUp className="h-[24px] leading-[24px]" />
<FaCircleArrowDown 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> </div>
</div> </CardBody>
</CardBody> <CardFooter className="pt-1">
<CardFooter className="pt-1"> <h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3>
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3> </CardFooter>
</CardFooter> </Card>
</Card> </div>
) )
} }

View File

@ -4,7 +4,8 @@ import BorderSwitch from '@renderer/components/base/border-swtich'
import { LuServer } from 'react-icons/lu' import { LuServer } from 'react-icons/lu'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { patchMihomoConfig } from '@renderer/utils/ipc' import { patchMihomoConfig } from '@renderer/utils/ipc'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const DNSCard: React.FC = () => { const DNSCard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
@ -12,42 +13,55 @@ const DNSCard: React.FC = () => {
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true) const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true)
const { dns, tun } = controledMihomoConfig || {} const { dns, tun } = controledMihomoConfig || {}
const { enable } = dns || {} const { enable } = dns || {}
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'dns'
})
const onChange = async (enable: boolean): Promise<void> => { const onChange = async (enable: boolean): Promise<void> => {
await patchControledMihomoConfig({ dns: { enable } }) await patchControledMihomoConfig({ dns: { enable } })
await patchMihomoConfig({ dns: { enable } }) await patchMihomoConfig({ dns: { enable } })
} }
return ( return (
<Card <div
className={`col-span-1 ${match ? 'bg-primary' : ''}`} style={{
isPressable position: 'relative',
onPress={() => navigate('/dns')} transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/dns')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<LuServer <Button
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`} isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<LuServer
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
/>
</Button>
<BorderSwitch
isShowBorder={match && enable}
isSelected={enable}
isDisabled={tun?.enable}
onValueChange={onChange}
/> />
</Button> </div>
<BorderSwitch </CardBody>
isShowBorder={match && enable} <CardFooter className="pt-1">
isSelected={enable} <h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>DNS</h3>
isDisabled={tun?.enable} </CardFooter>
onValueChange={onChange} </Card>
/> </div>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>DNS</h3>
</CardFooter>
</Card>
) )
} }

View File

@ -1,36 +1,51 @@
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react' import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
import { IoJournal } from 'react-icons/io5' import { IoJournal } from 'react-icons/io5'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const LogCard: React.FC = () => { const LogCard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const match = location.pathname.includes('/logs') const match = location.pathname.includes('/logs')
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'log'
})
return ( return (
<Card <div
className={`col-span-1 ${match ? 'bg-primary' : ''}`} style={{
isPressable position: 'relative',
onPress={() => navigate('/logs')} transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`col-span-1 ${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/logs')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<IoJournal <Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default" color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`} >
/> <IoJournal
</Button> color="default"
</div> className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
</CardBody> />
<CardFooter className="pt-1"> </Button>
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3> </div>
</CardFooter> </CardBody>
</Card> <CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3>
</CardFooter>
</Card>
</div>
) )
} }

View File

@ -3,6 +3,8 @@ import { calcTraffic } from '@renderer/utils/calc'
import { mihomoVersion, restartCore } from '@renderer/utils/ipc' import { mihomoVersion, restartCore } from '@renderer/utils/ipc'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { IoMdRefresh } from 'react-icons/io' import { IoMdRefresh } from 'react-icons/io'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import useSWR from 'swr' import useSWR from 'swr'
@ -11,6 +13,9 @@ const MihomoCoreCard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const match = location.pathname.includes('/mihomo') const match = location.pathname.includes('/mihomo')
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'mihomo'
})
const [mem, setMem] = useState(0) const [mem, setMem] = useState(0)
@ -31,46 +36,61 @@ const MihomoCoreCard: React.FC = () => {
}, []) }, [])
return ( return (
<Card <div
fullWidth style={{
isPressable position: 'relative',
onPress={() => navigate('/mihomo')} transform: CSS.Transform.toString(transform),
className={`col-span-2 ${match ? 'bg-primary' : ''}`} transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
> >
<CardBody> <Card
<div className="flex justify-between h-[32px]"> fullWidth
<h3 isPressable
className={`text-md font-bold leading-[32px] ${match ? 'text-white' : 'text-foreground'} `} 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 <Button
isIconOnly isIconOnly
size="sm" size="sm"
variant="light" variant="light"
color="default" color="default"
onPress={async () => { onPress={async () => {
await restartCore() await restartCore()
mutate()
setTimeout(() => {
mutate() mutate()
}, 2000) setTimeout(() => {
}} mutate()
}, 2000)
}}
>
<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]`} /> <h4></h4>
</Button> <h4>{calcTraffic(mem)}</h4>
</div> </div>
</CardBody> </CardFooter>
<CardFooter className="pt-1"> </Card>
<div </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>
) )
} }

View File

@ -22,7 +22,6 @@ const OutboundModeSwitcher: React.FC = () => {
<Tabs <Tabs
fullWidth fullWidth
color="primary" color="primary"
className="col-span-2"
selectedKey={mode} selectedKey={mode}
onSelectionChange={(key: Key) => onChangeMode(key as OutboundMode)} onSelectionChange={(key: Key) => onChangeMode(key as OutboundMode)}
> >

View File

@ -3,43 +3,58 @@ import BorderSwitch from '@renderer/components/base/border-swtich'
import React, { useState } from 'react' import React, { useState } from 'react'
import { MdFormatOverline } from 'react-icons/md' import { MdFormatOverline } from 'react-icons/md'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const OverrideCard: React.FC = () => { const OverrideCard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const match = location.pathname.includes('/override') const match = location.pathname.includes('/override')
const [enable, setEnable] = useState(false) const [enable, setEnable] = useState(false)
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'override'
})
return ( return (
<Card <div
className={`col-span-1 ${match ? 'bg-primary' : ''}`} style={{
isPressable position: 'relative',
onPress={() => navigate('/override')} transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`col-span-1 ${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/override')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<MdFormatOverline <Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default" color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`} >
<MdFormatOverline
color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
/>
</Button>
<BorderSwitch
isShowBorder={match && enable}
isSelected={enable}
onValueChange={setEnable}
/> />
</Button> </div>
<BorderSwitch </CardBody>
isShowBorder={match && enable} <CardFooter className="pt-1">
isSelected={enable} <h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3>
onValueChange={setEnable} </CardFooter>
/> </Card>
</div> </div>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3>
</CardFooter>
</Card>
) )
} }

View File

@ -5,6 +5,8 @@ import { calcTraffic, calcPercent } from '@renderer/utils/calc'
import { CgLoadbarDoc } from 'react-icons/cg' import { CgLoadbarDoc } from 'react-icons/cg'
import { IoMdRefresh } from 'react-icons/io' import { IoMdRefresh } from 'react-icons/io'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-cn'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useState } from 'react' import { useState } from 'react'
@ -21,6 +23,9 @@ const ProfileCard: React.FC = () => {
const [showRuntimeConfig, setShowRuntimeConfig] = useState(false) const [showRuntimeConfig, setShowRuntimeConfig] = useState(false)
const { profileConfig, addProfileItem } = useProfileConfig() const { profileConfig, addProfileItem } = useProfileConfig()
const { current, items } = profileConfig ?? {} const { current, items } = profileConfig ?? {}
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'profile'
})
const info = items?.find((item) => item.id === current) ?? { const info = items?.find((item) => item.id === current) ?? {
id: 'default', id: 'default',
type: 'local', type: 'local',
@ -32,16 +37,29 @@ const ProfileCard: React.FC = () => {
const total = extra?.total ?? 0 const total = extra?.total ?? 0
return ( return (
<> <div
style={{
position: 'relative',
transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
>
{showRuntimeConfig && <ConfigViewer onClose={() => setShowRuntimeConfig(false)} />} {showRuntimeConfig && <ConfigViewer onClose={() => setShowRuntimeConfig(false)} />}
<Card <Card
fullWidth fullWidth
className={`col-span-2 ${match ? 'bg-primary' : ''}`} className={`${match ? 'bg-primary' : ''}`}
isPressable isPressable
onPress={() => navigate('/profiles')} onPress={() => navigate('/profiles')}
> >
<CardBody className="pb-1"> <CardBody className="pb-1">
<div className="flex justify-between h-[32px]"> <div
ref={setNodeRef}
{...attributes}
{...listeners}
className="flex justify-between h-[32px]"
>
<h3 <h3
className={`text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px] ${match ? 'text-white' : 'text-foreground'} `} className={`text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px] ${match ? 'text-white' : 'text-foreground'} `}
> >
@ -115,7 +133,7 @@ const ProfileCard: React.FC = () => {
)} )}
</CardFooter> </CardFooter>
</Card> </Card>
</> </div>
) )
} }

View File

@ -1,6 +1,8 @@
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react' import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
import { mihomoProxies } from '@renderer/utils/ipc' import { mihomoProxies } from '@renderer/utils/ipc'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { LuGroup } from 'react-icons/lu' import { LuGroup } from 'react-icons/lu'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import useSWR from 'swr' import useSWR from 'swr'
@ -10,54 +12,68 @@ const ProxyCard: React.FC = () => {
const location = useLocation() const location = useLocation()
const match = location.pathname.includes('/proxies') const match = location.pathname.includes('/proxies')
const { data: proxies = { proxies: {} } } = useSWR('mihomoProxies', mihomoProxies) const { data: proxies = { proxies: {} } } = useSWR('mihomoProxies', mihomoProxies)
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'proxy'
})
const filtered = useMemo(() => { const filtered = useMemo(() => {
return Object.keys(proxies.proxies).filter((key) => 'all' in proxies.proxies[key]) return Object.keys(proxies.proxies).filter((key) => 'all' in proxies.proxies[key])
}, [proxies]) }, [proxies])
return ( return (
<Card <div
fullWidth style={{
className={`col-span-2 ${match ? 'bg-primary' : ''}`} position: 'relative',
isPressable transform: CSS.Transform.toString(transform),
onPress={() => navigate('/proxies')} transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-2"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/proxies')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<LuGroup <Button
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`} isIconOnly
/> className="bg-transparent pointer-events-none"
</Button> variant="flat"
<Chip color="default"
classNames={ >
match <LuGroup
? { className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
base: 'border-white', />
content: 'text-white' </Button>
} <Chip
: { classNames={
base: 'border-primary', match
content: 'text-primary' ? {
} base: 'border-white',
} content: 'text-white'
size="sm" }
variant="bordered" : {
className="mr-3 mt-2" base: 'border-primary',
> content: 'text-primary'
{filtered.length} }
</Chip> }
</div> size="sm"
</CardBody> variant="bordered"
<CardFooter className="pt-1"> className="mr-3 mt-2"
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3> >
</CardFooter> {filtered.length}
</Card> </Chip>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</h3>
</CardFooter>
</Card>
</div>
) )
} }

View File

@ -2,39 +2,53 @@ import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
import React from 'react' import React from 'react'
import { FaLayerGroup } from 'react-icons/fa6' import { FaLayerGroup } from 'react-icons/fa6'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const ResourceCard: React.FC = () => { const ResourceCard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const match = location.pathname.includes('/resources') const match = location.pathname.includes('/resources')
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'resource'
})
return ( return (
<Card <div
className={`col-span-1 ${match ? 'bg-primary' : ''}`} style={{
isPressable position: 'relative',
onPress={() => navigate('/resources')} transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`col-span-1 ${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/resources')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<FaLayerGroup <Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default" color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`} >
/> <FaLayerGroup
</Button> color="default"
</div> className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
</CardBody> />
<CardFooter className="pt-1"> </Button>
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}> </div>
</CardBody>
</h3> <CardFooter className="pt-1">
</CardFooter> <h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</Card>
</h3>
</CardFooter>
</Card>
</div>
) )
} }

View File

@ -2,6 +2,8 @@ import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
import { mihomoRules } from '@renderer/utils/ipc' import { mihomoRules } from '@renderer/utils/ipc'
import { MdOutlineAltRoute } from 'react-icons/md' import { MdOutlineAltRoute } from 'react-icons/md'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import useSWR from 'swr' import useSWR from 'swr'
const RuleCard: React.FC = () => { const RuleCard: React.FC = () => {
@ -11,50 +13,64 @@ const RuleCard: React.FC = () => {
const { data: rules } = useSWR<IMihomoRulesInfo>('mihomoRules', mihomoRules, { const { data: rules } = useSWR<IMihomoRulesInfo>('mihomoRules', mihomoRules, {
refreshInterval: 5000 refreshInterval: 5000
}) })
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'rule'
})
return ( return (
<Card <div
className={`col-span-1 ${match ? 'bg-primary' : ''}`} style={{
isPressable position: 'relative',
onPress={() => navigate('/rules')} transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`col-span-1 ${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/rules')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<MdOutlineAltRoute <Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default" color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`} >
/> <MdOutlineAltRoute
</Button> color="default"
<Chip className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
classNames={ />
match </Button>
? { <Chip
base: 'border-white', classNames={
content: 'text-white' match
} ? {
: { base: 'border-white',
base: 'border-primary', content: 'text-white'
content: 'text-primary' }
} : {
} base: 'border-primary',
size="sm" content: 'text-primary'
variant="bordered" }
className="mr-3 mt-2" }
> size="sm"
{rules?.rules?.length ?? 0} variant="bordered"
</Chip> className="mr-3 mt-2"
</div> >
</CardBody> {rules?.rules?.length ?? 0}
<CardFooter className="pt-1"> </Chip>
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3> </div>
</CardFooter> </CardBody>
</Card> <CardFooter className="pt-1">
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}></h3>
</CardFooter>
</Card>
</div>
) )
} }

View File

@ -4,6 +4,8 @@ import { RiScan2Fill } from 'react-icons/ri'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { patchMihomoConfig } from '@renderer/utils/ipc' import { patchMihomoConfig } from '@renderer/utils/ipc'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const SniffCard: React.FC = () => { const SniffCard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
@ -12,6 +14,9 @@ const SniffCard: React.FC = () => {
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true) const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true)
const { sniffer } = controledMihomoConfig || {} const { sniffer } = controledMihomoConfig || {}
const { enable } = sniffer || {} const { enable } = sniffer || {}
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'sniff'
})
const onChange = async (enable: boolean): Promise<void> => { const onChange = async (enable: boolean): Promise<void> => {
await patchControledMihomoConfig({ sniffer: { enable } }) await patchControledMihomoConfig({ sniffer: { enable } })
@ -19,37 +24,48 @@ const SniffCard: React.FC = () => {
} }
return ( return (
<Card <div
className={`col-span-1 ${match ? 'bg-primary' : ''}`} style={{
isPressable position: 'relative',
onPress={() => navigate('/sniffer')} transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`col-span-1 ${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/sniffer')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<RiScan2Fill <Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default" color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`} >
<RiScan2Fill
color="default"
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
/>
</Button>
<BorderSwitch
isShowBorder={match && enable}
isSelected={enable}
onValueChange={onChange}
/> />
</Button> </div>
<BorderSwitch </CardBody>
isShowBorder={match && enable} <CardFooter className="pt-1">
isSelected={enable} <h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
onValueChange={onChange}
/> </h3>
</div> </CardFooter>
</CardBody> </Card>
<CardFooter className="pt-1"> </div>
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</h3>
</CardFooter>
</Card>
) )
} }

View File

@ -5,6 +5,8 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
import { triggerSysProxy } from '@renderer/utils/ipc' import { triggerSysProxy } from '@renderer/utils/ipc'
import { AiOutlineGlobal } from 'react-icons/ai' import { AiOutlineGlobal } from 'react-icons/ai'
import React from 'react' import React from 'react'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const SysproxySwitcher: React.FC = () => { const SysproxySwitcher: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
@ -13,7 +15,9 @@ const SysproxySwitcher: React.FC = () => {
const { appConfig, patchAppConfig } = useAppConfig(true) const { appConfig, patchAppConfig } = useAppConfig(true)
const { sysProxy } = appConfig || {} const { sysProxy } = appConfig || {}
const { enable } = sysProxy || {} const { enable } = sysProxy || {}
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'sysproxy'
})
const onChange = async (enable: boolean): Promise<void> => { const onChange = async (enable: boolean): Promise<void> => {
try { try {
await triggerSysProxy(enable) await triggerSysProxy(enable)
@ -24,36 +28,47 @@ const SysproxySwitcher: React.FC = () => {
} }
return ( return (
<Card <div
className={`col-span-1 ${match ? 'bg-primary' : ''}`} style={{
isPressable position: 'relative',
onPress={() => navigate('/sysproxy')} transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1 "
> >
<CardBody className="pb-1 pt-0 px-0"> <Card
<div className="flex justify-between"> fullWidth
<Button className={`${match ? 'bg-primary' : ''}`}
isIconOnly isPressable
className="bg-transparent pointer-events-none" onPress={() => navigate('/sysproxy')}
variant="flat" >
color="default" <CardBody className="pb-1 pt-0 px-0">
> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<AiOutlineGlobal <Button
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`} isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<AiOutlineGlobal
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
/>
</Button>
<BorderSwitch
isShowBorder={match && enable}
isSelected={enable}
onValueChange={onChange}
/> />
</Button> </div>
<BorderSwitch </CardBody>
isShowBorder={match && enable} <CardFooter className="pt-1">
isSelected={enable} <h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
onValueChange={onChange}
/> </h3>
</div> </CardFooter>
</CardBody> </Card>
<CardFooter className="pt-1"> </div>
<h3 className={`text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
</h3>
</CardFooter>
</Card>
) )
} }

View File

@ -4,6 +4,8 @@ import BorderSwitch from '@renderer/components/base/border-swtich'
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb' import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { encryptString, patchMihomoConfig, isEncryptionAvailable } from '@renderer/utils/ipc' import { encryptString, patchMihomoConfig, isEncryptionAvailable } from '@renderer/utils/ipc'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { platform } from '@renderer/utils/init' import { platform } from '@renderer/utils/init'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useAppConfig } from '@renderer/hooks/use-app-config' import { useAppConfig } from '@renderer/hooks/use-app-config'
@ -12,12 +14,15 @@ import BasePasswordModal from '../base/base-password-modal'
const TunSwitcher: React.FC = () => { const TunSwitcher: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const match = location.pathname.includes('/tun') const match = location.pathname.includes('/tun') || false
const [openPasswordModal, setOpenPasswordModal] = useState(false) const [openPasswordModal, setOpenPasswordModal] = useState(false)
const { appConfig, patchAppConfig } = useAppConfig() const { appConfig, patchAppConfig } = useAppConfig()
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true) const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true)
const { tun } = controledMihomoConfig || {} const { tun } = controledMihomoConfig || {}
const { enable } = tun || {} const { enable } = tun || {}
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: 'tun'
})
const onChange = async (enable: boolean): Promise<void> => { const onChange = async (enable: boolean): Promise<void> => {
if (enable && platform !== 'win32') { if (enable && platform !== 'win32') {
@ -40,7 +45,15 @@ const TunSwitcher: React.FC = () => {
} }
return ( return (
<> <div
style={{
position: 'relative',
transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className="col-span-1"
>
{openPasswordModal && ( {openPasswordModal && (
<BasePasswordModal <BasePasswordModal
onCancel={() => setOpenPasswordModal(false)} onCancel={() => setOpenPasswordModal(false)}
@ -51,13 +64,15 @@ const TunSwitcher: React.FC = () => {
}} }}
/> />
)} )}
<Card <Card
className={`col-span-1 ${match ? 'bg-primary' : ''}`} fullWidth
className={`${match ? 'bg-primary' : ''}`}
isPressable isPressable
onPress={() => navigate('/tun')} onPress={() => navigate('/tun')}
> >
<CardBody className="pb-1 pt-0 px-0"> <CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between"> <div ref={setNodeRef} {...attributes} {...listeners} className="flex justify-between">
<Button <Button
isIconOnly isIconOnly
className="bg-transparent pointer-events-none" className="bg-transparent pointer-events-none"
@ -81,7 +96,7 @@ const TunSwitcher: React.FC = () => {
</h3> </h3>
</CardFooter> </CardFooter>
</Card> </Card>
</> </div>
) )
} }

View File

@ -194,6 +194,7 @@ interface IAppConfig {
core: 'mihomo' | 'mihomo-alpha' core: 'mihomo' | 'mihomo-alpha'
proxyDisplayMode: 'simple' | 'full' proxyDisplayMode: 'simple' | 'full'
proxyDisplayOrder: 'default' | 'delay' | 'name' proxyDisplayOrder: 'default' | 'delay' | 'name'
siderOrder: string[]
appTheme: AppTheme appTheme: AppTheme
autoCheckUpdate: boolean autoCheckUpdate: boolean
silentStart: boolean silentStart: boolean