singsalot/src/redux/selectors.ts

201 lines
6.2 KiB
TypeScript

import { createSelector } from '@reduxjs/toolkit';
import type { RootState, QueueItem, Singer, Song } from '../types';
import {
selectSongs,
selectQueue,
selectFavorites,
selectHistory,
selectTopPlayed,
selectNewSongs,
selectSongList,
selectSingers,
selectIsAdmin,
selectCurrentSinger,
selectPlayerState
} from './index';
import {
objectToArray,
filterSongs,
sortQueueByOrder,
sortHistoryByDate,
sortTopPlayedByCount,
sortSongsByArtistAndTitle,
limitArray
} from '../utils/dataProcessing';
import { UI_CONSTANTS } from '../constants';
// Enhanced selectors with data processing
export const selectSongsArray = createSelector(
[selectSongs],
(songs) => sortSongsByArtistAndTitle(objectToArray(songs))
);
// Selector that filters songs and excludes disabled ones
export const selectSongsArrayWithoutDisabled = createSelector(
[selectSongsArray, (_state: RootState, disabledSongPaths: Set<string>) => disabledSongPaths],
(songs, disabledSongPaths) => songs.filter(song => !disabledSongPaths.has(song.path))
);
export const selectFilteredSongs = createSelector(
[selectSongsArray, (_state: RootState, searchTerm: string) => searchTerm],
(songs, searchTerm) => filterSongs(songs, searchTerm)
);
// Enhanced filtered songs that also excludes disabled songs
export const selectFilteredSongsWithoutDisabled = createSelector(
[selectSongsArray, (_state: RootState, searchTerm: string, disabledSongPaths: Set<string>) => ({ searchTerm, disabledSongPaths })],
(songs, { searchTerm, disabledSongPaths }) => filterSongs(songs, searchTerm, disabledSongPaths)
);
export const selectQueueArray = createSelector(
[selectQueue],
(queue) => sortQueueByOrder(objectToArray(queue))
);
export const selectQueueStats = createSelector(
[selectQueue],
(queue) => {
const queueArray = Object.values(queue) as QueueItem[];
const totalSongs = queueArray.length;
const singers = [...new Set(queueArray.map(item => item.singer.name))];
const estimatedDuration = totalSongs * 3; // Rough estimate: 3 minutes per song
return {
totalSongs,
singers,
estimatedDuration,
};
}
);
export const selectHistoryArray = createSelector(
[selectHistory],
(history) => limitArray(sortHistoryByDate(objectToArray(history)), UI_CONSTANTS.HISTORY.MAX_ITEMS)
);
// History array without disabled songs
export const selectHistoryArrayWithoutDisabled = createSelector(
[selectHistoryArray, (_state: RootState, disabledSongPaths: Set<string>) => disabledSongPaths],
(history, disabledSongPaths) => history.filter(song => !disabledSongPaths.has(song.path))
);
export const selectFavoritesArray = createSelector(
[selectFavorites],
(favorites) => sortSongsByArtistAndTitle(objectToArray(favorites))
);
// Favorites array without disabled songs
export const selectFavoritesArrayWithoutDisabled = createSelector(
[selectFavoritesArray, (_state: RootState, disabledSongPaths: Set<string>) => disabledSongPaths],
(favorites, disabledSongPaths) => favorites.filter(song => !disabledSongPaths.has(song.path))
);
export const selectNewSongsArray = createSelector(
[selectNewSongs],
(newSongs) => sortSongsByArtistAndTitle(objectToArray(newSongs))
);
// New songs array without disabled songs
export const selectNewSongsArrayWithoutDisabled = createSelector(
[selectNewSongsArray, (_state: RootState, disabledSongPaths: Set<string>) => disabledSongPaths],
(newSongs, disabledSongPaths) => newSongs.filter(song => !disabledSongPaths.has(song.path))
);
export const selectSingersArray = createSelector(
[selectSingers],
(singers) => (objectToArray(singers) as Singer[]).sort((a, b) => a.name.localeCompare(b.name))
);
export const selectSongListArray = createSelector(
[selectSongList],
(songList) => objectToArray(songList)
);
export const selectArtistsArray = createSelector(
[selectSongs],
(songs) => {
const artists = new Set<string>();
(Object.values(songs) as Song[]).forEach((song) => {
if (song.artist) {
artists.add(song.artist);
}
});
return Array.from(artists).sort((a, b) => a.localeCompare(b));
}
);
export const selectTopPlayedArray = createSelector(
[selectTopPlayed],
(topPlayed) => sortTopPlayedByCount(objectToArray(topPlayed))
);
// User-specific selectors
export const selectUserQueueItems = createSelector(
[selectQueueArray, selectCurrentSinger],
(queueArray, currentSinger) =>
queueArray.filter((item: QueueItem) => item.singer.name === currentSinger)
);
export const selectCanReorderQueue = createSelector(
[selectIsAdmin],
(isAdmin) => Boolean(isAdmin)
);
// Search-specific selectors
export const selectSearchResults = createSelector(
[selectFilteredSongs],
(filteredSongs) => ({
songs: filteredSongs,
count: filteredSongs.length,
})
);
// Enhanced search results that exclude disabled songs
export const selectSearchResultsWithoutDisabled = createSelector(
[selectFilteredSongsWithoutDisabled],
(filteredSongs) => ({
songs: filteredSongs,
count: filteredSongs.length,
})
);
// Queue-specific selectors
export const selectQueueWithUserInfo = createSelector(
[selectQueueArray, selectCurrentSinger],
(queueArray, currentSinger) => {
// If no items, return empty array
if (queueArray.length === 0) return [];
// If no current singer, return items without isCurrentUser flag
if (!currentSinger) {
return queueArray.map(item => ({
...item,
isCurrentUser: false,
}));
}
// Map items and add isCurrentUser flag
return queueArray.map(item => ({
...item,
isCurrentUser: item.singer.name === currentSinger,
}));
}
);
// Memoized selector for queue length to prevent unnecessary re-renders
export const selectQueueLength = createSelector(
[selectQueue],
(queue) => Object.keys(queue).length
);
// Memoized selector for queue object to prevent unnecessary re-renders
export const selectQueueObject = createSelector(
[selectQueue],
(queue) => Object.keys(queue).length > 0 ? { ...queue } : {}
);
// Memoized selector for player state to prevent unnecessary re-renders
export const selectPlayerStateMemoized = createSelector(
[selectPlayerState],
(playerState) => playerState ? { ...playerState } : null
);