fixed history issues
Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>
This commit is contained in:
parent
7fde4cb0cc
commit
b8e8866681
@ -10,6 +10,7 @@ import { useAppSelector } from '../../redux';
|
||||
import { selectSingersArray, selectControllerName, selectQueueObject } from '../../redux';
|
||||
import { queueService } from '../../firebase/services';
|
||||
import { useToast } from '../../hooks/useToast';
|
||||
import { useActions } from '../../hooks';
|
||||
import { ModalHeader } from './ModalHeader';
|
||||
import { NumberDisplay } from './NumberDisplay';
|
||||
import { SongInfoDisplay } from './SongItem';
|
||||
@ -29,6 +30,7 @@ const SelectSinger: React.FC<SelectSingerProps> = ({ isOpen, onClose, song }) =>
|
||||
const showSuccess = toast?.showSuccess;
|
||||
const showError = toast?.showError;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { handleAddToQueue } = useActions();
|
||||
|
||||
const handleSelectSinger = async (singer: Singer) => {
|
||||
if (!controllerName) {
|
||||
@ -38,23 +40,7 @@ const SelectSinger: React.FC<SelectSingerProps> = ({ isOpen, onClose, song }) =>
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Calculate the next order by finding the highest order value and adding 1
|
||||
const queueItems = Object.values(currentQueue) as QueueItem[];
|
||||
const maxOrder = queueItems.length > 0
|
||||
? Math.max(...queueItems.map(item => item.order || 0))
|
||||
: 0;
|
||||
const nextOrder = maxOrder + 1;
|
||||
|
||||
const queueItem: Omit<QueueItem, 'key'> = {
|
||||
order: nextOrder,
|
||||
singer: {
|
||||
name: singer.name,
|
||||
lastLogin: singer.lastLogin || '',
|
||||
},
|
||||
song: song,
|
||||
};
|
||||
|
||||
await queueService.addToQueue(controllerName, queueItem);
|
||||
await handleAddToQueue(song, singer);
|
||||
if (showSuccess) showSuccess(`${song.title} added to queue for ${singer.name}`);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useMemo, useCallback } from 'react';
|
||||
import { IonItem, IonLabel } from '@ionic/react';
|
||||
import ActionButton from './ActionButton';
|
||||
import { useAppSelector } from '../../redux';
|
||||
import { selectQueue, selectFavorites } from '../../redux';
|
||||
import { selectQueue, selectFavorites, selectCurrentSinger } from '../../redux';
|
||||
import { useActions } from '../../hooks/useActions';
|
||||
import { useModal } from '../../hooks/useModalContext';
|
||||
import { debugLog } from '../../utils/logger';
|
||||
@ -209,6 +209,7 @@ const SongItem: React.FC<SongItemProps> = React.memo(({
|
||||
// Get current state from Redux
|
||||
const queue = useAppSelector(selectQueue);
|
||||
const favorites = useAppSelector(selectFavorites);
|
||||
const currentSingerName = useAppSelector(selectCurrentSinger);
|
||||
|
||||
// Get unified action handlers
|
||||
const { handleAddToQueue, handleToggleFavorite, handleRemoveFromQueue } = useActions();
|
||||
@ -255,8 +256,15 @@ const SongItem: React.FC<SongItemProps> = React.memo(({
|
||||
|
||||
// Memoized handler functions for performance
|
||||
const handleAddToQueueClick = useCallback(async () => {
|
||||
await handleAddToQueue(song);
|
||||
}, [handleAddToQueue, song]);
|
||||
// Find the current singer object from the queue or create a minimal one
|
||||
let singer = undefined;
|
||||
if (currentSingerName) {
|
||||
// Try to find a matching singer in the queue (for lastLogin)
|
||||
const queueSingers = (Object.values(queue) as QueueItem[]).map(item => item.singer);
|
||||
singer = queueSingers.find(s => s.name === currentSingerName) || { name: currentSingerName, lastLogin: '' };
|
||||
}
|
||||
await handleAddToQueue(song, singer);
|
||||
}, [handleAddToQueue, song, currentSingerName, queue]);
|
||||
|
||||
const handleToggleFavoriteClick = useCallback(async () => {
|
||||
await handleToggleFavorite(song);
|
||||
|
||||
@ -279,51 +279,86 @@ export const playerService = {
|
||||
|
||||
// History operations
|
||||
export const historyService = {
|
||||
// Add song to history
|
||||
// Add song to history (by path, with count)
|
||||
addToHistory: async (controllerName: string, song: Omit<Song, 'key'>) => {
|
||||
const historyRef = ref(database, `controllers/${controllerName}/history`);
|
||||
const historySnapshot = await get(historyRef);
|
||||
const currentHistory = historySnapshot.exists() ? historySnapshot.val() : {};
|
||||
|
||||
// Find the next available sequential key
|
||||
const now = Date.now();
|
||||
// Find if song with same path exists
|
||||
const existingEntry = Object.entries(currentHistory).find(
|
||||
([, item]) => typeof item === 'object' && item !== null && 'path' in item && (item as { path: string }).path === song.path
|
||||
);
|
||||
if (existingEntry) {
|
||||
const [key, item] = existingEntry;
|
||||
await update(ref(database, `controllers/${controllerName}/history/${key}`), {
|
||||
count: (item.count || 1) + 1,
|
||||
lastPlayed: now,
|
||||
});
|
||||
// Move this entry to the most recent by updating lastPlayed
|
||||
// (No need to reorder keys, just use lastPlayed for recency)
|
||||
// Cap size after update
|
||||
} else {
|
||||
// Add new entry with count: 1 and lastPlayed
|
||||
const nextKey = findNextSequentialKey(Object.keys(currentHistory));
|
||||
debugLog('addToHistory - existing keys:', Object.keys(currentHistory));
|
||||
debugLog('addToHistory - next key:', nextKey);
|
||||
|
||||
const newHistoryRef = ref(database, `controllers/${controllerName}/history/${nextKey}`);
|
||||
await set(newHistoryRef, song);
|
||||
return { key: nextKey.toString() };
|
||||
await set(ref(database, `controllers/${controllerName}/history/${nextKey}`), {
|
||||
...song,
|
||||
count: 1,
|
||||
lastPlayed: now,
|
||||
});
|
||||
}
|
||||
// Cap history size (remove oldest by lastPlayed if over 250)
|
||||
const updatedSnapshot = await get(historyRef);
|
||||
const updatedHistory = updatedSnapshot.exists() ? updatedSnapshot.val() : {};
|
||||
const entries = Object.entries(updatedHistory);
|
||||
if (entries.length > 250) {
|
||||
// Find the oldest entry by lastPlayed using a for loop for type safety
|
||||
let oldestKey: string | null = null;
|
||||
let oldestTime: number | null = null;
|
||||
for (const [key, item] of entries) {
|
||||
if (typeof item === 'object' && item !== null && 'lastPlayed' in item && typeof (item as { lastPlayed?: number }).lastPlayed === 'number') {
|
||||
const lastPlayed = (item as { lastPlayed: number }).lastPlayed;
|
||||
if (oldestTime === null || lastPlayed < oldestTime) {
|
||||
oldestTime = lastPlayed;
|
||||
oldestKey = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldestKey) {
|
||||
await remove(ref(database, `controllers/${controllerName}/history/${oldestKey}`));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Remove song from history
|
||||
removeFromHistory: async (controllerName: string, historyItemKey: string) => {
|
||||
// Remove song from history (by path, with count logic)
|
||||
removeFromHistory: async (controllerName: string, songPath: string) => {
|
||||
const historyRef = ref(database, `controllers/${controllerName}/history`);
|
||||
const historySnapshot = await get(historyRef);
|
||||
|
||||
if (!historySnapshot.exists()) {
|
||||
throw new Error('History not found');
|
||||
}
|
||||
|
||||
const history = historySnapshot.val();
|
||||
debugLog('removeFromHistory - original history:', history);
|
||||
|
||||
// Find the item to remove and get its key
|
||||
const itemToRemove = Object.entries(history).find(([key, item]) =>
|
||||
key === historyItemKey && item
|
||||
// Find entry by path
|
||||
const existingEntry = Object.entries(history).find(
|
||||
([, item]) => typeof item === 'object' && item !== null && 'path' in item && (item as { path: string }).path === songPath
|
||||
);
|
||||
|
||||
if (!itemToRemove) {
|
||||
if (!existingEntry) {
|
||||
throw new Error('History item not found');
|
||||
}
|
||||
|
||||
const [removedKey, removedItem] = itemToRemove;
|
||||
debugLog('removeFromHistory - removing item:', removedItem, 'with key:', removedKey);
|
||||
|
||||
// Use utility function to create shift-down updates
|
||||
const updates = shiftDownAfterDeletion(history, removedKey, 'history item');
|
||||
|
||||
// Apply all updates atomically
|
||||
await update(historyRef, updates);
|
||||
const [key, item] = existingEntry;
|
||||
let count = 1;
|
||||
if (typeof item === 'object' && item !== null && 'count' in item) {
|
||||
count = (item as { count?: number }).count ?? 1;
|
||||
}
|
||||
const now = Date.now();
|
||||
if (count > 1) {
|
||||
await update(ref(database, `controllers/${controllerName}/history/${key}`), {
|
||||
count: count - 1,
|
||||
lastPlayed: now,
|
||||
});
|
||||
} else {
|
||||
await remove(ref(database, `controllers/${controllerName}/history/${key}`));
|
||||
}
|
||||
},
|
||||
|
||||
// Listen to history changes
|
||||
|
||||
@ -2,13 +2,14 @@ import { useState, useCallback } from 'react';
|
||||
import { useAppSelector, useAppDispatch } from '../redux';
|
||||
import { selectControllerName, selectPlayerStateMemoized, selectIsAdmin } from '../redux';
|
||||
import { reorderQueueAsync } from '../redux/queueSlice';
|
||||
import { addToQueue as addToQueueThunk } from '../redux/queueSlice';
|
||||
import { useSongOperations } from './useSongOperations';
|
||||
import { useToast } from './useToast';
|
||||
import { useDisabledSongs } from './useDisabledSongs';
|
||||
import { historyService } from '../firebase/services';
|
||||
import { debugLog } from '../utils/logger';
|
||||
import { PlayerState } from '../types';
|
||||
import type { Song, QueueItem } from '../types';
|
||||
import type { Song, QueueItem, Singer } from '../types';
|
||||
|
||||
export type QueueMode = 'delete' | 'reorder';
|
||||
|
||||
@ -31,14 +32,56 @@ export const useActions = () => {
|
||||
const canDeleteFirstItem = isAdmin && (playerState?.state === PlayerState.stopped || playerState?.state === PlayerState.paused); // Only allow deleting first item if not playing
|
||||
|
||||
// Song operations
|
||||
const handleAddToQueue = useCallback(async (song: Song) => {
|
||||
const handleAddToQueue = useCallback(async (song: Song, singerOverride?: Singer) => {
|
||||
try {
|
||||
await addToQueue(song);
|
||||
// If a singer is provided, use it; otherwise, use the current singer from state
|
||||
let singer = singerOverride;
|
||||
if (!singer) {
|
||||
// Try to get from Redux state
|
||||
const state = (window as unknown as { store?: { getState?: () => unknown } }).store?.getState?.();
|
||||
if (state && typeof state === 'object' && 'auth' in state) {
|
||||
const authState = (state as { auth?: { data?: { singer?: Singer } } }).auth;
|
||||
if (authState && authState.data && authState.data.singer) {
|
||||
singer = authState.data.singer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!singer) throw new Error('No singer specified');
|
||||
// Calculate order
|
||||
const state = (window as unknown as { store?: { getState?: () => unknown } }).store?.getState?.();
|
||||
let queueItems: Array<QueueItem & { key: string }> = [];
|
||||
if (state && typeof state === 'object' && 'queue' in state) {
|
||||
const queueState = (state as { queue?: { data?: Record<string, QueueItem> } }).queue;
|
||||
if (queueState && queueState.data && typeof queueState.data === 'object') {
|
||||
queueItems = Object.entries(queueState.data).map(([key, item]) => ({ ...item, key }));
|
||||
}
|
||||
}
|
||||
const maxOrder = queueItems.length > 0
|
||||
? Math.max(...queueItems.map(item => item.order || 0))
|
||||
: 0;
|
||||
const nextOrder = maxOrder + 1;
|
||||
const queueItem: Omit<QueueItem, 'key'> = {
|
||||
order: nextOrder,
|
||||
singer: {
|
||||
name: singer.name,
|
||||
lastLogin: singer.lastLogin || '',
|
||||
},
|
||||
song: song,
|
||||
};
|
||||
await dispatch(addToQueueThunk({ controllerName, queueItem })).unwrap();
|
||||
if (controllerName) {
|
||||
try {
|
||||
await historyService.addToHistory(controllerName, song);
|
||||
if (showSuccess) showSuccess('Song added to history');
|
||||
} catch {
|
||||
if (showError) showError('Failed to add song to history');
|
||||
}
|
||||
}
|
||||
if (showSuccess) showSuccess('Song added to queue');
|
||||
} catch {
|
||||
if (showError) showError('Failed to add song to queue');
|
||||
}
|
||||
}, [addToQueue, showSuccess, showError]);
|
||||
}, [addToQueue, showSuccess, showError, controllerName]);
|
||||
|
||||
// Utility to fix queue order after deletes
|
||||
const fixQueueOrder = useCallback(async () => {
|
||||
@ -69,13 +112,21 @@ export const useActions = () => {
|
||||
|
||||
try {
|
||||
await removeFromQueue(queueItem.key);
|
||||
if (controllerName && queueItem.song && queueItem.song.path) {
|
||||
try {
|
||||
await historyService.removeFromHistory(controllerName, queueItem.song.path);
|
||||
if (showSuccess) showSuccess('Song removed from history');
|
||||
} catch {
|
||||
if (showError) showError('Failed to remove song from history');
|
||||
}
|
||||
}
|
||||
if (showSuccess) showSuccess('Song removed from queue');
|
||||
// After removal, fix the order of all items
|
||||
await fixQueueOrder();
|
||||
} catch {
|
||||
if (showError) showError('Failed to remove song from queue');
|
||||
}
|
||||
}, [removeFromQueue, showSuccess, showError, fixQueueOrder]);
|
||||
}, [removeFromQueue, showSuccess, showError, fixQueueOrder, controllerName]);
|
||||
|
||||
const handleToggleFavorite = useCallback(async (song: Song) => {
|
||||
try {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user