diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx
index 275bcbe76..7e4547b60 100644
--- a/src/renderer/app.tsx
+++ b/src/renderer/app.tsx
@@ -7,7 +7,7 @@ import '@mantine/core/styles.css';
import '@mantine/dates/styles.css';
import '@mantine/notifications/styles.css';
import isElectron from 'is-electron';
-import { lazy, Suspense, useEffect, useMemo, useRef, useState } from 'react';
+import { lazy, memo, Suspense, useEffect, useMemo, useRef, useState } from 'react';
import i18n from '/@/i18n/i18n';
import { openSettingsModal } from '/@/renderer/features/settings/utils/open-settings-modal';
@@ -38,67 +38,26 @@ const UpdateAvailableDialog = lazy(() =>
const ipc = isElectron() ? window.api.ipc : null;
export const App = () => {
+ return ;
+};
+
+const ThemedApp = () => {
const { mode, theme } = useAppTheme();
- const language = useLanguage();
- const { content, enabled } = useCssSettings();
- const { bindings } = useHotkeySettings();
- const cssRef = useRef(null);
-
- useSyncSettingsToMain();
- useCheckForUpdates();
+ return (
+
+
+
+ );
+};
+const AppShell = memo(function AppShell() {
const [webAudio, setWebAudio] = useState();
- useEffect(() => {
- if (enabled && content) {
- // Yes, CSS is sanitized here as well. Prevent a suer from changing the
- // localStorage to bypass sanitizing.
- const sanitized = sanitizeCss(content);
- if (!cssRef.current) {
- cssRef.current = document.createElement('style');
- document.body.appendChild(cssRef.current);
- }
-
- cssRef.current.textContent = sanitized;
-
- return () => {
- cssRef.current!.textContent = '';
- };
- }
-
- return () => {};
- }, [content, enabled]);
-
const webAudioProvider = useMemo(() => {
return { setWebAudio, webAudio };
}, [webAudio]);
- useEffect(() => {
- if (isElectron()) {
- ipc?.send('set-global-shortcuts', bindings);
- }
- }, [bindings]);
-
- useEffect(() => {
- if (language) {
- i18n.changeLanguage(language);
- }
- }, [language]);
-
- useEffect(() => {
- if (isElectron()) {
- window.api.utils.rendererOpenSettings(() => {
- openSettingsModal();
- });
-
- return () => {
- ipc?.removeAllListeners('renderer-open-settings');
- };
- }
- return undefined;
- }, []);
-
const notificationStyles = useMemo(
() => ({
root: {
@@ -109,7 +68,8 @@ export const App = () => {
);
return (
-
+ <>
+
{
-
+ >
);
+});
+
+const AppEffects = () => (
+ <>
+
+
+
+
+
+
+ >
+);
+
+const SyncSettingsEffect = () => {
+ useSyncSettingsToMain();
+
+ return null;
+};
+
+const UpdateCheckEffect = () => {
+ useCheckForUpdates();
+
+ return null;
+};
+
+const CssSettingsEffect = () => {
+ const { content, enabled } = useCssSettings();
+ const cssRef = useRef(null);
+
+ useEffect(() => {
+ if (!enabled || !content) {
+ if (cssRef.current) {
+ cssRef.current.textContent = '';
+ }
+
+ return;
+ }
+
+ // Yes, CSS is sanitized here as well. Prevent a user from changing the
+ // localStorage to bypass sanitizing.
+ const sanitized = sanitizeCss(content);
+ if (!cssRef.current) {
+ cssRef.current = document.createElement('style');
+ document.body.appendChild(cssRef.current);
+ }
+
+ cssRef.current.textContent = sanitized;
+
+ return () => {
+ if (cssRef.current) {
+ cssRef.current.textContent = '';
+ }
+ };
+ }, [content, enabled]);
+
+ return null;
+};
+
+const GlobalShortcutsEffect = () => {
+ const { bindings } = useHotkeySettings();
+
+ useEffect(() => {
+ if (isElectron()) {
+ ipc?.send('set-global-shortcuts', bindings);
+ }
+ }, [bindings]);
+
+ return null;
+};
+
+const LanguageEffect = () => {
+ const language = useLanguage();
+
+ useEffect(() => {
+ if (language) {
+ i18n.changeLanguage(language);
+ }
+ }, [language]);
+
+ return null;
+};
+
+const OpenSettingsEffect = () => {
+ useEffect(() => {
+ if (isElectron()) {
+ window.api.utils.rendererOpenSettings(() => {
+ openSettingsModal();
+ });
+
+ return () => {
+ ipc?.removeAllListeners('renderer-open-settings');
+ };
+ }
+
+ return undefined;
+ }, []);
+
+ return null;
};
diff --git a/src/renderer/hooks/use-garbage-collection.ts b/src/renderer/hooks/use-garbage-collection.ts
index 291cbabae..41f484816 100644
--- a/src/renderer/hooks/use-garbage-collection.ts
+++ b/src/renderer/hooks/use-garbage-collection.ts
@@ -7,15 +7,23 @@ const GARBAGE_COLLECTION_INTERVAL = 1000 * 60 * 5;
export const useGarbageCollection = () => {
const intervalIdRef = useRef(null);
+ const startInterval = () => {
+ if (intervalIdRef.current) {
+ clearInterval(intervalIdRef.current);
+ }
+
+ intervalIdRef.current = setInterval(() => {
+ window.api?.utils?.forceGarbageCollection?.();
+ }, GARBAGE_COLLECTION_INTERVAL);
+ };
+
// Clear the cache on an interval
useEffect(() => {
if (!isElectron()) {
return;
}
- intervalIdRef.current = setInterval(() => {
- window.api?.utils?.forceGarbageCollection?.();
- }, GARBAGE_COLLECTION_INTERVAL);
+ startInterval();
return () => {
if (intervalIdRef.current) {
@@ -38,5 +46,6 @@ export const useGarbageCollection = () => {
}
window.api?.utils?.forceGarbageCollection?.();
+ startInterval();
}, [location]);
};
diff --git a/src/renderer/hooks/use-server-authenticated.ts b/src/renderer/hooks/use-server-authenticated.ts
index b7a005112..91d89f6d0 100644
--- a/src/renderer/hooks/use-server-authenticated.ts
+++ b/src/renderer/hooks/use-server-authenticated.ts
@@ -2,13 +2,13 @@ import { isAxiosError } from 'axios';
import isElectron from 'is-electron';
import debounce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';
-import { useCallback, useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router';
import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller';
import { AppRoute } from '/@/renderer/router/routes';
-import { getServerById, useAuthStoreActions, useCurrentServer } from '/@/renderer/store';
+import { getServerById, useAuthStoreActions, useCurrentServerId } from '/@/renderer/store';
import { LogCategory, logFn } from '/@/renderer/utils/logger';
import { logMsg } from '/@/renderer/utils/logger-message';
import { toast } from '/@/shared/components/toast/toast';
@@ -40,13 +40,18 @@ const isNetworkError = (error: any): boolean => {
export const useServerAuthenticated = () => {
const priorServerId = useRef(undefined);
- const server = useCurrentServer();
+ const serverId = useCurrentServerId();
const [ready, setReady] = useState(AuthState.LOADING);
const navigate = useNavigate();
+ const navigateRef = useRef(navigate);
const retryCountRef = useRef(0);
const { setCurrentServer, updateServer } = useAuthStoreActions();
+ useEffect(() => {
+ navigateRef.current = navigate;
+ }, [navigate]);
+
const authenticateServer = useCallback(
async (serverWithAuth: NonNullable>, retryAttempt = 0) => {
const authStartTime = Date.now();
@@ -312,7 +317,7 @@ export const useServerAuthenticated = () => {
// Don't clear credentials on network failure - preserve them for when network returns
setReady(AuthState.INVALID);
- navigate(AppRoute.NO_NETWORK, { replace: true });
+ navigateRef.current(AppRoute.NO_NETWORK, { replace: true });
return;
}
@@ -341,18 +346,19 @@ export const useServerAuthenticated = () => {
setReady(AuthState.INVALID);
}
},
- [updateServer, setCurrentServer, navigate],
+ [updateServer, setCurrentServer],
);
- const debouncedAuth = debounce(
- (serverWithAuth: NonNullable>) => {
- authenticateServer(serverWithAuth).catch(console.error);
- },
- 300,
+ const debouncedAuth = useMemo(
+ () =>
+ debounce((serverWithAuth: NonNullable>) => {
+ authenticateServer(serverWithAuth).catch(console.error);
+ }, 300),
+ [authenticateServer],
);
useEffect(() => {
- if (!server) {
+ if (!serverId) {
logFn.debug(logMsg[LogCategory.SYSTEM].serverAuthenticationInvalid, {
category: LogCategory.SYSTEM,
meta: {
@@ -363,9 +369,9 @@ export const useServerAuthenticated = () => {
return;
}
- if (priorServerId.current !== server.id) {
- const serverWithAuth = getServerById(server.id);
- priorServerId.current = server.id;
+ if (priorServerId.current !== serverId) {
+ const serverWithAuth = getServerById(serverId);
+ priorServerId.current = serverId;
retryCountRef.current = 0; // Reset retry count when server changes
if (!serverWithAuth) {
@@ -373,7 +379,7 @@ export const useServerAuthenticated = () => {
category: LogCategory.SYSTEM,
meta: {
reason: 'Server not found in store',
- serverId: server.id,
+ serverId,
},
});
setReady(AuthState.INVALID);
@@ -383,7 +389,10 @@ export const useServerAuthenticated = () => {
setReady(AuthState.LOADING);
debouncedAuth(serverWithAuth);
}
- }, [debouncedAuth, server]);
+ return () => {
+ debouncedAuth.cancel();
+ };
+ }, [debouncedAuth, serverId]);
return ready;
};
diff --git a/src/renderer/layouts/default-layout.tsx b/src/renderer/layouts/default-layout.tsx
index cf5e7687f..c3a5a20fa 100644
--- a/src/renderer/layouts/default-layout.tsx
+++ b/src/renderer/layouts/default-layout.tsx
@@ -7,7 +7,7 @@ import styles from './default-layout.module.css';
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
import { MainContent } from '/@/renderer/layouts/default-layout/main-content';
import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
-import { useSettingsStore, useWindowSettings } from '/@/renderer/store/settings.store';
+import { useSettingsStore, useWindowBarStyle } from '/@/renderer/store/settings.store';
import { Platform, PlayerType } from '/@/shared/types/types';
if (!isElectron()) {
@@ -29,7 +29,7 @@ interface DefaultLayoutProps {
}
export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
- const { windowBarStyle } = useWindowSettings();
+ const windowBarStyle = useWindowBarStyle();
return (
<>
diff --git a/src/renderer/layouts/default-layout/main-content.tsx b/src/renderer/layouts/default-layout/main-content.tsx
index 18ef1aa9d..48207b144 100644
--- a/src/renderer/layouts/default-layout/main-content.tsx
+++ b/src/renderer/layouts/default-layout/main-content.tsx
@@ -175,13 +175,18 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
);
useEffect(() => {
+ if (!isResizing && !isResizingRight) {
+ return;
+ }
+
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResizing);
+
return () => {
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResizing);
};
- }, [resize, stopResizing]);
+ }, [isResizing, isResizingRight, resize, stopResizing]);
return (
{
const {
expanded: isFullScreenPlayerExpanded,
visualizerExpanded: isFullScreenVisualizerExpanded,
- } = useFullScreenPlayerStore();
- const { windowBarStyle } = useWindowSettings();
+ } = useFullScreenPlayerOverlayState();
+ const windowBarStyle = useWindowBarStyle();
return (
<>
diff --git a/src/renderer/layouts/responsive-layout.tsx b/src/renderer/layouts/responsive-layout.tsx
index f4207a15d..d51aec746 100644
--- a/src/renderer/layouts/responsive-layout.tsx
+++ b/src/renderer/layouts/responsive-layout.tsx
@@ -1,4 +1,5 @@
import isElectron from 'is-electron';
+import { useCallback, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router';
import { useAppTracker } from '/@/renderer/features/analytics/hooks/use-app-tracker';
@@ -9,8 +10,8 @@ import { DefaultLayout } from '/@/renderer/layouts/default-layout';
import { MobileLayout } from '/@/renderer/layouts/mobile-layout/mobile-layout';
import { AppRoute } from '/@/renderer/router/routes';
import {
- useCommandPalette,
- useHotkeySettings,
+ useCommandPaletteState,
+ useLayoutHotkeyBindings,
useSettingsStoreActions,
useZoomFactor,
} from '/@/renderer/store';
@@ -42,40 +43,72 @@ export const ResponsiveLayout = ({ shell }: ResponsiveLayoutProps) => {
);
};
+const localSettings = isElectron() ? window.api.localSettings : null;
+
const LayoutHotkeys = () => {
const navigate = useNavigate();
- const localSettings = isElectron() ? window.api.localSettings : null;
const zoomFactor = useZoomFactor();
const { setSettings } = useSettingsStoreActions();
- const { bindings } = useHotkeySettings();
- const { opened, ...handlers } = useCommandPalette();
+ const bindings = useLayoutHotkeyBindings();
+ const { close, open, opened, toggle } = useCommandPaletteState();
- const updateZoom = (increase: number) => {
- const newVal = zoomFactor + increase;
- if (newVal > 300 || newVal < 50 || !isElectron()) return;
- setSettings({
- general: {
- zoomFactor: newVal,
- },
- });
- localSettings?.setZoomFactor(zoomFactor);
- };
- localSettings?.setZoomFactor(zoomFactor);
+ const handlers = useMemo(
+ () => ({
+ close,
+ open,
+ toggle,
+ }),
+ [close, open, toggle],
+ );
- const zoomHotkeys: HotkeyItem[] = [
- [bindings.zoomIn.hotkey, () => updateZoom(5)],
- [bindings.zoomOut.hotkey, () => updateZoom(-5)],
- ];
+ const updateZoom = useCallback(
+ (increase: number) => {
+ const newVal = zoomFactor + increase;
+ if (newVal > 300 || newVal < 50 || !localSettings) return;
- useHotkeys([
- [bindings.globalSearch.hotkey, () => handlers.open()],
- [bindings.browserBack.hotkey, () => navigate(-1)],
- [bindings.browserForward.hotkey, () => navigate(1)],
- [bindings.navigateHome.hotkey, () => navigate(AppRoute.HOME)],
- ...(isElectron() ? zoomHotkeys : []),
- ]);
+ setSettings({
+ general: {
+ zoomFactor: newVal,
+ },
+ });
+ localSettings?.setZoomFactor(newVal);
+ },
+ [setSettings, zoomFactor],
+ );
- return ;
+ useEffect(() => {
+ if (localSettings) {
+ localSettings?.setZoomFactor(zoomFactor);
+ }
+ }, [zoomFactor]);
+
+ const hotkeys = useMemo(
+ () => [
+ [bindings.globalSearch.hotkey, open],
+ [bindings.browserBack.hotkey, () => navigate(-1)],
+ [bindings.browserForward.hotkey, () => navigate(1)],
+ [bindings.navigateHome.hotkey, () => navigate(AppRoute.HOME)],
+ ...(localSettings
+ ? ([
+ [bindings.zoomIn.hotkey, () => updateZoom(5)],
+ [bindings.zoomOut.hotkey, () => updateZoom(-5)],
+ ] as HotkeyItem[])
+ : []),
+ ],
+ [bindings, navigate, open, updateZoom],
+ );
+
+ const modalProps = useMemo(
+ () => ({
+ handlers,
+ opened,
+ }),
+ [handlers, opened],
+ );
+
+ useHotkeys(hotkeys);
+
+ return ;
};
const GarbageCollection = () => {
diff --git a/src/renderer/router/app-outlet.tsx b/src/renderer/router/app-outlet.tsx
index 5565173db..3cf1bb6f3 100644
--- a/src/renderer/router/app-outlet.tsx
+++ b/src/renderer/router/app-outlet.tsx
@@ -1,37 +1,45 @@
-import { useMemo } from 'react';
+import { useEffect, useMemo } from 'react';
import { Navigate, Outlet } from 'react-router';
+import { shallow } from 'zustand/shallow';
import { isServerLock } from '/@/renderer/features/action-required/utils/window-properties';
import { AppRoute } from '/@/renderer/router/routes';
-import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store';
+import { useAuthStore, useAuthStoreActions } from '/@/renderer/store';
const normalizeUrl = (url: string) => url.replace(/\/$/, '');
export const AppOutlet = () => {
- const currentServer = useCurrentServer();
+ const currentServer = useAuthStore(
+ (state) =>
+ state.currentServer
+ ? {
+ id: state.currentServer.id,
+ url: state.currentServer.url,
+ }
+ : null,
+ shallow,
+ );
const { deleteServer, setCurrentServer } = useAuthStoreActions();
- const isActionsRequired = useMemo(() => {
- // When SERVER_LOCK is enabled and the configured URL has changed,
- // clear the stale session so the user re-authenticates against the new server.
- if (isServerLock() && currentServer && window.SERVER_URL) {
- const configuredUrl = normalizeUrl(window.SERVER_URL);
- const persistedUrl = normalizeUrl(currentServer.url);
-
- if (configuredUrl !== persistedUrl) {
- deleteServer(currentServer.id);
- setCurrentServer(null);
- return true;
- }
+ const hasServerLockMismatch = useMemo(() => {
+ if (!isServerLock() || !currentServer || !window.SERVER_URL) {
+ return false;
}
- const isServerRequired = !currentServer;
+ const configuredUrl = normalizeUrl(window.SERVER_URL);
+ const persistedUrl = normalizeUrl(currentServer.url);
- const actions = [isServerRequired];
- const isActionRequired = actions.some((c) => c);
+ return configuredUrl !== persistedUrl;
+ }, [currentServer]);
- return isActionRequired;
- }, [currentServer, deleteServer, setCurrentServer]);
+ useEffect(() => {
+ if (hasServerLockMismatch && currentServer) {
+ deleteServer(currentServer.id);
+ setCurrentServer(null);
+ }
+ }, [currentServer, deleteServer, hasServerLockMismatch, setCurrentServer]);
+
+ const isActionsRequired = !currentServer || hasServerLockMismatch;
if (isActionsRequired) {
return ;
diff --git a/src/renderer/router/app-router.tsx b/src/renderer/router/app-router.tsx
index 78ac7aad9..ef3214b22 100644
--- a/src/renderer/router/app-router.tsx
+++ b/src/renderer/router/app-router.tsx
@@ -186,22 +186,22 @@ const VisualizerSettingsContextModal = (props: any) => (
);
+const appRouterModals = {
+ addToPlaylist: AddToPlaylistContextModal,
+ base: BaseContextModal,
+ lyricsSettings: LyricsSettingsContextModal,
+ saveAndReplace: SaveAndReplaceContextModal,
+ settings: SettingsContextModal,
+ shareItem: ShareItemContextModal,
+ shuffleAll: ShuffleAllContextModal,
+ updatePlaylist: UpdatePlaylistContextModal,
+ visualizerSettings: VisualizerSettingsContextModal,
+};
+
export const AppRouter = () => {
const router = (
-
+
}>
diff --git a/src/renderer/router/titlebar-outlet.tsx b/src/renderer/router/titlebar-outlet.tsx
index 9a847ef03..1218d90c6 100644
--- a/src/renderer/router/titlebar-outlet.tsx
+++ b/src/renderer/router/titlebar-outlet.tsx
@@ -3,11 +3,11 @@ import { Outlet } from 'react-router';
import styles from './titlebar-outlet.module.css';
import { Titlebar } from '/@/renderer/features/titlebar/components/titlebar';
-import { useWindowSettings } from '/@/renderer/store/settings.store';
+import { useWindowBarStyle } from '/@/renderer/store/settings.store';
import { Platform } from '/@/shared/types/types';
export const TitlebarOutlet = () => {
- const { windowBarStyle } = useWindowSettings();
+ const windowBarStyle = useWindowBarStyle();
return (
<>
diff --git a/src/renderer/store/app.store.ts b/src/renderer/store/app.store.ts
index 0e53346c0..549ced89a 100644
--- a/src/renderer/store/app.store.ts
+++ b/src/renderer/store/app.store.ts
@@ -4,6 +4,7 @@ import type { LibraryItem } from '/@/shared/types/domain-types';
import merge from 'lodash/merge';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
+import { shallow } from 'zustand/shallow';
import { createWithEqualityFn } from 'zustand/traditional';
import { AlbumListSort, SongListSort, SortOrder } from '/@/shared/types/domain-types';
@@ -280,6 +281,17 @@ export const useTitlebarStore = () => useAppStore((state) => state.titlebar);
export const useCommandPalette = () => useAppStore((state) => state.commandPalette);
+export const useCommandPaletteState = () =>
+ useAppStore(
+ (state) => ({
+ close: state.commandPalette.close,
+ open: state.commandPalette.open,
+ opened: state.commandPalette.opened,
+ toggle: state.commandPalette.toggle,
+ }),
+ shallow,
+ );
+
export const usePageSidebar = (key: string): [boolean, (value: boolean) => void] => {
const isOpen = useAppStore((state) => state.pageSidebar[key] ?? false);
const setPageSidebar = useAppStore((state) => state.actions.setPageSidebar);
diff --git a/src/renderer/store/full-screen-player.store.ts b/src/renderer/store/full-screen-player.store.ts
index 1a1e303b8..75bac522a 100644
--- a/src/renderer/store/full-screen-player.store.ts
+++ b/src/renderer/store/full-screen-player.store.ts
@@ -1,6 +1,7 @@
import merge from 'lodash/merge';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
+import { shallow } from 'zustand/shallow';
import { createWithEqualityFn } from 'zustand/traditional';
export interface FullScreenPlayerSlice extends FullScreenPlayerState {
@@ -62,3 +63,12 @@ export const useFullScreenPlayerStoreActions = () =>
export const useSetFullScreenPlayerStore = () =>
useFullScreenPlayerStore((state) => state.actions.setStore);
+
+export const useFullScreenPlayerOverlayState = () =>
+ useFullScreenPlayerStore(
+ (state) => ({
+ expanded: state.expanded,
+ visualizerExpanded: state.visualizerExpanded,
+ }),
+ shallow,
+ );
diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts
index a8c68a536..4a073f222 100644
--- a/src/renderer/store/player.store.ts
+++ b/src/renderer/store/player.store.ts
@@ -1,5 +1,6 @@
import merge from 'lodash/merge';
import { nanoid } from 'nanoid';
+import { useMemo } from 'react';
import { persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { useShallow } from 'zustand/react/shallow';
@@ -1636,10 +1637,13 @@ export const usePlayerActions = () => {
})),
);
- return {
- ...actions,
- setTimestamp: setTimestampStore,
- };
+ return useMemo(
+ () => ({
+ ...actions,
+ setTimestamp: setTimestampStore,
+ }),
+ [actions],
+ );
};
export type AddToQueueByPlayType = Play;
diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts
index ebf5be17d..c7a125f77 100644
--- a/src/renderer/store/settings.store.ts
+++ b/src/renderer/store/settings.store.ts
@@ -2421,8 +2421,26 @@ export const usePlayButtonBehavior = () =>
export const useWindowSettings = () => useSettingsStore((state) => state.window, shallow);
+export const useWindowBarStyle = () =>
+ useSettingsStore((state) => state.window.windowBarStyle, shallow);
+
export const useHotkeySettings = () => useSettingsStore((state) => state.hotkeys, shallow);
+export const useHotkeyBindings = () => useSettingsStore((state) => state.hotkeys.bindings, shallow);
+
+export const useLayoutHotkeyBindings = () =>
+ useSettingsStore(
+ (state) => ({
+ browserBack: state.hotkeys.bindings.browserBack,
+ browserForward: state.hotkeys.bindings.browserForward,
+ globalSearch: state.hotkeys.bindings.globalSearch,
+ navigateHome: state.hotkeys.bindings.navigateHome,
+ zoomIn: state.hotkeys.bindings.zoomIn,
+ zoomOut: state.hotkeys.bindings.zoomOut,
+ }),
+ shallow,
+ );
+
export const useMpvSettings = () =>
useSettingsStore((state) => state.playback.mpvProperties, shallow);