import { AddUrlDialog } from '@/components/AddUrlDialog' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' // import { useSocket } from '@/contexts/SocketContext' import { useAddUrlShortcut } from '@/hooks/use-keyboard-shortcuts' import { useTheme } from '@/hooks/use-theme' import { formatDuration } from '@/lib/format-duration' import { formatSize } from '@/lib/format-size' import { cn } from '@/lib/utils' import { apiService } from '@/services/api' import NumberFlow from '@number-flow/react' import { Clock, Play, Plus, Square, Weight } from 'lucide-react' import React, { useEffect, useState } from 'react' import { toast } from 'sonner' const lightModeColors = [ 'bg-red-600/30 hover:bg-red-600/40 text-red-900 border-red-600/20', 'bg-blue-700/30 hover:bg-blue-700/40 text-blue-900 border-blue-700/20', 'bg-yellow-400/30 hover:bg-yellow-400/40 text-yellow-800 border-yellow-400/20', 'bg-purple-700/30 hover:bg-purple-700/40 text-purple-900 border-purple-700/20', 'bg-green-600/30 hover:bg-green-600/40 text-green-900 border-green-600/20', 'bg-pink-500/30 hover:bg-pink-500/40 text-pink-900 border-pink-500/20', 'bg-cyan-500/30 hover:bg-cyan-500/40 text-cyan-900 border-cyan-500/20', 'bg-amber-500/30 hover:bg-amber-500/40 text-amber-900 border-amber-500/20', 'bg-indigo-800/30 hover:bg-indigo-800/40 text-indigo-900 border-indigo-800/20', 'bg-lime-500/30 hover:bg-lime-500/40 text-lime-900 border-lime-500/20', 'bg-fuchsia-600/30 hover:bg-fuchsia-600/40 text-fuchsia-900 border-fuchsia-600/20', 'bg-orange-600/30 hover:bg-orange-600/40 text-orange-900 border-orange-600/20', ] const darkModeColors = [ 'bg-red-700/40 hover:bg-red-700/50 text-red-100 border border-red-500/50', 'bg-blue-800/40 hover:bg-blue-800/50 text-blue-100 border border-blue-600/50', 'bg-yellow-600/40 hover:bg-yellow-600/50 text-yellow-100 border border-yellow-400/50', 'bg-purple-800/40 hover:bg-purple-800/50 text-purple-100 border border-purple-600/50', 'bg-green-700/40 hover:bg-green-700/50 text-green-100 border border-green-500/50', 'bg-pink-700/40 hover:bg-pink-700/50 text-pink-100 border border-pink-500/50', 'bg-cyan-700/40 hover:bg-cyan-700/50 text-cyan-100 border border-cyan-500/50', 'bg-amber-700/40 hover:bg-amber-700/50 text-amber-100 border border-amber-500/50', 'bg-indigo-900/40 hover:bg-indigo-900/50 text-indigo-100 border border-indigo-700/50', 'bg-lime-700/40 hover:bg-lime-700/50 text-lime-100 border border-lime-500/50', 'bg-fuchsia-800/40 hover:bg-fuchsia-800/50 text-fuchsia-100 border border-fuchsia-600/50', 'bg-orange-700/40 hover:bg-orange-700/50 text-orange-100 border border-orange-500/50', ] interface Sound { id: number name: string filename: string type: string duration: number size: number play_count: number is_normalized: boolean normalized_filename?: string } interface SoundCardProps { sound: Sound onPlay: (soundId: number) => void colorClasses: string } const SoundCard: React.FC = ({ sound, onPlay, colorClasses, }) => { const handlePlay = () => { onPlay(sound.id) } return (

{sound.name}

{formatDuration(sound.duration)}
{formatSize(sound.size)}
) } export function SoundboardPage() { const [sounds, setSounds] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [searchTerm, setSearchTerm] = useState('') const [addUrlDialogOpen, setAddUrlDialogOpen] = useState(false) const [currentColors, setCurrentColors] = useState(lightModeColors) // const { socket, isConnected } = useSocket() // Setup keyboard shortcut for CTRL+U useAddUrlShortcut(() => setAddUrlDialogOpen(true)) useEffect(() => { fetchSounds() }, []) // Listen for sound_play_count_changed events from socket useEffect(() => { const handleSoundPlayCountChanged = (event: CustomEvent) => { const { sound_id, play_count } = event.detail; // Update the sound in the local state setSounds(prevSounds => prevSounds.map(sound => sound.id === sound_id ? { ...sound, play_count } : sound ) ); }; // Listen for the custom event window.addEventListener('sound_play_count_changed', handleSoundPlayCountChanged as EventListener); // Cleanup return () => { window.removeEventListener('sound_play_count_changed', handleSoundPlayCountChanged as EventListener); }; }, []); const { theme } = useTheme() useEffect(() => { if (theme === 'dark') { setCurrentColors(darkModeColors) } else { setCurrentColors(lightModeColors) } }, [theme]) const getSoundColor = (soundIdx: number) => { const index = soundIdx % currentColors.length return currentColors[index] } const fetchSounds = async () => { try { setLoading(true) const response = await apiService.get('/api/soundboard/sounds?type=SDB') const data = await response.json() setSounds(data.sounds || []) } catch { setError('Failed to load sounds') toast.error('Failed to load sounds') } finally { setLoading(false) } } const handlePlaySound = async (soundId: number) => { try { // // Try socket.io first if connected // if (socket && isConnected) { // socket.emit('play_sound', { soundId }) // return // } // Fallback to API request await apiService.post(`/api/soundboard/sounds/${soundId}/play`) } catch { setError('Failed to play sound') toast.error('Failed to play sound') } } const handleStopAll = async () => { try { await apiService.post('/api/soundboard/stop-all') toast.success('All sounds stopped') } catch { setError('Failed to stop sounds') toast.error('Failed to stop sounds') } } const handleForceStopAll = async () => { try { const response = await apiService.post('/api/soundboard/force-stop') const data = await response.json() toast.success(`Force stopped ${data.stopped_count} sound instances`) } catch { setError('Failed to force stop sounds') toast.error('Failed to force stop sounds') } } const filteredSounds = sounds.filter(sound => sound.name.toLowerCase().includes(searchTerm.toLowerCase()), ) if (loading) { return (
Loading sounds...
) } if (error) { return (
{error}
) } return (
setSearchTerm(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
{filteredSounds.length} of {sounds.length} sounds
{filteredSounds.map((sound, idx) => ( ))}
{filteredSounds.length === 0 && (
{searchTerm ? 'No sounds found matching your search.' : 'No sounds available.'}
)} {/* Add URL Dialog */}
) }