Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>
This commit is contained in:
parent
50eba457f6
commit
3aee1fc44e
35
src/App.tsx
35
src/App.tsx
@ -5,6 +5,7 @@ import TopPlayed from './features/TopPlayed/Top100';
|
||||
import { FirebaseProvider } from './firebase/FirebaseProvider';
|
||||
import { ErrorBoundary } from './components/common';
|
||||
import { AuthInitializer } from './components/Auth';
|
||||
import { SongInfoProvider } from './components/common/SongInfoProvider';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@ -12,22 +13,24 @@ function App() {
|
||||
<FirebaseProvider>
|
||||
<Router>
|
||||
<AuthInitializer>
|
||||
<Layout>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/queue" replace />} />
|
||||
<Route path="/search" element={<Search />} />
|
||||
<Route path="/queue" element={<Queue />} />
|
||||
<Route path="/favorites" element={<Favorites />} />
|
||||
<Route path="/new-songs" element={<NewSongs />} />
|
||||
<Route path="/artists" element={<Artists />} />
|
||||
<Route path="/song-lists" element={<SongLists />} />
|
||||
<Route path="/history" element={<History />} />
|
||||
<Route path="/top-played" element={<TopPlayed />} />
|
||||
<Route path="/singers" element={<Singers />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="*" element={<Navigate to="/queue" replace />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
<SongInfoProvider>
|
||||
<Layout>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/queue" replace />} />
|
||||
<Route path="/search" element={<Search />} />
|
||||
<Route path="/queue" element={<Queue />} />
|
||||
<Route path="/favorites" element={<Favorites />} />
|
||||
<Route path="/new-songs" element={<NewSongs />} />
|
||||
<Route path="/artists" element={<Artists />} />
|
||||
<Route path="/song-lists" element={<SongLists />} />
|
||||
<Route path="/history" element={<History />} />
|
||||
<Route path="/top-played" element={<TopPlayed />} />
|
||||
<Route path="/singers" element={<Singers />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="*" element={<Navigate to="/queue" replace />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
</SongInfoProvider>
|
||||
</AuthInitializer>
|
||||
</Router>
|
||||
</FirebaseProvider>
|
||||
|
||||
48
src/components/common/SongInfoProvider.tsx
Normal file
48
src/components/common/SongInfoProvider.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import SongInfo from './SongInfo';
|
||||
import { SongInfoContext, type SongInfoContextType } from '../../contexts/SongInfoContext';
|
||||
import type { Song } from '../../types';
|
||||
|
||||
interface SongInfoProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const SongInfoProvider: React.FC<SongInfoProviderProps> = ({ children }) => {
|
||||
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const openSongInfo = useCallback((song: Song) => {
|
||||
setSelectedSong(song);
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
|
||||
const closeSongInfo = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
setSelectedSong(null);
|
||||
}, []);
|
||||
|
||||
const contextValue: SongInfoContextType = {
|
||||
openSongInfo,
|
||||
closeSongInfo,
|
||||
isOpen,
|
||||
selectedSong,
|
||||
};
|
||||
|
||||
return (
|
||||
<SongInfoContext.Provider value={contextValue}>
|
||||
{children}
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isOpen}
|
||||
onClose={closeSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</SongInfoContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -3,6 +3,9 @@ import { IonItem, IonLabel } from '@ionic/react';
|
||||
import ActionButton from './ActionButton';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectQueue, selectFavorites } from '../../redux';
|
||||
import { useSongOperations } from '../../hooks/useSongOperations';
|
||||
import { useToast } from '../../hooks/useToast';
|
||||
import { useSongInfo } from '../../hooks/useSongInfoContext';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
import type { SongItemProps, QueueItem, Song } from '../../types';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
@ -92,11 +95,11 @@ export const SongActionButtons: React.FC<{
|
||||
showRemoveButton?: boolean;
|
||||
showDeleteButton?: boolean;
|
||||
showFavoriteButton?: boolean;
|
||||
onDeleteItem?: () => void;
|
||||
onAddToQueue?: () => void;
|
||||
onRemoveFromQueue?: () => void;
|
||||
onToggleFavorite?: () => void;
|
||||
onDeleteItem?: () => void;
|
||||
onSelectSinger?: () => void;
|
||||
onShowSongInfo?: () => void;
|
||||
}> = ({
|
||||
isAdmin,
|
||||
isInQueue,
|
||||
@ -106,20 +109,20 @@ export const SongActionButtons: React.FC<{
|
||||
showRemoveButton = false,
|
||||
showDeleteButton = false,
|
||||
showFavoriteButton = false,
|
||||
onDeleteItem,
|
||||
onAddToQueue,
|
||||
onRemoveFromQueue,
|
||||
onToggleFavorite,
|
||||
onDeleteItem,
|
||||
onSelectSinger
|
||||
onShowSongInfo
|
||||
}) => {
|
||||
const buttons = [];
|
||||
|
||||
// Info button
|
||||
if (showInfoButton && onSelectSinger) {
|
||||
if (showInfoButton && onShowSongInfo) {
|
||||
buttons.push(
|
||||
<ActionButton
|
||||
key="info"
|
||||
onClick={onSelectSinger}
|
||||
onClick={onShowSongInfo}
|
||||
variant={ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.INFORMATION_CIRCLE}
|
||||
@ -129,11 +132,11 @@ export const SongActionButtons: React.FC<{
|
||||
}
|
||||
|
||||
// Add to Queue button
|
||||
if (showAddButton && !isInQueue) {
|
||||
if (showAddButton && !isInQueue && onAddToQueue) {
|
||||
buttons.push(
|
||||
<ActionButton
|
||||
key="add"
|
||||
onClick={onAddToQueue || (() => {})}
|
||||
onClick={onAddToQueue}
|
||||
variant={ActionButtonVariant.PRIMARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={Icons.ADD}
|
||||
@ -171,11 +174,11 @@ export const SongActionButtons: React.FC<{
|
||||
}
|
||||
|
||||
// Toggle Favorite button
|
||||
if (showFavoriteButton) {
|
||||
if (showFavoriteButton && onToggleFavorite) {
|
||||
buttons.push(
|
||||
<ActionButton
|
||||
key="favorite"
|
||||
onClick={onToggleFavorite || (() => {})}
|
||||
onClick={onToggleFavorite}
|
||||
variant={isInFavorites ? ActionButtonVariant.DANGER : ActionButtonVariant.SECONDARY}
|
||||
size={ActionButtonSize.SMALL}
|
||||
icon={isInFavorites ? Icons.HEART : Icons.HEART_OUTLINE}
|
||||
@ -195,11 +198,7 @@ export const SongActionButtons: React.FC<{
|
||||
const SongItem: React.FC<SongItemProps> = ({
|
||||
song,
|
||||
context,
|
||||
onAddToQueue,
|
||||
onRemoveFromQueue,
|
||||
onToggleFavorite,
|
||||
onDeleteItem,
|
||||
onSelectSinger,
|
||||
isAdmin = false,
|
||||
className = '',
|
||||
showActions = true,
|
||||
@ -215,10 +214,20 @@ const SongItem: React.FC<SongItemProps> = ({
|
||||
const queue = useAppSelector(selectQueue);
|
||||
const favorites = useAppSelector(selectFavorites);
|
||||
|
||||
// Get song operations and hooks
|
||||
const { addToQueue, removeFromQueue, toggleFavorite } = useSongOperations();
|
||||
const { showSuccess, showError } = useToast();
|
||||
const { openSongInfo } = useSongInfo();
|
||||
|
||||
// Check if song is in queue or favorites based on 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);
|
||||
|
||||
// Find queue item key for removal (only needed for queue context)
|
||||
const queueItemKey = context === 'queue'
|
||||
? (Object.entries(queue) as [string, QueueItem][]).find(([, item]) => item.song.path === song.path)?.[0]
|
||||
: null;
|
||||
|
||||
// Debug logging for favorites
|
||||
debugLog('SongItem render:', {
|
||||
songTitle: song.title,
|
||||
@ -239,6 +248,40 @@ const SongItem: React.FC<SongItemProps> = ({
|
||||
const shouldShowDeleteButton = showDeleteButton !== undefined ? showDeleteButton : context === 'history' && isAdmin;
|
||||
const shouldShowFavoriteButton = showFavoriteButton !== undefined ? showFavoriteButton : false; // Disabled for all contexts
|
||||
|
||||
// Handle song operations internally
|
||||
const handleAddToQueue = async () => {
|
||||
try {
|
||||
await addToQueue(song);
|
||||
showSuccess('Song added to queue');
|
||||
} catch {
|
||||
showError('Failed to add song to queue');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFromQueue = async () => {
|
||||
if (!queueItemKey) return;
|
||||
|
||||
try {
|
||||
await removeFromQueue(queueItemKey);
|
||||
showSuccess('Song removed from queue');
|
||||
} catch {
|
||||
showError('Failed to remove song from queue');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleFavorite = async () => {
|
||||
try {
|
||||
await toggleFavorite(song);
|
||||
showSuccess(isInFavorites ? 'Removed from favorites' : 'Added to favorites');
|
||||
} catch {
|
||||
showError('Failed to update favorites');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectSinger = () => {
|
||||
openSongInfo(song);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonItem className={className}>
|
||||
<SongInfoDisplay
|
||||
@ -258,11 +301,11 @@ const SongItem: React.FC<SongItemProps> = ({
|
||||
showRemoveButton={shouldShowRemoveButton}
|
||||
showDeleteButton={shouldShowDeleteButton}
|
||||
showFavoriteButton={shouldShowFavoriteButton}
|
||||
onAddToQueue={onAddToQueue}
|
||||
onRemoveFromQueue={onRemoveFromQueue}
|
||||
onToggleFavorite={onToggleFavorite}
|
||||
onDeleteItem={onDeleteItem}
|
||||
onSelectSinger={onSelectSinger}
|
||||
onAddToQueue={context === 'queue' ? handleRemoveFromQueue : handleAddToQueue}
|
||||
onRemoveFromQueue={context === 'queue' ? handleRemoveFromQueue : onDeleteItem}
|
||||
onToggleFavorite={context === 'favorites' ? onDeleteItem : handleToggleFavorite}
|
||||
onShowSongInfo={handleSelectSinger}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
11
src/contexts/SongInfoContext.tsx
Normal file
11
src/contexts/SongInfoContext.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { createContext } from 'react';
|
||||
import type { Song } from '../types';
|
||||
|
||||
export interface SongInfoContextType {
|
||||
openSongInfo: (song: Song) => void;
|
||||
closeSongInfo: () => void;
|
||||
isOpen: boolean;
|
||||
selectedSong: Song | null;
|
||||
}
|
||||
|
||||
export const SongInfoContext = createContext<SongInfoContextType | null>(null);
|
||||
@ -1,14 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonSearchbar, IonModal, IonHeader, IonToolbar, IonTitle, IonContent, IonItem } from '@ionic/react';
|
||||
import { list } from 'ionicons/icons';
|
||||
import { InfiniteScrollList, SongItem, ListItem, NumberDisplay, SongInfo, ActionButton } from '../../components/common';
|
||||
import { InfiniteScrollList, SongItem, ListItem, NumberDisplay, ActionButton } from '../../components/common';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import { useArtists } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectSongs } from '../../redux';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
import type { Song } from '../../types';
|
||||
|
||||
|
||||
const Artists: React.FC = () => {
|
||||
const {
|
||||
@ -19,15 +19,11 @@ const Artists: React.FC = () => {
|
||||
handleSearchChange,
|
||||
getSongsByArtist,
|
||||
getSongCountByArtist,
|
||||
handleAddToQueue,
|
||||
} = useArtists();
|
||||
|
||||
const songs = useAppSelector(selectSongs);
|
||||
const songsCount = Object.keys(songs).length;
|
||||
const [selectedArtist, setSelectedArtist] = useState<string | null>(null);
|
||||
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
||||
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
|
||||
|
||||
// Debug logging
|
||||
debugLog('Artists component - artists count:', artists.length);
|
||||
debugLog('Artists component - selected artist:', selectedArtist);
|
||||
@ -42,16 +38,6 @@ const Artists: React.FC = () => {
|
||||
setSelectedArtist(null);
|
||||
};
|
||||
|
||||
const handleSongInfo = (song: Song) => {
|
||||
setSelectedSong(song);
|
||||
setIsSongInfoOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseSongInfo = () => {
|
||||
setIsSongInfoOpen(false);
|
||||
setSelectedSong(null);
|
||||
};
|
||||
|
||||
const selectedArtistSongs = selectedArtist ? getSongsByArtist(selectedArtist) : [];
|
||||
|
||||
// Render artist item for InfiniteScrollList
|
||||
@ -156,8 +142,6 @@ const Artists: React.FC = () => {
|
||||
<SongItem
|
||||
song={song}
|
||||
context="search"
|
||||
onAddToQueue={() => handleAddToQueue(song)}
|
||||
onSelectSinger={() => handleSongInfo(song)}
|
||||
showAddButton={true}
|
||||
showInfoButton={true}
|
||||
showFavoriteButton={false}
|
||||
@ -173,14 +157,6 @@ const Artists: React.FC = () => {
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isSongInfoOpen}
|
||||
onClose={handleCloseSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
|
||||
import React from 'react';
|
||||
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||
import { useFavorites } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectFavorites } from '../../redux';
|
||||
@ -14,9 +14,6 @@ const Favorites: React.FC = () => {
|
||||
handleToggleFavorite,
|
||||
} = useFavorites();
|
||||
|
||||
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
||||
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
|
||||
|
||||
const favorites = useAppSelector(selectFavorites);
|
||||
const favoritesCount = Object.keys(favorites).length;
|
||||
|
||||
@ -24,16 +21,6 @@ const Favorites: React.FC = () => {
|
||||
debugLog('Favorites component - favorites count:', favoritesCount);
|
||||
debugLog('Favorites component - favorites items:', favoritesItems);
|
||||
|
||||
const handleSongInfo = (song: Song) => {
|
||||
setSelectedSong(song);
|
||||
setIsSongInfoOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseSongInfo = () => {
|
||||
setIsSongInfoOpen(false);
|
||||
setSelectedSong(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<InfiniteScrollList<Song>
|
||||
@ -48,7 +35,6 @@ const Favorites: React.FC = () => {
|
||||
showInfoButton={true}
|
||||
showAddButton={false}
|
||||
showDeleteButton={true}
|
||||
onSelectSinger={() => handleSongInfo(song)}
|
||||
onDeleteItem={() => handleToggleFavorite(song)}
|
||||
/>
|
||||
)}
|
||||
@ -58,14 +44,6 @@ const Favorites: React.FC = () => {
|
||||
loadingMessage="Please wait while favorites data is being loaded"
|
||||
/>
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isSongInfoOpen}
|
||||
onClose={handleCloseSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { IonChip, IonIcon } from '@ionic/react';
|
||||
import { time } from 'ionicons/icons';
|
||||
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
|
||||
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||
import { useHistory } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectHistory, selectIsAdmin } from '../../redux';
|
||||
@ -14,30 +14,16 @@ const History: React.FC = () => {
|
||||
historyItems,
|
||||
hasMore,
|
||||
loadMore,
|
||||
handleAddToQueue,
|
||||
handleDeleteFromHistory,
|
||||
} = useHistory();
|
||||
|
||||
const history = useAppSelector(selectHistory);
|
||||
const isAdmin = useAppSelector(selectIsAdmin);
|
||||
const historyCount = Object.keys(history).length;
|
||||
const [selectedSong, setSelectedSong] = React.useState<Song | null>(null);
|
||||
const [isSongInfoOpen, setIsSongInfoOpen] = React.useState(false);
|
||||
|
||||
// Debug logging
|
||||
debugLog('History component - history count:', historyCount);
|
||||
debugLog('History component - history items:', historyItems);
|
||||
|
||||
const handleSongInfo = (song: Song) => {
|
||||
setSelectedSong(song);
|
||||
setIsSongInfoOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseSongInfo = () => {
|
||||
setIsSongInfoOpen(false);
|
||||
setSelectedSong(null);
|
||||
};
|
||||
|
||||
// Render extra content for history items (play date)
|
||||
const renderExtraContent = (item: Song) => {
|
||||
if (item.date) {
|
||||
@ -64,8 +50,6 @@ const History: React.FC = () => {
|
||||
<SongItem
|
||||
song={song}
|
||||
context="history"
|
||||
onAddToQueue={() => handleAddToQueue(song)}
|
||||
onSelectSinger={() => handleSongInfo(song)}
|
||||
onDeleteItem={() => handleDeleteFromHistory(song)}
|
||||
isAdmin={isAdmin}
|
||||
showAddButton={true}
|
||||
@ -83,14 +67,6 @@ const History: React.FC = () => {
|
||||
loadingMessage="Please wait while history data is being loaded"
|
||||
/>
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isSongInfoOpen}
|
||||
onClose={handleCloseSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
|
||||
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||
import { useNewSongs } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectNewSongsArray } from '../../redux';
|
||||
@ -11,28 +11,15 @@ const NewSongs: React.FC = () => {
|
||||
newSongsItems,
|
||||
hasMore,
|
||||
loadMore,
|
||||
handleAddToQueue,
|
||||
} = useNewSongs();
|
||||
|
||||
const newSongsArray = useAppSelector(selectNewSongsArray);
|
||||
const newSongsCount = newSongsArray.length;
|
||||
const [selectedSong, setSelectedSong] = React.useState<Song | null>(null);
|
||||
const [isSongInfoOpen, setIsSongInfoOpen] = React.useState(false);
|
||||
|
||||
// Debug logging
|
||||
debugLog('NewSongs component - new songs count:', newSongsCount);
|
||||
debugLog('NewSongs component - new songs items:', newSongsItems);
|
||||
|
||||
const handleSongInfo = (song: Song) => {
|
||||
setSelectedSong(song);
|
||||
setIsSongInfoOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseSongInfo = () => {
|
||||
setIsSongInfoOpen(false);
|
||||
setSelectedSong(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<InfiniteScrollList<Song>
|
||||
@ -44,8 +31,6 @@ const NewSongs: React.FC = () => {
|
||||
<SongItem
|
||||
song={song}
|
||||
context="search"
|
||||
onAddToQueue={() => handleAddToQueue(song)}
|
||||
onSelectSinger={() => handleSongInfo(song)}
|
||||
showAddButton={true}
|
||||
showInfoButton={true}
|
||||
showFavoriteButton={false}
|
||||
@ -57,14 +42,6 @@ const NewSongs: React.FC = () => {
|
||||
loadingMessage="Please wait while new songs data is being loaded"
|
||||
/>
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isSongInfoOpen}
|
||||
onClose={handleCloseSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { IonSearchbar } from '@ionic/react';
|
||||
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
|
||||
import { useSearch, useSongInfo } from '../../hooks';
|
||||
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||
import { useSearch } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectIsAdmin, selectSongs } from '../../redux';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
@ -12,12 +12,9 @@ const Search: React.FC = () => {
|
||||
searchTerm,
|
||||
searchResults,
|
||||
handleSearchChange,
|
||||
handleAddToQueue,
|
||||
loadMore,
|
||||
} = useSearch();
|
||||
|
||||
const { isOpen, selectedSong, openSongInfo, closeSongInfo } = useSongInfo();
|
||||
|
||||
const isAdmin = useAppSelector(selectIsAdmin);
|
||||
const songs = useAppSelector(selectSongs);
|
||||
const songsCount = Object.keys(songs).length;
|
||||
@ -63,8 +60,6 @@ const Search: React.FC = () => {
|
||||
<SongItem
|
||||
song={song}
|
||||
context="search"
|
||||
onAddToQueue={() => handleAddToQueue(song)}
|
||||
onSelectSinger={() => openSongInfo(song)}
|
||||
isAdmin={isAdmin}
|
||||
showAddButton={true}
|
||||
showInfoButton={true}
|
||||
@ -86,14 +81,6 @@ const Search: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isOpen}
|
||||
onClose={closeSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { IonItem, IonModal, IonHeader, IonToolbar, IonTitle, IonChip, IonContent, IonList, IonAccordionGroup, IonAccordion } from '@ionic/react';
|
||||
import { list } from 'ionicons/icons';
|
||||
import { InfiniteScrollList, SongItem, ListItem, TwoLineDisplay, NumberDisplay, SongInfo, ActionButton } from '../../components/common';
|
||||
import { InfiniteScrollList, SongItem, ListItem, TwoLineDisplay, NumberDisplay, ActionButton } from '../../components/common';
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import { useSongLists } from '../../hooks';
|
||||
@ -17,15 +17,13 @@ const SongLists: React.FC = () => {
|
||||
hasMore,
|
||||
loadMore,
|
||||
checkSongAvailability,
|
||||
handleAddToQueue,
|
||||
} = useSongLists();
|
||||
|
||||
const songListData = useAppSelector(selectSongList);
|
||||
const songListCount = Object.keys(songListData).length;
|
||||
const [selectedSongList, setSelectedSongList] = useState<string | null>(null);
|
||||
const [expandedSongKey, setExpandedSongKey] = useState<string | null>(null);
|
||||
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
||||
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
|
||||
|
||||
|
||||
|
||||
|
||||
@ -43,15 +41,7 @@ const SongLists: React.FC = () => {
|
||||
setExpandedSongKey(expandedSongKey === songKey ? null : songKey);
|
||||
}, [expandedSongKey]);
|
||||
|
||||
const handleSongInfo = useCallback((song: Song) => {
|
||||
setSelectedSong(song);
|
||||
setIsSongInfoOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleCloseSongInfo = useCallback(() => {
|
||||
setIsSongInfoOpen(false);
|
||||
setSelectedSong(null);
|
||||
}, []);
|
||||
|
||||
const finalSelectedList = selectedSongList
|
||||
? allSongLists.find(list => list.key === selectedSongList)
|
||||
@ -152,8 +142,6 @@ const SongLists: React.FC = () => {
|
||||
key={song.key || `${song.title}-${song.artist}-${sidx}`}
|
||||
song={song}
|
||||
context="search"
|
||||
onAddToQueue={() => handleAddToQueue(song)}
|
||||
onSelectSinger={() => handleSongInfo(song)}
|
||||
showAddButton={true}
|
||||
showInfoButton={true}
|
||||
showFavoriteButton={false}
|
||||
@ -195,14 +183,6 @@ const SongLists: React.FC = () => {
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isSongInfoOpen}
|
||||
onClose={handleCloseSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -4,14 +4,13 @@ import { list } from 'ionicons/icons';
|
||||
import { useTopPlayed } from '../../hooks';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectTopPlayed, selectSongsArray } from '../../redux';
|
||||
import { InfiniteScrollList, SongItem, ListItem, SongInfo, ActionButton } from '../../components/common';
|
||||
import { InfiniteScrollList, SongItem, ListItem, ActionButton } from '../../components/common';
|
||||
import { filterSongs } from '../../utils/dataProcessing';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
import { useSongOperations } from '../../hooks';
|
||||
import { useToast } from '../../hooks';
|
||||
|
||||
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
|
||||
import { Icons } from '../../constants';
|
||||
import type { TopPlayed, Song } from '../../types';
|
||||
import type { TopPlayed } from '../../types';
|
||||
|
||||
const Top100: React.FC = () => {
|
||||
debugLog('Top100 component - RENDERING START');
|
||||
@ -26,11 +25,8 @@ const Top100: React.FC = () => {
|
||||
const topPlayed = useAppSelector(selectTopPlayed);
|
||||
const topPlayedCount = Object.keys(topPlayed).length;
|
||||
const allSongs = useAppSelector(selectSongsArray);
|
||||
const { addToQueue } = useSongOperations();
|
||||
const { showSuccess, showError } = useToast();
|
||||
const [selectedTopPlayed, setSelectedTopPlayed] = useState<TopPlayed | null>(null);
|
||||
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
||||
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
|
||||
|
||||
|
||||
debugLog('Top100 component - Redux data:', { topPlayedCount, topPlayedItems: topPlayedItems.length });
|
||||
|
||||
@ -42,15 +38,7 @@ const Top100: React.FC = () => {
|
||||
setSelectedTopPlayed(null);
|
||||
}, []);
|
||||
|
||||
const handleSongInfo = useCallback((song: Song) => {
|
||||
setSelectedSong(song);
|
||||
setIsSongInfoOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleCloseSongInfo = useCallback(() => {
|
||||
setIsSongInfoOpen(false);
|
||||
setSelectedSong(null);
|
||||
}, []);
|
||||
|
||||
// Find songs that match the selected top played item
|
||||
const selectedSongs = useMemo(() => {
|
||||
@ -75,15 +63,6 @@ const Top100: React.FC = () => {
|
||||
return filteredSongs;
|
||||
}, [selectedTopPlayed, allSongs]);
|
||||
|
||||
const handleAddToQueue = useCallback(async (song: Song) => {
|
||||
try {
|
||||
await addToQueue(song);
|
||||
showSuccess('Song added to queue');
|
||||
} catch {
|
||||
showError('Failed to add song to queue');
|
||||
}
|
||||
}, [addToQueue, showSuccess, showError]);
|
||||
|
||||
|
||||
|
||||
// Use real Firebase data from the hook
|
||||
@ -161,8 +140,6 @@ const Top100: React.FC = () => {
|
||||
key={song.key || `${song.title}-${song.artist}`}
|
||||
song={song}
|
||||
context="search"
|
||||
onAddToQueue={() => handleAddToQueue(song)}
|
||||
onSelectSinger={() => handleSongInfo(song)}
|
||||
showAddButton={true}
|
||||
showInfoButton={true}
|
||||
showFavoriteButton={false}
|
||||
@ -172,14 +149,6 @@ const Top100: React.FC = () => {
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
|
||||
{/* Song Info Modal */}
|
||||
{selectedSong && (
|
||||
<SongInfo
|
||||
isOpen={isSongInfoOpen}
|
||||
onClose={handleCloseSongInfo}
|
||||
song={selectedSong}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
10
src/hooks/useSongInfoContext.ts
Normal file
10
src/hooks/useSongInfoContext.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useContext } from 'react';
|
||||
import { SongInfoContext, type SongInfoContextType } from '../contexts/SongInfoContext';
|
||||
|
||||
export const useSongInfo = (): SongInfoContextType => {
|
||||
const context = useContext(SongInfoContext);
|
||||
if (context === null) {
|
||||
throw new Error('useSongInfo must be used within a SongInfoProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@ -165,11 +165,7 @@ export interface ActionButtonProps {
|
||||
export interface SongItemProps {
|
||||
song: Song;
|
||||
context: 'search' | 'queue' | 'favorites' | 'history' | 'songlists' | 'top100' | 'new';
|
||||
onAddToQueue?: () => void;
|
||||
onRemoveFromQueue?: () => void;
|
||||
onToggleFavorite?: () => void;
|
||||
onDeleteItem?: () => void;
|
||||
onSelectSinger?: () => void;
|
||||
isAdmin?: boolean;
|
||||
className?: string;
|
||||
showActions?: boolean;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user