diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 494fdb271..11095a747 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -14,7 +14,7 @@ import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-co 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'; +import { useCssSettings, useHotkeySettings, useLanguage } from '/@/renderer/store'; import { useAppTheme } from '/@/renderer/themes/use-app-theme'; import { sanitizeCss } from '/@/renderer/utils/sanitize'; import { WebAudio } from '/@/shared/types/types'; @@ -26,7 +26,7 @@ const ipc = isElectron() ? window.api.ipc : null; export const App = () => { const { mode, theme } = useAppTheme(); - const language = useSettingsStore((store) => store.general.language); + const language = useLanguage(); const { content, enabled } = useCssSettings(); const { bindings } = useHotkeySettings(); diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx index c79df8c47..33e3bc7bc 100644 --- a/src/renderer/components/item-card/item-card.tsx +++ b/src/renderer/components/item-card/item-card.tsx @@ -19,7 +19,7 @@ import { ItemControls } from '/@/renderer/components/item-list/types'; import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; import { AppRoute } from '/@/renderer/router/routes'; -import { useGeneralSettings } from '/@/renderer/store'; +import { useShowRatings } from '/@/renderer/store'; import { formatDateAbsolute, formatDateAbsoluteUTC, @@ -77,7 +77,7 @@ export const ItemCard = ({ type = 'poster', withControls, }: ItemCardProps) => { - const { showRatings } = useGeneralSettings(); + const showRatings = useShowRatings(); const imageUrl = getImageUrl(data); const rows = providedRows || []; diff --git a/src/renderer/components/item-image/item-image.tsx b/src/renderer/components/item-image/item-image.tsx index a85b96d9f..7422a4cbf 100644 --- a/src/renderer/components/item-image/item-image.tsx +++ b/src/renderer/components/item-image/item-image.tsx @@ -7,6 +7,7 @@ import { getServerById, useAuthStore, useCurrentServerId, + useImageRes, useSettingsStore, } from '/@/renderer/store'; import { BaseImage, ImageProps } from '/@/shared/components/image/image'; @@ -73,7 +74,7 @@ export const useItemImageUrl = (args: UseItemImageUrlProps) => { const { id, imageUrl, itemType, size, type, useRemoteUrl } = args; const serverId = useCurrentServerId(); - const imageRes = useSettingsStore((store) => store.general.imageRes); + const imageRes = useImageRes(); const sizeByType: number | undefined = type ? imageRes[type] : undefined; return useMemo(() => { diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index 9fd86bb3f..d5ef8c1c8 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -21,7 +21,7 @@ import { searchLibraryItems } from '/@/renderer/features/shared/utils'; import { useContainerQuery } from '/@/renderer/hooks'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer, usePlayerSong } from '/@/renderer/store'; -import { useGeneralSettings, useSettingsStore } from '/@/renderer/store/settings.store'; +import { useExternalLinks, useSettingsStore } from '/@/renderer/store/settings.store'; import { sentenceCase, titleCase } from '/@/renderer/utils'; import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify'; import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types'; @@ -312,7 +312,7 @@ export const AlbumDetailContent = () => { ); const { ref, ...cq } = useContainerQuery(); - const { externalLinks, lastFM, musicBrainz } = useGeneralSettings(); + const { externalLinks, lastFM, musicBrainz } = useExternalLinks(); const genreCarousels = useMemo(() => { const genreLimit = 2; diff --git a/src/renderer/features/albums/components/album-detail-header.tsx b/src/renderer/features/albums/components/album-detail-header.tsx index 52530239f..e2dae4212 100644 --- a/src/renderer/features/albums/components/album-detail-header.tsx +++ b/src/renderer/features/albums/components/album-detail-header.tsx @@ -14,7 +14,7 @@ import { LibraryHeaderMenu, } from '/@/renderer/features/shared/components/library-header'; import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; +import { useCurrentServer, useShowRatings } from '/@/renderer/store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { formatDateAbsoluteUTC, formatDurationString } from '/@/renderer/utils'; import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types'; @@ -29,7 +29,7 @@ export const AlbumDetailHeader = forwardRef((_props, ref) => { const { albumId } = useParams() as { albumId: string }; const { t } = useTranslation(); const server = useCurrentServer(); - const { showRatings } = useGeneralSettings(); + const showRatings = useShowRatings(); const detailQuery = useQuery( albumQueries.detail({ query: { id: albumId }, serverId: server?.id }), ); diff --git a/src/renderer/features/albums/routes/album-detail-route.tsx b/src/renderer/features/albums/routes/album-detail-route.tsx index 0947dc3cf..4330a65e5 100644 --- a/src/renderer/features/albums/routes/album-detail-route.tsx +++ b/src/renderer/features/albums/routes/album-detail-route.tsx @@ -16,13 +16,13 @@ import { LibraryContainer } from '/@/renderer/features/shared/components/library import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { useFastAverageColor, useWaitForColorCalculation } from '/@/renderer/hooks'; -import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; +import { useAlbumBackground, useCurrentServer } from '/@/renderer/store'; import { LibraryItem } from '/@/shared/types/domain-types'; const AlbumDetailRoute = () => { const scrollAreaRef = useRef(null); const headerRef = useRef(null); - const { albumBackground, albumBackgroundBlur } = useGeneralSettings(); + const { albumBackground, albumBackgroundBlur } = useAlbumBackground(); const { albumId } = useParams() as { albumId: string }; const server = useCurrentServer(); diff --git a/src/renderer/features/artists/components/album-artist-detail-content.tsx b/src/renderer/features/artists/components/album-artist-detail-content.tsx index fc5218754..b534db906 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -49,7 +49,13 @@ import { useCurrentServerId, usePlayerSong, } from '/@/renderer/store'; -import { useGeneralSettings, useSettingsStore } from '/@/renderer/store/settings.store'; +import { + useArtistItems, + useArtistRadioCount, + useArtistReleaseTypeItems, + useExternalLinks, + useSettingsStore, +} from '/@/renderer/store/settings.store'; import { titleCase } from '/@/renderer/utils'; import { sanitize } from '/@/renderer/utils/sanitize'; import { sortAlbumList } from '/@/shared/api/utils'; @@ -589,8 +595,9 @@ export const AlbumArtistDetailContent = ({ albumsQuery, detailQuery, }: AlbumArtistDetailContentProps) => { - const { artistItems, artistRadioCount, externalLinks, lastFM, musicBrainz } = - useGeneralSettings(); + const artistItems = useArtistItems(); + const artistRadioCount = useArtistRadioCount(); + const { externalLinks, lastFM, musicBrainz } = useExternalLinks(); const { albumArtistId, artistId } = useParams() as { albumArtistId?: string; artistId?: string; @@ -1064,7 +1071,7 @@ interface ArtistAlbumsProps { const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { const { t } = useTranslation(); - const { artistReleaseTypeItems } = useGeneralSettings(); + const artistReleaseTypeItems = useArtistReleaseTypeItems(); const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300); const albumArtistDetailSort = useAppStore((state) => state.albumArtistDetailSort); diff --git a/src/renderer/features/artists/components/album-artist-detail-header.tsx b/src/renderer/features/artists/components/album-artist-detail-header.tsx index badb01ac8..cd354296a 100644 --- a/src/renderer/features/artists/components/album-artist-detail-header.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-header.tsx @@ -14,7 +14,7 @@ import { LibraryHeaderMenu, } from '/@/renderer/features/shared/components/library-header'; import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; +import { useCurrentServer, useShowRatings } from '/@/renderer/store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { formatDurationString } from '/@/renderer/utils'; import { Group } from '/@/shared/components/group/group'; @@ -30,7 +30,7 @@ export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref { const headerRef = useRef(null); const server = useCurrentServer(); const serverId = useCurrentServerId(); - const { artistBackground, artistBackgroundBlur } = useGeneralSettings(); + const { artistBackground, artistBackgroundBlur } = useArtistBackground(); const { albumArtistId, artistId } = useParams() as { albumArtistId?: string; diff --git a/src/renderer/features/context-menu/actions/play-artist-radio-action.tsx b/src/renderer/features/context-menu/actions/play-artist-radio-action.tsx index b33199fd4..b7d7296a7 100644 --- a/src/renderer/features/context-menu/actions/play-artist-radio-action.tsx +++ b/src/renderer/features/context-menu/actions/play-artist-radio-action.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { queryKeys } from '/@/renderer/api/query-keys'; import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; -import { useCurrentServerId, useGeneralSettings, usePlayButtonBehavior } from '/@/renderer/store'; +import { useArtistRadioCount, useCurrentServerId, usePlayButtonBehavior } from '/@/renderer/store'; import { ContextMenu } from '/@/shared/components/context-menu/context-menu'; import { AlbumArtist, Artist } from '/@/shared/types/domain-types'; import { Play } from '/@/shared/types/types'; @@ -16,7 +16,7 @@ interface PlayArtistRadioActionProps { } export const PlayArtistRadioAction = ({ artist, disabled }: PlayArtistRadioActionProps) => { - const { artistRadioCount } = useGeneralSettings(); + const artistRadioCount = useArtistRadioCount(); const { t } = useTranslation(); const player = usePlayer(); const serverId = useCurrentServerId(); diff --git a/src/renderer/features/context-menu/actions/set-rating-action.tsx b/src/renderer/features/context-menu/actions/set-rating-action.tsx index 717217d22..2fd9c2b0b 100644 --- a/src/renderer/features/context-menu/actions/set-rating-action.tsx +++ b/src/renderer/features/context-menu/actions/set-rating-action.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; -import { useCurrentServer, useCurrentServerId, useGeneralSettings } from '/@/renderer/store'; +import { useCurrentServer, useCurrentServerId, useShowRatings } from '/@/renderer/store'; import { ContextMenu } from '/@/shared/components/context-menu/context-menu'; import { Rating } from '/@/shared/components/rating/rating'; import { LibraryItem } from '/@/shared/types/domain-types'; @@ -17,7 +17,7 @@ export const SetRatingAction = ({ ids, itemType }: SetRatingActionProps) => { const { t } = useTranslation(); const server = useCurrentServer(); const serverId = useCurrentServerId(); - const { showRatings } = useGeneralSettings(); + const showRatings = useShowRatings(); const setRatingMutation = useSetRating({}); diff --git a/src/renderer/features/discord-rpc/use-discord-rpc.ts b/src/renderer/features/discord-rpc/use-discord-rpc.ts index b1da380da..773d8e3c2 100644 --- a/src/renderer/features/discord-rpc/use-discord-rpc.ts +++ b/src/renderer/features/discord-rpc/use-discord-rpc.ts @@ -9,7 +9,7 @@ import { DiscordLinkType, useAppStore, useDiscordSettings, - useGeneralSettings, + useLastfmApiKey, usePlayerSong, usePlayerStore, useTimestampStoreBase, @@ -31,7 +31,7 @@ const truncate = (field: string) => export const useDiscordRpc = () => { const discordSettings = useDiscordSettings(); - const generalSettings = useGeneralSettings(); + const lastfmApiKey = useLastfmApiKey(); const privateMode = useAppStore((state) => state.privateMode); const [lastUniqueId, setlastUniqueId] = useState(''); @@ -220,12 +220,12 @@ export const useDiscordRpc = () => { if ( activity.largeImageKey === undefined && - generalSettings.lastfmApiKey && + lastfmApiKey && song?.album && song?.albumArtists.length ) { const albumInfo = await fetch( - `https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${generalSettings.lastfmApiKey}&artist=${encodeURIComponent(song.albumArtists[0].name)}&album=${encodeURIComponent(song.album)}&format=json`, + `https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${lastfmApiKey}&artist=${encodeURIComponent(song.albumArtists[0].name)}&album=${encodeURIComponent(song.album)}&format=json`, ); const albumInfoJson = await albumInfo.json(); @@ -292,7 +292,7 @@ export const useDiscordRpc = () => { discordSettings.showAsListening, discordSettings.showServerImage, discordSettings.showPaused, - generalSettings.lastfmApiKey, + lastfmApiKey, discordSettings.clientId, discordSettings.displayType, discordSettings.linkType, diff --git a/src/renderer/features/home/routes/home-route.tsx b/src/renderer/features/home/routes/home-route.tsx index fc088c102..5363068bb 100644 --- a/src/renderer/features/home/routes/home-route.tsx +++ b/src/renderer/features/home/routes/home-route.tsx @@ -13,7 +13,8 @@ import { SongInfiniteCarousel } from '/@/renderer/features/songs/components/song import { HomeItem, useCurrentServer, - useGeneralSettings, + useHomeFeature, + useHomeItems, useWindowSettings, } from '/@/renderer/store'; import { Spinner } from '/@/shared/components/spinner/spinner'; @@ -32,7 +33,8 @@ const HomeRoute = () => { const scrollAreaRef = useRef(null); const server = useCurrentServer(); const { windowBarStyle } = useWindowSettings(); - const { homeFeature, homeItems } = useGeneralSettings(); + const homeFeature = useHomeFeature(); + const homeItems = useHomeItems(); const isJellyfin = server?.type === ServerType.JELLYFIN; diff --git a/src/renderer/features/now-playing/components/play-queue.tsx b/src/renderer/features/now-playing/components/play-queue.tsx index c7494cb17..7b866ff71 100644 --- a/src/renderer/features/now-playing/components/play-queue.tsx +++ b/src/renderer/features/now-playing/components/play-queue.tsx @@ -20,12 +20,12 @@ import { mapShuffledToQueueIndex, subscribeCurrentTrack, subscribePlayerQueue, + useFollowCurrentSong, useListSettings, usePlayerActions, usePlayerQueueType, usePlayerSong, usePlayerStore, - useSettingsStore, } from '/@/renderer/store'; import { Flex } from '/@/shared/components/flex/flex'; import { LoadingOverlay } from '/@/shared/components/loading-overlay/loading-overlay'; @@ -51,7 +51,7 @@ export const PlayQueue = forwardRef(({ listKey, sear const mergedRef = useMergedRef(ref, tableRef); const { getQueue } = usePlayerActions(); const queueType = usePlayerQueueType(); - const followCurrentSong = useSettingsStore((state) => state.general.followCurrentSong); + const followCurrentSong = useFollowCurrentSong(); const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 200); diff --git a/src/renderer/features/now-playing/components/sidebar-play-queue.tsx b/src/renderer/features/now-playing/components/sidebar-play-queue.tsx index 784953c47..4f648f510 100644 --- a/src/renderer/features/now-playing/components/sidebar-play-queue.tsx +++ b/src/renderer/features/now-playing/components/sidebar-play-queue.tsx @@ -12,12 +12,15 @@ import { Lyrics } from '/@/renderer/features/lyrics/lyrics'; import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls'; import { + useCombinedLyricsAndVisualizer, useFullScreenPlayerStore, - useGeneralSettings, usePlaybackSettings, usePlayerSong, useSettingsStore, useSettingsStoreActions, + useShowLyricsInSidebar, + useShowVisualizerInSidebar, + useSidebarPanelOrder, } from '/@/renderer/store'; import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon'; import { Flex } from '/@/shared/components/flex/flex'; @@ -43,12 +46,10 @@ export const SidebarPlayQueue = () => { const [search, setSearch] = useState(undefined); const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore(); const [shouldRender, setShouldRender] = useState(!isFullScreenPlayerExpanded); - const { - combinedLyricsAndVisualizer, - showLyricsInSidebar, - showVisualizerInSidebar, - sidebarPanelOrder, - } = useGeneralSettings(); + const combinedLyricsAndVisualizer = useCombinedLyricsAndVisualizer(); + const showLyricsInSidebar = useShowLyricsInSidebar(); + const showVisualizerInSidebar = useShowVisualizerInSidebar(); + const sidebarPanelOrder = useSidebarPanelOrder(); const { type, webAudio } = usePlaybackSettings(); const showVisualizer = showVisualizerInSidebar && type === PlayerType.WEB && webAudio; const showPanel = showLyricsInSidebar || showVisualizer; @@ -217,9 +218,9 @@ export const SidebarPlayQueue = () => { const PanelReorderControls = ({ panelType }: { panelType: 'lyrics' | 'visualizer' }) => { const { t } = useTranslation(); - const generalSettings = useGeneralSettings(); - const { combinedLyricsAndVisualizer, sidebarPanelOrder } = generalSettings; const { setSettings } = useSettingsStoreActions(); + const sidebarPanelOrder = useSidebarPanelOrder(); + const combinedLyricsAndVisualizer = useCombinedLyricsAndVisualizer(); const currentIndex = sidebarPanelOrder.indexOf(panelType); const canMoveUp = currentIndex > 0; @@ -238,11 +239,10 @@ const PanelReorderControls = ({ panelType }: { panelType: 'lyrics' | 'visualizer setSettings({ general: { - ...generalSettings, sidebarPanelOrder: newOrder, }, }); - }, [canMoveUp, currentIndex, generalSettings, sidebarPanelOrder, setSettings]); + }, [canMoveUp, currentIndex, sidebarPanelOrder, setSettings]); const handleMoveDown = useCallback(() => { if (!canMoveDown) return; @@ -255,17 +255,15 @@ const PanelReorderControls = ({ panelType }: { panelType: 'lyrics' | 'visualizer setSettings({ general: { - ...generalSettings, sidebarPanelOrder: newOrder, }, }); - }, [canMoveDown, currentIndex, generalSettings, sidebarPanelOrder, setSettings]); + }, [canMoveDown, currentIndex, sidebarPanelOrder, setSettings]); const handleClose = useCallback(() => { if (combinedLyricsAndVisualizer && panelType === 'lyrics') { setSettings({ general: { - ...generalSettings, showLyricsInSidebar: false, showVisualizerInSidebar: false, }, @@ -273,19 +271,17 @@ const PanelReorderControls = ({ panelType }: { panelType: 'lyrics' | 'visualizer } else if (panelType === 'lyrics') { setSettings({ general: { - ...generalSettings, showLyricsInSidebar: false, }, }); } else if (panelType === 'visualizer') { setSettings({ general: { - ...generalSettings, showVisualizerInSidebar: false, }, }); } - }, [combinedLyricsAndVisualizer, generalSettings, panelType, setSettings]); + }, [combinedLyricsAndVisualizer, panelType, setSettings]); return (
diff --git a/src/renderer/features/player/components/center-controls.tsx b/src/renderer/features/player/components/center-controls.tsx index 05c445a40..dbd847189 100644 --- a/src/renderer/features/player/components/center-controls.tsx +++ b/src/renderer/features/player/components/center-controls.tsx @@ -13,17 +13,18 @@ import { useRadioPlayer, } from '/@/renderer/features/radio/hooks/use-radio-player'; import { + useButtonSize, usePlayerRepeat, usePlayerShuffle, usePlayerSong, usePlayerStatus, - useSettingsStore, + useSkipButtons, } from '/@/renderer/store'; import { Icon } from '/@/shared/components/icon/icon'; import { PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types'; export const CenterControls = () => { - const skip = useSettingsStore((state) => state.general.skipButtons); + const skip = useSkipButtons(); const isRadioActive = useIsRadioActive(); @@ -85,7 +86,7 @@ const RadioCenterPlayButton = ({ disabled }: { disabled?: boolean }) => { const RadioStopButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const { stop } = useRadioControls(); return ( @@ -104,7 +105,7 @@ const RadioStopButton = ({ disabled }: { disabled?: boolean }) => { const StopButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const { mediaStop } = usePlayer(); return ( @@ -123,7 +124,7 @@ const StopButton = ({ disabled }: { disabled?: boolean }) => { const ShuffleButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const shuffle = usePlayerShuffle(); const { toggleShuffle } = usePlayer(); @@ -156,7 +157,7 @@ const ShuffleButton = ({ disabled }: { disabled?: boolean }) => { const PreviousButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const { mediaPrevious } = usePlayer(); return ( @@ -175,7 +176,7 @@ const PreviousButton = ({ disabled }: { disabled?: boolean }) => { const SkipBackwardButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const { mediaSkipBackward } = usePlayer(); return ( @@ -211,7 +212,7 @@ const CenterPlayButton = ({ disabled }: { disabled?: boolean }) => { const SkipForwardButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const { mediaSkipForward } = usePlayer(); return ( @@ -233,7 +234,7 @@ const SkipForwardButton = ({ disabled }: { disabled?: boolean }) => { const NextButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const { mediaNext } = usePlayer(); return ( @@ -252,7 +253,7 @@ const NextButton = ({ disabled }: { disabled?: boolean }) => { const RepeatButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); const repeat = usePlayerRepeat(); const { toggleRepeat } = usePlayer(); @@ -298,7 +299,7 @@ const RepeatButton = ({ disabled }: { disabled?: boolean }) => { const ShuffleAllButton = ({ disabled }: { disabled?: boolean }) => { const { t } = useTranslation(); - const buttonSize = useSettingsStore((state) => state.general.buttonSize); + const buttonSize = useButtonSize(); return ( & { placeholder?: string }) => { - const nativeAspectRatio = useSettingsStore((store) => store.general.nativeAspectRatio); + const nativeAspectRatio = useNativeAspectRatio(); if (!props.src) { return ( diff --git a/src/renderer/features/player/components/mobile-fullscreen-player-album-art.tsx b/src/renderer/features/player/components/mobile-fullscreen-player-album-art.tsx index dd1b6ff57..718325a12 100644 --- a/src/renderer/features/player/components/mobile-fullscreen-player-album-art.tsx +++ b/src/renderer/features/player/components/mobile-fullscreen-player-album-art.tsx @@ -7,9 +7,9 @@ import styles from './mobile-fullscreen-player.module.css'; import { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { useFullScreenPlayerStore, + useImageRes, usePlayerData, usePlayerSong, - useSettingsStore, } from '/@/renderer/store'; import { Center } from '/@/shared/components/center/center'; import { Icon } from '/@/shared/components/icon/icon'; @@ -78,7 +78,7 @@ export const MobileFullscreenPlayerAlbumArt = () => { const mainImageRef = useRef(null); const [mainImageDimensions, setMainImageDimensions] = useState({ idealSize: 1000 }); - const albumArtRes = useSettingsStore((store) => store.general.imageRes.fullScreenPlayer); + const { fullScreenPlayer: albumArtRes } = useImageRes(); const { useImageAspectRatio } = useFullScreenPlayerStore(); const currentSong = usePlayerSong(); const { nextSong } = usePlayerData(); diff --git a/src/renderer/features/player/components/player-config.tsx b/src/renderer/features/player/components/player-config.tsx index ee47b0556..db7f23514 100644 --- a/src/renderer/features/player/components/player-config.tsx +++ b/src/renderer/features/player/components/player-config.tsx @@ -12,10 +12,12 @@ import { usePlayerStatus, } from '/@/renderer/store'; import { - useGeneralSettings, + useCombinedLyricsAndVisualizer, usePlaybackSettings, useSettingsStore, useSettingsStoreActions, + useShowLyricsInSidebar, + useShowVisualizerInSidebar, } from '/@/renderer/store/settings.store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Popover } from '/@/shared/components/popover/popover'; @@ -49,7 +51,9 @@ export const PlayerConfig = () => { const { setCrossfadeDuration, setCrossfadeStyle, setQueueType, setSpeed, setTransitionType } = usePlayerActions(); const preservePitch = useSettingsStore((state) => state.playback.preservePitch); - const generalSettings = useGeneralSettings(); + const showLyricsInSidebar = useShowLyricsInSidebar(); + const showVisualizerInSidebar = useShowVisualizerInSidebar(); + const combinedLyricsAndVisualizer = useCombinedLyricsAndVisualizer(); const playbackSettings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); @@ -325,11 +329,10 @@ export const PlayerConfig = () => { { component: ( { setSettings({ general: { - ...generalSettings, showLyricsInSidebar: e.currentTarget.checked, }, }); @@ -342,11 +345,10 @@ export const PlayerConfig = () => { { component: ( { setSettings({ general: { - ...generalSettings, showVisualizerInSidebar: e.currentTarget.checked, }, }); @@ -359,11 +361,10 @@ export const PlayerConfig = () => { { component: ( { setSettings({ general: { - ...generalSettings, combinedLyricsAndVisualizer: e.currentTarget.checked, }, }); @@ -395,7 +396,9 @@ export const PlayerConfig = () => { setTransitionType, setCrossfadeStyle, setPreservePitch, - generalSettings, + showLyricsInSidebar, + showVisualizerInSidebar, + combinedLyricsAndVisualizer, ]); return ( diff --git a/src/renderer/features/player/components/playerbar-waveform.tsx b/src/renderer/features/player/components/playerbar-waveform.tsx index 572cd0932..b9e325534 100644 --- a/src/renderer/features/player/components/playerbar-waveform.tsx +++ b/src/renderer/features/player/components/playerbar-waveform.tsx @@ -10,8 +10,8 @@ import { useSongUrl } from '/@/renderer/features/player/audio-player/hooks/use-s import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { BarAlign, - useGeneralSettings, usePlaybackSettings, + usePlayerbarSlider, usePlayerSong, usePlayerTimestamp, } from '/@/renderer/store'; @@ -22,7 +22,7 @@ import { Text } from '/@/shared/components/text/text'; export const PlayerbarWaveform = () => { const currentSong = usePlayerSong(); const { transcode } = usePlaybackSettings(); - const { playerbarSlider } = useGeneralSettings(); + const playerbarSlider = usePlayerbarSlider(); const currentTime = usePlayerTimestamp(); const containerRef = useRef(null); const { mediaSeekToTimestamp } = usePlayer(); diff --git a/src/renderer/features/player/components/playerbar.tsx b/src/renderer/features/player/components/playerbar.tsx index 7f868257f..89adccbd0 100644 --- a/src/renderer/features/player/components/playerbar.tsx +++ b/src/renderer/features/player/components/playerbar.tsx @@ -9,11 +9,11 @@ import { MobilePlayerbar } from '/@/renderer/features/player/components/mobile-p import { RightControls } from '/@/renderer/features/player/components/right-controls'; import { useIsMobile } from '/@/renderer/hooks/use-is-mobile'; import { useFullScreenPlayerStore, useSetFullScreenPlayerStore } from '/@/renderer/store'; -import { useGeneralSettings } from '/@/renderer/store/settings.store'; +import { usePlayerbarOpenDrawer } from '/@/renderer/store'; import { PlaybackSelectors } from '/@/shared/constants/playback-selectors'; export const Playerbar = () => { - const { playerbarOpenDrawer } = useGeneralSettings(); + const playerbarOpenDrawer = usePlayerbarOpenDrawer(); const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore(); const setFullScreenPlayerStore = useSetFullScreenPlayerStore(); const isMobile = useIsMobile(); diff --git a/src/renderer/features/player/components/right-controls.tsx b/src/renderer/features/player/components/right-controls.tsx index 584a16e62..7e932b60b 100644 --- a/src/renderer/features/player/components/right-controls.tsx +++ b/src/renderer/features/player/components/right-controls.tsx @@ -14,16 +14,17 @@ import { useAutoDJSettings, useCurrentServer, useFullScreenPlayerStore, - useGeneralSettings, useHotkeySettings, usePlayerData, usePlayerMuted, usePlayerSong, usePlayerVolume, useSetFullScreenPlayerStore, - useSettingsStore, useSettingsStoreActions, useSidebarRightExpanded, + useSideQueueType, + useVolumeWheelStep, + useVolumeWidth, } from '/@/renderer/store'; import { useFullScreenPlayerStoreActions } from '/@/renderer/store/full-screen-player.store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; @@ -114,7 +115,7 @@ const QueueButton = () => { const { t } = useTranslation(); const isSidebarRightExpanded = useSidebarRightExpanded(); const { setSideBar } = useAppStoreActions(); - const { sideQueueType } = useGeneralSettings(); + const sideQueueType = useSideQueueType(); const { bindings } = useHotkeySettings(); @@ -355,8 +356,8 @@ const VolumeButton = () => { const { bindings } = useHotkeySettings(); const volume = usePlayerVolume(); const muted = usePlayerMuted(); - const { volumeWheelStep } = useGeneralSettings(); - const volumeWidth = useSettingsStore((state) => state.general.volumeWidth); + const volumeWheelStep = useVolumeWheelStep(); + const volumeWidth = useVolumeWidth(); const { mediaToggleMute, setVolume } = usePlayer(); const isMinWidth = useMediaQuery('(max-width: 480px)'); diff --git a/src/renderer/features/player/hooks/use-media-session.ts b/src/renderer/features/player/hooks/use-media-session.ts index fbc6e5151..5af751267 100644 --- a/src/renderer/features/player/hooks/use-media-session.ts +++ b/src/renderer/features/player/hooks/use-media-session.ts @@ -8,6 +8,7 @@ import { usePlaybackSettings, usePlayerStore, useSettingsStore, + useSkipButtons, useTimestampStoreBase, } from '/@/renderer/store'; import { LibraryItem, QueueSong } from '/@/shared/types/domain-types'; @@ -18,7 +19,7 @@ const mediaSession = navigator.mediaSession; export const useMediaSession = () => { const { mediaSession: mediaSessionEnabled } = usePlaybackSettings(); const player = usePlayer(); - const skip = useSettingsStore((state) => state.general.skipButtons); + const skip = useSkipButtons(); const playbackType = useSettingsStore((state) => state.playback.type); const isMediaSessionEnabled = useMemo(() => { diff --git a/src/renderer/features/settings/components/advanced/advanced-tab.tsx b/src/renderer/features/settings/components/advanced/advanced-tab.tsx index 4c2b23971..39bf92d7f 100644 --- a/src/renderer/features/settings/components/advanced/advanced-tab.tsx +++ b/src/renderer/features/settings/components/advanced/advanced-tab.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { Fragment } from 'react/jsx-runtime'; import { AnalyticsSettings } from '/@/renderer/features/settings/components/advanced/analytics-settings'; @@ -16,7 +17,7 @@ const sections = [ { component: CacheSettings, key: 'cache' }, ]; -export const AdvancedTab = () => { +export const AdvancedTab = memo(() => { return ( {sections.map(({ component: Section, key }, index) => ( @@ -27,4 +28,4 @@ export const AdvancedTab = () => { ))} ); -}; +}); diff --git a/src/renderer/features/settings/components/advanced/analytics-settings.tsx b/src/renderer/features/settings/components/advanced/analytics-settings.tsx index cf8b9ee4d..d416f55ad 100644 --- a/src/renderer/features/settings/components/advanced/analytics-settings.tsx +++ b/src/renderer/features/settings/components/advanced/analytics-settings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -6,7 +7,7 @@ import { } from '/@/renderer/features/settings/components/settings-section'; import { Switch } from '/@/shared/components/switch/switch'; -export const AnalyticsSettings = () => { +export const AnalyticsSettings = memo(() => { const { t } = useTranslation(); const handleToggleAnalytics = (disable: boolean) => { @@ -36,4 +37,4 @@ export const AnalyticsSettings = () => { title={t('page.setting.analytics', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/advanced/export-import-settings.tsx b/src/renderer/features/settings/components/advanced/export-import-settings.tsx index 4084a6b9f..84d8977d6 100644 --- a/src/renderer/features/settings/components/advanced/export-import-settings.tsx +++ b/src/renderer/features/settings/components/advanced/export-import-settings.tsx @@ -1,6 +1,6 @@ import { openModal } from '@mantine/modals'; import { t } from 'i18next'; -import { useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { ExportImportSettingsModal } from '/@/renderer/components/export-import-settings-modal/export-import-settings-modal'; import { @@ -10,7 +10,7 @@ import { import { useSettingsForExport } from '/@/renderer/store'; import { Button } from '/@/shared/components/button/button'; -export const ExportImportSettings = () => { +export const ExportImportSettings = memo(() => { const settingForExport = useSettingsForExport(); const onExportSettings = useCallback(() => { @@ -68,4 +68,4 @@ export const ExportImportSettings = () => { title={t('page.setting.exportImport', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/advanced/logger-settings.tsx b/src/renderer/features/settings/components/advanced/logger-settings.tsx index c1424bb8f..8ef3a87f9 100644 --- a/src/renderer/features/settings/components/advanced/logger-settings.tsx +++ b/src/renderer/features/settings/components/advanced/logger-settings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -9,7 +10,7 @@ import { Select } from '/@/shared/components/select/select'; const DEFAULT_LOG_LEVEL: LogLevel = process.env.NODE_ENV === 'production' ? 'info' : 'debug'; -export const LoggerSettings = () => { +export const LoggerSettings = memo(() => { const { t } = useTranslation(); const getCurrentLogLevel = (): LogLevel => { @@ -84,4 +85,4 @@ export const LoggerSettings = () => { title={t('page.setting.logger', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/advanced/styles-settings.tsx b/src/renderer/features/settings/components/advanced/styles-settings.tsx index b4b543a8e..c4f4a3549 100644 --- a/src/renderer/features/settings/components/advanced/styles-settings.tsx +++ b/src/renderer/features/settings/components/advanced/styles-settings.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { SettingsOptions } from '/@/renderer/features/settings/components/settings-option'; @@ -10,7 +10,7 @@ import { Switch } from '/@/shared/components/switch/switch'; import { Text } from '/@/shared/components/text/text'; import { Textarea } from '/@/shared/components/textarea/textarea'; -export const StylesSettings = () => { +export const StylesSettings = memo(() => { const [open, setOpen] = useState(false); const { t } = useTranslation(); @@ -108,4 +108,4 @@ export const StylesSettings = () => { )} ); -}; +}); diff --git a/src/renderer/features/settings/components/general/application-settings.tsx b/src/renderer/features/settings/components/general/application-settings.tsx index d137d733c..6b804adea 100644 --- a/src/renderer/features/settings/components/general/application-settings.tsx +++ b/src/renderer/features/settings/components/general/application-settings.tsx @@ -2,7 +2,7 @@ import type { IpcRendererEvent } from 'electron'; import { t } from 'i18next'; import isElectron from 'is-electron'; -import { useCallback, useEffect, useState } from 'react'; +import { memo, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import i18n, { languages } from '/@/i18n/i18n'; @@ -78,7 +78,7 @@ if (isElectron()) { }); } -export const ApplicationSettings = () => { +export const ApplicationSettings = memo(() => { const { t } = useTranslation(); const settings = useGeneralSettings(); const fontSettings = useFontSettings(); @@ -618,4 +618,4 @@ export const ApplicationSettings = () => { title={t('page.setting.application', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/art-resolution-settings.tsx b/src/renderer/features/settings/components/general/art-resolution-settings.tsx index 12cbaa3e9..722f67e32 100644 --- a/src/renderer/features/settings/components/general/art-resolution-settings.tsx +++ b/src/renderer/features/settings/components/general/art-resolution-settings.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { memo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import i18n from '/@/i18n/i18n'; @@ -34,7 +34,7 @@ const options = [ }, ]; -export const ImageResolutionSettings = () => { +export const ImageResolutionSettings = memo(() => { const { t } = useTranslation(); const { setSettings } = useSettingsStoreActions(); const settings = useGeneralSettings(); @@ -108,4 +108,4 @@ export const ImageResolutionSettings = () => { )} ); -}; +}); diff --git a/src/renderer/features/settings/components/general/artist-settings.tsx b/src/renderer/features/settings/components/general/artist-settings.tsx index a53733448..1d77775a2 100644 --- a/src/renderer/features/settings/components/general/artist-settings.tsx +++ b/src/renderer/features/settings/components/general/artist-settings.tsx @@ -1,3 +1,5 @@ +import { memo } from 'react'; + import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items'; import { ArtistItem, @@ -14,7 +16,7 @@ const ARTIST_ITEMS: Array<[ArtistItem, string]> = [ [ArtistItem.SIMILAR_ARTISTS, 'page.albumArtistDetail.relatedArtists'], ]; -export const ArtistSettings = () => { +export const ArtistSettings = memo(() => { const { artistItems } = useGeneralSettings(); const { setArtistItems } = useSettingsStoreActions(); @@ -27,7 +29,7 @@ export const ArtistSettings = () => { title="setting.artistConfiguration" /> ); -}; +}); const ARTIST_RELEASE_TYPE_ITEMS: Array<[ArtistReleaseTypeItem, string]> = [ [ArtistReleaseTypeItem.APPEARS_ON, 'page.albumArtistDetail.appearsOn'], @@ -50,7 +52,7 @@ const ARTIST_RELEASE_TYPE_ITEMS: Array<[ArtistReleaseTypeItem, string]> = [ [ArtistReleaseTypeItem.RELEASE_TYPE_SPOKENWORD, 'releaseType.secondary.spokenWord'], ]; -export const ArtistReleaseTypeSettings = () => { +export const ArtistReleaseTypeSettings = memo(() => { const { artistReleaseTypeItems } = useGeneralSettings(); const { setArtistReleaseTypeItems } = useSettingsStoreActions(); @@ -63,4 +65,4 @@ export const ArtistReleaseTypeSettings = () => { title="setting.artistReleaseTypeConfiguration" /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/control-settings.tsx b/src/renderer/features/settings/components/general/control-settings.tsx index 126de3743..97d963442 100644 --- a/src/renderer/features/settings/components/general/control-settings.tsx +++ b/src/renderer/features/settings/components/general/control-settings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -21,7 +22,7 @@ import { Text } from '/@/shared/components/text/text'; import { Tooltip } from '/@/shared/components/tooltip/tooltip'; import { Play } from '/@/shared/types/types'; -export const ControlSettings = () => { +export const ControlSettings = memo(() => { const { t } = useTranslation(); const settings = useGeneralSettings(); const playerbarSlider = usePlayerbarSlider(); @@ -486,4 +487,4 @@ export const ControlSettings = () => { title={t('page.setting.controls', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/general-tab.tsx b/src/renderer/features/settings/components/general/general-tab.tsx index f0218f6b0..f439842b9 100644 --- a/src/renderer/features/settings/components/general/general-tab.tsx +++ b/src/renderer/features/settings/components/general/general-tab.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { Fragment } from 'react/jsx-runtime'; import { ApplicationSettings } from '/@/renderer/features/settings/components/general/application-settings'; @@ -14,7 +14,7 @@ import { Divider } from '/@/shared/components/divider/divider'; import { Stack } from '/@/shared/components/stack/stack'; import { ServerFeature } from '/@/shared/types/features-types'; -export const GeneralTab = () => { +export const GeneralTab = memo(() => { const server = useCurrentServer(); const supportsSmartPlaylists = hasFeature(server, ServerFeature.PLAYLISTS_SMART); @@ -45,4 +45,4 @@ export const GeneralTab = () => { ))} ); -}; +}); diff --git a/src/renderer/features/settings/components/general/home-settings.tsx b/src/renderer/features/settings/components/general/home-settings.tsx index cd4007a36..4e9b48c14 100644 --- a/src/renderer/features/settings/components/general/home-settings.tsx +++ b/src/renderer/features/settings/components/general/home-settings.tsx @@ -1,3 +1,5 @@ +import { memo } from 'react'; + import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items'; import { HomeItem, @@ -15,7 +17,7 @@ const HOME_ITEMS: Array<[string, string]> = [ [HomeItem.MOST_PLAYED, 'page.home.mostPlayed'], ]; -export const HomeSettings = () => { +export const HomeSettings = memo(() => { const { homeItems } = useGeneralSettings(); const { setHomeItems } = useSettingsStoreActions(); @@ -28,4 +30,4 @@ export const HomeSettings = () => { title="setting.homeConfiguration" /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/lyric-settings.tsx b/src/renderer/features/settings/components/general/lyric-settings.tsx index 73fbbccac..aba66a248 100644 --- a/src/renderer/features/settings/components/general/lyric-settings.tsx +++ b/src/renderer/features/settings/components/general/lyric-settings.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { languages } from '/@/i18n/i18n'; @@ -16,7 +17,7 @@ import { LyricSource } from '/@/shared/types/domain-types'; const localSettings = isElectron() ? window.api.localSettings : null; -export const LyricSettings = () => { +export const LyricSettings = memo(() => { const { t } = useTranslation(); const settings = useLyricsSettings(); const { setSettings } = useSettingsStoreActions(); @@ -210,4 +211,4 @@ export const LyricSettings = () => { title={t('page.setting.lyrics', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/path-settings.tsx b/src/renderer/features/settings/components/general/path-settings.tsx index 554965317..3694db6a0 100644 --- a/src/renderer/features/settings/components/general/path-settings.tsx +++ b/src/renderer/features/settings/components/general/path-settings.tsx @@ -1,22 +1,19 @@ import { useQuery } from '@tanstack/react-query'; +import { memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; -import { - useCurrentServerId, - useGeneralSettings, - useSettingsStore, - useSettingsStoreActions, -} from '/@/renderer/store'; +import { useCurrentServerId, useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Code } from '/@/shared/components/code/code'; import { Group } from '/@/shared/components/group/group'; import { Stack } from '/@/shared/components/stack/stack'; import { TextInput } from '/@/shared/components/text-input/text-input'; import { Text } from '/@/shared/components/text/text'; +import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback'; import { Played } from '/@/shared/types/domain-types'; -export const PathSettings = () => { +export const PathSettings = memo(() => { const { t } = useTranslation(); const serverId = useCurrentServerId(); const randomSong = useQuery({ @@ -31,6 +28,37 @@ export const PathSettings = () => { const { pathReplace, pathReplaceWith } = useGeneralSettings(); const { setSettings } = useSettingsStoreActions(); + const [localPathReplace, setLocalPathReplace] = useState(pathReplace); + const [localPathReplaceWith, setLocalPathReplaceWith] = useState(pathReplaceWith); + + useEffect(() => { + setLocalPathReplace(pathReplace); + }, [pathReplace]); + + useEffect(() => { + setLocalPathReplaceWith(pathReplaceWith); + }, [pathReplaceWith]); + + const debouncedSetPathReplace = useDebouncedCallback((value: string) => { + setSettings({ + general: { + pathReplace: value, + }, + }); + + randomSong.refetch(); + }, 500); + + const debouncedSetPathReplaceWith = useDebouncedCallback((value: string) => { + setSettings({ + general: { + pathReplaceWith: value, + }, + }); + + randomSong.refetch(); + }, 500); + return ( @@ -50,34 +78,28 @@ export const PathSettings = () => { - setSettings({ - general: { - ...useSettingsStore.getState().general, - pathReplace: e.currentTarget.value, - }, - }) - } + onChange={(e) => { + const value = e.currentTarget.value; + setLocalPathReplace(value); + debouncedSetPathReplace(value); + }} placeholder={t('setting.pathReplace_optionRemovePrefix', { postProcess: 'sentenceCase', })} - value={pathReplace} + value={localPathReplace} /> - setSettings({ - general: { - ...useSettingsStore.getState().general, - pathReplaceWith: e.currentTarget.value, - }, - }) - } + onChange={(e) => { + const value = e.currentTarget.value; + setLocalPathReplaceWith(value); + debouncedSetPathReplaceWith(value); + }} placeholder={t('setting.pathReplace_optionAddPrefix', { postProcess: 'sentenceCase', })} - value={pathReplaceWith} + value={localPathReplaceWith} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/query-builder-settings.tsx b/src/renderer/features/settings/components/general/query-builder-settings.tsx index 024a1f7ce..3be2f3a23 100644 --- a/src/renderer/features/settings/components/general/query-builder-settings.tsx +++ b/src/renderer/features/settings/components/general/query-builder-settings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -21,7 +22,7 @@ const QUERY_VALUE_INPUT_TYPES = [ { label: 'String', value: 'string' }, ] as const; -export const QueryBuilderSettings = () => { +export const QueryBuilderSettings = memo(() => { const { t } = useTranslation(); const queryBuilder = useQueryBuilderSettings(); const { setSettings } = useSettingsStoreActions(); @@ -148,4 +149,4 @@ export const QueryBuilderSettings = () => { })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/scrobble-settings.tsx b/src/renderer/features/settings/components/general/scrobble-settings.tsx index 2acf840c8..661c9f20a 100644 --- a/src/renderer/features/settings/components/general/scrobble-settings.tsx +++ b/src/renderer/features/settings/components/general/scrobble-settings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -10,7 +11,7 @@ import { Slider } from '/@/shared/components/slider/slider'; import { Switch } from '/@/shared/components/switch/switch'; import { toast } from '/@/shared/components/toast/toast'; -export const ScrobbleSettings = () => { +export const ScrobbleSettings = memo(() => { const { t } = useTranslation(); const settings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); @@ -24,9 +25,7 @@ export const ScrobbleSettings = () => { onChange={(e) => { setSettings({ playback: { - ...settings, scrobble: { - ...settings.scrobble, enabled: e.currentTarget.checked, }, }, @@ -51,9 +50,7 @@ export const ScrobbleSettings = () => { onChange={(e) => { setSettings({ playback: { - ...settings, scrobble: { - ...settings.scrobble, scrobbleAtPercentage: e, }, }, @@ -79,9 +76,7 @@ export const ScrobbleSettings = () => { if (e === '') return; setSettings({ playback: { - ...settings, scrobble: { - ...settings.scrobble, scrobbleAtDuration: Number(e), }, }, @@ -125,9 +120,7 @@ export const ScrobbleSettings = () => { setSettings({ playback: { - ...settings, scrobble: { - ...settings.scrobble, notify: e.currentTarget.checked, }, }, @@ -150,4 +143,4 @@ export const ScrobbleSettings = () => { title={t('page.setting.scrobble', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/sidebar-settings.tsx b/src/renderer/features/settings/components/general/sidebar-settings.tsx index 52fff9757..b05fa7dda 100644 --- a/src/renderer/features/settings/components/general/sidebar-settings.tsx +++ b/src/renderer/features/settings/components/general/sidebar-settings.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent } from 'react'; +import { ChangeEvent, memo } from 'react'; import { useTranslation } from 'react-i18next'; import { SidebarReorder } from '/@/renderer/features/settings/components/general/sidebar-reorder'; @@ -9,7 +9,7 @@ import { import { useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store'; import { Switch } from '/@/shared/components/switch/switch'; -export const SidebarSettings = () => { +export const SidebarSettings = memo(() => { const { t } = useTranslation(); const settings = useGeneralSettings(); const { setSettings } = useSettingsStoreActions(); @@ -17,7 +17,6 @@ export const SidebarSettings = () => { const handleSetSidebarPlaylistList = (e: ChangeEvent) => { setSettings({ general: { - ...settings, sidebarPlaylistList: e.target.checked, }, }); @@ -26,7 +25,6 @@ export const SidebarSettings = () => { const handleSetSidebarCollapsedNavigation = (e: ChangeEvent) => { setSettings({ general: { - ...settings, sidebarCollapsedNavigation: e.target.checked, }, }); @@ -67,7 +65,6 @@ export const SidebarSettings = () => { onChange={(e) => { setSettings({ general: { - ...settings, showLyricsInSidebar: e.currentTarget.checked, }, }); @@ -88,7 +85,6 @@ export const SidebarSettings = () => { onChange={(e) => { setSettings({ general: { - ...settings, showVisualizerInSidebar: e.currentTarget.checked, }, }); @@ -109,7 +105,6 @@ export const SidebarSettings = () => { onChange={(e) => { setSettings({ general: { - ...settings, combinedLyricsAndVisualizer: e.currentTarget.checked, }, }); @@ -131,4 +126,4 @@ export const SidebarSettings = () => { title={t('page.setting.sidebar', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/general/theme-settings.tsx b/src/renderer/features/settings/components/general/theme-settings.tsx index 86656ed30..a5029bdda 100644 --- a/src/renderer/features/settings/components/general/theme-settings.tsx +++ b/src/renderer/features/settings/components/general/theme-settings.tsx @@ -1,5 +1,5 @@ import isElectron from 'is-electron'; -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import i18n from '/@/i18n/i18n'; @@ -85,7 +85,7 @@ const renderThemeOption = ({ option }: { option: { label: string; value: string ); }; -export const ThemeSettings = () => { +export const ThemeSettings = memo(() => { const { t } = useTranslation(); const settings = useGeneralSettings(); const { setSettings } = useSettingsStoreActions(); @@ -101,7 +101,6 @@ export const ThemeSettings = () => { onChange={(e) => { setSettings({ general: { - ...settings, followSystemTheme: e.currentTarget.checked, }, }); @@ -135,7 +134,6 @@ export const ThemeSettings = () => { setSettings({ general: { - ...settings, theme, }, }); @@ -167,7 +165,6 @@ export const ThemeSettings = () => { onChange={(e) => { setSettings({ general: { - ...settings, themeDark: e as AppTheme, }, }); @@ -191,7 +188,6 @@ export const ThemeSettings = () => { onChange={(e) => { setSettings({ general: { - ...settings, themeLight: e as AppTheme, }, }); @@ -214,7 +210,6 @@ export const ThemeSettings = () => { onChange={(e) => { setSettings({ general: { - ...settings, useThemeAccentColor: e.currentTarget.checked, }, }); @@ -238,7 +233,6 @@ export const ThemeSettings = () => { onChangeEnd={(e) => { setSettings({ general: { - ...settings, accent: e, }, }); @@ -270,4 +264,4 @@ export const ThemeSettings = () => { title={t('page.setting.theme', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/hotkeys/hotkey-manager-settings.tsx b/src/renderer/features/settings/components/hotkeys/hotkey-manager-settings.tsx index e59da6037..7238a5d61 100644 --- a/src/renderer/features/settings/components/hotkeys/hotkey-manager-settings.tsx +++ b/src/renderer/features/settings/components/hotkeys/hotkey-manager-settings.tsx @@ -1,6 +1,6 @@ import isElectron from 'is-electron'; import debounce from 'lodash/debounce'; -import { ChangeEvent, KeyboardEvent, useCallback, useMemo, useState } from 'react'; +import { ChangeEvent, KeyboardEvent, memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styles from './hotkeys-manager-settings.module.css'; @@ -120,9 +120,9 @@ const BINDINGS_MAP: Record = { zoomOut: i18n.t('setting.hotkey', { context: 'zoomOut', postProcess: 'sentenceCase' }), }; -export const HotkeyManagerSettings = () => { +export const HotkeyManagerSettings = memo(() => { const { t } = useTranslation(); - const { bindings, globalMediaHotkeys } = useHotkeySettings(); + const { bindings } = useHotkeySettings(); const { setSettings } = useSettingsStoreActions(); const [selected, setSelected] = useState(null); const keyword = useSettingSearchContext(); @@ -162,7 +162,6 @@ export const HotkeyManagerSettings = () => { setSettings({ hotkeys: { bindings: updatedBindings, - globalMediaHotkeys, }, }); @@ -188,13 +187,12 @@ export const HotkeyManagerSettings = () => { setSettings({ hotkeys: { bindings: updatedBindings, - globalMediaHotkeys, }, }); ipc?.send('set-global-shortcuts', updatedBindings); }, - [bindings, globalMediaHotkeys, setSettings], + [bindings, setSettings], ); const handleClearHotkey = useCallback( @@ -207,13 +205,12 @@ export const HotkeyManagerSettings = () => { setSettings({ hotkeys: { bindings: updatedBindings, - globalMediaHotkeys, }, }); ipc?.send('set-global-shortcuts', updatedBindings); }, - [bindings, globalMediaHotkeys, setSettings], + [bindings, setSettings], ); const duplicateHotkeyMap = useMemo(() => { @@ -367,4 +364,4 @@ export const HotkeyManagerSettings = () => { options={options} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/hotkeys/hotkeys-tab.tsx b/src/renderer/features/settings/components/hotkeys/hotkeys-tab.tsx index 90de2fcbd..741f834c8 100644 --- a/src/renderer/features/settings/components/hotkeys/hotkeys-tab.tsx +++ b/src/renderer/features/settings/components/hotkeys/hotkeys-tab.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { Fragment } from 'react/jsx-runtime'; import { HotkeyManagerSettings } from '/@/renderer/features/settings/components/hotkeys/hotkey-manager-settings'; @@ -13,7 +14,7 @@ const sections = [ { component: HotkeyManagerSettings, key: 'hotkey-manager' }, ]; -export const HotkeysTab = () => { +export const HotkeysTab = memo(() => { return ( {sections.map(({ component: Section, hidden, key }, index) => ( @@ -24,4 +25,4 @@ export const HotkeysTab = () => { ))} ); -}; +}); diff --git a/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx b/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx index 453112a6b..e3ea101cb 100644 --- a/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx +++ b/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -6,11 +7,7 @@ import { SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; import { openRestartRequiredToast } from '/@/renderer/features/settings/restart-toast'; -import { - useHotkeySettings, - usePlaybackSettings, - useSettingsStoreActions, -} from '/@/renderer/store/settings.store'; +import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store'; import { Switch } from '/@/shared/components/switch/switch'; import { PlayerType } from '/@/shared/types/types'; @@ -18,11 +15,9 @@ const isLinux = isElectron() ? window.api.utils.isLinux() : false; const isDesktop = isElectron(); const localSettings = isElectron() ? window.api.localSettings : null; -export const MediaSessionSettings = () => { +export const MediaSessionSettings = memo(() => { const { t } = useTranslation(); const { mediaSession, type: playbackType } = usePlaybackSettings(); - const playbackSettings = usePlaybackSettings(); - const hotkeySettings = useHotkeySettings(); const { setSettings } = useSettingsStoreActions(); function handleMediaSessionChange(e: boolean) { @@ -31,7 +26,6 @@ export const MediaSessionSettings = () => { localSettings!.set('global_media_hotkeys', false); setSettings({ hotkeys: { - ...hotkeySettings, globalMediaHotkeys: false, }, }); @@ -40,7 +34,6 @@ export const MediaSessionSettings = () => { localSettings!.set('mediaSession', e); setSettings({ playback: { - ...playbackSettings, mediaSession: e, }, }); @@ -70,4 +63,4 @@ export const MediaSessionSettings = () => { ]; return ; -}; +}); diff --git a/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx b/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx index 49810dcdc..29673e231 100644 --- a/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx +++ b/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -11,10 +12,9 @@ import { Switch } from '/@/shared/components/switch/switch'; const localSettings = isElectron() ? window.api.localSettings : null; -export const WindowHotkeySettings = () => { +export const WindowHotkeySettings = memo(() => { const { t } = useTranslation(); const settings = useHotkeySettings(); - const playbackSettings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); const { mediaSession } = usePlaybackSettings(); @@ -28,7 +28,6 @@ export const WindowHotkeySettings = () => { localSettings!.set('global_media_hotkeys', e.currentTarget.checked); setSettings({ hotkeys: { - ...settings, globalMediaHotkeys: e.currentTarget.checked, }, }); @@ -45,7 +44,6 @@ export const WindowHotkeySettings = () => { localSettings!.set('mediaSession', false); setSettings({ playback: { - ...playbackSettings, mediaSession: false, }, }); @@ -64,4 +62,4 @@ export const WindowHotkeySettings = () => { ]; return ; -}; +}); diff --git a/src/renderer/features/settings/components/playback/audio-settings.tsx b/src/renderer/features/settings/components/playback/audio-settings.tsx index 46f9f353f..e07405e75 100644 --- a/src/renderer/features/settings/components/playback/audio-settings.tsx +++ b/src/renderer/features/settings/components/playback/audio-settings.tsx @@ -1,5 +1,5 @@ import isElectron from 'is-electron'; -import { useEffect, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -20,7 +20,7 @@ const getAudioDevice = async () => { return (devices || []).filter((dev: MediaDeviceInfo) => dev.kind === 'audiooutput'); }; -export const AudioSettings = () => { +export const AudioSettings = memo(() => { const { t } = useTranslation(); const settings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); @@ -61,7 +61,7 @@ export const AudioSettings = () => { defaultValue={settings.type} disabled={status === PlayerStatus.PLAYING} onChange={(e) => { - setSettings({ playback: { ...settings, type: e as PlayerType } }); + setSettings({ playback: { type: e as PlayerType } }); ipc?.send('settings-set', { property: 'playbackType', value: e }); }} /> @@ -84,7 +84,7 @@ export const AudioSettings = () => { data={audioDevices} defaultValue={settings.audioDeviceId} disabled={settings.type !== PlayerType.WEB} - onChange={(e) => setSettings({ playback: { ...settings, audioDeviceId: e } })} + onChange={(e) => setSettings({ playback: { audioDeviceId: e } })} /> ), description: t('setting.audioDevice', { @@ -100,7 +100,7 @@ export const AudioSettings = () => { defaultChecked={settings.webAudio} onChange={(e) => { setSettings({ - playback: { ...settings, webAudio: e.currentTarget.checked }, + playback: { webAudio: e.currentTarget.checked }, }); }} /> @@ -121,7 +121,7 @@ export const AudioSettings = () => { defaultChecked={settings.preservePitch} onChange={(e) => { setSettings({ - playback: { ...settings, preservePitch: e.currentTarget.checked }, + playback: { preservePitch: e.currentTarget.checked }, }); }} /> @@ -142,7 +142,6 @@ export const AudioSettings = () => { onChange={(e) => { setSettings({ playback: { - ...settings, audioFadeOnStatusChange: e.currentTarget.checked, }, }); @@ -165,4 +164,4 @@ export const AudioSettings = () => { title={t('page.setting.audio', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/playback/auto-dj-settings.tsx b/src/renderer/features/settings/components/playback/auto-dj-settings.tsx index 897b6e71b..7ea98b221 100644 --- a/src/renderer/features/settings/components/playback/auto-dj-settings.tsx +++ b/src/renderer/features/settings/components/playback/auto-dj-settings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -7,7 +8,7 @@ import { import { useAutoDJSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store'; import { NumberInput } from '/@/shared/components/number-input/number-input'; -export const AutoDJSettings = () => { +export const AutoDJSettings = memo(() => { const { t } = useTranslation(); const settings = useAutoDJSettings(); const { setSettings } = useSettingsStoreActions(); @@ -23,7 +24,6 @@ export const AutoDJSettings = () => { onChange={(e) => { setSettings({ autoDJ: { - ...settings, itemCount: Number(e), }, }); @@ -47,7 +47,6 @@ export const AutoDJSettings = () => { onChange={(e) => { setSettings({ autoDJ: { - ...settings, timing: Number(e), }, }); @@ -69,4 +68,4 @@ export const AutoDJSettings = () => { title={t('setting.autoDJ', { postProcess: 'titleCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/playback/mpv-settings.tsx b/src/renderer/features/settings/components/playback/mpv-settings.tsx index 6b099d854..d4426e0c7 100644 --- a/src/renderer/features/settings/components/playback/mpv-settings.tsx +++ b/src/renderer/features/settings/components/playback/mpv-settings.tsx @@ -1,5 +1,5 @@ import isElectron from 'is-electron'; -import { useEffect, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -68,7 +68,7 @@ export const getMpvProperties = (settings: SettingsState['playback']['mpvPropert return properties; }; -export const MpvSettings = () => { +export const MpvSettings = memo(() => { const { t } = useTranslation(); const settings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); @@ -114,9 +114,7 @@ export const MpvSettings = () => { ) => { setSettings({ playback: { - ...settings, mpvProperties: { - ...settings.mpvProperties, [setting]: value, }, }, @@ -146,7 +144,6 @@ export const MpvSettings = () => { const handleSetExtraParameters = (data: string[]) => { setSettings({ playback: { - ...settings, mpvExtraParameters: data, }, }); @@ -421,4 +418,4 @@ export const MpvSettings = () => { ); -}; +}); diff --git a/src/renderer/features/settings/components/playback/playback-tab.tsx b/src/renderer/features/settings/components/playback/playback-tab.tsx index 107696f5d..71f87696b 100644 --- a/src/renderer/features/settings/components/playback/playback-tab.tsx +++ b/src/renderer/features/settings/components/playback/playback-tab.tsx @@ -1,5 +1,6 @@ import isElectron from 'is-electron'; -import { lazy, Suspense, useMemo } from 'react'; +import { lazy, memo, Suspense, useMemo } from 'react'; +import { shallow } from 'zustand/shallow'; import { AudioSettings } from '/@/renderer/features/settings/components/playback/audio-settings'; import { AutoDJSettings } from '/@/renderer/features/settings/components/playback/auto-dj-settings'; @@ -16,9 +17,14 @@ const MpvSettings = lazy(() => }), ); -export const PlaybackTab = () => { - const audioType = useSettingsStore((state) => state.playback.type); - const useWebAudio = useSettingsStore((state) => state.playback.webAudio); +export const PlaybackTab = memo(() => { + const { audioType, useWebAudio } = useSettingsStore( + (state) => ({ + audioType: state.playback.type, + useWebAudio: state.playback.webAudio, + }), + shallow, + ); const hasFancyAudio = useMemo(() => { return ( @@ -39,4 +45,4 @@ export const PlaybackTab = () => { ); -}; +}); diff --git a/src/renderer/features/settings/components/playback/player-filter-settings.tsx b/src/renderer/features/settings/components/playback/player-filter-settings.tsx index 0d6a7a802..15243ed56 100644 --- a/src/renderer/features/settings/components/playback/player-filter-settings.tsx +++ b/src/renderer/features/settings/components/playback/player-filter-settings.tsx @@ -1,5 +1,5 @@ import { nanoid } from 'nanoid/non-secure'; -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -262,7 +262,7 @@ const FilterValueInput = ({ } }; -export const PlayerFilterSettings = () => { +export const PlayerFilterSettings = memo(() => { const { t } = useTranslation(); const filters = useSettingsStore((state) => state.playback.filters); const { setPlaybackFilters } = useSettingsStoreActions(); @@ -432,4 +432,4 @@ export const PlayerFilterSettings = () => { title={t('page.setting.playerFilters', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/playback/transcode-settings.tsx b/src/renderer/features/settings/components/playback/transcode-settings.tsx index 0cb30dd4c..eba5ffb37 100644 --- a/src/renderer/features/settings/components/playback/transcode-settings.tsx +++ b/src/renderer/features/settings/components/playback/transcode-settings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -9,7 +10,7 @@ import { NumberInput } from '/@/shared/components/number-input/number-input'; import { Switch } from '/@/shared/components/switch/switch'; import { TextInput } from '/@/shared/components/text-input/text-input'; -export const TranscodeSettings = () => { +export const TranscodeSettings = memo(() => { const { t } = useTranslation(); const { transcode } = usePlaybackSettings(); const { setTranscodingConfig } = useSettingsStoreActions(); @@ -92,4 +93,4 @@ export const TranscodeSettings = () => { title={t('page.setting.transcoding', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/settings-option.tsx b/src/renderer/features/settings/components/settings-option.tsx index 85b7b890b..b14329a4b 100644 --- a/src/renderer/features/settings/components/settings-option.tsx +++ b/src/renderer/features/settings/components/settings-option.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { memo } from 'react'; import { Group } from '/@/shared/components/group/group'; import { Icon } from '/@/shared/components/icon/icon'; @@ -13,38 +13,40 @@ interface SettingsOptionProps { title: React.ReactNode | string; } -export const SettingsOptions = ({ control, description, note, title }: SettingsOptionProps) => { - return ( - <> - - - - - {title} - - {note && ( - - - +export const SettingsOptions = memo( + ({ control, description, note, title }: SettingsOptionProps) => { + return ( + <> + + + + + {title} + + {note && ( + + + + )} + + {React.isValidElement(description) ? ( + description + ) : ( + + {description} + )} - - {React.isValidElement(description) ? ( - description - ) : ( - - {description} - - )} - - {control} - - - ); -}; + + {control} + + + ); + }, +); diff --git a/src/renderer/features/settings/components/window/cache-settngs.tsx b/src/renderer/features/settings/components/window/cache-settngs.tsx index 70cf677cb..491e9e8d4 100644 --- a/src/renderer/features/settings/components/window/cache-settngs.tsx +++ b/src/renderer/features/settings/components/window/cache-settngs.tsx @@ -1,7 +1,7 @@ import { closeAllModals, openModal } from '@mantine/modals'; import { useQueryClient } from '@tanstack/react-query'; import isElectron from 'is-electron'; -import { useCallback, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -14,7 +14,7 @@ import { toast } from '/@/shared/components/toast/toast'; const browser = isElectron() ? window.api.browser : null; -export const CacheSettings = () => { +export const CacheSettings = memo(() => { const [isClearing, setIsClearing] = useState(false); const queryClient = useQueryClient(); const { t } = useTranslation(); @@ -115,4 +115,4 @@ export const CacheSettings = () => { )} ); -}; +}); diff --git a/src/renderer/features/settings/components/window/discord-settings.tsx b/src/renderer/features/settings/components/window/discord-settings.tsx index 9b8b8cfcc..3c3cf7759 100644 --- a/src/renderer/features/settings/components/window/discord-settings.tsx +++ b/src/renderer/features/settings/components/window/discord-settings.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -16,7 +17,7 @@ import { Select } from '/@/shared/components/select/select'; import { Switch } from '/@/shared/components/switch/switch'; import { TextInput } from '/@/shared/components/text-input/text-input'; -export const DiscordSettings = () => { +export const DiscordSettings = memo(() => { const { t } = useTranslation(); const settings = useDiscordSettings(); const generalSettings = useGeneralSettings(); @@ -30,7 +31,6 @@ export const DiscordSettings = () => { onChange={(e) => { setSettings({ discord: { - ...settings, enabled: e.currentTarget.checked, }, }); @@ -58,7 +58,6 @@ export const DiscordSettings = () => { onBlur={(e) => { setSettings({ discord: { - ...settings, clientId: e.currentTarget.value, }, }); @@ -84,7 +83,6 @@ export const DiscordSettings = () => { onChange={(e) => { setSettings({ discord: { - ...settings, showPaused: e.currentTarget.checked, }, }); @@ -107,7 +105,6 @@ export const DiscordSettings = () => { onChange={(e) => { setSettings({ discord: { - ...settings, showAsListening: e.currentTarget.checked, }, }); @@ -150,7 +147,6 @@ export const DiscordSettings = () => { if (!e) return; setSettings({ discord: { - ...settings, displayType: e as DiscordDisplayType, }, }); @@ -195,7 +191,6 @@ export const DiscordSettings = () => { if (!e) return; setSettings({ discord: { - ...settings, linkType: e as DiscordLinkType, }, }); @@ -222,7 +217,6 @@ export const DiscordSettings = () => { onChange={(e) => { setSettings({ discord: { - ...settings, showServerImage: e.currentTarget.checked, }, }); @@ -248,7 +242,6 @@ export const DiscordSettings = () => { onBlur={(e) => { setSettings({ general: { - ...generalSettings, lastfmApiKey: e.currentTarget.value, }, }); @@ -274,4 +267,4 @@ export const DiscordSettings = () => { title={t('page.setting.discord', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/window/password-settings.tsx b/src/renderer/features/settings/components/window/password-settings.tsx index e01b79adb..ff9da9592 100644 --- a/src/renderer/features/settings/components/window/password-settings.tsx +++ b/src/renderer/features/settings/components/window/password-settings.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -17,7 +18,7 @@ const PASSWORD_SETTINGS: { label: string; value: string }[] = [ { label: 'KDE 6 (kwallet6)', value: 'kwallet6' }, ]; -export const PasswordSettings = () => { +export const PasswordSettings = memo(() => { const { t } = useTranslation(); const settings = useGeneralSettings(); const { setSettings } = useSettingsStoreActions(); @@ -53,4 +54,4 @@ export const PasswordSettings = () => { ]; return ; -}; +}); diff --git a/src/renderer/features/settings/components/window/remote-settings.tsx b/src/renderer/features/settings/components/window/remote-settings.tsx index 0303810d6..28b185526 100644 --- a/src/renderer/features/settings/components/window/remote-settings.tsx +++ b/src/renderer/features/settings/components/window/remote-settings.tsx @@ -1,5 +1,6 @@ import isElectron from 'is-electron'; import debounce from 'lodash/debounce'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { SettingsSection } from '/@/renderer/features/settings/components/settings-section'; @@ -12,7 +13,7 @@ import { toast } from '/@/shared/components/toast/toast'; const remote = isElectron() ? window.api.remote : null; -export const RemoteSettings = () => { +export const RemoteSettings = memo(() => { const { t } = useTranslation(); const settings = useRemoteSettings(); const { setSettings } = useSettingsStoreActions(); @@ -25,7 +26,6 @@ export const RemoteSettings = () => { if (errorMsg === null) { setSettings({ remote: { - ...settings, enabled, }, }); @@ -44,7 +44,6 @@ export const RemoteSettings = () => { if (!errorMsg) { setSettings({ remote: { - ...settings, port, }, }); @@ -115,7 +114,6 @@ export const RemoteSettings = () => { remote!.updateUsername(username); setSettings({ remote: { - ...settings, username, }, }); @@ -139,7 +137,6 @@ export const RemoteSettings = () => { remote!.updatePassword(password); setSettings({ remote: { - ...settings, password, }, }); @@ -161,4 +158,4 @@ export const RemoteSettings = () => { title={t('page.setting.remote', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/window/update-settings.tsx b/src/renderer/features/settings/components/window/update-settings.tsx index 8cc624f93..6332ca0bc 100644 --- a/src/renderer/features/settings/components/window/update-settings.tsx +++ b/src/renderer/features/settings/components/window/update-settings.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -13,10 +14,10 @@ const localSettings = isElectron() ? window.api.localSettings : null; const utils = isElectron() ? window.api.utils : null; function disableAutoUpdates(): boolean { - return !isElectron() || utils?.disableAutoUpdates(); + return Boolean(!isElectron() || utils?.disableAutoUpdates()); } -export const UpdateSettings = () => { +export const UpdateSettings = memo(() => { const { t } = useTranslation(); const settings = useWindowSettings(); const { setSettings } = useSettingsStoreActions(); @@ -49,7 +50,6 @@ export const UpdateSettings = () => { localSettings?.set('release_channel', value); setSettings({ window: { - ...settings, releaseChannel: value as 'beta' | 'latest', }, }); @@ -74,7 +74,6 @@ export const UpdateSettings = () => { localSettings?.set('disable_auto_updates', e.currentTarget.checked); setSettings({ window: { - ...settings, disableAutoUpdate: e.currentTarget.checked, }, }); @@ -96,4 +95,4 @@ export const UpdateSettings = () => { title={t('page.setting.updates', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/window/window-settings.tsx b/src/renderer/features/settings/components/window/window-settings.tsx index 2a4e711c7..cdfb0923a 100644 --- a/src/renderer/features/settings/components/window/window-settings.tsx +++ b/src/renderer/features/settings/components/window/window-settings.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -20,7 +21,7 @@ const WINDOW_BAR_OPTIONS = [ const localSettings = isElectron() ? window.api.localSettings : null; -export const WindowSettings = () => { +export const WindowSettings = memo(() => { const { t } = useTranslation(); const settings = useWindowSettings(); const { setSettings } = useSettingsStoreActions(); @@ -50,7 +51,6 @@ export const WindowSettings = () => { localSettings?.set('window_window_bar_style', e as Platform); setSettings({ window: { - ...settings, windowBarStyle: e as Platform, }, }); @@ -77,7 +77,6 @@ export const WindowSettings = () => { if (e.currentTarget.checked) { setSettings({ window: { - ...settings, tray: true, }, }); @@ -88,7 +87,6 @@ export const WindowSettings = () => { setSettings({ window: { - ...settings, exitToTray: false, minimizeToTray: false, startMinimized: false, @@ -120,7 +118,6 @@ export const WindowSettings = () => { localSettings?.set('window_minimize_to_tray', e.currentTarget.checked); setSettings({ window: { - ...settings, minimizeToTray: e.currentTarget.checked, }, }); @@ -145,7 +142,6 @@ export const WindowSettings = () => { localSettings?.set('window_exit_to_tray', e.currentTarget.checked); setSettings({ window: { - ...settings, exitToTray: e.currentTarget.checked, }, }); @@ -170,7 +166,6 @@ export const WindowSettings = () => { localSettings?.set('window_start_minimized', e.currentTarget.checked); setSettings({ window: { - ...settings, startMinimized: e.currentTarget.checked, }, }); @@ -198,7 +193,6 @@ export const WindowSettings = () => { ); setSettings({ window: { - ...settings, preventSleepOnPlayback: e.currentTarget.checked, }, }); @@ -220,4 +214,4 @@ export const WindowSettings = () => { title={t('page.setting.application', { postProcess: 'sentenceCase' })} /> ); -}; +}); diff --git a/src/renderer/features/settings/components/window/window-tab.tsx b/src/renderer/features/settings/components/window/window-tab.tsx index 494525460..f92b07569 100644 --- a/src/renderer/features/settings/components/window/window-tab.tsx +++ b/src/renderer/features/settings/components/window/window-tab.tsx @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import { memo } from 'react'; import { Fragment } from 'react/jsx-runtime'; import { DiscordSettings } from '/@/renderer/features/settings/components/window/discord-settings'; @@ -17,7 +18,7 @@ const sections = [ { component: PasswordSettings, hidden: !utils?.isLinux(), key: 'password' }, ]; -export const WindowTab = () => { +export const WindowTab = memo(() => { return ( {sections.map(({ component: Section, hidden, key }, index) => ( @@ -28,4 +29,4 @@ export const WindowTab = () => { ))} ); -}; +}); diff --git a/src/renderer/features/sidebar/components/collapsed-sidebar.tsx b/src/renderer/features/sidebar/components/collapsed-sidebar.tsx index d6214727a..20859e125 100644 --- a/src/renderer/features/sidebar/components/collapsed-sidebar.tsx +++ b/src/renderer/features/sidebar/components/collapsed-sidebar.tsx @@ -17,7 +17,8 @@ import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu'; import { SidebarItemType, useCurrentServer, - useGeneralSettings, + useSidebarCollapsedNavigation, + useSidebarItems, useWindowSettings, } from '/@/renderer/store'; import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu'; @@ -32,7 +33,8 @@ export const CollapsedSidebar = () => { const { t } = useTranslation(); const navigate = useNavigate(); const { windowBarStyle } = useWindowSettings(); - const { sidebarCollapsedNavigation, sidebarItems } = useGeneralSettings(); + const sidebarCollapsedNavigation = useSidebarCollapsedNavigation(); + const sidebarItems = useSidebarItems(); const currentServer = useCurrentServer(); const translatedSidebarItemMap = useMemo( diff --git a/src/renderer/features/sidebar/components/mobile-sidebar.tsx b/src/renderer/features/sidebar/components/mobile-sidebar.tsx index 7638f23b1..2d268ea41 100644 --- a/src/renderer/features/sidebar/components/mobile-sidebar.tsx +++ b/src/renderer/features/sidebar/components/mobile-sidebar.tsx @@ -11,7 +11,11 @@ import { SidebarPlaylistList, SidebarSharedPlaylistList, } from '/@/renderer/features/sidebar/components/sidebar-playlist-list'; -import { SidebarItemType, useGeneralSettings } from '/@/renderer/store/settings.store'; +import { + SidebarItemType, + useSidebarItems, + useSidebarPlaylistList, +} from '/@/renderer/store/settings.store'; import { Accordion } from '/@/shared/components/accordion/accordion'; import { Group } from '/@/shared/components/group/group'; import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; @@ -19,7 +23,7 @@ import { Text } from '/@/shared/components/text/text'; export const MobileSidebar = () => { const { t } = useTranslation(); - const { sidebarPlaylistList } = useGeneralSettings(); + const sidebarPlaylistList = useSidebarPlaylistList(); const translatedSidebarItemMap = useMemo( () => ({ @@ -38,7 +42,7 @@ export const MobileSidebar = () => { [t], ); - const { sidebarItems } = useGeneralSettings(); + const sidebarItems = useSidebarItems(); const sidebarItemsWithRoute: SidebarItemType[] = useMemo(() => { if (!sidebarItems) return []; diff --git a/src/renderer/features/sidebar/components/sidebar.tsx b/src/renderer/features/sidebar/components/sidebar.tsx index 0558f00df..2674c41b7 100644 --- a/src/renderer/features/sidebar/components/sidebar.tsx +++ b/src/renderer/features/sidebar/components/sidebar.tsx @@ -25,7 +25,8 @@ import { } from '/@/renderer/store'; import { SidebarItemType, - useGeneralSettings, + useSidebarItems, + useSidebarPlaylistList, useWindowSettings, } from '/@/renderer/store/settings.store'; import { Accordion } from '/@/shared/components/accordion/accordion'; @@ -41,7 +42,7 @@ import { Platform } from '/@/shared/types/types'; export const Sidebar = () => { const { t } = useTranslation(); - const { sidebarPlaylistList } = useGeneralSettings(); + const sidebarPlaylistList = useSidebarPlaylistList(); const translatedSidebarItemMap = useMemo( () => ({ @@ -62,7 +63,7 @@ export const Sidebar = () => { [t], ); - const { sidebarItems } = useGeneralSettings(); + const sidebarItems = useSidebarItems(); const { windowBarStyle } = useWindowSettings(); const sidebarImageEnabled = useAppStore((state) => state.sidebar.image); const isRadioPlaying = useRadioStore((state) => state.isPlaying); diff --git a/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer-settings-form.tsx b/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer-settings-form.tsx index 9d2f7d5c4..5b4af7514 100644 --- a/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer-settings-form.tsx +++ b/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer-settings-form.tsx @@ -127,9 +127,7 @@ const useUpdateAudioMotionAnalyzer = () => { ) => { setSettings({ visualizer: { - ...visualizer, audiomotionanalyzer: { - ...visualizer.audiomotionanalyzer, [property]: value, }, }, @@ -149,9 +147,7 @@ const useUpdateButterchurn = () => { ) => { setSettings({ visualizer: { - ...visualizer, butterchurn: { - ...visualizer.butterchurn, [property]: value, }, }, @@ -177,7 +173,6 @@ export const VisualizerSettingsForm = () => { const handleTypeChange = (value: string) => { setSettings({ visualizer: { - ...visualizer, type: value as 'audiomotionanalyzer' | 'butterchurn', }, }); @@ -464,9 +459,7 @@ const PresetSettings = () => { setSettings({ visualizer: { - ...visualizer, audiomotionanalyzer: { - ...visualizer.audiomotionanalyzer, ...presetValue, }, }, @@ -501,9 +494,7 @@ const PresetSettings = () => { setSettings({ visualizer: { - ...visualizer, audiomotionanalyzer: { - ...visualizer.audiomotionanalyzer, presets: updatedPresets, }, }, @@ -517,9 +508,7 @@ const PresetSettings = () => { setSettings({ visualizer: { - ...visualizer, audiomotionanalyzer: { - ...visualizer.audiomotionanalyzer, presets: [...visualizer.audiomotionanalyzer.presets, newPreset], }, }, @@ -644,9 +633,7 @@ const PresetSettings = () => { setSettings({ visualizer: { - ...visualizer, audiomotionanalyzer: { - ...visualizer.audiomotionanalyzer, presets: updatedPresets, }, }, @@ -776,9 +763,7 @@ const PresetSettings = () => { setSettings({ visualizer: { - ...visualizer, audiomotionanalyzer: { - ...visualizer.audiomotionanalyzer, ...configValue, }, }, diff --git a/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx b/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx index 860404e78..a2968e7ed 100644 --- a/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx +++ b/src/renderer/features/visualizer/components/audiomotionanalyzer/visualizer.tsx @@ -6,13 +6,13 @@ import styles from './visualizer.module.css'; import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio'; import { openVisualizerSettingsModal } from '/@/renderer/features/player/utils/open-visualizer-settings-modal'; import { ComponentErrorBoundary } from '/@/renderer/features/shared/components/component-error-boundary'; -import { useSettingsStore } from '/@/renderer/store'; +import { useAccent, useSettingsStore } from '/@/renderer/store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; const VisualizerInner = () => { const { webAudio } = useWebAudio(); const canvasRef = createRef(); - const accent = useSettingsStore((store) => store.general.accent); + const accent = useAccent(); const visualizer = useSettingsStore((store) => store.visualizer); const opacity = useSettingsStore((store) => store.visualizer.audiomotionanalyzer.opacity); const [motion, setMotion] = useState(); diff --git a/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx b/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx index dcc201b9d..1157f7860 100644 --- a/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx +++ b/src/renderer/features/visualizer/components/butternchurn/visualizer.tsx @@ -277,12 +277,9 @@ const VisualizerInner = () => { visualizer.loadPreset(nextPreset, currentSettings.blendTime || 0.0); // Update currentPreset in settings - const currentVisualizer = useSettingsStore.getState().visualizer; setSettings({ visualizer: { - ...currentVisualizer, butterchurn: { - ...currentVisualizer.butterchurn, currentPreset: nextPresetName, }, }, diff --git a/src/renderer/hooks/use-should-pad-titlebar.tsx b/src/renderer/hooks/use-should-pad-titlebar.tsx index 2f17297d3..a594aec7d 100644 --- a/src/renderer/hooks/use-should-pad-titlebar.tsx +++ b/src/renderer/hooks/use-should-pad-titlebar.tsx @@ -2,14 +2,14 @@ import isElectron from 'is-electron'; import { useLocation } from 'react-router'; import { AppRoute } from '/@/renderer/router/routes'; -import { useGeneralSettings, useSidebarRightExpanded, useWindowSettings } from '/@/renderer/store'; +import { useSidebarRightExpanded, useSideQueueType, useWindowSettings } from '/@/renderer/store'; import { Platform } from '/@/shared/types/types'; export const useShouldPadTitlebar = () => { const location = useLocation(); const isSidebarExpanded = useSidebarRightExpanded(); const isQueuePage = location.pathname === AppRoute.NOW_PLAYING; - const { sideQueueType } = useGeneralSettings(); + const sideQueueType = useSideQueueType(); const { windowBarStyle } = useWindowSettings(); const conditions = [ diff --git a/src/renderer/layouts/default-layout.tsx b/src/renderer/layouts/default-layout.tsx index 577da63ed..cf5e7687f 100644 --- a/src/renderer/layouts/default-layout.tsx +++ b/src/renderer/layouts/default-layout.tsx @@ -13,7 +13,6 @@ import { Platform, PlayerType } from '/@/shared/types/types'; if (!isElectron()) { useSettingsStore.getState().actions.setSettings({ playback: { - ...useSettingsStore.getState().playback, type: PlayerType.WEB, }, }); diff --git a/src/renderer/layouts/default-layout/main-content.tsx b/src/renderer/layouts/default-layout/main-content.tsx index f41ccce08..cc51b8124 100644 --- a/src/renderer/layouts/default-layout/main-content.tsx +++ b/src/renderer/layouts/default-layout/main-content.tsx @@ -9,8 +9,7 @@ import styles from './main-content.module.css'; import { FullScreenOverlay } from '/@/renderer/layouts/default-layout/full-screen-overlay'; import { LeftSidebar } from '/@/renderer/layouts/default-layout/left-sidebar'; import { RightSidebar } from '/@/renderer/layouts/default-layout/right-sidebar'; -import { useAppStore, useAppStoreActions } from '/@/renderer/store'; -import { useGeneralSettings } from '/@/renderer/store/settings.store'; +import { useAppStore, useAppStoreActions, useSideQueueType } from '/@/renderer/store'; import { constrainRightSidebarWidth, constrainSidebarWidth } from '/@/renderer/utils'; import { Spinner } from '/@/shared/components/spinner/spinner'; @@ -27,7 +26,7 @@ export const MainContent = ({ shell }: { shell?: boolean }) => { shallow, ); const { setSideBar } = useAppStoreActions(); - const { sideQueueType } = useGeneralSettings(); + const sideQueueType = useSideQueueType(); const [isResizing, setIsResizing] = useState(false); const [isResizingRight, setIsResizingRight] = useState(false); diff --git a/src/renderer/layouts/default-layout/player-bar.tsx b/src/renderer/layouts/default-layout/player-bar.tsx index 0fdab281f..28288647e 100644 --- a/src/renderer/layouts/default-layout/player-bar.tsx +++ b/src/renderer/layouts/default-layout/player-bar.tsx @@ -3,10 +3,10 @@ import clsx from 'clsx'; import styles from './player-bar.module.css'; import { Playerbar } from '/@/renderer/features/player/components/playerbar'; -import { useGeneralSettings } from '/@/renderer/store/settings.store'; +import { usePlayerbarOpenDrawer } from '/@/renderer/store'; export const PlayerBar = () => { - const { playerbarOpenDrawer } = useGeneralSettings(); + const playerbarOpenDrawer = usePlayerbarOpenDrawer(); return (
({ @@ -55,7 +55,7 @@ export const RightSidebar = forwardRef( ref: Ref, ) => { const rightExpanded = useAppStore((state) => state.sidebar.rightExpanded); - const { sideQueueType } = useGeneralSettings(); + const sideQueueType = useSideQueueType(); return ( <> diff --git a/src/renderer/layouts/responsive-layout.tsx b/src/renderer/layouts/responsive-layout.tsx index 4afb06005..02e8e4e53 100644 --- a/src/renderer/layouts/responsive-layout.tsx +++ b/src/renderer/layouts/responsive-layout.tsx @@ -10,9 +10,9 @@ import { MobileLayout } from '/@/renderer/layouts/mobile-layout/mobile-layout'; import { AppRoute } from '/@/renderer/router/routes'; import { useCommandPalette, - useGeneralSettings, useHotkeySettings, useSettingsStoreActions, + useZoomFactor, } from '/@/renderer/store'; import { HotkeyItem, useHotkeys } from '/@/shared/hooks/use-hotkeys'; @@ -45,23 +45,22 @@ export const ResponsiveLayout = ({ shell }: ResponsiveLayoutProps) => { const LayoutHotkeys = () => { const navigate = useNavigate(); const localSettings = isElectron() ? window.api.localSettings : null; - const settings = useGeneralSettings(); + const zoomFactor = useZoomFactor(); const { setSettings } = useSettingsStoreActions(); const { bindings } = useHotkeySettings(); const { opened, ...handlers } = useCommandPalette(); const updateZoom = (increase: number) => { - const newVal = settings.zoomFactor + increase; + const newVal = zoomFactor + increase; if (newVal > 300 || newVal < 50 || !isElectron()) return; setSettings({ general: { - ...settings, zoomFactor: newVal, }, }); - localSettings?.setZoomFactor(settings.zoomFactor); + localSettings?.setZoomFactor(zoomFactor); }; - localSettings?.setZoomFactor(settings.zoomFactor); + localSettings?.setZoomFactor(zoomFactor); const zoomHotkeys: HotkeyItem[] = [ [bindings.zoomIn.hotkey, () => updateZoom(5)], diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 9871bf8f1..ebb1449a4 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -1,4 +1,5 @@ import isElectron from 'is-electron'; +import merge from 'lodash/merge'; import { generatePath } from 'react-router'; import { z } from 'zod'; import { devtools, persist } from 'zustand/middleware'; @@ -42,6 +43,17 @@ type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; }; +const deepMergeIntoState = >( + state: T, + updates: DeepPartial, +): void => { + // Skip 'actions' property + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { actions, ...updatesWithoutActions } = updates as any; + + merge(state, updatesWithoutActions); +}; + const HomeItemSchema = z.enum([ 'genres', 'mostPlayed', @@ -767,7 +779,7 @@ export interface SettingsSlice extends z.infer { setHomeItems: (item: SortableItem[]) => void; setList: (type: ItemListKey, data: DeepPartial) => void; setPlaybackFilters: (filters: PlayerFilter[]) => void; - setSettings: (data: Partial) => void; + setSettings: (data: DeepPartial) => void; setSidebarItems: (items: SidebarItemType[]) => void; setTable: (type: ItemListKey, data: DataTableProps) => void; setTranscodingConfig: (config: TranscodingConfig) => void; @@ -1642,7 +1654,7 @@ const getInitialState = (): SettingsState => { export const useSettingsStore = createWithEqualityFn()( persist( devtools( - immer((set, get) => ({ + immer((set) => ({ actions: { reset: () => { const freshState = getInitialState(); @@ -1723,7 +1735,9 @@ export const useSettingsStore = createWithEqualityFn()( }); }, setSettings: (data) => { - set({ ...get(), ...data }); + set((state) => { + deepMergeIntoState(state, data); + }); }, setSidebarItems: (items: SidebarItemType[]) => { set((state) => { @@ -1980,11 +1994,128 @@ export const useListSettings = (type: ItemListKey) => shallow, ) as ItemListSettings; -export const usePrimaryColor = () => useSettingsStore((store) => store.general.accent); +export const usePrimaryColor = () => useSettingsStore((store) => store.general.accent, shallow); -export const usePlayerbarSlider = () => useSettingsStore((store) => store.general.playerbarSlider); +export const usePlayerbarSlider = () => + useSettingsStore((store) => store.general.playerbarSlider, shallow); -export const useGenreTarget = () => useSettingsStore((store) => store.general.genreTarget); +export const useGenreTarget = () => useSettingsStore((store) => store.general.genreTarget, shallow); + +export const useLanguage = () => useSettingsStore((state) => state.general.language, shallow); + +export const useAccent = () => useSettingsStore((state) => state.general.accent, shallow); + +export const useNativeAspectRatio = () => + useSettingsStore((state) => state.general.nativeAspectRatio, shallow); + +export const useButtonSize = () => useSettingsStore((state) => state.general.buttonSize, shallow); + +export const useSkipButtons = () => useSettingsStore((state) => state.general.skipButtons, shallow); + +export const useImageRes = () => useSettingsStore((state) => state.general.imageRes, shallow); + +export const useVolumeWidth = () => useSettingsStore((state) => state.general.volumeWidth, shallow); + +export const useFollowCurrentSong = () => + useSettingsStore((state) => state.general.followCurrentSong, shallow); + +export const useThemeSettings = () => + useSettingsStore( + (state) => ({ + followSystemTheme: state.general.followSystemTheme, + theme: state.general.theme, + themeDark: state.general.themeDark, + themeLight: state.general.themeLight, + useThemeAccentColor: state.general.useThemeAccentColor, + }), + shallow, + ); + +export const useSideQueueType = () => + useSettingsStore((state) => state.general.sideQueueType, shallow); + +export const useVolumeWheelStep = () => + useSettingsStore((state) => state.general.volumeWheelStep, shallow); + +export const useSidebarPlaylistList = () => + useSettingsStore((state) => state.general.sidebarPlaylistList, shallow); + +export const useSidebarItems = () => + useSettingsStore((state) => state.general.sidebarItems, shallow); + +export const useSidebarCollapsedNavigation = () => + useSettingsStore((state) => state.general.sidebarCollapsedNavigation, shallow); + +export const usePlayerbarOpenDrawer = () => + useSettingsStore((state) => state.general.playerbarOpenDrawer, shallow); + +export const useShowRatings = () => useSettingsStore((state) => state.general.showRatings, shallow); + +export const useArtistRadioCount = () => + useSettingsStore((state) => state.general.artistRadioCount, shallow); + +export const useArtistBackground = () => + useSettingsStore( + (state) => ({ + artistBackground: state.general.artistBackground, + artistBackgroundBlur: state.general.artistBackgroundBlur, + }), + shallow, + ); + +export const useAlbumBackground = () => + useSettingsStore( + (state) => ({ + albumBackground: state.general.albumBackground, + albumBackgroundBlur: state.general.albumBackgroundBlur, + }), + shallow, + ); + +export const useExternalLinks = () => + useSettingsStore( + (state) => ({ + externalLinks: state.general.externalLinks, + lastFM: state.general.lastFM, + musicBrainz: state.general.musicBrainz, + }), + shallow, + ); + +export const useHomeFeature = () => useSettingsStore((state) => state.general.homeFeature, shallow); + +export const useHomeItems = () => useSettingsStore((state) => state.general.homeItems, shallow); + +export const useArtistItems = () => useSettingsStore((state) => state.general.artistItems, shallow); + +export const useArtistReleaseTypeItems = () => + useSettingsStore((state) => state.general.artistReleaseTypeItems, shallow); + +export const useZoomFactor = () => useSettingsStore((state) => state.general.zoomFactor, shallow); + +export const usePathReplace = () => + useSettingsStore( + (state) => ({ + pathReplace: state.general.pathReplace, + pathReplaceWith: state.general.pathReplaceWith, + }), + shallow, + ); + +export const useLastfmApiKey = () => + useSettingsStore((state) => state.general.lastfmApiKey, shallow); + +export const useSidebarPanelOrder = () => + useSettingsStore((state) => state.general.sidebarPanelOrder, shallow); + +export const useCombinedLyricsAndVisualizer = () => + useSettingsStore((state) => state.general.combinedLyricsAndVisualizer, shallow); + +export const useShowLyricsInSidebar = () => + useSettingsStore((state) => state.general.showLyricsInSidebar, shallow); + +export const useShowVisualizerInSidebar = () => + useSettingsStore((state) => state.general.showVisualizerInSidebar, shallow); export const useAutoDJSettings = () => useSettingsStore((store) => store.autoDJ, shallow); diff --git a/src/renderer/themes/use-app-theme.ts b/src/renderer/themes/use-app-theme.ts index 9b477d8b8..481f59828 100644 --- a/src/renderer/themes/use-app-theme.ts +++ b/src/renderer/themes/use-app-theme.ts @@ -1,7 +1,12 @@ import { useMantineColorScheme } from '@mantine/core'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useSettingsStore } from '/@/renderer/store/settings.store'; +import { + useAccent, + useFontSettings, + useNativeAspectRatio, + useThemeSettings, +} from '/@/renderer/store/settings.store'; import { createMantineTheme } from '/@/renderer/themes/mantine-theme'; import { getAppTheme } from '/@/shared/themes/app-theme'; import { AppTheme, AppThemeConfiguration } from '/@/shared/themes/app-theme-types'; @@ -36,15 +41,15 @@ export const THEME_DATA = [ ]; export const useAppTheme = (overrideTheme?: AppTheme) => { - const accent = useSettingsStore((store) => store.general.accent); - const nativeImageAspect = useSettingsStore((store) => store.general.nativeAspectRatio); - const { builtIn, custom, system, type } = useSettingsStore((state) => state.font); + const accent = useAccent(); + const nativeImageAspect = useNativeAspectRatio(); + const { builtIn, custom, system, type } = useFontSettings(); const textStyleRef = useRef(null); const loadedStylesheetsRef = useRef>(new Set()); const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches; const [isDarkTheme, setIsDarkTheme] = useState(getCurrentTheme()); const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } = - useSettingsStore((state) => state.general); + useThemeSettings(); const mqListener = (e: any) => { setIsDarkTheme(e.matches); @@ -263,11 +268,11 @@ export const useColorScheme = () => { }; export const useAppThemeColors = () => { - const accent = useSettingsStore((store) => store.general.accent); + const accent = useAccent(); const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches; const [isDarkTheme] = useState(getCurrentTheme()); const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } = - useSettingsStore((state) => state.general); + useThemeSettings(); const getSelectedTheme = () => { if (followSystemTheme) {