Using core.items to contain all loaded assets
This commit is contained in:
@ -167,6 +167,13 @@ export function loadUserPlaylists(uri, force_reload = false) {
|
||||
* We've got a loaded record, now we just need to plug it in to our state and stores.
|
||||
* */
|
||||
|
||||
export function itemLoaded(item) {
|
||||
return {
|
||||
type: 'ITEM_LOADED',
|
||||
item,
|
||||
};
|
||||
}
|
||||
|
||||
export function trackLoaded(track) {
|
||||
return tracksLoaded([track]);
|
||||
}
|
||||
@ -177,9 +184,6 @@ export function tracksLoaded(tracks) {
|
||||
};
|
||||
}
|
||||
|
||||
export function artistLoaded(artist) {
|
||||
return artistsLoaded([artist]);
|
||||
}
|
||||
export function artistsLoaded(artists) {
|
||||
return {
|
||||
type: 'ARTISTS_LOADED',
|
||||
|
||||
@ -399,7 +399,6 @@ const CoreMiddleware = (function () {
|
||||
if (action.item) {
|
||||
store.dispatch({
|
||||
type: 'RESTORED_FROM_COLD_STORE',
|
||||
item_type: uriType(action.item.uri),
|
||||
item: action.item,
|
||||
});
|
||||
}
|
||||
@ -618,40 +617,27 @@ const CoreMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'ITEM_LOADED':
|
||||
const mergedItem = {
|
||||
...core.items[action.item.uri] || {},
|
||||
...action.item,
|
||||
};
|
||||
store.dispatch(coreActions.updateColdStore([mergedItem]));
|
||||
next({
|
||||
...action,
|
||||
item: mergedItem,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'ARTISTS_LOADED':
|
||||
var artists_index = { ...core.artists };
|
||||
var artists_loaded = [];
|
||||
var tracks_loaded = [];
|
||||
|
||||
for (const raw_artist of action.artists) {
|
||||
console.log(raw_artist);
|
||||
var artist = formatArtist(raw_artist);
|
||||
|
||||
// Already have an artist in the index
|
||||
if (artists_index[artist.uri]) {
|
||||
// And we've already got some images, make sure we merge the arrays,
|
||||
// rather than overwriting
|
||||
if (artists_index[artist.uri].images && artist.images) {
|
||||
const existing_images = artists_index[artist.uri].images;
|
||||
let are_new_images = true;
|
||||
|
||||
// loop all extisting images to make sure we're not adding one that
|
||||
// we already have
|
||||
for (const existing_image of existing_images) {
|
||||
// We only need to check one size, the formatter should insist on consistency
|
||||
// Note that we depend on having a one-item array of images provided per action
|
||||
if (existing_image.huge == artist.images[0].huge) {
|
||||
are_new_images = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Only if they're new images should we merge them in
|
||||
if (are_new_images) {
|
||||
artist.images = Object.assign([], [...existing_images, ...artist.images]);
|
||||
}
|
||||
}
|
||||
|
||||
artist = { ...artists_index[artist.uri], ...artist };
|
||||
}
|
||||
artist = { ...artists_index[artist.uri], ...artist };
|
||||
/*
|
||||
// Migrate nested tracks objects into references to our tracks index
|
||||
if (raw_artist.tracks) {
|
||||
@ -665,10 +651,11 @@ const CoreMiddleware = (function () {
|
||||
}
|
||||
|
||||
action.artists = artists_loaded;
|
||||
|
||||
/*
|
||||
if (tracks_loaded.length > 0) {
|
||||
store.dispatch(coreActions.tracksLoaded(tracks_loaded));
|
||||
}
|
||||
*/
|
||||
|
||||
store.dispatch(coreActions.updateColdStore(artists_loaded));
|
||||
|
||||
|
||||
@ -66,6 +66,10 @@ export default function reducer(core = {}, action) {
|
||||
* and appended to their relevant index.
|
||||
* */
|
||||
|
||||
|
||||
case 'ITEM_LOADED':
|
||||
return { ...core, items: { ...core.items, [action.item.uri]: action.item } };
|
||||
|
||||
case 'TRACKS_LOADED':
|
||||
var tracks = { ...core.tracks };
|
||||
for (const track of action.tracks) {
|
||||
@ -141,44 +145,16 @@ export default function reducer(core = {}, action) {
|
||||
|
||||
|
||||
case 'RESTORED_FROM_COLD_STORE':
|
||||
switch (action.item_type) {
|
||||
case 'artist':
|
||||
return {
|
||||
...core,
|
||||
artists: {
|
||||
...core.artists,
|
||||
[action.item.uri]: {
|
||||
...core.artists[action.item.uri],
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
};
|
||||
case 'album':
|
||||
return {
|
||||
...core,
|
||||
albums: {
|
||||
...core.albums,
|
||||
[action.item.uri]: {
|
||||
...core.albums[action.item.uri],
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
};
|
||||
case 'playlist':
|
||||
return {
|
||||
...core,
|
||||
playlists: {
|
||||
...core.playlists,
|
||||
[action.item.uri]: {
|
||||
...core.albums[action.item.uri],
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
return core;
|
||||
}
|
||||
return {
|
||||
...core,
|
||||
items: {
|
||||
...core.items,
|
||||
[action.item.uri]: {
|
||||
...core.items[action.item.uri],
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an item from an index
|
||||
|
||||
@ -211,9 +211,6 @@ export function getTrack(uri) {
|
||||
dispatch(coreActions.trackLoaded(merged_track));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.info(`LastFM: No results for track '${track_name}' by '${artist_name}'`);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -231,21 +228,16 @@ export function getArtist(uri, artist, mbid = false) {
|
||||
.then(
|
||||
(response) => {
|
||||
if (response.artist) {
|
||||
const artist = {
|
||||
dispatch(coreActions.artistLoaded({
|
||||
uri,
|
||||
mbid: response.artist.mbid,
|
||||
biography: response.artist.bio.content,
|
||||
biography_publish_date: response.artist.bio.published,
|
||||
biography_link: response.artist.bio.links.link.href,
|
||||
listeners: parseInt(response.artist.stats.listeners),
|
||||
};
|
||||
|
||||
dispatch(coreActions.artistLoaded(artist));
|
||||
}));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.info(`LastFM: No results for artist '${artist}'`);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -284,9 +276,6 @@ export function getAlbum(uri, artist, album, mbid = false) {
|
||||
dispatch(coreActions.albumLoaded(album));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.info(`LastFM: No results for album '${album}'`);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
formatPlaylist,
|
||||
formatUser,
|
||||
formatAlbum,
|
||||
formatArtist,
|
||||
formatArtists,
|
||||
formatAlbums,
|
||||
} from '../../util/format';
|
||||
@ -1078,104 +1079,54 @@ export function getGenres() {
|
||||
* */
|
||||
export function getArtist(uri, full = false) {
|
||||
return (dispatch, getState) => {
|
||||
// Start with an empty object
|
||||
// As each requests completes, they'll add to this object
|
||||
let artist = {};
|
||||
|
||||
// We need our artist, obviously
|
||||
const requests = [
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}`, 'GET', false, true)
|
||||
.then(
|
||||
(response) => {
|
||||
artist = { ...artist, ...response };
|
||||
},
|
||||
),
|
||||
];
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}`, 'GET', false, true)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch(coreActions.itemLoaded(formatArtist(response)));
|
||||
},
|
||||
);
|
||||
|
||||
// Do we want a full artist, with all supporting material?
|
||||
if (full) {
|
||||
requests.push(
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`)
|
||||
.then(
|
||||
(response) => {
|
||||
artist = { ...artist, tracks: formatTracks(response.tracks) };
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
requests.push(
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/related-artists`)
|
||||
.then(
|
||||
(response) => {
|
||||
artist = { ...artist, related_artists: formatArtists(response.artists) };
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$.when.apply($, requests).then(() => {
|
||||
dispatch(coreActions.artistLoaded(artist));
|
||||
|
||||
// Get supporting biography and imagery
|
||||
if (artist.musicbrainz_id) {
|
||||
dispatch(lastfmActions.getArtist(artist.uri, false, artist.musicbrainz_id));
|
||||
} else {
|
||||
dispatch(lastfmActions.getArtist(artist.uri, artist.name.replace('&', 'and')));
|
||||
}
|
||||
|
||||
// Get all their albums
|
||||
if (full) {
|
||||
let albums = [];
|
||||
const fetchAlbums = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
albums = [...albums, ...formatAlbums(response.items)];
|
||||
if (response.next) {
|
||||
fetchAlbums(response.next);
|
||||
} else {
|
||||
dispatch(coreActions.artistLoaded({
|
||||
uri: artist.uri,
|
||||
albums,
|
||||
}));
|
||||
}
|
||||
});
|
||||
fetchAlbums(`artists/${getFromUri('artistid', uri)}/albums?market=${getState().spotify.country}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function getArtists(uris) {
|
||||
return (dispatch, getState) => {
|
||||
// now get all the artists for this album (full objects)
|
||||
let ids = '';
|
||||
for (let i = 0; i < uris.length; i++) {
|
||||
if (ids != '') ids += ',';
|
||||
ids += getFromUri('artistid', uris[i]);
|
||||
}
|
||||
|
||||
request(dispatch, getState, `artists/?ids=${ids}`)
|
||||
.then(
|
||||
(response) => {
|
||||
for (var i = i; i < response.length; i++) {
|
||||
const artist = response;
|
||||
for (var i = 0; i < artist.albums.length; i++) {
|
||||
dispatch({
|
||||
type: 'ALBUM_LOADED',
|
||||
album: artist.albums[i],
|
||||
});
|
||||
}
|
||||
artist.albums_uris = arrayOf('uri', artist.albums);
|
||||
artist.albums_more = artist.albums.next;
|
||||
dispatch(coreActions.artistLoaded(artist));
|
||||
// All albums (gets all pages, may take some time to iterate them all)
|
||||
let albums = [];
|
||||
const fetchAlbums = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
albums = [...albums, ...formatAlbums(response.items)];
|
||||
if (response.next) {
|
||||
fetchAlbums(response.next);
|
||||
} else {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
albums,
|
||||
}));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load artists',
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
});
|
||||
fetchAlbums(`artists/${getFromUri('artistid', uri)}/albums?limit=50&include_groups=album,single&market=${getState().spotify.country}`);
|
||||
|
||||
// Get top tracks
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
tracks: formatTracks(response.tracks),
|
||||
}));
|
||||
},
|
||||
);
|
||||
|
||||
// Related artists
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/related-artists`)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
related_artists: formatArtists(response.artists),
|
||||
}));
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -1202,7 +1153,6 @@ export function getArtistImages(artist) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function playArtistTopTracks(uri) {
|
||||
return (dispatch, getState) => {
|
||||
const { artists } = getState().core;
|
||||
@ -1220,12 +1170,6 @@ export function playArtistTopTracks(uri) {
|
||||
const uris = arrayOf('uri', response.tracks);
|
||||
dispatch(mopidyActions.playURIs(uris, uri));
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not play artist\'s top tracks',
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1306,47 +1250,28 @@ export function getAlbum(uri) {
|
||||
request(dispatch, getState, `albums/${getFromUri('albumid', uri)}`)
|
||||
.then(
|
||||
(response) => {
|
||||
// dispatch our loaded artists (simple objects)
|
||||
dispatch(coreActions.artistsLoaded(response.artists));
|
||||
|
||||
const tracks = Object.assign([], response.tracks.items);
|
||||
|
||||
const album = {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
...formatAlbum(response),
|
||||
artists_uris: arrayOf('uri', response.artists),
|
||||
tracks_more: response.tracks.next,
|
||||
tracks_total: response.tracks.total,
|
||||
};
|
||||
album.tracks = tracks.map((track) => ({
|
||||
...track,
|
||||
album: {
|
||||
name: album.name,
|
||||
uri: album.uri,
|
||||
},
|
||||
tracks: formatTracks(response.tracks.items),
|
||||
}));
|
||||
|
||||
dispatch(coreActions.albumLoaded(album));
|
||||
let tracks = formatTracks(response.tracks.items);
|
||||
const fetchTracks = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
tracks = [...tracks, ...formatTracks(response.items)];
|
||||
if (response.next) {
|
||||
fetchTracks(response.next);
|
||||
} else {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
tracks,
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// now get all the artists for this album (full objects)
|
||||
// we do this to get the artist artwork
|
||||
const artist_ids = [];
|
||||
for (var i = 0; i < response.artists.length; i++) {
|
||||
artist_ids.push(getFromUri('artistid', response.artists[i].uri));
|
||||
if (response.tracks.next) {
|
||||
fetchTracks(response.tracks.next);
|
||||
}
|
||||
|
||||
// get all album artists as full objects
|
||||
request(dispatch, getState, `artists/?ids=${artist_ids}`)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch(coreActions.artistsLoaded(response.artists));
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load album\'s artists',
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
|
||||
@ -41,6 +41,7 @@ let state = {
|
||||
playlists: {},
|
||||
users: {},
|
||||
tracks: {},
|
||||
items: {},
|
||||
http_streaming_enabled: false,
|
||||
http_streaming_cachebuster: null,
|
||||
http_streaming_url: `http://${window.location.hostname}:8000/mopidy`,
|
||||
@ -151,6 +152,7 @@ const corePersistConfig = {
|
||||
storage: localForage,
|
||||
debug: window.test_mode,
|
||||
blacklist: [
|
||||
'items',
|
||||
'albums',
|
||||
'artists',
|
||||
'playlists',
|
||||
|
||||
@ -84,7 +84,7 @@ export class Album extends React.Component {
|
||||
}
|
||||
|
||||
// We have just received our full album or our album artists
|
||||
if ((!prevAlbum && album) || (!prevAlbum.artists && album.artists)) {
|
||||
if ((!prevAlbum && album)) {
|
||||
if (album.artists && album.wiki === undefined) {
|
||||
getAlbum(album.uri, album.artists[0].name, album.name);
|
||||
}
|
||||
@ -216,7 +216,7 @@ export class Album extends React.Component {
|
||||
<li>
|
||||
{i18n(
|
||||
'specs.tracks',
|
||||
{ count: album.tracks_total || album.tracks.length },
|
||||
{ count: album.tracks.length },
|
||||
)}
|
||||
</li>
|
||||
) : null}
|
||||
@ -295,10 +295,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
load_queue: state.ui.load_queue,
|
||||
tracks: state.core.tracks,
|
||||
artists: state.core.artists,
|
||||
album:
|
||||
state.core.albums && state.core.albums[uri] !== undefined
|
||||
? state.core.albums[uri]
|
||||
: false,
|
||||
album: (state.core.items[uri] || null),
|
||||
albums: state.core.albums,
|
||||
spotify_library_albums: state.spotify.library_albums,
|
||||
local_library_albums: state.mopidy.library_albums,
|
||||
|
||||
@ -146,29 +146,26 @@ class Artist extends React.Component {
|
||||
const {
|
||||
uri,
|
||||
uiActions,
|
||||
artist: artistProp,
|
||||
artists,
|
||||
albums,
|
||||
tracks,
|
||||
artist: {
|
||||
tracks = [],
|
||||
related_artists = [],
|
||||
} = {},
|
||||
sort,
|
||||
sort_reverse,
|
||||
filter,
|
||||
} = this.props;
|
||||
const artist = collate(
|
||||
artistProp,
|
||||
{
|
||||
artists,
|
||||
albums,
|
||||
tracks,
|
||||
},
|
||||
);
|
||||
let {
|
||||
artist: {
|
||||
albums = [],
|
||||
} = {},
|
||||
} = this.props;
|
||||
|
||||
if (sort && artist.albums) {
|
||||
artist.albums = sortItems(artist.albums, sort, sort_reverse);
|
||||
if (sort && albums) {
|
||||
albums = sortItems(albums, sort, sort_reverse);
|
||||
}
|
||||
|
||||
if (filter && artist.albums) {
|
||||
artist.albums = applyFilter('type', filter, artist.albums);
|
||||
if (filter && albums) {
|
||||
albums = applyFilter('type', filter, albums);
|
||||
}
|
||||
|
||||
const sort_options = [
|
||||
@ -211,21 +208,21 @@ class Artist extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="body overview">
|
||||
<div className={`top-tracks col col--w${artist.related_artists && artist.related_artists.length > 0 ? '70' : '100'}`}>
|
||||
{artist.tracks && <h4><I18n path="artist.overview.top_tracks" /></h4>}
|
||||
<div className={`top-tracks col col--w${related_artists && related_artists.length > 0 ? '70' : '100'}`}>
|
||||
{tracks && <h4><I18n path="artist.overview.top_tracks" /></h4>}
|
||||
<div className="list-wrapper">
|
||||
<TrackList className="artist-track-list" uri={artist.uri} tracks={artist.tracks ? artist.tracks.splice(0, 10) : []} />
|
||||
<TrackList className="artist-track-list" uri={uri} tracks={tracks ? tracks.slice(0, 10) : []} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col col--w5" />
|
||||
|
||||
{artist.related_artists && artist.related_artists.length > 0 && (
|
||||
{related_artists && related_artists.length > 0 && (
|
||||
<div className="col col--w25 related-artists">
|
||||
<h4><I18n path="artist.overview.related_artists.title" /></h4>
|
||||
<div className="list-wrapper">
|
||||
<RelatedArtists
|
||||
artists={artist.related_artists.slice(0, 6)}
|
||||
artists={related_artists.slice(0, 6)}
|
||||
uiActions={uiActions}
|
||||
/>
|
||||
</div>
|
||||
@ -240,7 +237,7 @@ class Artist extends React.Component {
|
||||
|
||||
<div className="cf" />
|
||||
|
||||
{artist.albums && (
|
||||
{albums && (
|
||||
<div className="albums">
|
||||
<h4>
|
||||
<div><I18n path="artist.overview.albums" /></div>
|
||||
@ -275,12 +272,7 @@ class Artist extends React.Component {
|
||||
</h4>
|
||||
|
||||
<section className="grid-wrapper no-top-padding">
|
||||
<AlbumGrid albums={artist.albums} />
|
||||
<LazyLoadListener
|
||||
loadKey={artist.albums_more}
|
||||
showLoader={artist.albums_more}
|
||||
loadMore={this.loadMore}
|
||||
/>
|
||||
<AlbumGrid albums={albums} />
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
@ -289,31 +281,12 @@ class Artist extends React.Component {
|
||||
}
|
||||
|
||||
renderTracks = () => {
|
||||
const {
|
||||
artist: artistProp,
|
||||
artists,
|
||||
tracks,
|
||||
} = this.props;
|
||||
|
||||
const artist = collate(
|
||||
artistProp,
|
||||
{
|
||||
artists,
|
||||
tracks,
|
||||
},
|
||||
);
|
||||
|
||||
const is_loading_tracks = (
|
||||
!artist.tracks_uris
|
||||
|| (artist.tracks_uris && !artist.tracks)
|
||||
|| (artist.tracks_uris.length !== artist.tracks.length)
|
||||
);
|
||||
const { artist: { uri, tracks } } = this.props;
|
||||
|
||||
return (
|
||||
<div className="body related-artists">
|
||||
<section className="list-wrapper no-top-padding">
|
||||
<TrackList className="artist-track-list" uri={artist.uri} tracks={artist.tracks} />
|
||||
<LazyLoadListener showLoader={is_loading_tracks} />
|
||||
<TrackList className="artist-track-list" uri={uri} tracks={tracks} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
@ -559,7 +532,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
theme: state.ui.theme,
|
||||
slim_mode: state.ui.slim_mode,
|
||||
load_queue: state.ui.load_queue,
|
||||
artist: (state.core.artists[uri] !== undefined ? state.core.artists[uri] : false),
|
||||
artist: (state.core.items[uri] !== undefined ? state.core.items[uri] : false),
|
||||
tracks: state.core.tracks,
|
||||
artists: state.core.artists,
|
||||
spotify_library_artists: state.spotify.library_artists,
|
||||
|
||||
Reference in New Issue
Block a user