From 303eba87caf547064ee2f908239ebe1d09aad44f Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 14 Dec 2025 15:13:57 -0800 Subject: [PATCH] add a hook to sync main settings with renderer --- src/renderer/app.tsx | 2 + .../hooks/use-sync-settings-to-main.ts | 126 ++++++++++++++++++ src/renderer/utils/logger-message.ts | 1 + 3 files changed, 129 insertions(+) create mode 100644 src/renderer/hooks/use-sync-settings-to-main.ts diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 42f6d6113..7fd38407a 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -12,6 +12,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import i18n from '/@/i18n/i18n'; import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-context'; import { useServerVersion } from '/@/renderer/hooks/use-server-version'; +import { useSyncSettingsToMain } from '/@/renderer/hooks/use-sync-settings-to-main'; import { ReleaseNotesModal } from './release-notes-modal'; import { AppRouter } from '/@/renderer/router/app-router'; import { useCssSettings, useHotkeySettings, useSettingsStore } from '/@/renderer/store'; @@ -32,6 +33,7 @@ export const App = () => { const { bindings } = useHotkeySettings(); const cssRef = useRef(null); useServerVersion(); + useSyncSettingsToMain(); const [webAudio, setWebAudio] = useState(); diff --git a/src/renderer/hooks/use-sync-settings-to-main.ts b/src/renderer/hooks/use-sync-settings-to-main.ts new file mode 100644 index 000000000..545a7e6f1 --- /dev/null +++ b/src/renderer/hooks/use-sync-settings-to-main.ts @@ -0,0 +1,126 @@ +import isElectron from 'is-electron'; +import { useEffect, useRef } from 'react'; + +import { openRestartRequiredToast } from '/@/renderer/features/settings/restart-toast'; +import { useSettingsStore } from '/@/renderer/store/settings.store'; +import { logFn } from '/@/renderer/utils/logger'; +import { logMsg } from '/@/renderer/utils/logger-message'; + +// Synchronizes settings from the renderer store to the main process electron store +// on app initialization. If there are differences, it updates the main store and shows +// a restart required toast. +export const useSyncSettingsToMain = () => { + const settings = useSettingsStore((state) => ({ + general: state.general, + hotkeys: state.hotkeys, + lyrics: state.lyrics, + playback: state.playback, + window: state.window, + })); + + const hasRunRef = useRef(false); + + useEffect(() => { + if (hasRunRef.current) { + return; + } + + if (!isElectron() || !window.api.localSettings) { + hasRunRef.current = true; + return; + } + + // Wait a small amount of time to ensure the store is hydrated from localStorage + const timeoutId = setTimeout(() => { + if (hasRunRef.current) { + return; + } + hasRunRef.current = true; + + const localSettings = window.api.localSettings; + let hasDifferences = false; + + const settingsMappings: Array<{ + mainStoreKey: string; + rendererValue: any; + }> = [ + { + mainStoreKey: 'window_window_bar_style', + rendererValue: settings.window.windowBarStyle, + }, + { + mainStoreKey: 'window_start_minimized', + rendererValue: settings.window.startMinimized, + }, + { + mainStoreKey: 'window_exit_to_tray', + rendererValue: settings.window.exitToTray, + }, + { + mainStoreKey: 'window_minimize_to_tray', + rendererValue: settings.window.minimizeToTray, + }, + { + mainStoreKey: 'disable_auto_updates', + rendererValue: settings.window.disableAutoUpdate, + }, + { + mainStoreKey: 'release_channel', + rendererValue: settings.window.releaseChannel, + }, + { + mainStoreKey: 'window_enable_tray', + rendererValue: settings.window.tray, + }, + { + mainStoreKey: 'password_store', + rendererValue: settings.general.passwordStore, + }, + { + mainStoreKey: 'mediaSession', + rendererValue: settings.playback.mediaSession, + }, + { + mainStoreKey: 'playbackType', + rendererValue: settings.playback.type, + }, + { + mainStoreKey: 'global_media_hotkeys', + rendererValue: settings.hotkeys.globalMediaHotkeys, + }, + { + mainStoreKey: 'enableNeteaseTranslation', + rendererValue: settings.lyrics.enableNeteaseTranslation, + }, + ]; + + // Compare and sync each setting + for (const mapping of settingsMappings) { + const mainValue = localSettings.get(mapping.mainStoreKey); + const rendererValue = mapping.rendererValue; + + const mainValueNormalized = mainValue === undefined ? null : mainValue; + const rendererValueNormalized = rendererValue === undefined ? null : rendererValue; + + if ( + JSON.stringify(mainValueNormalized) !== JSON.stringify(rendererValueNormalized) + ) { + hasDifferences = true; + localSettings.set(mapping.mainStoreKey, rendererValue); + } + } + + // Show restart toast if there were differences + if (hasDifferences) { + logFn.info(logMsg.system.settingsSynchronized); + openRestartRequiredToast(); + } + }, 100); + + return () => { + clearTimeout(timeoutId); + }; + // Only run once on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +}; diff --git a/src/renderer/utils/logger-message.ts b/src/renderer/utils/logger-message.ts index 5b4c0a932..2b5e82197 100644 --- a/src/renderer/utils/logger-message.ts +++ b/src/renderer/utils/logger-message.ts @@ -118,5 +118,6 @@ export const logMsg = { serverAuthenticationFailed: 'Server authentication failed', serverAuthenticationInvalid: 'Server authentication invalid', serverAuthenticationSuccess: 'Server authentication successful', + settingsSynchronized: 'Differences found between renderer and main process settings', }, };