mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 11:42:19 +08:00
use window frame
This commit is contained in:
parent
cf943948bf
commit
179c606d78
12
changelog.md
12
changelog.md
|
@ -1,13 +1,9 @@
|
|||
### Break Changes
|
||||
|
||||
- YAML覆写语法有所变动,请更新后参考文档进行修改
|
||||
- 1.2.x YAML覆写语法有所变动,请更新后参考文档进行修改
|
||||
|
||||
### New Features
|
||||
|
||||
- YAML覆写功能支持对数组进行覆盖/前置/追加操作
|
||||
- 缓存代理组图标
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复provider过期时间解析错误
|
||||
- 修复订阅名称解析错误
|
||||
- 支持使用外部编辑器打开文件
|
||||
- 允许禁用系统标题栏
|
||||
- 重写连接页面
|
||||
|
|
|
@ -103,7 +103,7 @@ app.whenReady().then(async () => {
|
|||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
registerIpcMainHandlers()
|
||||
createWindow()
|
||||
await createWindow()
|
||||
await createTray()
|
||||
await initShortcut()
|
||||
app.on('activate', function () {
|
||||
|
@ -140,9 +140,9 @@ async function handleDeepLink(url: string): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
export function createWindow(show = false): void {
|
||||
export async function createWindow(): Promise<void> {
|
||||
Menu.setApplicationMenu(null)
|
||||
// Create the browser window.
|
||||
const { useWindowFrame = true } = await getAppConfig()
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 800,
|
||||
defaultHeight: 600
|
||||
|
@ -155,13 +155,13 @@ export function createWindow(show = false): void {
|
|||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
show: false,
|
||||
frame: false,
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
color: '#00000000',
|
||||
symbolColor: '#8D8D8D',
|
||||
height: 48
|
||||
},
|
||||
frame: useWindowFrame,
|
||||
titleBarStyle: useWindowFrame ? 'default' : 'hidden',
|
||||
titleBarOverlay: useWindowFrame
|
||||
? false
|
||||
: {
|
||||
height: 49
|
||||
},
|
||||
autoHideMenuBar: true,
|
||||
...(process.platform === 'linux' ? { icon: icon } : {}),
|
||||
webPreferences: {
|
||||
|
@ -173,7 +173,7 @@ export function createWindow(show = false): void {
|
|||
mainWindowState.manage(mainWindow)
|
||||
mainWindow.on('ready-to-show', async () => {
|
||||
const { silentStart } = await getAppConfig()
|
||||
if (!silentStart || show) {
|
||||
if (!silentStart) {
|
||||
mainWindow?.show()
|
||||
mainWindow?.focusOnWebView()
|
||||
}
|
||||
|
@ -210,7 +210,5 @@ export function showMainWindow(): void {
|
|||
if (mainWindow) {
|
||||
mainWindow.show()
|
||||
mainWindow.focusOnWebView()
|
||||
} else {
|
||||
createWindow(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
profileConfigPath,
|
||||
profilesDir
|
||||
} from '../utils/dirs'
|
||||
import { app } from 'electron'
|
||||
|
||||
export async function webdavBackup(): Promise<boolean> {
|
||||
const webdav = await import('webdav')
|
||||
|
@ -52,8 +51,6 @@ export async function webdavRestore(filename: string): Promise<void> {
|
|||
const zipData = await client.getFileContents(`mihomo-party/${filename}`)
|
||||
const zip = new AdmZip(zipData as Buffer)
|
||||
zip.extractAllTo(dataDir(), true)
|
||||
app.relaunch()
|
||||
app.quit()
|
||||
}
|
||||
|
||||
export async function listWebdavBackups(): Promise<string[]> {
|
||||
|
|
|
@ -16,8 +16,6 @@ export async function setPortable(portable: boolean): Promise<void> {
|
|||
} else {
|
||||
await rm(path.join(exeDir(), 'PORTABLE'))
|
||||
}
|
||||
app.relaunch()
|
||||
app.quit()
|
||||
}
|
||||
|
||||
export function dataDir(): string {
|
||||
|
|
|
@ -63,6 +63,7 @@ import { listWebdavBackups, webdavBackup, webdavDelete, webdavRestore } from '..
|
|||
import { getInterfaces } from '../sys/interface'
|
||||
import { copyEnv } from '../resolve/tray'
|
||||
import { registerShortcut } from '../resolve/shortcut'
|
||||
import { mainWindow } from '..'
|
||||
|
||||
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -178,11 +179,18 @@ export function registerIpcMainHandlers(): void {
|
|||
ipcMain.handle('setNativeTheme', (_e, theme) => {
|
||||
setNativeTheme(theme)
|
||||
})
|
||||
ipcMain.handle('setTitleBarOverlay', (_e, overlay) => {
|
||||
mainWindow?.setTitleBarOverlay(overlay)
|
||||
})
|
||||
ipcMain.handle('openFile', (_e, type, id, ext) => openFile(type, id, ext))
|
||||
ipcMain.handle('copyEnv', ipcErrorWrapper(copyEnv))
|
||||
ipcMain.handle('alert', (_e, msg) => {
|
||||
dialog.showErrorBox('Mihomo Party', msg)
|
||||
})
|
||||
ipcMain.handle('relaunchApp', () => {
|
||||
app.relaunch()
|
||||
app.quit()
|
||||
})
|
||||
ipcMain.handle('quitApp', () => app.quit())
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,9 @@ import MihomoCoreCard from '@renderer/components/sider/mihomo-core-card'
|
|||
import ResourceCard from '@renderer/components/sider/resource-card'
|
||||
import UpdaterButton from '@renderer/components/updater/updater-button'
|
||||
import { useAppConfig } from './hooks/use-app-config'
|
||||
import { setNativeTheme } from './utils/ipc'
|
||||
import { setNativeTheme, setTitleBarOverlay } from './utils/ipc'
|
||||
import { platform } from './utils/init'
|
||||
import { TitleBarOverlayOptions } from 'electron'
|
||||
|
||||
const App: React.FC = () => {
|
||||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
|
@ -36,6 +38,7 @@ const App: React.FC = () => {
|
|||
appTheme = 'system',
|
||||
controlDns = true,
|
||||
controlSniff = true,
|
||||
useWindowFrame = true,
|
||||
siderOrder = [
|
||||
'sysproxy',
|
||||
'tun',
|
||||
|
@ -53,7 +56,7 @@ const App: React.FC = () => {
|
|||
} = appConfig || {}
|
||||
const [order, setOrder] = useState(siderOrder)
|
||||
const sensors = useSensors(useSensor(PointerSensor))
|
||||
const { setTheme } = useTheme()
|
||||
const { setTheme, systemTheme } = useTheme()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const page = useRoutes(routes)
|
||||
|
@ -71,6 +74,30 @@ const App: React.FC = () => {
|
|||
setNativeTheme('dark')
|
||||
}
|
||||
setTheme(appTheme)
|
||||
if (!useWindowFrame) {
|
||||
let theme = appTheme as string
|
||||
if (appTheme === 'system') {
|
||||
theme = systemTheme || 'light'
|
||||
}
|
||||
const options = { height: 48 } as TitleBarOverlayOptions
|
||||
try {
|
||||
if (platform !== 'darwin') {
|
||||
if (theme.includes('light')) {
|
||||
options.color = '#FFFFFF'
|
||||
options.symbolColor = '#000000'
|
||||
} else if (theme.includes('dark')) {
|
||||
options.color = '#000000'
|
||||
options.symbolColor = '#FFFFFF'
|
||||
} else {
|
||||
options.color = '#18181b'
|
||||
options.symbolColor = '#FFFFFF'
|
||||
}
|
||||
}
|
||||
setTitleBarOverlay(options)
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}, [appTheme])
|
||||
|
||||
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
|
||||
|
@ -108,7 +135,11 @@ const App: React.FC = () => {
|
|||
<div className="side w-[250px] h-full overflow-y-auto no-scrollbar">
|
||||
<div className="app-drag sticky top-0 z-40 backdrop-blur bg-background/40 h-[49px]">
|
||||
<div className="flex justify-between p-2">
|
||||
<h3 className="text-lg font-bold leading-[32px]">Mihomo Party</h3>
|
||||
<h3
|
||||
className={`text-lg font-bold leading-[32px] ${!useWindowFrame && platform === 'darwin' ? 'invisible' : ''}`}
|
||||
>
|
||||
Mihomo Party
|
||||
</h3>
|
||||
<UpdaterButton />
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { Divider } from '@nextui-org/react'
|
||||
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||
interface Props {
|
||||
title?: React.ReactNode
|
||||
header?: React.ReactNode
|
||||
|
@ -8,22 +10,42 @@ interface Props {
|
|||
}
|
||||
|
||||
const BasePage = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
const { appConfig } = useAppConfig()
|
||||
const { useWindowFrame = true } = appConfig || {}
|
||||
const [overlayWidth, setOverlayWidth] = React.useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (platform !== 'darwin' && !useWindowFrame) {
|
||||
try {
|
||||
// @ts-ignore windowControlsOverlay
|
||||
const windowControlsOverlay = window.navigator.windowControlsOverlay
|
||||
setOverlayWidth(window.innerWidth - windowControlsOverlay.getTitlebarAreaRect().width)
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
useImperativeHandle(ref, () => {
|
||||
return contentRef.current as HTMLDivElement
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={contentRef} className="w-full h-full overflow-y-auto custom-scrollbar">
|
||||
<div className="sticky top-0 z-40 h-[49px] w-full backdrop-blur bg-background/40">
|
||||
<div ref={contentRef} className="w-full h-full">
|
||||
<div className="sticky top-0 z-40 h-[49px] w-full bg-background">
|
||||
<div className="app-drag p-2 flex justify-between h-[48px]">
|
||||
<div className="title h-full text-lg leading-[32px]">{props.title}</div>
|
||||
<div className="header h-full mr-[130px]">{props.header}</div>
|
||||
<div style={{ marginRight: overlayWidth }} className="header h-full">
|
||||
{props.header}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
</div>
|
||||
<div className="content">{props.children}</div>
|
||||
<div className="content h-[calc(100vh-49px)] overflow-y-auto custom-scrollbar">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
disableAutoRun,
|
||||
enableAutoRun,
|
||||
isPortable,
|
||||
relaunchApp,
|
||||
restartCore,
|
||||
setNativeTheme,
|
||||
setPortable
|
||||
} from '@renderer/utils/ipc'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
|
@ -28,6 +28,7 @@ const GeneralConfig: React.FC = () => {
|
|||
useDockIcon = true,
|
||||
showTraffic = true,
|
||||
proxyInTray = true,
|
||||
useWindowFrame = true,
|
||||
envType = platform === 'win32' ? 'powershell' : 'bash',
|
||||
autoCheckUpdate,
|
||||
appTheme = 'system'
|
||||
|
@ -43,13 +44,6 @@ const GeneralConfig: React.FC = () => {
|
|||
themeStr += `-${color}`
|
||||
}
|
||||
}
|
||||
if (themeStr.includes('light')) {
|
||||
setNativeTheme('light')
|
||||
} else if (themeStr === 'system') {
|
||||
setNativeTheme('system')
|
||||
} else {
|
||||
setNativeTheme('dark')
|
||||
}
|
||||
setTheme(themeStr)
|
||||
patchAppConfig({ appTheme: themeStr as AppTheme })
|
||||
} else {
|
||||
|
@ -172,6 +166,7 @@ const GeneralConfig: React.FC = () => {
|
|||
onSelectionChange={async (v) => {
|
||||
try {
|
||||
await setPortable(v.currentKey === 'portable')
|
||||
await relaunchApp()
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
} finally {
|
||||
|
@ -184,6 +179,16 @@ const GeneralConfig: React.FC = () => {
|
|||
</Select>
|
||||
</SettingItem>
|
||||
)}
|
||||
<SettingItem title="使用系统标题栏" divider>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={useWindowFrame}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ useWindowFrame: v })
|
||||
await relaunchApp()
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem title="背景色" divider={appTheme !== 'system'}>
|
||||
<Tabs
|
||||
size="sm"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
|
||||
import { webdavDelete, webdavRestore } from '@renderer/utils/ipc'
|
||||
import { relaunchApp, webdavDelete, webdavRestore } from '@renderer/utils/ipc'
|
||||
import React, { useState } from 'react'
|
||||
import { MdDeleteForever } from 'react-icons/md'
|
||||
interface Props {
|
||||
|
@ -36,6 +36,7 @@ const WebdavRestoreModal: React.FC<Props> = (props) => {
|
|||
setRestoring(true)
|
||||
try {
|
||||
await webdavRestore(filename)
|
||||
await relaunchApp()
|
||||
} catch (e) {
|
||||
alert(`恢复失败: ${e}`)
|
||||
} finally {
|
||||
|
|
|
@ -123,7 +123,7 @@ const Connections: React.FC = () => {
|
|||
{isDetailModalOpen && selected && (
|
||||
<ConnectionDetailModal onClose={() => setIsDetailModalOpen(false)} connection={selected} />
|
||||
)}
|
||||
<div className="overflow-x-auto sticky top-[49px] z-40">
|
||||
<div className="overflow-x-auto sticky top-0 z-40">
|
||||
<div className="flex p-2 gap-2">
|
||||
<Input
|
||||
variant="flat"
|
||||
|
|
|
@ -45,7 +45,7 @@ const Logs: React.FC = () => {
|
|||
|
||||
return (
|
||||
<BasePage title="实时日志">
|
||||
<div className="sticky top-[49px] z-40">
|
||||
<div className="sticky top-0 z-40">
|
||||
<div className="w-full flex p-2">
|
||||
<Input
|
||||
size="sm"
|
||||
|
|
|
@ -134,7 +134,7 @@ const Override: React.FC = () => {
|
|||
</>
|
||||
}
|
||||
>
|
||||
<div className="sticky top-[49px] z-40 backdrop-blur bg-background/40">
|
||||
<div className="sticky top-0 z-40 bg-background">
|
||||
<div className="flex p-2">
|
||||
<Input
|
||||
size="sm"
|
||||
|
|
|
@ -124,10 +124,9 @@ const Profiles: React.FC = () => {
|
|||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="sticky top-[49px] z-40 backdrop-blur bg-background/40">
|
||||
<div className="sticky top-0 z-40 bg-background">
|
||||
<div className="flex p-2">
|
||||
<Input
|
||||
// variant="bordered"
|
||||
size="sm"
|
||||
value={url}
|
||||
onValueChange={setUrl}
|
||||
|
|
|
@ -22,7 +22,7 @@ const Rules: React.FC = () => {
|
|||
|
||||
return (
|
||||
<BasePage title="分流规则">
|
||||
<div className="sticky top-[49px] z-40">
|
||||
<div className="sticky top-0 z-40">
|
||||
<div className="flex p-2">
|
||||
<Input
|
||||
size="sm"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { TitleBarOverlayOptions } from 'electron'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function ipcErrorWrapper(response: any): any {
|
||||
if (typeof response === 'object' && 'invokeError' in response) {
|
||||
|
@ -289,6 +291,14 @@ export async function webdavDelete(filename: string): Promise<void> {
|
|||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('webdavDelete', filename))
|
||||
}
|
||||
|
||||
export async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setTitleBarOverlay', overlay))
|
||||
}
|
||||
|
||||
export async function relaunchApp(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('relaunchApp'))
|
||||
}
|
||||
|
||||
export async function quitApp(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('quitApp'))
|
||||
}
|
||||
|
|
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
|
@ -217,6 +217,7 @@ interface IAppConfig {
|
|||
proxyDisplayOrder: 'default' | 'delay' | 'name'
|
||||
envType?: 'bash' | 'cmd' | 'powershell'
|
||||
proxyCols: 'auto' | '1' | '2' | '3' | '4'
|
||||
useWindowFrame: boolean
|
||||
proxyInTray: boolean
|
||||
siderOrder: string[]
|
||||
appTheme: AppTheme
|
||||
|
|
Loading…
Reference in New Issue
Block a user