add tray icon

This commit is contained in:
pompurin404 2024-07-30 20:41:06 +08:00
parent bad31c2398
commit cb6b167fd5
No known key found for this signature in database
18 changed files with 292 additions and 77 deletions

BIN
resources/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,29 +1,39 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { app, shell, BrowserWindow, Tray, Menu } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import icon from '../../resources/icon.ico?asset'
let window: BrowserWindow | null = null
let tray: Tray | null = null
let trayContextMenu: Menu | null = null
function createWindow(): void {
// Create the browser window.
const mainWindow = new BrowserWindow({
window = new BrowserWindow({
minWidth: 800,
minHeight: 600,
width: 800,
height: 600,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
icon: icon,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
window.on('ready-to-show', () => {
window?.show()
window?.focusOnWebView()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
window.on('close', (event) => {
event.preventDefault()
window?.hide()
})
window.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
@ -31,12 +41,44 @@ function createWindow(): void {
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
window.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
window.loadFile(join(__dirname, '../renderer/index.html'))
}
}
function createTray(): void {
tray = new Tray(icon)
trayContextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
type: 'normal',
click: (): void => {
window?.show()
window?.focusOnWebView()
}
},
{
label: '重启应用',
type: 'normal',
click: (): void => {
app.relaunch()
app.quit()
}
},
{ type: 'separator' },
{ label: '退出应用', type: 'normal', click: (): void => app.quit() }
])
tray.setContextMenu(trayContextMenu)
tray.setIgnoreDoubleClickEvents(true)
tray.setToolTip('Another Mihomo GUI.')
tray.setTitle('Mihomo Party')
tray.addListener('click', () => {
window?.isVisible() ? window?.hide() : window?.show()
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
@ -51,10 +93,8 @@ app.whenReady().then(() => {
optimizer.watchWindowShortcuts(window)
})
// IPC test
ipcMain.on('ping', () => console.log('pong'))
createWindow()
createTray()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
@ -72,5 +112,6 @@ app.on('window-all-closed', () => {
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
app.on('before-quit', () => {
app.exit()
})

View File

@ -1,21 +1,23 @@
import { useTheme } from 'next-themes'
import { useEffect } from 'react'
import { useRoutes } from 'react-router-dom'
import { useLocation, useNavigate, 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 { IoSettings } from 'react-icons/io5'
import routes from '@renderer/routes'
import RouteItem from './components/sider/route-item'
import ProfileSwitcher from './components/sider/profile-switcher'
import ProfileCard from '@renderer/components/sider/profile-card'
import ProxyCard from '@renderer/components/sider/proxy-card'
import RuleCard from '@renderer/components/sider/rule-card'
import OverrideCard from '@renderer/components/sider/override-card'
import ConnCard from '@renderer/components/sider/conn-card'
import LogCard from '@renderer/components/sider/log-card'
function App(): JSX.Element {
const { setTheme } = useTheme()
const navigate = useNavigate()
const location = useLocation()
const page = useRoutes(routes)
useEffect(() => {
@ -39,32 +41,45 @@ function App(): JSX.Element {
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">
<div className="side w-[250px] h-full border-r border-neutral-700">
<div className="flex justify-between h-[32px] m-2">
<h3 className="select-none text-lg font-bold leading-[32px]"></h3>
<Button
size="sm"
isIconOnly
variant="light"
color={location.pathname.includes('/settings') ? 'primary' : 'default'}
variant={location.pathname.includes('/settings') ? 'solid' : 'light'}
onPress={() => {
open('https://github.com/pompurin404/mihomo-party')
navigate('/settings')
}}
startContent={<IoLogoGithub className="text-[20px]" />}
startContent={<IoSettings className="text-[20px]" />}
/>
</div>
<div className="mx-2">
<OutboundModeSwitcher />
</div>
<OutboundModeSwitcher />
<h3 className="select-none text-lg font-bold my-2"></h3>
<div className="flex justify-between">
<h3 className="select-none text-lg font-bold m-2"></h3>
<div className="flex justify-between mx-2">
<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} />
<h3 className="select-none text-lg font-bold m-2"></h3>
<div className="w-full h-[calc(100%-260px)] overflow-y-auto no-scrollbar">
<div className="mx-2">
<ProfileCard />
<ProxyCard />
</div>
<div className="flex justify-between mx-2">
<ConnCard />
<LogCard />
</div>
<div className="flex justify-between mx-2">
<RuleCard />
<OverrideCard />
</div>
</div>
</div>
<div className="main w-[calc(100%-250px)] h-full overflow-y-auto">{page}</div>
</div>

View File

@ -1,3 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
}

View File

@ -0,0 +1,35 @@
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
import { IoLink } from 'react-icons/io5'
import { useLocation, useNavigate } from 'react-router-dom'
export default function ConnCard(): JSX.Element {
const navigate = useNavigate()
const location = useLocation()
return (
<Card
className={`w-[50%] mr-1 mb-2 ${location.pathname.includes('/connections') ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/connections')}
>
<CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<IoLink color="default" className="text-[20px]" />
</Button>
<Chip size="sm" color="secondary" variant="bordered" className="mr-3 mt-2">
1103
</Chip>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className="select-none text-md font-bold"></h3>
</CardFooter>
</Card>
)
}

View File

@ -0,0 +1,32 @@
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
import { IoJournal } from 'react-icons/io5'
import { useLocation, useNavigate } from 'react-router-dom'
export default function LogCard(): JSX.Element {
const navigate = useNavigate()
const location = useLocation()
return (
<Card
className={`w-[50%] ml-1 mb-2 ${location.pathname.includes('/logs') ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/logs')}
>
<CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<IoJournal color="default" className="text-[20px]" />
</Button>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className="select-none text-md font-bold"></h3>
</CardFooter>
</Card>
)
}

View File

@ -0,0 +1,33 @@
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
import { MdFormatOverline } from 'react-icons/md'
import { useLocation, useNavigate } from 'react-router-dom'
export default function OverrideCard(): JSX.Element {
const navigate = useNavigate()
const location = useLocation()
return (
<Card
className={`w-[50%] ml-1 mb-2 ${location.pathname.includes('/override') ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/override')}
>
<CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<MdFormatOverline color="default" className="text-[24px]" />
</Button>
<Switch />
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className="select-none text-md font-bold"></h3>
</CardFooter>
</Card>
)
}

View File

@ -2,7 +2,7 @@ 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 {
export default function ProfileCard(): JSX.Element {
const navigate = useNavigate()
const location = useLocation()
@ -17,7 +17,7 @@ export default function ProfileSwitcher(): JSX.Element {
<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]" />
<IoMdRefresh color="default" className="text-[24px]" />
</Button>
</div>
</CardBody>

View File

@ -0,0 +1,29 @@
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
import { SiSpeedtest } from 'react-icons/si'
import { useLocation, useNavigate } from 'react-router-dom'
export default function ProxyCard(): JSX.Element {
const navigate = useNavigate()
const location = useLocation()
return (
<Card
fullWidth
className={`mb-2 ${location.pathname.includes('/proxies') ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/proxies')}
>
<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">
<SiSpeedtest color="default" className="text-[20px]" />
</Button>
</div>
</CardBody>
<CardFooter className="pt-1">
<small></small>
</CardFooter>
</Card>
)
}

View File

@ -1,28 +0,0 @@
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>
)
}

View File

@ -0,0 +1,35 @@
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
import { IoGitNetwork } from 'react-icons/io5'
import { useLocation, useNavigate } from 'react-router-dom'
export default function RuleCard(): JSX.Element {
const navigate = useNavigate()
const location = useLocation()
return (
<Card
className={`w-[50%] mr-1 mb-2 ${location.pathname.includes('/rules') ? 'bg-primary' : ''}`}
isPressable
onPress={() => navigate('/rules')}
>
<CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<IoGitNetwork color="default" className="text-[20px]" />
</Button>
<Chip size="sm" color="secondary" variant="bordered" className="mr-3 mt-2">
1103
</Chip>
</div>
</CardBody>
<CardFooter className="pt-1">
<h3 className="select-none text-md font-bold"></h3>
</CardFooter>
</Card>
)
}

View File

@ -3,7 +3,7 @@ import { IoSettings } from 'react-icons/io5'
export default function SysproxySwitcher(): JSX.Element {
return (
<Card className="w-[48%]">
<Card className="w-[50%] mr-1">
<CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between">
<Button isIconOnly className="bg-transparent" variant="flat" color="default">

View File

@ -3,7 +3,7 @@ import { IoSettings } from 'react-icons/io5'
export default function SysproxySwitcher(): JSX.Element {
return (
<Card className="w-[48%]">
<Card className="w-[50%] ml-1">
<CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between">
<Button isIconOnly className="bg-transparent" variant="flat" color="default">

View File

@ -0,0 +1,3 @@
export default function Connections(): JSX.Element {
return <div>Connections</div>
}

View File

@ -0,0 +1,3 @@
export default function Logs(): JSX.Element {
return <div>Logs</div>
}

View File

@ -0,0 +1,3 @@
export default function Override(): JSX.Element {
return <div>Override</div>
}

View File

@ -1,3 +0,0 @@
export default function Overview(): JSX.Element {
return <div>Overview</div>
}

View File

@ -1,15 +1,13 @@
import { Navigate } from 'react-router-dom'
import Overview from '@renderer/pages/overview'
import Override from '@renderer/pages/override'
import Proxies from '@renderer/pages/proxies'
import Rules from '@renderer/pages/rules'
import Settings from '@renderer/pages/settings'
import Profiles from '@renderer/pages/profiles'
import Logs from '@renderer/pages/logs'
import Connections from '@renderer/pages/connections'
const routes = [
{
path: '/overview',
element: <Overview />
},
{
path: '/proxies',
element: <Proxies />
@ -18,6 +16,18 @@ const routes = [
path: '/rules',
element: <Rules />
},
{
path: '/logs',
element: <Logs />
},
{
path: '/connections',
element: <Connections />
},
{
path: '/override',
element: <Override />
},
{
path: '/profiles',
element: <Profiles />
@ -28,7 +38,7 @@ const routes = [
},
{
path: '/',
element: <Navigate to="/overview" />
element: <Navigate to="/proxies" />
}
]