From 0f8b96e73cb2b62b0884c1cb74f629c793e9a12e Mon Sep 17 00:00:00 2001 From: JSC Date: Sat, 4 Oct 2025 19:40:01 +0200 Subject: [PATCH] feat: add PlayNextQueue component to display upcoming tracks in Player --- src/components/player/PlayNextQueue.tsx | 92 +++++++++++++++++++++++++ src/components/player/Player.tsx | 21 ++++++ 2 files changed, 113 insertions(+) create mode 100644 src/components/player/PlayNextQueue.tsx diff --git a/src/components/player/PlayNextQueue.tsx b/src/components/player/PlayNextQueue.tsx new file mode 100644 index 0000000..89ab6a1 --- /dev/null +++ b/src/components/player/PlayNextQueue.tsx @@ -0,0 +1,92 @@ +import { Badge } from '@/components/ui/badge' +import { ScrollArea } from '@/components/ui/scroll-area' +import { filesService } from '@/lib/api/services/files' +import { type PlayerSound } from '@/lib/api/services/player' +import { cn } from '@/lib/utils' +import { formatDuration } from '@/utils/format-duration' +import { Music, ListPlus } from 'lucide-react' + +interface PlayNextQueueProps { + queue: PlayerSound[] + variant?: 'normal' | 'maximized' +} + +export function PlayNextQueue({ queue, variant = 'normal' }: PlayNextQueueProps) { + if (queue.length === 0) { + return null + } + + return ( +
+ {/* Header */} +
+
+ +

Play Next

+
+ + {queue.length} {queue.length === 1 ? 'track' : 'tracks'} + +
+ + {/* Track List */} + +
+ {queue.map((sound, index) => ( +
0 && 'mt-1', + )} + > + {/* Queue position - 1 column */} +
+ {index + 1} +
+ + {/* Thumbnail - 1 column */} +
+
+ {sound.thumbnail ? ( + + ) : ( + + )} +
+
+ + {/* Track name - 6 columns */} +
+ + {sound.name} + +
+ + {/* Duration - 2 columns */} +
+ + {formatDuration(sound.duration)} + +
+
+ ))} +
+
+
+ ) +} diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx index b1f8ae0..2ce3bb2 100644 --- a/src/components/player/Player.tsx +++ b/src/components/player/Player.tsx @@ -18,6 +18,7 @@ import { import { useCallback, useEffect, useRef, useState } from 'react' import { toast } from 'sonner' import { Playlist } from './Playlist' +import { PlayNextQueue } from './PlayNextQueue' import { PlayerControls } from './PlayerControls' import { PlayerProgress } from './PlayerProgress' import { PlayerTrackInfo } from './PlayerTrackInfo' @@ -66,6 +67,11 @@ function isPlayerStateEqual(state1: PlayerState, state2: PlayerState): boolean { } } + // Compare play_next_queue length + if (state1.play_next_queue.length !== state2.play_next_queue.length) { + return false + } + return true } @@ -81,6 +87,7 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) { volume: 80, previous_volume: 80, position: 0, + play_next_queue: [], }) const [displayMode, setDisplayMode] = useState(() => { // Initialize from localStorage or default to 'normal' @@ -368,6 +375,13 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) { ) } /> + + {/* Play Next Queue */} + {state.play_next_queue.length > 0 && ( +
+ +
+ )} )} @@ -453,6 +467,13 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) { } variant="maximized" /> + + {/* Play Next Queue */} + {state.play_next_queue.length > 0 && ( +
+ +
+ )} )}