feat: update PlayNextQueue component and integrate it into Player; adjust layout in Playlist for improved UI
This commit is contained in:
@@ -22,6 +22,7 @@ export function CompactPlayer({ className }: CompactPlayerProps) {
|
||||
volume: 80,
|
||||
previous_volume: 80,
|
||||
position: 0,
|
||||
play_next_queue: [],
|
||||
})
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
|
||||
@@ -1,44 +1,57 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover'
|
||||
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) {
|
||||
export function PlayNextQueue({ queue }: PlayNextQueueProps) {
|
||||
if (queue.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Calculate total duration
|
||||
const totalDuration = queue.reduce((sum, sound) => sum + sound.duration, 0)
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="flex items-center justify-between py-2 px-2 rounded hover:bg-muted/50 cursor-pointer transition-colors">
|
||||
<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>
|
||||
<span className="text-sm font-medium">Play Next</span>
|
||||
</div>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{queue.length} {queue.length === 1 ? 'track' : 'tracks'}
|
||||
</Badge>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side="left" align="start" className="w-80 p-0">
|
||||
<div className="w-full flex flex-col">
|
||||
{/* Header - Fixed */}
|
||||
<div className="flex items-center justify-between p-3 border-b">
|
||||
<h4 className="font-semibold text-sm">Play Next Queue</h4>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{queue.length}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Track List */}
|
||||
<ScrollArea className={variant === 'maximized' ? 'max-h-60' : 'max-h-40'}>
|
||||
<div className="w-full">
|
||||
{/* Track List - Scrollable */}
|
||||
<ScrollArea className="h-96">
|
||||
<div className="w-full space-y-1 p-3 pt-2">
|
||||
{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',
|
||||
)}
|
||||
className="grid grid-cols-10 gap-2 items-center py-1.5 px-2 rounded hover:bg-muted/50 text-xs"
|
||||
>
|
||||
{/* Queue position - 1 column */}
|
||||
<div className="col-span-1 flex justify-center">
|
||||
@@ -47,12 +60,7 @@ export function PlayNextQueue({ queue, variant = 'normal' }: PlayNextQueueProps)
|
||||
|
||||
{/* 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',
|
||||
)}
|
||||
>
|
||||
<div className="bg-muted rounded flex items-center justify-center overflow-hidden w-5 h-5">
|
||||
{sound.thumbnail ? (
|
||||
<img
|
||||
src={filesService.getThumbnailUrl(sound.id)}
|
||||
@@ -67,12 +75,7 @@ export function PlayNextQueue({ queue, variant = 'normal' }: PlayNextQueueProps)
|
||||
|
||||
{/* 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',
|
||||
)}
|
||||
>
|
||||
<span className="font-medium truncate block text-xs">
|
||||
{sound.name}
|
||||
</span>
|
||||
</div>
|
||||
@@ -87,6 +90,16 @@ export function PlayNextQueue({ queue, variant = 'normal' }: PlayNextQueueProps)
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{/* Footer - Fixed */}
|
||||
<div className="p-3 pt-2 border-t bg-muted/30">
|
||||
<div className="flex justify-between items-center text-xs">
|
||||
<span className="text-muted-foreground">Total playtime</span>
|
||||
<span className="font-medium">{formatDuration(totalDuration)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
|
||||
|
||||
{/* Play Next Queue */}
|
||||
{state.play_next_queue.length > 0 && (
|
||||
<div className="mt-4 pt-4 border-t">
|
||||
<div className="mt-2">
|
||||
<PlayNextQueue queue={state.play_next_queue} />
|
||||
</div>
|
||||
)}
|
||||
@@ -448,14 +448,14 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
|
||||
|
||||
{/* Playlist Sidebar */}
|
||||
{state.playlist && (
|
||||
<div className="w-96 border-l bg-muted/10 backdrop-blur-sm">
|
||||
<div className="p-4 border-b">
|
||||
<div className="w-96 border-l bg-muted/10 backdrop-blur-sm flex flex-col">
|
||||
<div className="p-4 border-b flex-shrink-0">
|
||||
<h3 className="font-semibold">Playlist</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{state.playlist.sounds.length} tracks
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="p-4 overflow-y-auto flex-1">
|
||||
<Playlist
|
||||
playlist={state.playlist}
|
||||
currentIndex={state.index}
|
||||
@@ -470,8 +470,8 @@ 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} variant="maximized" />
|
||||
<div className="mt-2">
|
||||
<PlayNextQueue queue={state.play_next_queue} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function Playlist({
|
||||
|
||||
{/* Track List */}
|
||||
<ScrollArea
|
||||
className={variant === 'maximized' ? 'h-[calc(100vh-280px)]' : 'h-60'}
|
||||
className={variant === 'maximized' ? 'h-[calc(100vh-320px)]' : 'h-60'}
|
||||
>
|
||||
<div className="w-full">
|
||||
{filteredSounds.map((sound) => {
|
||||
|
||||
Reference in New Issue
Block a user