feat: windows缩略图工具栏 (#1551)
* 从 v1 引入图标 * feat: windows缩略图工具栏 * 更新windows任务栏控制图标 图标基于microsoft fluent icon修改 移除like和unlike * update * 启动时显示taskbar buttons
BIN
src/main/assets/icons/taskbar/next.png
Normal file
|
After Width: | Height: | Size: 936 B |
BIN
src/main/assets/icons/taskbar/pause.png
Normal file
|
After Width: | Height: | Size: 612 B |
BIN
src/main/assets/icons/taskbar/play.png
Normal file
|
After Width: | Height: | Size: 844 B |
BIN
src/main/assets/icons/taskbar/previous.png
Normal file
|
After Width: | Height: | Size: 890 B |
|
Before Width: | Height: | Size: 223 B After Width: | Height: | Size: 223 B |
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 308 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 953 B After Width: | Height: | Size: 953 B |
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
|
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
|
|
@ -14,6 +14,7 @@ import log from './log'
|
||||||
import { initIpcMain } from './ipcMain'
|
import { initIpcMain } from './ipcMain'
|
||||||
import { createTray, YPMTray } from './tray'
|
import { createTray, YPMTray } from './tray'
|
||||||
import { IpcChannels } from '@/shared/IpcChannels'
|
import { IpcChannels } from '@/shared/IpcChannels'
|
||||||
|
import { createTaskbar, Thumbar } from './windowsTaskbar'
|
||||||
|
|
||||||
const isWindows = process.platform === 'win32'
|
const isWindows = process.platform === 'win32'
|
||||||
const isMac = process.platform === 'darwin'
|
const isMac = process.platform === 'darwin'
|
||||||
|
|
@ -32,6 +33,7 @@ interface TypedElectronStore {
|
||||||
class Main {
|
class Main {
|
||||||
win: BrowserWindow | null = null
|
win: BrowserWindow | null = null
|
||||||
tray: YPMTray | null = null
|
tray: YPMTray | null = null
|
||||||
|
thumbar: Thumbar | null = null
|
||||||
store = new Store<TypedElectronStore>({
|
store = new Store<TypedElectronStore>({
|
||||||
defaults: {
|
defaults: {
|
||||||
window: {
|
window: {
|
||||||
|
|
@ -62,7 +64,8 @@ class Main {
|
||||||
this.handleAppEvents()
|
this.handleAppEvents()
|
||||||
this.handleWindowEvents()
|
this.handleWindowEvents()
|
||||||
this.createTray()
|
this.createTray()
|
||||||
initIpcMain(this.win, this.tray)
|
this.createThumbar()
|
||||||
|
initIpcMain(this.win, this.tray, this.thumbar)
|
||||||
this.initDevTools()
|
this.initDevTools()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +96,10 @@ class Main {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createThumbar() {
|
||||||
|
if (isWindows) this.thumbar = createTaskbar(this.win!)
|
||||||
|
}
|
||||||
|
|
||||||
createWindow() {
|
createWindow() {
|
||||||
const options: BrowserWindowConstructorOptions = {
|
const options: BrowserWindowConstructorOptions = {
|
||||||
title: 'YesPlayMusic',
|
title: 'YesPlayMusic',
|
||||||
|
|
@ -150,7 +157,7 @@ class Main {
|
||||||
handleAppEvents() {
|
handleAppEvents() {
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
this.win = null
|
this.win = null
|
||||||
if (process.platform !== 'darwin') app.quit()
|
if (!isMac) app.quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('second-instance', () => {
|
app.on('second-instance', () => {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import log from './log'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { APIs } from '../shared/CacheAPIs'
|
import { APIs } from '../shared/CacheAPIs'
|
||||||
import { YPMTray } from './tray'
|
import { YPMTray } from './tray'
|
||||||
|
import { Thumbar } from './windowsTaskbar'
|
||||||
|
|
||||||
const on = <T extends keyof IpcChannelsParams>(
|
const on = <T extends keyof IpcChannelsParams>(
|
||||||
channel: T,
|
channel: T,
|
||||||
|
|
@ -14,9 +15,14 @@ const on = <T extends keyof IpcChannelsParams>(
|
||||||
ipcMain.on(channel, listener)
|
ipcMain.on(channel, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initIpcMain(win: BrowserWindow | null, tray: YPMTray | null) {
|
export function initIpcMain(
|
||||||
|
win: BrowserWindow | null,
|
||||||
|
tray: YPMTray | null,
|
||||||
|
thumbar: Thumbar | null
|
||||||
|
) {
|
||||||
initWindowIpcMain(win)
|
initWindowIpcMain(win)
|
||||||
initTrayIpcMain(tray)
|
initTrayIpcMain(tray)
|
||||||
|
initTaskbarIpcMain(thumbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,7 +51,7 @@ function initWindowIpcMain(win: BrowserWindow | null) {
|
||||||
function initTrayIpcMain(tray: YPMTray | null) {
|
function initTrayIpcMain(tray: YPMTray | null) {
|
||||||
on(IpcChannels.SetTrayTooltip, (e, { text }) => tray?.setTooltip(text))
|
on(IpcChannels.SetTrayTooltip, (e, { text }) => tray?.setTooltip(text))
|
||||||
|
|
||||||
on(IpcChannels.SetTrayLikeState, (e, { isLiked }) =>
|
on(IpcChannels.Like, (e, { isLiked }) =>
|
||||||
tray?.setLikeState(isLiked)
|
tray?.setLikeState(isLiked)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,6 +61,15 @@ function initTrayIpcMain(tray: YPMTray | null) {
|
||||||
on(IpcChannels.Repeat, (e, { mode }) => tray?.setRepeatMode(mode))
|
on(IpcChannels.Repeat, (e, { mode }) => tray?.setRepeatMode(mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理需要thumbar对象的事件
|
||||||
|
* @param {Thumbar} thumbar
|
||||||
|
*/
|
||||||
|
function initTaskbarIpcMain(thumbar: Thumbar | null) {
|
||||||
|
on(IpcChannels.Play, () => thumbar?.setPlayState(true))
|
||||||
|
on(IpcChannels.Pause, () => thumbar?.setPlayState(false))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除API缓存
|
* 清除API缓存
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import { RepeatMode } from '@/shared/playerDataTypes'
|
||||||
|
|
||||||
const iconDirRoot =
|
const iconDirRoot =
|
||||||
process.env.NODE_ENV === 'development'
|
process.env.NODE_ENV === 'development'
|
||||||
? path.join(process.cwd(), './src/main/assets/icons')
|
? path.join(process.cwd(), './src/main/assets/icons/tray')
|
||||||
: path.join(__dirname, './assets/icons')
|
: path.join(__dirname, './assets/icons/tray')
|
||||||
|
|
||||||
enum MenuItemIDs {
|
enum MenuItemIDs {
|
||||||
Play = 'play',
|
Play = 'play',
|
||||||
|
|
|
||||||
86
src/main/windowsTaskbar.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { IpcChannels } from '@/shared/IpcChannels'
|
||||||
|
import { BrowserWindow, nativeImage, ThumbarButton } from 'electron'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
enum ItemKeys {
|
||||||
|
Play = 'play',
|
||||||
|
Pause = 'pause',
|
||||||
|
Previous = 'previous',
|
||||||
|
Next = 'next',
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThumbarButtonMap = Map<ItemKeys, ThumbarButton>
|
||||||
|
|
||||||
|
const iconDirRoot =
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? path.join(process.cwd(), './src/main/assets/icons/taskbar')
|
||||||
|
: path.join(__dirname, './assets/icons/taskbar')
|
||||||
|
|
||||||
|
function createNativeImage(filename: string) {
|
||||||
|
return nativeImage.createFromPath(path.join(iconDirRoot, filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
function createThumbarButtons(win: BrowserWindow): ThumbarButtonMap {
|
||||||
|
return new Map<ItemKeys, ThumbarButton>()
|
||||||
|
.set(ItemKeys.Play, {
|
||||||
|
click: () => win.webContents.send(IpcChannels.Play),
|
||||||
|
icon: createNativeImage('play.png'),
|
||||||
|
tooltip: '播放',
|
||||||
|
})
|
||||||
|
.set(ItemKeys.Pause, {
|
||||||
|
click: () => win.webContents.send(IpcChannels.Pause),
|
||||||
|
icon: createNativeImage('pause.png'),
|
||||||
|
tooltip: '暂停',
|
||||||
|
})
|
||||||
|
.set(ItemKeys.Previous, {
|
||||||
|
click: () => win.webContents.send(IpcChannels.Previous),
|
||||||
|
icon: createNativeImage('previous.png'),
|
||||||
|
tooltip: '上一首',
|
||||||
|
})
|
||||||
|
.set(ItemKeys.Next, {
|
||||||
|
click: () => win.webContents.send(IpcChannels.Next),
|
||||||
|
icon: createNativeImage('next.png'),
|
||||||
|
tooltip: '下一首',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Thumbar {
|
||||||
|
setPlayState(isPlaying: boolean): void
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThumbarImpl implements Thumbar {
|
||||||
|
private _win: BrowserWindow
|
||||||
|
private _buttons: ThumbarButtonMap
|
||||||
|
|
||||||
|
private _playOrPause: ThumbarButton
|
||||||
|
private _previous: ThumbarButton
|
||||||
|
private _next: ThumbarButton
|
||||||
|
|
||||||
|
constructor(win: BrowserWindow) {
|
||||||
|
this._win = win
|
||||||
|
this._buttons = createThumbarButtons(win)
|
||||||
|
|
||||||
|
this._playOrPause = this._buttons.get(ItemKeys.Play)!
|
||||||
|
this._previous = this._buttons.get(ItemKeys.Previous)!
|
||||||
|
this._next = this._buttons.get(ItemKeys.Next)!
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateThumbarButtons(clear: boolean) {
|
||||||
|
this._win.setThumbarButtons(
|
||||||
|
clear
|
||||||
|
? []
|
||||||
|
: [this._previous, this._playOrPause, this._next]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlayState(isPlaying: boolean) {
|
||||||
|
this._playOrPause = this._buttons.get(
|
||||||
|
isPlaying ? ItemKeys.Pause : ItemKeys.Play
|
||||||
|
)!
|
||||||
|
this._updateThumbarButtons(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTaskbar(win: BrowserWindow): Thumbar {
|
||||||
|
return new ThumbarImpl(win)
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ const IpcRendererReact = () => {
|
||||||
}, [track])
|
}, [track])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.ipcRenderer?.send(IpcChannels.SetTrayLikeState, {
|
window.ipcRenderer?.send(IpcChannels.Like, {
|
||||||
isLiked: userLikedSongs?.ids?.includes(track?.id ?? 0) ?? false,
|
isLiked: userLikedSongs?.ids?.includes(track?.id ?? 0) ?? false,
|
||||||
})
|
})
|
||||||
}, [userLikedSongs, track])
|
}, [userLikedSongs, track])
|
||||||
|
|
@ -46,6 +46,13 @@ const IpcRendererReact = () => {
|
||||||
setIsPlaying(playing)
|
setIsPlaying(playing)
|
||||||
}, [state])
|
}, [state])
|
||||||
|
|
||||||
|
useEffectOnce(() => {
|
||||||
|
// 用于显示 windows taskbar buttons
|
||||||
|
if (playerSnapshot.track?.id) {
|
||||||
|
window.ipcRenderer?.send(IpcChannels.Pause)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ export const enum IpcChannels {
|
||||||
DevDbExportJson = 'dev-db-export-json',
|
DevDbExportJson = 'dev-db-export-json',
|
||||||
CacheCoverColor = 'cache-cover-color',
|
CacheCoverColor = 'cache-cover-color',
|
||||||
SetTrayTooltip = 'set-tray-tooltip',
|
SetTrayTooltip = 'set-tray-tooltip',
|
||||||
SetTrayLikeState = 'set-tray-like-state',
|
|
||||||
// 准备三个播放相关channel, 为 mpris 预留接口
|
// 准备三个播放相关channel, 为 mpris 预留接口
|
||||||
Play = 'play',
|
Play = 'play',
|
||||||
Pause = 'pause',
|
Pause = 'pause',
|
||||||
|
|
@ -41,9 +40,6 @@ export interface IpcChannelsParams {
|
||||||
[IpcChannels.SetTrayTooltip]: {
|
[IpcChannels.SetTrayTooltip]: {
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
[IpcChannels.SetTrayLikeState]: {
|
|
||||||
isLiked: boolean
|
|
||||||
}
|
|
||||||
[IpcChannels.Play]: void
|
[IpcChannels.Play]: void
|
||||||
[IpcChannels.Pause]: void
|
[IpcChannels.Pause]: void
|
||||||
[IpcChannels.PlayOrPause]: void
|
[IpcChannels.PlayOrPause]: void
|
||||||
|
|
@ -68,7 +64,6 @@ export interface IpcChannelsReturns {
|
||||||
[IpcChannels.DevDbExportJson]: void
|
[IpcChannels.DevDbExportJson]: void
|
||||||
[IpcChannels.CacheCoverColor]: void
|
[IpcChannels.CacheCoverColor]: void
|
||||||
[IpcChannels.SetTrayTooltip]: void
|
[IpcChannels.SetTrayTooltip]: void
|
||||||
[IpcChannels.SetTrayLikeState]: void
|
|
||||||
[IpcChannels.Play]: void
|
[IpcChannels.Play]: void
|
||||||
[IpcChannels.Pause]: void
|
[IpcChannels.Pause]: void
|
||||||
[IpcChannels.PlayOrPause]: void
|
[IpcChannels.PlayOrPause]: void
|
||||||
|
|
|
||||||