mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
various performance refactors
This commit is contained in:
+113
-55
@@ -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 <ThemedApp />;
|
||||
};
|
||||
|
||||
const ThemedApp = () => {
|
||||
const { mode, theme } = useAppTheme();
|
||||
const language = useLanguage();
|
||||
|
||||
const { content, enabled } = useCssSettings();
|
||||
const { bindings } = useHotkeySettings();
|
||||
const cssRef = useRef<HTMLStyleElement | null>(null);
|
||||
|
||||
useSyncSettingsToMain();
|
||||
useCheckForUpdates();
|
||||
return (
|
||||
<MantineProvider forceColorScheme={mode} theme={theme}>
|
||||
<AppShell />
|
||||
</MantineProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const AppShell = memo(function AppShell() {
|
||||
const [webAudio, setWebAudio] = useState<WebAudio>();
|
||||
|
||||
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 (
|
||||
<MantineProvider forceColorScheme={mode} theme={theme}>
|
||||
<>
|
||||
<AppEffects />
|
||||
<Notifications
|
||||
containerWidth="300px"
|
||||
position="bottom-center"
|
||||
@@ -126,6 +86,104 @@ export const App = () => {
|
||||
<ReleaseNotesModal />
|
||||
<UpdateAvailableDialog />
|
||||
</Suspense>
|
||||
</MantineProvider>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const AppEffects = () => (
|
||||
<>
|
||||
<SyncSettingsEffect />
|
||||
<UpdateCheckEffect />
|
||||
<CssSettingsEffect />
|
||||
<GlobalShortcutsEffect />
|
||||
<LanguageEffect />
|
||||
<OpenSettingsEffect />
|
||||
</>
|
||||
);
|
||||
|
||||
const SyncSettingsEffect = () => {
|
||||
useSyncSettingsToMain();
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const UpdateCheckEffect = () => {
|
||||
useCheckForUpdates();
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const CssSettingsEffect = () => {
|
||||
const { content, enabled } = useCssSettings();
|
||||
const cssRef = useRef<HTMLStyleElement | null>(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;
|
||||
};
|
||||
|
||||
@@ -7,15 +7,23 @@ const GARBAGE_COLLECTION_INTERVAL = 1000 * 60 * 5;
|
||||
export const useGarbageCollection = () => {
|
||||
const intervalIdRef = useRef<NodeJS.Timeout | null>(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]);
|
||||
};
|
||||
|
||||
@@ -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<string | undefined>(undefined);
|
||||
const server = useCurrentServer();
|
||||
const serverId = useCurrentServerId();
|
||||
const [ready, setReady] = useState(AuthState.LOADING);
|
||||
const navigate = useNavigate();
|
||||
const navigateRef = useRef(navigate);
|
||||
const retryCountRef = useRef<number>(0);
|
||||
|
||||
const { setCurrentServer, updateServer } = useAuthStoreActions();
|
||||
|
||||
useEffect(() => {
|
||||
navigateRef.current = navigate;
|
||||
}, [navigate]);
|
||||
|
||||
const authenticateServer = useCallback(
|
||||
async (serverWithAuth: NonNullable<ReturnType<typeof getServerById>>, 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<ReturnType<typeof getServerById>>) => {
|
||||
authenticateServer(serverWithAuth).catch(console.error);
|
||||
},
|
||||
300,
|
||||
const debouncedAuth = useMemo(
|
||||
() =>
|
||||
debounce((serverWithAuth: NonNullable<ReturnType<typeof getServerById>>) => {
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 (
|
||||
<motion.div
|
||||
|
||||
@@ -10,8 +10,7 @@ import { FullScreenVisualizer } from '/@/renderer/features/player/components/ful
|
||||
import { MobileFullscreenPlayer } from '/@/renderer/features/player/components/mobile-fullscreen-player';
|
||||
import { MobileSidebar } from '/@/renderer/features/sidebar/components/mobile-sidebar';
|
||||
import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
|
||||
import { useFullScreenPlayerStore } from '/@/renderer/store';
|
||||
import { useWindowSettings } from '/@/renderer/store';
|
||||
import { useFullScreenPlayerOverlayState, useWindowBarStyle } from '/@/renderer/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Drawer } from '/@/shared/components/drawer/drawer';
|
||||
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||
@@ -32,8 +31,8 @@ export const MobileLayout = ({ shell }: MobileLayoutProps) => {
|
||||
const {
|
||||
expanded: isFullScreenPlayerExpanded,
|
||||
visualizerExpanded: isFullScreenVisualizerExpanded,
|
||||
} = useFullScreenPlayerStore();
|
||||
const { windowBarStyle } = useWindowSettings();
|
||||
} = useFullScreenPlayerOverlayState();
|
||||
const windowBarStyle = useWindowBarStyle();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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 <CommandPalette modalProps={{ handlers, opened }} />;
|
||||
useEffect(() => {
|
||||
if (localSettings) {
|
||||
localSettings?.setZoomFactor(zoomFactor);
|
||||
}
|
||||
}, [zoomFactor]);
|
||||
|
||||
const hotkeys = useMemo<HotkeyItem[]>(
|
||||
() => [
|
||||
[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 <CommandPalette modalProps={modalProps} />;
|
||||
};
|
||||
|
||||
const GarbageCollection = () => {
|
||||
|
||||
@@ -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 <Navigate replace to={AppRoute.ACTION_REQUIRED} />;
|
||||
|
||||
@@ -186,22 +186,22 @@ const VisualizerSettingsContextModal = (props: any) => (
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
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 = (
|
||||
<HashRouter>
|
||||
<ModalsProvider
|
||||
modals={{
|
||||
addToPlaylist: AddToPlaylistContextModal,
|
||||
base: BaseContextModal,
|
||||
lyricsSettings: LyricsSettingsContextModal,
|
||||
saveAndReplace: SaveAndReplaceContextModal,
|
||||
settings: SettingsContextModal,
|
||||
shareItem: ShareItemContextModal,
|
||||
shuffleAll: ShuffleAllContextModal,
|
||||
updatePlaylist: UpdatePlaylistContextModal,
|
||||
visualizerSettings: VisualizerSettingsContextModal,
|
||||
}}
|
||||
>
|
||||
<ModalsProvider modals={appRouterModals}>
|
||||
<RouterErrorBoundary>
|
||||
<Routes>
|
||||
<Route element={<AuthenticationOutlet />}>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user