Track loading (current_track and next_track); Add to playlist
This commit is contained in:
@ -229,6 +229,7 @@ artist:
|
||||
overview:
|
||||
title: Overview
|
||||
top_tracks: Top tracks
|
||||
albums: Albums
|
||||
related_artists:
|
||||
title: Related artists
|
||||
more: All related artists
|
||||
|
||||
@ -107,16 +107,17 @@ export function updateColdStore(items) {
|
||||
* relevant service to load the record - all from one neat package.
|
||||
* */
|
||||
|
||||
export function loadItems(uris, forceRefetch = false) {
|
||||
export function loadItems(uris, forceRefetch = false, callbackAction = null) {
|
||||
return {
|
||||
type: 'LOAD_ITEMS',
|
||||
uris,
|
||||
forceRefetch,
|
||||
callbackAction,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadItem(uri, forceRefetch = false) {
|
||||
return loadItems([uri], forceRefetch);
|
||||
export function loadItem(uri, forceRefetch = false, callbackAction) {
|
||||
return loadItems([uri], forceRefetch, callbackAction);
|
||||
}
|
||||
|
||||
export function loadTrack(uri, forceRefetch = false) {
|
||||
|
||||
@ -319,6 +319,7 @@ const CoreMiddleware = (function () {
|
||||
type: `LOAD_${uriType(uri).toUpperCase()}`,
|
||||
uri,
|
||||
forceRefetch: action.forceRefetch,
|
||||
callbackAction: action.callbackAction,
|
||||
});
|
||||
});
|
||||
break;
|
||||
@ -345,7 +346,10 @@ const CoreMiddleware = (function () {
|
||||
fetchTrack();
|
||||
break;
|
||||
}
|
||||
if (store.getState().core.items[action.uri]) {
|
||||
if (
|
||||
store.getState().core.items[action.uri]
|
||||
&& store.getState().core.items[action.uri].images
|
||||
) {
|
||||
console.info(`Using "${action.uri}" from index`);
|
||||
break;
|
||||
}
|
||||
@ -354,6 +358,10 @@ const CoreMiddleware = (function () {
|
||||
if (result) {
|
||||
console.info(`Loading "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
|
||||
|
||||
if (!result.images) {
|
||||
fetchTrack();
|
||||
}
|
||||
} else {
|
||||
fetchTrack();
|
||||
}
|
||||
@ -435,19 +443,34 @@ const CoreMiddleware = (function () {
|
||||
if (
|
||||
store.getState().core.items[action.uri]
|
||||
&& store.getState().core.items[action.uri].tracks
|
||||
&& store.getState().core.items[action.uri].albums
|
||||
&& store.getState().core.items[action.uri].albums_uris
|
||||
&& store.getState().core.items[action.uri].images
|
||||
) {
|
||||
console.info(`Using "${action.uri}" from index`);
|
||||
break;
|
||||
}
|
||||
|
||||
localForage.getItem(action.uri).then((artist) => {
|
||||
if (artist) {
|
||||
console.info(`Restoring "${action.uri}" and ${artist.albums_uris.length} albums from database`);
|
||||
|
||||
const promises = artist.albums_uris.map((albumUri) => localForage.getItem(albumUri));
|
||||
Promise.all(promises).then(
|
||||
(albums) => store.dispatch(coreActions.restoreItemsFromColdStore(compact(albums))),
|
||||
);
|
||||
|
||||
store.dispatch(coreActions.restoreLibraryFromColdStore(artist));
|
||||
} else {
|
||||
fetchArtist();
|
||||
}
|
||||
});
|
||||
|
||||
localForage.getItem(action.uri).then((result) => {
|
||||
if (result) {
|
||||
console.info(`Restoring "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
|
||||
|
||||
if (!result.tracks || !result.albums || !result.images) {
|
||||
if (!result.tracks || !result.albums_uris || !result.images) {
|
||||
fetchArtist();
|
||||
}
|
||||
} else {
|
||||
@ -459,7 +482,6 @@ const CoreMiddleware = (function () {
|
||||
break;
|
||||
|
||||
case 'LOAD_PLAYLIST':
|
||||
console.log(action);
|
||||
const fetchPlaylist = () => {
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
@ -585,7 +607,7 @@ const CoreMiddleware = (function () {
|
||||
|
||||
localForage.getItem(action.uri).then((library) => {
|
||||
if (library) {
|
||||
console.info(`Restoring "${action.uri}" and ${library.items_uris.length} items from db`);
|
||||
console.info(`Restoring "${action.uri}" and ${library.items_uris.length} items from database`);
|
||||
|
||||
const promises = library.items_uris.map((libraryItem) => localForage.getItem(libraryItem));
|
||||
Promise.all(promises).then(
|
||||
|
||||
@ -76,7 +76,10 @@ export default function reducer(core = {}, action) {
|
||||
};
|
||||
|
||||
case 'ITEMS_LOADED':
|
||||
const mergedItems = action.items.reduce((obj, item) => (obj[item.uri] = item, obj), {});
|
||||
const mergedItems = action.items.reduce(
|
||||
(obj, item) => (obj[item.uri] = item, obj),
|
||||
{},
|
||||
);
|
||||
return {
|
||||
...core,
|
||||
items: {
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
formatSimpleObject,
|
||||
getTrackIcon,
|
||||
formatArtists,
|
||||
formatArtist,
|
||||
} from '../../util/format';
|
||||
import {
|
||||
arrayOf,
|
||||
@ -1566,55 +1567,23 @@ const MopidyMiddleware = (function () {
|
||||
...response,
|
||||
uri: response.uri,
|
||||
type: 'playlist',
|
||||
is_completely_loaded: true,
|
||||
provider: 'mopidy',
|
||||
tracks: (response.tracks ? response.tracks : []),
|
||||
tracks_total: (response.tracks ? response.tracks.length : 0),
|
||||
can_edit: true,
|
||||
};
|
||||
|
||||
// tracks? get the full track objects
|
||||
if (playlist.tracks.length > 0) {
|
||||
store.dispatch({
|
||||
type: 'MOPIDY_RESOLVE_PLAYLIST_TRACKS',
|
||||
tracks: playlist.tracks,
|
||||
key: playlist.uri,
|
||||
request(store, 'library.lookup', { uris: arrayOf('uri', response.tracks) })
|
||||
.then((tracksResponse) => {
|
||||
const tracks = response.tracks.map((simpleTrack) => {
|
||||
const fullTracks = tracksResponse[simpleTrack.uri];
|
||||
return {
|
||||
...simpleTrack,
|
||||
...(fullTracks.length ? fullTracks[0] : {}),
|
||||
};
|
||||
});
|
||||
playlist.tracks = tracks;
|
||||
store.dispatch(coreActions.itemLoaded(playlist));
|
||||
});
|
||||
}
|
||||
|
||||
store.dispatch(coreActions.playlistLoaded(playlist));
|
||||
});
|
||||
break;
|
||||
|
||||
case 'MOPIDY_RESOLVE_PLAYLIST_TRACKS':
|
||||
var tracks = Object.assign([], action.tracks);
|
||||
var uris = arrayOf('uri', tracks);
|
||||
|
||||
request(store, 'library.lookup', { uris })
|
||||
.then((response) => {
|
||||
for (const uri in response) {
|
||||
if (response.hasOwnProperty(uri)) {
|
||||
var track = response[uri][0];
|
||||
if (track) {
|
||||
// find the track reference, and drop in the full track data
|
||||
function getByURI(trackReference) {
|
||||
return track.uri == trackReference.uri;
|
||||
}
|
||||
const trackReferences = tracks.filter(getByURI);
|
||||
|
||||
// there could be multiple instances of this track, so accommodate this
|
||||
for (let j = 0; j < trackReferences.length; j++) {
|
||||
const key = tracks.indexOf(trackReferences[j]);
|
||||
tracks[key] = track;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatch({
|
||||
type: 'PLAYLIST_TRACKS',
|
||||
tracks,
|
||||
key: action.key,
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
@ -1873,36 +1842,22 @@ const MopidyMiddleware = (function () {
|
||||
for (const raw_artist of response[0].artists) {
|
||||
// We're only interested in the artist we asked for
|
||||
if (raw_artist.uri === artist.uri) {
|
||||
artist = { ...raw_artist };
|
||||
artist = { ...formatArtist(raw_artist) };
|
||||
}
|
||||
}
|
||||
|
||||
// Add our tracks and albums
|
||||
artist.albums_uris = arrayOf('uri', albums);
|
||||
artist.tracks = response;
|
||||
artist.tracks = formatTracks(response);
|
||||
|
||||
store.dispatch(coreActions.itemLoaded(artist));
|
||||
store.dispatch(lastfmActions.getArtist(artist.uri, artist.name, artist.musicbrainz_id));
|
||||
|
||||
// Load supprting information from LastFM and Discogs
|
||||
const existing_artist = store.getState().core.items[artist.uri];
|
||||
if (existing_artist) {
|
||||
if (!existing_artist.images) {
|
||||
if (store.getState().spotify.enabled) {
|
||||
store.dispatch(spotifyActions.getArtistImages(artist));
|
||||
} else {
|
||||
store.dispatch(discogsActions.getArtistImages(artist.uri, artist));
|
||||
}
|
||||
}
|
||||
|
||||
// Get biography and other stats from LastFM
|
||||
if (!existing_artist.biography) {
|
||||
// TODO: Move this condition into lastfmActions
|
||||
if (artist.musicbrainz_id) {
|
||||
store.dispatch(lastfmActions.getArtist(artist.uri, false, artist.musicbrainz_id));
|
||||
} else {
|
||||
store.dispatch(lastfmActions.getArtist(artist.uri, artist.name));
|
||||
}
|
||||
}
|
||||
if (store.getState().spotify.enabled) {
|
||||
store.dispatch(spotifyActions.getArtistImages(artist));
|
||||
} else {
|
||||
store.dispatch(discogsActions.getArtistImages(artist.uri, artist));
|
||||
}
|
||||
});
|
||||
break;
|
||||
@ -1982,24 +1937,13 @@ const MopidyMiddleware = (function () {
|
||||
|
||||
var track = formatTrack(action.tl_track);
|
||||
if (track.uri) {
|
||||
// Deliver the data we've got already
|
||||
store.dispatch({
|
||||
type: 'CURRENT_TRACK_LOADED',
|
||||
track,
|
||||
uri: track.uri,
|
||||
});
|
||||
|
||||
// Now attempt to get supporting images
|
||||
if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined) {
|
||||
// We've got Spotify running, and it's a spotify track - go straight to the source!
|
||||
if (store.getState().spotify.enabled && uriSource(track.uri) == 'spotify') {
|
||||
store.dispatch(spotifyActions.getTrack(track.uri));
|
||||
|
||||
// Some other source, rely on Mopidy backends to do their work
|
||||
} else {
|
||||
store.dispatch(mopidyActions.getImages([track.uri]));
|
||||
}
|
||||
}
|
||||
store.dispatch(coreActions.loadItem(track.uri));
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@ -1041,7 +1041,9 @@ export function getArtist(uri, full = false, forceRefetch = false) {
|
||||
request(dispatch, getState, endpoint, 'GET', false, true)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch(coreActions.itemLoaded(formatArtist(response)));
|
||||
const artist = formatArtist(response);
|
||||
dispatch(coreActions.itemLoaded(artist));
|
||||
dispatch(lastfmActions.getArtist(uri, artist.name, artist.mbid));
|
||||
},
|
||||
);
|
||||
|
||||
@ -1058,8 +1060,9 @@ export function getArtist(uri, full = false, forceRefetch = false) {
|
||||
} else {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
albums,
|
||||
albums_uris: arrayOf('uri', albums),
|
||||
}));
|
||||
dispatch(coreActions.itemsLoaded(albums));
|
||||
}
|
||||
});
|
||||
fetchAlbums(`artists/${getFromUri('artistid', uri)}/albums?limit=50&include_groups=album,single&market=${getState().spotify.country}`);
|
||||
@ -1257,18 +1260,11 @@ export function createPlaylist(name, description, is_public, is_collaborative) {
|
||||
request(dispatch, getState, `users/${getState().spotify.me.id}/playlists/`, 'POST', data)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch({
|
||||
type: 'PLAYLIST_LOADED',
|
||||
key: response.uri,
|
||||
playlist: {
|
||||
|
||||
...response,
|
||||
can_edit: true,
|
||||
tracks: [],
|
||||
tracks_more: null,
|
||||
tracks_total: 0,
|
||||
},
|
||||
});
|
||||
dispatch(coreActions.itemLoaded({
|
||||
...formatPlaylist(response),
|
||||
can_edit: true,
|
||||
tracks: [],
|
||||
}));
|
||||
|
||||
dispatch({
|
||||
type: 'LIBRARY_PLAYLISTS_LOADED',
|
||||
@ -1372,7 +1368,7 @@ export function getPlaylist(uri, forceRefetch = false, callbackAction = null) {
|
||||
|
||||
dispatch(coreActions.itemLoaded({
|
||||
...formatPlaylist(response),
|
||||
user: formatUser(response.owner),
|
||||
can_edit: (getState().spotify.me && getState().spotify.me.id === response.owner.id),
|
||||
tracks,
|
||||
description,
|
||||
}));
|
||||
|
||||
@ -363,7 +363,7 @@ const formatArtist = function (data) {
|
||||
'biography_link',
|
||||
'biography_publish_date',
|
||||
'related_artists',
|
||||
'albums',
|
||||
'albums_uris',
|
||||
'tracks',
|
||||
];
|
||||
|
||||
@ -424,7 +424,7 @@ const formatPlaylist = function (data) {
|
||||
'followers',
|
||||
'last_modified',
|
||||
'can_edit',
|
||||
'owner',
|
||||
'user',
|
||||
'tracks',
|
||||
];
|
||||
|
||||
@ -466,8 +466,8 @@ const formatPlaylist = function (data) {
|
||||
playlist.last_modified = data.added_at;
|
||||
}
|
||||
|
||||
if (data.owner) {
|
||||
playlist.owner = {
|
||||
if (data.owner && playlist.user === undefined) {
|
||||
playlist.user = {
|
||||
id: data.owner.id,
|
||||
uri: data.owner.uri,
|
||||
name: (data.owner.display_name || data.owner.id),
|
||||
|
||||
@ -20,9 +20,6 @@ import Loader from '../components/Loader';
|
||||
import * as coreActions from '../services/core/actions';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as pusherActions from '../services/pusher/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import {
|
||||
uriSource,
|
||||
getFromUri,
|
||||
@ -548,9 +545,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
pusherActions: bindActionCreators(pusherActions, dispatch),
|
||||
lastfmActions: bindActionCreators(lastfmActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Artist);
|
||||
|
||||
@ -44,9 +44,9 @@ class Playlist extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { coreActions: { loadPlaylist }, uri } = this.props;
|
||||
const { coreActions: { loadItem }, uri } = this.props;
|
||||
this.setWindowTitle();
|
||||
loadPlaylist(uri);
|
||||
loadItem(uri);
|
||||
}
|
||||
|
||||
componentDidUpdate = ({
|
||||
@ -57,7 +57,7 @@ class Playlist extends React.Component {
|
||||
uri,
|
||||
playlist,
|
||||
coreActions: {
|
||||
loadPlaylist,
|
||||
loadItem,
|
||||
},
|
||||
history: {
|
||||
push,
|
||||
@ -69,7 +69,7 @@ class Playlist extends React.Component {
|
||||
}
|
||||
|
||||
if (uri !== prevUri) {
|
||||
loadPlaylist(uri);
|
||||
loadItem(uri);
|
||||
}
|
||||
|
||||
if (!prevPlaylist && playlist) this.setWindowTitle(playlist);
|
||||
|
||||
@ -13,26 +13,28 @@ import * as spotifyActions from '../../services/spotify/actions';
|
||||
import { sourceIcon, decodeMopidyUri } from '../../util/helpers';
|
||||
import { sortItems } from '../../util/arrays';
|
||||
import { I18n, i18n } from '../../locale';
|
||||
import { collate } from '../../util/format';
|
||||
|
||||
class AddToPlaylist extends React.Component {
|
||||
componentDidMount = () => {
|
||||
const {
|
||||
spotify_library_playlists_status,
|
||||
mopidy_library_playlists_status,
|
||||
spotify_library_playlists,
|
||||
mopidy_library_playlists,
|
||||
spotify_available,
|
||||
spotifyActions,
|
||||
mopidyActions,
|
||||
coreActions: {
|
||||
loadLibrary,
|
||||
},
|
||||
uiActions: {
|
||||
setWindowTitle,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if ((!spotify_library_playlists_status || spotify_library_playlists_status !== 'finished') && spotify_available) {
|
||||
spotifyActions.getLibraryPlaylists();
|
||||
if (!spotify_library_playlists && spotify_available) {
|
||||
loadLibrary('spotify:library:playlists');
|
||||
}
|
||||
|
||||
if ((!mopidy_library_playlists_status || mopidy_library_playlists_status !== 'finished')) {
|
||||
mopidyActions.getLibraryPlaylists();
|
||||
if (!mopidy_library_playlists) {
|
||||
loadLibrary('mopidy:library:playlists');
|
||||
}
|
||||
|
||||
setWindowTitle(i18n('modal.add_to_playlist.title'));
|
||||
@ -50,23 +52,20 @@ class AddToPlaylist extends React.Component {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
render = () =>{
|
||||
const { playlists, uris, spotify_library_playlists_status } = this.props;
|
||||
render = () => {
|
||||
const {
|
||||
uris,
|
||||
items,
|
||||
spotify_library,
|
||||
mopidy_library,
|
||||
} = this.props;
|
||||
|
||||
if (!playlists) return (
|
||||
<div className="empty">
|
||||
<I18n path="modal.add_to_playlist.no_editable_playlists" />
|
||||
</div>
|
||||
);
|
||||
let playlists = [
|
||||
...(collate(spotify_library, { items }).items),
|
||||
...(collate(mopidy_library, { items }).items),
|
||||
].filter((playlist) => playlist.can_edit);
|
||||
|
||||
let editablePlaylists = [];
|
||||
for (let uri in playlists) {
|
||||
if (playlists[uri].can_edit) editablePlaylists.push(playlists[uri]);
|
||||
}
|
||||
|
||||
editablePlaylists = sortItems(editablePlaylists, 'name');
|
||||
|
||||
const isLoading = spotify_library_playlists_status === 'running';
|
||||
playlists = sortItems(playlists, 'name');
|
||||
|
||||
return (
|
||||
<Modal className="modal--add-to-playlist">
|
||||
@ -78,34 +77,34 @@ class AddToPlaylist extends React.Component {
|
||||
plural={uris.length > 1 ? 's' : ''}
|
||||
/>
|
||||
</h2>
|
||||
{editablePlaylists.length <= 0 && (
|
||||
{playlists.length ? (
|
||||
<div className="list small playlists">
|
||||
{playlists.map((playlist) => (
|
||||
<div
|
||||
className="list__item"
|
||||
key={playlist.uri}
|
||||
onClick={() => this.playlistSelected(playlist.uri)}
|
||||
>
|
||||
<Thumbnail images={playlist.images} size="small" />
|
||||
<h4 className="list__item__name">{ playlist.name }</h4>
|
||||
<ul className="list__item__details details">
|
||||
<li><Icon type="fontawesome" className="source" name={sourceIcon(playlist.uri)} /></li>
|
||||
<li>
|
||||
{playlist.tracks_total && (
|
||||
<span className="mid_grey-text">
|
||||
{`${playlist.tracks_total} tracks`}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="no-results">
|
||||
<I18n path="modal.add_to_playlist.no_playlists" />
|
||||
</div>
|
||||
)}
|
||||
<div className="list small playlists">
|
||||
{editablePlaylists.map((playlist) => (
|
||||
<div
|
||||
className="list__item"
|
||||
key={playlist.uri}
|
||||
onClick={() => this.playlistSelected(playlist.uri)}
|
||||
>
|
||||
<Thumbnail images={playlist.images} size="small" />
|
||||
<h4 className="list__item__name">{ playlist.name }</h4>
|
||||
<ul className="list__item__details details">
|
||||
<li><Icon type="fontawesome" className="source" name={sourceIcon(playlist.uri)} /></li>
|
||||
<li>
|
||||
{playlist.tracks_total && (
|
||||
<span className="mid_grey-text">
|
||||
{`${playlist.tracks_total} tracks`}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{isLoading && <Loader body lazy loading />}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@ -114,11 +113,10 @@ class AddToPlaylist extends React.Component {
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
uris: (ownProps.match.params.uris ? decodeURIComponent(ownProps.match.params.uris).split(',') : []),
|
||||
mopidy_uri_schemes: state.mopidy.uri_schemes,
|
||||
mopidy_library_playlists: state.mopidy.library_playlists,
|
||||
mopidy_library_playlists_status: (state.ui.processes.MOPIDY_LIBRARY_PLAYLISTS_PROCESSOR !== undefined ? state.ui.processes.MOPIDY_LIBRARY_PLAYLISTS_PROCESSOR.status : null),
|
||||
items: state.core.items,
|
||||
mopidy_library: state.core.libraries['mopidy:library:playlists'] || { items_uris: [] },
|
||||
spotify_library: state.core.libraries['spotify:library:playlists'] || { items_uris: [] },
|
||||
spotify_available: state.spotify.access_token,
|
||||
spotify_library_playlists: state.spotify.library_playlists,
|
||||
spotify_library_playlists_status: (state.ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR !== undefined ? state.ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status : null),
|
||||
load_queue: state.ui.load_queue,
|
||||
me_id: (state.spotify.me ? state.spotify.me.id : false),
|
||||
playlists: state.core.playlists,
|
||||
|
||||
Reference in New Issue
Block a user