Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create new playlist button on Playlists screen #1029

Merged
merged 1 commit into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/app/__mocks__/@nuclear/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ module.exports = {
NUMBER: 'number',
STRING: 'string',
DIRECTORY: 'directory'
}
},
PlaylistHelper: jest.requireActual('@nuclear/core/src/helpers').PlaylistHelper
};
12 changes: 4 additions & 8 deletions packages/app/app/actions/playlists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,13 @@ export const ADD_PLAYLIST = 'ADD_PLAYLIST';
export const DELETE_PLAYLIST = 'DELETE_PLAYLIST';
export const UPDATE_PLAYLIST = 'UPDATE_PLAYLIST';

export function addPlaylist(tracks, name) {
export function addPlaylist(tracks: Array<any>, name: string) {
return dispatch => {
let playlists = store.get('playlists') || [];
const playlist = PlaylistHelper.formatPlaylistForStorage(name, tracks, v4());

if (_.isEmpty(tracks)) {
dispatch({
type: null
});
if (name?.length === 0) {
return;
}
let playlists = store.get('playlists') || [];
const playlist = PlaylistHelper.formatPlaylistForStorage(name, tracks, v4());

playlists = [...playlists, playlist];

Expand Down
12 changes: 9 additions & 3 deletions packages/app/app/components/InputDialog/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useCallback, useEffect } from 'react';
import { Button, Input, Modal } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';

const InputDialog = ({ initialString, trigger, header, placeholder, accept, onAccept }) => {
const InputDialog = ({ initialString, trigger, header, placeholder, accept, onAccept, testIdPrefix = null }) => {
const [isOpen, setIsOpen] = useState(false);
const [inputString, setInputString] = useState(initialString);
const { t } = useTranslation('input-dialog');
Expand Down Expand Up @@ -41,13 +41,19 @@ const InputDialog = ({ initialString, trigger, header, placeholder, accept, onAc
placeholder={placeholder}
onChange={handleChange}
value={inputString}
data-testid={testIdPrefix && `${testIdPrefix}-input`}
/>
</Modal.Content>
<Modal.Actions>
<Button onClick={handleClose} basic color='red' inverted>
<Button
basic color='red' inverted
onClick={handleClose}
data-testid={testIdPrefix && `${testIdPrefix}-cancel`}>
{t('cancel')}
</Button>
<Button color='green' inverted onClick={onClick}>
<Button color='green' inverted
onClick={onClick}
data-testid={testIdPrefix && `${testIdPrefix}-accept`}>
{accept}
</Button>
</Modal.Actions>
Expand Down
43 changes: 34 additions & 9 deletions packages/app/app/components/Playlists/PlaylistsHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,53 @@ import { Button } from '@nuclear/ui';
import Header from '../../Header';
import styles from './styles.scss';
import { Icon } from 'semantic-ui-react';
import InputDialog from '../../InputDialog';

type PlaylistsHeaderProps = {
showText: boolean;
handleImportFromFile: React.MouseEventHandler;
createNew: (name: string) => void;
}

const PlaylistsHeader: React.FC<PlaylistsHeaderProps> = ({ showText, handleImportFromFile }) => {
const PlaylistsHeader: React.FC<PlaylistsHeaderProps> = ({ showText, handleImportFromFile, createNew }) => {
const { t } = useTranslation('playlists');

const handleAddPlaylist = (name) => {
createNew(name);
};

return (
<div className={styles.header_container}>
{showText && <Header>{t('header')}</Header>}
{!showText && <span />}

<Button
basic
onClick={handleImportFromFile}
data-testid='import-from-file'
>
<Icon name='file text' />
{t('import-button')}
</Button>
<div>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get rid of this div

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I can't because header has flex with space between

<InputDialog
header={<h4>Input playlist name:</h4>}
placeholder={t('dialog-placeholder')}
accept={t('dialog-accept')}
onAccept={handleAddPlaylist}
testIdPrefix='create-playlist'
trigger={
<Button
basic
data-testid='create-new'
>
<Icon name='plus' />
{t('create-button')}
</Button>
}
initialString={t('new-playlist')}
/>
<Button
basic
onClick={handleImportFromFile}
data-testid='import-from-file'
>
<Icon name='file text' />
{t('import-button')}
</Button>
</div>

</div>
);
Expand Down
6 changes: 4 additions & 2 deletions packages/app/app/components/Playlists/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ const EmptyState = () => {
type PlaylistsProps = {
playlists: Playlist[];
handleImportFromFile: React.MouseEventHandler;
createNew: (name: string) => void;
}

const Playlists: React.FC<PlaylistsProps> = ({ playlists, handleImportFromFile }) => {
const Playlists: React.FC<PlaylistsProps> = ({ playlists, handleImportFromFile, createNew }) => {

function isPlaylistsReallyEmpty() {
return (
Expand All @@ -42,6 +43,7 @@ const Playlists: React.FC<PlaylistsProps> = ({ playlists, handleImportFromFile }
<PlaylistsHeader
showText={isPlaylistsReallyNotEmpty()}
handleImportFromFile={handleImportFromFile}
createNew={createNew}
/>
{
isPlaylistsReallyEmpty() &&
Expand All @@ -60,7 +62,7 @@ const Playlists: React.FC<PlaylistsProps> = ({ playlists, handleImportFromFile }
/>
);
})}
</Segment>
</Segment>
}

</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, waitFor } from '@testing-library/react';
import { render, waitFor, fireEvent } from '@testing-library/react';
import React from 'react';
import { createMemoryHistory } from 'history';

Expand Down Expand Up @@ -39,6 +39,51 @@ describe('Playlist container', () => {
expect(history.location.pathname).toBe('/playlist/1');
});

it('should create an empty playlist custom name', async () => {
const { component, store } = mountComponent();
await waitFor(() => component.getByTestId('create-new').click());
const input = component.getByTestId('create-playlist-input').firstChild;
fireEvent.change(input, {target: {value: 'new-empty-playlist'}});
await waitFor(() => component.getByTestId('create-playlist-accept').click());
const state = store.getState();
expect(state.playlists.playlists).toEqual([
expect.objectContaining({
name: 'new-empty-playlist'
})
]);
});

it('should create an empty playlist default name', async () => {
const { component, store } = mountComponent();
await waitFor(() => component.getByTestId('create-new').click());
await waitFor(() =>
component.getByTestId('create-playlist-accept').click()
);
const state = store.getState();
expect(state.playlists.playlists).toEqual([
expect.objectContaining({
name: 'New playlist'
})
]);
});

it('should not create an empty playlist with empty name', async () => {
const { component, store } = mountComponent();
await waitFor(() => component.getByTestId('create-new').click());
const input = component.getByTestId('create-playlist-input').firstChild;
fireEvent.change(input, { target: { value: '' } });
await waitFor(() =>
component.getByTestId('create-playlist-accept').click()
);
const state = store.getState();

expect(state.playlists.playlists).not.toEqual([
expect.objectContaining({
name: ''
})
]);
});

const mountComponent = (initialStore?: AnyProps) => {
const initialState = initialStore ||
buildStoreState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,28 @@ exports[`Playlist container should display all playlists 1`] = `
>
Your playlists
</div>
<button
class="ui basic button nuclear button"
data-testid="import-from-file"
>
<i
aria-hidden="true"
class="file text icon"
/>
Import from file (JSON)
</button>
<div>
<button
class="ui basic button nuclear button"
data-testid="create-new"
>
<i
aria-hidden="true"
class="plus icon"
/>
Create new playlist
</button>
<button
class="ui basic button nuclear button"
data-testid="import-from-file"
>
<i
aria-hidden="true"
class="file text icon"
/>
Import from file (JSON)
</button>
</div>
</div>
<div
class="ui segment playlists_segment"
Expand Down Expand Up @@ -104,16 +116,28 @@ exports[`Playlist container should display empty playlists view 1`] = `
class="header_container"
>
<span />
<button
class="ui basic button nuclear button"
data-testid="import-from-file"
>
<i
aria-hidden="true"
class="file text icon"
/>
Import from file (JSON)
</button>
<div>
<button
class="ui basic button nuclear button"
data-testid="create-new"
>
<i
aria-hidden="true"
class="plus icon"
/>
Create new playlist
</button>
<button
class="ui basic button nuclear button"
data-testid="import-from-file"
>
<i
aria-hidden="true"
class="file text icon"
/>
Import from file (JSON)
</button>
</div>
</div>
<div
class="empty_state"
Expand Down
7 changes: 6 additions & 1 deletion packages/app/app/containers/PlaylistsContainer/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ export const usePlaylistsProps = () => {
dispatch(PlaylistActions.addPlaylistFromFile(filePath[0], t));
}, [dispatch, t]);

const createNew = useCallback((name: string) => {
dispatch(PlaylistActions.addPlaylist([], name));
}, [dispatch]);

return {
playlists,
handleImportFromFile
handleImportFromFile,
createNew
};
};
3 changes: 2 additions & 1 deletion packages/core/src/helpers/playlist/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import _ from 'lodash';
import { v4 } from 'uuid';
import { Playlist, PlaylistTrack, PlaylistTrackStream } from './types';

const formatPlaylistForStorage = (name: string, tracks: Array<any>, id: string = v4(), streamSource: string = null): Playlist => {
return {
name,
id,
tracks: formatTrackList(tracks, streamSource)
tracks: !_.isEmpty(tracks) ? formatTrackList(tracks, streamSource) : []
};
};

Expand Down
3 changes: 3 additions & 0 deletions packages/i18n/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@
"export-fail-title": "Playlist export fail",
"export-success-title": "Playlist exported successfully",
"header": "Your playlists",
"dialog-accept": "Save",
"create-button": "Create new playlist",
"new-playlist": "New playlist",
"import-button": "Import from file (JSON)",
"import-fail-title": "Playlist import fail",
"import-success-title": "Playlist imported successfully",
Expand Down