From 41f1f376bc55b2635dfe99d8ea4d8216df088104 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 4 Mar 2026 23:23:14 -0600 Subject: [PATCH] feat: customizable item layout on fullscreen player (#1769) * change container display to release type, readd badge styling to improve contrast * make everything customizable --- src/i18n/locales/en.json | 2 + .../components/full-screen-player-image.tsx | 42 +++++++-- .../player/components/full-screen-player.tsx | 1 - .../general/application-settings.tsx | 2 + .../general/fullscreen-player-settings.tsx | 38 +++++++++ src/renderer/store/settings.store.ts | 85 +++++++++++++++++++ 6 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 src/renderer/features/settings/components/general/fullscreen-player-settings.tsx diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 2ca1a0cec..323a5faed 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1013,6 +1013,8 @@ "sidebarCollapsedNavigation": "sidebar (collapsed) navigation", "sidebarConfiguration_description": "select the items and order in which they appear in the sidebar", "sidebarConfiguration": "sidebar configuration", + "playerItemConfiguration_description": "configure what items are shown, and in what order, on the fullscreen player", + "playerItemConfiguration": "player item configuration", "sidebarPlaylistList_description": "show or hide the playlist list in the sidebar", "sidebarPlaylistList": "sidebar playlist list", "sidebarPlaylistSorting_description": "allows manual playlist sorting in the sidebar using drag and drop instead of the default server order", diff --git a/src/renderer/features/player/components/full-screen-player-image.tsx b/src/renderer/features/player/components/full-screen-player-image.tsx index f47753ca1..588010ead 100644 --- a/src/renderer/features/player/components/full-screen-player-image.tsx +++ b/src/renderer/features/player/components/full-screen-player-image.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx'; +import { t } from 'i18next'; import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'motion/react'; import { Fragment, useEffect, useRef } from 'react'; import { generatePath, Link } from 'react-router'; @@ -101,7 +102,7 @@ export const FullScreenPlayerImage = () => { const currentSong = usePlayerSong(); const { nextSong } = usePlayerData(); - const { blurExplicitImages } = useGeneralSettings(); + const { blurExplicitImages, playerItems } = useGeneralSettings(); const isPlayingRadio = isRadioActive && isRadioPlaying; @@ -171,6 +172,38 @@ export const FullScreenPlayerImage = () => { nextSong?.explicitStatus, ]); + const builtDataItems = { + bit_depth: currentSong?.bitDepth && {currentSong?.bitDepth} bit, + bit_rate: currentSong?.bitRate && {currentSong?.bitRate} kbps, + bpm: currentSong?.bpm && ( + + {currentSong?.bpm} {t('common.bpm')} + + ), + codec: currentSong?.container && {currentSong?.container}, + disc_number: currentSong?.discNumber && ( + + {t('common.disc')} {currentSong?.discNumber} + + ), + genres: + currentSong?.genres && + currentSong?.genres + .slice(0, 2) + .map((genre) => {genre.name}), + release_date: currentSong?.releaseDate && {currentSong?.releaseDate}, + release_type: currentSong?.tags?.releasetype && ( + {currentSong?.tags?.releasetype[0]} + ), + release_year: currentSong?.releaseYear && {currentSong?.releaseYear}, + sample_rate: currentSong?.sampleRate && {currentSong?.sampleRate / 1000} kHz, + track_number: currentSong?.trackNumber && ( + + {t('common.trackNumber')} {currentSong?.trackNumber} + + ), + }; + return ( { {!isPlayingRadio && ( - {currentSong?.container && ( - {currentSong?.container} - )} - {currentSong?.releaseYear && ( - {currentSong?.releaseYear} - )} + {playerItems.map((i) => !i.disabled && builtDataItems[i.id])} )} diff --git a/src/renderer/features/player/components/full-screen-player.tsx b/src/renderer/features/player/components/full-screen-player.tsx index 668c8b450..323836355 100644 --- a/src/renderer/features/player/components/full-screen-player.tsx +++ b/src/renderer/features/player/components/full-screen-player.tsx @@ -555,7 +555,6 @@ const Controls = () => { /> - { + } diff --git a/src/renderer/features/settings/components/general/fullscreen-player-settings.tsx b/src/renderer/features/settings/components/general/fullscreen-player-settings.tsx new file mode 100644 index 000000000..41a7707db --- /dev/null +++ b/src/renderer/features/settings/components/general/fullscreen-player-settings.tsx @@ -0,0 +1,38 @@ +import { memo } from 'react'; + +import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items'; +import { + PlayerItem, + SortableItem, + useGeneralSettings, + useSettingsStoreActions, +} from '/@/renderer/store'; + +const PLAYER_ITEMS: Array<[PlayerItem, string]> = [ + [PlayerItem.BIT_DEPTH, 'common.bitDepth'], + [PlayerItem.BIT_RATE, 'common.bitrate'], + [PlayerItem.BPM, 'common.bpm'], + [PlayerItem.CODEC, 'common.codec'], + [PlayerItem.DISC_NUMBER, 'table.config.label.discNumber'], + [PlayerItem.GENRES, 'entity.genre_other'], + [PlayerItem.RELEASE_DATE, 'filter.releaseDate'], + [PlayerItem.RELEASE_TYPE, 'common.releaseType'], + [PlayerItem.RELEASE_YEAR, 'filter.releaseYear'], + [PlayerItem.SAMPLE_RATE, 'common.sampleRate'], + [PlayerItem.TRACK_NUMBER, 'table.config.label.trackNumber'], +]; + +export const FullscreenPlayerSettings = memo(() => { + const { playerItems } = useGeneralSettings(); + const { setPlayerItems } = useSettingsStoreActions(); + + return ( + []} + setItems={setPlayerItems} + title="setting.playerItemConfiguration" + /> + ); +}); diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index d7fce37df..b8da8223f 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -74,6 +74,20 @@ const HomeItemSchema = z.enum([ 'recentlyReleased', ]); +const PlayerItemSchema = z.enum([ + 'bit_depth', + 'bit_rate', + 'bpm', + 'disc_number', + 'sample_rate', + 'track_number', + 'codec', + 'release_year', + 'release_type', + 'release_date', + 'genres', +]); + const ArtistItemSchema = z.enum([ 'biography', 'compilations', @@ -461,6 +475,7 @@ export const GeneralSettingsSchema = z.object({ playButtonBehavior: z.nativeEnum(Play), playerbarOpenDrawer: z.boolean(), playerbarSlider: PlayerbarSliderSchema, + playerItems: z.array(SortableItemSchema(PlayerItemSchema)), playlistTarget: PlaylistTargetSchema, primaryShade: z.number().min(0).max(9), resume: z.boolean(), @@ -781,6 +796,20 @@ export enum PlayerbarSliderType { WAVEFORM = 'waveform', } +export enum PlayerItem { + BIT_DEPTH = 'bit_depth', + BIT_RATE = 'bit_rate', + BPM = 'bpm', + CODEC = 'codec', + DISC_NUMBER = 'disc_number', + GENRES = 'genres', + RELEASE_DATE = 'release_date', + RELEASE_TYPE = 'release_type', + RELEASE_YEAR = 'release_year', + SAMPLE_RATE = 'sample_rate', + TRACK_NUMBER = 'track_number', +} + export enum PlaylistTarget { ALBUM = 'album', TRACK = 'track', @@ -840,6 +869,7 @@ export interface SettingsSlice extends z.infer { setHomeItems: (item: SortableItem[]) => void; setList: (type: ItemListKey, data: DeepPartial) => void; setPlaybackFilters: (filters: PlayerFilter[]) => void; + setPlayerItems: (items: SortableItem[]) => void; setPlaylistBehavior: (target: PlaylistTarget) => void; setSettings: (data: DeepPartial) => void; setSidebarItems: (items: SidebarItemType[]) => void; @@ -864,6 +894,53 @@ export type TranscodingConfig = z.infer; export type VersionedSettings = SettingsState & { version: number }; +export const playerItems: SortableItem[] = [ + { + disabled: true, + id: PlayerItem.BIT_DEPTH, + }, + { + disabled: true, + id: PlayerItem.BIT_RATE, + }, + { + disabled: true, + id: PlayerItem.BPM, + }, + { + disabled: false, + id: PlayerItem.CODEC, + }, + { + disabled: true, + id: PlayerItem.DISC_NUMBER, + }, + { + disabled: true, + id: PlayerItem.GENRES, + }, + { + disabled: true, + id: PlayerItem.RELEASE_DATE, + }, + { + disabled: true, + id: PlayerItem.RELEASE_TYPE, + }, + { + disabled: false, + id: PlayerItem.RELEASE_YEAR, + }, + { + disabled: true, + id: PlayerItem.SAMPLE_RATE, + }, + { + disabled: true, + id: PlayerItem.TRACK_NUMBER, + }, +]; + export const sidebarItems: SidebarItemType[] = [ { disabled: true, @@ -1052,6 +1129,7 @@ const initialState: SettingsState = { barWidth: 2, type: PlayerbarSliderType.SLIDER, }, + playerItems, playlistTarget: PlaylistTarget.TRACK, primaryShade: 6, resume: true, @@ -1901,6 +1979,11 @@ export const useSettingsStore = createWithEqualityFn()( state.playback.filters = filters; }); }, + setPlayerItems: (items: SortableItem[]) => { + set((state) => { + state.general.playerItems = items; + }); + }, setPlaylistBehavior: (target: PlaylistTarget) => { set((state) => { state.general.playlistTarget = target; @@ -2412,6 +2495,8 @@ export const useSidebarPlaylistListFilterRegex = () => export const useSidebarItems = () => useSettingsStore((state) => state.general.sidebarItems, shallow); +export const usePlayerItems = () => useSettingsStore((state) => state.general.playerItems, shallow); + export const useSidebarCollapsedNavigation = () => useSettingsStore((state) => state.general.sidebarCollapsedNavigation, shallow);