Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-07-19 13:35:30 -05:00
parent 1238ab3b3e
commit f5fbaae66f
5 changed files with 95 additions and 26 deletions

View File

@ -7,14 +7,14 @@ import {
add, heart, heartOutline, ban, checkmark, close, people add, heart, heartOutline, ban, checkmark, close, people
} from 'ionicons/icons'; } from 'ionicons/icons';
import { useAppSelector } from '../../redux'; import { useAppSelector } from '../../redux';
import { selectIsAdmin, selectFavorites, selectSongs } from '../../redux'; import { selectIsAdmin, selectFavorites, selectSongs, selectQueue } from '../../redux';
import { useSongOperations } from '../../hooks/useSongOperations'; import { useSongOperations } from '../../hooks/useSongOperations';
import { useDisabledSongs } from '../../hooks/useDisabledSongs'; import { useDisabledSongs } from '../../hooks/useDisabledSongs';
import { useSelectSinger } from '../../hooks/useSelectSinger'; import { useSelectSinger } from '../../hooks/useSelectSinger';
import { useToast } from '../../hooks/useToast'; import { useToast } from '../../hooks/useToast';
import SelectSinger from './SelectSinger'; import SelectSinger from './SelectSinger';
import { SongInfoDisplay } from './SongItem'; import { SongInfoDisplay } from './SongItem';
import type { Song } from '../../types'; import type { Song, QueueItem } from '../../types';
interface SongInfoProps { interface SongInfoProps {
isOpen: boolean; isOpen: boolean;
@ -26,6 +26,7 @@ const SongInfo: React.FC<SongInfoProps> = ({ isOpen, onClose, song }) => {
const isAdmin = useAppSelector(selectIsAdmin); const isAdmin = useAppSelector(selectIsAdmin);
const favorites = useAppSelector(selectFavorites); const favorites = useAppSelector(selectFavorites);
const allSongs = useAppSelector(selectSongs); const allSongs = useAppSelector(selectSongs);
const queue = useAppSelector(selectQueue);
const { toggleFavorite } = useSongOperations(); const { toggleFavorite } = useSongOperations();
const { isSongDisabled, addDisabledSong, removeDisabledSong } = useDisabledSongs(); const { isSongDisabled, addDisabledSong, removeDisabledSong } = useDisabledSongs();
const { showSuccess, showError } = useToast(); const { showSuccess, showError } = useToast();
@ -40,6 +41,7 @@ const SongInfo: React.FC<SongInfoProps> = ({ isOpen, onClose, song }) => {
const isInFavorites = (Object.values(favorites) as Song[]).some(favSong => favSong.path === song.path); const isInFavorites = (Object.values(favorites) as Song[]).some(favSong => favSong.path === song.path);
const isDisabled = isSongDisabled(song); const isDisabled = isSongDisabled(song);
const isInQueue = (Object.values(queue) as QueueItem[]).some(queueItem => queueItem.song && queueItem.song.path === song.path);
const artistSongs = (Object.values(allSongs) as Song[]).filter(s => const artistSongs = (Object.values(allSongs) as Song[]).filter(s =>
s.artist.toLowerCase() === song.artist.toLowerCase() && s.path !== song.path s.artist.toLowerCase() === song.artist.toLowerCase() && s.path !== song.path
@ -110,16 +112,18 @@ const SongInfo: React.FC<SongInfoProps> = ({ isOpen, onClose, song }) => {
{/* Action Buttons */} {/* Action Buttons */}
<div className="flex flex-col items-center space-y-4"> <div className="flex flex-col items-center space-y-4">
{/* Queue Song Button */} {/* Queue Song Button */}
<IonButton {!isInQueue && (
fill="solid" <IonButton
color="primary" fill="solid"
onClick={handleQueueSong} color="primary"
className="h-12 w-80" onClick={handleQueueSong}
style={{ width: '320px' }} className="h-12 w-80"
> style={{ width: '320px' }}
<IonIcon icon={people} slot="start" /> >
Queue Song <IonIcon icon={people} slot="start" />
</IonButton> Queue Song
</IonButton>
)}
{/* Artist Songs Button */} {/* Artist Songs Button */}
<IonButton <IonButton

View File

@ -217,6 +217,15 @@ const SongItem: React.FC<SongItemProps> = ({
const isInQueue = (Object.values(queue) as QueueItem[]).some(item => item.song.path === song.path); const isInQueue = (Object.values(queue) as QueueItem[]).some(item => item.song.path === song.path);
const isInFavorites = (Object.values(favorites) as Song[]).some(favSong => favSong.path === song.path); const isInFavorites = (Object.values(favorites) as Song[]).some(favSong => favSong.path === song.path);
// Debug logging for favorites
console.log('SongItem render:', {
songTitle: song.title,
songPath: song.path,
favoritesCount: Object.keys(favorites).length,
isInFavorites,
favorites: (Object.values(favorites) as Song[]).map(f => f.path)
});
// Default values based on context if not explicitly provided // Default values based on context if not explicitly provided
const shouldShowPath = showPath !== undefined ? showPath : context !== 'queue'; const shouldShowPath = showPath !== undefined ? showPath : context !== 'queue';
const shouldShowCount = showCount !== undefined ? showCount : context === 'queue'; const shouldShowCount = showCount !== undefined ? showCount : context === 'queue';

View File

@ -1,5 +1,5 @@
import React from 'react'; import React, { useState } from 'react';
import { InfiniteScrollList, SongItem } from '../../components/common'; import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
import { useFavorites } from '../../hooks'; import { useFavorites } from '../../hooks';
import { useAppSelector } from '../../redux'; import { useAppSelector } from '../../redux';
import { selectFavorites } from '../../redux'; import { selectFavorites } from '../../redux';
@ -11,10 +11,12 @@ const Favorites: React.FC = () => {
favoritesItems, favoritesItems,
hasMore, hasMore,
loadMore, loadMore,
handleAddToQueue,
handleToggleFavorite, handleToggleFavorite,
} = useFavorites(); } = useFavorites();
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
const favorites = useAppSelector(selectFavorites); const favorites = useAppSelector(selectFavorites);
const favoritesCount = Object.keys(favorites).length; const favoritesCount = Object.keys(favorites).length;
@ -22,6 +24,16 @@ const Favorites: React.FC = () => {
debugLog('Favorites component - favorites count:', favoritesCount); debugLog('Favorites component - favorites count:', favoritesCount);
debugLog('Favorites component - favorites items:', favoritesItems); debugLog('Favorites component - favorites items:', favoritesItems);
const handleSongInfo = (song: Song) => {
setSelectedSong(song);
setIsSongInfoOpen(true);
};
const handleCloseSongInfo = () => {
setIsSongInfoOpen(false);
setSelectedSong(null);
};
return ( return (
<> <>
<InfiniteScrollList<Song> <InfiniteScrollList<Song>
@ -33,8 +45,10 @@ const Favorites: React.FC = () => {
<SongItem <SongItem
song={song} song={song}
context="favorites" context="favorites"
onAddToQueue={() => handleAddToQueue(song)} showInfoButton={true}
onToggleFavorite={() => handleToggleFavorite(song)} showAddButton={false}
onSelectSinger={() => handleSongInfo(song)}
onDelete={() => handleToggleFavorite(song)}
/> />
)} )}
emptyTitle="No favorites yet" emptyTitle="No favorites yet"
@ -42,6 +56,15 @@ const Favorites: React.FC = () => {
loadingTitle="Loading favorites..." loadingTitle="Loading favorites..."
loadingMessage="Please wait while favorites data is being loaded" loadingMessage="Please wait while favorites data is being loaded"
/> />
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isSongInfoOpen}
onClose={handleCloseSongInfo}
song={selectedSong}
/>
)}
</> </>
); );
}; };

View File

@ -2,7 +2,6 @@ import { useState, useEffect, useCallback } from 'react';
import { disabledSongsService } from '../firebase/services'; import { disabledSongsService } from '../firebase/services';
import { useAppSelector } from '../redux'; import { useAppSelector } from '../redux';
import { selectControllerName } from '../redux'; import { selectControllerName } from '../redux';
import { debugLog } from '../utils/logger';
import { useToast } from './useToast'; import { useToast } from './useToast';
import type { Song, DisabledSong } from '../types'; import type { Song, DisabledSong } from '../types';
@ -39,8 +38,21 @@ export const useDisabledSongs = () => {
const unsubscribe = disabledSongsService.subscribeToDisabledSongs( const unsubscribe = disabledSongsService.subscribeToDisabledSongs(
controllerName, controllerName,
(songs) => { (songs) => {
setDisabledSongs(songs); // Only update if the data has actually changed
setDisabledSongPaths(new Set(Object.keys(songs).map(key => decodeURIComponent(key)))); setDisabledSongs(prevSongs => {
if (JSON.stringify(prevSongs) !== JSON.stringify(songs)) {
return songs;
}
return prevSongs;
});
setDisabledSongPaths(prevPaths => {
const newPaths = new Set(Object.values(songs).map((song: DisabledSong) => song.path));
if (JSON.stringify(Array.from(prevPaths)) !== JSON.stringify(Array.from(newPaths))) {
return newPaths;
}
return prevPaths;
});
} }
); );
@ -49,7 +61,8 @@ export const useDisabledSongs = () => {
// Check if a song is disabled // Check if a song is disabled
const isSongDisabled = useCallback((song: Song): boolean => { const isSongDisabled = useCallback((song: Song): boolean => {
return disabledSongPaths.has(song.path); const isDisabled = disabledSongPaths.has(song.path);
return isDisabled;
}, [disabledSongPaths]); }, [disabledSongPaths]);
// Add a song to disabled list // Add a song to disabled list
@ -67,7 +80,6 @@ export const useDisabledSongs = () => {
} }
try { try {
debugLog('Adding disabled song:', { controllerName, song });
await disabledSongsService.addDisabledSong(controllerName, song); await disabledSongsService.addDisabledSong(controllerName, song);
showSuccess('Song marked as disabled'); showSuccess('Song marked as disabled');
} catch (error) { } catch (error) {

View File

@ -3,6 +3,8 @@ import { useAppSelector } from '../redux';
import { selectControllerName, selectCurrentSinger, selectQueueObject } from '../redux'; import { selectControllerName, selectCurrentSinger, selectQueueObject } from '../redux';
import { queueService, favoritesService } from '../firebase/services'; import { queueService, favoritesService } from '../firebase/services';
import type { Song, QueueItem } from '../types'; import type { Song, QueueItem } from '../types';
import { ref, get } from 'firebase/database';
import { database } from '../firebase/config';
export const useSongOperations = () => { export const useSongOperations = () => {
const controllerName = useAppSelector(selectControllerName); const controllerName = useAppSelector(selectControllerName);
@ -57,15 +59,34 @@ export const useSongOperations = () => {
} }
try { try {
if (song.favorite) { console.log('toggleFavorite called for song:', song.title, song.path);
// Check if the song is currently in favorites by looking it up
const favoritesRef = ref(database, `controllers/${controllerName}/favorites`);
const snapshot = await get(favoritesRef);
const favorites = snapshot.exists() ? snapshot.val() : {};
console.log('Current favorites:', favorites);
// Find if this song is already in favorites by matching the path
const existingFavoriteKey = Object.keys(favorites).find(key => {
const favoriteSong = favorites[key];
return favoriteSong && favoriteSong.path === song.path;
});
console.log('Existing favorite key:', existingFavoriteKey);
if (existingFavoriteKey) {
// Remove from favorites // Remove from favorites
if (song.key) { console.log('Removing from favorites');
await favoritesService.removeFromFavorites(controllerName, song.key); await favoritesService.removeFromFavorites(controllerName, existingFavoriteKey);
}
} else { } else {
// Add to favorites // Add to favorites
console.log('Adding to favorites');
await favoritesService.addToFavorites(controllerName, song); await favoritesService.addToFavorites(controllerName, song);
} }
console.log('toggleFavorite completed');
} catch (error) { } catch (error) {
console.error('Failed to toggle favorite:', error); console.error('Failed to toggle favorite:', error);
throw error; throw error;