diff --git a/resources/icon.ico b/resources/icon.ico new file mode 100644 index 0000000..93b62a3 Binary files /dev/null and b/resources/icon.ico differ diff --git a/src/main/index.ts b/src/main/index.ts index 5ba3a33..c38b66c 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -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() +}) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 0b7b9d6..e80cb48 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -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 (