Playlist and Users data storage overhaul

This commit is contained in:
James Barnsley
2018-08-31 16:04:55 +12:00
parent 02ab0616fa
commit dbb5effbb9
21 changed files with 15035 additions and 11241 deletions

View File

@ -6046,9 +6046,11 @@ main header {
height: 0.5em; height: 0.5em;
margin: 0.25em 1px; margin: 0.25em 1px;
background: #cecac4; background: #cecac4;
opacity: 0.25; } opacity: 0.2; }
.popularity-bars .bar.filled { .popularity-bars .bar.filled {
opacity: 1; } opacity: 1; }
.light-theme .popularity-bars .bar {
background: #121212; }
.popularity-value { .popularity-value {
display: none; } display: none; }
@ -6422,7 +6424,8 @@ main .track-view {
height: 4rem; height: 4rem;
margin-top: -1rem; } margin-top: -1rem; }
.search-view .search-form input:focus, .search-view .search-form input:active { .search-view .search-form input:focus, .search-view .search-form input:active {
border-color: #08d58f; } border-color: #08d58f;
background: transparent; }
.search-view .search-result-sections { .search-view .search-result-sections {
padding-bottom: 30px; } padding-bottom: 30px; }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -98,7 +98,7 @@
// Release details // Release details
// These are automatically injected to built HTML // These are automatically injected to built HTML
var build = "1535617670"; var build = "1535658713";
var version = "3.25.1"; var version = "3.25.1";
// Construct the script tag // Construct the script tag

View File

@ -35,7 +35,7 @@ class AlbumGrid extends React.Component{
<div className={className}> <div className={className}>
{ {
this.props.albums.map(item => { this.props.albums.map(item => {
var album = helpers.collateObject(item, {artists: this.props.artists}); var album = helpers.collate(item, {artists: this.props.artists});
return ( return (
<GridItem <GridItem
key={album.uri} key={album.uri}

View File

@ -50,7 +50,7 @@ class ArtistGrid extends React.Component{
<GridItem <GridItem
key={artist.uri} key={artist.uri}
type="artist" type="artist"
item={artist} item={helpers.collate(artist, {albums: this.props.albums})}
show_source_icon={this.props.show_source_icon} show_source_icon={this.props.show_source_icon}
onClick={e => {hashHistory.push(global.baseURL+'artist/'+encodeURIComponent(artist.uri))}} onClick={e => {hashHistory.push(global.baseURL+'artist/'+encodeURIComponent(artist.uri))}}
lastfmActions={this.props.lastfmActions} lastfmActions={this.props.lastfmActions}
@ -69,7 +69,9 @@ class ArtistGrid extends React.Component{
} }
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
return {} return {
albums: state.core.albums
}
} }
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {

View File

@ -55,7 +55,7 @@ export default class GridItem extends React.Component{
case 'artist': case 'artist':
return ( return (
<span> <span>
{item.followers ? item.followers.total.toLocaleString()+' followers' : null} {item.followers ? item.followers.toLocaleString()+' followers' : null}
{item.albums_uris ? item.albums_uris.length+' albums' : null} {item.albums_uris ? item.albums_uris.length+' albums' : null}
</span> </span>
) )
@ -73,7 +73,7 @@ export default class GridItem extends React.Component{
return ( return (
<span> <span>
{ item.artists ? <ArtistSentence artists={ item.artists } /> : null } { item.artists ? <ArtistSentence artists={ item.artists } /> : null }
{ item.followers ? item.followers.total.toLocaleString()+' followers' : null } { item.followers ? item.followers.toLocaleString()+' followers' : null }
</span> </span>
) )
} }

View File

@ -132,6 +132,10 @@ export let sizedImages = function(images){
huge: false huge: false
} }
if (!images){
return sizes;
}
// An array of images has been provided // An array of images has been provided
if (Array.isArray(images)){ if (Array.isArray(images)){
@ -314,9 +318,9 @@ export let formatAlbum = function(data){
var album = {}; var album = {};
var fields = [ var fields = [
'uri', 'uri',
'provider',
'name', 'name',
'type', 'type',
'provider',
'artists_uris', 'artists_uris',
'tracks_uris', 'tracks_uris',
'release_date', 'release_date',
@ -347,15 +351,17 @@ export let formatArtist = function(data){
var artist = {} var artist = {}
var fields = [ var fields = [
'uri', 'uri',
'provider',
'mbid',
'name', 'name',
'type', 'type',
'provider',
'popularity',
'images', 'images',
'popularity',
'followers',
'listeners',
'biography', 'biography',
'biography_link', 'biography_link',
'biography_publish_date', 'biography_publish_date',
'followers',
'related_artists_uris', 'related_artists_uris',
'albums_uris', 'albums_uris',
'tracks_uris' 'tracks_uris'
@ -388,6 +394,78 @@ export let formatArtist = function(data){
} }
/**
* Format our playlist objects into a universal format
*
* @param data obj
* @return playlist obj
**/
export let formatPlaylist = function(data){
var playlist = {}
var fields = [
'uri',
'provider',
'type',
'name',
'description',
'images',
'popularity',
'followers',
'last_modified_date',
'can_edit',
'user_uri',
'tracks_uris'
];
// Loop fields and import from data
for (var field of fields){
if (data.hasOwnProperty(field)){
playlist[field] = data[field];
}
}
if (data.followers && data.followers.total){
playlist.followers = data.followers.total;
}
return playlist;
}
/**
* Format a user objects into a universal format
*
* @param data obj
* @return playlist obj
**/
export let formatUser = function(data){
var user = {}
var fields = [
'uri',
'provider',
'name',
'images',
'followers'
];
// Loop fields and import from data
for (var field of fields){
if (data.hasOwnProperty(field)){
user[field] = data[field];
}
}
if (data.followers && data.followers.total){
user.followers = data.followers.total;
}
if (data.display_name && !user.name) user.name = data.display_name;
if (data.id && !user.name) user.name = data.id;
return user;
}
/** /**
* Format tracks into our universal format * Format tracks into our universal format
* *
@ -475,7 +553,7 @@ export let formatTracks = function(tracks){
* @param indexes Obj (the relevant core indexes) * @param indexes Obj (the relevant core indexes)
* @return object Obj * @return object Obj
**/ **/
export let collateObject = function(obj, indexes = {}){ export let collate = function(obj, indexes = {}){
// First, let's reset this object // First, let's reset this object
// This is important because by changing this object, we inadvertently // This is important because by changing this object, we inadvertently
@ -486,12 +564,12 @@ export let collateObject = function(obj, indexes = {}){
// Setup empty arrays for the appropriate reference objects // Setup empty arrays for the appropriate reference objects
// This helps create a consistent object structure // This helps create a consistent object structure
if (obj.artists_uris !== undefined) obj.artists = []; if (obj.artists_uris !== undefined) obj.artists = [];
if (obj.albums_uris !== undefined) obj.albums = []; if (obj.albums_uris !== undefined) obj.albums = [];
if (obj.tracks_uris !== undefined) obj.tracks = []; if (obj.tracks_uris !== undefined) obj.tracks = [];
if (obj.users_uris !== undefined) obj.users = []; if (obj.users_uris !== undefined) obj.users = [];
if (obj.playlists_uris !== undefined) obj.playlists = [];
if (indexes.artists){ if (indexes.artists){
if (obj.artists_uris){ if (obj.artists_uris){
for (var uri of obj.artists_uris){ for (var uri of obj.artists_uris){
if (indexes.artists[uri]){ if (indexes.artists[uri]){
@ -499,7 +577,6 @@ export let collateObject = function(obj, indexes = {}){
} }
} }
} }
if (obj.artist_uri){ if (obj.artist_uri){
if (indexes.artists[obj.artist_uri]){ if (indexes.artists[obj.artist_uri]){
obj.artist = indexes.artists[obj.artist_uri]; obj.artist = indexes.artists[obj.artist_uri];
@ -515,7 +592,6 @@ export let collateObject = function(obj, indexes = {}){
} }
} }
} }
if (obj.album_uri){ if (obj.album_uri){
if (indexes.albums[obj.album_uri]){ if (indexes.albums[obj.album_uri]){
obj.album = indexes.albums[obj.album_uri]; obj.album = indexes.albums[obj.album_uri];
@ -524,7 +600,6 @@ export let collateObject = function(obj, indexes = {}){
} }
if (indexes.tracks){ if (indexes.tracks){
if (obj.tracks_uris){ if (obj.tracks_uris){
for (var uri of obj.tracks_uris){ for (var uri of obj.tracks_uris){
if (indexes.tracks[uri]){ if (indexes.tracks[uri]){
@ -532,7 +607,6 @@ export let collateObject = function(obj, indexes = {}){
} }
} }
} }
if (obj.track_uri){ if (obj.track_uri){
if (indexes.tracks[obj.track_uri]){ if (indexes.tracks[obj.track_uri]){
obj.track = indexes.tracks[obj.track_uri]; obj.track = indexes.tracks[obj.track_uri];
@ -548,7 +622,6 @@ export let collateObject = function(obj, indexes = {}){
} }
} }
} }
if (obj.user_uri){ if (obj.user_uri){
if (indexes.users[obj.user_uri]){ if (indexes.users[obj.user_uri]){
obj.user = indexes.users[obj.user_uri]; obj.user = indexes.users[obj.user_uri];
@ -556,6 +629,21 @@ export let collateObject = function(obj, indexes = {}){
} }
} }
if (indexes.playlists){
if (obj.playlists_uris){
for (var uri of obj.playlists_uris){
if (indexes.playlists[uri]){
obj.playlists.push(indexes.playlists[uri]);
}
}
}
if (obj.playlist_uri){
if (indexes.playlists[obj.playlist_uri]){
obj.playlist = indexes.playlists[obj.playlist_uri];
}
}
}
return obj; return obj;
} }

View File

@ -86,38 +86,51 @@ export function cachebustHttpStream(){
* relevant service to load the record - all from one neat package. * relevant service to load the record - all from one neat package.
**/ **/
export function loadTrack(uri){ export function loadTrack(uri, force_reload = false){
return { return {
type: 'LOAD_TRACK', type: 'LOAD_TRACK',
uri: uri uri: uri,
force_reload: force_reload
} }
} }
export function loadAlbum(uri){ export function loadAlbum(uri, force_reload = false){
return { return {
type: 'LOAD_ALBUM', type: 'LOAD_ALBUM',
uri: uri uri: uri,
force_reload: force_reload
} }
} }
export function loadArtist(uri){ export function loadArtist(uri, force_reload = false){
return { return {
type: 'LOAD_ARTIST', type: 'LOAD_ARTIST',
uri: uri uri: uri,
force_reload: force_reload
} }
} }
export function loadPlaylist(uri){ export function loadPlaylist(uri, force_reload = false){
return { return {
type: 'LOAD_PLAYLIST', type: 'LOAD_PLAYLIST',
uri: uri uri: uri,
force_reload: force_reload
} }
} }
export function loadUser(uri){ export function loadUser(uri, force_reload = false){
return { return {
type: 'LOAD_USER', type: 'LOAD_USER',
uri: uri uri: uri,
force_reload: force_reload
}
}
export function loadUserPlaylists(uri, force_reload = false){
return {
type: 'LOAD_USER_PLAYLISTS',
uri: uri,
force_reload: force_reload
} }
} }
@ -177,6 +190,15 @@ export function usersLoaded(users){
users: users users: users
} }
} }
export function userPlaylistsLoaded(uri, playlists, more = null, total = null){
return {
type: 'USER_PLAYLISTS_LOADED',
uri: uri,
playlists: playlists,
more: more,
total: total
}
}
export function loadedMore(parent_type, parent_key, records_type, records_data){ export function loadedMore(parent_type, parent_key, records_type, records_data){
return { return {

View File

@ -357,6 +357,14 @@ const CoreMiddleware = (function(){
case 'LOAD_TRACK': case 'LOAD_TRACK':
if (
!action.force_reload &&
store.getState().core.tracks[action.uri]){
console.info('Loading "'+action.uri+'" from index');
break;
}
switch (helpers.uriSource(action.uri)){ switch (helpers.uriSource(action.uri)){
case 'spotify': case 'spotify':
store.dispatch(spotifyActions.getTrack(action.uri)); store.dispatch(spotifyActions.getTrack(action.uri));
@ -374,6 +382,15 @@ const CoreMiddleware = (function(){
break; break;
case 'LOAD_ALBUM': case 'LOAD_ALBUM':
if (
!action.force_reload &&
store.getState().core.albums[action.uri] &&
store.getState().core.albums[action.uri].tracks_uris){
console.info('Loading "'+action.uri+'" from index');
break;
}
switch (helpers.uriSource(action.uri)){ switch (helpers.uriSource(action.uri)){
case 'spotify': case 'spotify':
store.dispatch(spotifyActions.getAlbum(action.uri)); store.dispatch(spotifyActions.getAlbum(action.uri));
@ -391,6 +408,16 @@ const CoreMiddleware = (function(){
break; break;
case 'LOAD_ARTIST': case 'LOAD_ARTIST':
if (
!action.force_reload &&
store.getState().core.artists[action.uri] &&
store.getState().core.artists[action.uri].albums_uris &&
store.getState().core.artists[action.uri].tracks_uris){
console.info('Loading "'+action.uri+'" from index');
break;
}
switch (helpers.uriSource(action.uri)){ switch (helpers.uriSource(action.uri)){
case 'spotify': case 'spotify':
store.dispatch(spotifyActions.getArtist(action.uri, true)); store.dispatch(spotifyActions.getArtist(action.uri, true));
@ -408,6 +435,15 @@ const CoreMiddleware = (function(){
break; break;
case 'LOAD_PLAYLIST': case 'LOAD_PLAYLIST':
if (
!action.force_reload &&
store.getState().core.playlists[action.uri] &&
store.getState().core.playlists[action.uri].tracks_uris){
console.info('Loading "'+action.uri+'" from index');
break;
}
switch (helpers.uriSource(action.uri)){ switch (helpers.uriSource(action.uri)){
case 'spotify': case 'spotify':
store.dispatch(spotifyActions.getPlaylist(action.uri)); store.dispatch(spotifyActions.getPlaylist(action.uri));
@ -424,6 +460,46 @@ const CoreMiddleware = (function(){
next(action); next(action);
break; break;
case 'LOAD_USER':
console.log(action);
if (
!action.force_reload &&
store.getState().core.playlists[action.uri] &&
store.getState().core.playlists[action.uri].albums_uris ){
console.info('Loading "'+action.uri+'" from index');
break;
}
switch (helpers.uriSource(action.uri)){
case 'spotify':
store.dispatch(spotifyActions.getUser(action.uri));
store.dispatch(spotifyActions.following(action.uri));
break;
default:
// No Mopidy mechanism for users
break;
}
next(action);
break;
case 'LOAD_USER_PLAYLISTS':
switch (helpers.uriSource(action.uri)){
case 'spotify':
store.dispatch(spotifyActions.getUserPlaylists(action.uri));
break;
default:
// No Mopidy mechanism for users
break;
}
next(action);
break;
/** /**
* Index actions * Index actions
@ -537,7 +613,9 @@ const CoreMiddleware = (function(){
var playlists_loaded = []; var playlists_loaded = [];
var tracks_loaded = []; var tracks_loaded = [];
action.playlists.forEach(playlist => { for (var playlist of action.playlists){
playlist = helpers.formatPlaylist(playlist);
// Detect editability // Detect editability
switch (helpers.uriSource(playlist.uri)){ switch (helpers.uriSource(playlist.uri)){
@ -567,7 +645,7 @@ const CoreMiddleware = (function(){
// Update index // Update index
playlists_loaded.push(playlist); playlists_loaded.push(playlist);
}); }
action.playlists = playlists_loaded; action.playlists = playlists_loaded;
@ -581,20 +659,25 @@ const CoreMiddleware = (function(){
var users_index = Object.assign({}, core.users); var users_index = Object.assign({}, core.users);
var users_loaded = []; var users_loaded = [];
action.users.forEach(user => { for (var user of action.users){
if (users_index[user.uri]){ if (users_index[user.uri]){
user = Object.assign({}, users_index[user.uri], user); user = Object.assign({}, users_index[user.uri], user);
} }
users_loaded.push(user); users_loaded.push(user);
}); }
action.users = users_loaded; action.users = users_loaded;
next(action); next(action);
break; break;
case 'USER_PLAYLISTS_LOADED':
store.dispatch(coreActions.playlistsLoaded(action.playlists));
next(action);
break;
/** /**
* Loaded more linked assets * Loaded more linked assets
* Often fired during lazy-loading or async asset grabbing. * Often fired during lazy-loading or async asset grabbing.

View File

@ -124,37 +124,37 @@ export default function reducer(core = {}, action){
case 'TRACKS_LOADED': case 'TRACKS_LOADED':
var tracks = Object.assign({}, core.tracks); var tracks = Object.assign({}, core.tracks);
action.tracks.forEach(track => { for (var track of action.tracks){
tracks[track.uri] = track; tracks[track.uri] = track;
}); }
return Object.assign({}, core, { tracks: tracks }); return Object.assign({}, core, { tracks: tracks });
case 'ALBUMS_LOADED': case 'ALBUMS_LOADED':
var albums = Object.assign({}, core.albums); var albums = Object.assign({}, core.albums);
action.albums.forEach(album => { for (var album of action.albums){
albums[album.uri] = album; albums[album.uri] = album;
}); }
return Object.assign({}, core, { albums: albums }); return Object.assign({}, core, { albums: albums });
case 'ARTISTS_LOADED': case 'ARTISTS_LOADED':
var artists = Object.assign({}, core.artists); var artists = Object.assign({}, core.artists);
action.artists.forEach(artist => { for (var artist of action.artists){
artists[artist.uri] = artist; artists[artist.uri] = artist;
}); }
return Object.assign({}, core, { artists: artists }); return Object.assign({}, core, { artists: artists });
case 'PLAYLISTS_LOADED': case 'PLAYLISTS_LOADED':
var playlists = Object.assign({}, core.playlists); var playlists = Object.assign({}, core.playlists);
action.playlists.forEach(playlist => { for (var playlist of action.playlists){
playlists[playlist.uri] = playlist; playlists[playlist.uri] = playlist;
}); }
return Object.assign({}, core, { playlists: playlists }); return Object.assign({}, core, { playlists: playlists });
case 'USERS_LOADED': case 'USERS_LOADED':
var users = Object.assign({}, core.users); var users = Object.assign({}, core.users);
action.users.forEach(user => { for (var user of action.users){
users[user.uri] = user; users[user.uri] = user;
}); }
return Object.assign({}, core, { users: users }); return Object.assign({}, core, { users: users });
@ -167,8 +167,10 @@ export default function reducer(core = {}, action){
}); });
} }
var new_releases = [] var new_releases = [];
if (core.new_releases) new_releases = Object.assign([], core.new_releases) if (core.new_releases){
new_releases = Object.assign([], core.new_releases);
}
return Object.assign({}, core, { return Object.assign({}, core, {
new_releases: [...new_releases, ...action.uris], new_releases: [...new_releases, ...action.uris],
@ -199,19 +201,23 @@ export default function reducer(core = {}, action){
case 'USER_PLAYLISTS_LOADED': case 'USER_PLAYLISTS_LOADED':
var users = Object.assign({}, core.users) var users = Object.assign({}, core.users)
var playlists_uris = [] var existing_playlists_uris = [];
if (users[action.key] && users[action.key].playlists_uris) playlists_uris = users[action.key].playlists_uris if (users[action.uri] && users[action.uri].playlists_uris){
existing_playlists_uris = users[action.uri].playlists_uris;
}
var artist = Object.assign( var playlists_uris = [...existing_playlists_uris, ...helpers.arrayOf('uri',action.playlists)]
var user = Object.assign(
{}, {},
users[action.key], users[action.uri],
{ {
playlists_uris: [...playlists_uris, ...action.uris], playlists_uris: playlists_uris,
playlists_more: action.more, playlists_more: action.more,
playlists_total: action.total playlists_total: action.total
} }
) )
users[action.key] = artist users[action.uri] = user;
return Object.assign({}, core, { users: users }); return Object.assign({}, core, { users: users });

View File

@ -239,10 +239,10 @@ export function getArtist(uri, artist, mbid = false){
uri: uri, uri: uri,
images: response.artist.image, images: response.artist.image,
mbid: response.artist.mbid, mbid: response.artist.mbid,
bio: response.artist.bio, biography: response.artist.bio.content,
listeners: parseInt(response.artist.stats.listeners), biography_publish_date: response.artist.bio.published,
play_count: parseInt(response.artist.stats.playcount), biography_link: response.artist.bio.links.link.href,
on_tour: response.artist.stats.ontour listeners: parseInt(response.artist.stats.listeners)
}; };
dispatch(coreActions.artistLoaded(artist)); dispatch(coreActions.artistLoaded(artist));

View File

@ -1750,26 +1750,43 @@ const MopidyMiddleware = (function(){
.then(response => { .then(response => {
if (response.length <= 0) return if (response.length <= 0) return
var albums = [] var albums_loaded = [];
var artists_loaded = [];
var tracks_loaded = [];
for (var uri in response){ for (var uri in response){
if (response.hasOwnProperty(uri) && response[uri].length > 0 && response[uri][0] && response[uri][0].album){ if (response.hasOwnProperty(uri) && response[uri].length > 0 && response[uri][0] && response[uri][0].album){
var artists_uris = [];
for (var artist in response[uri][0].artists){
artists_loaded.push(helpers.formatArtist(artist));
artists_uris.push(artist.uri);
}
var tracks_uris = [];
for (var track in response[uri]){
tracks_loaded.push(helpers.formatTrack(track));
tracks_uris.push(track.uri);
}
var album = Object.assign( var album = Object.assign(
{}, {},
{ {
source: 'local', source: 'local',
artists: response[uri][0].artists, artists_uris: artists_uris,
tracks: response[uri], tracks_uris: tracks_uris,
tracks_total: response[uri].length tracks_total: tracks_uris.length
}, },
response[uri][0].album response[uri][0].album
) )
albums.push(album) albums_loaded.push(album);
} }
} }
store.dispatch(coreActions.albumsLoaded(albums)); store.dispatch(coreActions.albumsLoaded(albums_loaded));
store.dispatch(coreActions.artistsLoaded(artists_loaded));
store.dispatch(coreActions.tracksLoaded(tracks_loaded));
// Re-run any consequential processes in 100ms. This allows a small window for other // Re-run any consequential processes in 100ms. This allows a small window for other
// server requests before our next batch. It's a little crude but it means the server isn't // server requests before our next batch. It's a little crude but it means the server isn't
@ -1788,26 +1805,31 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_GET_ALBUM': case 'MOPIDY_GET_ALBUM':
request(socket, store, 'library.lookup', action.data) request(socket, store, 'library.lookup', action.data)
.then(response => { .then(response => {
if (response.length <= 0){ if (!response || response.length <= 0){
return; return;
} }
var artists = [];
for (var artist of response[0].artists){
artists.push(helpers.formatArtist(artist));
}
var album = Object.assign( var album = Object.assign(
{}, {},
{ images: [] }, {
response[0].album, images: []
},
helpers.formatAlbum(response[0].album),
{ {
source: 'local', source: 'local',
artists: response[0].artists, artists_uris: helpers.arrayOf('uri', artists),
tracks: response, tracks_uris: helpers.arrayOf('uri', response),
tracks_total: response.length tracks_total: response.length
} }
) );
var uris = []; store.dispatch(coreActions.albumLoaded(album));
for(var i = 0; i < album.tracks.length; i++){ store.dispatch(coreActions.artistsLoaded(artists));
uris.push(album.tracks[i].uri );
}
// load artwork from LastFM // load artwork from LastFM
if (album.images.length <= 0){ if (album.images.length <= 0){
@ -1819,29 +1841,17 @@ const MopidyMiddleware = (function(){
} }
} }
request(socket, store, 'library.lookup', { uris: uris } ) request(socket, store, 'library.lookup', { uris: album.tracks_uris } )
.then(response => { .then(response => {
var tracks_loaded = [];
for(var uri in response){ for (var uri in response){
if (response.hasOwnProperty(uri)){ if (response.hasOwnProperty(uri)){
tracks_loaded.push(response[uri][0]);
var track = response[uri][0];
// find the track reference, and drop in the full track data
function getByURI(trackReference){
return track.uri == trackReference.uri
}
var trackReferences = album.tracks.filter(getByURI);
// there could be multiple instances of this track, so accommodate this
for(var j = 0; j < trackReferences.length; j++){
var key = album.tracks.indexOf(trackReferences[j] );
album.tracks[ key ] = track;
}
} }
} }
store.dispatch(coreActions.albumLoaded(album)); store.dispatch(coreActions.tracksLoaded(tracks_loaded));
}) })
}) })
break; break;

View File

@ -1229,17 +1229,12 @@ export function playArtistTopTracks(uri){
* ====================================================================================== * ======================================================================================
**/ **/
export function getUser(uri, and_playlists = false){ export function getUser(uri){
return (dispatch, getState) => { return (dispatch, getState) => {
sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri))
// get the user
sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) )
.then( .then(
response => { response => {
dispatch({ dispatch(coreActions.userLoaded(helpers.formatUser(response)));
type: 'USERS_LOADED',
users: [response]
});
}, },
error => { error => {
dispatch(coreActions.handleException( dispatch(coreActions.handleException(
@ -1247,46 +1242,38 @@ export function getUser(uri, and_playlists = false){
error error
)); ));
} }
) );
if (and_playlists){
dispatch(getUserPlaylists(uri));
}
} }
} }
export function getUserPlaylists(user_uri){ export function getUserPlaylists(uri){
return (dispatch, getState) => { return (dispatch, getState) => {
// get the first page of playlists // get the first page of playlists
sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid', user_uri) +'/playlists?limit=40' ) sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid', uri) +'/playlists?limit=40' )
.then( .then(
response => { response => {
var playlists = [] var playlists = [];
for (var i = 0; i < response.items.length; i++){ for (var raw_playlist of response.items){
var can_edit = false var can_edit = false;
if (getState().spotify.me && response.items[i].owner.id == getState().spotify.me.id){ if (getState().spotify.me && raw_playlist.owner.id == getState().spotify.me.id){
can_edit = true can_edit = true;
} }
playlists.push(Object.assign( var playlist = Object.assign(
{}, {},
response.items[i], helpers.formatPlaylist(raw_playlist),
{ {
can_edit: can_edit, can_edit: can_edit,
tracks_total: response.items[i].tracks.total tracks_total: raw_playlist.tracks.total
} }
)) );
playlists.push(playlist);
} }
dispatch({ dispatch(coreActions.userPlaylistsLoaded(uri, playlists, response.more, response.total));
type: 'LOADED_MORE',
parent_type: 'user',
parent_key: user_uri,
records_type: 'playlist',
records_data: response
});
}, },
error => { error => {
dispatch(coreActions.handleException( dispatch(coreActions.handleException(
@ -1322,26 +1309,29 @@ export function getAlbum(uri){
// dispatch our loaded artists (simple objects) // dispatch our loaded artists (simple objects)
dispatch(coreActions.artistsLoaded(response.artists)); dispatch(coreActions.artistsLoaded(response.artists));
var tracks = Object.assign([], response.tracks.items);
var album = Object.assign( var album = Object.assign(
{}, {},
response, helpers.formatAlbum(response),
{ {
artists_uris: helpers.arrayOf('uri',response.artists), artists_uris: helpers.arrayOf('uri',response.artists),
tracks: response.tracks.items, tracks_uris: helpers.arrayOf('uri', tracks),
tracks_more: response.tracks.next, tracks_more: response.tracks.next,
tracks_total: response.tracks.total tracks_total: response.tracks.total
} }
) );
// add our album to all the tracks // add our album to all the tracks
for (var i = 0; i < album.tracks.length; i++){ for (var i = 0; i < tracks.length; i++){
album.tracks[i].album = { tracks[i].album = {
name: album.name, name: album.name,
uri: album.uri uri: album.uri
} }
} }
dispatch(coreActions.albumLoaded(album)); dispatch(coreActions.albumLoaded(album));
dispatch(coreActions.tracksLoaded(tracks));
// now get all the artists for this album (full objects) // now get all the artists for this album (full objects)
// we do this to get the artist artwork // we do this to get the artist artwork
@ -1535,19 +1525,24 @@ export function getPlaylist(uri){
description = description.split('<a href="spotify:user:').join('<a href="#'+global.baseURL+'user/spotify:user:') description = description.split('<a href="spotify:user:').join('<a href="#'+global.baseURL+'user/spotify:user:')
} }
var tracks = helpers.formatTracks(response.tracks.items);
var playlist = Object.assign( var playlist = Object.assign(
{}, {},
response, helpers.formatPlaylist(response),
{ {
is_completely_loaded: true, is_completely_loaded: true,
can_edit: (getState().spotify.me && response.owner.id == getState().spotify.me.id), can_edit: (getState().spotify.me && response.owner.id == getState().spotify.me.id),
tracks: helpers.formatTracks(response.tracks.items), user_uri: response.owner.uri,
tracks_uris: helpers.arrayOf('uri', tracks),
tracks_more: response.tracks.next, tracks_more: response.tracks.next,
tracks_total: response.tracks.total, tracks_total: response.tracks.total,
description: description description: description
} }
) )
//dispatch(coreActions.userLoaded(helpers.formatUser(response.owner)));
dispatch(coreActions.tracksLoaded(tracks));
dispatch(coreActions.playlistLoaded(playlist)); dispatch(coreActions.playlistLoaded(playlist));
}, },
error => { error => {

View File

@ -119,7 +119,7 @@ class Album extends React.Component{
} }
} }
var album = helpers.collateObject(this.props.album, {tracks: this.props.tracks, artists: this.props.artists}); var album = helpers.collate(this.props.album, {tracks: this.props.tracks, artists: this.props.artists});
if (album.tracks && album.tracks.length <= 0 && helpers.isLoading(this.props.load_queue,['spotify_albums/'+helpers.getFromUri('albumid',this.props.params.uri)])){ if (album.tracks && album.tracks.length <= 0 && helpers.isLoading(this.props.load_queue,['spotify_albums/'+helpers.getFromUri('albumid',this.props.params.uri)])){
var is_loading_tracks = true; var is_loading_tracks = true;
@ -138,8 +138,8 @@ class Album extends React.Component{
<h1>{album.name}</h1> <h1>{album.name}</h1>
<ul className="details"> <ul className="details">
{!this.props.slim_mode ? <li className="has-tooltip"><Icon type="fontawesome" name={helpers.sourceIcon(this.props.params.uri )} /><span className="tooltip">{helpers.uriSource(this.props.params.uri )} {album.type ? album.type : 'album'}</span></li> : null} {!this.props.slim_mode ? <li className="has-tooltip"><Icon type="fontawesome" name={helpers.sourceIcon(album.uri )} /><span className="tooltip">{helpers.uriSource(this.props.params.uri )} {album.type ? album.type : 'album'}</span></li> : null}
{!this.props.slim_mode && album.artists.length > 0 ? <li><ArtistSentence artists={album.artists} /></li> : null} {!this.props.slim_mode && album.artists && album.artists.length > 0 ? <li><ArtistSentence artists={album.artists} /></li> : null}
{album.release_date ? <li><Dater type="date" data={album.release_date} /></li> : null} {album.release_date ? <li><Dater type="date" data={album.release_date} /></li> : null}
<li> <li>
{album.tracks ? <span>{album.tracks.length} tracks, <Dater type="total-time" data={album.tracks} /></span> : '0 tracks, 0 mins' } {album.tracks ? <span>{album.tracks.length} tracks, <Dater type="total-time" data={album.tracks} /></span> : '0 tracks, 0 mins' }

View File

@ -33,15 +33,15 @@ class Artist extends React.Component{
componentDidMount(){ componentDidMount(){
this.setWindowTitle(); this.setWindowTitle();
this.loadArtist(); this.props.coreActions.loadArtist(this.props.params.uri);
} }
componentWillReceiveProps(nextProps){ componentWillReceiveProps(nextProps){
if (nextProps.params.uri != this.props.params.uri){ if (nextProps.params.uri != this.props.params.uri){
this.loadArtist(nextProps); this.props.coreActions.loadArtist(nextProps.params.uri);
}else if (!this.props.mopidy_connected && nextProps.mopidy_connected){ }else if (!this.props.mopidy_connected && nextProps.mopidy_connected){
if (helpers.uriSource(this.props.params.uri ) != 'spotify'){ if (helpers.uriSource(this.props.params.uri ) != 'spotify'){
this.loadArtist(nextProps); this.props.coreActions.loadArtist(nextProps.params.uri);
} }
} }
@ -72,30 +72,6 @@ class Artist extends React.Component{
this.props.uiActions.showContextMenu(data); this.props.uiActions.showContextMenu(data);
} }
loadArtist(props = this.props){
switch(helpers.uriSource(props.params.uri )){
case 'spotify':
if (props.artist && props.artist.albums_uris && props.artist.related_artists_uris){
console.info('Loading spotify artist from index');
} else {
this.props.spotifyActions.getArtist(props.params.uri, true);
}
this.props.spotifyActions.following(props.params.uri);
break
default:
if (props.mopidy_connected){
if (props.artist && props.artist.images && props.artist.albums_uris){
console.info('Loading local artist from index');
} else {
this.props.mopidyActions.getArtist(props.params.uri);
}
}
break
}
}
loadMore(){ loadMore(){
this.props.spotifyActions.getMore( this.props.spotifyActions.getMore(
this.props.artist.albums_more, this.props.artist.albums_more,

View File

@ -29,16 +29,16 @@ class Playlist extends React.Component{
} }
componentDidMount(){ componentDidMount(){
this.loadPlaylist();
this.setWindowTitle(); this.setWindowTitle();
this.props.coreActions.loadPlaylist(this.props.params.uri);
} }
componentWillReceiveProps(nextProps){ componentWillReceiveProps(nextProps){
if (nextProps.params.uri != this.props.params.uri){ if (nextProps.params.uri != this.props.params.uri){
this.loadPlaylist(nextProps); this.props.coreActions.loadPlaylist(nextProps.params.uri);
} else if (!this.props.mopidy_connected && nextProps.mopidy_connected){ } else if (!this.props.mopidy_connected && nextProps.mopidy_connected){
if (helpers.uriSource(this.props.params.uri) != 'spotify'){ if (helpers.uriSource(this.props.params.uri) != 'spotify'){
this.loadPlaylist(nextProps); this.props.coreActions.loadPlaylist(nextProps.params.uri);
} }
} }
@ -59,28 +59,6 @@ class Playlist extends React.Component{
} }
} }
loadPlaylist(props = this.props){
if (props.playlist && props.playlist.is_completely_loaded){
console.info('Loading playlist from index')
} else {
switch (helpers.uriSource(props.params.uri)){
case 'spotify':
this.props.spotifyActions.getPlaylist(props.params.uri);
this.props.spotifyActions.following(props.params.uri);
break
default:
if (props.mopidy_connected){
this.props.mopidyActions.getPlaylist(props.params.uri);
}
break
}
}
}
loadMore(){ loadMore(){
this.props.spotifyActions.getMore( this.props.spotifyActions.getMore(
this.props.playlist.tracks_more, this.props.playlist.tracks_more,
@ -140,7 +118,7 @@ class Playlist extends React.Component{
} }
renderActions(){ renderActions(){
switch(helpers.uriSource(this.props.playlist.uri )){ switch(helpers.uriSource(this.props.playlist.uri)){
case 'm3u': case 'm3u':
return ( return (
@ -196,22 +174,14 @@ class Playlist extends React.Component{
} }
} }
var playlist = helpers.collate(this.props.playlist, {tracks: this.props.tracks, users: this.props.users});
var context = 'playlist'; var context = 'playlist';
if (this.props.playlist.can_edit){ if (playlist.can_edit){
context = 'editable-playlist'; context = 'editable-playlist';
} }
var tracks = []; if (playlist.tracks && playlist.tracks.length <= 0 && helpers.isLoading(this.props.load_queue,['spotify_users/'+user_id+'/playlists/'+playlist_id, 'spotify_users/'+user_id+'/playlists/'+playlist_id+'/tracks'])){
if (this.props.playlist.tracks_uris && this.props.tracks){
for (var i = 0; i < this.props.playlist.tracks_uris.length; i++){
var uri = this.props.playlist.tracks_uris[i]
if (this.props.tracks.hasOwnProperty(uri)){
tracks.push(this.props.tracks[uri])
}
}
}
if (tracks.length <= 0 && helpers.isLoading(this.props.load_queue,['spotify_users/'+user_id+'/playlists/'+playlist_id, 'spotify_users/'+user_id+'/playlists/'+playlist_id+'/tracks'])){
var is_loading_tracks = true; var is_loading_tracks = true;
} else { } else {
var is_loading_tracks = false; var is_loading_tracks = false;
@ -220,21 +190,21 @@ class Playlist extends React.Component{
return ( return (
<div className="view playlist-view content-wrapper"> <div className="view playlist-view content-wrapper">
<div className="thumbnail-wrapper"> <div className="thumbnail-wrapper">
<Thumbnail size="large" canZoom images={ this.props.playlist.images } /> <Thumbnail size="large" canZoom images={playlist.images} />
</div> </div>
<div className="title"> <div className="title">
<h1>{ this.props.playlist.name }</h1> <h1>{playlist.name}</h1>
{ this.props.playlist.description ? <h2 className="description grey-text" dangerouslySetInnerHTML={{__html: this.props.playlist.description}}></h2> : null } {playlist.description ? <h2 className="description grey-text" dangerouslySetInnerHTML={{__html: playlist.description}}></h2> : null }
<ul className="details"> <ul className="details">
{ !this.props.slim_mode ? <li className="has-tooltip"><Icon type="fontawesome" name={helpers.sourceIcon(this.props.params.uri )} /><span className="tooltip">{helpers.uriSource(this.props.params.uri)} playlist</span></li> : null } {!this.props.slim_mode ? <li className="has-tooltip"><Icon type="fontawesome" name={helpers.sourceIcon(playlist.uri)} /><span className="tooltip">{helpers.uriSource(playlist.uri)} playlist</span></li> : null }
{ this.props.playlist.owner && !this.props.slim_mode ? <li><URILink type="user" uri={this.props.playlist.owner.uri}>{this.props.playlist.owner.id}</URILink></li> : null } {playlist.user_uri && !this.props.slim_mode ? <li><URILink type="user" uri={playlist.user_uri}>{playlist.user ? playlist.user.name : helpers.getFromUri('userid',playlist.user_uri)}</URILink></li> : null }
{ this.props.playlist.followers ? <li>{this.props.playlist.followers.total.toLocaleString()} followers</li> : null } {playlist.followers ? <li>{playlist.followers.toLocaleString()} followers</li> : null }
{ this.props.playlist.last_modified ? <li>Edited <Dater type="ago" data={this.props.playlist.last_modified} /></li> : null } {playlist.last_modified_date ? <li>Edited <Dater type="ago" data={playlist.last_modified_date} /></li> : null }
<li> <li>
{ this.props.playlist.tracks_total ? this.props.playlist.tracks_total : tracks.length} tracks,&nbsp; {playlist.tracks_total ? playlist.tracks_total : (playlist.tracks ? playlist.tracks.length : '0')} tracks,&nbsp;
<Dater type="total-time" data={tracks} /> <Dater type="total-time" data={playlist.tracks} />
</li> </li>
</ul> </ul>
</div> </div>
@ -242,8 +212,8 @@ class Playlist extends React.Component{
{ this.renderActions() } { this.renderActions() }
<section className="list-wrapper"> <section className="list-wrapper">
<TrackList uri={this.props.params.uri} className="playlist-track-list" context={context} tracks={tracks} removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> <TrackList uri={playlist.uri} className="playlist-track-list" context={context} tracks={playlist.tracks} removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } />
<LazyLoadListener loading={this.props.playlist.tracks_more} forceLoader={is_loading_tracks} loadMore={() => this.loadMore()}/> <LazyLoadListener loading={playlist.tracks_more} forceLoader={is_loading_tracks} loadMore={() => this.loadMore()}/>
</section> </section>
</div> </div>
) )
@ -263,6 +233,7 @@ const mapStateToProps = (state, ownProps) => {
allow_reporting: state.ui.allow_reporting, allow_reporting: state.ui.allow_reporting,
slim_mode: state.ui.slim_mode, slim_mode: state.ui.slim_mode,
load_queue: state.ui.load_queue, load_queue: state.ui.load_queue,
users: state.core.users,
tracks: state.core.tracks, tracks: state.core.tracks,
playlist: (state.core.playlists[uri] !== undefined ? state.core.playlists[uri] : false ), playlist: (state.core.playlists[uri] !== undefined ? state.core.playlists[uri] : false ),
spotify_library_playlists: state.spotify.library_playlists, spotify_library_playlists: state.spotify.library_playlists,

View File

@ -25,12 +25,14 @@ class User extends React.Component{
componentDidMount(){ componentDidMount(){
this.setWindowTitle(); this.setWindowTitle();
this.loadUser(); this.props.coreActions.loadUser(this.props.params.uri);
this.props.coreActions.loadUserPlaylists(this.props.params.uri);
} }
componentWillReceiveProps(nextProps){ componentWillReceiveProps(nextProps){
if (nextProps.params.uri != this.props.params.uri){ if (nextProps.params.uri != this.props.params.uri){
this.loadUser(nextProps); this.props.coreActions.loadUser(nextProps.params.uri);
this.props.coreActions.loadUserPlaylists(this.props.params.uri);
} }
if (!this.props.user && nextProps.user){ if (!this.props.user && nextProps.user){
@ -46,13 +48,6 @@ class User extends React.Component{
} }
} }
loadUser(props = this.props){
if (!props.user || props.user.playlists_uris === undefined){
this.props.spotifyActions.getUser(props.params.uri, true);
this.props.spotifyActions.following(props.params.uri);
}
}
loadMore(){ loadMore(){
this.props.spotifyActions.getMore( this.props.spotifyActions.getMore(
this.props.user.playlists_more, this.props.user.playlists_more,
@ -84,37 +79,29 @@ class User extends React.Component{
} }
} }
var playlists = []; var user = helpers.collate(this.props.user, {playlists: this.props.playlists});
if (this.props.user.playlists_uris){
for (var i = 0; i < this.props.user.playlists_uris.length; i++){
var uri = this.props.user.playlists_uris[i]
if (this.props.playlists.hasOwnProperty(uri)){
playlists.push(this.props.playlists[uri])
}
}
}
if (this.props.user && this.props.user.images){ if (user && user.images){
var image = helpers.sizedImages(this.props.user.images).huge var image = helpers.sizedImages(user.images).huge;
} else { } else {
var image = null var image = null;
} }
return ( return (
<div className="view user-view"> <div className="view user-view">
<div className="intro"> <div className="intro">
<Parallax image={image} /> <Parallax image={image} theme={this.props.theme} />
<div className="liner"> <div className="liner">
<h1>{ this.props.user.display_name ? this.props.user.display_name : this.props.user.id }</h1> <h1>{user.name}</h1>
<h2> <h2>
<ul className="details"> <ul className="details">
{this.props.user.playlists_total ? <li>{this.props.user.playlists_total ? this.props.user.playlists_total.toLocaleString() : 0} playlists</li> : null} {user.playlists_total ? <li>{user.playlists_total ? user.playlists_total.toLocaleString() : 0} playlists</li> : null}
{this.props.user.followers ? <li>{this.props.user.followers.total.toLocaleString()} followers</li> : null} {user.followers ? <li>{user.followers.toLocaleString()} followers</li> : null}
{this.isMe() ? <li><span className="blue-text">You</span></li> : null} {this.isMe() ? <li><span className="blue-text">You</span></li> : null}
</ul> </ul>
</h2> </h2>
<div className="actions"> <div className="actions">
<FollowButton className="primary" uri={this.props.params.uri} addText="Follow" removeText="Unfollow" /> <FollowButton className="primary" uri={user.uri} addText="Follow" removeText="Unfollow" />
</div> </div>
</div> </div>
</div> </div>
@ -122,8 +109,8 @@ class User extends React.Component{
<div className="content-wrapper"> <div className="content-wrapper">
<section className="grid-wrapper"> <section className="grid-wrapper">
<h4>Playlists</h4> <h4>Playlists</h4>
<PlaylistGrid playlists={playlists} /> <PlaylistGrid playlists={user.playlists} />
<LazyLoadListener loading={this.props.user.playlists_more} loadMore={() => this.loadMore()} /> <LazyLoadListener loading={user.playlists_more} loadMore={() => this.loadMore()} />
</section> </section>
</div> </div>
</div> </div>
@ -134,9 +121,10 @@ class User extends React.Component{
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
var uri = ownProps.params.uri; var uri = ownProps.params.uri;
return { return {
me: state.spotify.me,
theme: state.ui.theme,
load_queue: state.ui.load_queue, load_queue: state.ui.load_queue,
spotify_authorized: state.spotify.authorization, spotify_authorized: state.spotify.authorization,
me: state.spotify.me,
playlists: state.core.playlists, playlists: state.core.playlists,
user: (state.core.users[uri] !== undefined ? state.core.users[uri] : false) user: (state.core.users[uri] !== undefined ? state.core.users[uri] : false)
}; };

View File

@ -119,7 +119,7 @@ class DiscoverNewReleases extends React.Component{
// Pull the first playlist out and we'll use this as a banner // Pull the first playlist out and we'll use this as a banner
var first_album = albums.splice(0,1) var first_album = albums.splice(0,1)
if (first_album){ if (first_album){
first_album = first_album[0] first_album = helpers.collate(first_album[0], {artists: this.props.artists});
} }
var options = ( var options = (
@ -155,6 +155,7 @@ const mapStateToProps = (state, ownProps) => {
return { return {
theme: state.ui.theme, theme: state.ui.theme,
load_queue: state.ui.load_queue, load_queue: state.ui.load_queue,
artists: state.core.artists,
albums: state.core.albums, albums: state.core.albums,
new_releases: state.core.new_releases, new_releases: state.core.new_releases,
new_releases_more: state.core.new_releases_more, new_releases_more: state.core.new_releases_more,

View File

@ -11,11 +11,15 @@
height: 0.5em; height: 0.5em;
margin: 0.25em 1px; margin: 0.25em 1px;
background: $light_grey; background: $light_grey;
opacity: 0.25; opacity: 0.2;
&.filled { &.filled {
opacity: 1; opacity: 1;
} }
.light-theme & {
background: $darkest_grey;
}
} }
} }

View File

@ -27,6 +27,7 @@
&:focus, &:focus,
&:active { &:active {
border-color: $turquoise; border-color: $turquoise;
background: transparent;
} }
} }
} }