mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2025-02-01 03:20:46 +08:00
feat: 完成windows与linux下的Titlebar (#1482)
* feat: 完成windows与linux下的Titlebar * update * fix: win titlebar * 优化部分api命名 * 视觉效果更好的标题间距 * fix: Linux title没有居中 * update * fix: Main style * 向renderer公开IpcRenderer.on * update
This commit is contained in:
parent
24798a0bf3
commit
1444bbefa2
|
@ -11,6 +11,7 @@ import Store from 'electron-store'
|
|||
import { release } from 'os'
|
||||
import path, { join } from 'path'
|
||||
import logger from './logger'
|
||||
import { initIpcMain } from './ipcMain'
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
const isMac = process.platform === 'darwin'
|
||||
|
@ -64,6 +65,7 @@ async function createWindow() {
|
|||
minHeight: 720,
|
||||
vibrancy: 'fullscreen-ui',
|
||||
titleBarStyle: 'hiddenInset',
|
||||
frame: !(isWindows || isLinux), // TODO: 适用于linux下独立的启用开关
|
||||
}
|
||||
if (store.get('window')) {
|
||||
options.x = store.get('window.x')
|
||||
|
@ -99,6 +101,8 @@ async function createWindow() {
|
|||
app.whenReady().then(async () => {
|
||||
logger.info('[index] App ready')
|
||||
createWindow()
|
||||
handleWindowEvents()
|
||||
initIpcMain(win)
|
||||
|
||||
// Install devtool extension
|
||||
if (isDev) {
|
||||
|
@ -138,3 +142,13 @@ app.on('activate', () => {
|
|||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
const handleWindowEvents = () => {
|
||||
win?.on('maximize', () => {
|
||||
win?.webContents.send('is-maximized', true)
|
||||
})
|
||||
|
||||
win?.on('unmaximize', () => {
|
||||
win?.webContents.send('is-maximized', false)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { ipcMain } from 'electron'
|
||||
import { BrowserWindow, ipcMain, app } from 'electron'
|
||||
import { db, Tables } from './db'
|
||||
|
||||
export enum Events {
|
||||
ClearAPICache = 'clear-api-cache',
|
||||
Minimize = 'minimize',
|
||||
MaximizeOrUnmaximize = 'maximize-or-unmaximize',
|
||||
Close = 'close',
|
||||
}
|
||||
|
||||
ipcMain.on(Events.ClearAPICache, () => {
|
||||
|
@ -15,3 +18,18 @@ ipcMain.on(Events.ClearAPICache, () => {
|
|||
db.truncate(Tables.AUDIO)
|
||||
db.vacuum()
|
||||
})
|
||||
|
||||
export function initIpcMain(win: BrowserWindow | null) {
|
||||
ipcMain.on(Events.Minimize, () => {
|
||||
win?.minimize()
|
||||
})
|
||||
|
||||
ipcMain.on(Events.MaximizeOrUnmaximize, () => {
|
||||
if (!win) return
|
||||
win.isMaximized() ? win.unmaximize() : win.maximize()
|
||||
})
|
||||
|
||||
ipcMain.on(Events.Close, () => {
|
||||
app.exit()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)
|
||||
contextBridge.exposeInMainWorld('ipcRenderer', {
|
||||
sendSync: ipcRenderer.sendSync,
|
||||
send: ipcRenderer.send,
|
||||
on: (channel, listener) => {
|
||||
ipcRenderer.on(channel, listener)
|
||||
return () => ipcRenderer.removeListener(channel, listener)
|
||||
},
|
||||
})
|
||||
contextBridge.exposeInMainWorld('env', {
|
||||
isElectron: true,
|
||||
isEnableTitlebar:
|
||||
process.platform === 'win32' || process.platform === 'linux',
|
||||
isLinux: process.platform === 'linux',
|
||||
isMac: process.platform === 'darwin',
|
||||
isWin: process.platform === 'win32',
|
||||
|
|
|
@ -11,7 +11,7 @@ import Lyric from '@/components/Lyric'
|
|||
const App = () => {
|
||||
return (
|
||||
<QueryClientProvider client={reactQueryClient}>
|
||||
{window.env?.isWin && <TitleBar />}
|
||||
{window.env?.isEnableTitlebar && <TitleBar />}
|
||||
|
||||
<div id='layout' className='grid select-none grid-cols-[16rem_auto]'>
|
||||
<Sidebar />
|
||||
|
|
1
src/renderer/auto-imports.d.ts
vendored
1
src/renderer/auto-imports.d.ts
vendored
|
@ -7,6 +7,7 @@ declare global {
|
|||
const useContext: typeof import('react')['useContext']
|
||||
const useDebugValue: typeof import('react')['useDebugValue']
|
||||
const useEffect: typeof import('react')['useEffect']
|
||||
const useEffectOnce: typeof import('react-use')['useEffectOnce']
|
||||
const useImperativeHandle: typeof import('react')['useImperativeHandle']
|
||||
const useInfiniteQuery: typeof import('react-query')['useInfiniteQuery']
|
||||
const useMemo: typeof import('react')['useMemo']
|
||||
|
|
|
@ -8,7 +8,13 @@ const Main = () => {
|
|||
className='relative flex h-screen max-h-screen flex-grow flex-col overflow-y-auto bg-white dark:bg-[#1d1d1d]'
|
||||
>
|
||||
<Topbar />
|
||||
<main id='main' className='mb-24 flex-grow px-8'>
|
||||
<main
|
||||
id='main'
|
||||
className={classNames(
|
||||
'mb-24 flex-grow px-8',
|
||||
window.env?.isEnableTitlebar && 'mt-8'
|
||||
)}
|
||||
>
|
||||
<Router />
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -35,7 +35,9 @@ const primaryTabs: PrimaryTab[] = [
|
|||
const PrimaryTabs = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className='app-region-drag h-14'></div>
|
||||
<div
|
||||
className={classNames(window.env?.isMac && 'app-region-drag', 'h-14')}
|
||||
></div>
|
||||
{primaryTabs.map(tab => (
|
||||
<NavLink
|
||||
onClick={() => scrollToTop()}
|
||||
|
|
|
@ -1,20 +1,94 @@
|
|||
import { player } from '@/store'
|
||||
import SvgIcon from './SvgIcon'
|
||||
|
||||
const Controls = () => {
|
||||
const [isMaximized, setIsMaximized] = useState(false)
|
||||
|
||||
useEffectOnce(() => {
|
||||
return window.ipcRenderer?.on('is-maximized', (e, value) => {
|
||||
setIsMaximized(value)
|
||||
})
|
||||
})
|
||||
|
||||
const minimize = () => {
|
||||
window.ipcRenderer?.send('minimize')
|
||||
}
|
||||
|
||||
const maxRestore = () => {
|
||||
window.ipcRenderer?.send('maximize-or-unmaximize')
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
window.ipcRenderer?.send('close')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='app-region-no-drag flex h-full'>
|
||||
<button
|
||||
onClick={minimize}
|
||||
className='flex w-[2.875rem] items-center justify-center hover:bg-[#e9e9e9]'
|
||||
>
|
||||
<SvgIcon className='h-3 w-3' name='windows-minimize' />
|
||||
</button>
|
||||
<button
|
||||
onClick={maxRestore}
|
||||
className='flex w-[2.875rem] items-center justify-center hover:bg-[#e9e9e9]'
|
||||
>
|
||||
<SvgIcon
|
||||
className='h-3 w-3'
|
||||
name={isMaximized ? 'windows-un-maximize' : 'windows-maximize'}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={close}
|
||||
className='flex w-[2.875rem] items-center justify-center hover:bg-[#c42b1c] hover:text-white'
|
||||
>
|
||||
<SvgIcon className='h-3 w-3' name='windows-close' />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Title = ({ className }: { className?: string }) => {
|
||||
const playerSnapshot = useSnapshot(player)
|
||||
const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
|
||||
|
||||
return (
|
||||
<div className={classNames('text-sm text-gray-500', className)}>
|
||||
{track?.name && (
|
||||
<>
|
||||
<span>{track.name}</span>
|
||||
<span className='mx-2'>-</span>
|
||||
</>
|
||||
)}
|
||||
<span>YesPlayMusic</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Win = () => {
|
||||
return (
|
||||
<div className='flex h-8 w-screen items-center justify-between bg-gray-50'>
|
||||
<Title className='ml-3' />
|
||||
<Controls />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Linux = () => {
|
||||
return (
|
||||
<div className='flex h-8 w-screen items-center justify-between bg-gray-50'>
|
||||
<div></div>
|
||||
<Title className='text-center' />
|
||||
<Controls />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TitleBar = () => {
|
||||
return (
|
||||
<div className='app-region-drag flex h-8 w-screen items-center justify-between bg-gray-50'>
|
||||
<div className='ml-2 text-sm text-gray-500'>YesPlayMusic</div>
|
||||
<div className='flex h-full'>
|
||||
<button className='app-region-no-drag flex w-[2.875rem] items-center justify-center hover:bg-[#e9e9e9]'>
|
||||
<SvgIcon className='h-3 w-3' name='windows-minimize' />
|
||||
</button>
|
||||
<button className='app-region-no-drag flex w-[2.875rem] items-center justify-center hover:bg-[#e9e9e9]'>
|
||||
<SvgIcon className='h-3 w-3' name='windows-maximize' />
|
||||
</button>
|
||||
<button className='app-region-no-drag flex w-[2.875rem] items-center justify-center hover:bg-[#c42b1c] hover:text-white'>
|
||||
<SvgIcon className='h-3 w-3' name='windows-close' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='app-region-drag fixed z-30'>
|
||||
{window.env?.isWin ? <Win /> : <Linux />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -119,7 +119,9 @@ const Topbar = () => {
|
|||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'app-region-drag sticky top-0 z-30 flex h-16 min-h-[4rem] w-full cursor-default items-center justify-between px-8 transition duration-300',
|
||||
'sticky z-30 flex h-16 min-h-[4rem] w-full cursor-default items-center justify-between px-8 transition duration-300',
|
||||
window.env?.isMac && 'app-region-drag',
|
||||
window.env?.isEnableTitlebar ? 'top-8' : 'top-0',
|
||||
!scroll.arrivedState.top &&
|
||||
'bg-white bg-opacity-[.86] backdrop-blur-xl backdrop-saturate-[1.8] dark:bg-[#222] dark:bg-opacity-[.86]'
|
||||
)}
|
||||
|
|
1
src/renderer/global.d.ts
vendored
1
src/renderer/global.d.ts
vendored
|
@ -6,6 +6,7 @@ declare global {
|
|||
ipcRenderer?: import('electron').IpcRenderer
|
||||
env?: {
|
||||
isElectron: boolean
|
||||
isEnableTitlebar: boolean
|
||||
isLinux: boolean
|
||||
isMac: boolean
|
||||
isWin: boolean
|
||||
|
|
|
@ -36,6 +36,7 @@ export default defineConfig({
|
|||
{ 'react-query': ['useQuery', 'useMutation', 'useInfiniteQuery'] },
|
||||
{ 'react-router-dom': ['useNavigate', 'useParams'] },
|
||||
{ 'react-hot-toast': ['toast'] },
|
||||
{ 'react-use': ['useEffectOnce']},
|
||||
{ valtio: ['useSnapshot'] },
|
||||
],
|
||||
}),
|
||||
|
|
Loading…
Reference in New Issue
Block a user