Enhance album and queue pages with improved UI components and functionality
- Added new icons and improved layout in AlbumPage for better track display - Implemented track addition to queue functionality in AlbumPage - Enhanced QueuePage with clearer song information and improved styling - Added scrollable area for queue display and improved user interaction elements
This commit is contained in:
@@ -4,13 +4,14 @@ import { useParams } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
import { Album, Song } from '@/lib/navidrome';
|
||||
import { useNavidrome } from '@/app/components/NavidromeContext';
|
||||
import { Play, Heart } from 'lucide-react';
|
||||
import { Play, Heart, Clock, User, Plus } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import Link from 'next/link';
|
||||
import { PlusIcon } from "@radix-ui/react-icons";
|
||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext'
|
||||
import Loading from "@/app/components/loading";
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { getNavidromeAPI } from '@/lib/navidrome';
|
||||
|
||||
export default function AlbumPage() {
|
||||
@@ -20,7 +21,7 @@ export default function AlbumPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isStarred, setIsStarred] = useState(false);
|
||||
const { getAlbum, starItem, unstarItem } = useNavidrome();
|
||||
const { playTrack, addAlbumToQueue, playAlbum, playAlbumFromTrack } = useAudioPlayer();
|
||||
const { playTrack, addAlbumToQueue, playAlbum, playAlbumFromTrack, addToQueue, currentTrack } = useAudioPlayer();
|
||||
const api = getNavidromeAPI();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -80,6 +81,26 @@ export default function AlbumPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddToQueue = (song: Song) => {
|
||||
const track = {
|
||||
id: song.id,
|
||||
name: song.title,
|
||||
url: api.getStreamUrl(song.id),
|
||||
artist: song.artist,
|
||||
album: song.album,
|
||||
duration: song.duration,
|
||||
coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 300) : undefined,
|
||||
albumId: song.albumId,
|
||||
artistId: song.artistId
|
||||
};
|
||||
|
||||
addToQueue(track);
|
||||
};
|
||||
|
||||
const isCurrentlyPlaying = (song: Song): boolean => {
|
||||
return currentTrack?.id === song.id;
|
||||
};
|
||||
|
||||
const formatDuration = (duration: number): string => {
|
||||
const minutes = Math.floor(duration / 60);
|
||||
const seconds = duration % 60;
|
||||
@@ -123,26 +144,80 @@ export default function AlbumPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-4">
|
||||
<Separator />
|
||||
{tracklist.map((song, index) => (
|
||||
<div key={song.id} className="py-2 flex justify-between items-center hover:bg-hover rounded-lg cursor-pointer" onClick={() => handlePlayClick(song)}>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2 w-6 text-right">{song.track || index + 1}</div>
|
||||
<div>
|
||||
<p className="font-semibold text-lg flex items-center">
|
||||
{song.title}
|
||||
</p>
|
||||
<p className="text-sm font-normal flex items-center">
|
||||
<p className="text-gray-400">{song.artist}</p>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="h-[calc(100vh-500px)]">
|
||||
{tracklist.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-muted-foreground">No tracks available.</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm mr-4">{formatDuration(song.duration)}</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{tracklist.map((song, index) => (
|
||||
<div
|
||||
key={song.id}
|
||||
className={`group flex items-center p-3 rounded-lg hover:bg-accent/50 cursor-pointer transition-colors ${
|
||||
isCurrentlyPlaying(song) ? 'bg-accent/50 border-l-4 border-primary' : ''
|
||||
}`}
|
||||
onClick={() => handlePlayClick(song)}
|
||||
>
|
||||
{/* Track Number / Play Indicator */}
|
||||
<div className="w-8 text-center text-sm text-muted-foreground mr-3">
|
||||
{isCurrentlyPlaying(song) ? (
|
||||
<div className="w-4 h-4 mx-auto">
|
||||
<div className="w-full h-full bg-primary rounded-full animate-pulse" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<span className="group-hover:hidden">{song.track || index + 1}</span>
|
||||
<Play className="w-4 h-4 mx-auto hidden group-hover:block" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Song Info */}
|
||||
<div className="flex-1 min-w-0 mr-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<p className={`font-semibold truncate ${
|
||||
isCurrentlyPlaying(song) ? 'text-primary' : ''
|
||||
}`}>
|
||||
{song.title}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<User className="w-3 h-3" />
|
||||
<span className="truncate">{song.artist}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Duration */}
|
||||
<div className="flex items-center text-sm text-muted-foreground mr-4">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{formatDuration(song.duration)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center space-x-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleAddToQueue(song);
|
||||
}}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user