Re-fetching incomplete albums, need to do the same for Artist and Playlists

This commit is contained in:
James Barnsley
2020-09-05 11:49:28 +12:00
parent 6dca4ef475
commit f6f5e37aa9
13 changed files with 662 additions and 1099 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -106,7 +106,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1599105011";
var build = "1599193622";
var version = "3.52.4";
// Construct the script tag

View File

@ -32,7 +32,7 @@ export default class GridItem extends React.Component {
case 'album':
// If Mopidy doesn't find any images, then it will pass on the call to LastFM
if (mopidyActions) {
mopidyActions.getImages('albums', [item.uri]);
mopidyActions.getImages([item.uri]);
}
break;

View File

@ -77,10 +77,10 @@ export function clearStorage() {
};
}
export function restoreFromColdStore(item) {
export function restoreFromColdStore(items) {
return {
type: 'RESTORE_FROM_COLD_STORE',
item,
items,
};
}
@ -175,6 +175,13 @@ export function loadLibrary(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 itemsLoaded(items) {
return {
type: 'ITEMS_LOADED',
items,
};
}
export function itemLoaded(item) {
return {
type: 'ITEM_LOADED',

View File

@ -20,6 +20,7 @@ import { handleException } from './actions';
const coreActions = require('./actions.js');
const uiActions = require('../ui/actions.js');
const mopidyActions = require('../mopidy/actions.js');
const googleActions = require('../google/actions.js');
const spotifyActions = require('../spotify/actions.js');
const CoreMiddleware = (function () {
@ -348,31 +349,47 @@ const CoreMiddleware = (function () {
break;
case 'LOAD_ALBUM':
if (!action.force_reload && store.getState().core.items[action.uri]) {
console.info(`Loading "${action.uri}" from index`);
const fetchAlbum = () => {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(spotifyActions.getAlbum(action.uri));
if (spotify.me) {
store.dispatch(spotifyActions.following(action.uri));
}
break;
default:
store.dispatch(mopidyActions.getAlbum(action.uri));
break;
};
};
if (action.force_reload) {
fetchAlbum();
break;
}
if (
store.getState().core.items[action.uri] &&
store.getState().core.items[action.uri].tracks
) {
console.info(`Using "${action.uri}" from index`);
break;
}
// Try our cold storage
localForage.getItem(action.uri).then((result) => {
if (result && !action.force_reload) {
if (result) {
console.info(`Loading "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
} else {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(spotifyActions.getAlbum(action.uri));
store.dispatch(coreActions.restoreFromColdStore([result]));
console.log(result);
if (spotify.me) {
store.dispatch(spotifyActions.following(action.uri));
}
break;
default:
store.dispatch(mopidyActions.getAlbum(action.uri));
break;
// We don't have the complete Album, so refetch
if (!result.tracks) {
fetchAlbum();
}
};
} else {
fetchAlbum();
}
});
next(action);
@ -380,7 +397,6 @@ const CoreMiddleware = (function () {
// TODO: Relocate this
case 'UPDATE_COLD_STORE':
console.log(action);
if (action.items) {
action.items.map((item) => {
localForage.getItem(item.uri).then((result) => {
@ -390,10 +406,10 @@ const CoreMiddleware = (function () {
}
break;
case 'RESTORE_FROM_COLD_STORE':
if (action.item) {
if (action.items) {
store.dispatch({
type: 'RESTORED_FROM_COLD_STORE',
item: action.item,
items: action.items,
});
}
break;
@ -412,7 +428,7 @@ const CoreMiddleware = (function () {
localForage.getItem(action.uri).then((result) => {
if (result && !action.force_reload) {
console.info(`Loading "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
store.dispatch(coreActions.restoreFromColdStore([result]));
} else {
switch (uriSource(action.uri)) {
case 'spotify':
@ -462,7 +478,7 @@ const CoreMiddleware = (function () {
localForage.getItem(action.uri).then((result) => {
if (result) {
console.info(`Restoring "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
store.dispatch(coreActions.restoreFromColdStore([result]));
} else {
fetchPlaylist();
}
@ -527,7 +543,11 @@ const CoreMiddleware = (function () {
spotifyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
);
break;
case 'google':
store.dispatch(
googleActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
);
break;
default:
store.dispatch(
mopidyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
@ -545,10 +565,17 @@ const CoreMiddleware = (function () {
break;
}
localForage.getItem(action.uri).then((result) => {
if (result) {
console.info(`Restoring "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
localForage.getItem(action.uri).then((library) => {
if (library) {
console.info(`Restoring "${action.uri}" and ${library.items_uris.length} items from db`);
var promises = library.items_uris.map(function(item) { return localForage.getItem(item); });
Promise.all(promises).then(function(libraryItems) {
store.dispatch(coreActions.restoreFromColdStore(libraryItems));
});
//store.dispatch(coreActions.loadItems(result.items_uris));
store.dispatch(coreActions.restoreFromColdStore([library]));
} else {
fetchLibrary();
}
@ -660,6 +687,11 @@ const CoreMiddleware = (function () {
next(action);
break;
// TODO: Flatten ITEM_LOADED to become an alias for ITEMS_LOADED (prefer bulk)
case 'ITEMS_LOADED':
action.items.forEach((item) => store.dispatch(coreActions.itemLoaded(item)));
break;
case 'ITEM_LOADED':
const mergedItem = {
...core.items[action.item.uri] || {},

View File

@ -66,9 +66,14 @@ 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 } };
return {
...core,
items: {
...core.items,
[action.item.uri]: action.item,
},
};
case 'TRACKS_LOADED':
var tracks = { ...core.tracks };
@ -145,15 +150,16 @@ export default function reducer(core = {}, action) {
case 'RESTORED_FROM_COLD_STORE':
const { items } = core;
action.items.forEach((item) => {
items[item.uri] = {
...(items[item.uri] || {}),
...item,
};
});
return {
...core,
items: {
...core.items,
[action.item.uri]: {
...core.items[action.item.uri],
...action.item,
},
},
items,
};
/**

View File

@ -405,10 +405,9 @@ export function shuffleTracklist() {
* Asset-oriented actions
* */
export function getImages(context, uris) {
export function getImages(uris) {
return {
type: 'MOPIDY_GET_IMAGES',
context,
uris,
};
}

View File

@ -16,6 +16,7 @@ import {
formatTracks,
formatSimpleObject,
getTrackIcon,
formatArtists,
} from '../../util/format';
import {
arrayOf,
@ -1622,9 +1623,10 @@ const MopidyMiddleware = (function () {
});
if (index === playlist_uris.length - 1) {
store.dispatch(coreActions.itemsLoaded(libraryPlaylists));
store.dispatch(coreActions.itemLoaded({
uri: 'mopidy:library:playlists',
items: libraryPlaylists,
items_uris: arrayOf('uri', libraryPlaylists),
}));
}
});
@ -1855,166 +1857,52 @@ const MopidyMiddleware = (function () {
* ======================================================================================
* */
case 'MOPIDY_GET_LIBRARY_ALBUMS':
var last_run = store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR;
if (!last_run) {
request(store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri })
.then((response) => {
if (response.length <= 0) return;
const uris = arrayOf('uri', response);
store.dispatch({
type: 'MOPIDY_LIBRARY_ALBUMS_LOADED',
uris,
});
// Start our process to load the full album objects
store.dispatch(uiActions.startProcess(
'MOPIDY_LIBRARY_ALBUMS_PROCESSOR',
i18n('services.mopidy.loading_albums', { count: uris.length }),
{
uris,
total: uris.length,
remaining: uris.length,
},
));
});
} else if (last_run.status == 'cancelled') {
store.dispatch(uiActions.resumeProcess('MOPIDY_LIBRARY_ALBUMS_PROCESSOR'));
} else if (last_run.status == 'finished') {
// TODO: do we want to force a refresh?
}
break;
case 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR':
if (store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR !== undefined) {
const processor = store.getState().ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR;
if (processor.status == 'cancelling') {
store.dispatch(uiActions.processCancelled('MOPIDY_LIBRARY_ALBUMS_PROCESSOR'));
return false;
}
}
var uris = Object.assign([], action.data.uris);
var uris_to_load = uris.splice(0, 50);
if (uris_to_load.length > 0) {
store.dispatch(uiActions.updateProcess(
'MOPIDY_LIBRARY_ALBUMS_PROCESSOR',
`Loading ${uris.length} local albums`,
{
uris,
remaining: uris.length,
},
));
store.dispatch(mopidyActions.getAlbums(uris_to_load, { name: 'MOPIDY_LIBRARY_ALBUMS_PROCESSOR', data: { uris } }));
} else {
store.dispatch(uiActions.processFinished('MOPIDY_LIBRARY_ALBUMS_PROCESSOR'));
}
break;
case 'MOPIDY_GET_ALBUMS':
request(store, 'library.lookup', { uris: action.uris })
request(store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri })
.then((response) => {
const albums_loaded = [];
const artists_loaded = [];
const tracks_loaded = [];
for (const uri in response) {
if (response.hasOwnProperty(uri) && response[uri].length > 0 && response[uri][0].album) {
const tracks = response[uri];
const artists_uris = [];
if (tracks[0].artists) {
for (const artist of response[uri][0].artists) {
artists_uris.push(artist.uri);
artists_loaded.push(artist);
}
}
const tracks_uris = [];
for (const track of tracks) {
tracks_uris.push(track.uri);
tracks_loaded.push(track);
}
const album = {
const uris = arrayOf('uri', response);
request(store, 'library.lookup', { uris })
.then((response) => {
const libraryAlbums = indexToArray(response).map((tracks) => ({
source: 'local',
artists_uris,
tracks_uris,
tracks_total: tracks_uris.length,
artists: tracks[0].artists || null,
tracks,
last_modified: tracks[0].last_modified,
...tracks[0].album,
};
}));
albums_loaded.push(album);
}
}
store.dispatch(coreActions.albumsLoaded(albums_loaded));
store.dispatch(coreActions.artistsLoaded(artists_loaded));
store.dispatch(coreActions.tracksLoaded(tracks_loaded));
// Re-run any consequential processes in a few ms. 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
// locked until we're completely done.
if (action.processor) {
setTimeout(
() => {
store.dispatch(uiActions.runProcess(action.processor.name, action.processor.data));
},
10,
);
}
store.dispatch(coreActions.itemsLoaded(libraryAlbums));
store.dispatch(coreActions.itemLoaded({
uri: 'mopidy:library:albums',
items_uris: arrayOf('uri', libraryAlbums),
}));
});
});
break;
case 'MOPIDY_GET_ALBUM':
request(store, 'library.lookup', { uris: [action.uri] })
.then((_response) => {
const { uri } = action;
if (!_response) return;
let response = _response[action.uri];
let response = _response[uri];
if (!response || !response.length) return;
response = sortItems(response, 'track_number');
const artists = [];
if (response[0].artists) {
for (const artist of response[0].artists) {
artists.push(artist);
}
}
const album = {
...response[0].album,
source: 'local',
artists_uris: arrayOf('uri', artists),
tracks_uris: arrayOf('uri', response),
tracks_total: response.length,
artists: formatArtists(response[0].artists),
tracks: formatTracks(response),
};
store.dispatch(coreActions.albumLoaded(album));
store.dispatch(coreActions.artistsLoaded(artists));
store.dispatch(coreActions.itemLoaded(album));
// Load images
if (!response[0].album.images) {
store.dispatch(mopidyActions.getImages('albums', [album.uri]));
if (!album.images) {
store.dispatch(mopidyActions.getImages([album.uri]));
}
request(store, 'library.lookup', { uris: album.tracks_uris })
.then((response) => {
const tracks_loaded = [];
for (const uri in response) {
if (response.hasOwnProperty(uri)) {
tracks_loaded.push(response[uri][0]);
}
}
store.dispatch(coreActions.tracksLoaded(tracks_loaded));
});
});
break;
@ -2368,28 +2256,22 @@ const MopidyMiddleware = (function () {
if (action.uris) {
request(store, 'library.getImages', { uris: action.uris })
.then((response) => {
const records = [];
for (const uri in response) {
if (response.hasOwnProperty(uri)) {
let images = response[uri];
if (images.length) {
images = formatImages(digestMopidyImages(store.getState().mopidy, images));
records.push({
uri,
images,
});
} else {
store.dispatch(lastfmActions.getImages(action.context, uri));
}
}
}
const itemsWithImages = [];
Object.keys(response).forEach((uri) => {
const images = response[uri];
if (records.length) {
const action_data = {
type: (`${action.context}_LOADED`).toUpperCase(),
if (images) {
itemsWithImages.push({
uri,
images: formatImages(digestMopidyImages(store.getState().mopidy, images)),
});
} else {
store.dispatch(lastfmActions.getImages(uri));
};
action_data[action.context] = records;
store.dispatch(action_data);
});
if (itemsWithImages.length) {
store.dispatch(coreActions.itemsLoaded(itemsWithImages));
}
});
}

View File

@ -1735,90 +1735,39 @@ export function getLibraryPlaylists() {
const fetchLibraryPlaylists = (endpoint) => request(dispatch, getState, endpoint)
.then((response) => {
libraryPlaylists = [...libraryPlaylists, ...formatPlaylists(response.items)];
console.log('loaded page of playlists', response.items);
if (response.next) {
fetchLibraryPlaylists(response.next);
} else {
dispatch(coreActions.itemsLoaded(libraryPlaylists));
dispatch(coreActions.itemLoaded({
uri: 'spotify:library:playlists',
items: libraryPlaylists,
items_uris: arrayOf('uri', libraryPlaylists),
}));
}
});
fetchLibraryPlaylists('me/playlists?limit=50');
/*
const last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR;
if (!last_run) {
dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', 'Loading Spotify playlists', { next: 'me/playlists?limit=50' }));
} else if (last_run.status === 'cancelled') {
dispatch(uiActions.resumeProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR'));
// We've already finished, but the status has been flushed
} else if (last_run.status === 'finished' && !getState().spotify.library_playlists_loaded_all) {
dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', 'Loading Spotify playlists', { next: 'me/playlists?limit=50' }));
}
*/
};
}
export function getLibraryPlaylistsProcessor(data) {
export function getLibraryAlbums() {
return (dispatch, getState) => {
request(dispatch, getState, data.next)
.then(
(response) => {
dispatch({
type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED',
playlists: response.items,
});
let libraryAlbums = [];
const fetchLibraryAlbums = (endpoint) => request(dispatch, getState, endpoint)
.then((response) => {
libraryAlbums = [...libraryAlbums, ...formatAlbums(response.items)];
if (response.next) {
fetchLibraryAlbums(response.next);
} else {
dispatch(coreActions.itemsLoaded(libraryAlbums));
dispatch(coreActions.itemLoaded({
uri: 'spotify:library:albums',
items_uris: arrayOf('uri', libraryAlbums),
}));
}
});
// Check to see if we've been cancelled
if (getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR !== undefined) {
const processor = getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR;
if (processor.status == 'cancelling') {
dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR'));
return false;
}
}
// We got a next link, so we've got more work to be done
if (response.next) {
const { total } = response;
const loaded = getState().spotify.library_playlists.length;
const remaining = total - loaded;
dispatch(uiActions.updateProcess(
'SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR',
`Loading ${remaining} Spotify playlists`,
{
next: response.next,
total: response.total,
remaining,
},
));
dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR', { next: response.next }));
} else {
dispatch(uiActions.processFinished('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR'));
dispatch({ type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED_ALL' });
}
},
() => {
dispatch(uiActions.processFinished(
'SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR',
{
content: i18n(
'errors.could_not_load_library',
{
name: i18n('library.playlists.title'),
provider: i18n('services.spotify.title'),
},
),
level: 'error',
},
));
},
);
fetchLibraryAlbums('me/albums?limit=50');
};
}
@ -1889,82 +1838,3 @@ export function getLibraryArtistsProcessor(data) {
);
};
}
/**
* ALbums
* */
export function getLibraryAlbums() {
return (dispatch, getState) => {
const last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR;
if (!last_run) {
dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', 'Loading Spotify albums', { next: 'me/albums?limit=50' }));
} else if (last_run.status === 'cancelled') {
dispatch(uiActions.updateProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', 'Loading Spotify albums', { next: 'me/albums?limit=50' }));
// We've already finished, but the status has been flushed
} else if (last_run.status === 'finished' && !getState().spotify.library_albums_loaded_all) {
dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', 'Loading Spotify albums', { next: 'me/albums?limit=50' }));
}
};
}
export function getLibraryAlbumsProcessor(data) {
return (dispatch, getState) => {
request(dispatch, getState, data.next)
.then(
(response) => {
dispatch({
type: 'SPOTIFY_LIBRARY_ALBUMS_LOADED',
albums: response.items,
});
// Check to see if we've been cancelled
if (getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR !== undefined) {
const processor = getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR;
if (processor.status == 'cancelling') {
dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR'));
return false;
}
}
// We got a next link, so we've got more work to be done
if (response.next) {
const { total } = response;
const loaded = getState().spotify.library_albums.length;
const remaining = total - loaded;
dispatch(uiActions.updateProcess(
'SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR',
`Loading ${remaining} Spotify albums`,
{
next: response.next,
total: response.total,
remaining,
},
));
dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR', { next: response.next }));
} else {
dispatch(uiActions.processFinished('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR'));
}
},
() => {
dispatch(uiActions.processFinished(
'SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR',
{
content: i18n(
'errors.could_not_load_library',
{
name: i18n('library.albums.title'),
provider: i18n('services.spotify.title'),
},
),
level: 'error',
},
));
},
);
};
}

View File

@ -289,14 +289,18 @@ const formatAlbum = function (data) {
'wiki_publish_date',
'popularity',
'images',
'artists_uris',
'tracks',
'tracks_uris',
'tracks_total',
'tracks_more',
'artists', // Array of simple records
'artists',
];
// Nested album object (eg in spotify library)
if (data && data.album && isObject(data.album)) {
if (data.added_at) {
data.album.added_at = data.added_at;
}
data = data.album;
}
// Loop fields and import from data
for (const field of fields) {
if (data.hasOwnProperty(field)) {
@ -307,6 +311,16 @@ const formatAlbum = function (data) {
if (album.images && !album.images.formatted) {
album.images = formatImages(album.images);
}
if (album.tracks) {
if (album.tracks.items) {
album.tracks = formatTracks(album.tracks.items);
} else {
album.tracks = formatTracks(album.tracks);
}
}
if (album.artists) {
album.artists = formatSimpleObjects(album.artists);
}
if (data.last_modified && album.added_at === undefined) {
album.added_at = data.last_modified;
@ -591,33 +605,41 @@ const formatTrack = function (data) {
}
if (track.track_number === undefined && data.track_no !== undefined) {
track.track_number = data.track_no;
track.track_number = data.track_no;
}
if (track.disc_number === undefined && data.disc_no !== undefined) {
track.disc_number = data.disc_no;
track.disc_number = data.disc_no;
}
if (track.release_date === undefined && data.date !== undefined) {
track.release_date = data.date;
track.release_date = data.date;
}
if (track.explicit === undefined && data.explicit !== undefined) {
track.is_explicit = data.explicit;
track.is_explicit = data.explicit;
}
// Copy images from albums (if applicable)
// TOOD: Identify if we stil need this...
if (data.album && data.album.images) {
if (track.images === undefined || !track.images.formatted) {
track.images = formatImages(data.album.images);
}
if (track.images === undefined || !track.images.formatted) {
track.images = formatImages(data.album.images);
}
}
if (track.provider === undefined && track.uri !== undefined) {
track.provider = uriSource(track.uri);
}
if (track.artists) {
track.artists = formatSimpleObjects(track.artists);
}
if (track.album) {
track.album = formatSimpleObject(track.album);
}
return track;
};
@ -739,6 +761,7 @@ const collate = function (obj, indexes = {}) {
if (obj.playlists_uris !== undefined) obj.playlists = [];
if (obj.related_artists_uris !== undefined) obj.related_artists = [];
if (obj.clients_ids !== undefined) obj.clients = [];
if (obj.items_uris !== undefined) obj.items = [];
if (indexes.artists) {
if (obj.artists_uris) {
@ -832,6 +855,16 @@ const collate = function (obj, indexes = {}) {
}
}
if (indexes.items) {
if (obj.items_uris) {
for (const uri of obj.items_uris) {
if (indexes.items[uri]) {
obj.items.push(indexes.items[uri]);
}
}
}
}
return obj;
};

View File

@ -68,52 +68,44 @@ class LibraryAlbums extends React.Component {
getMopidyLibrary = () => {
const {
source,
mopidy_library_playlists,
mopidyActions: {
getLibraryAlbums,
coreActions: {
loadLibrary,
},
} = this.props;
if (source !== 'local' && source !== 'all') return;
if (mopidy_library_playlists) return;
getLibraryAlbums();
loadLibrary('mopidy:library:albums');
};
getGoogleLibrary = () => {
const {
source,
google_available,
google_library_albums_status,
googleActions: {
getLibraryAlbums,
coreActions: {
loadLibrary,
},
} = this.props;
if (!google_available) return;
if (source !== 'google' && source !== 'all') return;
if (google_library_albums_status === 'finished') return;
if (google_library_albums_status === 'started') return;
getLibraryAlbums();
loadLibrary('google:library:albums');
};
getSpotifyLibrary = () => {
const {
source,
spotify_available,
spotify_library_albums_status,
spotifyActions: {
getLibraryAlbums,
coreActions: {
loadLibrary,
},
} = this.props;
if (!spotify_available) return;
if (source !== 'spotify' && source !== 'all') return;
if (spotify_library_albums_status === 'finished') return;
if (spotify_library_albums_status === 'started') return;
getLibraryAlbums();
loadLibrary('spotify:library:albums');
};
handleContextMenu = (e, item) => {
@ -131,28 +123,6 @@ class LibraryAlbums extends React.Component {
});
}
moreURIsToLoad = () => {
const {
albums,
library_albums,
} = this.props;
const uris = [];
if (albums && library_albums) {
for (let i = 0; i < library_albums.length; i++) {
const uri = library_albums[i];
if (!albums.hasOwnProperty(uri) && uriSource(uri) == 'local') {
uris.push(uri);
}
// limit each lookup to 50 URIs
if (uris.length >= 50) break;
}
}
return uris;
}
loadMore = () => {
const {
limit,
@ -189,71 +159,40 @@ class LibraryAlbums extends React.Component {
}
renderView = () => {
let albums = [];
const {
spotify_library,
google_library,
mopidy_library,
items,
source,
sort,
sort_reverse,
view,
} = this.props;
const {
limit,
filter,
} = this.state;
// Spotify library items
if (this.props.spotify_library_albums && (this.props.source == 'all' || this.props.source == 'spotify')) {
for (var uri of this.props.spotify_library_albums) {
if (this.props.albums.hasOwnProperty(uri)) {
albums.push(this.props.albums[uri]);
}
}
let albums = [
...(source === 'all' || source === 'spotify' ? collate(spotify_library, { items }).items : []),
...(source === 'all' || source === 'google' ? collate(google_library, { items }).items : []),
...(source === 'all' || source === 'local' ? collate(mopidy_library, { items }).items : []),
];
if (sort) {
albums = sortItems(albums, sort, sort_reverse);
}
// Mopidy library items
if (this.props.mopidy_library_albums && (this.props.source == 'all' || this.props.source == 'local')) {
for (var uri of this.props.mopidy_library_albums) {
// Construct item placeholder. This is used as Mopidy needs to
// lookup ref objects to get the full object which can take some time
var source = uriSource(uri);
var album = {
uri,
source,
};
if (this.props.albums.hasOwnProperty(uri)) {
albums.push(this.props.albums[uri]);
}
}
}
// Google library items
if (this.props.google_library_albums && (this.props.source == 'all' || this.props.source == 'google')) {
for (var uri of this.props.google_library_albums) {
// Construct item placeholder. This is used as Mopidy needs to
// lookup ref objects to get the full object which can take some time
var source = uriSource(uri);
var album = {
uri,
source,
};
if (this.props.albums.hasOwnProperty(uri)) {
album = this.props.albums[uri];
}
albums.push(album);
}
}
// Collate each album into it's full object (including nested artists)
for (let i = 0; i < albums.length; i++) {
albums[i] = collate(albums[i], { artists: this.props.artists });
}
if (this.props.sort) {
albums = sortItems(albums, this.props.sort, this.props.sort_reverse);
}
if (this.state.filter && this.state.filter !== '') {
albums = applyFilter('name', this.state.filter, albums);
if (filter && filter !== '') {
albums = applyFilter('name', filter, albums);
}
// Apply our lazy-load-rendering
const total_albums = albums.length;
albums = albums.slice(0, this.state.limit);
albums = albums.slice(0, limit);
if (this.props.view == 'list') {
if (view === 'list') {
return (
<section className="content-wrapper">
<List
@ -266,8 +205,8 @@ class LibraryAlbums extends React.Component {
link_prefix="/album/"
/>
<LazyLoadListener
loadKey={total_albums > this.state.limit ? this.state.limit : total_albums}
showLoader={this.state.limit < total_albums}
loadKey={total_albums > limit ? limit : total_albums}
showLoader={limit < total_albums}
loadMore={this.loadMore}
/>
</section>
@ -280,15 +219,28 @@ class LibraryAlbums extends React.Component {
albums={albums}
/>
<LazyLoadListener
loadKey={total_albums > this.state.limit ? this.state.limit : total_albums}
showLoader={this.state.limit < total_albums}
loadKey={total_albums > limit ? limit : total_albums}
showLoader={limit < total_albums}
loadMore={() => this.loadMore()}
/>
</section>
);
}
render() {
render = () => {
const {
spotify_available,
google_available,
sort,
view,
source,
sort_reverse,
} = this.props;
const {
filter,
per_page,
} = this.state;
const source_options = [
{
value: 'all',
@ -300,14 +252,14 @@ class LibraryAlbums extends React.Component {
},
];
if (this.props.spotify_available) {
if (spotify_available) {
source_options.push({
value: 'spotify',
label: i18n('services.spotify.title'),
});
}
if (this.props.google_available) {
if (google_available) {
source_options.push({
value: 'google',
label: i18n('services.google.title'),
@ -355,23 +307,23 @@ class LibraryAlbums extends React.Component {
const options = (
<div className="header__options__wrapper">
<FilterField
initialValue={this.state.filter}
handleChange={(value) => this.setState({ filter: value, limit: this.state.per_page })}
initialValue={filter}
handleChange={(value) => this.setState({ filter: value, limit: per_page })}
onSubmit={e => this.props.uiActions.hideContextMenu()}
/>
<DropdownField
icon="swap_vert"
name={i18n('fields.sort')}
value={this.props.sort}
value={sort}
valueAsLabel
options={sort_options}
selected_icon={this.props.sort ? (this.props.sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
selected_icon={sort ? (sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
handleChange={(val) => { this.setSort(val); this.props.uiActions.hideContextMenu(); }}
/>
<DropdownField
icon="visibility"
name={i18n('fields.view')}
value={this.props.view}
value={view}
valueAsLabel
options={view_options}
handleChange={(val) => { this.props.uiActions.set({ library_albums_view: val }); this.props.uiActions.hideContextMenu(); }}
@ -379,7 +331,7 @@ class LibraryAlbums extends React.Component {
<DropdownField
icon="cloud"
name={i18n('fields.source')}
value={this.props.source}
value={source}
valueAsLabel
options={source_options}
handleChange={(val) => { this.props.uiActions.set({ library_albums_source: val }); this.props.uiActions.hideContextMenu(); }}
@ -399,19 +351,15 @@ class LibraryAlbums extends React.Component {
}
}
const mapStateToProps = (state, ownProps) => ({
const mapStateToProps = (state) => ({
mopidy_uri_schemes: state.mopidy.uri_schemes,
load_queue: state.ui.load_queue,
artists: state.core.artists,
albums: state.core.albums,
mopidy_library_albums: state.mopidy.library_albums,
mopidy_library_albums_status: (state.ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR !== undefined ? state.ui.processes.MOPIDY_LIBRARY_ALBUMS_PROCESSOR.status : null),
items: state.core.items,
mopidy_library: state.core.items['mopidy:library:albums'] || { items_uris: [] },
spotify_library: state.core.items['spotify:library:albums'] || { items_uris: [] },
google_library: state.core.items['google:library:albums'] || { items_uris: [] },
google_available: (state.mopidy.uri_schemes && state.mopidy.uri_schemes.includes('gmusic:')),
google_library_albums: state.google.library_albums,
google_library_albums_status: (state.ui.processes.GOOGLE_LIBRARY_ALBUMS_PROCESSOR !== undefined ? state.ui.processes.GOOGLE_LIBRARY_ALBUMS_PROCESSOR.status : null),
spotify_available: state.spotify.access_token,
spotify_library_albums: state.spotify.library_albums,
spotify_library_albums_status: (state.ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR !== undefined ? state.ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR.status : null),
view: state.ui.library_albums_view,
source: (state.ui.library_albums_source ? state.ui.library_albums_source : 'all'),
sort: (state.ui.library_albums_sort ? state.ui.library_albums_sort : null),

View File

@ -16,6 +16,7 @@ import * as mopidyActions from '../../services/mopidy/actions';
import * as spotifyActions from '../../services/spotify/actions';
import { applyFilter, removeDuplicates, sortItems } from '../../util/arrays';
import { I18n, i18n } from '../../locale';
import { collate } from '../../util/format';
class LibraryPlaylists extends React.Component {
constructor(props) {
@ -114,33 +115,38 @@ class LibraryPlaylists extends React.Component {
renderView = () => {
const {
spotify_library_playlists: {
items: spotify_playlists,
},
mopidy_library_playlists: {
items: mopidy_playlists,
},
spotify_library,
mopidy_library,
items,
sort,
sort_reverse,
view,
source,
} = this.props;
const {
filter,
limit,
} = this.state;
let playlists = [
...spotify_playlists,
...mopidy_playlists,
...(source === 'all' || source === 'spotify' ? collate(spotify_library, { items }).items : []),
...(source === 'all' || source === 'local' ? collate(mopidy_library, { items }).items : []),
];
if (this.props.sort) {
playlists = sortItems(playlists, this.props.sort, this.props.sort_reverse);
if (sort) {
playlists = sortItems(playlists, sort, sort_reverse);
}
playlists = removeDuplicates(playlists);
if (this.state.filter !== '') {
playlists = applyFilter('name', this.state.filter, playlists);
if (filter !== '') {
playlists = applyFilter('name', filter, playlists);
}
// Apply our lazy-load-rendering
const total_playlists = playlists.length;
playlists = playlists.slice(0, this.state.limit);
playlists = playlists.slice(0, limit);
if (this.props.view == 'list') {
if (view === 'list') {
return (
<section className="content-wrapper">
<List
@ -153,8 +159,8 @@ class LibraryPlaylists extends React.Component {
link_prefix="/playlist/"
/>
<LazyLoadListener
loadKey={total_playlists > this.state.limit ? this.state.limit : total_playlists}
loading={this.state.limit < total_playlists}
loadKey={total_playlists > limit ? limit : total_playlists}
loading={limit < total_playlists}
loadMore={() => this.loadMore()}
/>
</section>
@ -167,8 +173,8 @@ class LibraryPlaylists extends React.Component {
playlists={playlists}
/>
<LazyLoadListener
loadKey={total_playlists > this.state.limit ? this.state.limit : total_playlists}
loading={this.state.limit < total_playlists}
loadKey={total_playlists > limit ? limit : total_playlists}
loading={limit < total_playlists}
loadMore={() => this.loadMore()}
/>
</section>
@ -292,20 +298,23 @@ class LibraryPlaylists extends React.Component {
}
}
const mapStateToProps = (state) => ({
slim_mode: state.ui.slim_mode,
mopidy_uri_schemes: state.mopidy.uri_schemes,
spotify_available: state.spotify.access_token,
mopidy_library_playlists: state.core.items['mopidy:library:playlists'] || { items: [] },
spotify_library_playlists: state.core.items['spotify:library:playlists'] || { items: [] },
load_queue: state.ui.load_queue,
me_id: (state.spotify.me ? state.spotify.me.id : false),
view: state.ui.library_playlists_view,
source: (state.ui.library_playlists_source ? state.ui.library_playlists_source : 'all'),
sort: (state.ui.library_playlists_sort ? state.ui.library_playlists_sort : null),
sort_reverse: (state.ui.library_playlists_sort_reverse ? state.ui.library_playlists_sort_reverse : false),
playlists: state.core.playlists,
});
const mapStateToProps = (state) => {
return {
slim_mode: state.ui.slim_mode,
mopidy_uri_schemes: state.mopidy.uri_schemes,
spotify_available: state.spotify.access_token,
items: state.core.items,
mopidy_library: state.core.items['mopidy:library:playlists'] || { items_uris: [] },
spotify_library: state.core.items['spotify:library:playlists'] || { items_uris: [] },
load_queue: state.ui.load_queue,
me_id: (state.spotify.me ? state.spotify.me.id : false),
view: state.ui.library_playlists_view,
source: (state.ui.library_playlists_source ? state.ui.library_playlists_source : 'all'),
sort: (state.ui.library_playlists_sort ? state.ui.library_playlists_sort : null),
sort_reverse: (state.ui.library_playlists_sort_reverse ? state.ui.library_playlists_sort_reverse : false),
playlists: state.core.playlists,
};
};
const mapDispatchToProps = (dispatch) => ({
coreActions: bindActionCreators(coreActions, dispatch),