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;
|
||||||
@ -12,3 +12,4 @@ export { default as ListItem } from './ListItem';
|
|||||||
export { NumberDisplay } from './NumberDisplay';
|
export { NumberDisplay } from './NumberDisplay';
|
||||||
export { ModalHeader } from './ModalHeader';
|
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,
|
MAX_ITEMS: 100,
|
||||||
},
|
},
|
||||||
HISTORY: {
|
HISTORY: {
|
||||||
MAX_ITEMS: 50,
|
MAX_ITEMS: 250, // Increased from 50 to show more history items
|
||||||
},
|
},
|
||||||
TOP_PLAYED: {
|
TOP_PLAYED: {
|
||||||
MAX_ITEMS: 20,
|
MAX_ITEMS: 20,
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const SongLists: React.FC = () => {
|
|||||||
hasMore,
|
hasMore,
|
||||||
loadMore,
|
loadMore,
|
||||||
checkSongAvailability,
|
checkSongAvailability,
|
||||||
|
getSongCountForSongListSong,
|
||||||
} = useSongLists();
|
} = useSongLists();
|
||||||
|
|
||||||
const songListData = useAppSelector(selectSongList);
|
const songListData = useAppSelector(selectSongList);
|
||||||
@ -94,7 +95,8 @@ const SongLists: React.FC = () => {
|
|||||||
<IonAccordionGroup value={expandedSongKey}>
|
<IonAccordionGroup value={expandedSongKey}>
|
||||||
{selectedListWithAvailability?.songs.map((songListSong: SongListSong & { availableSongs: Song[] }, index) => {
|
{selectedListWithAvailability?.songs.map((songListSong: SongListSong & { availableSongs: Song[] }, index) => {
|
||||||
const availableSongs = songListSong.availableSongs;
|
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}`;
|
const songKey = songListSong.key || `${songListSong.title}-${songListSong.position}-${index}`;
|
||||||
|
|
||||||
if (isAvailable) {
|
if (isAvailable) {
|
||||||
@ -114,7 +116,7 @@ const SongLists: React.FC = () => {
|
|||||||
onClick={() => handleSongItemClick(songKey)}
|
onClick={() => handleSongItemClick(songKey)}
|
||||||
endContent={
|
endContent={
|
||||||
<IonChip color="primary">
|
<IonChip color="primary">
|
||||||
{availableSongs.length} version{availableSongs.length !== 1 ? 's' : ''}
|
{songCount} version{songCount !== 1 ? 's' : ''}
|
||||||
</IonChip>
|
</IonChip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,33 +1,66 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useAppSelector, selectSongListArray, selectSongsArray } from '../redux';
|
import { useAppSelector, selectSongListArray, selectSongsArray } from '../redux';
|
||||||
import { useActions } from './useActions';
|
import { useActions } from './useActions';
|
||||||
import { usePaginatedData } from './index';
|
import { usePaginatedData } from './index';
|
||||||
import type { SongListSong } from '../types';
|
import type { SongListSong, Song } from '../types';
|
||||||
|
|
||||||
export const useSongLists = () => {
|
export const useSongLists = () => {
|
||||||
const allSongLists = useAppSelector(selectSongListArray);
|
const allSongLists = useAppSelector(selectSongListArray);
|
||||||
const allSongs = useAppSelector(selectSongsArray);
|
const allSongs = useAppSelector(selectSongsArray);
|
||||||
const { handleAddToQueue, handleToggleFavorite } = useActions();
|
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
|
// Use the composable pagination hook
|
||||||
const pagination = usePaginatedData(allSongLists, {
|
const pagination = usePaginatedData(allSongLists, {
|
||||||
itemsPerPage: 20 // Default pagination size
|
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) => {
|
const checkSongAvailability = useCallback((songListSong: SongListSong) => {
|
||||||
if (songListSong.foundSongs && songListSong.foundSongs.length > 0) {
|
if (songListSong.foundSongs && songListSong.foundSongs.length > 0) {
|
||||||
return songListSong.foundSongs;
|
return songListSong.foundSongs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for songs by artist and title
|
// Use the pre-computed data for better performance
|
||||||
const matchingSongs = allSongs.filter(song =>
|
return getSongsByArtistTitle(songListSong.artist, songListSong.title);
|
||||||
(song.artist || '').toLowerCase() === (songListSong.artist || '').toLowerCase() &&
|
}, [getSongsByArtistTitle]);
|
||||||
(song.title || '').toLowerCase() === (songListSong.title || '').toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
return matchingSongs;
|
// Get song count for a song list song
|
||||||
}, [allSongs]);
|
const getSongCountForSongListSong = useCallback((songListSong: SongListSong) => {
|
||||||
|
return getSongCountByArtistTitle(songListSong.artist, songListSong.title);
|
||||||
|
}, [getSongCountByArtistTitle]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
songLists: pagination.items,
|
songLists: pagination.items,
|
||||||
@ -37,6 +70,9 @@ export const useSongLists = () => {
|
|||||||
currentPage: pagination.currentPage,
|
currentPage: pagination.currentPage,
|
||||||
totalPages: pagination.totalPages,
|
totalPages: pagination.totalPages,
|
||||||
checkSongAvailability,
|
checkSongAvailability,
|
||||||
|
getSongCountForSongListSong,
|
||||||
|
getSongsByArtistTitle,
|
||||||
|
getSongCountByArtistTitle,
|
||||||
handleAddToQueue,
|
handleAddToQueue,
|
||||||
handleToggleFavorite,
|
handleToggleFavorite,
|
||||||
isLoading: pagination.isLoading,
|
isLoading: pagination.isLoading,
|
||||||
|
|||||||
@ -122,3 +122,40 @@ export const getQueueStats = (queue: Record<string, QueueItem>) => {
|
|||||||
estimatedDuration: queueArray.length * 3, // Rough estimate: 3 minutes per song
|
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