Skip to content

Commit

Permalink
Fix for track table in album page
Browse files Browse the repository at this point in the history
  • Loading branch information
nukeop committed Jan 19, 2022
1 parent 6dca3d2 commit a886e8e
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 93 deletions.
198 changes: 123 additions & 75 deletions packages/app/app/actions/queue.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logger from 'electron-timber';
import _ from 'lodash';
import _, { isString } from 'lodash';

import { StreamProvider } from '@nuclear/core';
import { Track, TrackStream } from '@nuclear/ui/lib/types';

import { safeAddUuid } from './helpers';
import { startPlayback } from './player.js';
import { Track } from '@nuclear/ui/lib/types';
import { StreamProvider } from '@nuclear/core';

export const QUEUE_DROP = 'QUEUE_DROP';
export const ADD_QUEUE_ITEM = 'ADD_QUEUE_ITEM';
Expand All @@ -19,117 +20,158 @@ export const REPOSITION_SONG = 'REPOSITION_SONG';
export const STREAM_FAILED = 'STREAM_FAILED';
export const CHANGE_TRACK_STREAM = 'CHANGE_TRACK_STREAM';

const getSelectedStreamProvider = getState => {
export type QueueItem = {
loading?: boolean;
error?:
| boolean
| {
message: string;
details: string;
};
local?: boolean;
artist: string;
name: string;
thumbnail?: string;
streams?: TrackStream[];
};

export const toQueueItem = (track: Track): QueueItem => ({
...track,
artist: isString(track.artist) ? track.artist : track.artist.name,
name: track.title ? track.title : track.name
});

const getSelectedStreamProvider = (getState) => {
const {
plugin: {
plugins: { streamProviders }, selected
plugins: { streamProviders },
selected
}
} = getState();

return _.find(streamProviders, { sourceName: selected.streamProviders });
};

const getTrackStream = async (track: Track, selectedStreamProvider: StreamProvider, streamProviders: StreamProvider[]) => {
const getTrackStream = async (
track: Track,
selectedStreamProvider: StreamProvider,
streamProviders: StreamProvider[]
) => {
let streamData;
if (track?.streams && track.streams.length) {
const matchSelectedProvider = track.streams.find(
s => s.source === selectedStreamProvider.sourceName);
(s) => s.source === selectedStreamProvider.sourceName
);

if (matchSelectedProvider && matchSelectedProvider.id) {
streamData = await selectedStreamProvider.getStreamForId(matchSelectedProvider.id);
streamData = await selectedStreamProvider.getStreamForId(
matchSelectedProvider.id
);
} else {
const firstKnownProvider = streamProviders.find(
s => s.sourceName === track.streams[0].source);
(s) => s.sourceName === track.streams[0].source
);
streamData = await firstKnownProvider.getStreamForId(track.streams[0].id);
}
}

if (!streamData) {
streamData = await selectedStreamProvider.search({
artist: typeof track.artist === 'string' ? track.artist : track.artist.name,
artist:
typeof track.artist === 'string' ? track.artist : track.artist.name,
track: track.name
});
}

return streamData;
};

const addQueueItem = item => ({
const addQueueItem = (item: QueueItem) => ({
type: ADD_QUEUE_ITEM,
payload: { item }
});

const updateQueueItem = item => ({
const updateQueueItem = (item: QueueItem) => ({
type: UPDATE_QUEUE_ITEM,
payload: { item }
});

const playNextItem = item => ({
const playNextItem = (item: QueueItem) => ({
type: PLAY_NEXT_ITEM,
payload: { item }
});

export const addToQueue = (item, asNextItem = false) => async (dispatch, getState) => {
item = safeAddUuid(item);
item.loading = !item.local;
export const addToQueue =
(item: QueueItem, asNextItem = false) =>
async (dispatch, getState) => {
item = safeAddUuid(item);
item.loading = !item.local;

const {
connectivity,
plugin: {
plugins: { streamProviders }
}
} = getState();
const isAbleToAdd = (!connectivity && item.local) || connectivity;

isAbleToAdd && dispatch(!asNextItem ? addQueueItem(item) : playNextItem(item));

if (!item.local && isAbleToAdd) {
const selectedStreamProvider = getSelectedStreamProvider(getState);
try {
const streamData = await getTrackStream({
artist: item.artist,
name: item.name,
streams: item.streams
}, selectedStreamProvider, streamProviders);

if (streamData === undefined){
dispatch(removeFromQueue(item));
} else {
dispatch(updateQueueItem({
...item,
loading: false,
error: false,
streams: [
streamData
]
const {
connectivity,
plugin: {
plugins: { streamProviders }
}
));
}
} catch (e) {
logger.error(`An error has occurred when searching for a stream with ${selectedStreamProvider.sourceName} for "${item.artist} - ${item.name}."`);
logger.error(e);
dispatch(updateQueueItem({
...item,
loading: false,
error: {
message: `An error has occurred when searching for a stream with ${selectedStreamProvider.sourceName}.`,
details: e.message
} = getState();
const isAbleToAdd = (!connectivity && item.local) || connectivity;

isAbleToAdd &&
dispatch(!asNextItem ? addQueueItem(item) : playNextItem(item));

if (!item.local && isAbleToAdd) {
const selectedStreamProvider = getSelectedStreamProvider(getState);
try {
const streamData = await getTrackStream(
{
artist: item.artist,
name: item.name,
streams: item.streams
},
selectedStreamProvider,
streamProviders
);

if (streamData === undefined) {
dispatch(removeFromQueue(item));
} else {
dispatch(
updateQueueItem({
...item,
loading: false,
error: false,
streams: [streamData]
})
);
}
} catch (e) {
logger.error(
`An error has occurred when searching for a stream with ${selectedStreamProvider.sourceName} for "${item.artist} - ${item.name}."`
);
logger.error(e);
dispatch(
updateQueueItem({
...item,
loading: false,
error: {
message: `An error has occurred when searching for a stream with ${selectedStreamProvider.sourceName}.`,
details: e.message
}
})
);
}
}));
}
}
};
}
};

export function playTrack(streamProviders, item) {
return dispatch => {
export function playTrack(streamProviders, item: QueueItem) {
return (dispatch) => {
dispatch(clearQueue());
dispatch(addToQueue(item));
dispatch(selectSong(0));
dispatch(startPlayback());
};
}

export function removeFromQueue(item) {
export function removeFromQueue(item: QueueItem) {
return {
type: REMOVE_QUEUE_ITEM,
payload: item
Expand All @@ -138,28 +180,34 @@ export function removeFromQueue(item) {

export function addPlaylistTracksToQueue(tracks) {
return async (dispatch) => {
await tracks.forEach(async item => {
await tracks.forEach(async (item) => {
await dispatch(addToQueue(item));
});
};
}

export function rerollTrack(streamProvider, selectedStream, track) {
return dispatch => {
return (dispatch) => {
dispatch(updateQueueItem({ ...track, loading: true, error: false }));

streamProvider.getAlternateStream({ artist: track.artist, track: track.name }, selectedStream)
.then(newStream => {
const streams = _.map(track.streams, stream => {
streamProvider
.getAlternateStream(
{ artist: track.artist, track: track.name },
selectedStream
)
.then((newStream) => {
const streams = _.map(track.streams, (stream) => {
return stream.source === newStream.source ? newStream : stream;
});

dispatch(updateQueueItem({
...track,
loading: false,
error: false,
streams
}));
dispatch(
updateQueueItem({
...track,
loading: false,
error: false,
streams
})
);
});
};
}
Expand Down Expand Up @@ -253,4 +301,4 @@ export const queueDrop = (paths) => ({
payload: paths
});

export const playNext = (item) => addToQueue(item, true);
export const playNext = (item: QueueItem) => addToQueue(item, true);
10 changes: 6 additions & 4 deletions packages/app/app/components/PlaylistView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React, { useCallback } from 'react';
import _ from 'lodash';
import { TFunction } from 'i18next';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';
import { Icon } from 'semantic-ui-react';

import { Playlist } from '@nuclear/core';
import { Button, ContextPopup, PopupButton, timestampToTimeString } from '@nuclear/ui';
import { Playlist, Track, TrackType } from '@nuclear/core';
import { Track } from '@nuclear/ui/lib/types';

import InputDialog from '../InputDialog';
import artPlaceholder from '../../../resources/media/art_placeholder.png';

import styles from './styles.scss';
import { useHistory } from 'react-router';
import TrackTableContainer from '../../containers/TrackTableContainer';
import { TFunction } from 'i18next';

export type PlaylistViewProps = {
playlist: Playlist;
Expand Down Expand Up @@ -169,7 +171,7 @@ const PlaylistView: React.FC<PlaylistViewProps> = ({
</div>
</div>
<TrackTableContainer
tracks={playlist.tracks as TrackType[]}
tracks={playlist.tracks as Track[]}
onDelete={onDeleteTrack}
onReorder={onReorderTracks}
displayAlbum={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ describe('Album view container', () => {
const state = store.getState();
expect(state.queue.queueItems).toEqual([
expect.objectContaining({
artist: {
name: 'test artist'
},
artist: 'test artist',
name: 'test track 1'
})
]);
Expand All @@ -62,9 +60,29 @@ describe('Album view container', () => {
const state = store.getState();
expect(state.queue.queueItems).toEqual([
expect.objectContaining({
artist: {
name: 'test artist'
},
artist: 'test artist',
name: 'test track 1'
})
]);
expect(state.player.playbackStatus).toEqual('PLAYING');
});

it('should queue a single track to be played next after clicking the button in the popup', async () => {
const { component, store } = mountComponent();

await waitFor(() => component.getAllByTestId('track-popup-trigger')[1].click());
await waitFor(() => component.getByText(/play now/i).click());
await waitFor(() => component.getAllByTestId('track-popup-trigger')[0].click());
await waitFor(() => component.getByText(/play next/i).click());

const state = store.getState();
expect(state.queue.queueItems).toEqual([
expect.objectContaining({
artist: 'test artist',
name: 'test track 2'
}),
expect.objectContaining({
artist: 'test artist',
name: 'test track 1'
})
]);
Expand Down
6 changes: 3 additions & 3 deletions packages/app/app/containers/PlayerBarContainer/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ export const useTrackInfoProps = () => {
const hasTracks = queue.queueItems.length > 0;
const currentSong = _.get(queue.queueItems, queue.currentSong);

const track = _.get(currentSong, 'name');
const artist = _.get(currentSong, 'artist');
const cover = _.get(currentSong, 'thumbnail');
const track = currentSong?.name;
const artist = currentSong?.artist;
const cover = currentSong?.thumbnail;

const favorite = useSelector(s => getFavoriteTrack(s, artist, track));
const isFavorite = !_.isNil(favorite);
Expand Down
6 changes: 3 additions & 3 deletions packages/app/app/containers/TrackTableContainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ const TrackTableContainer: React.FC<TrackTableContainerProps> = ({
const isTrackFavorite = (track: Track) => !_.isNil(favoriteTracks.find(t => areTracksEqualByName(t, track)));

const onAddToQueue = useCallback((track: Track) => {
dispatch(queueActions.addToQueue(track));
dispatch(queueActions.addToQueue(queueActions.toQueueItem(track)));
}, [dispatch]);

const onPlayNow = useCallback((track: Track) => {
dispatch(queueActions.playTrack(null, track));
dispatch(queueActions.playTrack(null, queueActions.toQueueItem(track)));
}, [dispatch]);

const onPlayNext = useCallback((track: Track) => {
dispatch(queueActions.playNext(track));
dispatch(queueActions.playNext(queueActions.toQueueItem(track)));
}, [dispatch]);

const onPlayAll = useCallback((tracks: Track[]) => {
Expand Down
Loading

0 comments on commit a886e8e

Please sign in to comment.