diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index d086c2f..f7359c2 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -9,6 +9,7 @@ import { Configuration } from "./pages/Configuration"; import { MusicStorage } from "./pages/MusicStorage"; import { S3Configuration } from "./pages/S3Configuration"; import { PersistentMusicPlayer } from "./components/PersistentMusicPlayer"; +import { MusicPlayerProvider, useMusicPlayer } from "./contexts/MusicPlayerContext"; import { useXmlParser } from "./hooks/useXmlParser"; import { usePaginatedSongs } from "./hooks/usePaginatedSongs"; import { formatTotalDuration } from "./utils/formatters"; @@ -70,12 +71,12 @@ const findPlaylistByName = (playlists: PlaylistNode[], name: string): PlaylistNo return []; }; -export default function RekordboxReader() { +const RekordboxReader: React.FC = () => { const { playlists, setPlaylists, loading: xmlLoading } = useXmlParser(); const [selectedSong, setSelectedSong] = useState(null); const [isDatabaseInitialized, setIsDatabaseInitialized] = useState(false); const [isSwitchingPlaylist, setIsSwitchingPlaylist] = useState(false); - const [currentPlayingSong, setCurrentPlayingSong] = useState(null); + const { currentSong, playSong, closePlayer } = useMusicPlayer(); // Memoized song selection handler to prevent unnecessary re-renders const handleSongSelect = useCallback((song: Song) => { @@ -86,14 +87,14 @@ export default function RekordboxReader() { const handlePlaySong = useCallback((song: Song) => { // Check if song has S3 file if (song.s3File?.hasS3File) { - setCurrentPlayingSong(song); + playSong(song); } - }, []); + }, [playSong]); // Handle closing the music player const handleCloseMusicPlayer = useCallback(() => { - setCurrentPlayingSong(null); - }, []); + closePlayer(); + }, [closePlayer]); // Format total duration for display const getFormattedTotalDuration = useCallback((durationSeconds?: number): string => { @@ -680,9 +681,19 @@ export default function RekordboxReader() { {/* Persistent Music Player */} ); -} +}; + +const RekordboxReaderApp: React.FC = () => { + return ( + + + + ); +}; + +export default RekordboxReaderApp; diff --git a/packages/frontend/src/contexts/MusicPlayerContext.tsx b/packages/frontend/src/contexts/MusicPlayerContext.tsx new file mode 100644 index 0000000..625e011 --- /dev/null +++ b/packages/frontend/src/contexts/MusicPlayerContext.tsx @@ -0,0 +1,46 @@ +import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react'; +import type { Song } from '../types/interfaces'; + +interface MusicPlayerContextType { + currentSong: Song | null; + playSong: (song: Song) => void; + closePlayer: () => void; +} + +const MusicPlayerContext = createContext(undefined); + +interface MusicPlayerProviderProps { + children: ReactNode; +} + +export const MusicPlayerProvider: React.FC = ({ children }) => { + const [currentSong, setCurrentSong] = useState(null); + + const playSong = useCallback((song: Song) => { + setCurrentSong(song); + }, []); + + const closePlayer = useCallback(() => { + setCurrentSong(null); + }, []); + + const value: MusicPlayerContextType = { + currentSong, + playSong, + closePlayer, + }; + + return ( + + {children} + + ); +}; + +export const useMusicPlayer = (): MusicPlayerContextType => { + const context = useContext(MusicPlayerContext); + if (context === undefined) { + throw new Error('useMusicPlayer must be used within a MusicPlayerProvider'); + } + return context; +}; \ No newline at end of file diff --git a/packages/frontend/src/pages/MusicStorage.tsx b/packages/frontend/src/pages/MusicStorage.tsx index d613621..32c22b3 100644 --- a/packages/frontend/src/pages/MusicStorage.tsx +++ b/packages/frontend/src/pages/MusicStorage.tsx @@ -25,7 +25,7 @@ import { import { FiPlay, FiTrash2, FiMusic, FiRefreshCw } from 'react-icons/fi'; import { MusicUpload } from '../components/MusicUpload'; import { SongMatching } from '../components/SongMatching'; -import { PersistentMusicPlayer } from '../components/PersistentMusicPlayer'; +import { useMusicPlayer } from '../contexts/MusicPlayerContext'; import type { Song } from '../types/interfaces'; interface MusicFile { @@ -43,9 +43,9 @@ interface MusicFile { export const MusicStorage: React.FC = () => { const [musicFiles, setMusicFiles] = useState([]); - const [currentPlayingSong, setCurrentPlayingSong] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isSyncing, setIsSyncing] = useState(false); + const { playSong } = useMusicPlayer(); const toast = useToast(); // Load music files on component mount @@ -132,9 +132,7 @@ export const MusicStorage: React.FC = () => { if (response.ok) { setMusicFiles(prev => prev.filter(file => file._id !== fileId)); - if (currentPlayingSong?.s3File?.musicFileId === fileId) { - setCurrentPlayingSong(null); - } + // The persistent player will handle removing the song if it was playing this file toast({ title: 'File Deleted', description: 'Music file deleted successfully', @@ -177,7 +175,7 @@ export const MusicStorage: React.FC = () => { }, }; - setCurrentPlayingSong(song); + playSong(song); } catch (error) { console.error('Error playing music file:', error); toast({ @@ -190,11 +188,6 @@ export const MusicStorage: React.FC = () => { } }; - // Handle closing the music player - const handleCloseMusicPlayer = () => { - setCurrentPlayingSong(null); - }; - const formatFileSize = (bytes: number): string => { const sizes = ['Bytes', 'KB', 'MB', 'GB']; if (bytes === 0) return '0 Bytes'; @@ -389,10 +382,7 @@ export const MusicStorage: React.FC = () => { {/* Persistent Music Player */} - + {/* The PersistentMusicPlayer component is now managed by the global context */} ); }; \ No newline at end of file