feat: add PlayNextQueue component to display upcoming tracks in Player

This commit is contained in:
JSC
2025-10-04 19:40:01 +02:00
parent 9a2a9343d2
commit 0f8b96e73c
2 changed files with 113 additions and 0 deletions

View File

@@ -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 (
<div className="w-full">
{/* Header */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<ListPlus className="h-4 w-4 text-muted-foreground" />
<h4 className="font-medium text-sm">Play Next</h4>
</div>
<Badge variant="secondary" className="text-xs">
{queue.length} {queue.length === 1 ? 'track' : 'tracks'}
</Badge>
</div>
{/* Track List */}
<ScrollArea className={variant === 'maximized' ? 'max-h-60' : 'max-h-40'}>
<div className="w-full">
{queue.map((sound, index) => (
<div
key={`${sound.id}-${index}`}
className={cn(
'grid grid-cols-10 gap-2 items-center py-1.5 px-2 rounded text-xs bg-muted/30',
index > 0 && 'mt-1',
)}
>
{/* Queue position - 1 column */}
<div className="col-span-1 flex justify-center">
<span className="text-muted-foreground">{index + 1}</span>
</div>
{/* Thumbnail - 1 column */}
<div className="col-span-1">
<div
className={cn(
'bg-muted rounded flex items-center justify-center overflow-hidden',
variant === 'maximized' ? 'w-6 h-6' : 'w-5 h-5',
)}
>
{sound.thumbnail ? (
<img
src={filesService.getThumbnailUrl(sound.id)}
alt=""
className="w-full h-full object-cover"
/>
) : (
<Music className="h-3 w-3 text-muted-foreground" />
)}
</div>
</div>
{/* Track name - 6 columns */}
<div className="col-span-6">
<span
className={cn(
'font-medium truncate block text-muted-foreground',
variant === 'maximized' ? 'text-sm' : 'text-xs',
)}
>
{sound.name}
</span>
</div>
{/* Duration - 2 columns */}
<div className="col-span-2 text-right">
<span className="text-muted-foreground text-xs whitespace-nowrap">
{formatDuration(sound.duration)}
</span>
</div>
</div>
))}
</div>
</ScrollArea>
</div>
)
}

View File

@@ -18,6 +18,7 @@ import {
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { Playlist } from './Playlist' import { Playlist } from './Playlist'
import { PlayNextQueue } from './PlayNextQueue'
import { PlayerControls } from './PlayerControls' import { PlayerControls } from './PlayerControls'
import { PlayerProgress } from './PlayerProgress' import { PlayerProgress } from './PlayerProgress'
import { PlayerTrackInfo } from './PlayerTrackInfo' 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 return true
} }
@@ -81,6 +87,7 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
volume: 80, volume: 80,
previous_volume: 80, previous_volume: 80,
position: 0, position: 0,
play_next_queue: [],
}) })
const [displayMode, setDisplayMode] = useState<PlayerDisplayMode>(() => { const [displayMode, setDisplayMode] = useState<PlayerDisplayMode>(() => {
// Initialize from localStorage or default to 'normal' // 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 && (
<div className="mt-4 pt-4 border-t">
<PlayNextQueue queue={state.play_next_queue} />
</div>
)}
</div> </div>
)} )}
</CardContent> </CardContent>
@@ -453,6 +467,13 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
} }
variant="maximized" variant="maximized"
/> />
{/* Play Next Queue */}
{state.play_next_queue.length > 0 && (
<div className="mt-4 pt-4 border-t">
<PlayNextQueue queue={state.play_next_queue} variant="maximized" />
</div>
)}
</div> </div>
</div> </div>
)} )}