mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2025-02-01 06:03:00 +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 { release } from 'os'
|
||||||
import path, { join } from 'path'
|
import path, { join } from 'path'
|
||||||
import logger from './logger'
|
import logger from './logger'
|
||||||
|
import { initIpcMain } from './ipcMain'
|
||||||
|
|
||||||
const isWindows = process.platform === 'win32'
|
const isWindows = process.platform === 'win32'
|
||||||
const isMac = process.platform === 'darwin'
|
const isMac = process.platform === 'darwin'
|
||||||
|
@ -64,6 +65,7 @@ async function createWindow() {
|
||||||
minHeight: 720,
|
minHeight: 720,
|
||||||
vibrancy: 'fullscreen-ui',
|
vibrancy: 'fullscreen-ui',
|
||||||
titleBarStyle: 'hiddenInset',
|
titleBarStyle: 'hiddenInset',
|
||||||
|
frame: !(isWindows || isLinux), // TODO: 适用于linux下独立的启用开关
|
||||||
}
|
}
|
||||||
if (store.get('window')) {
|
if (store.get('window')) {
|
||||||
options.x = store.get('window.x')
|
options.x = store.get('window.x')
|
||||||
|
@ -99,6 +101,8 @@ async function createWindow() {
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
logger.info('[index] App ready')
|
logger.info('[index] App ready')
|
||||||
createWindow()
|
createWindow()
|
||||||
|
handleWindowEvents()
|
||||||
|
initIpcMain(win)
|
||||||
|
|
||||||
// Install devtool extension
|
// Install devtool extension
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
@ -138,3 +142,13 @@ app.on('activate', () => {
|
||||||
createWindow()
|
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'
|
import { db, Tables } from './db'
|
||||||
|
|
||||||
export enum Events {
|
export enum Events {
|
||||||
ClearAPICache = 'clear-api-cache',
|
ClearAPICache = 'clear-api-cache',
|
||||||
|
Minimize = 'minimize',
|
||||||
|
MaximizeOrUnmaximize = 'maximize-or-unmaximize',
|
||||||
|
Close = 'close',
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.on(Events.ClearAPICache, () => {
|
ipcMain.on(Events.ClearAPICache, () => {
|
||||||
|
@ -15,3 +18,18 @@ ipcMain.on(Events.ClearAPICache, () => {
|
||||||
db.truncate(Tables.AUDIO)
|
db.truncate(Tables.AUDIO)
|
||||||
db.vacuum()
|
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')
|
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', {
|
contextBridge.exposeInMainWorld('env', {
|
||||||
isElectron: true,
|
isElectron: true,
|
||||||
|
isEnableTitlebar:
|
||||||
|
process.platform === 'win32' || process.platform === 'linux',
|
||||||
isLinux: process.platform === 'linux',
|
isLinux: process.platform === 'linux',
|
||||||
isMac: process.platform === 'darwin',
|
isMac: process.platform === 'darwin',
|
||||||
isWin: process.platform === 'win32',
|
isWin: process.platform === 'win32',
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Lyric from '@/components/Lyric'
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={reactQueryClient}>
|
<QueryClientProvider client={reactQueryClient}>
|
||||||
{window.env?.isWin && <TitleBar />}
|
{window.env?.isEnableTitlebar && <TitleBar />}
|
||||||
|
|
||||||
<div id='layout' className='grid select-none grid-cols-[16rem_auto]'>
|
<div id='layout' className='grid select-none grid-cols-[16rem_auto]'>
|
||||||
<Sidebar />
|
<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 useContext: typeof import('react')['useContext']
|
||||||
const useDebugValue: typeof import('react')['useDebugValue']
|
const useDebugValue: typeof import('react')['useDebugValue']
|
||||||
const useEffect: typeof import('react')['useEffect']
|
const useEffect: typeof import('react')['useEffect']
|
||||||
|
const useEffectOnce: typeof import('react-use')['useEffectOnce']
|
||||||
const useImperativeHandle: typeof import('react')['useImperativeHandle']
|
const useImperativeHandle: typeof import('react')['useImperativeHandle']
|
||||||
const useInfiniteQuery: typeof import('react-query')['useInfiniteQuery']
|
const useInfiniteQuery: typeof import('react-query')['useInfiniteQuery']
|
||||||
const useMemo: typeof import('react')['useMemo']
|
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]'
|
className='relative flex h-screen max-h-screen flex-grow flex-col overflow-y-auto bg-white dark:bg-[#1d1d1d]'
|
||||||
>
|
>
|
||||||
<Topbar />
|
<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 />
|
<Router />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,7 +35,9 @@ const primaryTabs: PrimaryTab[] = [
|
||||||
const PrimaryTabs = () => {
|
const PrimaryTabs = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='app-region-drag h-14'></div>
|
<div
|
||||||
|
className={classNames(window.env?.isMac && 'app-region-drag', 'h-14')}
|
||||||
|
></div>
|
||||||
{primaryTabs.map(tab => (
|
{primaryTabs.map(tab => (
|
||||||
<NavLink
|
<NavLink
|
||||||
onClick={() => scrollToTop()}
|
onClick={() => scrollToTop()}
|
||||||
|
|
|
@ -1,20 +1,94 @@
|
||||||
|
import { player } from '@/store'
|
||||||
import SvgIcon from './SvgIcon'
|
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 = () => {
|
const TitleBar = () => {
|
||||||
return (
|
return (
|
||||||
<div className='app-region-drag flex h-8 w-screen items-center justify-between bg-gray-50'>
|
<div className='app-region-drag fixed z-30'>
|
||||||
<div className='ml-2 text-sm text-gray-500'>YesPlayMusic</div>
|
{window.env?.isWin ? <Win /> : <Linux />}
|
||||||
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,9 @@ const Topbar = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
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 &&
|
!scroll.arrivedState.top &&
|
||||||
'bg-white bg-opacity-[.86] backdrop-blur-xl backdrop-saturate-[1.8] dark:bg-[#222] dark:bg-opacity-[.86]'
|
'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
|
ipcRenderer?: import('electron').IpcRenderer
|
||||||
env?: {
|
env?: {
|
||||||
isElectron: boolean
|
isElectron: boolean
|
||||||
|
isEnableTitlebar: boolean
|
||||||
isLinux: boolean
|
isLinux: boolean
|
||||||
isMac: boolean
|
isMac: boolean
|
||||||
isWin: boolean
|
isWin: boolean
|
||||||
|
|
|
@ -36,6 +36,7 @@ export default defineConfig({
|
||||||
{ 'react-query': ['useQuery', 'useMutation', 'useInfiniteQuery'] },
|
{ 'react-query': ['useQuery', 'useMutation', 'useInfiniteQuery'] },
|
||||||
{ 'react-router-dom': ['useNavigate', 'useParams'] },
|
{ 'react-router-dom': ['useNavigate', 'useParams'] },
|
||||||
{ 'react-hot-toast': ['toast'] },
|
{ 'react-hot-toast': ['toast'] },
|
||||||
|
{ 'react-use': ['useEffectOnce']},
|
||||||
{ valtio: ['useSnapshot'] },
|
{ valtio: ['useSnapshot'] },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user