mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
feat: macOS menu enhancement (#1903)
This commit is contained in:
+99
-8
@@ -29,7 +29,7 @@ import packageJson from '../../package.json';
|
|||||||
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
|
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
|
||||||
import { shutdownServer } from './features/core/remote';
|
import { shutdownServer } from './features/core/remote';
|
||||||
import { store } from './features/core/settings';
|
import { store } from './features/core/settings';
|
||||||
import MenuBuilder from './menu';
|
import MenuBuilder, { MenuPlaybackState } from './menu';
|
||||||
import {
|
import {
|
||||||
autoUpdaterLogInterface,
|
autoUpdaterLogInterface,
|
||||||
createLog,
|
createLog,
|
||||||
@@ -41,7 +41,7 @@ import {
|
|||||||
} from './utils';
|
} from './utils';
|
||||||
import './features';
|
import './features';
|
||||||
|
|
||||||
import { PlayerType, TitleTheme } from '/@/shared/types/types';
|
import { PlayerRepeat, PlayerStatus, PlayerType, TitleTheme } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ALPHA_UPDATER_CONFIG: {
|
const ALPHA_UPDATER_CONFIG: {
|
||||||
bucket: string;
|
bucket: string;
|
||||||
@@ -277,6 +277,13 @@ let tray: null | Tray = null;
|
|||||||
let exitFromTray = false;
|
let exitFromTray = false;
|
||||||
let forceQuit = false;
|
let forceQuit = false;
|
||||||
let powerSaveBlockerId: null | number = null;
|
let powerSaveBlockerId: null | number = null;
|
||||||
|
let menuBuilder: MenuBuilder | null = null;
|
||||||
|
let currentPlaybackStatus: PlayerStatus = PlayerStatus.PAUSED;
|
||||||
|
let currentPrivateMode = false;
|
||||||
|
let currentRepeatMode: PlayerRepeat = PlayerRepeat.NONE;
|
||||||
|
let currentSidebarCollapsed = false;
|
||||||
|
let currentShuffleEnabled = false;
|
||||||
|
let playbackMenuAccelerators: MenuPlaybackState['accelerators'] = {};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
import('source-map-support').then((sourceMapSupport) => {
|
import('source-map-support').then((sourceMapSupport) => {
|
||||||
@@ -333,6 +340,23 @@ export const getMainWindow = () => {
|
|||||||
return mainWindow;
|
return mainWindow;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rebuildMainMenu = () => {
|
||||||
|
if (!menuBuilder || !mainWindow) return;
|
||||||
|
|
||||||
|
menuBuilder.buildMenu({
|
||||||
|
accelerators: playbackMenuAccelerators,
|
||||||
|
playbackStatus: currentPlaybackStatus,
|
||||||
|
privateMode: currentPrivateMode,
|
||||||
|
repeatMode: currentRepeatMode,
|
||||||
|
shuffleEnabled: currentShuffleEnabled,
|
||||||
|
sidebarCollapsed: currentSidebarCollapsed,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
Menu.setApplicationMenu(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const sendToastToRenderer = ({
|
export const sendToastToRenderer = ({
|
||||||
message,
|
message,
|
||||||
type,
|
type,
|
||||||
@@ -699,12 +723,8 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuBuilder = new MenuBuilder(mainWindow);
|
menuBuilder = new MenuBuilder(mainWindow);
|
||||||
menuBuilder.buildMenu();
|
rebuildMainMenu();
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
Menu.setApplicationMenu(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open URLs in the user's browser
|
// Open URLs in the user's browser
|
||||||
mainWindow.webContents.setWindowOpenHandler((edata) => {
|
mainWindow.webContents.setWindowOpenHandler((edata) => {
|
||||||
@@ -782,6 +802,17 @@ enum BindingActions {
|
|||||||
VOLUME_UP = 'volumeUp',
|
VOLUME_UP = 'volumeUp',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMenuAccelerator = (
|
||||||
|
data: Record<BindingActions, { allowGlobal: boolean; hotkey: string; isGlobal: boolean }>,
|
||||||
|
action: BindingActions,
|
||||||
|
) => {
|
||||||
|
const hotkey = data[action]?.hotkey;
|
||||||
|
|
||||||
|
if (!hotkey) return undefined;
|
||||||
|
|
||||||
|
return hotkeyToElectronAccelerator(hotkey);
|
||||||
|
};
|
||||||
|
|
||||||
const HOTKEY_ACTIONS: Record<BindingActions, () => void> = {
|
const HOTKEY_ACTIONS: Record<BindingActions, () => void> = {
|
||||||
[BindingActions.GLOBAL_SEARCH]: () => {},
|
[BindingActions.GLOBAL_SEARCH]: () => {},
|
||||||
[BindingActions.LOCAL_SEARCH]: () => {},
|
[BindingActions.LOCAL_SEARCH]: () => {},
|
||||||
@@ -835,6 +866,26 @@ ipcMain.on(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playbackMenuAccelerators = {
|
||||||
|
next: getMenuAccelerator(data, BindingActions.NEXT),
|
||||||
|
playPause:
|
||||||
|
getMenuAccelerator(data, BindingActions.PLAY_PAUSE) ||
|
||||||
|
getMenuAccelerator(data, BindingActions.PLAY) ||
|
||||||
|
getMenuAccelerator(data, BindingActions.PAUSE),
|
||||||
|
previous: getMenuAccelerator(data, BindingActions.PREVIOUS),
|
||||||
|
repeat: getMenuAccelerator(data, BindingActions.TOGGLE_REPEAT),
|
||||||
|
seekBackward: getMenuAccelerator(data, BindingActions.SKIP_BACKWARD),
|
||||||
|
seekForward: getMenuAccelerator(data, BindingActions.SKIP_FORWARD),
|
||||||
|
shuffle: getMenuAccelerator(data, BindingActions.SHUFFLE),
|
||||||
|
stop: getMenuAccelerator(data, BindingActions.STOP),
|
||||||
|
volumeDown: getMenuAccelerator(data, BindingActions.VOLUME_DOWN),
|
||||||
|
volumeUp: getMenuAccelerator(data, BindingActions.VOLUME_UP),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isMacOS()) {
|
||||||
|
rebuildMainMenu();
|
||||||
|
}
|
||||||
|
|
||||||
const globalMediaKeysEnabled = store.get('global_media_hotkeys', true) as boolean;
|
const globalMediaKeysEnabled = store.get('global_media_hotkeys', true) as boolean;
|
||||||
|
|
||||||
if (globalMediaKeysEnabled) {
|
if (globalMediaKeysEnabled) {
|
||||||
@@ -975,3 +1026,43 @@ if (!ipcMain.eventNames().includes('open-application-directory')) {
|
|||||||
shell.openPath(userDataPath);
|
shell.openPath(userDataPath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.on('update-playback', (_event, status: PlayerStatus) => {
|
||||||
|
currentPlaybackStatus = status;
|
||||||
|
|
||||||
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
|
rebuildMainMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('update-repeat', (_event, repeat: PlayerRepeat) => {
|
||||||
|
currentRepeatMode = repeat;
|
||||||
|
|
||||||
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
|
rebuildMainMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
||||||
|
currentShuffleEnabled = shuffle;
|
||||||
|
|
||||||
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
|
rebuildMainMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('update-private-mode', (_event, privateMode: boolean) => {
|
||||||
|
currentPrivateMode = privateMode;
|
||||||
|
|
||||||
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
|
rebuildMainMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('update-sidebar-collapsed', (_event, collapsedSidebar: boolean) => {
|
||||||
|
currentSidebarCollapsed = collapsedSidebar;
|
||||||
|
|
||||||
|
if (!isMacOS()) return;
|
||||||
|
|
||||||
|
rebuildMainMenu();
|
||||||
|
});
|
||||||
|
|||||||
+190
-4
@@ -1,18 +1,53 @@
|
|||||||
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, shell } from 'electron';
|
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, shell } from 'electron';
|
||||||
|
|
||||||
|
import packageJson from '../../package.json';
|
||||||
|
|
||||||
|
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
export type MenuPlaybackState = {
|
||||||
|
accelerators?: {
|
||||||
|
next?: string;
|
||||||
|
playPause?: string;
|
||||||
|
previous?: string;
|
||||||
|
repeat?: string;
|
||||||
|
seekBackward?: string;
|
||||||
|
seekForward?: string;
|
||||||
|
shuffle?: string;
|
||||||
|
stop?: string;
|
||||||
|
volumeDown?: string;
|
||||||
|
volumeUp?: string;
|
||||||
|
};
|
||||||
|
playbackStatus?: PlayerStatus;
|
||||||
|
privateMode?: boolean;
|
||||||
|
repeatMode?: PlayerRepeat;
|
||||||
|
shuffleEnabled?: boolean;
|
||||||
|
sidebarCollapsed?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
|
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
|
||||||
selector?: string;
|
selector?: string;
|
||||||
submenu?: DarwinMenuItemConstructorOptions[] | Menu;
|
submenu?: DarwinMenuItemConstructorOptions[] | Menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MenuBuilder {
|
export default class MenuBuilder {
|
||||||
|
developmentEnvironmentSetup = false;
|
||||||
mainWindow: BrowserWindow;
|
mainWindow: BrowserWindow;
|
||||||
|
|
||||||
constructor(mainWindow: BrowserWindow) {
|
constructor(mainWindow: BrowserWindow) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDarwinTemplate(): MenuItemConstructorOptions[] {
|
buildDarwinTemplate({
|
||||||
|
accelerators,
|
||||||
|
playbackStatus = PlayerStatus.PAUSED,
|
||||||
|
privateMode = false,
|
||||||
|
repeatMode = PlayerRepeat.NONE,
|
||||||
|
shuffleEnabled = false,
|
||||||
|
sidebarCollapsed = false,
|
||||||
|
}: MenuPlaybackState = {}): MenuItemConstructorOptions[] {
|
||||||
|
const isPlaying = playbackStatus === PlayerStatus.PLAYING;
|
||||||
|
const isRepeatEnabled = repeatMode !== PlayerRepeat.NONE;
|
||||||
|
|
||||||
const subMenuAbout: DarwinMenuItemConstructorOptions = {
|
const subMenuAbout: DarwinMenuItemConstructorOptions = {
|
||||||
label: 'Electron',
|
label: 'Electron',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -29,6 +64,21 @@ export default class MenuBuilder {
|
|||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-open-manage-servers');
|
||||||
|
},
|
||||||
|
label: 'Manage servers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
checked: privateMode,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-toggle-private-mode');
|
||||||
|
},
|
||||||
|
label: 'Private session',
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
{ label: 'Services', submenu: [] },
|
{ label: 'Services', submenu: [] },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
@@ -71,6 +121,22 @@ export default class MenuBuilder {
|
|||||||
const subMenuViewDev: MenuItemConstructorOptions = {
|
const subMenuViewDev: MenuItemConstructorOptions = {
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: [
|
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: 'Command+R',
|
accelerator: 'Command+R',
|
||||||
click: () => {
|
click: () => {
|
||||||
@@ -97,6 +163,22 @@ export default class MenuBuilder {
|
|||||||
const subMenuViewProd: MenuItemConstructorOptions = {
|
const subMenuViewProd: MenuItemConstructorOptions = {
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: [
|
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',
|
accelerator: 'Ctrl+Command+F',
|
||||||
click: () => {
|
click: () => {
|
||||||
@@ -119,6 +201,89 @@ export default class MenuBuilder {
|
|||||||
{ label: 'Bring All to Front', selector: 'arrangeInFront:' },
|
{ label: 'Bring All to Front', selector: 'arrangeInFront:' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
const subMenuPlayback: MenuItemConstructorOptions = {
|
||||||
|
label: 'Playback',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.playPause,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-play-pause');
|
||||||
|
},
|
||||||
|
label: isPlaying ? 'Pause' : 'Play',
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.next,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-next');
|
||||||
|
},
|
||||||
|
label: 'Next',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.previous,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-previous');
|
||||||
|
},
|
||||||
|
label: 'Previous',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.seekForward,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-skip-forward');
|
||||||
|
},
|
||||||
|
label: 'Seek Forward',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.seekBackward,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-skip-backward');
|
||||||
|
},
|
||||||
|
label: 'Seek Backforward',
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.shuffle,
|
||||||
|
checked: shuffleEnabled,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-toggle-shuffle');
|
||||||
|
},
|
||||||
|
label: 'Shuffle',
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.repeat,
|
||||||
|
checked: isRepeatEnabled,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-toggle-repeat');
|
||||||
|
},
|
||||||
|
label: 'Repeat',
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.stop,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-stop');
|
||||||
|
},
|
||||||
|
label: 'Stop',
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.volumeUp,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-volume-up');
|
||||||
|
},
|
||||||
|
label: 'Volume Up',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accelerator: accelerators?.volumeDown,
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-player-volume-down');
|
||||||
|
},
|
||||||
|
label: 'Volume Down',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
const subMenuHelp: MenuItemConstructorOptions = {
|
const subMenuHelp: MenuItemConstructorOptions = {
|
||||||
label: 'Help',
|
label: 'Help',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -148,6 +313,13 @@ export default class MenuBuilder {
|
|||||||
},
|
},
|
||||||
label: 'Search Issues',
|
label: 'Search Issues',
|
||||||
},
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
click: () => {
|
||||||
|
this.mainWindow.webContents.send('renderer-open-release-notes');
|
||||||
|
},
|
||||||
|
label: 'Version ' + packageJson.version,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,7 +328,14 @@ export default class MenuBuilder {
|
|||||||
? subMenuViewDev
|
? subMenuViewDev
|
||||||
: subMenuViewProd;
|
: subMenuViewProd;
|
||||||
|
|
||||||
return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
|
return [
|
||||||
|
subMenuAbout,
|
||||||
|
subMenuEdit,
|
||||||
|
subMenuView,
|
||||||
|
subMenuPlayback,
|
||||||
|
subMenuWindow,
|
||||||
|
subMenuHelp,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDefaultTemplate(): MenuItemConstructorOptions[] {
|
buildDefaultTemplate(): MenuItemConstructorOptions[] {
|
||||||
@@ -262,14 +441,14 @@ export default class MenuBuilder {
|
|||||||
return templateDefault;
|
return templateDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildMenu(): Menu {
|
buildMenu(playbackState: MenuPlaybackState = {}): Menu {
|
||||||
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
|
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
|
||||||
this.setupDevelopmentEnvironment();
|
this.setupDevelopmentEnvironment();
|
||||||
}
|
}
|
||||||
|
|
||||||
const template =
|
const template =
|
||||||
process.platform === 'darwin'
|
process.platform === 'darwin'
|
||||||
? this.buildDarwinTemplate()
|
? this.buildDarwinTemplate(playbackState)
|
||||||
: this.buildDefaultTemplate();
|
: this.buildDefaultTemplate();
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate(template);
|
const menu = Menu.buildFromTemplate(template);
|
||||||
@@ -279,6 +458,13 @@ export default class MenuBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupDevelopmentEnvironment(): void {
|
setupDevelopmentEnvironment(): void {
|
||||||
|
// buildMenu can run multiple times as menu state updates; attach this once.
|
||||||
|
if (this.developmentEnvironmentSetup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.developmentEnvironmentSetup = true;
|
||||||
|
|
||||||
this.mainWindow.webContents.on('context-menu', (_, props) => {
|
this.mainWindow.webContents.on('context-menu', (_, props) => {
|
||||||
const { x, y } = props;
|
const { x, y } = props;
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,26 @@ const rendererOpenSettings = (cb: (event: IpcRendererEvent) => void) => {
|
|||||||
ipcRenderer.on('renderer-open-settings', cb);
|
ipcRenderer.on('renderer-open-settings', cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rendererOpenCommandPalette = (cb: (event: IpcRendererEvent) => void) => {
|
||||||
|
ipcRenderer.on('renderer-open-command-palette', cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rendererOpenManageServers = (cb: (event: IpcRendererEvent) => void) => {
|
||||||
|
ipcRenderer.on('renderer-open-manage-servers', cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rendererTogglePrivateMode = (cb: (event: IpcRendererEvent) => void) => {
|
||||||
|
ipcRenderer.on('renderer-toggle-private-mode', cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rendererToggleSidebar = (cb: (event: IpcRendererEvent) => void) => {
|
||||||
|
ipcRenderer.on('renderer-toggle-sidebar', cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rendererOpenReleaseNotes = (cb: (event: IpcRendererEvent) => void) => {
|
||||||
|
ipcRenderer.on('renderer-open-release-notes', cb);
|
||||||
|
};
|
||||||
|
|
||||||
export const utils = {
|
export const utils = {
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
disableAutoUpdates,
|
disableAutoUpdates,
|
||||||
@@ -78,7 +98,12 @@ export const utils = {
|
|||||||
openApplicationDirectory,
|
openApplicationDirectory,
|
||||||
openItem,
|
openItem,
|
||||||
playerErrorListener,
|
playerErrorListener,
|
||||||
|
rendererOpenCommandPalette,
|
||||||
|
rendererOpenManageServers,
|
||||||
|
rendererOpenReleaseNotes,
|
||||||
rendererOpenSettings,
|
rendererOpenSettings,
|
||||||
|
rendererTogglePrivateMode,
|
||||||
|
rendererToggleSidebar,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Utils = typeof utils;
|
export type Utils = typeof utils;
|
||||||
|
|||||||
+4
-16
@@ -10,9 +10,9 @@ import isElectron from 'is-electron';
|
|||||||
import { lazy, memo, Suspense, useEffect, useMemo, useRef, useState } from 'react';
|
import { lazy, memo, Suspense, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { openSettingsModal } from '/@/renderer/features/settings/utils/open-settings-modal';
|
|
||||||
import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-context';
|
import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-context';
|
||||||
import { useCheckForUpdates } from '/@/renderer/hooks/use-check-for-updates';
|
import { useCheckForUpdates } from '/@/renderer/hooks/use-check-for-updates';
|
||||||
|
import { useNativeMenuSync } from '/@/renderer/hooks/use-native-menu-sync';
|
||||||
import { useSyncSettingsToMain } from '/@/renderer/hooks/use-sync-settings-to-main';
|
import { useSyncSettingsToMain } from '/@/renderer/hooks/use-sync-settings-to-main';
|
||||||
import { AppRouter } from '/@/renderer/router/app-router';
|
import { AppRouter } from '/@/renderer/router/app-router';
|
||||||
import { useCssSettings, useHotkeySettings, useLanguage } from '/@/renderer/store';
|
import { useCssSettings, useHotkeySettings, useLanguage } from '/@/renderer/store';
|
||||||
@@ -97,7 +97,7 @@ const AppEffects = () => (
|
|||||||
<CssSettingsEffect />
|
<CssSettingsEffect />
|
||||||
<GlobalShortcutsEffect />
|
<GlobalShortcutsEffect />
|
||||||
<LanguageEffect />
|
<LanguageEffect />
|
||||||
<OpenSettingsEffect />
|
<NativeMenuSyncEffect />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -170,20 +170,8 @@ const LanguageEffect = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const OpenSettingsEffect = () => {
|
const NativeMenuSyncEffect = () => {
|
||||||
useEffect(() => {
|
useNativeMenuSync();
|
||||||
if (isElectron()) {
|
|
||||||
window.api.utils.rendererOpenSettings(() => {
|
|
||||||
openSettingsModal();
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
ipc?.removeAllListeners('renderer-open-settings');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { openModal } from '@mantine/modals';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import packageJson from '../../../package.json';
|
||||||
|
|
||||||
|
import { ServerList } from '/@/renderer/features/servers/components/server-list';
|
||||||
|
import { openSettingsModal } from '/@/renderer/features/settings/utils/open-settings-modal';
|
||||||
|
import { openReleaseNotesModal } from '/@/renderer/release-notes-modal';
|
||||||
|
import {
|
||||||
|
useAppStore,
|
||||||
|
useAppStoreActions,
|
||||||
|
useCommandPalette,
|
||||||
|
usePlayerHydrated,
|
||||||
|
usePlayerRepeat,
|
||||||
|
usePlayerShuffle,
|
||||||
|
usePlayerStatus,
|
||||||
|
} from '/@/renderer/store';
|
||||||
|
import { PlayerShuffle } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
const ipc = isElectron() ? window.api.ipc : null;
|
||||||
|
|
||||||
|
export const useNativeMenuSync = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const privateMode = useAppStore((state) => state.privateMode);
|
||||||
|
const sidebar = useAppStore((state) => state.sidebar);
|
||||||
|
const { setPrivateMode, setSideBar } = useAppStoreActions();
|
||||||
|
const { open: openCommandPalette } = useCommandPalette();
|
||||||
|
const playerHydrated = usePlayerHydrated();
|
||||||
|
const playerRepeat = usePlayerRepeat();
|
||||||
|
const playerShuffle = usePlayerShuffle();
|
||||||
|
const playerStatus = usePlayerStatus();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.utils.rendererOpenSettings(() => {
|
||||||
|
openSettingsModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc?.removeAllListeners('renderer-open-settings');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.utils.rendererOpenCommandPalette(() => {
|
||||||
|
openCommandPalette();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc?.removeAllListeners('renderer-open-command-palette');
|
||||||
|
};
|
||||||
|
}, [openCommandPalette]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.utils.rendererOpenManageServers(() => {
|
||||||
|
openModal({
|
||||||
|
children: <ServerList />,
|
||||||
|
title: t('page.manageServers.title', { postProcess: 'titleCase' }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc?.removeAllListeners('renderer-open-manage-servers');
|
||||||
|
};
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.utils.rendererTogglePrivateMode(() => {
|
||||||
|
setPrivateMode(!privateMode);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc?.removeAllListeners('renderer-toggle-private-mode');
|
||||||
|
};
|
||||||
|
}, [privateMode, setPrivateMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.utils.rendererToggleSidebar(() => {
|
||||||
|
setSideBar({ collapsed: !sidebar.collapsed });
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc?.removeAllListeners('renderer-toggle-sidebar');
|
||||||
|
};
|
||||||
|
}, [setSideBar, sidebar.collapsed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerHydrated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc?.send('update-playback', playerStatus);
|
||||||
|
ipc?.send('update-repeat', playerRepeat);
|
||||||
|
ipc?.send('update-shuffle', playerShuffle !== PlayerShuffle.NONE);
|
||||||
|
}, [playerHydrated, playerRepeat, playerShuffle, playerStatus]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc?.send('update-private-mode', privateMode);
|
||||||
|
}, [privateMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc?.send('update-sidebar-collapsed', sidebar.collapsed);
|
||||||
|
}, [sidebar.collapsed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isElectron()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.utils.rendererOpenReleaseNotes(() => {
|
||||||
|
openReleaseNotesModal(
|
||||||
|
t('common.newVersion', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
version: packageJson.version,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipc?.removeAllListeners('renderer-open-release-notes');
|
||||||
|
};
|
||||||
|
}, [t]);
|
||||||
|
};
|
||||||
@@ -92,6 +92,7 @@ interface GroupedQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
hydrated: boolean;
|
||||||
player: {
|
player: {
|
||||||
crossfadeDuration: number;
|
crossfadeDuration: number;
|
||||||
crossfadeStyle: CrossfadeStyle;
|
crossfadeStyle: CrossfadeStyle;
|
||||||
@@ -297,6 +298,7 @@ function regenerateShuffledIndexesIfNeeded(state: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
|
hydrated: false,
|
||||||
player: {
|
player: {
|
||||||
crossfadeDuration: 5,
|
crossfadeDuration: 5,
|
||||||
crossfadeStyle: CrossfadeStyle.EQUAL_POWER,
|
crossfadeStyle: CrossfadeStyle.EQUAL_POWER,
|
||||||
@@ -1564,6 +1566,9 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
return persistedState as Partial<PlayerState>;
|
return persistedState as Partial<PlayerState>;
|
||||||
},
|
},
|
||||||
name: 'player-store',
|
name: 'player-store',
|
||||||
|
onRehydrateStorage: () => () => {
|
||||||
|
usePlayerStoreBase.setState({ hydrated: true });
|
||||||
|
},
|
||||||
partialize: (state) => {
|
partialize: (state) => {
|
||||||
const shouldRestorePlayQueue = useSettingsStore.getState().general.resume;
|
const shouldRestorePlayQueue = useSettingsStore.getState().general.resume;
|
||||||
|
|
||||||
@@ -2025,6 +2030,10 @@ export const usePlayerStatus = () => {
|
|||||||
return usePlayerStoreBase((state) => state.player.status);
|
return usePlayerStoreBase((state) => state.player.status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const usePlayerHydrated = () => {
|
||||||
|
return usePlayerStoreBase((state) => state.hydrated);
|
||||||
|
};
|
||||||
|
|
||||||
export const usePlayerVolume = () => {
|
export const usePlayerVolume = () => {
|
||||||
return usePlayerStoreBase((state) => state.player.volume);
|
return usePlayerStoreBase((state) => state.player.volume);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user