diff --git a/src/locales/de.ts b/src/locales/de.ts index 9a3f40de..862a6b72 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -41,6 +41,7 @@ export const deTranslation = { mostPlayed: 'Meistgehört', recentlyPlayed: 'Kürzlich gespielt', discover: 'Entdecken', + discoverSongs: 'Titel entdecken', loadMore: 'Mehr laden', discoverMore: 'Mehr entdecken', discoverArtists: 'Künstler entdecken', diff --git a/src/locales/en.ts b/src/locales/en.ts index 6fd07e7f..21f42b40 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -43,6 +43,7 @@ export const enTranslation = { mostPlayed: 'Most Played', recentlyPlayed: 'Recently Played', discover: 'Discover', + discoverSongs: 'Discover Songs', loadMore: 'Load More', discoverMore: 'Discover More', discoverArtists: 'Discover Artists', diff --git a/src/locales/es.ts b/src/locales/es.ts index 2eaf3837..e9838e49 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -42,6 +42,7 @@ export const esTranslation = { mostPlayed: 'Más Reproducidos', recentlyPlayed: 'Reproducidos Recientemente', discover: 'Descubrir', + discoverSongs: 'Descubrir canciones', loadMore: 'Cargar Más', discoverMore: 'Descubrir Más', discoverArtists: 'Descubrir Artistas', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 00332085..e0b62d28 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -41,6 +41,7 @@ export const frTranslation = { mostPlayed: 'Les plus écoutés', recentlyPlayed: 'Récemment écoutés', discover: 'Découvrir', + discoverSongs: 'Découvrir des titres', loadMore: 'Charger plus', discoverMore: 'Découvrir plus', discoverArtists: 'Découvrir des artistes', diff --git a/src/locales/nb.ts b/src/locales/nb.ts index d840baec..c85fc0ea 100644 --- a/src/locales/nb.ts +++ b/src/locales/nb.ts @@ -41,6 +41,7 @@ export const nbTranslation = { mostPlayed: 'Mest spilt', recentlyPlayed: 'Nylig spilt', discover: 'Oppdag', + discoverSongs: 'Oppdag spor', loadMore: 'Last inn flere', discoverMore: 'Oppdag flere', discoverArtists: 'Oppdag artister', diff --git a/src/locales/nl.ts b/src/locales/nl.ts index babf582c..0f21b74f 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -41,6 +41,7 @@ export const nlTranslation = { mostPlayed: 'Meest gespeeld', recentlyPlayed: 'Recent afgespeeld', discover: 'Ontdekken', + discoverSongs: 'Nummers ontdekken', loadMore: 'Meer laden', discoverMore: 'Meer ontdekken', discoverArtists: 'Artiesten ontdekken', diff --git a/src/locales/ru.ts b/src/locales/ru.ts index 48a35936..b2add156 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -43,6 +43,7 @@ export const ruTranslation = { mostPlayed: 'Популярное', recentlyPlayed: 'Недавно проиграно', discover: 'Обзор', + discoverSongs: 'Открыть треки', loadMore: 'Ещё', discoverMore: 'Смотреть ещё', discoverArtists: 'Исполнители', diff --git a/src/locales/zh.ts b/src/locales/zh.ts index fc259946..bdde858e 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -41,6 +41,7 @@ export const zhTranslation = { mostPlayed: '最常播放', recentlyPlayed: '最近播放', discover: '发现', + discoverSongs: '发现曲目', loadMore: '加载更多', discoverMore: '发现更多', discoverArtists: '发现艺术家', diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 86a72937..7511f10f 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react'; import Hero from '../components/Hero'; import AlbumRow from '../components/AlbumRow'; -import { getAlbumList, getArtists, SubsonicAlbum, SubsonicArtist } from '../api/subsonic'; +import SongRail from '../components/SongRail'; +import { getAlbumList, getArtists, getRandomSongs, SubsonicAlbum, SubsonicArtist, SubsonicSong } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; import { NavLink, useNavigate } from 'react-router-dom'; import { ChevronRight } from 'lucide-react'; @@ -13,6 +14,7 @@ import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../uti const HOME_RANDOM_FETCH = 100; const HOME_HERO_COUNT = 8; const HOME_DISCOVER_SLICE = 20; +const HOME_DISCOVER_SONGS_SIZE = 18; export default function Home() { const homeSections = useHomeStore(s => s.sections); @@ -30,6 +32,7 @@ export default function Home() { const [mostPlayed, setMostPlayed] = useState([]); const [recentlyPlayed, setRecentlyPlayed] = useState([]); const [randomArtists, setRandomArtists] = useState([]); + const [discoverSongs, setDiscoverSongs] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -41,13 +44,16 @@ export default function Home() { const albumMix = mixCfg.enabled && (mixCfg.minAlbum > 0 || mixCfg.minArtist > 0); const randomSize = albumMix ? HOME_RANDOM_FETCH : HOME_DISCOVER_SLICE; - const [s, n, rRaw, f, rp, artists] = await Promise.all([ + const [s, n, rRaw, f, rp, artists, songs] = await Promise.all([ getAlbumList('starred', 12).catch(() => []), getAlbumList('newest', 12).catch(() => []), getAlbumList('random', randomSize).catch(() => []), getAlbumList('frequent', 12).catch(() => []), getAlbumList('recent', 12).catch(() => []), isVisible('discoverArtists') ? getArtists().catch(() => []) : Promise.resolve([]), + isVisible('discoverSongs') + ? getRandomSongs(HOME_DISCOVER_SONGS_SIZE).catch(() => [] as SubsonicSong[]) + : Promise.resolve([]), ]); if (cancelled) return; const r = await filterAlbumsByMixRatings(rRaw, mixCfg); @@ -57,6 +63,7 @@ export default function Home() { setRandom(r.slice(HOME_HERO_COUNT, HOME_DISCOVER_SLICE)); setMostPlayed(f); setRecentlyPlayed(rp); + setDiscoverSongs(songs); const shuffled = [...artists]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); @@ -128,6 +135,12 @@ export default function Home() { moreText={t('home.discoverMore')} /> )} + {isVisible('discoverSongs') && discoverSongs.length > 0 && ( + + )} {isVisible('discoverArtists') && randomArtists.length > 0 && (
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 8236b535..1ff824cf 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -4209,6 +4209,7 @@ function HomeCustomizer() { hero: t('home.hero'), recent: t('home.recent'), discover: t('home.discover'), + discoverSongs: t('home.discoverSongs'), discoverArtists: t('home.discoverArtists'), recentlyPlayed: t('home.recentlyPlayed'), starred: t('home.starred'), diff --git a/src/store/homeStore.ts b/src/store/homeStore.ts index 676750c9..62573cb7 100644 --- a/src/store/homeStore.ts +++ b/src/store/homeStore.ts @@ -1,7 +1,7 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -export type HomeSectionId = 'hero' | 'recent' | 'discover' | 'discoverArtists' | 'recentlyPlayed' | 'starred' | 'mostPlayed'; +export type HomeSectionId = 'hero' | 'recent' | 'discover' | 'discoverSongs' | 'discoverArtists' | 'recentlyPlayed' | 'starred' | 'mostPlayed'; export interface HomeSectionConfig { id: HomeSectionId; @@ -12,6 +12,7 @@ export const DEFAULT_HOME_SECTIONS: HomeSectionConfig[] = [ { id: 'hero', visible: true }, { id: 'recent', visible: true }, { id: 'discover', visible: true }, + { id: 'discoverSongs', visible: true }, { id: 'discoverArtists', visible: true }, { id: 'recentlyPlayed', visible: true }, { id: 'starred', visible: true }, @@ -33,6 +34,19 @@ export const useHomeStore = create()( })), reset: () => set({ sections: DEFAULT_HOME_SECTIONS }), }), - { name: 'psysonic_home' } + { + name: 'psysonic_home', + onRehydrateStorage: () => (state) => { + // Append any sections introduced after the user first persisted their order, + // so new defaults show up without forcing a manual Reset. + if (!state) return; + const safe = (state.sections ?? []).filter( + (s): s is HomeSectionConfig => s != null && typeof s.id === 'string', + ); + const known = new Set(safe.map(s => s.id)); + const missing = DEFAULT_HOME_SECTIONS.filter(s => !known.has(s.id)); + state.sections = missing.length > 0 ? [...safe, ...missing] : safe; + }, + } ) );