mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-22 02:16:39 +02:00
feat: improve native macOS application menu (#2242)
This commit is contained in:
+27
-18
@@ -328,7 +328,7 @@ ipcMain.on('input-focus-state', (_event, focused: boolean) => {
|
|||||||
if (inputFocused === next) return;
|
if (inputFocused === next) return;
|
||||||
inputFocused = next;
|
inputFocused = next;
|
||||||
if (isMacOS()) {
|
if (isMacOS()) {
|
||||||
rebuildMainMenu();
|
updateMainMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -387,23 +387,32 @@ export const getMainWindow = () => {
|
|||||||
return mainWindow;
|
return mainWindow;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMainMenuState = (): MenuPlaybackState => ({
|
||||||
|
accelerators: playbackMenuAccelerators,
|
||||||
|
inputFocused,
|
||||||
|
playbackStatus: currentPlaybackStatus,
|
||||||
|
privateMode: currentPrivateMode,
|
||||||
|
repeatMode: currentRepeatMode,
|
||||||
|
shuffleEnabled: currentShuffleEnabled,
|
||||||
|
sidebarCollapsed: currentSidebarCollapsed,
|
||||||
|
});
|
||||||
|
|
||||||
const rebuildMainMenu = () => {
|
const rebuildMainMenu = () => {
|
||||||
if (!menuBuilder || !mainWindow) return;
|
if (!menuBuilder || !mainWindow) return;
|
||||||
|
|
||||||
menuBuilder.buildMenu({
|
menuBuilder.buildMenu(getMainMenuState());
|
||||||
accelerators: inputFocused ? {} : playbackMenuAccelerators,
|
|
||||||
playbackStatus: currentPlaybackStatus,
|
|
||||||
privateMode: currentPrivateMode,
|
|
||||||
repeatMode: currentRepeatMode,
|
|
||||||
shuffleEnabled: currentShuffleEnabled,
|
|
||||||
sidebarCollapsed: currentSidebarCollapsed,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
Menu.setApplicationMenu(null);
|
Menu.setApplicationMenu(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateMainMenu = () => {
|
||||||
|
if (!menuBuilder || !mainWindow) return;
|
||||||
|
|
||||||
|
menuBuilder.updateMenu(getMainMenuState());
|
||||||
|
};
|
||||||
|
|
||||||
export const sendToastToRenderer = ({
|
export const sendToastToRenderer = ({
|
||||||
message,
|
message,
|
||||||
type,
|
type,
|
||||||
@@ -961,11 +970,11 @@ ipcMain.on(
|
|||||||
}
|
}
|
||||||
|
|
||||||
playbackMenuAccelerators = {
|
playbackMenuAccelerators = {
|
||||||
|
globalSearch: getMenuAccelerator(data, BindingActions.GLOBAL_SEARCH),
|
||||||
next: getMenuAccelerator(data, BindingActions.NEXT),
|
next: getMenuAccelerator(data, BindingActions.NEXT),
|
||||||
playPause:
|
pause: getMenuAccelerator(data, BindingActions.PAUSE),
|
||||||
getMenuAccelerator(data, BindingActions.PLAY_PAUSE) ||
|
play: getMenuAccelerator(data, BindingActions.PLAY),
|
||||||
getMenuAccelerator(data, BindingActions.PLAY) ||
|
playPause: getMenuAccelerator(data, BindingActions.PLAY_PAUSE),
|
||||||
getMenuAccelerator(data, BindingActions.PAUSE),
|
|
||||||
previous: getMenuAccelerator(data, BindingActions.PREVIOUS),
|
previous: getMenuAccelerator(data, BindingActions.PREVIOUS),
|
||||||
repeat: getMenuAccelerator(data, BindingActions.TOGGLE_REPEAT),
|
repeat: getMenuAccelerator(data, BindingActions.TOGGLE_REPEAT),
|
||||||
seekBackward: getMenuAccelerator(data, BindingActions.SKIP_BACKWARD),
|
seekBackward: getMenuAccelerator(data, BindingActions.SKIP_BACKWARD),
|
||||||
@@ -1186,7 +1195,7 @@ ipcMain.on('update-playback', (_event, status: PlayerStatus) => {
|
|||||||
|
|
||||||
if (!isMacOS()) return;
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
rebuildMainMenu();
|
updateMainMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-repeat', (_event, repeat: PlayerRepeat) => {
|
ipcMain.on('update-repeat', (_event, repeat: PlayerRepeat) => {
|
||||||
@@ -1194,7 +1203,7 @@ ipcMain.on('update-repeat', (_event, repeat: PlayerRepeat) => {
|
|||||||
|
|
||||||
if (!isMacOS()) return;
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
rebuildMainMenu();
|
updateMainMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
||||||
@@ -1202,7 +1211,7 @@ ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
|||||||
|
|
||||||
if (!isMacOS()) return;
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
rebuildMainMenu();
|
updateMainMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-private-mode', (_event, privateMode: boolean) => {
|
ipcMain.on('update-private-mode', (_event, privateMode: boolean) => {
|
||||||
@@ -1210,7 +1219,7 @@ ipcMain.on('update-private-mode', (_event, privateMode: boolean) => {
|
|||||||
|
|
||||||
if (!isMacOS()) return;
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
rebuildMainMenu();
|
updateMainMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-sidebar-collapsed', (_event, collapsedSidebar: boolean) => {
|
ipcMain.on('update-sidebar-collapsed', (_event, collapsedSidebar: boolean) => {
|
||||||
@@ -1218,5 +1227,5 @@ ipcMain.on('update-sidebar-collapsed', (_event, collapsedSidebar: boolean) => {
|
|||||||
|
|
||||||
if (!isMacOS()) return;
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
rebuildMainMenu();
|
updateMainMenu();
|
||||||
});
|
});
|
||||||
|
|||||||
+166
-114
@@ -1,4 +1,4 @@
|
|||||||
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, shell } from 'electron';
|
import { BrowserWindow, Menu, MenuItemConstructorOptions, shell } from 'electron';
|
||||||
|
|
||||||
import packageJson from '../../package.json';
|
import packageJson from '../../package.json';
|
||||||
|
|
||||||
@@ -6,7 +6,10 @@ import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
|
|||||||
|
|
||||||
export type MenuPlaybackState = {
|
export type MenuPlaybackState = {
|
||||||
accelerators?: {
|
accelerators?: {
|
||||||
|
globalSearch?: string;
|
||||||
next?: string;
|
next?: string;
|
||||||
|
pause?: string;
|
||||||
|
play?: string;
|
||||||
playPause?: string;
|
playPause?: string;
|
||||||
previous?: string;
|
previous?: string;
|
||||||
repeat?: string;
|
repeat?: string;
|
||||||
@@ -17,6 +20,7 @@ export type MenuPlaybackState = {
|
|||||||
volumeDown?: string;
|
volumeDown?: string;
|
||||||
volumeUp?: string;
|
volumeUp?: string;
|
||||||
};
|
};
|
||||||
|
inputFocused?: boolean;
|
||||||
playbackStatus?: PlayerStatus;
|
playbackStatus?: PlayerStatus;
|
||||||
privateMode?: boolean;
|
privateMode?: boolean;
|
||||||
repeatMode?: PlayerRepeat;
|
repeatMode?: PlayerRepeat;
|
||||||
@@ -24,12 +28,53 @@ export type MenuPlaybackState = {
|
|||||||
sidebarCollapsed?: boolean;
|
sidebarCollapsed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
|
const MENU_ITEM_IDS = {
|
||||||
selector?: string;
|
next: 'playback-next',
|
||||||
submenu?: DarwinMenuItemConstructorOptions[] | Menu;
|
pause: 'playback-pause',
|
||||||
}
|
play: 'playback-play',
|
||||||
|
previous: 'playback-previous',
|
||||||
|
privateMode: 'app-private-mode',
|
||||||
|
repeat: 'playback-repeat',
|
||||||
|
seekBackward: 'playback-seek-backward',
|
||||||
|
seekForward: 'playback-seek-forward',
|
||||||
|
shuffle: 'playback-shuffle',
|
||||||
|
sidebarCollapsed: 'view-sidebar-collapsed',
|
||||||
|
stop: 'playback-stop',
|
||||||
|
volumeDown: 'playback-volume-down',
|
||||||
|
volumeUp: 'playback-volume-up',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const NON_TYPING_MODIFIERS = new Set([
|
||||||
|
'alt',
|
||||||
|
'cmd',
|
||||||
|
'command',
|
||||||
|
'commandorcontrol',
|
||||||
|
'control',
|
||||||
|
'ctrl',
|
||||||
|
'meta',
|
||||||
|
'option',
|
||||||
|
'super',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hasTypingSensitiveAccelerator = (accelerator?: string): boolean => {
|
||||||
|
if (!accelerator) return false;
|
||||||
|
|
||||||
|
const parts = accelerator.toLowerCase().split('+');
|
||||||
|
const key = parts.at(-1) || '';
|
||||||
|
|
||||||
|
if (/^f(?:[1-9]|1\d|2[0-4])$/.test(key) || /^(?:media|volume)/.test(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !parts.some((part) => NON_TYPING_MODIFIERS.has(part));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPlaybackItemEnabled = (inputFocused: boolean, accelerator?: string): boolean => {
|
||||||
|
return !inputFocused || !hasTypingSensitiveAccelerator(accelerator);
|
||||||
|
};
|
||||||
|
|
||||||
export default class MenuBuilder {
|
export default class MenuBuilder {
|
||||||
|
applicationMenu: Menu | null = null;
|
||||||
developmentEnvironmentSetup = false;
|
developmentEnvironmentSetup = false;
|
||||||
mainWindow: BrowserWindow;
|
mainWindow: BrowserWindow;
|
||||||
|
|
||||||
@@ -39,6 +84,7 @@ export default class MenuBuilder {
|
|||||||
|
|
||||||
buildDarwinTemplate({
|
buildDarwinTemplate({
|
||||||
accelerators,
|
accelerators,
|
||||||
|
inputFocused = false,
|
||||||
playbackStatus = PlayerStatus.PAUSED,
|
playbackStatus = PlayerStatus.PAUSED,
|
||||||
privateMode = false,
|
privateMode = false,
|
||||||
repeatMode = PlayerRepeat.NONE,
|
repeatMode = PlayerRepeat.NONE,
|
||||||
@@ -48,13 +94,10 @@ export default class MenuBuilder {
|
|||||||
const isPlaying = playbackStatus === PlayerStatus.PLAYING;
|
const isPlaying = playbackStatus === PlayerStatus.PLAYING;
|
||||||
const isRepeatEnabled = repeatMode !== PlayerRepeat.NONE;
|
const isRepeatEnabled = repeatMode !== PlayerRepeat.NONE;
|
||||||
|
|
||||||
const subMenuAbout: DarwinMenuItemConstructorOptions = {
|
const subMenuAbout: MenuItemConstructorOptions = {
|
||||||
label: 'Electron',
|
label: 'Electron',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{ role: 'about' },
|
||||||
label: 'About Feishin',
|
|
||||||
selector: 'orderFrontStandardAboutPanel:',
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
accelerator: 'Command+,',
|
accelerator: 'Command+,',
|
||||||
@@ -68,61 +111,44 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-open-manage-servers');
|
this.mainWindow.webContents.send('renderer-open-manage-servers');
|
||||||
},
|
},
|
||||||
label: 'Manage servers',
|
label: 'Manage Servers',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
checked: privateMode,
|
checked: privateMode,
|
||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-toggle-private-mode');
|
this.mainWindow.webContents.send('renderer-toggle-private-mode');
|
||||||
},
|
},
|
||||||
label: 'Private session',
|
id: MENU_ITEM_IDS.privateMode,
|
||||||
|
label: 'Private Session',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Services', submenu: [] },
|
{ role: 'services' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{ role: 'hide' },
|
||||||
accelerator: 'Command+H',
|
{ role: 'hideOthers' },
|
||||||
label: 'Hide Feishin',
|
{ role: 'unhide' },
|
||||||
selector: 'hide:',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accelerator: 'Command+Shift+H',
|
|
||||||
label: 'Hide Others',
|
|
||||||
selector: 'hideOtherApplications:',
|
|
||||||
},
|
|
||||||
{ label: 'Show All', selector: 'unhideAllApplications:' },
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{ role: 'quit' },
|
||||||
accelerator: 'Command+Q',
|
|
||||||
click: () => {
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
label: 'Quit',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const subMenuEdit: DarwinMenuItemConstructorOptions = {
|
const subMenuFile: MenuItemConstructorOptions = {
|
||||||
label: 'Edit',
|
label: 'File',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ accelerator: 'Command+Z', label: 'Undo', selector: 'undo:' },
|
|
||||||
{ accelerator: 'Shift+Command+Z', label: 'Redo', selector: 'redo:' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ accelerator: 'Command+X', label: 'Cut', selector: 'cut:' },
|
|
||||||
{ accelerator: 'Command+C', label: 'Copy', selector: 'copy:' },
|
|
||||||
{ accelerator: 'Command+V', label: 'Paste', selector: 'paste:' },
|
|
||||||
{
|
{
|
||||||
accelerator: 'Command+A',
|
click: () => {
|
||||||
label: 'Select All',
|
this.mainWindow.webContents.send('renderer-open-create-playlist');
|
||||||
selector: 'selectAll:',
|
},
|
||||||
|
label: 'Create Playlist...',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const subMenuViewDev: MenuItemConstructorOptions = {
|
const subMenuEdit: MenuItemConstructorOptions = { role: 'editMenu' };
|
||||||
|
const subMenuView: MenuItemConstructorOptions = {
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
accelerator: 'Command+K',
|
accelerator: accelerators?.globalSearch,
|
||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-open-command-palette');
|
this.mainWindow.webContents.send('renderer-open-command-palette');
|
||||||
},
|
},
|
||||||
@@ -133,83 +159,47 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-toggle-sidebar');
|
this.mainWindow.webContents.send('renderer-toggle-sidebar');
|
||||||
},
|
},
|
||||||
label: 'Collapse sidebar',
|
id: MENU_ITEM_IDS.sidebarCollapsed,
|
||||||
|
label: 'Collapse Sidebar',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{ role: 'togglefullscreen' },
|
||||||
{
|
{
|
||||||
accelerator: 'Command+R',
|
label: 'Developer',
|
||||||
click: () => {
|
submenu: [{ role: 'reload' }, { role: 'toggleDevTools' }],
|
||||||
this.mainWindow.webContents.reload();
|
|
||||||
},
|
|
||||||
label: 'Reload',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accelerator: 'Ctrl+Command+F',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
|
||||||
},
|
|
||||||
label: 'Toggle Full Screen',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accelerator: 'Alt+Command+I',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.toggleDevTools();
|
|
||||||
},
|
|
||||||
label: 'Toggle Developer Tools',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const subMenuViewProd: MenuItemConstructorOptions = {
|
const subMenuWindow: MenuItemConstructorOptions = { role: 'windowMenu' };
|
||||||
label: 'View',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
accelerator: 'Command+K',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.send('renderer-open-command-palette');
|
|
||||||
},
|
|
||||||
label: 'Command Palette...',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
checked: sidebarCollapsed,
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.send('renderer-toggle-sidebar');
|
|
||||||
},
|
|
||||||
label: 'Collapse sidebar',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
|
||||||
{
|
|
||||||
accelerator: 'Ctrl+Command+F',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
|
||||||
},
|
|
||||||
label: 'Toggle Full Screen',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const subMenuWindow: DarwinMenuItemConstructorOptions = {
|
|
||||||
label: 'Window',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
accelerator: 'Command+M',
|
|
||||||
label: 'Minimize',
|
|
||||||
selector: 'performMiniaturize:',
|
|
||||||
},
|
|
||||||
{ accelerator: 'Command+W', label: 'Close', selector: 'performClose:' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Bring All to Front', selector: 'arrangeInFront:' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const subMenuPlayback: MenuItemConstructorOptions = {
|
const subMenuPlayback: MenuItemConstructorOptions = {
|
||||||
label: 'Playback',
|
label: 'Playback',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
accelerator: accelerators?.playPause,
|
accelerator: accelerators?.play || accelerators?.playPause,
|
||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-play-pause');
|
this.mainWindow.webContents.send('renderer-player-play');
|
||||||
},
|
},
|
||||||
label: isPlaying ? 'Pause' : 'Play',
|
enabled: isPlaybackItemEnabled(
|
||||||
|
inputFocused,
|
||||||
|
accelerators?.play || accelerators?.playPause,
|
||||||
|
),
|
||||||
|
id: MENU_ITEM_IDS.play,
|
||||||
|
label: 'Play',
|
||||||
|
visible: !isPlaying,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.pause || accelerators?.playPause,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-pause');
|
||||||
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(
|
||||||
|
inputFocused,
|
||||||
|
accelerators?.pause || accelerators?.playPause,
|
||||||
|
),
|
||||||
|
id: MENU_ITEM_IDS.pause,
|
||||||
|
label: 'Pause',
|
||||||
|
visible: isPlaying,
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
@@ -217,6 +207,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-next');
|
this.mainWindow.webContents.send('renderer-player-next');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.next),
|
||||||
|
id: MENU_ITEM_IDS.next,
|
||||||
label: 'Next',
|
label: 'Next',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -224,6 +216,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-previous');
|
this.mainWindow.webContents.send('renderer-player-previous');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.previous),
|
||||||
|
id: MENU_ITEM_IDS.previous,
|
||||||
label: 'Previous',
|
label: 'Previous',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -231,6 +225,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-skip-forward');
|
this.mainWindow.webContents.send('renderer-player-skip-forward');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.seekForward),
|
||||||
|
id: MENU_ITEM_IDS.seekForward,
|
||||||
label: 'Seek Forward',
|
label: 'Seek Forward',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -238,6 +234,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-skip-backward');
|
this.mainWindow.webContents.send('renderer-player-skip-backward');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.seekBackward),
|
||||||
|
id: MENU_ITEM_IDS.seekBackward,
|
||||||
label: 'Seek Backforward',
|
label: 'Seek Backforward',
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -247,6 +245,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-toggle-shuffle');
|
this.mainWindow.webContents.send('renderer-player-toggle-shuffle');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.shuffle),
|
||||||
|
id: MENU_ITEM_IDS.shuffle,
|
||||||
label: 'Shuffle',
|
label: 'Shuffle',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
@@ -256,6 +256,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-toggle-repeat');
|
this.mainWindow.webContents.send('renderer-player-toggle-repeat');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.repeat),
|
||||||
|
id: MENU_ITEM_IDS.repeat,
|
||||||
label: 'Repeat',
|
label: 'Repeat',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
@@ -265,6 +267,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-stop');
|
this.mainWindow.webContents.send('renderer-player-stop');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.stop),
|
||||||
|
id: MENU_ITEM_IDS.stop,
|
||||||
label: 'Stop',
|
label: 'Stop',
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -273,6 +277,8 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-volume-up');
|
this.mainWindow.webContents.send('renderer-player-volume-up');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.volumeUp),
|
||||||
|
id: MENU_ITEM_IDS.volumeUp,
|
||||||
label: 'Volume Up',
|
label: 'Volume Up',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -280,12 +286,14 @@ export default class MenuBuilder {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.mainWindow.webContents.send('renderer-player-volume-down');
|
this.mainWindow.webContents.send('renderer-player-volume-down');
|
||||||
},
|
},
|
||||||
|
enabled: isPlaybackItemEnabled(inputFocused, accelerators?.volumeDown),
|
||||||
|
id: MENU_ITEM_IDS.volumeDown,
|
||||||
label: 'Volume Down',
|
label: 'Volume Down',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const subMenuHelp: MenuItemConstructorOptions = {
|
const subMenuHelp: MenuItemConstructorOptions = {
|
||||||
label: 'Help',
|
role: 'help',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
click() {
|
click() {
|
||||||
@@ -323,13 +331,9 @@ export default class MenuBuilder {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const subMenuView =
|
|
||||||
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'
|
|
||||||
? subMenuViewDev
|
|
||||||
: subMenuViewProd;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
subMenuAbout,
|
subMenuAbout,
|
||||||
|
subMenuFile,
|
||||||
subMenuEdit,
|
subMenuEdit,
|
||||||
subMenuView,
|
subMenuView,
|
||||||
subMenuPlayback,
|
subMenuPlayback,
|
||||||
@@ -452,6 +456,7 @@ export default class MenuBuilder {
|
|||||||
: this.buildDefaultTemplate();
|
: this.buildDefaultTemplate();
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate(template);
|
const menu = Menu.buildFromTemplate(template);
|
||||||
|
this.applicationMenu = menu;
|
||||||
Menu.setApplicationMenu(menu);
|
Menu.setApplicationMenu(menu);
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
@@ -478,4 +483,51 @@ export default class MenuBuilder {
|
|||||||
]).popup({ window: this.mainWindow });
|
]).popup({ window: this.mainWindow });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateMenu({
|
||||||
|
accelerators,
|
||||||
|
inputFocused = false,
|
||||||
|
playbackStatus = PlayerStatus.PAUSED,
|
||||||
|
privateMode = false,
|
||||||
|
repeatMode = PlayerRepeat.NONE,
|
||||||
|
shuffleEnabled = false,
|
||||||
|
sidebarCollapsed = false,
|
||||||
|
}: MenuPlaybackState = {}): void {
|
||||||
|
if (process.platform !== 'darwin' || !this.applicationMenu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateModeItem = this.applicationMenu.getMenuItemById(MENU_ITEM_IDS.privateMode);
|
||||||
|
const sidebarItem = this.applicationMenu.getMenuItemById(MENU_ITEM_IDS.sidebarCollapsed);
|
||||||
|
const pauseItem = this.applicationMenu.getMenuItemById(MENU_ITEM_IDS.pause);
|
||||||
|
const playItem = this.applicationMenu.getMenuItemById(MENU_ITEM_IDS.play);
|
||||||
|
const repeatItem = this.applicationMenu.getMenuItemById(MENU_ITEM_IDS.repeat);
|
||||||
|
const shuffleItem = this.applicationMenu.getMenuItemById(MENU_ITEM_IDS.shuffle);
|
||||||
|
|
||||||
|
if (privateModeItem) privateModeItem.checked = privateMode;
|
||||||
|
if (sidebarItem) sidebarItem.checked = sidebarCollapsed;
|
||||||
|
if (pauseItem) pauseItem.visible = playbackStatus === PlayerStatus.PLAYING;
|
||||||
|
if (playItem) playItem.visible = playbackStatus !== PlayerStatus.PLAYING;
|
||||||
|
if (repeatItem) repeatItem.checked = repeatMode !== PlayerRepeat.NONE;
|
||||||
|
if (shuffleItem) shuffleItem.checked = shuffleEnabled;
|
||||||
|
|
||||||
|
const playbackAccelerators = [
|
||||||
|
[MENU_ITEM_IDS.play, accelerators?.play || accelerators?.playPause],
|
||||||
|
[MENU_ITEM_IDS.pause, accelerators?.pause || accelerators?.playPause],
|
||||||
|
[MENU_ITEM_IDS.next, accelerators?.next],
|
||||||
|
[MENU_ITEM_IDS.previous, accelerators?.previous],
|
||||||
|
[MENU_ITEM_IDS.seekForward, accelerators?.seekForward],
|
||||||
|
[MENU_ITEM_IDS.seekBackward, accelerators?.seekBackward],
|
||||||
|
[MENU_ITEM_IDS.shuffle, accelerators?.shuffle],
|
||||||
|
[MENU_ITEM_IDS.repeat, accelerators?.repeat],
|
||||||
|
[MENU_ITEM_IDS.stop, accelerators?.stop],
|
||||||
|
[MENU_ITEM_IDS.volumeUp, accelerators?.volumeUp],
|
||||||
|
[MENU_ITEM_IDS.volumeDown, accelerators?.volumeDown],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
for (const [id, accelerator] of playbackAccelerators) {
|
||||||
|
const item = this.applicationMenu.getMenuItemById(id);
|
||||||
|
if (item) item.enabled = isPlaybackItemEnabled(inputFocused, accelerator);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,10 @@ const rendererOpenManageServers = (cb: () => void) => {
|
|||||||
ipcRenderer.on('renderer-open-manage-servers', () => cb());
|
ipcRenderer.on('renderer-open-manage-servers', () => cb());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rendererOpenCreatePlaylist = (cb: () => void) => {
|
||||||
|
ipcRenderer.on('renderer-open-create-playlist', () => cb());
|
||||||
|
};
|
||||||
|
|
||||||
const rendererTogglePrivateMode = (cb: () => void) => {
|
const rendererTogglePrivateMode = (cb: () => void) => {
|
||||||
ipcRenderer.on('renderer-toggle-private-mode', cb);
|
ipcRenderer.on('renderer-toggle-private-mode', cb);
|
||||||
};
|
};
|
||||||
@@ -175,6 +179,7 @@ export const utils = {
|
|||||||
readLocalImage,
|
readLocalImage,
|
||||||
readSongMetadataBatch,
|
readSongMetadataBatch,
|
||||||
rendererOpenCommandPalette,
|
rendererOpenCommandPalette,
|
||||||
|
rendererOpenCreatePlaylist,
|
||||||
rendererOpenManageServers,
|
rendererOpenManageServers,
|
||||||
rendererOpenReleaseNotes,
|
rendererOpenReleaseNotes,
|
||||||
rendererOpenSettings,
|
rendererOpenSettings,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import packageJson from '../../../package.json';
|
import packageJson from '../../../package.json';
|
||||||
|
|
||||||
|
import { openCreatePlaylistModal } from '/@/renderer/features/playlists/components/create-playlist-form';
|
||||||
import { ServerList } from '/@/renderer/features/servers/components/server-list';
|
import { ServerList } from '/@/renderer/features/servers/components/server-list';
|
||||||
import { openSettingsModal } from '/@/renderer/features/settings/utils/open-settings-modal';
|
import { openSettingsModal } from '/@/renderer/features/settings/utils/open-settings-modal';
|
||||||
import { openReleaseNotesModal } from '/@/renderer/release-notes-modal';
|
import { openReleaseNotesModal } from '/@/renderer/release-notes-modal';
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
useAppStore,
|
useAppStore,
|
||||||
useAppStoreActions,
|
useAppStoreActions,
|
||||||
useCommandPalette,
|
useCommandPalette,
|
||||||
|
useCurrentServer,
|
||||||
usePlayerHydrated,
|
usePlayerHydrated,
|
||||||
usePlayerRepeat,
|
usePlayerRepeat,
|
||||||
usePlayerShuffle,
|
usePlayerShuffle,
|
||||||
@@ -27,6 +29,7 @@ export const useNativeMenuSync = () => {
|
|||||||
const sidebar = useAppStore((state) => state.sidebar);
|
const sidebar = useAppStore((state) => state.sidebar);
|
||||||
const { setPrivateMode, setSideBar } = useAppStoreActions();
|
const { setPrivateMode, setSideBar } = useAppStoreActions();
|
||||||
const { open: openCommandPalette } = useCommandPalette();
|
const { open: openCommandPalette } = useCommandPalette();
|
||||||
|
const server = useCurrentServer();
|
||||||
const playerHydrated = usePlayerHydrated();
|
const playerHydrated = usePlayerHydrated();
|
||||||
const playerRepeat = usePlayerRepeat();
|
const playerRepeat = usePlayerRepeat();
|
||||||
const playerShuffle = usePlayerShuffle();
|
const playerShuffle = usePlayerShuffle();
|
||||||
@@ -60,6 +63,20 @@ export const useNativeMenuSync = () => {
|
|||||||
};
|
};
|
||||||
}, [openCommandPalette]);
|
}, [openCommandPalette]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.utils.rendererOpenCreatePlaylist(() => {
|
||||||
|
openCreatePlaylistModal(server);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc?.removeAllListeners('renderer-open-create-playlist');
|
||||||
|
};
|
||||||
|
}, [server]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron()) {
|
if (!isElectron()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
Reference in New Issue
Block a user