feat(artist-detail): user-configurable visibility and order of page sections (closes #252) (#254)

Per bcorporaal's request in #252: album-oriented listeners want a
cleaner artist page focused on the album grid, with the freedom to
reorder/hide bio, top tracks, and similar artists.

Five sections are now reorder- and toggle-able from Settings →
Personalisation → "Artist page sections": Bio, Top tracks, Similar
artists, Albums, Also featured on. The fixed header block (artist
photo, name, action buttons) stays at the top.

Implementation reuses the existing sidebar customizer pattern:

- new `src/store/artistLayoutStore.ts` modelled on `sidebarStore.ts`
  with the same persist + onRehydrateStorage migration so future-added
  sections don't silently disappear from existing users' configs.
- `ArtistDetail.tsx` render block refactored from a flat sequence of
  conditional JSX into a `renderableSectionIds.map()` driven by the
  store. The "first rendered section gets marginTop: 0" rule now
  works regardless of the configured order — both hidden-by-toggle
  and empty-data sections are filtered before the layout decides
  what's first.
- `ArtistLayoutCustomizer` component in Settings, lifted from
  `LyricsSourcesCustomizer` (drag via `useDragSource` + `psy-drop`
  event, per-row toggle, reset button).
- 7 new i18n keys per locale across all 8 languages.

The store hook MUST stay above the loading / !artist early returns —
otherwise React's hook call order mismatches between renders and the
component fails silently. Lesson learned during the refactor.

Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Frank Stellmacher
2026-04-21 22:08:39 +02:00
committed by GitHub
parent c40243dfea
commit f7a721b7b1
11 changed files with 402 additions and 103 deletions
+8
View File
@@ -658,6 +658,14 @@ export const deTranslation = {
homeCustomizerTitle: 'Startseite',
sidebarTitle: 'Seitenleiste',
sidebarReset: 'Zurücksetzen',
artistLayoutTitle: 'Künstlerseiten-Abschnitte',
artistLayoutDesc: 'Per Drag & Drop neu anordnen, einzelne Abschnitte der Künstlerseite ein- oder ausblenden. Abschnitte ohne Daten werden automatisch übersprungen.',
artistLayoutReset: 'Zurücksetzen',
artistLayoutBio: 'Künstler-Biografie',
artistLayoutTopTracks: 'Top-Tracks',
artistLayoutSimilar: 'Ähnliche Künstler*innen',
artistLayoutAlbums: 'Alben',
artistLayoutFeatured: 'Auch enthalten auf',
sidebarDrag: 'Ziehen zum Umsortieren',
sidebarFixed: 'Immer sichtbar',
randomNavSplitTitle: 'Mix-Navigation aufteilen',
+8
View File
@@ -660,6 +660,14 @@ export const enTranslation = {
homeCustomizerTitle: 'Home Page',
sidebarTitle: 'Sidebar',
sidebarReset: 'Reset to default',
artistLayoutTitle: 'Artist page sections',
artistLayoutDesc: 'Drag to reorder, toggle to hide individual sections of the artist page. Sections without data are skipped automatically.',
artistLayoutReset: 'Reset to default',
artistLayoutBio: 'Artist biography',
artistLayoutTopTracks: 'Top tracks',
artistLayoutSimilar: 'Similar artists',
artistLayoutAlbums: 'Albums',
artistLayoutFeatured: 'Also featured on',
sidebarDrag: 'Drag to reorder',
sidebarFixed: 'Always visible',
randomNavSplitTitle: 'Split Mix navigation',
+8
View File
@@ -651,6 +651,14 @@ export const esTranslation = {
homeCustomizerTitle: 'Página de Inicio',
sidebarTitle: 'Barra Lateral',
sidebarReset: 'Restablecer a predeterminado',
artistLayoutTitle: 'Secciones de la página del artista',
artistLayoutDesc: 'Arrastra para reordenar, alterna para ocultar secciones individuales de la página del artista. Las secciones sin datos se omiten automáticamente.',
artistLayoutReset: 'Restablecer a predeterminado',
artistLayoutBio: 'Biografía del artista',
artistLayoutTopTracks: 'Mejores pistas',
artistLayoutSimilar: 'Artistas similares',
artistLayoutAlbums: 'Álbumes',
artistLayoutFeatured: 'También presente en',
sidebarDrag: 'Arrastra para reordenar',
sidebarFixed: 'Siempre visible',
randomNavSplitTitle: 'Dividir navegación Mix',
+8
View File
@@ -646,6 +646,14 @@ export const frTranslation = {
homeCustomizerTitle: 'Page d\'accueil',
sidebarTitle: 'Barre latérale',
sidebarReset: 'Réinitialiser',
artistLayoutTitle: 'Sections de la page artiste',
artistLayoutDesc: 'Glissez pour réorganiser, basculez pour masquer des sections individuelles de la page artiste. Les sections sans données sont ignorées automatiquement.',
artistLayoutReset: 'Réinitialiser',
artistLayoutBio: 'Biographie de l\'artiste',
artistLayoutTopTracks: 'Titres populaires',
artistLayoutSimilar: 'Artistes similaires',
artistLayoutAlbums: 'Albums',
artistLayoutFeatured: 'Apparaît aussi sur',
sidebarDrag: 'Glisser pour réorganiser',
sidebarFixed: 'Toujours visible',
randomNavSplitTitle: 'Diviser la navigation Mix',
+8
View File
@@ -645,6 +645,14 @@ export const nbTranslation = {
homeCustomizerTitle: 'Hjemmeside',
sidebarTitle: 'Sidefelt',
sidebarReset: 'Tilbakestill til standard',
artistLayoutTitle: 'Artistsidens seksjoner',
artistLayoutDesc: 'Dra for å omorganisere, veksle for å skjule individuelle seksjoner av artistsiden. Seksjoner uten data hoppes over automatisk.',
artistLayoutReset: 'Tilbakestill til standard',
artistLayoutBio: 'Artistbiografi',
artistLayoutTopTracks: 'Toppspor',
artistLayoutSimilar: 'Lignende artister',
artistLayoutAlbums: 'Album',
artistLayoutFeatured: 'Også med på',
sidebarDrag: 'Dra for å endre rekkefølge',
sidebarFixed: 'Alltid synlig',
randomNavSplitTitle: 'Del Mix-navigasjon',
+8
View File
@@ -645,6 +645,14 @@ export const nlTranslation = {
homeCustomizerTitle: 'Startpagina',
sidebarTitle: 'Zijbalk',
sidebarReset: 'Standaard herstellen',
artistLayoutTitle: 'Secties artiestenpagina',
artistLayoutDesc: 'Versleep om te herschikken, schakel om individuele secties van de artiestenpagina te verbergen. Secties zonder gegevens worden automatisch overgeslagen.',
artistLayoutReset: 'Standaard herstellen',
artistLayoutBio: 'Biografie van de artiest',
artistLayoutTopTracks: 'Populaire nummers',
artistLayoutSimilar: 'Soortgelijke artiesten',
artistLayoutAlbums: 'Albums',
artistLayoutFeatured: 'Ook te horen op',
sidebarDrag: 'Slepen om te herordenen',
sidebarFixed: 'Altijd zichtbaar',
randomNavSplitTitle: 'Mix-navigatie splitsen',
+8
View File
@@ -673,6 +673,14 @@ export const ruTranslation = {
homeCustomizerTitle: 'Главная',
sidebarTitle: 'Боковая панель',
sidebarReset: 'Сбросить порядок',
artistLayoutTitle: 'Разделы страницы исполнителя',
artistLayoutDesc: 'Перетаскивайте, чтобы изменить порядок, переключайте, чтобы скрыть отдельные разделы страницы исполнителя. Разделы без данных пропускаются автоматически.',
artistLayoutReset: 'Сбросить',
artistLayoutBio: 'Биография исполнителя',
artistLayoutTopTracks: 'Топ-треки',
artistLayoutSimilar: 'Похожие исполнители',
artistLayoutAlbums: 'Альбомы',
artistLayoutFeatured: 'Также участвует в',
sidebarDrag: 'Перетащите для порядка',
sidebarFixed: 'Всегда показывать',
randomNavSplitTitle: 'Разделить навигацию микса',
+8
View File
@@ -641,6 +641,14 @@ export const zhTranslation = {
homeCustomizerTitle: '首页',
sidebarTitle: '侧边栏',
sidebarReset: '重置为默认',
artistLayoutTitle: '艺术家页面板块',
artistLayoutDesc: '拖动以重新排序,切换以隐藏艺术家页面的各个板块。没有数据的板块会自动跳过。',
artistLayoutReset: '重置为默认',
artistLayoutBio: '艺术家简介',
artistLayoutTopTracks: '热门曲目',
artistLayoutSimilar: '相似艺术家',
artistLayoutAlbums: '专辑',
artistLayoutFeatured: '也参与了',
sidebarDrag: '拖动以重新排序',
sidebarFixed: '始终显示',
randomNavSplitTitle: '拆分混音导航',