From e603048a8061cd13cc02118462cafdd1f12ef7a6 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 6 Mar 2026 19:32:38 -0800 Subject: [PATCH] add settings override with env variables --- README.md | 2 + docs/ENV_SETTINGS.md | 126 +++++++ settings.js.template | 87 ++++- src/renderer/global.d.ts | 72 ++++ src/renderer/store/env-settings-overrides.ts | 376 +++++++++++++++++++ src/renderer/store/settings.store.ts | 9 +- 6 files changed, 670 insertions(+), 2 deletions(-) create mode 100644 docs/ENV_SETTINGS.md create mode 100644 src/renderer/store/env-settings-overrides.ts diff --git a/README.md b/README.md index 37637f35d..e12f4753e 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ services: 5. _Optional_ - To disable Umami analytics tracking in the Docker/web version, set the environment variable `ANALYTICS_DISABLED=true`. When enabled, the analytics script will not be loaded and all tracking will be disabled. +6. _Optional_ - App settings (theme, language, sidebar options, etc.) can be overridden with environment variables on first run. The variables use the `FS_` prefix (e.g. `FS_GENERAL_THEME=defaultDark`, `FS_GENERAL_LANGUAGE=de`). See [the settings environment variable documentation](docs/ENV_SETTINGS.md) for the full list. + ## FAQ ### MPV is either not working or is rapidly switching between pause/play states diff --git a/docs/ENV_SETTINGS.md b/docs/ENV_SETTINGS.md new file mode 100644 index 000000000..7901e23a5 --- /dev/null +++ b/docs/ENV_SETTINGS.md @@ -0,0 +1,126 @@ +# Environment variables for settings (web / Docker) + +These variables override app settings **on first run** when no persisted settings exist. They are injected via `settings.js` (from `settings.js.template`) and only apply to the **web** build. + +**Format:** All values are strings; booleans use `true`/`false`, numbers are numeric strings. Leave unset or empty to use the default. + +--- + +## General + +| Setting | Default | Env variable | Available values / Description | +|-------------|---------|--------------|--------------------------------| +| `general.accent` | `rgb(53, 116, 252)` | `FS_GENERAL_ACCENT` | CSS `rgb(r, g, b)` string (e.g. `rgb(53, 116, 252)`). Invalid values are ignored. | +| `general.albumBackground` | `false` | `FS_GENERAL_ALBUM_BACKGROUND` | `true` / `false` — Show album background image. | +| `general.albumBackgroundBlur` | `3` | `FS_GENERAL_ALBUM_BACKGROUND_BLUR` | Blur amount for album background (number). | +| `general.artistBackground` | `true` | `FS_GENERAL_ARTIST_BACKGROUND` | `true` / `false` — Show artist background image. | +| `general.artistBackgroundBlur` | `3` | `FS_GENERAL_ARTIST_BACKGROUND_BLUR` | Blur amount for artist background (number). | +| `general.blurExplicitImages` | `false` | `FS_GENERAL_BLUR_EXPLICIT_IMAGES` | `true` / `false` — Blur explicit images. | +| `general.combinedLyricsAndVisualizer` | `false` | `FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER` | `true` / `false` — Combine lyrics and visualizer panel. | +| `general.enableGridMultiSelect` | `false` | `FS_GENERAL_ENABLE_GRID_MULTI_SELECT` | `true` / `false` — Enable multi-select in grid views. | +| `general.externalLinks` | `true` | `FS_GENERAL_EXTERNAL_LINKS` | `true` / `false` — Show external links in UI. | +| `general.followCurrentSong` | `true` | `FS_GENERAL_FOLLOW_CURRENT_SONG` | `true` / `false` — Follow current song in list. | +| `general.followSystemTheme` | `false` | `FS_GENERAL_FOLLOW_SYSTEM_THEME` | `true` / `false` — Use OS light/dark preference. | +| `general.homeFeature` | `true` | `FS_GENERAL_HOME_FEATURE` | `true` / `false` — Show home featured carousel. | +| `general.homeFeatureStyle` | `single` | `FS_GENERAL_HOME_FEATURE_STYLE` | `multiple` / `single` — Home featured carousel style. | +| `general.language` | `en` | `FS_GENERAL_LANGUAGE` | UI language code (e.g. `en`, `de`, `fr`). | +| `general.theme` | `defaultDark` | `FS_GENERAL_THEME` | One of: `ayuDark`, `ayuLight`, `catppuccinLatte`, `catppuccinMocha`, `defaultDark`, `defaultLight`, `dracula`, `githubDark`, `githubLight`, `glassyDark`, `gruvboxDark`, `gruvboxLight`, `highContrastDark`, `highContrastLight`, `materialDark`, `materialLight`, `monokai`, `nightOwl`, `nord`, `oneDark`, `rosePine`, `rosePineDawn`, `rosePineMoon`, `shadesOfPurple`, `solarizedDark`, `solarizedLight`, `tokyoNight`, `vscodeDarkPlus`, `vscodeLightPlus`. | +| `general.themeDark` | `defaultDark` | `FS_GENERAL_THEME_DARK` | Same as theme (used when system is dark). | +| `general.themeLight` | `defaultLight` | `FS_GENERAL_THEME_LIGHT` | Same as theme (used when system is light). | +| `general.lastfmApiKey` | *(empty)* | `FS_GENERAL_LASTFM_API_KEY` | Last.fm API key. | +| `general.lastFM` | `true` | `FS_GENERAL_LAST_FM` | `true` / `false` — Enable Last.fm. | +| `general.musicBrainz` | `true` | `FS_GENERAL_MUSIC_BRAINZ` | `true` / `false` — MusicBrainz links. | +| `general.nativeAspectRatio` | `false` | `FS_GENERAL_NATIVE_ASPECT_RATIO` | `true` / `false` — Use native cover art aspect ratio. | +| `general.pathReplace` | *(empty)* | `FS_GENERAL_PATH_REPLACE` | Path pattern to replace (e.g. server path in Docker). | +| `general.pathReplaceWith` | *(empty)* | `FS_GENERAL_PATH_REPLACE_WITH` | Replacement path. | +| `general.playerbarOpenDrawer` | `false` | `FS_GENERAL_PLAYERBAR_OPEN_DRAWER` | `true` / `false` — Open queue/lyrics as drawer from player bar. | +| `general.primaryShade` | `6` | `FS_GENERAL_PRIMARY_SHADE` | Mantine primary shade 0–9 (number). | +| `general.resume` | `true` | `FS_GENERAL_RESUME` | `true` / `false` — Resume playback on load. | +| `general.showLyricsInSidebar` | `true` | `FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR` | `true` / `false` — Show lyrics in sidebar. | +| `general.showRatings` | `true` | `FS_GENERAL_SHOW_RATINGS` | `true` / `false` — Show star ratings. | +| `general.showVisualizerInSidebar` | `true` | `FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR` | `true` / `false` — Show visualizer in sidebar. | +| `general.sidebarCollapsedNavigation` | `true` | `FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION` | `true` / `false` — Start with collapsed sidebar nav. | +| `general.sidebarCollapseShared` | `false` | `FS_GENERAL_SIDEBAR_COLLAPSE_SHARED` | `true` / `false` — Share sidebar collapse state. | +| `general.sidebarPlaylistList` | `true` | `FS_GENERAL_SIDEBAR_PLAYLIST_LIST` | `true` / `false` — Show playlist list in sidebar. | +| `general.sidebarPlaylistSorting` | `false` | `FS_GENERAL_SIDEBAR_PLAYLIST_SORTING` | `true` / `false` — Enable playlist sorting in sidebar. | +| `general.sideQueueType` | `sideQueue` | `FS_GENERAL_SIDE_QUEUE_TYPE` | `sideDrawerQueue` / `sideQueue` — Side play queue style. | +| `general.useThemeAccentColor` | `false` | `FS_GENERAL_USE_THEME_ACCENT_COLOR` | `true` / `false` — Use theme’s accent color instead of custom. | +| `general.useThemePrimaryShade` | `true` | `FS_GENERAL_USE_THEME_PRIMARY_SHADE` | `true` / `false` — Use theme’s primary shade. | +| `general.zoomFactor` | `100` | `FS_GENERAL_ZOOM_FACTOR` | UI zoom percentage (number). | + +--- + +## Playback + +| Setting path | Default | Env variable | Available values / Description | +|-------------|---------|--------------|--------------------------------| +| `playback.mediaSession` | `false` | `FS_PLAYBACK_MEDIA_SESSION` | `true` / `false` — Media Session API (e.g. browser/media keys). | +| `playback.webAudio` | `true` | `FS_PLAYBACK_WEB_AUDIO` | `true` / `false` — Use Web Audio for playback. | +| `playback.audioFadeOnStatusChange` | `true` | `FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE` | `true` / `false` — Fade on play/pause. | +| `playback.preservePitch` | `true` | `FS_PLAYBACK_PRESERVE_PITCH` | `true` / `false` — Preserve pitch when changing speed. | +| `playback.scrobble.enabled` | `true` | `FS_PLAYBACK_SCROBBLE_ENABLED` | `true` / `false` — Enable scrobbling. | +| `playback.scrobble.notify` | `false` | `FS_PLAYBACK_SCROBBLE_NOTIFY` | `true` / `false` — Scrobble notifications. | +| `playback.scrobble.scrobbleAtDuration` | `240` | `FS_PLAYBACK_SCROBBLE_AT_DURATION` | Seconds of playback before scrobble. | +| `playback.scrobble.scrobbleAtPercentage` | `75` | `FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE` | Percentage of track before scrobble. | +| `playback.transcode.enabled` | `false` | `FS_PLAYBACK_TRANSCODE_ENABLED` | `true` / `false` — Enable transcoding. | + +--- + +## Discord + +| Setting path | Default | Env variable | Available values / Description | +|-------------|---------|--------------|--------------------------------| +| `discord.enabled` | `false` | `FS_DISCORD_ENABLED` | `true` / `false` — Discord rich presence. | +| `discord.clientId` | *(built-in)* | `FS_DISCORD_CLIENT_ID` | Custom Discord application ID. | +| `discord.displayType` | `feishin` | `FS_DISCORD_DISPLAY_TYPE` | `artist` / `feishin` / `song`. | +| `discord.linkType` | `none` | `FS_DISCORD_LINK_TYPE` | `last_fm` / `musicbrainz` / `musicbrainz_last_fm` / `none`. | +| `discord.showAsListening` | `false` | `FS_DISCORD_SHOW_AS_LISTENING` | `true` / `false`. | +| `discord.showPaused` | `true` | `FS_DISCORD_SHOW_PAUSED` | `true` / `false` — Show paused state. | +| `discord.showServerImage` | `false` | `FS_DISCORD_SHOW_SERVER_IMAGE` | `true` / `false`. | +| `discord.showStateIcon` | `true` | `FS_DISCORD_SHOW_STATE_ICON` | `true` / `false`. | + +--- + +## Lyrics + +| Setting path | Default | Env variable | Available values / Description | +|-------------|---------|--------------|--------------------------------| +| `lyrics.fetch` | `true` | `FS_LYRICS_FETCH` | `true` / `false` — Fetch lyrics. | +| `lyrics.follow` | `true` | `FS_LYRICS_FOLLOW` | `true` / `false` — Follow current line. | +| `lyrics.delayMs` | `0` | `FS_LYRICS_DELAY_MS` | Sync delay in milliseconds. | +| `lyrics.preferLocalLyrics` | `true` | `FS_LYRICS_PREFER_LOCAL` | `true` / `false` — Prefer local lyric files. | +| `lyrics.showMatch` | `true` | `FS_LYRICS_SHOW_MATCH` | `true` / `false`. | +| `lyrics.showProvider` | `true` | `FS_LYRICS_SHOW_PROVIDER` | `true` / `false`. | +| `lyrics.enableAutoTranslation` | `false` | `FS_LYRICS_ENABLE_AUTO_TRANSLATION` | `true` / `false`. | +| `lyrics.translationApiKey` | *(empty)* | `FS_LYRICS_TRANSLATION_API_KEY` | API key for lyric translation. | +| `lyrics.translationTargetLanguage` | `en` | `FS_LYRICS_TRANSLATION_TARGET_LANGUAGE` | Target language code. | +| `lyrics.alignment` | `center` | `FS_LYRICS_ALIGNMENT` | `center` / `left` / `right`. | + +--- + +## Auto DJ + +| Setting path | Default | Env variable | Available values / Description | +|-------------|---------|--------------|--------------------------------| +| `autoDJ.enabled` | `false` | `FS_AUTO_DJ_ENABLED` | `true` / `false`. | +| `autoDJ.itemCount` | `5` | `FS_AUTO_DJ_ITEM_COUNT` | Number of items to add. | +| `autoDJ.timing` | `1` | `FS_AUTO_DJ_TIMING` | Timing value (number). | + +--- + +## CSS + +| Setting path | Default | Env variable | Available values / Description | +|-------------|---------|--------------|--------------------------------| +| `css.content` | *(empty)* | `FS_CSS_CONTENT` | Custom CSS string (sanitized like in-app custom CSS). Set `FS_CSS_ENABLED=true` to apply. | +| `css.enabled` | `false` | `FS_CSS_ENABLED` | `true` / `false` — Enable custom CSS. | + +--- + +## Font + +| Setting path | Default | Env variable | Available values / Description | +|-------------|---------|--------------|--------------------------------| +| `font.type` | `builtIn` | `FS_FONT_TYPE` | `builtIn` / `system` / `custom`. | +| `font.builtIn` | `Inter` | `FS_FONT_BUILT_IN` | Built-in font name. | +| `font.system` | *(empty)* | `FS_FONT_SYSTEM` | System font name (when type is `system`). | diff --git a/settings.js.template b/settings.js.template index 8639edff4..af9642954 100644 --- a/settings.js.template +++ b/settings.js.template @@ -1 +1,86 @@ -"use strict";window.SERVER_URL="${SERVER_URL}";window.SERVER_NAME="${SERVER_NAME}";window.SERVER_TYPE="${SERVER_TYPE}";window.SERVER_LOCK="${SERVER_LOCK}";window.LEGACY_AUTHENTICATION="${LEGACY_AUTHENTICATION}";window.ANALYTICS_DISABLED="${ANALYTICS_DISABLED}"; +"use strict"; + +window.SERVER_URL = "${SERVER_URL}"; +window.SERVER_NAME = "${SERVER_NAME}"; +window.SERVER_TYPE = "${SERVER_TYPE}"; +window.SERVER_LOCK = "${SERVER_LOCK}"; +window.LEGACY_AUTHENTICATION = "${LEGACY_AUTHENTICATION}"; +window.ANALYTICS_DISABLED = "${ANALYTICS_DISABLED}"; + +window.FS_GENERAL_ACCENT = "${FS_GENERAL_ACCENT}"; +window.FS_GENERAL_ALBUM_BACKGROUND = "${FS_GENERAL_ALBUM_BACKGROUND}"; +window.FS_GENERAL_ALBUM_BACKGROUND_BLUR = "${FS_GENERAL_ALBUM_BACKGROUND_BLUR}"; +window.FS_GENERAL_ARTIST_BACKGROUND = "${FS_GENERAL_ARTIST_BACKGROUND}"; +window.FS_GENERAL_ARTIST_BACKGROUND_BLUR = "${FS_GENERAL_ARTIST_BACKGROUND_BLUR}"; +window.FS_GENERAL_BLUR_EXPLICIT_IMAGES = "${FS_GENERAL_BLUR_EXPLICIT_IMAGES}"; +window.FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER = "${FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER}"; +window.FS_GENERAL_ENABLE_GRID_MULTI_SELECT = "${FS_GENERAL_ENABLE_GRID_MULTI_SELECT}"; +window.FS_GENERAL_EXTERNAL_LINKS = "${FS_GENERAL_EXTERNAL_LINKS}"; +window.FS_GENERAL_FOLLOW_CURRENT_SONG = "${FS_GENERAL_FOLLOW_CURRENT_SONG}"; +window.FS_GENERAL_FOLLOW_SYSTEM_THEME = "${FS_GENERAL_FOLLOW_SYSTEM_THEME}"; +window.FS_GENERAL_HOME_FEATURE = "${FS_GENERAL_HOME_FEATURE}"; +window.FS_GENERAL_HOME_FEATURE_STYLE = "${FS_GENERAL_HOME_FEATURE_STYLE}"; +window.FS_GENERAL_LANGUAGE = "${FS_GENERAL_LANGUAGE}"; +window.FS_GENERAL_LAST_FM = "${FS_GENERAL_LAST_FM}"; +window.FS_GENERAL_LASTFM_API_KEY = "${FS_GENERAL_LASTFM_API_KEY}"; +window.FS_GENERAL_MUSIC_BRAINZ = "${FS_GENERAL_MUSIC_BRAINZ}"; +window.FS_GENERAL_NATIVE_ASPECT_RATIO = "${FS_GENERAL_NATIVE_ASPECT_RATIO}"; +window.FS_GENERAL_PATH_REPLACE = "${FS_GENERAL_PATH_REPLACE}"; +window.FS_GENERAL_PATH_REPLACE_WITH = "${FS_GENERAL_PATH_REPLACE_WITH}"; +window.FS_GENERAL_PLAYERBAR_OPEN_DRAWER = "${FS_GENERAL_PLAYERBAR_OPEN_DRAWER}"; +window.FS_GENERAL_PRIMARY_SHADE = "${FS_GENERAL_PRIMARY_SHADE}"; +window.FS_GENERAL_RESUME = "${FS_GENERAL_RESUME}"; +window.FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR = "${FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR}"; +window.FS_GENERAL_SHOW_RATINGS = "${FS_GENERAL_SHOW_RATINGS}"; +window.FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR = "${FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR}"; +window.FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION = "${FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION}"; +window.FS_GENERAL_SIDEBAR_COLLAPSE_SHARED = "${FS_GENERAL_SIDEBAR_COLLAPSE_SHARED}"; +window.FS_GENERAL_SIDEBAR_PLAYLIST_LIST = "${FS_GENERAL_SIDEBAR_PLAYLIST_LIST}"; +window.FS_GENERAL_SIDEBAR_PLAYLIST_SORTING = "${FS_GENERAL_SIDEBAR_PLAYLIST_SORTING}"; +window.FS_GENERAL_SIDE_QUEUE_TYPE = "${FS_GENERAL_SIDE_QUEUE_TYPE}"; +window.FS_GENERAL_THEME = "${FS_GENERAL_THEME}"; +window.FS_GENERAL_THEME_DARK = "${FS_GENERAL_THEME_DARK}"; +window.FS_GENERAL_THEME_LIGHT = "${FS_GENERAL_THEME_LIGHT}"; +window.FS_GENERAL_USE_THEME_ACCENT_COLOR = "${FS_GENERAL_USE_THEME_ACCENT_COLOR}"; +window.FS_GENERAL_USE_THEME_PRIMARY_SHADE = "${FS_GENERAL_USE_THEME_PRIMARY_SHADE}"; +window.FS_GENERAL_ZOOM_FACTOR = "${FS_GENERAL_ZOOM_FACTOR}"; + +window.FS_PLAYBACK_MEDIA_SESSION = "${FS_PLAYBACK_MEDIA_SESSION}"; +window.FS_PLAYBACK_WEB_AUDIO = "${FS_PLAYBACK_WEB_AUDIO}"; +window.FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE = "${FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE}"; +window.FS_PLAYBACK_PRESERVE_PITCH = "${FS_PLAYBACK_PRESERVE_PITCH}"; +window.FS_PLAYBACK_SCROBBLE_ENABLED = "${FS_PLAYBACK_SCROBBLE_ENABLED}"; +window.FS_PLAYBACK_SCROBBLE_NOTIFY = "${FS_PLAYBACK_SCROBBLE_NOTIFY}"; +window.FS_PLAYBACK_SCROBBLE_AT_DURATION = "${FS_PLAYBACK_SCROBBLE_AT_DURATION}"; +window.FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE = "${FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE}"; +window.FS_PLAYBACK_TRANSCODE_ENABLED = "${FS_PLAYBACK_TRANSCODE_ENABLED}"; + +window.FS_DISCORD_ENABLED = "${FS_DISCORD_ENABLED}"; +window.FS_DISCORD_CLIENT_ID = "${FS_DISCORD_CLIENT_ID}"; +window.FS_DISCORD_DISPLAY_TYPE = "${FS_DISCORD_DISPLAY_TYPE}"; +window.FS_DISCORD_LINK_TYPE = "${FS_DISCORD_LINK_TYPE}"; +window.FS_DISCORD_SHOW_AS_LISTENING = "${FS_DISCORD_SHOW_AS_LISTENING}"; +window.FS_DISCORD_SHOW_PAUSED = "${FS_DISCORD_SHOW_PAUSED}"; +window.FS_DISCORD_SHOW_SERVER_IMAGE = "${FS_DISCORD_SHOW_SERVER_IMAGE}"; +window.FS_DISCORD_SHOW_STATE_ICON = "${FS_DISCORD_SHOW_STATE_ICON}"; + +window.FS_LYRICS_FETCH = "${FS_LYRICS_FETCH}"; +window.FS_LYRICS_FOLLOW = "${FS_LYRICS_FOLLOW}"; +window.FS_LYRICS_DELAY_MS = "${FS_LYRICS_DELAY_MS}"; +window.FS_LYRICS_PREFER_LOCAL = "${FS_LYRICS_PREFER_LOCAL}"; +window.FS_LYRICS_SHOW_MATCH = "${FS_LYRICS_SHOW_MATCH}"; +window.FS_LYRICS_SHOW_PROVIDER = "${FS_LYRICS_SHOW_PROVIDER}"; +window.FS_LYRICS_ENABLE_AUTO_TRANSLATION = "${FS_LYRICS_ENABLE_AUTO_TRANSLATION}"; +window.FS_LYRICS_TRANSLATION_API_KEY = "${FS_LYRICS_TRANSLATION_API_KEY}"; +window.FS_LYRICS_TRANSLATION_TARGET_LANGUAGE = "${FS_LYRICS_TRANSLATION_TARGET_LANGUAGE}"; +window.FS_LYRICS_ALIGNMENT = "${FS_LYRICS_ALIGNMENT}"; + +window.FS_AUTO_DJ_ENABLED = "${FS_AUTO_DJ_ENABLED}"; +window.FS_AUTO_DJ_ITEM_COUNT = "${FS_AUTO_DJ_ITEM_COUNT}"; +window.FS_AUTO_DJ_TIMING = "${FS_AUTO_DJ_TIMING}"; + +window.FS_CSS_CONTENT = "${FS_CSS_CONTENT}"; +window.FS_CSS_ENABLED = "${FS_CSS_ENABLED}"; +window.FS_FONT_TYPE = "${FS_FONT_TYPE}"; +window.FS_FONT_BUILT_IN = "${FS_FONT_BUILT_IN}"; +window.FS_FONT_SYSTEM = "${FS_FONT_SYSTEM}"; diff --git a/src/renderer/global.d.ts b/src/renderer/global.d.ts index 02af53bd7..864f0c063 100644 --- a/src/renderer/global.d.ts +++ b/src/renderer/global.d.ts @@ -1,6 +1,78 @@ declare global { interface Window { ANALYTICS_DISABLED?: boolean | string; + FS_AUTO_DJ_ENABLED?: string; + FS_AUTO_DJ_ITEM_COUNT?: string; + FS_AUTO_DJ_TIMING?: string; + FS_CSS_CONTENT?: string; + FS_CSS_ENABLED?: string; + FS_DISCORD_CLIENT_ID?: string; + FS_DISCORD_DISPLAY_TYPE?: string; + FS_DISCORD_ENABLED?: string; + FS_DISCORD_LINK_TYPE?: string; + FS_DISCORD_SHOW_AS_LISTENING?: string; + FS_DISCORD_SHOW_PAUSED?: string; + FS_DISCORD_SHOW_SERVER_IMAGE?: string; + FS_DISCORD_SHOW_STATE_ICON?: string; + FS_FONT_BUILT_IN?: string; + FS_FONT_SYSTEM?: string; + FS_FONT_TYPE?: string; + FS_GENERAL_ACCENT?: string; + FS_GENERAL_ALBUM_BACKGROUND?: string; + FS_GENERAL_ALBUM_BACKGROUND_BLUR?: string; + FS_GENERAL_ARTIST_BACKGROUND?: string; + FS_GENERAL_ARTIST_BACKGROUND_BLUR?: string; + FS_GENERAL_BLUR_EXPLICIT_IMAGES?: string; + FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER?: string; + FS_GENERAL_ENABLE_GRID_MULTI_SELECT?: string; + FS_GENERAL_EXTERNAL_LINKS?: string; + FS_GENERAL_FOLLOW_CURRENT_SONG?: string; + FS_GENERAL_FOLLOW_SYSTEM_THEME?: string; + FS_GENERAL_HOME_FEATURE?: string; + FS_GENERAL_HOME_FEATURE_STYLE?: string; + FS_GENERAL_LANGUAGE?: string; + FS_GENERAL_LAST_FM?: string; + FS_GENERAL_LASTFM_API_KEY?: string; + FS_GENERAL_MUSIC_BRAINZ?: string; + FS_GENERAL_NATIVE_ASPECT_RATIO?: string; + FS_GENERAL_PATH_REPLACE?: string; + FS_GENERAL_PATH_REPLACE_WITH?: string; + FS_GENERAL_PLAYERBAR_OPEN_DRAWER?: string; + FS_GENERAL_PRIMARY_SHADE?: string; + FS_GENERAL_RESUME?: string; + FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR?: string; + FS_GENERAL_SHOW_RATINGS?: string; + FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR?: string; + FS_GENERAL_SIDE_QUEUE_TYPE?: string; + FS_GENERAL_SIDEBAR_COLLAPSE_SHARED?: string; + FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION?: string; + FS_GENERAL_SIDEBAR_PLAYLIST_LIST?: string; + FS_GENERAL_SIDEBAR_PLAYLIST_SORTING?: string; + FS_GENERAL_THEME?: string; + FS_GENERAL_THEME_DARK?: string; + FS_GENERAL_THEME_LIGHT?: string; + FS_GENERAL_USE_THEME_ACCENT_COLOR?: string; + FS_GENERAL_USE_THEME_PRIMARY_SHADE?: string; + FS_GENERAL_ZOOM_FACTOR?: string; + FS_LYRICS_ALIGNMENT?: string; + FS_LYRICS_DELAY_MS?: string; + FS_LYRICS_ENABLE_AUTO_TRANSLATION?: string; + FS_LYRICS_FETCH?: string; + FS_LYRICS_FOLLOW?: string; + FS_LYRICS_PREFER_LOCAL?: string; + FS_LYRICS_SHOW_MATCH?: string; + FS_LYRICS_SHOW_PROVIDER?: string; + FS_LYRICS_TRANSLATION_API_KEY?: string; + FS_LYRICS_TRANSLATION_TARGET_LANGUAGE?: string; + FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE?: string; + FS_PLAYBACK_MEDIA_SESSION?: string; + FS_PLAYBACK_PRESERVE_PITCH?: string; + FS_PLAYBACK_SCROBBLE_AT_DURATION?: string; + FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE?: string; + FS_PLAYBACK_SCROBBLE_ENABLED?: string; + FS_PLAYBACK_SCROBBLE_NOTIFY?: string; + FS_PLAYBACK_TRANSCODE_ENABLED?: string; + FS_PLAYBACK_WEB_AUDIO?: string; LEGACY_AUTHENTICATION?: boolean | string; SERVER_LOCK?: boolean | string; SERVER_NAME?: string; diff --git a/src/renderer/store/env-settings-overrides.ts b/src/renderer/store/env-settings-overrides.ts new file mode 100644 index 000000000..4e2b34ff3 --- /dev/null +++ b/src/renderer/store/env-settings-overrides.ts @@ -0,0 +1,376 @@ +import type { SettingsState } from './settings.store'; + +import { sanitizeCss } from '/@/renderer/utils/sanitize'; + +const APP_THEMES = new Set([ + 'ayuDark', + 'ayuLight', + 'catppuccinLatte', + 'catppuccinMocha', + 'defaultDark', + 'defaultLight', + 'dracula', + 'githubDark', + 'githubLight', + 'glassyDark', + 'gruvboxDark', + 'gruvboxLight', + 'highContrastDark', + 'highContrastLight', + 'materialDark', + 'materialLight', + 'monokai', + 'nightOwl', + 'nord', + 'oneDark', + 'rosePine', + 'rosePineDawn', + 'rosePineMoon', + 'shadesOfPurple', + 'solarizedDark', + 'solarizedLight', + 'tokyoNight', + 'vscodeDarkPlus', + 'vscodeLightPlus', +]); + +const DISCORD_DISPLAY_TYPES = new Set(['artist', 'feishin', 'song']); +const DISCORD_LINK_TYPES = new Set(['last_fm', 'musicbrainz', 'musicbrainz_last_fm', 'none']); +const LYRICS_ALIGNMENTS = new Set(['center', 'left', 'right']); +const FONT_TYPES = new Set(['builtIn', 'custom', 'system']); +const HOME_FEATURE_STYLES = new Set(['multiple', 'single']); +const SIDE_QUEUE_TYPES = new Set(['sideDrawerQueue', 'sideQueue']); + +export type EnvSettingsOverrides = DeepPartial< + Pick +>; + +type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; +}; + +interface EnvSettingSpec { + enumSet?: Set; + key: string; + path: [string, string, string] | [string, string]; + skipIfEmpty?: boolean; + transform?: (raw: string) => unknown; + type: 'bool' | 'enum' | 'num' | 'string'; +} + +function setAtPath( + obj: EnvSettingsOverrides, + path: [string, string, string] | [string, string], + value: unknown, +): void { + const [a, b, c] = path; + const root = (obj as Record)[a] ?? {}; + (obj as Record)[a] = root; + const branch = root as Record; + if (c === undefined) { + branch[b] = value; + } else { + const nested = branch[b] ?? {}; + branch[b] = nested; + (nested as Record)[c] = value; + } +} + +const RGB_ACCENT_REGEX = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/; + +const ENV_SETTING_SPECS: EnvSettingSpec[] = [ + { + key: 'FS_GENERAL_ACCENT', + path: ['general', 'accent'], + transform: (s) => (RGB_ACCENT_REGEX.test(s) ? s : undefined), + type: 'string', + }, + { key: 'FS_GENERAL_ALBUM_BACKGROUND', path: ['general', 'albumBackground'], type: 'bool' }, + { + key: 'FS_GENERAL_ALBUM_BACKGROUND_BLUR', + path: ['general', 'albumBackgroundBlur'], + type: 'num', + }, + { key: 'FS_GENERAL_ARTIST_BACKGROUND', path: ['general', 'artistBackground'], type: 'bool' }, + { + key: 'FS_GENERAL_ARTIST_BACKGROUND_BLUR', + path: ['general', 'artistBackgroundBlur'], + type: 'num', + }, + { + key: 'FS_GENERAL_BLUR_EXPLICIT_IMAGES', + path: ['general', 'blurExplicitImages'], + type: 'bool', + }, + { + key: 'FS_GENERAL_COMBINED_LYRICS_AND_VISUALIZER', + path: ['general', 'combinedLyricsAndVisualizer'], + type: 'bool', + }, + { + key: 'FS_GENERAL_ENABLE_GRID_MULTI_SELECT', + path: ['general', 'enableGridMultiSelect'], + type: 'bool', + }, + { key: 'FS_GENERAL_FOLLOW_CURRENT_SONG', path: ['general', 'followCurrentSong'], type: 'bool' }, + { key: 'FS_GENERAL_HOME_FEATURE', path: ['general', 'homeFeature'], type: 'bool' }, + { + enumSet: HOME_FEATURE_STYLES, + key: 'FS_GENERAL_HOME_FEATURE_STYLE', + path: ['general', 'homeFeatureStyle'], + type: 'enum', + }, + { + key: 'FS_GENERAL_LANGUAGE', + path: ['general', 'language'], + skipIfEmpty: true, + type: 'string', + }, + { + key: 'FS_GENERAL_PRIMARY_SHADE', + path: ['general', 'primaryShade'], + transform: (s) => { + const n = parseNum(s); + return n !== undefined ? Math.min(9, Math.max(0, Math.round(n))) : undefined; + }, + type: 'num', + }, + { enumSet: APP_THEMES, key: 'FS_GENERAL_THEME', path: ['general', 'theme'], type: 'enum' }, + { + enumSet: APP_THEMES, + key: 'FS_GENERAL_THEME_DARK', + path: ['general', 'themeDark'], + type: 'enum', + }, + { + enumSet: APP_THEMES, + key: 'FS_GENERAL_THEME_LIGHT', + path: ['general', 'themeLight'], + type: 'enum', + }, + { key: 'FS_GENERAL_FOLLOW_SYSTEM_THEME', path: ['general', 'followSystemTheme'], type: 'bool' }, + { key: 'FS_GENERAL_PATH_REPLACE', path: ['general', 'pathReplace'], type: 'string' }, + { key: 'FS_GENERAL_PATH_REPLACE_WITH', path: ['general', 'pathReplaceWith'], type: 'string' }, + { key: 'FS_GENERAL_LASTFM_API_KEY', path: ['general', 'lastfmApiKey'], type: 'string' }, + { key: 'FS_GENERAL_LAST_FM', path: ['general', 'lastFM'], type: 'bool' }, + { key: 'FS_GENERAL_MUSIC_BRAINZ', path: ['general', 'musicBrainz'], type: 'bool' }, + { key: 'FS_GENERAL_NATIVE_ASPECT_RATIO', path: ['general', 'nativeAspectRatio'], type: 'bool' }, + { + key: 'FS_GENERAL_PLAYERBAR_OPEN_DRAWER', + path: ['general', 'playerbarOpenDrawer'], + type: 'bool', + }, + { key: 'FS_GENERAL_EXTERNAL_LINKS', path: ['general', 'externalLinks'], type: 'bool' }, + { + key: 'FS_GENERAL_SHOW_LYRICS_IN_SIDEBAR', + path: ['general', 'showLyricsInSidebar'], + type: 'bool', + }, + { key: 'FS_GENERAL_SHOW_RATINGS', path: ['general', 'showRatings'], type: 'bool' }, + { + key: 'FS_GENERAL_SHOW_VISUALIZER_IN_SIDEBAR', + path: ['general', 'showVisualizerInSidebar'], + type: 'bool', + }, + { + key: 'FS_GENERAL_SIDEBAR_COLLAPSED_NAVIGATION', + path: ['general', 'sidebarCollapsedNavigation'], + type: 'bool', + }, + { + key: 'FS_GENERAL_SIDEBAR_COLLAPSE_SHARED', + path: ['general', 'sidebarCollapseShared'], + type: 'bool', + }, + { + key: 'FS_GENERAL_SIDEBAR_PLAYLIST_LIST', + path: ['general', 'sidebarPlaylistList'], + type: 'bool', + }, + { + key: 'FS_GENERAL_SIDEBAR_PLAYLIST_SORTING', + path: ['general', 'sidebarPlaylistSorting'], + type: 'bool', + }, + { + enumSet: SIDE_QUEUE_TYPES, + key: 'FS_GENERAL_SIDE_QUEUE_TYPE', + path: ['general', 'sideQueueType'], + type: 'enum', + }, + { key: 'FS_GENERAL_RESUME', path: ['general', 'resume'], type: 'bool' }, + { + key: 'FS_GENERAL_USE_THEME_ACCENT_COLOR', + path: ['general', 'useThemeAccentColor'], + type: 'bool', + }, + { + key: 'FS_GENERAL_USE_THEME_PRIMARY_SHADE', + path: ['general', 'useThemePrimaryShade'], + type: 'bool', + }, + { key: 'FS_GENERAL_ZOOM_FACTOR', path: ['general', 'zoomFactor'], type: 'num' }, + { key: 'FS_PLAYBACK_MEDIA_SESSION', path: ['playback', 'mediaSession'], type: 'bool' }, + { key: 'FS_PLAYBACK_WEB_AUDIO', path: ['playback', 'webAudio'], type: 'bool' }, + { + key: 'FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE', + path: ['playback', 'audioFadeOnStatusChange'], + type: 'bool', + }, + { key: 'FS_PLAYBACK_PRESERVE_PITCH', path: ['playback', 'preservePitch'], type: 'bool' }, + { + key: 'FS_PLAYBACK_SCROBBLE_ENABLED', + path: ['playback', 'scrobble', 'enabled'], + type: 'bool', + }, + { key: 'FS_PLAYBACK_SCROBBLE_NOTIFY', path: ['playback', 'scrobble', 'notify'], type: 'bool' }, + { + key: 'FS_PLAYBACK_SCROBBLE_AT_DURATION', + path: ['playback', 'scrobble', 'scrobbleAtDuration'], + type: 'num', + }, + { + key: 'FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE', + path: ['playback', 'scrobble', 'scrobbleAtPercentage'], + type: 'num', + }, + { + key: 'FS_PLAYBACK_TRANSCODE_ENABLED', + path: ['playback', 'transcode', 'enabled'], + type: 'bool', + }, + { key: 'FS_DISCORD_ENABLED', path: ['discord', 'enabled'], type: 'bool' }, + { + key: 'FS_DISCORD_CLIENT_ID', + path: ['discord', 'clientId'], + skipIfEmpty: true, + type: 'string', + }, + { + enumSet: DISCORD_DISPLAY_TYPES, + key: 'FS_DISCORD_DISPLAY_TYPE', + path: ['discord', 'displayType'], + type: 'enum', + }, + { + enumSet: DISCORD_LINK_TYPES, + key: 'FS_DISCORD_LINK_TYPE', + path: ['discord', 'linkType'], + type: 'enum', + }, + { key: 'FS_DISCORD_SHOW_AS_LISTENING', path: ['discord', 'showAsListening'], type: 'bool' }, + { key: 'FS_DISCORD_SHOW_PAUSED', path: ['discord', 'showPaused'], type: 'bool' }, + { key: 'FS_DISCORD_SHOW_SERVER_IMAGE', path: ['discord', 'showServerImage'], type: 'bool' }, + { key: 'FS_DISCORD_SHOW_STATE_ICON', path: ['discord', 'showStateIcon'], type: 'bool' }, + { key: 'FS_LYRICS_FETCH', path: ['lyrics', 'fetch'], type: 'bool' }, + { key: 'FS_LYRICS_FOLLOW', path: ['lyrics', 'follow'], type: 'bool' }, + { key: 'FS_LYRICS_DELAY_MS', path: ['lyrics', 'delayMs'], type: 'num' }, + { key: 'FS_LYRICS_PREFER_LOCAL', path: ['lyrics', 'preferLocalLyrics'], type: 'bool' }, + { key: 'FS_LYRICS_SHOW_MATCH', path: ['lyrics', 'showMatch'], type: 'bool' }, + { key: 'FS_LYRICS_SHOW_PROVIDER', path: ['lyrics', 'showProvider'], type: 'bool' }, + { + key: 'FS_LYRICS_ENABLE_AUTO_TRANSLATION', + path: ['lyrics', 'enableAutoTranslation'], + type: 'bool', + }, + { key: 'FS_LYRICS_TRANSLATION_API_KEY', path: ['lyrics', 'translationApiKey'], type: 'string' }, + { + key: 'FS_LYRICS_TRANSLATION_TARGET_LANGUAGE', + path: ['lyrics', 'translationTargetLanguage'], + skipIfEmpty: true, + type: 'string', + }, + { + enumSet: LYRICS_ALIGNMENTS, + key: 'FS_LYRICS_ALIGNMENT', + path: ['lyrics', 'alignment'], + type: 'enum', + }, + { key: 'FS_AUTO_DJ_ENABLED', path: ['autoDJ', 'enabled'], type: 'bool' }, + { key: 'FS_AUTO_DJ_ITEM_COUNT', path: ['autoDJ', 'itemCount'], type: 'num' }, + { key: 'FS_AUTO_DJ_TIMING', path: ['autoDJ', 'timing'], type: 'num' }, + { + key: 'FS_CSS_CONTENT', + path: ['css', 'content'], + transform: (s) => (s.trim() === '' ? undefined : sanitizeCss(`