From dff599e5afd153ab658d3b2d71459667a41393c7 Mon Sep 17 00:00:00 2001 From: Lars Behrends Date: Sat, 11 Apr 2026 01:42:45 +0200 Subject: [PATCH] Add track support and UI list in DetailView Introduce Track and ApiTrack types and add tracks to ApiMediaItem/Media. Map ApiMediaItem.tracks into Media in convertApiToMedia. Implement a conditional Tracks section in DetailView that lists sorted tracks with search input, track number, title, artist, duration and a play button (only shown when tracks exist). Files changed: src/types.ts, src/api.ts, src/components/DetailView.tsx. --- src/api.ts | 13 +++++++- src/components/DetailView.tsx | 57 ++++++++++++++++++++++++++++++++++- src/types.ts | 10 ++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/api.ts b/src/api.ts index 09616d3..fb4a562 100644 --- a/src/api.ts +++ b/src/api.ts @@ -39,6 +39,15 @@ export interface ApiEpisode { thumbnail: string; } +export interface ApiTrack { + id: number; + media_id: number; + track_number: number; + title: string; + duration: number | null; + artist: string; +} + export interface ApiMediaItem { id: number; title: string; @@ -70,6 +79,7 @@ export interface ApiMediaItem { lastActivity?: string | null; playtime?: number; episodes?: ApiEpisode[]; + tracks?: ApiTrack[]; } export interface ApiStaff { @@ -334,7 +344,8 @@ export function convertApiToMedia(apiItem: ApiMediaItem): Media { playCount: apiItem.playCount, lastActivity: apiItem.lastActivity, playtime: apiItem.playtime, - episodes: apiItem.episodes + episodes: apiItem.episodes, + tracks: apiItem.tracks }; } diff --git a/src/components/DetailView.tsx b/src/components/DetailView.tsx index 96d6ff9..2840ad4 100644 --- a/src/components/DetailView.tsx +++ b/src/components/DetailView.tsx @@ -1,4 +1,4 @@ -import { Media, Staff } from '@/types'; +import { Media, Staff, Track } from '@/types'; import { useNavigate } from 'react-router-dom'; import { useState, useMemo, useEffect } from 'react'; import { @@ -364,6 +364,61 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) { )} + + {/* Tracks Section - Only show if tracks data exists (Music) */} + {media.tracks && media.tracks.length > 0 && ( +
+
+
+
+ {media.tracks.length} Track{media.tracks.length !== 1 ? 's' : ''} +
+
+
+
+ + +
+ + +
+
+ +
+
+ {media.tracks + .sort((a, b) => a.track_number - b.track_number) + .map((track, index) => ( +
+
+
+ {track.track_number} +
+
+

+ {track.title} +

+

{track.artist}

+
+ {track.duration && ( + + {track.duration}s + + )} + +
+
+ ))} +
+
+
+ )} ); diff --git a/src/types.ts b/src/types.ts index ca8cd5e..b4c90f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,6 +16,7 @@ export interface Media { studios?: string[]; status?: 'watching' | 'completed' | 'planned' | 'dropped' | 'reading' | 'listening' | 'playing' | 'on-hold'; episodes?: Episode[]; + tracks?: Track[]; staff?: Staff[]; categories?: string[]; platforms?: string[]; @@ -39,6 +40,15 @@ export interface Episode { thumbnail: string; } +export interface Track { + id: number; + media_id: number; + track_number: number; + title: string; + duration: number | null; + artist: string; +} + export interface Staff { id: string; name: string;