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,