Skip to content

Commit

Permalink
Merge pull request #1568 from sferra/feature/add-album-to-playlist
Browse files Browse the repository at this point in the history
Enable adding albums to playlists
  • Loading branch information
nukeop committed Mar 18, 2024
2 parents 80a7b1c + f24ddc7 commit ec5a6c9
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 7 deletions.
54 changes: 50 additions & 4 deletions packages/app/app/components/AlbumView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import React, { useState } from 'react';
import Img from 'react-image';
import _ from 'lodash';
import { Dimmer, Icon, Loader } from 'semantic-ui-react';
import { Dimmer, Icon, Loader, Dropdown } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
import { Loader as NuclearLoader, ContextPopup, PopupButton } from '@nuclear/ui';
import { Loader as NuclearLoader, ContextPopup, PopupButton, PopupDropdown, InputDialog } from '@nuclear/ui';
import styles from './styles.scss';
import artPlaceholder from '../../../resources/media/art_placeholder.png';
import TrackTableContainer from '../../containers/TrackTableContainer';
Expand All @@ -18,6 +18,9 @@ type AlbumViewProps = {
playAll: React.MouseEventHandler;
removeFavoriteAlbum: React.MouseEventHandler;
addFavoriteAlbum: React.MouseEventHandler;
addAlbumToPlaylist: (playlistName: string) => void;
playlistNames: string[];
addAlbumToNewPlaylist?: (playlistName: string) => void;
}

export const AlbumView: React.FC<AlbumViewProps> = ({
Expand All @@ -28,9 +31,16 @@ export const AlbumView: React.FC<AlbumViewProps> = ({
addAlbumToQueue,
playAll,
removeFavoriteAlbum,
addFavoriteAlbum
addFavoriteAlbum,
addAlbumToPlaylist,
playlistNames,
addAlbumToNewPlaylist
}) => {
const { t } = useTranslation('album');
const [isCreatePlaylistDialogOpen, setIsCreatePlaylistDialogOpen] = useState(false);
const displayPlaylistCreationDialog = () => setIsCreatePlaylistDialogOpen(true);
const hidePlaylistCreationDialog = () => setIsCreatePlaylistDialogOpen(false);

const release_date: Date = new Date(album.year);
return <div className={styles.album_view_container}>
<Dimmer.Dimmable>
Expand Down Expand Up @@ -124,7 +134,43 @@ export const AlbumView: React.FC<AlbumViewProps> = ({
icon='download'
label={t('download')}
/>
<PopupDropdown
text={t('add-to-playlist')}
data-testid='add-album-to-playlist'
>
{
playlistNames?.map((playlistName, i) => (
<Dropdown.Item
key={i}
onClick={() => addAlbumToPlaylist(playlistName)}
>
<Icon name='music'/>
{playlistName}
</Dropdown.Item>
))
}
<Dropdown.Item
onClick={() => displayPlaylistCreationDialog()}
data-testid='playlist-popup-create-playlist'
>
<Icon name='plus'/>
{t('create-playlist')}
</Dropdown.Item>
</PopupDropdown>
</ContextPopup>
<InputDialog
isOpen={isCreatePlaylistDialogOpen}
onClose={() => hidePlaylistCreationDialog()}
header={<h4>{t('create-playlist-dialog-title')}</h4>}
placeholder={t('create-playlist-dialog-placeholder')}
acceptLabel={t('create-playlist-dialog-accept')}
cancelLabel={t('create-playlist-dialog-cancel')}
onAccept={(input) => {
addAlbumToNewPlaylist(input);
}}
initialString={`${album.artist} - ${album.title}`}
testIdPrefix='create-playlist-dialog'
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { waitFor } from '@testing-library/react';
import { mountedComponentFactory, setupI18Next } from '../../../test/testUtils';
import { buildStoreState } from '../../../test/storeBuilders';
import PlayerBarContainer from '../PlayerBarContainer';
import userEvent from '@testing-library/user-event';

describe('Album view container', () => {
beforeAll(() => {
Expand Down Expand Up @@ -256,6 +257,53 @@ describe('Album view container', () => {
]);
});

it('should add album tracks to an existing playlist using the album menu popup', async () => {
const { component, store } = mountComponent(
buildStoreState()
.withAlbumDetails()
.withPlaylists([{
id: 'test playlist id',
name: 'test playlist',
tracks: []
}])
.build()
);
userEvent.click(component.getByTestId('more-button'));
userEvent.click(component.getByTestId('add-album-to-playlist'));
userEvent.click(component.getByText('test playlist'));

const state = store.getState();
expect(state.playlists.localPlaylists.data[0].tracks).toEqual([
expect.objectContaining({ uuid: 'track-1-id' }),
expect.objectContaining({ uuid: 'track-2-id' }),
expect.objectContaining({ uuid: 'track-3-id' })
]);
});

it('should add album tracks to a new playlist using the album menu popup', async () => {
const { component, store } = mountComponent();
userEvent.click(component.getByTestId('more-button'));
userEvent.click(component.getByTestId('add-album-to-playlist'));
userEvent.click(component.getByTestId('playlist-popup-create-playlist'));

expect(component.getByTestId('create-playlist-dialog-input')).toBeVisible();
expect(component.getByTestId('create-playlist-dialog-accept')).toBeVisible();
expect(component.getByTestId('create-playlist-dialog-cancel')).toBeVisible();

userEvent.click(component.getByTestId('create-playlist-dialog-accept'));

const state = store.getState();
expect(state.playlists.localPlaylists.data).toHaveLength(1);

const firstPlaylist = state.playlists.localPlaylists.data[0];
expect(firstPlaylist.name).toBe('test artist - test album');
expect(firstPlaylist.tracks).toEqual([
expect.objectContaining({ uuid: 'track-1-id' }),
expect.objectContaining({ uuid: 'track-2-id' }),
expect.objectContaining({ uuid: 'track-3-id' })
]);
});

const mountComponent = mountedComponentFactory(
['/album/test-album-id'],
buildStoreState()
Expand Down
35 changes: 34 additions & 1 deletion packages/app/app/containers/AlbumViewContainer/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { pluginsSelectors } from '../../selectors/plugins';
import { searchSelectors } from '../../selectors/search';
import { stringDurationToSeconds } from '../../utils';
import { AlbumDetailsState } from '../../reducers/search';
import { playlistsSelectors } from '../../selectors/playlists';
import * as PlaylistActions from '../../actions/playlists';
import { PlaylistTrack, Track } from '@nuclear/core';

export const useAlbumViewProps = () => {
const dispatch = useDispatch();
Expand All @@ -24,6 +27,7 @@ export const useAlbumViewProps = () => {
// TODO replace this any with a proper type
const plugins: any = useSelector(pluginsSelectors.plugins);
const favoriteAlbums = useSelector(favoritesSelectors.albums);
const localPlaylists = useSelector(playlistsSelectors.localPlaylists);

const albumFromFavorites = favoriteAlbums.find(album => album.id === albumId);
const album = albumFromFavorites || albumDetails[albumId];
Expand Down Expand Up @@ -89,6 +93,32 @@ export const useAlbumViewProps = () => {
dispatch(FavoritesActions.removeFavoriteAlbum(album));
}, [album, dispatch]);

const playlistNames = localPlaylists.data?.map(playlist => playlist.name);

function getPlaylistTracks() {
const tracksWithId: PlaylistTrack[] = [];
album.tracklist?.forEach((track: Track) => tracksWithId.push(safeAddUuid(track)));
return tracksWithId;
}

const addAlbumToPlaylist = useCallback(async (playlistName: string) => {
const tracksWithId: PlaylistTrack[] = getPlaylistTracks();
const originalPlaylist = localPlaylists.data?.find(playlist => playlist.name === playlistName);
const playlistWithAlbumTracks = {
...originalPlaylist,
tracks: [
...originalPlaylist.tracks,
...tracksWithId
]
};
dispatch(PlaylistActions.updatePlaylist(playlistWithAlbumTracks));
}, [album, localPlaylists, dispatch]);

const addAlbumToNewPlaylist = useCallback(async (playlistName: string) => {
dispatch(PlaylistActions.addPlaylist(getPlaylistTracks(), playlistName));
}, [album, dispatch]);


return {
album,
isFavorite,
Expand All @@ -97,6 +127,9 @@ export const useAlbumViewProps = () => {
addAlbumToQueue,
playAll,
addFavoriteAlbum,
removeFavoriteAlbum
removeFavoriteAlbum,
addAlbumToPlaylist,
playlistNames,
addAlbumToNewPlaylist
};
};
10 changes: 8 additions & 2 deletions packages/i18n/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
"play": "Play",
"queue": "Add album to queue",
"tracks": "Tracks:",
"year": "Year:"
"year": "Year:",
"add-to-playlist": "Add album to playlist",
"create-playlist": "Create new playlist",
"create-playlist-dialog-title": "Input playlist name:",
"create-playlist-dialog-placeholder": "Playlist name...",
"create-playlist-dialog-accept": "Save",
"create-playlist-dialog-cancel": "Cancel"
},
"app": {
"collection": "Collection",
Expand Down Expand Up @@ -462,4 +468,4 @@
"sign-in-button": "Sign in"
}
}
}
}

0 comments on commit ec5a6c9

Please sign in to comment.