From a2926ef47eada0397b459a815af4f580574b82f6 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 23 Nov 2025 18:15:38 -0800 Subject: [PATCH] reorganize and redesign settings --- src/i18n/locales/en.json | 19 +- .../components/advanced/advanced-tab.tsx | 20 +- .../advanced/export-import-settings.tsx | 54 ++- .../general/application-settings.tsx | 309 +++++++++++++++- .../components/general/control-settings.tsx | 349 +----------------- .../components/general/draggable-items.tsx | 2 - .../components/general/general-tab.tsx | 34 +- .../{playback => general}/lyric-settings.tsx | 7 +- .../components/general/remote-settings.tsx | 7 +- .../scrobble-settings.tsx | 7 +- .../components/general/sidebar-settings.tsx | 9 +- .../components/general/theme-settings.tsx | 9 +- .../hotkeys/hotkey-manager-settings.tsx | 184 ++++----- .../components/hotkeys/hotkeys-tab.tsx | 17 +- .../media-session-settings.tsx | 2 +- .../components/playback/audio-settings.tsx | 9 +- .../components/playback/playback-tab.tsx | 10 +- .../playback/transcode-settings.tsx | 7 +- .../settings/components/settings-content.tsx | 38 +- .../settings/components/settings-section.tsx | 23 +- .../components/window/cache-settngs.tsx | 7 +- .../components/window/discord-settings.tsx | 7 +- .../components/window/update-settings.tsx | 7 +- .../components/window/window-settings.tsx | 7 +- .../settings/components/window/window-tab.tsx | 21 +- src/renderer/store/settings.store.ts | 4 - 26 files changed, 629 insertions(+), 540 deletions(-) rename src/renderer/features/settings/components/{playback => general}/lyric-settings.tsx (98%) rename src/renderer/features/settings/components/{playback => general}/scrobble-settings.tsx (97%) rename src/renderer/features/settings/components/{playback => hotkeys}/media-session-settings.tsx (96%) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 68bff6568..04f816546 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -459,7 +459,20 @@ "generalTab": "general", "hotkeysTab": "hotkeys", "playbackTab": "playback", - "windowTab": "window" + "windowTab": "window", + "updates": "update", + "cache": "cache", + "application": "application", + "theme": "theme", + "controls": "controls", + "sidebar": "sidebar", + "remote": "remote", + "exportImport": "export/import", + "scrobble": "scrobble", + "audio": "audio", + "lyrics": "lyrics", + "transcoding": "transcoding", + "discord": "discord" }, "sidebar": { "albumArtists": "$t(entity.albumArtist_other)", @@ -614,8 +627,6 @@ "discordServeImage_description": "share cover art for {{discord}} rich presence from server itself, only available for Jellyfin and Navidrome. {{discord}} uses a bot to fetch images, so your server must be reachable from the public internet", "discordUpdateInterval": "{{discord}} rich presence update interval", "discordUpdateInterval_description": "the time in seconds between each update (minimum 15 seconds)", - "doubleClickBehavior_description": "if true, all matching tracks in a track search will be queued. otherwise, only the clicked one will be queued", - "doubleClickBehavior": "queue all searched tracks when double clicking", "enableAutoTranslation_description": "enable translation automatically when lyrics are loaded", "enableAutoTranslation": "enable auto translation", "enableRemote_description": "enables the remote control server to allow other devices to control the application", @@ -634,8 +645,6 @@ "exportImportSettings_offendingKeyError": "\"{{offendingKey}}\" is incorrect - {{reason}}", "externalLinks_description": "enables showing external links (Last.fm, MusicBrainz) on artist/album pages", "externalLinks": "show external links", - "floatingQueueArea_description": "display a hover icon on the right side of the screen to view the play queue", - "floatingQueueArea": "show floating queue hover area", "followLyric_description": "scroll the lyric to the current playing position", "followLyric": "follow current lyric", "font_description": "sets the font to use for the application", diff --git a/src/renderer/features/settings/components/advanced/advanced-tab.tsx b/src/renderer/features/settings/components/advanced/advanced-tab.tsx index 090fc1da0..89c9bc02a 100644 --- a/src/renderer/features/settings/components/advanced/advanced-tab.tsx +++ b/src/renderer/features/settings/components/advanced/advanced-tab.tsx @@ -1,14 +1,26 @@ +import { Fragment } from 'react/jsx-runtime'; + import { ExportImportSettings } from '/@/renderer/features/settings/components/advanced/export-import-settings'; -import { StylesSettings } from '/@/renderer/features/settings/components/advanced/styles-settings'; +import { CacheSettings } from '/@/renderer/features/settings/components/window/cache-settngs'; import { UpdateSettings } from '/@/renderer/features/settings/components/window/update-settings'; +import { Divider } from '/@/shared/components/divider/divider'; import { Stack } from '/@/shared/components/stack/stack'; +const sections = [ + { component: UpdateSettings, key: 'update' }, + { component: ExportImportSettings, key: 'export-import' }, + { component: CacheSettings, key: 'cache' }, +]; + export const AdvancedTab = () => { return ( - - - + {sections.map(({ component: Section, key }, index) => ( + +
+ {index < sections.length - 1 && } + + ))} ); }; 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 f22941c4b..4084a6b9f 100644 --- a/src/renderer/features/settings/components/advanced/export-import-settings.tsx +++ b/src/renderer/features/settings/components/advanced/export-import-settings.tsx @@ -3,7 +3,10 @@ import { t } from 'i18next'; import { useCallback } from 'react'; import { ExportImportSettingsModal } from '/@/renderer/components/export-import-settings-modal/export-import-settings-modal'; -import { SettingsOptions } from '/@/renderer/features/settings/components/settings-option'; +import { + SettingOption, + SettingsSection, +} from '/@/renderer/features/settings/components/settings-section'; import { useSettingsForExport } from '/@/renderer/store'; import { Button } from '/@/shared/components/button/button'; @@ -28,26 +31,41 @@ export const ExportImportSettings = () => { openModal({ children: , size: 'lg', - title: t('setting.exportImportSettings_importModalTitle').toString(), + title: t('setting.exportImportSettings_importModalTitle', { + postProcess: 'sentenceCase', + }), }); }; + const options: SettingOption[] = [ + { + control: ( + <> + + + + ), + description: t('setting.exportImportSettings_control_description', { + postProcess: 'sentenceCase', + }), + title: t('setting.exportImportSettings_control_title', { + postProcess: 'sentenceCase', + }), + }, + ]; + return ( - <> - - - - - } - description={t('setting.exportImportSettings_control_description').toString()} - title={t('setting.exportImportSettings_control_title').toString()} - /> - + ); }; diff --git a/src/renderer/features/settings/components/general/application-settings.tsx b/src/renderer/features/settings/components/general/application-settings.tsx index 80d0f34bf..0d4226f14 100644 --- a/src/renderer/features/settings/components/general/application-settings.tsx +++ b/src/renderer/features/settings/components/general/application-settings.tsx @@ -1,15 +1,19 @@ import type { IpcRendererEvent } from 'electron'; +import { t } from 'i18next'; import isElectron from 'is-electron'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import i18n, { languages } from '/@/i18n/i18n'; +import { ArtistSettings } from '/@/renderer/features/settings/components/general/artist-settings'; +import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings'; import { SettingOption, SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; import { + SideQueueType, useFontSettings, useGeneralSettings, useSettingsStoreActions, @@ -18,6 +22,8 @@ import { type Font, FONT_OPTIONS } from '/@/renderer/types/fonts'; import { FileInput } from '/@/shared/components/file-input/file-input'; import { NumberInput } from '/@/shared/components/number-input/number-input'; import { Select } from '/@/shared/components/select/select'; +import { Slider } from '/@/shared/components/slider/slider'; +import { Switch } from '/@/shared/components/switch/switch'; import { toast } from '/@/shared/components/toast/toast'; import { FontType } from '/@/shared/types/types'; @@ -26,6 +32,23 @@ const ipc = isElectron() ? window.api.ipc : null; // Electron 32+ removed file.path, use this which is exposed in preload to get real path const webUtils = isElectron() ? window.electron.webUtils : null; +const SIDE_QUEUE_OPTIONS = [ + { + label: t('setting.sidePlayQueueStyle', { + context: 'optionAttached', + postProcess: 'sentenceCase', + }), + value: 'sideQueue', + }, + { + label: t('setting.sidePlayQueueStyle', { + context: 'optionDetached', + postProcess: 'sentenceCase', + }), + value: 'sideDrawerQueue', + }, +]; + const FONT_TYPES: Font[] = [ { label: i18n.t('setting.fontType', { @@ -284,7 +307,291 @@ export const ApplicationSettings = () => { postProcess: 'sentenceCase', }), }, + { + control: ( + { + localSettings?.set('resume', e.target.checked); + setSettings({ + general: { + ...settings, + resume: e.currentTarget.checked, + }, + }); + }} + /> + ), + description: t('setting.savePlayQueue', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: !isElectron(), + title: t('setting.savePlayQueue', { postProcess: 'sentenceCase' }), + }, + { + control: ( + + setSettings({ + general: { + ...settings, + homeFeature: e.currentTarget.checked, + }, + }) + } + /> + ), + description: t('setting.homeFeature', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: false, + title: t('setting.homeFeature', { postProcess: 'sentenceCase' }), + }, + { + control: ( + + setSettings({ + general: { + ...settings, + albumBackground: e.currentTarget.checked, + }, + }) + } + /> + ), + description: t('setting.albumBackground', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: false, + title: t('setting.albumBackground', { postProcess: 'sentenceCase' }), + }, + { + control: ( + `${e} rem`} + max={6} + min={0} + onChangeEnd={(e) => { + setSettings({ + general: { + ...settings, + albumBackgroundBlur: e, + }, + }); + }} + step={0.5} + w={100} + /> + ), + description: t('setting.albumBackgroundBlur', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: !settings.albumBackground, + title: t('setting.albumBackgroundBlur', { postProcess: 'sentenceCase' }), + }, + { + control: ( + + setSettings({ + general: { + ...settings, + artistBackground: e.currentTarget.checked, + }, + }) + } + /> + ), + description: t('setting.artistBackground', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: false, + title: t('setting.artistBackground', { postProcess: 'sentenceCase' }), + }, + { + control: ( + `${e} rem`} + max={6} + min={0} + onChangeEnd={(e) => { + setSettings({ + general: { + ...settings, + artistBackgroundBlur: e, + }, + }); + }} + step={0.5} + w={100} + /> + ), + description: t('setting.artistBackgroundBlur', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: !settings.artistBackground, + title: t('setting.artistBackgroundBlur', { postProcess: 'sentenceCase' }), + }, + { + control: ( + + setSettings({ + general: { + ...settings, + nativeAspectRatio: e.currentTarget.checked, + }, + }) + } + /> + ), + description: t('setting.imageAspectRatio', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: false, + title: t('setting.imageAspectRatio', { postProcess: 'sentenceCase' }), + }, + { + control: ( + { - setSettings({ - general: { - ...settings, - sideQueueType: e as SideQueueType, - }, - }); - }} - /> - ), - description: t('setting.sidePlayQueueStyle', { - context: 'description', - postProcess: 'sentenceCase', - }), - isHidden: false, - title: t('setting.sidePlayQueueStyle', { postProcess: 'sentenceCase' }), - }, - { - control: ( - { - setSettings({ - general: { - ...settings, - showQueueDrawerButton: e.currentTarget.checked, - }, - }); - }} - /> - ), - description: t('setting.sidePlayQueueStyle', { - context: 'description', - postProcess: 'sentenceCase', - }), - isHidden: false, - title: t('setting.floatingQueueArea', { postProcess: 'sentenceCase' }), - }, { control: ( { isHidden: false, title: t('setting.volumeWidth', { postProcess: 'sentenceCase' }), }, - { - control: ( - { - localSettings?.set('resume', e.target.checked); - setSettings({ - general: { - ...settings, - resume: e.currentTarget.checked, - }, - }); - }} - /> - ), - description: t('setting.savePlayQueue', { - context: 'description', - postProcess: 'sentenceCase', - }), - isHidden: !isElectron(), - title: t('setting.savePlayQueue', { postProcess: 'sentenceCase' }), - }, + { control: ( { }), title: t('setting.externalLinks', { postProcess: 'sentenceCase' }), }, - { - control: ( - { - setSettings({ - general: { - ...settings, - lastFM: e.currentTarget.checked, - }, - }); - }} - /> - ), - description: t('setting.lastfm', { - context: 'description', - postProcess: 'sentenceCase', - }), - isHidden: !settings.externalLinks, - title: t('setting.lastfm', { postProcess: 'sentenceCase' }), - }, - { - control: ( - { - setSettings({ - general: { - ...settings, - musicBrainz: e.currentTarget.checked, - }, - }); - }} - /> - ), - description: t('setting.musicbrainz', { - context: 'description', - postProcess: 'sentenceCase', - }), - isHidden: !settings.externalLinks, - title: t('setting.musicbrainz', { postProcess: 'sentenceCase' }), - }, + { control: (