Просмотр исходного кода

feat: add entire artist or album to queue at once

Fela Maslen 5 лет назад
Родитель
Сommit
304a3bb648

+ 3 - 3
gmus-web/src/actions/actions.ts

@@ -62,10 +62,10 @@ export const songInfoFetched = (song: Song | null, replace = false): ActionSongI
   payload: { song, replace },
 });
 
-export type ActionQueuePushed = ActionLocal<ActionTypeLocal.QueuePushed, number>;
-export const queuePushed = (songId: number): ActionQueuePushed => ({
+export type ActionQueuePushed = ActionLocal<ActionTypeLocal.QueuePushed, number[]>;
+export const queuePushed = (songIds: number[]): ActionQueuePushed => ({
   type: ActionTypeLocal.QueuePushed,
-  payload: songId,
+  payload: songIds,
 });
 
 export type ActionQueueShifted = ActionLocal<ActionTypeLocal.QueueShifted, void>;

+ 3 - 0
gmus-web/src/components/ui/cmus/reducer/fixtures.ts

@@ -28,6 +28,9 @@ export const stateFromMode = (fromModeWindow: LibraryModeWindow): CmusUIState =>
 
 export const stateWithActiveArtist: CmusUIState = {
   ...initialCmusUIState,
+  artistSongs: {
+    'My artist': [{ id: 184, album: 'Album 1' } as Song, { id: 37, album: 'Album 2' } as Song],
+  },
   library: {
     ...initialCmusUIState.library,
     activeArtist: 'My artist',

+ 10 - 1
gmus-web/src/components/ui/cmus/reducer/keypress.spec.ts

@@ -8,6 +8,7 @@ import {
   stateFromMode,
   stateLibrary,
   stateQueue,
+  stateWithActiveArtist,
   stateWithActiveSong,
 } from './fixtures';
 import { cmusUIReducer, initialCmusUIState } from './reducer';
@@ -77,11 +78,19 @@ describe(ActionTypeKeyPressed, () => {
     const action: ActionKeyPressed = { type: ActionTypeKeyPressed, key: Keys.E };
 
     describe('when in library view', () => {
+      describe('when in artist list mode', () => {
+        it("should add all the active artist's songs to the queue", () => {
+          expect.assertions(1);
+          const result = cmusUIReducer(stateWithActiveArtist, action);
+          expect(result.globalAction).toStrictEqual(queuePushed([184, 37]));
+        });
+      });
+
       describe('when in songs list mode', () => {
         it('should set global action to add the selected song to the queue', () => {
           expect.assertions(1);
           const result = cmusUIReducer(stateWithActiveSong, action);
-          expect(result.globalAction).toStrictEqual(queuePushed(1867));
+          expect(result.globalAction).toStrictEqual(queuePushed([1867]));
         });
       });
     });

+ 19 - 8
gmus-web/src/components/ui/cmus/reducer/keypress.ts

@@ -1,5 +1,6 @@
 import { masterSet, playPaused, queuePushed, queueRemoved, stateSet } from '../../../../actions';
 import { ActionKeyPressed, Keys } from '../../../../hooks/vim';
+import { getFilteredSongs } from '../selectors';
 import { CmusUIState, LibraryModeWindow, Overlay, View } from '../types';
 import { handleOrder } from './order';
 import { handleScroll } from './scroll';
@@ -74,6 +75,23 @@ function handleActivate(state: CmusUIState): CmusUIState {
   }
 }
 
+function addSelectedToQueue(state: CmusUIState): CmusUIState {
+  if (state.view !== View.Library) {
+    return state;
+  }
+  switch (state.library.modeWindow) {
+    case LibraryModeWindow.ArtistList:
+      return withGlobalAction(state, queuePushed(getFilteredSongs(state).map(({ id }) => id)));
+    case LibraryModeWindow.SongList:
+      if (!state.library.activeSongId) {
+        return state;
+      }
+      return withGlobalAction(state, queuePushed([state.library.activeSongId]));
+    default:
+      return state;
+  }
+}
+
 export function handleKeyPress(state: CmusUIState, action: ActionKeyPressed): CmusUIState {
   switch (action.key) {
     case Keys.colon:
@@ -128,14 +146,7 @@ export function handleKeyPress(state: CmusUIState, action: ActionKeyPressed): Cm
       return state;
 
     case Keys.E:
-      if (
-        state.view === View.Library &&
-        state.library.modeWindow === LibraryModeWindow.SongList &&
-        state.library.activeSongId
-      ) {
-        return withGlobalAction(state, queuePushed(state.library.activeSongId));
-      }
-      return state;
+      return addSelectedToQueue(state);
 
     case Keys.J:
       return handleScroll(state, 1);

+ 4 - 4
gmus-web/src/effects/effects.spec.ts

@@ -248,7 +248,7 @@ describe(globalEffects.name, () => {
   });
 
   describe(ActionTypeLocal.QueuePushed, () => {
-    const action = queuePushed(184);
+    const action = queuePushed([184, 79]);
 
     it('should add to the end of the queue', () => {
       expect.assertions(1);
@@ -264,18 +264,18 @@ describe(globalEffects.name, () => {
         payload: {
           ...initialState.player,
           master: 'some-master',
-          queue: [23, 184],
+          queue: [23, 184, 79],
         },
       });
     });
 
-    describe('when the song is already in the queue', () => {
+    describe('when the songs are already in the queue', () => {
       it('should not modify the queue', () => {
         expect.assertions(1);
         const result = globalEffects(
           {
             ...initialState,
-            player: { ...initialState.player, queue: [184, 23] },
+            player: { ...initialState.player, queue: [184, 23, 79] },
           },
           action,
         );

+ 16 - 10
gmus-web/src/effects/effects.ts

@@ -1,5 +1,6 @@
 import {
   ActionQueueOrdered,
+  ActionQueuePushed,
   ActionTypeLocal,
   ActionTypeRemote,
   LocalAction,
@@ -24,6 +25,20 @@ function reorderQueue(queue: number[], action: ActionQueueOrdered): number[] {
   return reverseInArray(queue, reverseIndex);
 }
 
+function pushToQueue(state: GlobalState, action: ActionQueuePushed): RemoteAction | null {
+  const nextQueue = Array.from(new Set([...state.player.queue, ...action.payload]));
+  if (!state.player.master || nextQueue.length === state.player.queue.length) {
+    return null;
+  }
+  return {
+    type: ActionTypeRemote.StateSet,
+    payload: {
+      ...state.player,
+      queue: nextQueue,
+    },
+  };
+}
+
 export function globalEffects(prevState: GlobalState, action: LocalAction): RemoteAction | null {
   switch (action.type) {
     case ActionTypeLocal.StateSet:
@@ -94,16 +109,7 @@ export function globalEffects(prevState: GlobalState, action: LocalAction): Remo
       };
 
     case ActionTypeLocal.QueuePushed:
-      if (!prevState.player.master || prevState.player.queue.includes(action.payload)) {
-        return null;
-      }
-      return {
-        type: ActionTypeRemote.StateSet,
-        payload: {
-          ...prevState.player,
-          queue: [...prevState.player.queue, action.payload],
-        },
-      };
+      return pushToQueue(prevState, action);
     case ActionTypeLocal.QueueShifted:
       if (!prevState.player.master) {
         return null;