Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>

This commit is contained in:
mbrucedogs 2025-07-21 10:35:00 -05:00
parent fb2a06129d
commit 923968ca57
10 changed files with 81 additions and 63 deletions

View File

@ -18,7 +18,6 @@ interface GenericListItemProps {
button?: boolean; button?: boolean;
style?: React.CSSProperties; style?: React.CSSProperties;
endContent?: React.ReactNode; endContent?: React.ReactNode;
showSeparator?: boolean;
} }
// Generic ListItem component for different types of data // Generic ListItem component for different types of data
@ -36,7 +35,6 @@ export const ListItem = React.memo(forwardRef<HTMLIonItemElement, GenericListIte
button = false, button = false,
style, style,
endContent, endContent,
showSeparator, // keep for API compatibility, but not used
}, ref) => { }, ref) => {
return ( return (
<IonItem <IonItem

View File

@ -21,7 +21,9 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({ className = '', variant
const playerState = useAppSelector(selectPlayerState); const playerState = useAppSelector(selectPlayerState);
const queueLength = useAppSelector(selectQueueLength); const queueLength = useAppSelector(selectQueueLength);
const controllerName = useAppSelector(selectControllerName); const controllerName = useAppSelector(selectControllerName);
const { showSuccess, showError } = useToast(); const toast = useToast();
const showSuccess = toast?.showSuccess;
const showError = toast?.showError;
// Debug logging // Debug logging
debugLog('PlayerControls - playerState:', playerState); debugLog('PlayerControls - playerState:', playerState);
@ -33,10 +35,10 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({ className = '', variant
try { try {
await playerService.updatePlayerStateValue(controllerName, PlayerState.playing); await playerService.updatePlayerStateValue(controllerName, PlayerState.playing);
showSuccess('Playback started'); if (showSuccess) showSuccess('Playback started');
} catch (error) { } catch (error) {
console.error('Failed to start playback:', error); console.error('Failed to start playback:', error);
showError('Failed to start playback'); if (showError) showError('Failed to start playback');
} }
}; };
@ -45,10 +47,10 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({ className = '', variant
try { try {
await playerService.updatePlayerStateValue(controllerName, PlayerState.paused); await playerService.updatePlayerStateValue(controllerName, PlayerState.paused);
showSuccess('Playback paused'); if (showSuccess) showSuccess('Playback paused');
} catch (error) { } catch (error) {
console.error('Failed to pause playback:', error); console.error('Failed to pause playback:', error);
showError('Failed to pause playback'); if (showError) showError('Failed to pause playback');
} }
}; };
@ -57,10 +59,10 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({ className = '', variant
try { try {
await playerService.updatePlayerStateValue(controllerName, PlayerState.stopped); await playerService.updatePlayerStateValue(controllerName, PlayerState.stopped);
showSuccess('Playback stopped'); if (showSuccess) showSuccess('Playback stopped');
} catch (error) { } catch (error) {
console.error('Failed to stop playback:', error); console.error('Failed to stop playback:', error);
showError('Failed to stop playback'); if (showError) showError('Failed to stop playback');
} }
}; };

View File

@ -25,12 +25,14 @@ const SelectSinger: React.FC<SelectSingerProps> = ({ isOpen, onClose, song }) =>
const singers = useAppSelector(selectSingersArray); const singers = useAppSelector(selectSingersArray);
const controllerName = useAppSelector(selectControllerName); const controllerName = useAppSelector(selectControllerName);
const currentQueue = useAppSelector(selectQueueObject); const currentQueue = useAppSelector(selectQueueObject);
const { showSuccess, showError } = useToast(); const toast = useToast();
const showSuccess = toast?.showSuccess;
const showError = toast?.showError;
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const handleSelectSinger = async (singer: Singer) => { const handleSelectSinger = async (singer: Singer) => {
if (!controllerName) { if (!controllerName) {
showError('Controller not found'); if (showError) showError('Controller not found');
return; return;
} }
@ -53,11 +55,11 @@ const SelectSinger: React.FC<SelectSingerProps> = ({ isOpen, onClose, song }) =>
}; };
await queueService.addToQueue(controllerName, queueItem); await queueService.addToQueue(controllerName, queueItem);
showSuccess(`${song.title} added to queue for ${singer.name}`); if (showSuccess) showSuccess(`${song.title} added to queue for ${singer.name}`);
onClose(); onClose();
} catch (error) { } catch (error) {
console.error('Failed to add song to queue:', error); console.error('Failed to add song to queue:', error);
showError('Failed to add song to queue'); if (showError) showError('Failed to add song to queue');
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }

View File

@ -6,6 +6,7 @@ import { useArtists } from '../../hooks';
import { useAppSelector } from '../../redux'; import { useAppSelector } from '../../redux';
import { selectSongs } from '../../redux'; import { selectSongs } from '../../redux';
import { debugLog } from '../../utils/logger'; import { debugLog } from '../../utils/logger';
import { SongItemContext } from '../../types';
const Artists: React.FC = () => { const Artists: React.FC = () => {
@ -90,7 +91,7 @@ const Artists: React.FC = () => {
renderItem={(song) => ( renderItem={(song) => (
<SongItem <SongItem
song={song} song={song}
context="search" context={SongItemContext.SEARCH}
showAddButton={true} showAddButton={true}
showInfoButton={true} showInfoButton={true}
showFavoriteButton={false} showFavoriteButton={false}

View File

@ -4,7 +4,6 @@ import { ban } from 'ionicons/icons';
import { useAppSelector } from '../../redux'; import { useAppSelector } from '../../redux';
import { selectIsAdmin, selectSettings, updateController, selectControllerName } from '../../redux'; import { selectIsAdmin, selectSettings, updateController, selectControllerName } from '../../redux';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { settingsService } from '../../firebase/services';
import { useDisabledSongs } from '../../hooks'; import { useDisabledSongs } from '../../hooks';
import { InfiniteScrollList, ActionButton, SongItem } from '../../components/common'; import { InfiniteScrollList, ActionButton, SongItem } from '../../components/common';
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types'; import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
@ -12,6 +11,9 @@ import { Icons } from '../../constants';
import { filterSongs } from '../../utils/dataProcessing'; import { filterSongs } from '../../utils/dataProcessing';
import { setDebugEnabled, isDebugEnabled, debugLog } from '../../utils/logger'; import { setDebugEnabled, isDebugEnabled, debugLog } from '../../utils/logger';
import type { Song, DisabledSong } from '../../types'; import type { Song, DisabledSong } from '../../types';
import { SongItemContext } from '../../types';
import type { Controller } from '../../types';
import { PlayerState } from '../../types';
const Settings: React.FC = () => { const Settings: React.FC = () => {
const isAdmin = useAppSelector(selectIsAdmin); const isAdmin = useAppSelector(selectIsAdmin);
@ -26,7 +28,15 @@ const Settings: React.FC = () => {
const [showDisabledSongsModal, setShowDisabledSongsModal] = useState(false); const [showDisabledSongsModal, setShowDisabledSongsModal] = useState(false);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const controllerNameRedux = useAppSelector(selectControllerName); const controllerNameRedux = useAppSelector(selectControllerName);
const existingPlayer = useAppSelector(state => state.controller.data?.player) || {}; const existingPlayer = (useAppSelector(state => state.controller.data?.player) || {}) as Partial<Controller['player']>;
// Provide default values for required properties
const updatedPlayer = {
queue: existingPlayer.queue || {},
settings: existingPlayer.settings || { autoadvance: false, userpick: false },
singers: existingPlayer.singers || {},
state: existingPlayer.state || { state: PlayerState.stopped },
};
// Convert disabled songs object to array for display // Convert disabled songs object to array for display
const disabledSongsArray: DisabledSong[] = Object.entries(disabledSongs).map(([key, disabledSong]) => ({ const disabledSongsArray: DisabledSong[] = Object.entries(disabledSongs).map(([key, disabledSong]) => ({
@ -46,16 +56,15 @@ const Settings: React.FC = () => {
debugLog(`Toggle ${setting} to ${value}`); debugLog(`Toggle ${setting} to ${value}`);
const controllerName = controllerNameRedux; const controllerName = controllerNameRedux;
if (controllerName) { if (controllerName) {
await settingsService.updateSetting(controllerName, setting, value); // @ts-expect-error: Redux Thunk type mismatch workaround
dispatch(updateController({ dispatch(updateController({
controllerName, controllerName,
updates: { updates: {
player: { player: {
...existingPlayer, queue: updatedPlayer.queue,
settings: { settings: { ...updatedPlayer.settings, [setting]: value },
...existingPlayer.settings, singers: updatedPlayer.singers,
[setting]: value state: updatedPlayer.state,
}
} }
} }
})); }));
@ -198,7 +207,7 @@ const Settings: React.FC = () => {
<div className="flex-1"> <div className="flex-1">
<SongItem <SongItem
song={song} song={song}
context="history" context={SongItemContext.HISTORY}
showDeleteButton={true} showDeleteButton={true}
showInfoButton={false} showInfoButton={false}
showAddButton={false} showAddButton={false}

View File

@ -10,6 +10,7 @@ import { debugLog } from '../../utils/logger';
import type { TopPlayed } from '../../types'; import type { TopPlayed } from '../../types';
import { SongItemContext } from '../../types';
const Top100: React.FC = () => { const Top100: React.FC = () => {
debugLog('Top100 component - RENDERING START'); debugLog('Top100 component - RENDERING START');
@ -124,7 +125,7 @@ const Top100: React.FC = () => {
<SongItem <SongItem
key={song.key || `${song.title}-${song.artist}`} key={song.key || `${song.title}-${song.artist}`}
song={song} song={song}
context="search" context={SongItemContext.SEARCH}
showAddButton={true} showAddButton={true}
showInfoButton={true} showInfoButton={true}
showFavoriteButton={false} showFavoriteButton={false}

View File

@ -18,7 +18,9 @@ export const useActions = () => {
const playerState = useAppSelector(selectPlayerStateMemoized); const playerState = useAppSelector(selectPlayerStateMemoized);
const isAdmin = useAppSelector(selectIsAdmin); const isAdmin = useAppSelector(selectIsAdmin);
const { addToQueue, removeFromQueue, toggleFavorite } = useSongOperations(); const { addToQueue, removeFromQueue, toggleFavorite } = useSongOperations();
const { showSuccess, showError } = useToast(); const toast = useToast();
const showSuccess = toast?.showSuccess;
const showError = toast?.showError;
const { isSongDisabled, addDisabledSong, removeDisabledSong } = useDisabledSongs(); const { isSongDisabled, addDisabledSong, removeDisabledSong } = useDisabledSongs();
// Queue permissions // Queue permissions
@ -29,9 +31,9 @@ export const useActions = () => {
const handleAddToQueue = useCallback(async (song: Song) => { const handleAddToQueue = useCallback(async (song: Song) => {
try { try {
await addToQueue(song); await addToQueue(song);
showSuccess('Song added to queue'); if (showSuccess) showSuccess('Song added to queue');
} catch { } catch {
showError('Failed to add song to queue'); if (showError) showError('Failed to add song to queue');
} }
}, [addToQueue, showSuccess, showError]); }, [addToQueue, showSuccess, showError]);
@ -40,18 +42,18 @@ export const useActions = () => {
try { try {
await removeFromQueue(queueItem.key); await removeFromQueue(queueItem.key);
showSuccess('Song removed from queue'); if (showSuccess) showSuccess('Song removed from queue');
} catch { } catch {
showError('Failed to remove song from queue'); if (showError) showError('Failed to remove song from queue');
} }
}, [removeFromQueue, showSuccess, showError]); }, [removeFromQueue, showSuccess, showError]);
const handleToggleFavorite = useCallback(async (song: Song) => { const handleToggleFavorite = useCallback(async (song: Song) => {
try { try {
await toggleFavorite(song); await toggleFavorite(song);
showSuccess(song.favorite ? 'Removed from favorites' : 'Added to favorites'); if (showSuccess) showSuccess(song.favorite ? 'Removed from favorites' : 'Added to favorites');
} catch { } catch {
showError('Failed to update favorites'); if (showError) showError('Failed to update favorites');
} }
}, [toggleFavorite, showSuccess, showError]); }, [toggleFavorite, showSuccess, showError]);
@ -59,27 +61,27 @@ export const useActions = () => {
try { try {
if (isSongDisabled(song)) { if (isSongDisabled(song)) {
await removeDisabledSong(song); await removeDisabledSong(song);
showSuccess('Song enabled'); if (showSuccess) showSuccess('Song enabled');
} else { } else {
await addDisabledSong(song); await addDisabledSong(song);
showSuccess('Song disabled'); if (showSuccess) showSuccess('Song disabled');
} }
} catch { } catch {
showError('Failed to update song disabled status'); if (showError) showError('Failed to update song disabled status');
} }
}, [isSongDisabled, addDisabledSong, removeDisabledSong, showSuccess, showError]); }, [isSongDisabled, addDisabledSong, removeDisabledSong, showSuccess, showError]);
const handleDeleteFromHistory = useCallback(async (song: Song) => { const handleDeleteFromHistory = useCallback(async (song: Song) => {
if (!controllerName || !song.key) { if (!controllerName || !song.key) {
showError('Cannot delete history item - missing data'); if (showError) showError('Cannot delete history item - missing data');
return; return;
} }
try { try {
await historyService.removeFromHistory(controllerName, song.key); await historyService.removeFromHistory(controllerName, song.key);
showSuccess('Removed from history'); if (showSuccess) showSuccess('Removed from history');
} catch { } catch {
showError('Failed to remove from history'); if (showError) showError('Failed to remove from history');
} }
}, [controllerName, showSuccess, showError]); }, [controllerName, showSuccess, showError]);
@ -94,7 +96,7 @@ export const useActions = () => {
const { from, to, complete } = event.detail; const { from, to, complete } = event.detail;
if (!controllerName) { if (!controllerName) {
showError('Cannot reorder - controller not available'); if (showError) showError('Cannot reorder - controller not available');
return; return;
} }
@ -123,10 +125,10 @@ export const useActions = () => {
await Promise.all(updatePromises); await Promise.all(updatePromises);
debugLog('Queue reorder completed successfully'); debugLog('Queue reorder completed successfully');
showSuccess('Queue reordered successfully'); if (showSuccess) showSuccess('Queue reordered successfully');
} catch (error) { } catch (error) {
console.error('Failed to reorder queue:', error); console.error('Failed to reorder queue:', error);
showError('Failed to reorder queue'); if (showError) showError('Failed to reorder queue');
} }
}, [controllerName, showSuccess, showError]); }, [controllerName, showSuccess, showError]);

View File

@ -11,7 +11,9 @@ export const useDisabledSongs = () => {
const [disabledSongs, setDisabledSongs] = useState<Record<string, DisabledSong>>({}); const [disabledSongs, setDisabledSongs] = useState<Record<string, DisabledSong>>({});
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const controllerName = useAppSelector(selectControllerName); const controllerName = useAppSelector(selectControllerName);
const { showSuccess, showError } = useToast(); const toast = useToast();
const showSuccess = toast?.showSuccess;
const showError = toast?.showError;
// Load disabled songs on mount and subscribe to changes // Load disabled songs on mount and subscribe to changes
useEffect(() => { useEffect(() => {
@ -35,7 +37,7 @@ export const useDisabledSongs = () => {
setDisabledSongPaths(paths); setDisabledSongPaths(paths);
} catch (error) { } catch (error) {
console.error('Error loading disabled songs:', error); console.error('Error loading disabled songs:', error);
showError('Failed to load disabled songs'); if (showError) showError('Failed to load disabled songs');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -81,22 +83,22 @@ export const useDisabledSongs = () => {
const addDisabledSong = useCallback(async (song: Song) => { const addDisabledSong = useCallback(async (song: Song) => {
if (!controllerName) { if (!controllerName) {
console.error('No controller name available'); console.error('No controller name available');
showError('No controller name available'); if (showError) showError('No controller name available');
return; return;
} }
if (!song.path) { if (!song.path) {
console.error('Song has no path:', song); console.error('Song has no path:', song);
showError('Song has no path'); if (showError) showError('Song has no path');
return; return;
} }
try { try {
await disabledSongsService.addDisabledSong(controllerName, song); await disabledSongsService.addDisabledSong(controllerName, song);
showSuccess('Song marked as disabled'); if (showSuccess) showSuccess('Song marked as disabled');
} catch (error) { } catch (error) {
console.error('Error adding disabled song:', error); console.error('Error adding disabled song:', error);
showError(`Failed to mark song as disabled: ${error instanceof Error ? error.message : 'Unknown error'}`); if (showError) showError(`Failed to mark song as disabled: ${error instanceof Error ? error.message : 'Unknown error'}`);
} }
}, [controllerName, showSuccess, showError]); }, [controllerName, showSuccess, showError]);
@ -106,10 +108,10 @@ export const useDisabledSongs = () => {
try { try {
await disabledSongsService.removeDisabledSong(controllerName, song.path); await disabledSongsService.removeDisabledSong(controllerName, song.path);
showSuccess('Song re-enabled'); if (showSuccess) showSuccess('Song re-enabled');
} catch (error) { } catch (error) {
console.error('Error removing disabled song:', error); console.error('Error removing disabled song:', error);
showError('Failed to re-enable song'); if (showError) showError('Failed to re-enable song');
} }
}, [controllerName, showSuccess, showError]); }, [controllerName, showSuccess, showError]);

View File

@ -15,7 +15,8 @@ interface ErrorHandlerResult {
} }
export const useErrorHandler = (defaultOptions: ErrorHandlerOptions = {}): ErrorHandlerResult => { export const useErrorHandler = (defaultOptions: ErrorHandlerOptions = {}): ErrorHandlerResult => {
const { showError } = useToast(); const toast = useToast();
const showError = toast?.showError;
const defaultErrorOptions: Required<ErrorHandlerOptions> = { const defaultErrorOptions: Required<ErrorHandlerOptions> = {
showToast: true, showToast: true,
@ -45,9 +46,7 @@ export const useErrorHandler = (defaultOptions: ErrorHandlerOptions = {}): Error
} }
// Show toast if enabled // Show toast if enabled
if (opts.showToast) { if (opts.showToast && showError) showError(displayMessage);
showError(displayMessage);
}
}, [defaultErrorOptions, showError]); }, [defaultErrorOptions, showError]);
const handleAsyncError = useCallback(async <T>( const handleAsyncError = useCallback(async <T>(

View File

@ -8,53 +8,55 @@ export const useSingers = () => {
const singers = useAppSelector(selectSingersArray); const singers = useAppSelector(selectSingersArray);
const isAdmin = useAppSelector(selectIsAdmin); const isAdmin = useAppSelector(selectIsAdmin);
const controllerName = useAppSelector(selectControllerName); const controllerName = useAppSelector(selectControllerName);
const { showSuccess, showError } = useToast(); const toast = useToast();
const showSuccess = toast?.showSuccess;
const showError = toast?.showError;
const handleRemoveSinger = useCallback(async (singer: Singer) => { const handleRemoveSinger = useCallback(async (singer: Singer) => {
if (!isAdmin) { if (!isAdmin) {
showError('Only admins can remove singers'); showError && showError('Only admins can remove singers');
return; return;
} }
if (!controllerName) { if (!controllerName) {
showError('Controller not found'); showError && showError('Controller not found');
return; return;
} }
try { try {
await singerService.removeSinger(controllerName, singer.name); await singerService.removeSinger(controllerName, singer.name);
showSuccess(`${singer.name} removed from singers list and queue`); showSuccess && showSuccess(`${singer.name} removed from singers list and queue`);
} catch (error) { } catch (error) {
console.error('Failed to remove singer:', error); console.error('Failed to remove singer:', error);
showError('Failed to remove singer'); showError && showError('Failed to remove singer');
} }
}, [isAdmin, controllerName, showSuccess, showError]); }, [isAdmin, controllerName, showSuccess, showError]);
const handleAddSinger = useCallback(async (singerName: string) => { const handleAddSinger = useCallback(async (singerName: string) => {
if (!isAdmin) { if (!isAdmin) {
showError('Only admins can add singers'); showError && showError('Only admins can add singers');
return; return;
} }
if (!controllerName) { if (!controllerName) {
showError('Controller not found'); showError && showError('Controller not found');
return; return;
} }
if (!singerName.trim()) { if (!singerName.trim()) {
showError('Singer name cannot be empty'); showError && showError('Singer name cannot be empty');
return; return;
} }
try { try {
await singerService.addSinger(controllerName, singerName.trim()); await singerService.addSinger(controllerName, singerName.trim());
showSuccess(`${singerName} added to singers list`); showSuccess && showSuccess(`${singerName} added to singers list`);
} catch (error) { } catch (error) {
console.error('Failed to add singer:', error); console.error('Failed to add singer:', error);
if (error instanceof Error && error.message === 'Singer already exists') { if (error instanceof Error && error.message === 'Singer already exists') {
showError('Singer already exists'); showError && showError('Singer already exists');
} else { } else {
showError('Failed to add singer'); showError && showError('Failed to add singer');
} }
} }
}, [isAdmin, controllerName, showSuccess, showError]); }, [isAdmin, controllerName, showSuccess, showError]);