diff --git a/src/api/mappers.ts b/src/api/mappers.ts
index bcbd6db..22c879d 100644
--- a/src/api/mappers.ts
+++ b/src/api/mappers.ts
@@ -130,6 +130,9 @@ export const toTrack = (r: RawTrack): Track => ({
liked: false,
format: r.file_format,
fileSize: r.file_size,
+ source: r.source,
+ createdAt: r.created_at,
+ enrichedAt: r.enriched_at ?? undefined,
});
export const toAlbum = (r: RawAlbum): Album => ({
diff --git a/src/api/types.ts b/src/api/types.ts
index 97d2b83..9d6cb26 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -30,6 +30,12 @@ export interface Track {
format?: string;
bitrate?: number;
liked: boolean;
+ /** Where the track entered the library (e.g. `upload`, `local_folder`). */
+ source?: string;
+ /** ISO timestamp the track was added to the library. */
+ createdAt?: string;
+ /** ISO timestamp the last successful enrichment ran; undefined if never. */
+ enrichedAt?: string;
}
export interface Album {
diff --git a/src/components/common/Icon.tsx b/src/components/common/Icon.tsx
index d2ce855..19fda55 100644
--- a/src/components/common/Icon.tsx
+++ b/src/components/common/Icon.tsx
@@ -19,6 +19,7 @@ import {
GearSix,
HardDrives,
Heart,
+ Info,
MagnifyingGlass,
Pause,
Play,
@@ -69,6 +70,7 @@ const ICONS = {
'skip-forward': SkipForward,
repeat: Repeat,
heart: Heart,
+ info: Info,
'thumbs-down': ThumbsDown,
'speaker-high': SpeakerHigh,
'speaker-x': SpeakerSimpleX,
diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx
index 4bccaf8..aef5ebc 100644
--- a/src/components/layout/AppShell.tsx
+++ b/src/components/layout/AppShell.tsx
@@ -3,6 +3,7 @@ import { Suspense } from 'react';
import { Sidebar } from './Sidebar';
import { PersistentPlayer } from '../player/PersistentPlayer';
import { QueuePanel } from '../player/QueuePanel';
+import { TrackInfoDrawer } from '../track/TrackInfoDrawer';
import { LoadingSkeleton } from '../common/LoadingSkeleton';
export function AppShell() {
@@ -31,6 +32,7 @@ export function AppShell() {
+
diff --git a/src/components/player/PersistentPlayer.tsx b/src/components/player/PersistentPlayer.tsx
index ee0f9e5..5ca7849 100644
--- a/src/components/player/PersistentPlayer.tsx
+++ b/src/components/player/PersistentPlayer.tsx
@@ -10,9 +10,9 @@ import {
setVolume,
toggleShuffle,
setRepeat,
- toggleNowPlaying,
toggleQueue,
} from '../../store/slices/player';
+import { openTrackInfo } from '../../store/slices/ui';
import { useAudioPlayer } from '../../hooks/useAudioPlayer';
import { useStreamCached } from '../../hooks/useStreamCached';
import { useResolvedQueueEntry } from '../../hooks/useResolvedQueueEntry';
@@ -49,8 +49,11 @@ export function PersistentPlayer() {
dispatch(toggleNowPlaying())}
- style={{ cursor: 'pointer' }}
+ onClick={() =>
+ currentEntry && dispatch(openTrackInfo(currentEntry.trackId))
+ }
+ style={{ cursor: currentEntry ? 'pointer' : 'default' }}
+ title={currentEntry ? t('trackInfo.open') : undefined}
>
diff --git a/src/components/player/QueuePanel.tsx b/src/components/player/QueuePanel.tsx
index b24d1a4..a0f72a3 100644
--- a/src/components/player/QueuePanel.tsx
+++ b/src/components/player/QueuePanel.tsx
@@ -10,6 +10,7 @@ import {
type QueueEntry,
} from '../../store/slices/queue';
import { toggleQueue } from '../../store/slices/player';
+import { openTrackInfo } from '../../store/slices/ui';
import { useResolvedQueueEntry } from '../../hooks/useResolvedQueueEntry';
export function QueuePanel() {
@@ -85,14 +86,27 @@ export function QueuePanel() {
{now.title}
{now.artistName}
-
+
{isRadio && (
-
+
{t('queue.radioActive')}
@@ -161,7 +175,11 @@ function QueueRow({
const albumTitle = resolved?.albumTitle ?? entry.albumTitle;
return (
-
+
diff --git a/src/components/track/TrackContextMenu.tsx b/src/components/track/TrackContextMenu.tsx
index 41cd6f4..1704fd5 100644
--- a/src/components/track/TrackContextMenu.tsx
+++ b/src/components/track/TrackContextMenu.tsx
@@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next';
import { useAppDispatch } from '../../hooks/useAppDispatch';
import { addToQueue, addNextInQueue } from '../../store/slices/queue';
import { play } from '../../store/slices/player';
+import { openTrackInfo } from '../../store/slices/ui';
import type { Track } from '../../api/types';
interface Props {
@@ -42,21 +43,45 @@ export function TrackContextMenu({
return (