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

View File

@ -21,7 +21,9 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({ className = '', variant
const playerState = useAppSelector(selectPlayerState);
const queueLength = useAppSelector(selectQueueLength);
const controllerName = useAppSelector(selectControllerName);
const { showSuccess, showError } = useToast();
const toast = useToast();
const showSuccess = toast?.showSuccess;
const showError = toast?.showError;
// Debug logging
debugLog('PlayerControls - playerState:', playerState);
@ -33,10 +35,10 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({ className = '', variant
try {
await playerService.updatePlayerStateValue(controllerName, PlayerState.playing);
showSuccess('Playback started');
if (showSuccess) showSuccess('Playback started');
} catch (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 {
await playerService.updatePlayerStateValue(controllerName, PlayerState.paused);
showSuccess('Playback paused');
if (showSuccess) showSuccess('Playback paused');
} catch (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 {
await playerService.updatePlayerStateValue(controllerName, PlayerState.stopped);
showSuccess('Playback stopped');
if (showSuccess) showSuccess('Playback stopped');
} catch (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 controllerName = useAppSelector(selectControllerName);
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 handleSelectSinger = async (singer: Singer) => {
if (!controllerName) {
showError('Controller not found');
if (showError) showError('Controller not found');
return;
}
@ -53,11 +55,11 @@ const SelectSinger: React.FC<SelectSingerProps> = ({ isOpen, onClose, song }) =>
};
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();
} catch (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 {
setIsLoading(false);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,9 @@ export const useDisabledSongs = () => {
const [disabledSongs, setDisabledSongs] = useState<Record<string, DisabledSong>>({});
const [loading, setLoading] = useState(true);
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
useEffect(() => {
@ -35,7 +37,7 @@ export const useDisabledSongs = () => {
setDisabledSongPaths(paths);
} catch (error) {
console.error('Error loading disabled songs:', error);
showError('Failed to load disabled songs');
if (showError) showError('Failed to load disabled songs');
} finally {
setLoading(false);
}
@ -81,22 +83,22 @@ export const useDisabledSongs = () => {
const addDisabledSong = useCallback(async (song: Song) => {
if (!controllerName) {
console.error('No controller name available');
showError('No controller name available');
if (showError) showError('No controller name available');
return;
}
if (!song.path) {
console.error('Song has no path:', song);
showError('Song has no path');
if (showError) showError('Song has no path');
return;
}
try {
await disabledSongsService.addDisabledSong(controllerName, song);
showSuccess('Song marked as disabled');
if (showSuccess) showSuccess('Song marked as disabled');
} catch (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]);
@ -106,10 +108,10 @@ export const useDisabledSongs = () => {
try {
await disabledSongsService.removeDisabledSong(controllerName, song.path);
showSuccess('Song re-enabled');
if (showSuccess) showSuccess('Song re-enabled');
} catch (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]);

View File

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

View File

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