diff --git a/src/components/shared/ContextMenu.tsx b/src/components/shared/ContextMenu.tsx index 16e52f8..42122e9 100644 --- a/src/components/shared/ContextMenu.tsx +++ b/src/components/shared/ContextMenu.tsx @@ -1,5 +1,6 @@ /* eslint-disable no-await-in-loop */ import React, { useRef, useState } from 'react'; +import _ from 'lodash'; import { useQuery, useQueryClient } from 'react-query'; import { useHistory } from 'react-router'; import { Popover, Whisper } from 'rsuite'; @@ -10,7 +11,12 @@ import { removeProcessingPlaylist, setContextMenu, } from '../../redux/miscSlice'; -import { setStar } from '../../redux/playQueueSlice'; +import { + appendPlayQueue, + fixPlayer2Index, + removeFromPlayQueue, + setStar, +} from '../../redux/playQueueSlice'; import { ContextMenuDivider, ContextMenuWindow, @@ -20,11 +26,13 @@ import { } from './styled'; import { notifyToast } from './toast'; import { sleep } from '../../shared/utils'; +import { setStatus } from '../../redux/playerSlice'; -export const ContextMenuButton = ({ children, ...rest }: any) => { +export const ContextMenuButton = ({ text, children, ...rest }: any) => { return ( {children} + {text} ); }; @@ -56,6 +64,7 @@ export const GlobalContextMenu = () => { const history = useHistory(); const dispatch = useAppDispatch(); const queryClient = useQueryClient(); + const playQueue = useAppSelector((state) => state.playQueue); const misc = useAppSelector((state) => state.misc); const multiSelect = useAppSelector((state) => state.multiSelect); const playlistTriggerRef = useRef(); @@ -65,6 +74,40 @@ export const GlobalContextMenu = () => { getPlaylists('name') ); + const handleAddToQueue = () => { + const entriesByRowIndexAsc = _.orderBy( + multiSelect.selected, + 'rowIndex', + 'asc' + ); + + notifyToast( + 'info', + `Added ${multiSelect.selected.length} song(s) to the queue` + ); + + dispatch(appendPlayQueue({ entries: entriesByRowIndexAsc })); + dispatch(setContextMenu({ show: false })); + }; + + const handleRemoveFromQueue = async () => { + dispatch(removeFromPlayQueue({ entries: multiSelect.selected })); + if (playQueue.currentPlayer === 1) { + dispatch(fixPlayer2Index()); + } + dispatch(setContextMenu({ show: false })); + + // If the currently playing song is removed, then the player will autostart because + // the player src changed. If that happens, automatically set the playing status + if ( + _.map(multiSelect.selected, 'uniqueId').includes( + playQueue.current.uniqueId + ) + ) { + setTimeout(() => dispatch(setStatus('PLAYING')), 50); + } + }; + const handleAddToPlaylist = async () => { // If the window is closed, the selectedPlaylistId will be deleted const localSelectedPlaylistId = selectedPlaylistId; @@ -173,12 +216,15 @@ export const GlobalContextMenu = () => { numOfButtons={7} numOfDividers={3} > - - Selected: {multiSelect.selected.length} - + - Add to queue - Remove from current + + { } > playlistTriggerRef.current.state.isOverlayShown ? playlistTriggerRef.current.close() : playlistTriggerRef.current.open() } - > - Add to playlist - + /> - - handleFavorite(false)}> - Add to favorites - - handleFavorite(true)}> - Add to favorites (ordered) - - - Remove from favorites - + handleFavorite(false)} + /> + handleFavorite(true)} + /> + )} diff --git a/src/redux/playQueueSlice.ts b/src/redux/playQueueSlice.ts index 6f04473..9a6054a 100644 --- a/src/redux/playQueueSlice.ts +++ b/src/redux/playQueueSlice.ts @@ -774,13 +774,67 @@ const playQueueSlice = createSlice({ }, appendPlayQueue: (state, action: PayloadAction<{ entries: Entry[] }>) => { - action.payload.entries.map((entry: any) => state.entry.push(entry)); + // We'll need to update the uniqueId otherwise selecting a song with duplicates + // will select them all at once + const refreshedEntries = action.payload.entries.map((entry: any) => { + return { + ...entry, + uniqueId: nanoid(), + }; + }); + + refreshedEntries.map((entry: any) => state.entry.push(entry)); + if (state.shuffle) { - const shuffledEntries = _.shuffle(action.payload.entries); + const shuffledEntries = _.shuffle(refreshedEntries); shuffledEntries.map((entry: any) => state.shuffledEntry.push(entry)); } }, + removeFromPlayQueue: ( + state, + action: PayloadAction<{ entries: Entry[] }> + ) => { + const uniqueIds = _.map(action.payload.entries, 'uniqueId'); + + state.entry = state.entry.filter( + (entry) => !uniqueIds.includes(entry.uniqueId) + ); + + state.shuffledEntry = (state.shuffledEntry || []).filter( + (entry) => !uniqueIds.includes(entry.uniqueId) + ); + + state.sortedEntry = (state.sortedEntry || []).filter( + (entry) => !uniqueIds.includes(entry.uniqueId) + ); + + // If the current song is removed, then reset to the first entry + if (uniqueIds.includes(state.currentSongUniqueId)) { + if (state.sortColumn) { + state.current = { ...state.sortedEntry[0] }; + state.currentSongId = state.sortedEntry[0].id; + state.currentSongUniqueId = state.sortedEntry[0].uniqueId; + } else if (state.shuffle) { + state.current = { ...state.shuffledEntry[0] }; + state.currentSongId = state.shuffledEntry[0].id; + state.currentSongUniqueId = state.shuffledEntry[0].uniqueId; + } else { + state.current = { ...state.entry[0] }; + state.currentSongId = state.entry[0].id; + state.currentSongUniqueId = state.entry[0].uniqueId; + } + + if (state.currentPlayer === 1) { + state.player1.index = 0; + } else { + state.player2.index = 0; + } + + state.currentIndex = 0; + } + }, + clearPlayQueue: (state) => { state.entry = []; state.shuffledEntry = []; @@ -1006,6 +1060,7 @@ export const { setPlayQueue, setPlayQueueByRowClick, appendPlayQueue, + removeFromPlayQueue, clearPlayQueue, setIsLoading, setIsLoaded,