diff --git a/packages/app/__mocks__/@nuclear/core.ts b/packages/app/__mocks__/@nuclear/core.ts index ee18e2ee47..e7e152d51e 100644 --- a/packages/app/__mocks__/@nuclear/core.ts +++ b/packages/app/__mocks__/@nuclear/core.ts @@ -66,8 +66,6 @@ module.exports = { urlSearch: jest.fn().mockResolvedValue([]), liveStreamSearch: jest.fn().mockResolvedValue([]) }, - NuclearPlaylistsService: jest.requireActual('@nuclear/core/src/rest/Nuclear/Playlists').NuclearPlaylistsService, - NuclearStreamMappingsService: jest.requireActual('@nuclear/core/src/rest/Nuclear/StreamMappings').NuclearStreamMappingsService, Deezer: { ...jest.requireActual('@nuclear/core/src/rest/Deezer'), getEditorialCharts: jest.fn().mockResolvedValue({ diff --git a/packages/app/app/actions/actionTypes.ts b/packages/app/app/actions/actionTypes.ts index 8728d865b9..bf754a88fc 100644 --- a/packages/app/app/actions/actionTypes.ts +++ b/packages/app/app/actions/actionTypes.ts @@ -20,15 +20,14 @@ export enum Search { ALBUM_INFO_SEARCH_START = 'ALBUM_INFO_SEARCH_START', ALBUM_INFO_SEARCH_SUCCESS = 'ALBUM_INFO_SEARCH_SUCCESS', ALBUM_INFO_SEARCH_ERROR = 'ALBUM_INFO_SEARCH_ERROR', - PODCAST_SEARCH_SUCCESS = 'PODCAST_SEARCH_SUCCESS', ARTIST_INFO_SEARCH_START = 'ARTIST_INFO_SEARCH_START', ARTIST_INFO_SEARCH_SUCCESS = 'ARTIST_INFO_SEARCH_SUCCESS', ARTIST_INFO_SEARCH_ERROR = 'ARTIST_INFO_SEARCH_ERROR', ARTIST_RELEASES_SEARCH_START = 'ARTIST_RELEASES_SEARCH_START', ARTIST_RELEASES_SEARCH_SUCCESS = 'ARTIST_RELEASES_SEARCH_SUCCESS', ARTIST_RELEASES_SEARCH_ERROR = 'ARTIST_RELEASES_SEARCH_ERROR', - LASTFM_TRACK_SEARCH_START = 'LASTFM_TRACK_SEARCH_START', - LASTFM_TRACK_SEARCH_SUCCESS = 'LASTFM_TRACK_SEARCH_SUCCESS', + TRACK_SEARCH_START = 'TRACK_SEARCH_START', + TRACK_SEARCH_ERROR = 'TRACK_SEARCH_ERROR', TRACK_SEARCH_SUCCESS = 'TRACK_SEARCH_SUCCESS', YOUTUBE_PLAYLIST_SEARCH_START = 'YOUTUBE_PLAYLIST_SEARCH_START', YOUTUBE_PLAYLIST_SEARCH_SUCCESS = 'YOUTUBE_PLAYLIST_SEARCH_SUCCESS', @@ -62,10 +61,6 @@ export enum Playlists { LOAD_LOCAL_PLAYLISTS_SUCCESS = 'LOAD_LOCAL_PLAYLISTS_SUCCESS', LOAD_LOCAL_PLAYLISTS_ERROR = 'LOAD_LOCAL_PLAYLISTS_ERROR', UPDATE_LOCAL_PLAYLISTS = 'UPDATE_LOCAL_PLAYLISTS', - - LOAD_REMOTE_PLAYLISTS_START = 'LOAD_REMOTE_PLAYLISTS_START', - LOAD_REMOTE_PLAYLISTS_SUCCESS = 'LOAD_REMOTE_PLAYLISTS_SUCCESS', - LOAD_REMOTE_PLAYLISTS_ERROR = 'LOAD_REMOTE_PLAYLISTS_ERROR', } export enum ImportFavs { diff --git a/packages/app/app/actions/nuclear/identity.ts b/packages/app/app/actions/nuclear/identity.ts deleted file mode 100644 index 23cda1d547..0000000000 --- a/packages/app/app/actions/nuclear/identity.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createAsyncAction, createStandardAction } from 'typesafe-actions'; - -import { - SignInResponseBody, - SignUpResponseBody -} from '@nuclear/core/src/rest/Nuclear/Identity.types'; -import { ErrorBody } from '@nuclear/core/src/rest/Nuclear/types'; - -import { NuclearIdentity } from '../actionTypes'; - -export const signUpAction = createAsyncAction( - NuclearIdentity.SIGN_UP_START, - NuclearIdentity.SIGN_UP_SUCCESS, - NuclearIdentity.SIGN_UP_ERROR -)(); - -export const signInAction = createAsyncAction( - NuclearIdentity.SIGN_IN_START, - NuclearIdentity.SIGN_IN_SUCCESS, - NuclearIdentity.SIGN_IN_ERROR -)(); - -export const signOutAction = createStandardAction(NuclearIdentity.SIGN_OUT)(); diff --git a/packages/app/app/actions/playlists.ts b/packages/app/app/actions/playlists.ts index f42eedff92..796b16f7a2 100644 --- a/packages/app/app/actions/playlists.ts +++ b/packages/app/app/actions/playlists.ts @@ -3,9 +3,7 @@ import { v4 } from 'uuid'; import { createAsyncAction, createStandardAction } from 'typesafe-actions'; import { ipcRenderer } from 'electron'; -import { store, PlaylistHelper, Playlist, PlaylistTrack, rest, IpcEvents } from '@nuclear/core'; -import { GetPlaylistsByUserIdResponseBody } from '@nuclear/core/src/rest/Nuclear/Playlists.types'; -import { ErrorBody } from '@nuclear/core/src/rest/Nuclear/types'; +import { store, PlaylistHelper, Playlist, PlaylistTrack, IpcEvents } from '@nuclear/core'; import { Playlists } from './actionTypes'; @@ -15,7 +13,6 @@ import { updatePlaylistsOrderEffect } from './playlists.effects'; import { success, error } from './toasts'; -import { IdentityStore } from '../reducers/nuclear/identity'; import { PlaylistsStore } from '../reducers/playlists'; import { isEmpty } from 'lodash'; @@ -27,12 +24,6 @@ export const loadLocalPlaylistsAction = createAsyncAction( Playlists.LOAD_LOCAL_PLAYLISTS_ERROR ), void>(); -export const loadRemotePlaylistsAction = createAsyncAction( - Playlists.LOAD_REMOTE_PLAYLISTS_START, - Playlists.LOAD_REMOTE_PLAYLISTS_SUCCESS, - Playlists.LOAD_REMOTE_PLAYLISTS_ERROR -)(); - export const addPlaylist = (tracks: Array, name: string) => dispatch => { if (name?.length === 0) { return; @@ -62,30 +53,6 @@ export const loadLocalPlaylists = () => dispatch => { } }; - -export const loadRemotePlaylists = ({ token, signedInUser }: IdentityStore) => async (dispatch, getState) => { - dispatch(loadRemotePlaylistsAction.request()); - const { settings } = getState(); - const service = new rest.NuclearPlaylistsService( - settings.nuclearPlaylistsServiceUrl - ); - - try { - if (token) { - const playlists = await service.getPlaylistsByUserId(token, signedInUser.id); - if (playlists.ok) { - dispatch(loadRemotePlaylistsAction.success(playlists.body as GetPlaylistsByUserIdResponseBody)); - } else { - throw playlists.body; - } - } else { - throw new Error('No token'); - } - } catch (e) { - dispatch(loadRemotePlaylistsAction.failure(e.message)); - } -}; - export const updatePlaylist = (playlist: Playlist) => dispatch => { const playlists = updatePlaylistEffect(store)(playlist); dispatch(updatePlaylistsAction(playlists)); diff --git a/packages/app/app/actions/queue.ts b/packages/app/app/actions/queue.ts index a331378523..51c8837e41 100644 --- a/packages/app/app/actions/queue.ts +++ b/packages/app/app/actions/queue.ts @@ -3,7 +3,9 @@ import { isEmpty, isString, find } from 'lodash'; import { createStandardAction } from 'typesafe-actions'; import { v4 } from 'uuid'; -import { rest, StreamProvider } from '@nuclear/core'; +import { StreamProvider } from '@nuclear/core'; +import StreamProviderPlugin from '@nuclear/core/src/plugins/streamProvider'; +import { Nuclear } from '@nuclear/core/src/rest'; import { getTrackArtist, getTrackTitle } from '@nuclear/ui'; import { Track } from '@nuclear/ui/lib/types'; @@ -13,8 +15,6 @@ import { QueueItem, TrackStream } from '../reducers/queue'; import { RootState } from '../reducers'; import { LocalLibraryState } from './local'; import { Queue } from './actionTypes'; -import StreamProviderPlugin from '@nuclear/core/src/plugins/streamProvider'; -import { isSuccessCacheEntry } from '@nuclear/core/src/rest/Nuclear/StreamMappings'; import { queue as queueSelector } from '../selectors/queue'; import { error } from './toasts'; import { random } from 'lodash'; @@ -200,7 +200,7 @@ const verifyStreamWithService = async ( } try { - const StreamMappingsService = rest.NuclearStreamMappingsService.get(process.env.NUCLEAR_VERIFICATION_SERVICE_URL); + const StreamMappingsService = Nuclear.NuclearStreamMappingsService.get(process.env.NUCLEAR_VERIFICATION_SERVICE_URL); const topStream = await StreamMappingsService.getTopStream( getTrackArtist(track), getTrackTitle(track), @@ -208,7 +208,7 @@ const verifyStreamWithService = async ( settings?.userId ); - if (!isSuccessCacheEntry(topStream)) { + if (!Nuclear.isSuccessCacheEntry(topStream)) { return streamData; } diff --git a/packages/app/app/actions/search.ts b/packages/app/app/actions/search.ts index a154bb9a70..26bbe2bd56 100644 --- a/packages/app/app/actions/search.ts +++ b/packages/app/app/actions/search.ts @@ -1,18 +1,14 @@ import { logger } from '@nuclear/core'; import { rest } from '@nuclear/core'; -import _, { isString } from 'lodash'; -import artPlaceholder from '../../resources/media/art_placeholder.png'; -import globals from '../globals'; +import _ from 'lodash'; import { error } from './toasts'; import { Search } from './actionTypes'; import { History } from 'history'; import { RootState } from '../reducers'; -import { AlbumDetails, ArtistDetails, SearchResultsAlbum, SearchResultsArtist, SearchResultsPodcast, SearchResultsSource } from '@nuclear/core/src/plugins/plugins.types'; -import { createStandardAction } from 'typesafe-actions'; -import { LastfmTrackMatch, LastfmTrackMatchInternal } from '@nuclear/core/src/rest/Lastfm.types'; +import { AlbumDetails, ArtistDetails, SearchResultsAlbum, SearchResultsArtist, SearchResultsSource, SearchResultsTrack } from '@nuclear/core/src/plugins/plugins.types'; +import { createAsyncAction, createStandardAction } from 'typesafe-actions'; import { YoutubeResult } from '@nuclear/core/src/rest/Youtube'; - -const lastfm = new rest.LastFmApi(globals.lastfmApiKey, globals.lastfmApiSecret); +import { getTrackArtist } from '@nuclear/ui'; export const SearchActions = { unifiedSearchStart: createStandardAction(Search.UNIFIED_SEARCH_START)(), @@ -28,7 +24,7 @@ export const SearchActions = { }; }), youtubeLiveStreamSearchStart: createStandardAction(Search.YOUTUBE_LIVESTREAM_SEARCH_START)(), - youtubeLiveStreamSearchSuccess: createStandardAction(Search.YOUTUBE_LIVESTREAM_SEARCH_SUCCESS).map((id: string, info: YoutubeResult[]) => { + youtubeLiveStreamSearchSuccess: createStandardAction(Search.YOUTUBE_LIVESTREAM_SEARCH_SUCCESS).map((id: string, info: SearchResultsTrack[]) => { return { payload: { id, @@ -68,19 +64,8 @@ export const SearchActions = { } }; }), - podcastSearchSuccess: createStandardAction(Search.PODCAST_SEARCH_SUCCESS)(), setSearchDropdownVisibility: createStandardAction(Search.SEARCH_DROPDOWN_DISPLAY_CHANGE)(), updateSearchHistory: createStandardAction(Search.UPDATE_SEARCH_HISTORY)(), - lastFmTrackSearchStart: createStandardAction(Search.LASTFM_TRACK_SEARCH_START)(), - lastFmTrackSearchSuccess: createStandardAction(Search.LASTFM_TRACK_SEARCH_SUCCESS).map((terms: string, searchResults: LastfmTrackMatchInternal[]) => { - return { - payload: { - id: terms, - info: searchResults - } - }; - }), - trackSearchSuccess: createStandardAction(Search.TRACK_SEARCH_SUCCESS)(), artistSearchSuccess: createStandardAction(Search.ARTIST_SEARCH_SUCCESS)(), artistInfoStart: createStandardAction(Search.ARTIST_INFO_SEARCH_START)(), artistInfoSuccess: createStandardAction(Search.ARTIST_INFO_SEARCH_SUCCESS).map((artistId: string, info: ArtistDetails) => { @@ -121,7 +106,12 @@ export const SearchActions = { error } }; - }) + }), + trackSearchAction: createAsyncAction( + Search.TRACK_SEARCH_START, + Search.TRACK_SEARCH_SUCCESS, + Search.TRACK_SEARCH_ERROR + )() }; @@ -131,9 +121,12 @@ const getSelectedMetaProvider = (getState: () => RootState, wantedProvider: Sear plugins: { metaProviders }, selected } } = getState(); - return wantedProvider ? + const selectedProvider = wantedProvider ? _.find(metaProviders, { searchName: wantedProvider }) : _.find(metaProviders, { sourceName: selected.metaProviders }); + + + return selectedProvider || _.find(metaProviders, { isDefault: true }) || null; }; export const artistSearch = (terms: string) => async (dispatch, getState: () => RootState) => { @@ -149,57 +142,17 @@ export const albumSearch = (terms: string) => async (dispatch, getState: () => R }; export const trackSearch = (terms: string) => async (dispatch, getState: () => RootState) => { - const selectedProvider = getSelectedMetaProvider(getState); - const results = await selectedProvider.searchForTracks(terms); - dispatch(SearchActions.trackSearchSuccess(results)); -}; - -export const podcastSearch = (terms: string) => async (dispatch, getState: () => RootState) => { - const selectedProvider = getSelectedMetaProvider(getState); - const results = await selectedProvider.searchForPodcast(terms); - dispatch(SearchActions.podcastSearchSuccess(results)); -}; - - -const isAcceptableLastFMThumbnail = (thumbnail: string) => - !(/https?:\/\/lastfm-img\d.akamaized.net\/i\/u\/\d+s\/2a96cbd8b46e442fc41c2b86b821562f\.png/.test(thumbnail)); - -const getTrackThumbnail = (track: LastfmTrackMatch) => { - const image = - _.get( - track, - ['image', 1, '#text'], - _.get( - track, - ['image', 0, '#text'], - artPlaceholder - ) - ); - - return !isString(image) ? artPlaceholder : isAcceptableLastFMThumbnail(image) ? image : artPlaceholder; + dispatch(SearchActions.trackSearchAction.request()); + try { + const selectedProvider = getSelectedMetaProvider(getState); + const results = await selectedProvider.searchForTracks(terms); + dispatch(SearchActions.trackSearchAction.success(results)); + } catch (e) { + logger.error(e); + dispatch(SearchActions.trackSearchAction.failure()); + } }; -export const mapLastFMTrackToInternal = (track: LastfmTrackMatch) => ({ - ...track, - thumbnail: getTrackThumbnail(track) -}); - -export function lastFmTrackSearch(terms: string) { - return dispatch => { - dispatch(SearchActions.lastFmTrackSearchStart(terms)); - Promise.all([lastfm.searchTracks(terms)]) - .then(results => Promise.all(results.map(info => info.json()))) - .then(results => { - dispatch( - SearchActions.lastFmTrackSearchSuccess(terms, _.get(results[0], 'results.trackmatches.track', []).map(mapLastFMTrackToInternal)) - ); - }) - .catch(error => { - logger.error(error); - }); - }; -} - export function youtubePlaylistSearch(terms: string) { return dispatch => { dispatch(SearchActions.youtubePlaylistSearchStart(terms)); @@ -218,7 +171,13 @@ export function youtubePlaylistSearch(terms: string) { export const youtubeLiveStreamSearch = (terms: string) => async (dispatch) => { dispatch(SearchActions.youtubeLiveStreamSearchStart(terms)); try { - const results = await rest.Youtube.liveStreamSearch(terms); + const results = (await rest.Youtube.liveStreamSearch(terms)).map(el => ({ + id: el.streams[0].id, + title: el.name, + artist: getTrackArtist(el), + thumb: el.thumbnail, + source: SearchResultsSource.Youtube + }) satisfies SearchResultsTrack); dispatch(SearchActions.youtubeLiveStreamSearchSuccess(terms, results)); } catch (e) { logger.error(e); @@ -233,8 +192,7 @@ export function unifiedSearch(terms: string, history: History) { Promise.all([ dispatch(albumSearch(terms)), dispatch(artistSearch(terms)), - dispatch(podcastSearch(terms)), - dispatch(lastFmTrackSearch(terms)), + dispatch(trackSearch(terms)), dispatch(youtubePlaylistSearch(terms)), dispatch(youtubeLiveStreamSearch(terms)) ]) @@ -251,28 +209,28 @@ export function unifiedSearch(terms: string, history: History) { }; } -export const albumInfoSearch = (albumId: string, releaseType: 'master' | 'release' = 'master', release: SearchResultsAlbum) => async (dispatch, getState:() => RootState) => { - dispatch(SearchActions.albumInfoStart(albumId)); +export const albumInfoSearch = (release: SearchResultsAlbum) => async (dispatch, getState:() => RootState) => { + dispatch(SearchActions.albumInfoStart(release.id)); try { const selectedProvider = getSelectedMetaProvider(getState); - const albumDetails = await selectedProvider.fetchAlbumDetails(albumId, releaseType, release?.resourceUrl); - dispatch(SearchActions.albumInfoSuccess(albumId, albumDetails)); + const albumDetails = await selectedProvider.fetchAlbumDetails(release.id, release.type, release?.resourceUrl); + dispatch(SearchActions.albumInfoSuccess(release.id, albumDetails)); } catch (e) { logger.error(e); - dispatch(SearchActions.albumInfoError(albumId, e)); + dispatch(SearchActions.albumInfoError(release.id, e)); } }; -export const artistInfoSearch = (artistId: string, artist: SearchResultsArtist) => async (dispatch, getState: () => RootState) => { - dispatch(SearchActions.artistInfoStart(artistId)); +export const artistInfoSearch = (artist: SearchResultsArtist) => async (dispatch, getState: () => RootState) => { + dispatch(SearchActions.artistInfoStart(artist.id)); try { const selectedProvider = getSelectedMetaProvider(getState, artist?.source); - const artistDetails = await selectedProvider.fetchArtistDetails(artistId); - dispatch(SearchActions.artistInfoSuccess(artistId, artistDetails)); + const artistDetails = await selectedProvider.fetchArtistDetails(artist.id); + dispatch(SearchActions.artistInfoSuccess(artist.id, artistDetails)); } catch (e) { logger.error(e); - dispatch(SearchActions.artistInfoError(artistId, e)); + dispatch(SearchActions.artistInfoError(artist.id, e)); } }; diff --git a/packages/app/app/components/ArtistView/ArtistHeader/index.tsx b/packages/app/app/components/ArtistView/ArtistHeader/index.tsx index db341a0845..b4a3327495 100644 --- a/packages/app/app/components/ArtistView/ArtistHeader/index.tsx +++ b/packages/app/app/components/ArtistView/ArtistHeader/index.tsx @@ -24,6 +24,9 @@ export const ArtistHeader: React.FC = ({ addFavoriteArtist }) => { const { t }= useTranslation('artist'); + + const avatar = artist.thumb ?? artist.images?.[1] ?? artPlaceholder; + return
{ @@ -31,8 +34,7 @@ export const ArtistHeader: React.FC = ({
= ({ return ( !_.isEmpty(tracks) &&
{t('popular-tracks')}
Promise; artistInfoSearchByName: (artistName: string) => Promise; - albumInfoSearch: (albumId: string, releaseType: ReleaseTypeProps, release: SearchResultsAlbum) => Promise; + albumInfoSearch: (release: SearchResultsAlbum) => Promise; removeFavoriteArtist: React.MouseEventHandler; addFavoriteArtist: React.MouseEventHandler; } @@ -42,7 +42,7 @@ const ArtistView: React.FC = ({ const isOnTour = () => artist.onTour || false; const areReleasesLoading = () => artist.releasesLoading || isLoading(); const onAlbumClick = (album) => { - albumInfoSearch(album.id, album.type, album); + albumInfoSearch(album); history.push('/album/' + album.id); }; diff --git a/packages/app/app/components/LibraryView/index.js b/packages/app/app/components/LibraryView/index.js index 3d1c87e796..aa23be6aeb 100644 --- a/packages/app/app/components/LibraryView/index.js +++ b/packages/app/app/components/LibraryView/index.js @@ -6,7 +6,6 @@ import { LIST_TYPE } from '@nuclear/ui/lib/components/LibraryListTypeToggle'; import _ from 'lodash'; import PropTypes from 'prop-types'; import EmptyState from './EmptyState'; -import trackRowStyles from '../TrackRow/styles.scss'; import styles from './index.scss'; import NoSearchResults from './NoSearchResults'; import LibrarySimpleList from './LibrarySimpleList'; @@ -89,7 +88,7 @@ const LibraryView = ({ ) ) : ( - + {!pending && listType === LIST_TYPE.SIMPLE_LIST && ( diff --git a/packages/app/app/components/SearchResults/AllResults/index.js b/packages/app/app/components/SearchResults/AllResults/index.js deleted file mode 100644 index 0e7e303966..0000000000 --- a/packages/app/app/components/SearchResults/AllResults/index.js +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { Card } from '@nuclear/ui'; - -import artPlaceholder from '../../../../resources/media/art_placeholder.png'; - -import PlaylistResults from '../PlaylistResults'; -import TracksResults from '../TracksResults'; - -import styles from './styles.scss'; -import { withTranslation } from 'react-i18next'; - -@withTranslation('search') -class AllResults extends React.Component { - constructor(props) { - super(props); - } - renderResults(collection, onClick) { - const selectedProvider = _.find(this.props.metaProviders, { sourceName: this.props.selectedPlugins.metaProviders }); - - return collection.slice(0, 5).map((el, i) => { - const id = _.get(el, `ids.${selectedProvider.searchName}`, el.id); - - return ( - onClick(id, el.type, el)} - key={'item-' + i} - /> - ); - }); - } - - renderTracks(arr = [], limit = 5) { - return (); - - } - - renderPlaylistSection = () => -
-

{this.props.t('playlist', { count: this.props.playlistSearchResults.length })}

-
- -
-
- - renderSection(title, collection, onClick) { - return (
-

{title}

-
- {this.renderResults( - collection, - onClick - )} -
-
); - } - - renderArtistsSection() { - const { t, artistSearchResults, artistInfoSearch } = this.props; - - return this.renderSection(t('artist', { count: artistSearchResults.length }), artistSearchResults, artistInfoSearch); - } - - renderAlbumsSection() { - const { t, albumSearchResults, albumInfoSearch } = this.props; - - return this.renderSection(t('album', { count: albumSearchResults.length }), albumSearchResults, albumInfoSearch); - } - - renderTracksSection() { - return (
-

{this.props.t('track_plural')}

-
- {this.renderTracks(this.props.trackSearchResults.info)} -
-
); - } - - render() { - const tracksLength = _.get(this.props.trackSearchResults, ['info', 'length'], 0); - const artistsLength = _.get(this.props.artistSearchResults, ['length'], 0); - const albumsLength = _.get(this.props.albumSearchResults, ['length'], 0); - const playlistsLength = _.get(this.props.playlistSearchResults, ['info', 'length'], 0); - if (tracksLength + artistsLength + albumsLength + playlistsLength === 0) { - return
{this.props.t('empty')}
; - } - - return ( -
- {artistsLength > 0 && this.renderArtistsSection()} - {albumsLength > 0 && this.renderAlbumsSection()} - {tracksLength > 0 && this.renderTracksSection()} - {playlistsLength > 0 && this.renderPlaylistSection()} -
- ); - } -} - -export default AllResults; diff --git a/packages/app/app/components/SearchResults/AllResults/index.tsx b/packages/app/app/components/SearchResults/AllResults/index.tsx new file mode 100644 index 0000000000..0828568b45 --- /dev/null +++ b/packages/app/app/components/SearchResults/AllResults/index.tsx @@ -0,0 +1,133 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; +import { Tab } from 'semantic-ui-react'; + +import { + SearchResultsAlbum, + SearchResultsArtist +} from '@nuclear/core/src/plugins/plugins.types'; +import { Card } from '@nuclear/ui'; + +import { TracksResults } from '../TracksResults'; +import { searchSelectors } from '../../../selectors/search'; +import { SearchState } from '../../../reducers/search'; +import artPlaceholder from '../../../../resources/media/art_placeholder.png'; +import styles from './styles.scss'; +import parentStyles from '../styles.scss'; + + +type SearchCollection = SearchState[ + | 'artistSearchResults' + | 'albumSearchResults']; + +type ResultsProps = { + collection: SearchCollection; + onClick: ( + item: SearchResultsArtist | SearchResultsAlbum + ) => void; +}; +const Results: FC = ({ collection, onClick }) => { + return ( + <> + {collection.slice(0, 5).map((item, index) => ( + onClick(item)} + key={'item-' + index} /> + ))} + + ); +}; + +type ResultsSectionProps = { + title: string; + collection: SearchCollection; + onClick: ( + item: SearchResultsArtist | SearchResultsAlbum) => void; +}; +const ResultsSection: FC = ({ + title, + collection, + onClick +}) => { + return ( +
+

{title}

+
+ +
+
+ ); +}; + +type AllResultsProps = { + artistInfoSearch?: (item: SearchResultsArtist) => void; + albumInfoSearch?: (item: SearchResultsAlbum) => void; + loading?: boolean; + attached?: boolean; +}; + +export const AllResults: FC = ({ + artistInfoSearch, + albumInfoSearch, + loading = false, + attached = false +}) => { + const { t } = useTranslation('search'); + const artistSearchResults = useSelector(searchSelectors.artistSearchResults); + const albumSearchResults = useSelector(searchSelectors.albumSearchResults); + const trackSearchResults = useSelector(searchSelectors.trackSearchResults); + + const tracksLength = trackSearchResults?.length || 0; + const artistsLength = artistSearchResults?.length || 0; + const albumsLength = albumSearchResults?.length || 0; + + const content = () => { + if (artistsLength + albumsLength === 0) { + return
{t('empty')}
; + } + + return ( +
+ {artistsLength > 0 && ( + + )} + {albumsLength > 0 && ( + + )} + {tracksLength > 0 && ( +
+

{t('track_plural')}

+
+ +
+
+ )} +
+ ); + }; + + return ( + +
+
+ {content()} +
+
+
+ ); +}; diff --git a/packages/app/app/components/SearchResults/AllResults/styles.scss b/packages/app/app/components/SearchResults/AllResults/styles.scss index 356f6f080a..936bade9d7 100644 --- a/packages/app/app/components/SearchResults/AllResults/styles.scss +++ b/packages/app/app/components/SearchResults/AllResults/styles.scss @@ -1,22 +1,23 @@ .all_results_container { + display: flex; + flex-flow: row wrap; + justify-content: space-around; + width: 100%; + + .column { display: flex; - flex-flow: row wrap; - justify-content: space-around; + flex-flow: column; width: 100%; - .column { - display: flex; - flex-flow: column; - width: 100%; - - h3 { - font-variant: small-caps; - margin: 1rem; - } + h3 { + font-variant: small-caps; + margin: 1rem; + } - .row { - display: flex; - flex-flow: row wrap; - } + .row { + display: flex; + flex-flow: row wrap; + flex: 1 1 auto; } + } } diff --git a/packages/app/app/components/SearchResults/ItemResults.tsx b/packages/app/app/components/SearchResults/ItemResults.tsx new file mode 100644 index 0000000000..3bcfbaea61 --- /dev/null +++ b/packages/app/app/components/SearchResults/ItemResults.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { get } from 'lodash'; +import { Tab } from 'semantic-ui-react'; + +import { + SearchResultsAlbum, + SearchResultsArtist +} from '@nuclear/core/src/plugins/plugins.types'; +import { Card } from '@nuclear/ui'; +import parentStyles from './styles.scss'; + +type ItemResultsProps = { + collection: T[]; + loading?: boolean; + attached?: boolean; + selectedProvider?: { searchName: string }; + onItemClick: (item: T) => void; + emptyText: string; +}; + +const ItemResults = ({ + collection, + loading = false, + attached = false, + selectedProvider, + onItemClick, + emptyText +}: ItemResultsProps) => { + const getItemHeader = (item: T): string => { + return (item as SearchResultsAlbum).title || (item as SearchResultsArtist).name; + }; + + const getItemContent = (item: T): string | undefined => { + if ('artist' in item) { + return item.artist; + } + return undefined; + }; + + return ( + +
+ {collection.length > 0 + ? loading + ? null + : collection.map((el, i) => { + const id = get(el, `ids.${selectedProvider?.searchName}`, el.id); + return ( + onItemClick(el)} + /> + ); + }) + : emptyText} +
+
+ ); +}; + +export default ItemResults; diff --git a/packages/app/app/components/SearchResults/PlaylistResults/index.js b/packages/app/app/components/SearchResults/PlaylistResults/index.js deleted file mode 100644 index 40bdff15dd..0000000000 --- a/packages/app/app/components/SearchResults/PlaylistResults/index.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; - -import TracksResults from '../TracksResults'; -import FontAwesome from 'react-fontawesome'; -import artPlaceholder from '../../../../resources/media/art_placeholder.png'; -import _ from 'lodash'; -import { withTranslation } from 'react-i18next'; - -@withTranslation('search') -class PlaylistResults extends React.Component { - constructor(props) { - super(props); - } - - addTrack(track) { - if (typeof track !== 'undefined') { - this.props.addToQueue({ - artist: track.artist, - name: track.name, - thumbnail: track.thumbnail ?? _.get(track, 'image[1][\'#text\']', artPlaceholder) - }); - } - } - renderAddAllButton(tracks) { - return (tracks.length > 0 ? { - tracks - .map(track => { - this.addTrack(track); - }); - }} - aria-label={this.props.t('queue-add')} - > - Add all - : null - ); - } - - renderLoading() { - return (
Loading...
); - } - - renderResults = () => -
- {this.renderAddAllButton(this.props.playlistSearchResults.info)} -
- - renderNoResult() { - return (
{this.props.t('empty')}
); - } - render() { - return ( - this.props.playlistSearchStarted ? ((this.props.playlistSearchStarted.length > 0 && typeof this.props.playlistSearchResults.info === 'undefined') ? this.renderLoading() : this.renderResults()) : this.renderNoResult() - ); - } -} - -export default PlaylistResults; diff --git a/packages/app/app/components/SearchResults/PlaylistResults/index.tsx b/packages/app/app/components/SearchResults/PlaylistResults/index.tsx new file mode 100644 index 0000000000..7d766565ef --- /dev/null +++ b/packages/app/app/components/SearchResults/PlaylistResults/index.tsx @@ -0,0 +1,97 @@ +import React, { FC } from 'react'; +import { get } from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { Tab } from 'semantic-ui-react'; +import FontAwesome from 'react-fontawesome'; + +import { TracksResults } from '../TracksResults'; +import artPlaceholder from '../../../../resources/media/art_placeholder.png'; +import parentStyles from '../styles.scss'; + +type PlaylistResultsProps = { + playlistSearchStarted: boolean | string; + playlistSearchResults: any; + addToQueue: (track: any) => void; + clearQueue: () => void; + startPlayback: () => void; + selectSong: (track: any) => void; + loading?: boolean; + attached?: boolean; +}; + +const PlaylistResults: FC = ({ + playlistSearchStarted, + playlistSearchResults, + addToQueue, + loading = false, + attached = false +}) => { + const { t } = useTranslation('search'); + + const addTrack = (track: any) => { + if (typeof track !== 'undefined') { + addToQueue({ + artist: track.artist, + name: track.name, + thumbnail: track.thumbnail ?? get(track, 'image[1][\'#text\']', artPlaceholder) + }); + } + }; + + const renderAddAllButton = (tracks: any[]) => { + return tracks.length > 0 ? ( + { + tracks.map(track => { + addTrack(track); + }); + }} + aria-label={t('queue-add')} + > + Add all + + ) : null; + }; + + const renderLoading = () => ( +
Loading...
+ ); + + const renderResults = () => ( +
+ {renderAddAllButton(playlistSearchResults.info)} + +
+ ); + + const renderNoResult = () => ( +
{t('empty')}
+ ); + + const content = () => { + if (!playlistSearchStarted) { + return renderNoResult(); + } + + if (typeof playlistSearchStarted === 'string' && playlistSearchStarted.length > 0 && typeof playlistSearchResults.info === 'undefined') { + return renderLoading(); + } + + return renderResults(); + }; + + return ( + +
+ {content()} +
+
+ ); +}; + +export default PlaylistResults; diff --git a/packages/app/app/components/SearchResults/TracksResults/index.js b/packages/app/app/components/SearchResults/TracksResults/index.js deleted file mode 100644 index 28d8cf1727..0000000000 --- a/packages/app/app/components/SearchResults/TracksResults/index.js +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { Segment } from 'semantic-ui-react'; -import { useTranslation } from 'react-i18next'; - -import TrackRow from '../../TrackRow'; - -import trackRowStyles from '../../TrackRow/styles.scss'; - -const TracksResults = ({ tracks, limit }) => { - const { t } = useTranslation('search'); - const collection = tracks || []; - - if (collection.length === 0) { - return t('empty'); - } else { - return ( - - - - {_.take(collection, limit).map((track, index) => { - if ( - track && - _.hasIn(track, 'name') && (_.hasIn(track, 'image') || _.hasIn(track, 'thumbnail')) && - _.hasIn(track, 'artist') - ) { - const newTrack = _.cloneDeep(track); - if (!newTrack.artist.name) { - _.set(newTrack, 'artist.name', newTrack.artist); - } - return ( - - ); - } - })} - -
-
- ); - } -}; - -export default TracksResults; diff --git a/packages/app/app/components/SearchResults/TracksResults/index.tsx b/packages/app/app/components/SearchResults/TracksResults/index.tsx new file mode 100644 index 0000000000..38d653a3bc --- /dev/null +++ b/packages/app/app/components/SearchResults/TracksResults/index.tsx @@ -0,0 +1,63 @@ +import React, { FC } from 'react'; +import { take } from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { Tab } from 'semantic-ui-react'; + +import { SearchResultsTrack } from '@nuclear/core/src/plugins/plugins.types'; +import { + HEADER_HEIGHT, + ITEM_HEIGHT +} from '@nuclear/ui/lib/components/GridTrackTable'; +import TrackTableContainer from '../../../containers/TrackTableContainer'; +import styles from './styles.scss'; +import parentStyles from '../styles.scss'; + +type TracksResultsProps = { + tracks: SearchResultsTrack[]; + limit?: number; + loading?: boolean; + attached?: boolean; + asPane?: boolean; +}; + +export const TracksResults: FC = ({ + tracks, + limit, + loading = false, + attached = false, + asPane = false +}) => { + const { t } = useTranslation('search'); + const collection = tracks || []; + + const height = `${HEADER_HEIGHT + ITEM_HEIGHT * Math.min(collection.length, 5)}px`; + + const content = () => { + if (collection.length === 0) { + return
{t('empty')}
; + } + + return ( +
+ +
+ ); + }; + + if (asPane) { + return ( + +
+ {content()} +
+
+ ); + } + + return content(); +}; diff --git a/packages/app/app/components/SearchResults/TracksResults/styles.scss b/packages/app/app/components/SearchResults/TracksResults/styles.scss index aa5d60dcf2..a138a61a22 100644 --- a/packages/app/app/components/SearchResults/TracksResults/styles.scss +++ b/packages/app/app/components/SearchResults/TracksResults/styles.scss @@ -1,93 +1,8 @@ - @use "sass:color"; -@use '../../../vars'; - -.all_results_container { - display: flex; - flex-flow: row wrap; - justify-content: space-around; - width: 100%; - - .column { - display: flex; - flex-flow: column; - width: 100%; - - h3 { - font-variant: small-caps; - margin: 1rem; - } - - .row { - display: flex; - flex-flow: row wrap; - } - } -} +@use "../../../vars"; - -.popular_tracks_container { +.tracks_results_container { display: flex; - flex: 1 1 auto; flex-flow: column; - - margin: 0 0.5rem; - - .header { - margin-bottom: 1rem; - - font-size: 16px; - font-variant: small-caps; - } - - .track_row { - display: flex; - align-items: center; - flex-flow: row; - - transition: vars.$short-duration ease-in-out; - - border-bottom: 1px solid rgba(vars.$background2, 0.2); - - &:hover { - background: color.adjust(vars.$background, $lightness: 10%); - } - - img { - flex: 0 0 auto; - - width: 3rem; - height: 3rem; - } - - .popular_track_name { - flex: 1 1 auto; - margin: 0.25rem 0.5rem; - text-align: left; - } - - .playcount { - flex: 1 1 auto; - margin: 0.25rem 0.5rem; - text-align: right; - text-transform: uppercase; - } - } - - .expand_button { - display: flex; - justify-content: center; - padding: 0.5rem; - margin-top: 0.5rem; - transition: vars.$short-duration; - cursor: pointer; - - &:hover { - background: color.adjust(vars.$background, $lightness: 5%); - } - } -} - -.add_button { - text-align: left; + flex: 1 1 auto; } diff --git a/packages/app/app/components/SearchResults/index.js b/packages/app/app/components/SearchResults/index.js deleted file mode 100644 index 9b22444e99..0000000000 --- a/packages/app/app/components/SearchResults/index.js +++ /dev/null @@ -1,182 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { Tab } from 'semantic-ui-react'; -import { withTranslation } from 'react-i18next'; -import { Card } from '@nuclear/ui'; - -import AllResults from './AllResults'; -import TracksResults from './TracksResults'; -import PlaylistResults from './PlaylistResults'; - -import styles from './styles.scss'; - -@withTranslation('search') -class SearchResults extends React.Component { - renderAllResultsPane() { - return ( - -
-
- -
-
-
- ); - } - - renderPane(collection, onClick) { - const selectedProvider = _.find(this.props.metaProviders, { sourceName: this.props.selectedPlugins.metaProviders }); - - return ( - -
- {collection.length > 0 - ? this.props.unifiedSearchStarted - ? null - : collection.map((el, i) => { - const id = _.get(el, `ids.${selectedProvider.searchName}`, el.id); - return ( - onClick(id, el.type)} - /> - ); - }) - : this.props.t('empty')} -
-
- ); - } - - renderTrackListPane(collection) { - if (typeof collection !== 'undefined') { - - return ( - -
- {collection.length > 0 - ? this.props.unifiedSearchStarted - ? null - : - : this.props.t('empty')} -
-
- ); - } else { - return ( - -
{this.props.t('empty')}
-
- ); - } - } - - renderPlaylistPane() { - return ( - - - - ); - } - - panes() { - const artistsHasResults = _.get(this.props.artistSearchResults, ['length'], 0) > 0; - const albumsHasResults = _.get(this.props.albumSearchResults, ['length'], 0) > 0; - const tracksHasResults = _.get(this.props.trackSearchResults, ['info', 'length'], 0) > 0; - const playlistsHasResults = _.get(this.props.playlistSearchResults, ['info', 'length'], 0) > 0; - const liveStreamsHasResults = _.get(this.props.liveStreamSearchResults, ['info', 'length'], 0) > 0; - const podcastsHasResults = _.get(this.props.podcastSearchResults, ['length'], 0) > 0; - - const panes = [ - { - menuItem: this.props.t('all'), - render: () => this.renderAllResultsPane() - }, - artistsHasResults && { - menuItem: this.props.t('artist_plural'), - render: () => - this.renderPane( - this.props.artistSearchResults, - this.artistInfoSearch.bind(this) - ) - }, - albumsHasResults && { - menuItem: this.props.t('album_plural'), - render: () => - this.renderPane( - this.props.albumSearchResults, - this.albumInfoSearch.bind(this) - ) - }, - tracksHasResults && { - menuItem: this.props.t('track_plural'), - render: () => this.renderTrackListPane(this.props.trackSearchResults.info) - }, - playlistsHasResults && { - menuItem: this.props.t('playlist'), - render: () => this.renderPlaylistPane() - }, - liveStreamsHasResults && { - menuItem: this.props.t('live-stream'), - render: () => this.renderTrackListPane(this.props.liveStreamSearchResults.info) - }, - podcastsHasResults && { - menuItem: this.props.t('podcast'), - render: () => - this.renderPane( - this.props.podcastSearchResults, - this.podcastInfoSearch.bind(this) - ) - } - ].filter(pane => !!pane); - - return panes; - } - - albumInfoSearch(albumId, releaseType, release) { - this.props.albumInfoSearch(albumId, releaseType, release); - this.props.history.push('/album/' + albumId); - } - - artistInfoSearch(artistId) { - this.props.artistInfoSearch(artistId); - this.props.history.push('/artist/' + artistId); - } - - podcastInfoSearch(podcastId, releaseType, release) { - this.props.albumInfoSearch(podcastId, releaseType, release); - this.props.history.push('/album/' + podcastId); - } - - render() { - return ( -
- -
- ); - } -} - -export default SearchResults; diff --git a/packages/app/app/components/SearchResults/index.tsx b/packages/app/app/components/SearchResults/index.tsx new file mode 100644 index 0000000000..42d8318289 --- /dev/null +++ b/packages/app/app/components/SearchResults/index.tsx @@ -0,0 +1,160 @@ +import React, { FC, useMemo } from 'react'; +import { get } from 'lodash'; +import { Tab } from 'semantic-ui-react'; +import { useTranslation } from 'react-i18next'; +import { + SearchResultsAlbum, + SearchResultsArtist +} from '@nuclear/core/src/plugins/plugins.types'; + +import { albumInfoSearch as albumInfoSearchAction, artistInfoSearch as artistInfoSearchAction } from '../../actions/search'; +import {clearQueue as clearQueueAction, selectSong as selectSongAction} from '../../actions/queue'; +import {startPlayback as startPlaybackAction} from '../../actions/player'; +import { AllResults } from './AllResults'; +import { TracksResults } from './TracksResults'; +import PlaylistResults from './PlaylistResults'; +import ItemResults from './ItemResults'; + +import { useDispatch, useSelector } from 'react-redux'; +import { pluginsSelectors } from '../../selectors/plugins'; +import { useHistory } from 'react-router'; +import { searchSelectors } from '../../selectors/search'; +import { useDispatchedCallback } from '../../hooks/useDispatchedCallback'; +import { addToQueue as addToQueueAction } from '../../actions/queue'; + +const SearchResults: FC = () => { + const { t } = useTranslation('search'); + const dispatch = useDispatch(); + const history = useHistory(); + + const { metaProviders } = useSelector(pluginsSelectors.plugins); + const selectedPlugins = useSelector(pluginsSelectors.selected); + const unifiedSearchStarted = useSelector(searchSelectors.unifiedSearchStarted); + const playlistSearchStarted = useSelector(searchSelectors.playlistSearchStarted); + const playlistSearchResults = useSelector(searchSelectors.playlistSearchResults); + const trackSearchResults = useSelector(searchSelectors.trackSearchResults); + const albumSearchResults = useSelector(searchSelectors.albumSearchResults); + const artistSearchResults = useSelector(searchSelectors.artistSearchResults); + const liveStreamSearchResults = useSelector(searchSelectors.liveStreamSearchResults); + + const selectedProvider = useMemo(() => + metaProviders.find(provider => provider.sourceName === selectedPlugins.metaProviders), + [metaProviders, selectedPlugins.metaProviders] + ); + + const albumInfoSearch = (release?: SearchResultsAlbum) => { + dispatch(albumInfoSearchAction(release)); + history.push(`/album/${release?.id}`); + }; + + const artistInfoSearch = (artist?: SearchResultsArtist) => { + dispatch(artistInfoSearchAction(artist)); + history.push(`/artist/${artist?.id}`); + }; + + const addToQueue = useDispatchedCallback(addToQueueAction); + const clearQueue = useDispatchedCallback(clearQueueAction); + const startPlayback = useDispatchedCallback(startPlaybackAction); + const selectSong = useDispatchedCallback(selectSongAction); + + const panes = useMemo(() => { + const artistsHasResults = artistSearchResults.length > 0; + const albumsHasResults = albumSearchResults.length > 0; + const tracksHasResults = get(trackSearchResults, ['info', 'length'], trackSearchResults.length) > 0; + const playlistsHasResults = get(playlistSearchResults, ['info', 'length'], 0) > 0; + const liveStreamsHasResults = get(liveStreamSearchResults, ['info', 'length'], 0) > 0; + + return [ + { + menuItem: t('all'), + render: () => ( + + ) + }, + artistsHasResults && { + menuItem: t('artist_plural'), + render: () => ( + + ) + }, + albumsHasResults && { + menuItem: t('album_plural'), + render: () => ( + + ) + }, + tracksHasResults && { + menuItem: t('track_plural'), + render: () => ( + + ) + }, + playlistsHasResults && { + menuItem: t('playlist'), + render: () => ( + startPlayback(false)} + selectSong={selectSong} + loading={false} + attached={false} + /> + ) + }, + liveStreamsHasResults && { + menuItem: t('live-stream'), + render: () => ( + + ) + } + ].filter(Boolean); + }, [ + artistSearchResults, albumSearchResults, trackSearchResults, + playlistSearchResults, liveStreamSearchResults, + unifiedSearchStarted, selectedProvider, t, + albumInfoSearch, artistInfoSearch, + playlistSearchStarted, addToQueue, clearQueue, startPlayback, selectSong + ]); + + return ( +
+ +
+ ); +}; + +export default SearchResults; diff --git a/packages/app/app/components/TrackRow/index.js b/packages/app/app/components/TrackRow/index.js deleted file mode 100644 index 65b2afc6be..0000000000 --- a/packages/app/app/components/TrackRow/index.js +++ /dev/null @@ -1,196 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import PropTypes from 'prop-types'; -import numeral from 'numeral'; -import { Icon } from 'semantic-ui-react'; -import { head } from 'lodash'; - -import { formatDuration, getTrackArtist, getTrackTitle } from '@nuclear/ui'; - -import * as QueueActions from '../../actions/queue'; - -import TrackPopupContainer from '../../containers/TrackPopupContainer'; -import artPlaceholder from '../../../resources/media/art_placeholder.png'; - -import styles from './styles.scss'; - -class TrackRow extends React.Component { - // this function should be moved onto interface for 'track' - renderAlbum(track) { - return ( - - {track.album} - - ); - } - - renderDuration(track) { - if (track.duration === 0) { - return ; - } - return ( - - {formatDuration(track.duration)} - - ); - } - - playTrack() { - this.props.actions.playTrack(this.props.streamProviders, { - artist: this.props.track.artist.name, - name: this.props.track.name, - thumbnail: this.getTrackThumbnail(), - local: this.props.track.local, - stream: head(this.props.track.streams) - }); - } - - getTrackThumbnail() { - const thumb = _.get( - this.props.track, - 'thumb', - _.get( - this.props.track, - 'thumbnail', - _.get( - this.props.track, - 'image[0][#text]', - artPlaceholder - ) - ) - ); - - return thumb === '' ? artPlaceholder : thumb; - } - - canAddToFavorites() { - return _.findIndex(this.props.favoriteTracks, (currentTrack) => { - return currentTrack.name === this.props.track.name && currentTrack.artist.name === this.props.track.artist.name; - }) < 0; - } - - renderTrigger(track) { - const { - displayCover, - displayTrackNumber, - displayArtist, - displayAlbum, - displayDuration, - displayPlayCount, - withDeleteButton, - onDelete - } = this.props; - return ( - - { - withDeleteButton && - - - - - - } - { - displayCover && - - - - } - {displayTrackNumber && {track.position}} - {displayArtist && {track.artist.name}} - {track.name} - {displayAlbum && this.renderAlbum(track)} - {displayDuration && this.renderDuration(track)} - {displayPlayCount && {numeral(track.playcount).format('0,0')}} - - ); - } - - render() { - - const { - track, - withAddToQueue, - withPlayNext, - withPlayNow, - withAddToFavorites, - withAddToPlaylist, - withAddToDownloads - } = this.props; - return ( - - ); - } -} - -TrackRow.propTypes = { - track: PropTypes.object, - - displayCover: PropTypes.bool, - displayTrackNumber: PropTypes.bool, - displayArtist: PropTypes.bool, - displayAlbum: PropTypes.bool, - displayDuration: PropTypes.bool, - displayPlayCount: PropTypes.bool, - - withAddToQueue: PropTypes.bool, - withPlayNext: PropTypes.bool, - withPlayNow: PropTypes.bool, - withAddToFavorites: PropTypes.bool, - withAddToPlaylist: PropTypes.bool, - withAddToDownloads: PropTypes.bool, - withDeleteButton: PropTypes.bool, - - onDelete: PropTypes.func -}; - -TrackRow.defaultProps = { - withAddToQueue: true, - withPlayNext: true, - withPlayNow: true, - withAddToFavorites: true, - withAddToDownloads: true, - withDeleteButton: false -}; - -function mapStateToProps(state, { track }) { - return { - streamProviders: track.local - ? state.plugin.plugins.streamProviders.filter(({ sourceName }) => { - return sourceName === 'Local'; - }) - : state.plugin.plugins.streamProviders, - settings: state.settings, - favoriteTracks: state.favorites.tracks - }; -} - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators( - QueueActions, - dispatch - ) - }; -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(TrackRow); - diff --git a/packages/app/app/components/TrackRow/styles.scss b/packages/app/app/components/TrackRow/styles.scss deleted file mode 100644 index 3c6c99d488..0000000000 --- a/packages/app/app/components/TrackRow/styles.scss +++ /dev/null @@ -1,92 +0,0 @@ -@use "sass:color"; -@use '../../vars'; - -.tracks_container { - display: flex; - flex: 1 1 auto; - flex-flow: column; - - margin: 0 0.5rem; - - .header { - margin-bottom: 1rem; - - font-size: 16px; - font-variant: small-caps; - } - - .expand_button { - display: flex; - justify-content: center; - padding: 0.5rem; - margin-top: 0.5rem; - transition: vars.$short-duration; - cursor: pointer; - - &:hover { - background: color.adjust(vars.$background, $lightness: 5%); - } - } - - table { - width: 100%; - } - - thead { - font-variant: all-small-caps; - border-bottom: 1px solid vars.$background2; - color: rgba(vars.$white, 0.5); - font-size: 20px; - } - - th { - padding: 1rem; - text-align: left; - } -} - -.add_button { - text-align: left; -} - -.track { - transition: vars.$short-duration; - cursor: pointer; - - &:hover { - background: rgba(vars.$background2, 0.25); - } - - &:last-child { - td { - border-bottom: none; - } - } - - td { - padding: 1rem 1rem; - border-bottom: 1px solid vars.$background2; - } - - .track_number, - .track_duration { - text-align: center; - } -} - -.track_thumbnail { - position: relative; - box-sizing: content-box; - padding: 0 !important; - - img { - width: 3em; - height: 3em; - display: block; - object-fit: cover; - } -} - -.track_row_buttons { - width: 1px; -} diff --git a/packages/app/app/containers/AlbumViewContainer/__snapshots__/AlbumViewContainer.test.tsx.snap b/packages/app/app/containers/AlbumViewContainer/__snapshots__/AlbumViewContainer.test.tsx.snap index e3d17fecb1..25c738cdde 100644 --- a/packages/app/app/containers/AlbumViewContainer/__snapshots__/AlbumViewContainer.test.tsx.snap +++ b/packages/app/app/containers/AlbumViewContainer/__snapshots__/AlbumViewContainer.test.tsx.snap @@ -131,7 +131,7 @@ exports[`Album view container should display an album 1`] = `