Refactor song submission process to include YouTube metadata fetching; remove YouTube link requirement from settings for improved flexibility
This commit is contained in:
@ -8,8 +8,7 @@ const LobbyScreen = () => {
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [settings, setSettings] = useState({
|
||||
songsPerPlayer: 4,
|
||||
maxPlayers: 10,
|
||||
requireYoutubeLinks: true
|
||||
maxPlayers: 10
|
||||
});
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
@ -79,7 +78,6 @@ const LobbyScreen = () => {
|
||||
<h3><FontAwesomeIcon icon={faGear} /> Game Settings</h3>
|
||||
<p>Songs per player: {settings.songsPerPlayer}</p>
|
||||
<p>Max players: {settings.maxPlayers}</p>
|
||||
<p>YouTube links required: {settings.requireYoutubeLinks ? 'Yes' : 'No'}</p>
|
||||
|
||||
{isHost && (
|
||||
<button className="btn secondary" onClick={() => setShowSettings(true)}>
|
||||
@ -140,19 +138,6 @@ const LobbyScreen = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group checkbox">
|
||||
<label htmlFor="requireYoutubeLinks">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="requireYoutubeLinks"
|
||||
name="requireYoutubeLinks"
|
||||
checked={settings.requireYoutubeLinks}
|
||||
onChange={handleSettingsChange}
|
||||
/>
|
||||
Require YouTube links
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="modal-actions">
|
||||
<button className="btn secondary" onClick={() => setShowSettings(false)}>
|
||||
Cancel
|
||||
|
@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPlus, faTrash, faCheck, faMusic, faVideoCamera, faSearch, faSpinner, faExternalLinkAlt, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const SongSubmissionScreen = () => {
|
||||
const { lobby, currentPlayer, addSong, removeSong, setPlayerReady, searchYouTube } = useGame();
|
||||
const { lobby, currentPlayer, addSong, removeSong, setPlayerReady, searchYouTube, getYouTubeMetadata } = useGame();
|
||||
const [songs, setSongs] = useState([]);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [songForm, setSongForm] = useState({
|
||||
@ -51,7 +51,7 @@ const SongSubmissionScreen = () => {
|
||||
if (player.songs && Array.isArray(player.songs)) {
|
||||
console.log('Found player songs for', player.name, ':', player.songs.length);
|
||||
console.log('Songs data:', player.songs);
|
||||
setSongs(player.songs);
|
||||
setSongs(player.songs || []);
|
||||
} else {
|
||||
console.log('No songs array for player:', player);
|
||||
setSongs([]);
|
||||
@ -65,7 +65,7 @@ const SongSubmissionScreen = () => {
|
||||
};
|
||||
|
||||
fetchPlayerSongs();
|
||||
// Include lobby in the dependency array to ensure this runs when the lobby state changes
|
||||
// Important: This effect should run whenever the lobby state changes
|
||||
}, [lobby, currentPlayer]);
|
||||
|
||||
// Extract video ID from YouTube URL
|
||||
@ -116,12 +116,34 @@ const SongSubmissionScreen = () => {
|
||||
const videoId = extractVideoId(value);
|
||||
if (videoId) {
|
||||
setSongForm({ youtubeLink: value });
|
||||
setSelectedVideo({
|
||||
|
||||
// Set basic information immediately for a responsive UI
|
||||
const basicVideoInfo = {
|
||||
id: videoId,
|
||||
url: value,
|
||||
thumbnail: `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`
|
||||
});
|
||||
};
|
||||
setSelectedVideo(basicVideoInfo);
|
||||
setSearchResults([]); // Clear any existing search results
|
||||
|
||||
// Fetch additional metadata from the server
|
||||
try {
|
||||
setIsLoadingMetadata(true);
|
||||
const metadata = await getYouTubeMetadata(videoId);
|
||||
if (metadata) {
|
||||
// Update selected video with full metadata
|
||||
setSelectedVideo({
|
||||
...basicVideoInfo,
|
||||
title: metadata.title || 'Unknown Title',
|
||||
artist: metadata.artist || 'Unknown Artist',
|
||||
thumbnail: metadata.thumbnail || basicVideoInfo.thumbnail
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching video metadata:', error);
|
||||
} finally {
|
||||
setIsLoadingMetadata(false);
|
||||
}
|
||||
} else if (value.trim()) {
|
||||
// Clear any previous timeout
|
||||
if (searchTimeout.current) {
|
||||
@ -175,6 +197,9 @@ const SongSubmissionScreen = () => {
|
||||
// Use selected video data if available, otherwise fallback to search query or direct input
|
||||
let songData;
|
||||
|
||||
// Generate a consistent ID format that will work for deletion later
|
||||
const generateSongId = () => `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
if (selectedVideo) {
|
||||
// We have a selected video with full details - use all available metadata
|
||||
songData = {
|
||||
@ -182,7 +207,7 @@ const SongSubmissionScreen = () => {
|
||||
title: selectedVideo.title,
|
||||
artist: selectedVideo.artist,
|
||||
thumbnail: selectedVideo.thumbnail,
|
||||
id: `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` // Generate a unique ID
|
||||
id: generateSongId() // Consistent ID format
|
||||
};
|
||||
|
||||
console.log("Adding song with full metadata:", songData);
|
||||
@ -199,7 +224,7 @@ const SongSubmissionScreen = () => {
|
||||
youtubeLink: youtubeLink,
|
||||
// Include the videoId to make server-side processing easier
|
||||
videoId: videoId,
|
||||
id: `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` // Generate a unique ID
|
||||
id: generateSongId() // Consistent ID format
|
||||
};
|
||||
|
||||
console.log("Adding song with YouTube link:", songData);
|
||||
@ -210,19 +235,35 @@ const SongSubmissionScreen = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Add the song and manually update the local songs array to ensure UI reflects the change
|
||||
addSong(songData);
|
||||
|
||||
// Optimistically add the song to the local state to immediately reflect changes in UI
|
||||
// Note: The server response will ultimately override this if needed
|
||||
setSongs(prevSongs => [...prevSongs, songData]);
|
||||
|
||||
// Reset form
|
||||
setSongForm({ youtubeLink: '' });
|
||||
setSearchQuery('');
|
||||
setSearchResults([]);
|
||||
setSelectedVideo(null);
|
||||
setIsFormVisible(false);
|
||||
// Add the song and update UI when the server responds
|
||||
// We use the Promise-based approach to wait for the server-generated ID for proper deletion
|
||||
|
||||
// Add the song with callback to update songs when the server responds
|
||||
addSong(songData)
|
||||
.then((updatedLobby) => {
|
||||
console.log('Song added successfully, received updated lobby:', updatedLobby);
|
||||
if (updatedLobby && currentPlayer) {
|
||||
const player = updatedLobby.players.find(p => p.id === currentPlayer.id);
|
||||
if (player && player.songs) {
|
||||
console.log('Setting songs from addSong response:', player.songs);
|
||||
setSongs([...player.songs]); // Create a new array to ensure state update
|
||||
} else {
|
||||
console.warn('Player or songs not found in updated lobby');
|
||||
}
|
||||
} else {
|
||||
console.warn('Missing lobby or currentPlayer in response');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error adding song:', error);
|
||||
});
|
||||
|
||||
// Reset form
|
||||
setSongForm({ youtubeLink: '' });
|
||||
setSearchQuery('');
|
||||
setSearchResults([]);
|
||||
setSelectedVideo(null);
|
||||
setIsFormVisible(false);
|
||||
};
|
||||
|
||||
const handleRemoveSong = (songId) => {
|
||||
|
@ -86,7 +86,7 @@ export function GameProvider({ children }) {
|
||||
// Save game info when lobby is joined
|
||||
useEffect(() => {
|
||||
if (lobby && currentPlayer) {
|
||||
localStorage.setItem('songBattleGame', JSON.stringify({
|
||||
sessionStorage.setItem('songBattleGame', JSON.stringify({
|
||||
lobbyId: lobby.id,
|
||||
playerName: currentPlayer.name
|
||||
}));
|
||||
@ -330,45 +330,52 @@ export function GameProvider({ children }) {
|
||||
const addSong = (song) => {
|
||||
if (!socket || !isConnected || !lobby) {
|
||||
setError('Not connected to server or no active lobby');
|
||||
return;
|
||||
return Promise.reject('Not connected to server or no active lobby');
|
||||
}
|
||||
|
||||
console.log('Attempting to add song:', song);
|
||||
console.log('Current player state:', currentPlayer);
|
||||
console.log('Current lobby state before adding song:', lobby);
|
||||
|
||||
socket.emit('add_song', { song }, (response) => {
|
||||
console.log('Song addition response:', response);
|
||||
if (response.error) {
|
||||
console.error('Error adding song:', response.error);
|
||||
setError(response.error);
|
||||
} else if (response.lobby) {
|
||||
// Log detailed lobby state for debugging
|
||||
console.log('Song added successfully, full lobby response:', response.lobby);
|
||||
console.log('Current player songs:', response.lobby.players.find(p => p.id === currentPlayer.id)?.songs);
|
||||
console.log('All players song data:',
|
||||
response.lobby.players.map(p => ({
|
||||
name: p.name,
|
||||
id: p.id,
|
||||
songCount: p.songCount,
|
||||
songs: p.songs ? p.songs.map(s => ({id: s.id, title: s.title})) : 'No songs'
|
||||
}))
|
||||
);
|
||||
|
||||
// Force a deep clone of the lobby to ensure React detects the change
|
||||
const updatedLobby = JSON.parse(JSON.stringify(response.lobby));
|
||||
setLobby(updatedLobby);
|
||||
|
||||
// Verify the state was updated correctly
|
||||
setTimeout(() => {
|
||||
// This won't show the updated state immediately due to React's state update mechanism
|
||||
console.log('Lobby state after update (may not reflect immediate changes):', lobby);
|
||||
console.log('Updated lobby that was set:', updatedLobby);
|
||||
}, 0);
|
||||
} else {
|
||||
console.error('Song addition succeeded but no lobby data was returned');
|
||||
setError('Failed to update song list');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.emit('add_song', { song }, (response) => {
|
||||
console.log('Song addition response:', response);
|
||||
if (response.error) {
|
||||
console.error('Error adding song:', response.error);
|
||||
setError(response.error);
|
||||
reject(response.error);
|
||||
} else if (response.lobby) {
|
||||
// Log detailed lobby state for debugging
|
||||
console.log('Song added successfully, full lobby response:', response.lobby);
|
||||
console.log('Current player songs:', response.lobby.players.find(p => p.id === currentPlayer.id)?.songs);
|
||||
console.log('All players song data:',
|
||||
response.lobby.players.map(p => ({
|
||||
name: p.name,
|
||||
id: p.id,
|
||||
songCount: p.songCount,
|
||||
songs: p.songs ? p.songs.map(s => ({id: s.id, title: s.title})) : 'No songs'
|
||||
}))
|
||||
);
|
||||
|
||||
// Force a deep clone of the lobby to ensure React detects the change
|
||||
const updatedLobby = JSON.parse(JSON.stringify(response.lobby));
|
||||
setLobby(updatedLobby);
|
||||
|
||||
// Verify the state was updated correctly
|
||||
setTimeout(() => {
|
||||
// This won't show the updated state immediately due to React's state update mechanism
|
||||
console.log('Lobby state after update (may not reflect immediate changes):', lobby);
|
||||
console.log('Updated lobby that was set:', updatedLobby);
|
||||
}, 0);
|
||||
|
||||
// Resolve with the updated lobby
|
||||
resolve(updatedLobby);
|
||||
} else {
|
||||
console.error('Song addition succeeded but no lobby data was returned');
|
||||
setError('Failed to update song list');
|
||||
reject('Failed to update song list');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -391,6 +398,26 @@ export function GameProvider({ children }) {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get metadata for a YouTube video by ID
|
||||
const getYouTubeMetadata = (videoId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!socket || !isConnected) {
|
||||
setError('Not connected to server');
|
||||
reject('Not connected to server');
|
||||
return;
|
||||
}
|
||||
|
||||
socket.emit('get_video_metadata', { videoId }, (response) => {
|
||||
if (response.error) {
|
||||
setError(response.error);
|
||||
reject(response.error);
|
||||
} else {
|
||||
resolve(response.metadata);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Remove a song
|
||||
const removeSong = (songId) => {
|
||||
@ -456,7 +483,8 @@ export function GameProvider({ children }) {
|
||||
setPlayerReady,
|
||||
submitVote,
|
||||
leaveLobby,
|
||||
searchYouTube
|
||||
searchYouTube,
|
||||
getYouTubeMetadata
|
||||
}}>
|
||||
{children}
|
||||
</GameContext.Provider>
|
||||
|
Reference in New Issue
Block a user