mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 11:42:19 +08:00
sider design
This commit is contained in:
parent
1d2abbd7d5
commit
bad31c2398
10
package.json
10
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "mihomo-party",
|
||||
"version": "1.0.0",
|
||||
"description": "An Electron application with React and TypeScript",
|
||||
"description": "Mihomo Party",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "mihomo-party",
|
||||
"homepage": "https://mihomo.party",
|
||||
|
@ -26,7 +26,9 @@
|
|||
"@nextui-org/react": "^2.4.6",
|
||||
"electron-updater": "^6.2.1",
|
||||
"framer-motion": "^11.3.19",
|
||||
"next-themes": "^0.3.0"
|
||||
"next-themes": "^0.3.0",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-router-dom": "^6.25.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||
|
@ -36,17 +38,17 @@
|
|||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"electron": "^31.3.1",
|
||||
"electron-builder": "^25.0.2",
|
||||
"electron-vite": "^2.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.40",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"prettier": "^3.3.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.3.5"
|
||||
}
|
||||
|
|
|
@ -26,6 +26,12 @@ importers:
|
|||
next-themes:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-icons:
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1(react@18.3.1)
|
||||
react-router-dom:
|
||||
specifier: ^6.25.1
|
||||
version: 6.25.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
devDependencies:
|
||||
'@electron-toolkit/eslint-config-prettier':
|
||||
specifier: ^2.0.0
|
||||
|
@ -1556,6 +1562,10 @@ packages:
|
|||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@remix-run/router@1.18.0':
|
||||
resolution: {integrity: sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.19.1':
|
||||
resolution: {integrity: sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==}
|
||||
cpu: [arm]
|
||||
|
@ -3467,6 +3477,11 @@ packages:
|
|||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-icons@5.2.1:
|
||||
resolution: {integrity: sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
|
@ -3494,6 +3509,19 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-router-dom@6.25.1:
|
||||
resolution: {integrity: sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
|
||||
react-router@6.25.1:
|
||||
resolution: {integrity: sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
|
||||
react-style-singleton@2.2.1:
|
||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -6471,6 +6499,8 @@ snapshots:
|
|||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
react: 18.3.1
|
||||
|
||||
'@remix-run/router@1.18.0': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.19.1':
|
||||
optional: true
|
||||
|
||||
|
@ -8662,6 +8692,10 @@ snapshots:
|
|||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-icons@5.2.1(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
@ -8685,6 +8719,18 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 18.3.3
|
||||
|
||||
react-router-dom@6.25.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@remix-run/router': 1.18.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-router: 6.25.1(react@18.3.1)
|
||||
|
||||
react-router@6.25.1(react@18.3.1):
|
||||
dependencies:
|
||||
'@remix-run/router': 1.18.0
|
||||
react: 18.3.1
|
||||
|
||||
react-style-singleton@2.2.1(@types/react@18.3.3)(react@18.3.1):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
|
|
|
@ -6,8 +6,10 @@ import icon from '../../resources/icon.png?asset'
|
|||
function createWindow(): void {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 900,
|
||||
height: 670,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
width: 800,
|
||||
height: 600,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Electron</title>
|
||||
<title>Mihomo Party</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
import { Switch } from '@nextui-org/switch'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect } from 'react'
|
||||
import { useRoutes } from 'react-router-dom'
|
||||
import OutboundModeSwitcher from '@renderer/components/sider/outbound-mode-switcher'
|
||||
import SysproxySwitcher from '@renderer/components/sider/sysproxy-switcher'
|
||||
import TunSwitcher from '@renderer/components/sider/tun-switcher'
|
||||
import { Button } from '@nextui-org/react'
|
||||
import { IoHome, IoSettings } from 'react-icons/io5'
|
||||
import { IoWifi } from 'react-icons/io5'
|
||||
import { IoGitNetwork } from 'react-icons/io5'
|
||||
import { IoLogoGithub } from 'react-icons/io5'
|
||||
import routes from '@renderer/routes'
|
||||
import RouteItem from './components/sider/route-item'
|
||||
import ProfileSwitcher from './components/sider/profile-switcher'
|
||||
|
||||
function App(): JSX.Element {
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
const page = useRoutes(routes)
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
|
@ -24,7 +37,38 @@ function App(): JSX.Element {
|
|||
}
|
||||
}, [])
|
||||
|
||||
return <Switch />
|
||||
return (
|
||||
<div className="w-full h-[100vh] flex">
|
||||
<div className="side w-[250px] h-full p-2 border-r border-neutral-700">
|
||||
<div className="flex justify-between h-[32px] mb-2">
|
||||
<h3 className="select-none text-lg font-bold leading-[32px]">出站</h3>
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
open('https://github.com/pompurin404/mihomo-party')
|
||||
}}
|
||||
startContent={<IoLogoGithub className="text-[20px]" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<OutboundModeSwitcher />
|
||||
<h3 className="select-none text-lg font-bold my-2">代理</h3>
|
||||
<div className="flex justify-between">
|
||||
<SysproxySwitcher />
|
||||
<TunSwitcher />
|
||||
</div>
|
||||
<h3 className="select-none text-lg font-bold my-2">配置</h3>
|
||||
<ProfileSwitcher />
|
||||
<RouteItem title="概览" pathname="/overview" icon={IoHome} />
|
||||
<RouteItem title="代理" pathname="/proxies" icon={IoWifi} />
|
||||
<RouteItem title="规则" pathname="/rules" icon={IoGitNetwork} />
|
||||
<RouteItem title="设置" pathname="/settings" icon={IoSettings} />
|
||||
</div>
|
||||
<div className="main w-[calc(100%-250px)] h-full overflow-y-auto">{page}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
30
src/renderer/src/components/sider/outbound-mode-switcher.tsx
Normal file
30
src/renderer/src/components/sider/outbound-mode-switcher.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Tabs, Tab } from '@nextui-org/react'
|
||||
import { Key, useState } from 'react'
|
||||
|
||||
export default function OutboundModeSwitcher(): JSX.Element {
|
||||
const [mode, setMode] = useState<OutboundMode>('rule')
|
||||
return (
|
||||
<Tabs
|
||||
fullWidth
|
||||
color="primary"
|
||||
selectedKey={mode}
|
||||
onSelectionChange={(key: Key) => setMode(key as OutboundMode)}
|
||||
>
|
||||
<Tab
|
||||
className={`select-none ${mode === 'rule' ? 'font-bold' : ''}`}
|
||||
key="rule"
|
||||
title="规则"
|
||||
/>
|
||||
<Tab
|
||||
className={`select-none ${mode === 'global' ? 'font-bold' : ''}`}
|
||||
key="global"
|
||||
title="全局"
|
||||
/>
|
||||
<Tab
|
||||
className={`select-none ${mode === 'direct' ? 'font-bold' : ''}`}
|
||||
key="direct"
|
||||
title="直连"
|
||||
/>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
29
src/renderer/src/components/sider/profile-switcher.tsx
Normal file
29
src/renderer/src/components/sider/profile-switcher.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Button, Card, CardBody, CardFooter, Slider } from '@nextui-org/react'
|
||||
import { IoMdRefresh } from 'react-icons/io'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
export default function ProfileSwitcher(): JSX.Element {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Card
|
||||
fullWidth
|
||||
className={`mb-2 ${location.pathname.includes('/profiles') ? 'bg-primary' : ''}`}
|
||||
isPressable
|
||||
onPress={() => navigate('/profiles')}
|
||||
>
|
||||
<CardBody>
|
||||
<div className="flex justify-between h-[32px]">
|
||||
<h3 className="select-none text-md font-bold leading-[32px]">订阅名称</h3>
|
||||
<Button isIconOnly size="sm" variant="light" color="default">
|
||||
<IoMdRefresh color="default" className="text-[20px]" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="pt-1">
|
||||
<Slider className="pointer-events-none" color="foreground" value={20} hideThumb />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
28
src/renderer/src/components/sider/route-item.tsx
Normal file
28
src/renderer/src/components/sider/route-item.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Button } from '@nextui-org/react'
|
||||
import { IconType } from 'react-icons'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
pathname: string
|
||||
icon: IconType
|
||||
}
|
||||
|
||||
export default function RouteItem(props: Props): JSX.Element {
|
||||
const { pathname, icon: Icon, title } = props
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Button
|
||||
fullWidth
|
||||
color={location.pathname.includes(pathname) ? 'primary' : 'default'}
|
||||
variant={location.pathname.includes(pathname) ? 'solid' : 'light'}
|
||||
className="text-md mb-2"
|
||||
startContent={<Icon className="text-[20px]" />}
|
||||
onPress={() => navigate(pathname)}
|
||||
>
|
||||
<div className="w-full">{title}</div>
|
||||
</Button>
|
||||
)
|
||||
}
|
20
src/renderer/src/components/sider/sysproxy-switcher.tsx
Normal file
20
src/renderer/src/components/sider/sysproxy-switcher.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
|
||||
import { IoSettings } from 'react-icons/io5'
|
||||
|
||||
export default function SysproxySwitcher(): JSX.Element {
|
||||
return (
|
||||
<Card className="w-[48%]">
|
||||
<CardBody className="pb-1 pt-0 px-0">
|
||||
<div className="flex justify-between">
|
||||
<Button isIconOnly className="bg-transparent" variant="flat" color="default">
|
||||
<IoSettings color="default" className="text-lg" />
|
||||
</Button>
|
||||
<Switch />
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="pt-1">
|
||||
<h3 className="select-none text-md font-bold">系统代理</h3>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
20
src/renderer/src/components/sider/tun-switcher.tsx
Normal file
20
src/renderer/src/components/sider/tun-switcher.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
|
||||
import { IoSettings } from 'react-icons/io5'
|
||||
|
||||
export default function SysproxySwitcher(): JSX.Element {
|
||||
return (
|
||||
<Card className="w-[48%]">
|
||||
<CardBody className="pb-1 pt-0 px-0">
|
||||
<div className="flex justify-between">
|
||||
<Button isIconOnly className="bg-transparent" variant="flat" color="default">
|
||||
<IoSettings color="default" className="text-lg" />
|
||||
</Button>
|
||||
<Switch />
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="pt-1">
|
||||
<h3 className="select-none text-md font-bold">虚拟网卡</h3>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { NextUIProvider } from '@nextui-org/react'
|
||||
|
||||
|
@ -10,7 +11,9 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|||
<React.StrictMode>
|
||||
<NextUIProvider>
|
||||
<NextThemesProvider attribute="class" defaultTheme="dark">
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
</React.StrictMode>
|
||||
|
|
3
src/renderer/src/pages/overview.tsx
Normal file
3
src/renderer/src/pages/overview.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Overview(): JSX.Element {
|
||||
return <div>Overview</div>
|
||||
}
|
3
src/renderer/src/pages/profiles.tsx
Normal file
3
src/renderer/src/pages/profiles.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Profiles(): JSX.Element {
|
||||
return <div>Profiles</div>
|
||||
}
|
3
src/renderer/src/pages/proxies.tsx
Normal file
3
src/renderer/src/pages/proxies.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Proxies(): JSX.Element {
|
||||
return <div>Proxies</div>
|
||||
}
|
3
src/renderer/src/pages/rules.tsx
Normal file
3
src/renderer/src/pages/rules.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Rules(): JSX.Element {
|
||||
return <div>Rules</div>
|
||||
}
|
3
src/renderer/src/pages/settings.tsx
Normal file
3
src/renderer/src/pages/settings.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Settings(): JSX.Element {
|
||||
return <div>Settings</div>
|
||||
}
|
35
src/renderer/src/routes/index.tsx
Normal file
35
src/renderer/src/routes/index.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { Navigate } from 'react-router-dom'
|
||||
import Overview from '@renderer/pages/overview'
|
||||
import Proxies from '@renderer/pages/proxies'
|
||||
import Rules from '@renderer/pages/rules'
|
||||
import Settings from '@renderer/pages/settings'
|
||||
import Profiles from '@renderer/pages/profiles'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/overview',
|
||||
element: <Overview />
|
||||
},
|
||||
{
|
||||
path: '/proxies',
|
||||
element: <Proxies />
|
||||
},
|
||||
{
|
||||
path: '/rules',
|
||||
element: <Rules />
|
||||
},
|
||||
{
|
||||
path: '/profiles',
|
||||
element: <Profiles />
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
element: <Settings />
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
element: <Navigate to="/overview" />
|
||||
}
|
||||
]
|
||||
|
||||
export default routes
|
1
src/renderer/src/utils/types.d.ts
vendored
Normal file
1
src/renderer/src/utils/types.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
type OutboundMode = 'rule' | 'global' | 'direct'
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
|
||||
"include": [
|
||||
"src/renderer/src/env.d.ts",
|
||||
"src/renderer/src/utils/env.d.ts",
|
||||
"src/renderer/src/**/*",
|
||||
"src/renderer/src/**/*.tsx",
|
||||
"src/preload/*.d.ts"
|
||||
|
|
Loading…
Reference in New Issue
Block a user