import React, { useState, useMemo, useRef, useEffect } from 'react'; import { Song } from '../types'; import { Play, MoreHorizontal, Heart, ThumbsDown, ListPlus, Pause, Search, Filter, Check, Globe, Lock, Loader2, ThumbsUp, Share2, Video, Info, Clock } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import { SongDropdownMenu } from './SongDropdownMenu'; import { ShareModal } from './ShareModal'; import { AlbumCover } from './AlbumCover'; interface SongListProps { songs: Song[]; currentSong: Song | null; selectedSong: Song | null; likedSongIds: Set; isPlaying: boolean; referenceTracks?: { id: string; filename: string; audio_url: string; duration?: number | null; created_at?: string }[]; onPlay: (song: Song) => void; onSelect: (song: Song) => void; onToggleLike: (songId: string) => void; onAddToPlaylist: (song: Song) => void; onOpenVideo?: (song: Song) => void; onShowDetails?: (song: Song) => void; onNavigateToProfile?: (username: string) => void; onReusePrompt?: (song: Song) => void; onDelete?: (song: Song) => void; onDeleteMany?: (songs: Song[]) => void; onUseAsReference?: (song: Song) => void; onCoverSong?: (song: Song) => void; } // ... existing code ... // Define Filter Types type FilterType = 'liked' | 'public' | 'private' | 'generating'; const FILTERS: { id: FilterType; label: string; icon: React.ReactNode }[] = [ { id: 'liked', label: 'Liked', icon: }, { id: 'public', label: 'Public', icon: }, { id: 'private', label: 'Private', icon: }, { id: 'generating', label: 'Generating', icon: }, ]; export const SongList: React.FC = ({ songs, currentSong, selectedSong, likedSongIds, isPlaying, referenceTracks = [], onPlay, onSelect, onToggleLike, onAddToPlaylist, onOpenVideo, onShowDetails, onNavigateToProfile, onReusePrompt, onDelete, onDeleteMany, onUseAsReference, onCoverSong }) => { const { user } = useAuth(); const [searchQuery, setSearchQuery] = useState(''); const [activeFilters, setActiveFilters] = useState>(new Set()); const [isFilterOpen, setIsFilterOpen] = useState(false); const [isSelecting, setIsSelecting] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); const filterRef = useRef(null); // Close filter dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (filterRef.current && !filterRef.current.contains(event.target as Node)) { setIsFilterOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); useEffect(() => { setSelectedIds(prev => { if (prev.size === 0) return prev; const validIds = new Set(songs.map(song => song.id)); const next = new Set(); prev.forEach(id => { if (validIds.has(id)) next.add(id); }); return next; }); }, [songs]); const toggleFilter = (filterId: FilterType) => { setActiveFilters(prev => { const newFilters = new Set(prev); if (newFilters.has(filterId)) { newFilters.delete(filterId); } else { newFilters.add(filterId); } return newFilters; }); }; const filteredSongs = useMemo(() => { return songs.filter(song => { // 1. Search Logic const matchesSearch = song.title.toLowerCase().includes(searchQuery.toLowerCase()) || song.style.toLowerCase().includes(searchQuery.toLowerCase()) || song.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())); if (!matchesSearch) return false; // 2. Filter Logic if (activeFilters.size === 0) return true; if (activeFilters.has('liked') && !likedSongIds.has(song.id)) return false; if (activeFilters.has('public') && !song.isPublic) return false; if (activeFilters.has('private') && song.isPublic) return false; if (activeFilters.has('generating') && !song.isGenerating) return false; return true; }); }, [songs, searchQuery, activeFilters, likedSongIds]); const filteredUploads = useMemo(() => { if (activeFilters.size > 0) return []; if (!referenceTracks.length) return []; return referenceTracks.filter(track => { const title = track.filename.replace(/\.[^/.]+$/, ''); return title.toLowerCase().includes(searchQuery.toLowerCase()); }); }, [referenceTracks, searchQuery, activeFilters]); const listItems = useMemo(() => { const songItems = filteredSongs.map(song => ({ type: 'song' as const, id: song.id, createdAt: song.createdAt, song })); const uploadItems = filteredUploads.map(track => ({ type: 'upload' as const, id: track.id, createdAt: new Date(track.created_at || Date.now()), track })); return [...songItems, ...uploadItems].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); }, [filteredSongs, filteredUploads]); const selectableSongs = useMemo( () => filteredSongs.filter(song => !song.isGenerating), [filteredSongs] ); const allSelected = selectableSongs.length > 0 && selectableSongs.every(song => selectedIds.has(song.id)); const selectedSongs = selectableSongs.filter(song => selectedIds.has(song.id)); return (
{/* Container constraint */} {/* Header */}
Workspaces My Workspace
setSearchQuery(e.target.value)} placeholder="Search your songs..." className="w-full bg-zinc-100 dark:bg-[#121214] border border-zinc-200 dark:border-white/10 rounded-lg pl-10 pr-4 py-2.5 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-zinc-400 dark:focus:border-white/20 placeholder-zinc-500 dark:placeholder-zinc-600 transition-colors" />
{/* Filter Dropdown */} {isFilterOpen && (
Refine By
{FILTERS.map(filter => ( ))}
)}
{isSelecting && (
{selectedSongs.length} selected
)}
{/* List */}
{/* Reduced vertical spacing */} {listItems.length === 0 ? (

No songs match your filters.

) : ( listItems.map((item) => ( item.type === 'song' ? ( onPlay(item.song)} onSelect={() => onSelect(item.song)} onToggleSelect={() => { if (item.song.isGenerating) return; setSelectedIds(prev => { const next = new Set(prev); if (next.has(item.song.id)) next.delete(item.song.id); else next.add(item.song.id); return next; }); }} onToggleLike={() => onToggleLike(item.song.id)} onAddToPlaylist={() => onAddToPlaylist(item.song)} onOpenVideo={() => onOpenVideo && onOpenVideo(item.song)} onShowDetails={() => onShowDetails && onShowDetails(item.song)} onNavigateToProfile={onNavigateToProfile} onReusePrompt={() => onReusePrompt?.(item.song)} onDelete={() => onDelete?.(item.song)} onUseAsReference={() => onUseAsReference?.(item.song)} onCoverSong={() => onCoverSong?.(item.song)} /> ) : ( { onPlay({ id: `upload_${item.id}`, title, lyrics: '', style: 'Upload', coverUrl: '', duration: '0:00', createdAt: item.createdAt, tags: [], audioUrl, isPublic: false, } as Song); }} /> ) )) )}
{/* End container */}
); }; interface SongItemProps { song: Song; isCurrent: boolean; isSelected: boolean; isSelectionMode: boolean; isChecked: boolean; isLiked: boolean; isPlaying: boolean; isOwner: boolean; onPlay: () => void; onSelect: () => void; onToggleSelect: () => void; onToggleLike: () => void; onAddToPlaylist: () => void; onOpenVideo?: () => void; onShowDetails?: () => void; onNavigateToProfile?: (username: string) => void; onReusePrompt?: () => void; onDelete?: () => void; onUseAsReference?: () => void; onCoverSong?: () => void; } const SongItem: React.FC = ({ song, isCurrent, isSelected, isSelectionMode, isChecked, isLiked, isPlaying, isOwner, onPlay, onSelect, onToggleSelect, onToggleLike, onAddToPlaylist, onOpenVideo, onShowDetails, onNavigateToProfile, onReusePrompt, onDelete, onUseAsReference, onCoverSong }) => { const [showDropdown, setShowDropdown] = useState(false); const [shareModalOpen, setShareModalOpen] = useState(false); const [imageError, setImageError] = useState(false); return ( <>
{ if (!song.audioUrl || song.isGenerating) return; e.dataTransfer.effectAllowed = 'copy'; e.dataTransfer.setData('application/x-ace-audio', JSON.stringify({ url: song.audioUrl, title: song.title || 'Untitled', source: 'song', })); }} className={`group flex items-center gap-4 p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-[#18181b] transition-all cursor-pointer border ${isSelected ? 'bg-zinc-100 dark:bg-[#18181b] border-zinc-200 dark:border-white/10' : 'border-transparent bg-transparent'} ${song.audioUrl && !song.isGenerating ? 'cursor-grab active:cursor-grabbing' : ''}`} > {isSelectionMode && ( )} {/* Cover Art - Reduced size */}
{/* Use gradient fallback if no coverUrl or image fails to load */} {(!song.coverUrl || imageError) ? ( ) : ( {song.title} setImageError(true)} /> )} {song.isGenerating ? (
{song.queuePosition ? ( /* Queue indicator */ <>
Queue #{song.queuePosition} ) : ( /* Generating - Music Waveform Animation */
)}
) : (
{ e.stopPropagation(); onPlay(); }} >
{isCurrent && isPlaying ? ( ) : ( )}
)}
{/* Content */}

{song.title || (song.isGenerating ? (song.queuePosition ? "Queued..." : "Creating...") : "Untitled")}

v1.5 {song.isPublic === false && ( )}
{ e.stopPropagation(); if (song.creator && onNavigateToProfile) { onNavigateToProfile(song.creator); } }} >
{(song.creator?.[0] || 'U').toUpperCase()}
{song.creator || 'Unknown'}

{song.style}

{song.isGenerating && (
1 ? (song.progress ?? 0) / 100 : (song.progress ?? 0)) * 100) )}%`, }} />
)}
{/* Actions Row - Hidden while generating */} {!song.isGenerating && (
{/* Info Button - Visible only on small/medium screens where sidebar is hidden */}
setShowDropdown(false)} isOwner={isOwner} onCreateVideo={() => onOpenVideo?.(song)} onReusePrompt={() => onReusePrompt?.(song)} onAddToPlaylist={() => onAddToPlaylist?.(song)} onDelete={() => onDelete?.(song)} onShare={() => setShareModalOpen(true)} onUseAsReference={() => onUseAsReference?.()} onCoverSong={() => onCoverSong?.()} />
)}
{/* Timestamp */}
{song.isGenerating ? ( {song.queuePosition ? `#${song.queuePosition}` : 'Creating...'} ) : song.duration}
setShareModalOpen(false)} song={song} /> ); }; const UploadItem: React.FC<{ track: { id: string; filename: string; audio_url: string; duration?: number | null }; onPlay: (audioUrl: string, title: string) => void; }> = ({ track, onPlay }) => { const title = track.filename.replace(/\.[^/.]+$/, ''); const duration = track.duration ? `${Math.floor(track.duration / 60)}:${String(Math.floor(track.duration % 60)).padStart(2, '0')}` : '--:--'; return (
{ e.dataTransfer.effectAllowed = 'copy'; e.dataTransfer.setData('application/x-ace-audio', JSON.stringify({ url: track.audio_url, title, source: 'upload', })); }} >
{ e.stopPropagation(); onPlay(track.audio_url, title); }} >
{title}
Upload
{duration}
); };