import React, { useEffect, useRef } from 'react'; import { Song } from '../types'; import { Video, Edit3, Layers, Repeat, ListPlus, Download, Trash2, Share2 } from 'lucide-react'; interface SongDropdownMenuProps { song: Song; isOpen: boolean; onClose: () => void; isOwner?: boolean; position?: 'left' | 'right'; direction?: 'up' | 'down'; onCreateVideo?: () => void; onEditAudio?: () => void; onExtractStems?: () => void; onReusePrompt?: () => void; onAddToPlaylist?: () => void; onDownload?: () => void; onShare?: () => void; onDelete?: () => void; onUseAsReference?: () => void; onCoverSong?: () => void; } interface MenuItemProps { icon: React.ReactNode; label: string; onClick?: () => void; danger?: boolean; disabled?: boolean; } const MenuItem: React.FC = ({ icon, label, onClick, danger, disabled }) => ( ); const MenuDivider: React.FC = () => (
); export const SongDropdownMenu: React.FC = ({ song, isOpen, onClose, isOwner = false, position = 'right', direction = 'down', onCreateVideo, onEditAudio, onExtractStems, onReusePrompt, onAddToPlaylist, onDownload, onShare, onDelete, onUseAsReference, onCoverSong }) => { const menuRef = useRef(null); useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { onClose(); } }; const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose(); } }; if (isOpen) { document.addEventListener('mousedown', handleClickOutside); document.addEventListener('keydown', handleEscape); } return () => { document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('keydown', handleEscape); }; }, [isOpen, onClose]); if (!isOpen) return null; const handleAction = (action?: () => void) => { if (action) { action(); } onClose(); }; const handleEditAudio = () => { if (!song.audioUrl) return; const audioUrl = song.audioUrl.startsWith('http') ? song.audioUrl : `${window.location.origin}${song.audioUrl}`; window.open(`/editor?audioUrl=${encodeURIComponent(audioUrl)}`, '_blank'); onClose(); }; const handleExtractStems = () => { if (!song.audioUrl) return; const baseUrl = window.location.port === '3000' ? `${window.location.protocol}//${window.location.hostname}:3001` : window.location.origin; const audioUrl = song.audioUrl.startsWith('http') ? song.audioUrl : `${baseUrl}${song.audioUrl}`; window.open(`${baseUrl}/demucs-web/?audioUrl=${encodeURIComponent(audioUrl)}`, '_blank'); onClose(); }; const handleDownload = async () => { if (!song.audioUrl) return; try { // Fetch as blob to handle cross-origin const response = await fetch(song.audioUrl); const blob = await response.blob(); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${song.title || 'song'}.mp3`; document.body.appendChild(link); link.click(); document.body.removeChild(link); // Clean up blob URL URL.revokeObjectURL(url); } catch (error) { console.error('Download failed:', error); } onClose(); }; const positionClasses = position === 'left' ? 'left-0' : 'right-0'; const directionClasses = direction === 'up' ? 'bottom-full mb-2' : 'top-full mt-2'; const animationClasses = direction === 'up' ? 'animate-in fade-in slide-in-from-bottom-2' : 'animate-in fade-in slide-in-from-top-2'; return (
e.stopPropagation()} > {/* Creative Actions */} } label="Create Video" onClick={() => handleAction(onCreateVideo)} /> {isOwner && ( } label="Edit Audio" onClick={onEditAudio ? () => handleAction(onEditAudio) : handleEditAudio} /> )} } label="Extract Stems" onClick={onExtractStems ? () => handleAction(onExtractStems) : handleExtractStems} /> {onReusePrompt && ( } label="Reuse Prompt" onClick={() => handleAction(onReusePrompt)} /> )} {onUseAsReference && ( } label="Use as Reference" onClick={() => handleAction(onUseAsReference)} disabled={!song.audioUrl} /> )} {onCoverSong && ( } label="Cover Song" onClick={() => handleAction(onCoverSong)} disabled={!song.audioUrl} /> )} {/* Library Actions */} } label="Add to Playlist" onClick={() => handleAction(onAddToPlaylist)} /> } label="Download" onClick={onDownload ? () => handleAction(onDownload) : handleDownload} /> } label="Share" onClick={() => handleAction(onShare)} /> {/* Owner-only Actions */} {isOwner && ( <> } label="Delete Song" onClick={() => handleAction(onDelete)} danger /> )}
); };