Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
a243e1e034
commit
4b2d1dcf77
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { InfiniteScrollList, SongItem } from '../../components/common';
|
import { InfiniteScrollList, SongItem } from '../../components/common';
|
||||||
import { useNewSongs } from '../../hooks';
|
import { useNewSongs } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectNewSongs } from '../../redux';
|
import { selectNewSongsArray } from '../../redux';
|
||||||
import { debugLog } from '../../utils/logger';
|
import { debugLog } from '../../utils/logger';
|
||||||
import type { Song } from '../../types';
|
import type { Song } from '../../types';
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ const NewSongs: React.FC = () => {
|
|||||||
handleToggleFavorite,
|
handleToggleFavorite,
|
||||||
} = useNewSongs();
|
} = useNewSongs();
|
||||||
|
|
||||||
const newSongs = useAppSelector(selectNewSongs);
|
const newSongsArray = useAppSelector(selectNewSongsArray);
|
||||||
const newSongsCount = Object.keys(newSongs).length;
|
const newSongsCount = newSongsArray.length;
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
debugLog('NewSongs component - new songs count:', newSongsCount);
|
debugLog('NewSongs component - new songs count:', newSongsCount);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { trash, reorderThreeOutline, reorderTwoOutline, playCircle } from 'ionic
|
|||||||
import { ActionButton } from '../../components/common';
|
import { ActionButton } from '../../components/common';
|
||||||
import { useQueue } from '../../hooks';
|
import { useQueue } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectQueue, selectPlayerState, selectIsAdmin, selectControllerName } from '../../redux';
|
import { selectQueueLength, selectPlayerStateMemoized, selectIsAdmin, selectControllerName } from '../../redux';
|
||||||
import { PlayerState } from '../../types';
|
import { PlayerState } from '../../types';
|
||||||
import { queueService } from '../../firebase/services';
|
import { queueService } from '../../firebase/services';
|
||||||
import { debugLog } from '../../utils/logger';
|
import { debugLog } from '../../utils/logger';
|
||||||
@ -23,11 +23,10 @@ const Queue: React.FC = () => {
|
|||||||
handleRemoveFromQueue,
|
handleRemoveFromQueue,
|
||||||
} = useQueue();
|
} = useQueue();
|
||||||
|
|
||||||
const queue = useAppSelector(selectQueue);
|
const queueCount = useAppSelector(selectQueueLength);
|
||||||
const playerState = useAppSelector(selectPlayerState);
|
const playerState = useAppSelector(selectPlayerStateMemoized);
|
||||||
const isAdmin = useAppSelector(selectIsAdmin);
|
const isAdmin = useAppSelector(selectIsAdmin);
|
||||||
const controllerName = useAppSelector(selectControllerName);
|
const controllerName = useAppSelector(selectControllerName);
|
||||||
const queueCount = Object.keys(queue).length;
|
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
debugLog('Queue component - queue count:', queueCount);
|
debugLog('Queue component - queue count:', queueCount);
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useAppSelector } from '../redux';
|
import { useAppSelector } from '../redux';
|
||||||
import { selectControllerName, selectCurrentSinger } 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';
|
||||||
|
|
||||||
export const useSongOperations = () => {
|
export const useSongOperations = () => {
|
||||||
const controllerName = useAppSelector(selectControllerName);
|
const controllerName = useAppSelector(selectControllerName);
|
||||||
const currentSinger = useAppSelector(selectCurrentSinger);
|
const currentSinger = useAppSelector(selectCurrentSinger);
|
||||||
const currentQueue = useAppSelector((state) => state.controller.data?.player?.queue || {});
|
const currentQueue = useAppSelector(selectQueueObject);
|
||||||
|
|
||||||
const addToQueue = useCallback(async (song: Song) => {
|
const addToQueue = useCallback(async (song: Song) => {
|
||||||
if (!controllerName || !currentSinger) {
|
if (!controllerName || !currentSinger) {
|
||||||
|
|||||||
@ -72,8 +72,8 @@ export const selectAuth = (state: { auth: AuthState }) => state.auth.data;
|
|||||||
export const selectAuthLoading = (state: { auth: AuthState }) => state.auth.loading;
|
export const selectAuthLoading = (state: { auth: AuthState }) => state.auth.loading;
|
||||||
export const selectAuthError = (state: { auth: AuthState }) => state.auth.error;
|
export const selectAuthError = (state: { auth: AuthState }) => state.auth.error;
|
||||||
export const selectIsAuthenticated = (state: { auth: AuthState }) => state.auth.data?.authenticated || false;
|
export const selectIsAuthenticated = (state: { auth: AuthState }) => state.auth.data?.authenticated || false;
|
||||||
export const selectCurrentSinger = (state: { auth: AuthState }) => state.auth.data?.singer || '';
|
export const selectCurrentSinger = (state: { auth: AuthState }) => state.auth.data?.singer ?? '';
|
||||||
export const selectIsAdmin = (state: { auth: AuthState }) => state.auth.data?.isAdmin || false;
|
export const selectIsAdmin = (state: { auth: AuthState }) => Boolean(state.auth.data?.isAdmin);
|
||||||
export const selectControllerName = (state: { auth: AuthState }) => state.auth.data?.controller || '';
|
export const selectControllerName = (state: { auth: AuthState }) => state.auth.data?.controller ?? '';
|
||||||
|
|
||||||
export default authSlice.reducer;
|
export default authSlice.reducer;
|
||||||
@ -151,14 +151,17 @@ export const selectControllerLoading = (state: { controller: ControllerState })
|
|||||||
export const selectControllerError = (state: { controller: ControllerState }) => state.controller.error;
|
export const selectControllerError = (state: { controller: ControllerState }) => state.controller.error;
|
||||||
export const selectLastUpdated = (state: { controller: ControllerState }) => state.controller.lastUpdated;
|
export const selectLastUpdated = (state: { controller: ControllerState }) => state.controller.lastUpdated;
|
||||||
|
|
||||||
|
// Constants for empty objects to prevent new references
|
||||||
|
const EMPTY_OBJECT = {};
|
||||||
|
|
||||||
// Selectors for specific data
|
// Selectors for specific data
|
||||||
export const selectSongs = (state: { controller: ControllerState }) => state.controller.data?.songs || {};
|
export const selectSongs = (state: { controller: ControllerState }) => state.controller.data?.songs ?? EMPTY_OBJECT;
|
||||||
export const selectQueue = (state: { controller: ControllerState }) => state.controller.data?.player?.queue || {};
|
export const selectQueue = (state: { controller: ControllerState }) => state.controller.data?.player?.queue ?? EMPTY_OBJECT;
|
||||||
export const selectFavorites = (state: { controller: ControllerState }) => state.controller.data?.favorites || {};
|
export const selectFavorites = (state: { controller: ControllerState }) => state.controller.data?.favorites ?? EMPTY_OBJECT;
|
||||||
export const selectHistory = (state: { controller: ControllerState }) => state.controller.data?.history || {};
|
export const selectHistory = (state: { controller: ControllerState }) => state.controller.data?.history ?? EMPTY_OBJECT;
|
||||||
export const selectTopPlayed = (state: { controller: ControllerState }) => state.controller.data?.topPlayed || {};
|
export const selectTopPlayed = (state: { controller: ControllerState }) => state.controller.data?.topPlayed ?? EMPTY_OBJECT;
|
||||||
export const selectNewSongs = (state: { controller: ControllerState }) => state.controller.data?.newSongs || {};
|
export const selectNewSongs = (state: { controller: ControllerState }) => state.controller.data?.newSongs ?? EMPTY_OBJECT;
|
||||||
export const selectSongList = (state: { controller: ControllerState }) => state.controller.data?.songList || {};
|
export const selectSongList = (state: { controller: ControllerState }) => state.controller.data?.songList ?? EMPTY_OBJECT;
|
||||||
export const selectPlayerState = (state: { controller: ControllerState }) => {
|
export const selectPlayerState = (state: { controller: ControllerState }) => {
|
||||||
const playerState = state.controller.data?.player?.state;
|
const playerState = state.controller.data?.player?.state;
|
||||||
|
|
||||||
@ -173,6 +176,6 @@ export const selectPlayerState = (state: { controller: ControllerState }) => {
|
|||||||
return playerState;
|
return playerState;
|
||||||
};
|
};
|
||||||
export const selectSettings = (state: { controller: ControllerState }) => state.controller.data?.player?.settings;
|
export const selectSettings = (state: { controller: ControllerState }) => state.controller.data?.player?.settings;
|
||||||
export const selectSingers = (state: { controller: ControllerState }) => state.controller.data?.player?.singers || {};
|
export const selectSingers = (state: { controller: ControllerState }) => state.controller.data?.player?.singers ?? EMPTY_OBJECT;
|
||||||
|
|
||||||
export default controllerSlice.reducer;
|
export default controllerSlice.reducer;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import type { RootState } from '../types';
|
import type { RootState, QueueItem, Singer, Song } from '../types';
|
||||||
import {
|
import {
|
||||||
selectSongs,
|
selectSongs,
|
||||||
selectQueue,
|
selectQueue,
|
||||||
@ -10,7 +10,8 @@ import {
|
|||||||
selectSongList,
|
selectSongList,
|
||||||
selectSingers,
|
selectSingers,
|
||||||
selectIsAdmin,
|
selectIsAdmin,
|
||||||
selectCurrentSinger
|
selectCurrentSinger,
|
||||||
|
selectPlayerState
|
||||||
} from './index';
|
} from './index';
|
||||||
import {
|
import {
|
||||||
objectToArray,
|
objectToArray,
|
||||||
@ -19,8 +20,7 @@ import {
|
|||||||
sortHistoryByDate,
|
sortHistoryByDate,
|
||||||
sortTopPlayedByCount,
|
sortTopPlayedByCount,
|
||||||
sortSongsByArtistAndTitle,
|
sortSongsByArtistAndTitle,
|
||||||
limitArray,
|
limitArray
|
||||||
getQueueStats
|
|
||||||
} from '../utils/dataProcessing';
|
} from '../utils/dataProcessing';
|
||||||
import { UI_CONSTANTS } from '../constants';
|
import { UI_CONSTANTS } from '../constants';
|
||||||
|
|
||||||
@ -54,7 +54,18 @@ export const selectQueueArray = createSelector(
|
|||||||
|
|
||||||
export const selectQueueStats = createSelector(
|
export const selectQueueStats = createSelector(
|
||||||
[selectQueue],
|
[selectQueue],
|
||||||
(queue) => getQueueStats(queue)
|
(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(
|
export const selectHistoryArray = createSelector(
|
||||||
@ -92,7 +103,7 @@ export const selectNewSongsArrayWithoutDisabled = createSelector(
|
|||||||
|
|
||||||
export const selectSingersArray = createSelector(
|
export const selectSingersArray = createSelector(
|
||||||
[selectSingers],
|
[selectSingers],
|
||||||
(singers) => objectToArray(singers).sort((a, b) => a.name.localeCompare(b.name))
|
(singers) => (objectToArray(singers) as Singer[]).sort((a, b) => a.name.localeCompare(b.name))
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectSongListArray = createSelector(
|
export const selectSongListArray = createSelector(
|
||||||
@ -104,7 +115,7 @@ export const selectArtistsArray = createSelector(
|
|||||||
[selectSongs],
|
[selectSongs],
|
||||||
(songs) => {
|
(songs) => {
|
||||||
const artists = new Set<string>();
|
const artists = new Set<string>();
|
||||||
Object.values(songs).forEach(song => {
|
(Object.values(songs) as Song[]).forEach((song) => {
|
||||||
if (song.artist) {
|
if (song.artist) {
|
||||||
artists.add(song.artist);
|
artists.add(song.artist);
|
||||||
}
|
}
|
||||||
@ -122,12 +133,12 @@ export const selectTopPlayedArray = createSelector(
|
|||||||
export const selectUserQueueItems = createSelector(
|
export const selectUserQueueItems = createSelector(
|
||||||
[selectQueueArray, selectCurrentSinger],
|
[selectQueueArray, selectCurrentSinger],
|
||||||
(queueArray, currentSinger) =>
|
(queueArray, currentSinger) =>
|
||||||
queueArray.filter(item => item.singer.name === currentSinger)
|
queueArray.filter((item: QueueItem) => item.singer.name === currentSinger)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectCanReorderQueue = createSelector(
|
export const selectCanReorderQueue = createSelector(
|
||||||
[selectIsAdmin],
|
[selectIsAdmin],
|
||||||
(isAdmin) => isAdmin
|
(isAdmin) => Boolean(isAdmin)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Search-specific selectors
|
// Search-specific selectors
|
||||||
@ -151,15 +162,40 @@ export const selectSearchResultsWithoutDisabled = createSelector(
|
|||||||
// Queue-specific selectors
|
// Queue-specific selectors
|
||||||
export const selectQueueWithUserInfo = createSelector(
|
export const selectQueueWithUserInfo = createSelector(
|
||||||
[selectQueueArray, selectCurrentSinger],
|
[selectQueueArray, selectCurrentSinger],
|
||||||
(queueArray, currentSinger) =>
|
(queueArray, currentSinger) => {
|
||||||
queueArray.map(item => ({
|
// 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,
|
...item,
|
||||||
isCurrentUser: item.singer.name === currentSinger,
|
isCurrentUser: item.singer.name === currentSinger,
|
||||||
}))
|
}));
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Memoized selector for queue length to prevent unnecessary re-renders
|
// Memoized selector for queue length to prevent unnecessary re-renders
|
||||||
export const selectQueueLength = createSelector(
|
export const selectQueueLength = createSelector(
|
||||||
[selectQueue],
|
[selectQueue],
|
||||||
(queue) => Object.keys(queue).length
|
(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
|
||||||
);
|
);
|
||||||
@ -5,6 +5,8 @@ import type { Song, QueueItem, TopPlayed } from '../types';
|
|||||||
export const objectToArray = <T extends { key?: string }>(
|
export const objectToArray = <T extends { key?: string }>(
|
||||||
obj: Record<string, T>
|
obj: Record<string, T>
|
||||||
): T[] => {
|
): T[] => {
|
||||||
|
if (Object.keys(obj).length === 0) return [];
|
||||||
|
|
||||||
return Object.entries(obj).map(([key, item]) => ({
|
return Object.entries(obj).map(([key, item]) => ({
|
||||||
...item,
|
...item,
|
||||||
key,
|
key,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user