Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>
This commit is contained in:
parent
98e3633d31
commit
683050f271
40
src/components/common/SongCountDisplay.tsx
Normal file
40
src/components/common/SongCountDisplay.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { IonChip } from '@ionic/react';
|
||||
import { getSongCountByArtistTitle } from '../../utils/dataProcessing';
|
||||
import type { Song } from '../../types';
|
||||
|
||||
interface SongCountDisplayProps {
|
||||
songs: Song[];
|
||||
artist: string;
|
||||
title: string;
|
||||
showLabel?: boolean;
|
||||
color?: 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'danger' | 'medium' | 'light' | 'dark';
|
||||
}
|
||||
|
||||
export const SongCountDisplay: React.FC<SongCountDisplayProps> = ({
|
||||
songs,
|
||||
artist,
|
||||
title,
|
||||
showLabel = true,
|
||||
color = 'primary'
|
||||
}) => {
|
||||
const count = getSongCountByArtistTitle(songs, artist, title);
|
||||
|
||||
if (count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = showLabel
|
||||
? `${count} version${count !== 1 ? 's' : ''}`
|
||||
: count.toString();
|
||||
|
||||
return (
|
||||
<IonChip
|
||||
color={color}
|
||||
>
|
||||
{label}
|
||||
</IonChip>
|
||||
);
|
||||
};
|
||||
|
||||
export default SongCountDisplay;
|
||||
@ -11,4 +11,5 @@ export { TwoLineDisplay } from './TwoLineDisplay';
|
||||
export { default as ListItem } from './ListItem';
|
||||
export { NumberDisplay } from './NumberDisplay';
|
||||
export { ModalHeader } from './ModalHeader';
|
||||
export { default as VirtualizedList } from './VirtualizedList';
|
||||
export { default as VirtualizedList } from './VirtualizedList';
|
||||
export { default as SongCountDisplay } from './SongCountDisplay';
|
||||
@ -33,7 +33,7 @@ export const UI_CONSTANTS = {
|
||||
MAX_ITEMS: 100,
|
||||
},
|
||||
HISTORY: {
|
||||
MAX_ITEMS: 50,
|
||||
MAX_ITEMS: 250, // Increased from 50 to show more history items
|
||||
},
|
||||
TOP_PLAYED: {
|
||||
MAX_ITEMS: 20,
|
||||
|
||||
@ -16,6 +16,7 @@ const SongLists: React.FC = () => {
|
||||
hasMore,
|
||||
loadMore,
|
||||
checkSongAvailability,
|
||||
getSongCountForSongListSong,
|
||||
} = useSongLists();
|
||||
|
||||
const songListData = useAppSelector(selectSongList);
|
||||
@ -94,7 +95,8 @@ const SongLists: React.FC = () => {
|
||||
<IonAccordionGroup value={expandedSongKey}>
|
||||
{selectedListWithAvailability?.songs.map((songListSong: SongListSong & { availableSongs: Song[] }, index) => {
|
||||
const availableSongs = songListSong.availableSongs;
|
||||
const isAvailable = availableSongs.length > 0;
|
||||
const songCount = getSongCountForSongListSong(songListSong);
|
||||
const isAvailable = songCount > 0;
|
||||
const songKey = songListSong.key || `${songListSong.title}-${songListSong.position}-${index}`;
|
||||
|
||||
if (isAvailable) {
|
||||
@ -114,7 +116,7 @@ const SongLists: React.FC = () => {
|
||||
onClick={() => handleSongItemClick(songKey)}
|
||||
endContent={
|
||||
<IonChip color="primary">
|
||||
{availableSongs.length} version{availableSongs.length !== 1 ? 's' : ''}
|
||||
{songCount} version{songCount !== 1 ? 's' : ''}
|
||||
</IonChip>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -1,33 +1,66 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useAppSelector, selectSongListArray, selectSongsArray } from '../redux';
|
||||
import { useActions } from './useActions';
|
||||
import { usePaginatedData } from './index';
|
||||
import type { SongListSong } from '../types';
|
||||
import type { SongListSong, Song } from '../types';
|
||||
|
||||
export const useSongLists = () => {
|
||||
const allSongLists = useAppSelector(selectSongListArray);
|
||||
const allSongs = useAppSelector(selectSongsArray);
|
||||
const { handleAddToQueue, handleToggleFavorite } = useActions();
|
||||
|
||||
// Pre-compute songs by artist and title combination for performance
|
||||
const songsByArtistTitle = useMemo(() => {
|
||||
const songsMap = new Map<string, Song[]>();
|
||||
const countsMap = new Map<string, number>();
|
||||
|
||||
allSongs.forEach(song => {
|
||||
const artist = (song.artist || '').toLowerCase();
|
||||
const title = (song.title || '').toLowerCase();
|
||||
const key = `${artist}|${title}`;
|
||||
|
||||
if (!songsMap.has(key)) {
|
||||
songsMap.set(key, []);
|
||||
countsMap.set(key, 0);
|
||||
}
|
||||
songsMap.get(key)!.push(song);
|
||||
countsMap.set(key, countsMap.get(key)! + 1);
|
||||
});
|
||||
|
||||
return { songsMap, countsMap };
|
||||
}, [allSongs]);
|
||||
|
||||
// Use the composable pagination hook
|
||||
const pagination = usePaginatedData(allSongLists, {
|
||||
itemsPerPage: 20 // Default pagination size
|
||||
});
|
||||
|
||||
// Check if a song exists in the catalog
|
||||
// Get songs by artist and title (now using cached data)
|
||||
const getSongsByArtistTitle = useCallback((artist: string, title: string) => {
|
||||
const key = `${(artist || '').toLowerCase()}|${(title || '').toLowerCase()}`;
|
||||
return songsByArtistTitle.songsMap.get(key) || [];
|
||||
}, [songsByArtistTitle.songsMap]);
|
||||
|
||||
// Get song count by artist and title (now using cached data)
|
||||
const getSongCountByArtistTitle = useCallback((artist: string, title: string) => {
|
||||
const key = `${(artist || '').toLowerCase()}|${(title || '').toLowerCase()}`;
|
||||
return songsByArtistTitle.countsMap.get(key) || 0;
|
||||
}, [songsByArtistTitle.countsMap]);
|
||||
|
||||
// Check if a song exists in the catalog (enhanced version)
|
||||
const checkSongAvailability = useCallback((songListSong: SongListSong) => {
|
||||
if (songListSong.foundSongs && songListSong.foundSongs.length > 0) {
|
||||
return songListSong.foundSongs;
|
||||
}
|
||||
|
||||
// Search for songs by artist and title
|
||||
const matchingSongs = allSongs.filter(song =>
|
||||
(song.artist || '').toLowerCase() === (songListSong.artist || '').toLowerCase() &&
|
||||
(song.title || '').toLowerCase() === (songListSong.title || '').toLowerCase()
|
||||
);
|
||||
|
||||
return matchingSongs;
|
||||
}, [allSongs]);
|
||||
// Use the pre-computed data for better performance
|
||||
return getSongsByArtistTitle(songListSong.artist, songListSong.title);
|
||||
}, [getSongsByArtistTitle]);
|
||||
|
||||
// Get song count for a song list song
|
||||
const getSongCountForSongListSong = useCallback((songListSong: SongListSong) => {
|
||||
return getSongCountByArtistTitle(songListSong.artist, songListSong.title);
|
||||
}, [getSongCountByArtistTitle]);
|
||||
|
||||
return {
|
||||
songLists: pagination.items,
|
||||
@ -37,6 +70,9 @@ export const useSongLists = () => {
|
||||
currentPage: pagination.currentPage,
|
||||
totalPages: pagination.totalPages,
|
||||
checkSongAvailability,
|
||||
getSongCountForSongListSong,
|
||||
getSongsByArtistTitle,
|
||||
getSongCountByArtistTitle,
|
||||
handleAddToQueue,
|
||||
handleToggleFavorite,
|
||||
isLoading: pagination.isLoading,
|
||||
|
||||
@ -121,4 +121,41 @@ export const getQueueStats = (queue: Record<string, QueueItem>) => {
|
||||
singers: [...new Set(queueArray.map(item => item.singer.name))],
|
||||
estimatedDuration: queueArray.length * 3, // Rough estimate: 3 minutes per song
|
||||
};
|
||||
};
|
||||
|
||||
// Get songs by artist and title combination
|
||||
export const getSongsByArtistTitle = (songs: Song[], artist: string, title: string): Song[] => {
|
||||
const artistLower = (artist || '').toLowerCase();
|
||||
const titleLower = (title || '').toLowerCase();
|
||||
|
||||
return songs.filter(song =>
|
||||
(song.artist || '').toLowerCase() === artistLower &&
|
||||
(song.title || '').toLowerCase() === titleLower
|
||||
);
|
||||
};
|
||||
|
||||
// Get song count by artist and title combination
|
||||
export const getSongCountByArtistTitle = (songs: Song[], artist: string, title: string): number => {
|
||||
return getSongsByArtistTitle(songs, artist, title).length;
|
||||
};
|
||||
|
||||
// Create a map of song counts by artist and title for performance
|
||||
export const createSongCountMapByArtistTitle = (songs: Song[]): Map<string, number> => {
|
||||
const countsMap = new Map<string, number>();
|
||||
|
||||
songs.forEach(song => {
|
||||
const artist = (song.artist || '').toLowerCase();
|
||||
const title = (song.title || '').toLowerCase();
|
||||
const key = `${artist}|${title}`;
|
||||
|
||||
countsMap.set(key, (countsMap.get(key) || 0) + 1);
|
||||
});
|
||||
|
||||
return countsMap;
|
||||
};
|
||||
|
||||
// Get song count using a pre-computed map
|
||||
export const getSongCountFromMap = (countsMap: Map<string, number>, artist: string, title: string): number => {
|
||||
const key = `${(artist || '').toLowerCase()}|${(title || '').toLowerCase()}`;
|
||||
return countsMap.get(key) || 0;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user