use window frame

This commit is contained in:
pompurin404 2024-08-31 14:00:32 +08:00
parent cf943948bf
commit 179c606d78
No known key found for this signature in database
17 changed files with 116 additions and 50 deletions

View File

@ -1,13 +1,9 @@
### Break Changes
- YAML覆写语法有所变动请更新后参考文档进行修改
- 1.2.x YAML覆写语法有所变动请更新后参考文档进行修改
### New Features
- YAML覆写功能支持对数组进行覆盖/前置/追加操作
- 缓存代理组图标
### Bug Fixes
- 修复provider过期时间解析错误
- 修复订阅名称解析错误
- 支持使用外部编辑器打开文件
- 允许禁用系统标题栏
- 重写连接页面

View File

@ -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)
}
}

View File

@ -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[]> {

View File

@ -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 {

View File

@ -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())
}

View File

@ -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"

View File

@ -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>
)
})

View File

@ -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"

View File

@ -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 {

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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}

View File

@ -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"

View File

@ -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'))
}

View File

@ -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