mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 03:32:17 +08:00
support for sorting sidebar items
This commit is contained in:
parent
a8e886b9bb
commit
5c7f3f48f7
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user