Loading playlist library with new URI; Starting #600

This commit is contained in:
James Barnsley
2020-09-04 09:21:42 +12:00
parent 6020781be0
commit 6dca4ef475
13 changed files with 579 additions and 415 deletions

File diff suppressed because one or more lines are too long

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 = "1598691847";
var build = "1599105011";
var version = "3.52.4";
// Construct the script tag

View File

@ -610,6 +610,39 @@ class ContextMenu extends React.Component {
hideContextMenu();
}
reload = () => {
const {
uiActions: {
hideContextMenu,
},
coreActions: {
loadArtist,
loadAlbum,
loadPlaylist,
},
menu: {
uris,
} = {},
} = this.props;
const uri = uris[0];
switch (uriType(uri)) {
case 'artist':
loadArtist(uri, true);
break;
case 'album':
loadAlbum(uri, true);
break;
case 'playlist':
loadPlaylist(uri, true);
break;
default:
break;
}
hideContextMenu();
}
renderTitle = () => {
const {
uiActions: {
@ -1092,6 +1125,16 @@ class ContextMenu extends React.Component {
</div>
);
const reload = (
<div className="context-menu__item">
<a className="context-menu__item__link" onClick={this.reload}>
<span className="context-menu__item__label">
<I18n path="context_menu.reload" />
</span>
</a>
</div>
);
switch (context.name) {
case 'album':
return (
@ -1104,6 +1147,7 @@ class ContextMenu extends React.Component {
<div className="context-menu__divider" />
{go_to_artist}
{copy_uris}
{reload}
</div>
);
case 'artist':
@ -1116,6 +1160,7 @@ class ContextMenu extends React.Component {
<div className="context-menu__divider" />
{context.source === 'spotify' && go_to_recommendations}
{copy_uris}
{reload}
</div>
);
case 'playlist':
@ -1138,6 +1183,7 @@ class ContextMenu extends React.Component {
{delete_playlist}
</div>
)}
{reload}
</div>
);
case 'current-track':

View File

@ -173,6 +173,7 @@ context_menu:
discover_similar: Discover similar
start_radio: Start radio
copy_uri: Copy URI(s)
reload: Reload
dropzones:
dragging_things: 'Dragging %{count} things'
playback_controls:

View File

@ -160,6 +160,14 @@ export function loadUserPlaylists(uri, force_reload = false) {
};
}
export function loadLibrary(uri, force_reload = false) {
return {
type: 'LOAD_LIBRARY',
uri,
force_reload,
};
}
/**
* Record loaders

View File

@ -4,7 +4,7 @@ import ReactGA from 'react-ga';
import localForage from 'localforage';
import { arrayOf } from '../../util/arrays';
import URILink from '../../components/URILink';
import { uriSource, upgradeSpotifyPlaylistUris, uriType } from '../../util/helpers';
import { uriSource, upgradeSpotifyPlaylistUris, uriType, titleCase } from '../../util/helpers';
import {
formatTracks,
formatTrack,
@ -348,20 +348,14 @@ const CoreMiddleware = (function () {
break;
case 'LOAD_ALBUM':
// Load from Redux store
if (
!action.force_reload
&& store.getState().core.albums[action.uri]
&& store.getState().core.albums[action.uri].tracks_uris) {
if (!action.force_reload && store.getState().core.items[action.uri]) {
console.info(`Loading "${action.uri}" from index`);
break;
}
// Try our cold storage
localForage.getItem(action.uri).then((result) => {
if (result) {
if (result && !action.force_reload) {
console.info(`Loading "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
} else {
@ -416,7 +410,7 @@ const CoreMiddleware = (function () {
// Try our cold storage
localForage.getItem(action.uri).then((result) => {
if (result) {
if (result && !action.force_reload) {
console.info(`Loading "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
} else {
@ -440,28 +434,39 @@ const CoreMiddleware = (function () {
break;
case 'LOAD_PLAYLIST':
if (
!action.force_reload
&& store.getState().core.playlists[action.uri]
&& store.getState().core.playlists[action.uri].tracks_uris !== undefined
) {
console.info(`Loading "${action.uri}" from index`);
const fetchPlaylist = () => {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(spotifyActions.getPlaylist(action.uri));
if (spotify.me) {
store.dispatch(spotifyActions.following(action.uri));
}
break;
default:
store.dispatch(mopidyActions.getPlaylist(action.uri));
break;
}
};
if (action.force_reload) {
fetchPlaylist();
break;
}
if (store.getState().core.items[action.uri]) {
console.info(`Using "${action.uri}" from index`);
break;
}
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(spotifyActions.getPlaylist(action.uri));
if (spotify.me) {
store.dispatch(spotifyActions.following(action.uri));
}
break;
default:
store.dispatch(mopidyActions.getPlaylist(action.uri));
break;
}
localForage.getItem(action.uri).then((result) => {
if (result) {
console.info(`Restoring "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
} else {
fetchPlaylist();
}
});
next(action);
break;
@ -514,6 +519,44 @@ const CoreMiddleware = (function () {
next(action);
break;
case 'LOAD_LIBRARY':
const fetchLibrary = () => {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(
spotifyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
);
break;
default:
store.dispatch(
mopidyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
);
break;
}
};
if (action.force_reload) {
fetchLibrary();
break;
}
if (store.getState().core.items[action.uri]) {
console.info(`Using "${action.uri}" from index`);
break;
}
localForage.getItem(action.uri).then((result) => {
if (result) {
console.info(`Restoring "${action.uri}" from database`);
store.dispatch(coreActions.restoreFromColdStore(result));
} else {
fetchLibrary();
}
});
next(action);
break;
/**
* Index actions

View File

@ -1600,39 +1600,35 @@ const MopidyMiddleware = (function () {
case 'MOPIDY_GET_LIBRARY_PLAYLISTS':
request(store, 'playlists.asList')
.then((response) => {
// drop in our URI list
const playlist_uris = arrayOf('uri', response);
const playlist_uris_filtered = [];
// Remove any Spotify playlists. These will be handled by our Spotify API
for (var i = 0; i < playlist_uris.length; i++) {
if (uriSource(playlist_uris[i]) != 'spotify') {
playlist_uris_filtered.push(playlist_uris[i]);
}
}
store.dispatch({ type: 'MOPIDY_LIBRARY_PLAYLISTS_LOADED', uris: playlist_uris_filtered });
store.dispatch({ type: 'MOPIDY_LIBRARY_PLAYLISTS_LOADED_ALL' });
const playlist_uris = arrayOf('uri', response).filter(
(uri) => uriSource(uri) !== 'spotify',
);
const libraryPlaylists = [];
// get the full playlist objects
for (var i = 0; i < playlist_uris_filtered.length; i++) {
request(store, 'playlists.lookup', { uri: playlist_uris_filtered[i] })
playlist_uris.forEach((uri, index) => {
request(store, 'playlists.lookup', { uri })
.then((response) => {
const source = uriSource(response.uri);
const playlist = {
libraryPlaylists.push({
type: 'playlist',
name: response.name,
uri: response.uri,
source,
source: uriSource(response.uri),
provider: 'mopidy',
last_modified: response.last_modified,
tracks_total: (response.tracks ? response.tracks.length : 0),
};
tracks: response.tracks,
});
store.dispatch(coreActions.playlistLoaded(playlist));
if (index === playlist_uris.length - 1) {
store.dispatch(coreActions.itemLoaded({
uri: 'mopidy:library:playlists',
items: libraryPlaylists,
}));
}
});
}
});
});
break;

View File

@ -18,6 +18,7 @@ import {
formatArtist,
formatArtists,
formatAlbums,
formatPlaylists,
} from '../../util/format';
import URILink from '../../components/URILink';
import { i18n } from '../../locale';
@ -1403,6 +1404,8 @@ export function getPlaylist(uri) {
request(dispatch, getState, `playlists/${getFromUri('playlistid', uri)}?market=${getState().spotify.country}`)
.then(
(response) => {
let tracks = formatTracks(response.tracks.items);
// convert links in description
let description = null;
if (response.description) {
@ -1412,22 +1415,29 @@ export function getPlaylist(uri) {
description = description.split('<a href="spotify:user:').join('<a href="#' + '/user/spotify:user:');
}
const tracks = formatTracks(response.tracks.items);
const playlist = {
dispatch(coreActions.itemLoaded({
...formatPlaylist(response),
is_completely_loaded: true,
user_uri: response.owner.uri,
tracks_uris: tracks ? arrayOf('uri', tracks) : null,
tracks_more: response.tracks.next,
tracks_total: response.tracks.total,
user: formatUser(response.owner),
tracks,
description,
};
}));
dispatch(coreActions.userLoaded(formatUser(response.owner)));
dispatch(coreActions.tracksLoaded(tracks));
dispatch(coreActions.playlistLoaded(playlist));
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,
}));
}
});
if (response.tracks.next) {
fetchTracks(response.tracks.next);
}
},
(error) => {
dispatch(coreActions.handleException(
@ -1721,6 +1731,23 @@ export function flushLibrary() {
export function getLibraryPlaylists() {
return (dispatch, getState) => {
let libraryPlaylists = [];
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.itemLoaded({
uri: 'spotify:library:playlists',
items: libraryPlaylists,
}));
}
});
fetchLibraryPlaylists('me/playlists?limit=50');
/*
const last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR;
if (!last_run) {
@ -1732,6 +1759,7 @@ export function getLibraryPlaylists() {
} 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' }));
}
*/
};
}

View File

@ -247,7 +247,7 @@ const formatArtists = function (records = []) {
const formatPlaylists = function (records = []) {
const formatted = [];
for (const record of records) {
formatted.push(formatTrack(record));
formatted.push(formatPlaylist(record));
}
return formatted;
};
@ -411,10 +411,7 @@ const formatPlaylist = function (data) {
'last_modified',
'can_edit',
'owner',
'user_uri',
'tracks_uris',
'tracks_total',
'tracks_more',
'tracks',
];
// Loop fields and import from data

View File

@ -176,6 +176,8 @@ const uriType = function (uri) {
}
switch (exploded[1]) {
case 'library':
return exploded[2];
case 'track':
case 'artist':
case 'album':

View File

@ -83,27 +83,6 @@ class Playlist extends React.Component {
);
}
loadMore = () => {
const {
spotifyActions: {
getMore,
},
playlist: {
uri,
tracks_more,
},
} = this.props;
getMore(
tracks_more,
{
parent_type: 'playlist',
parent_key: uri,
records_type: 'track',
},
);
}
handleContextMenu = (e) => {
const {
uiActions: {
@ -163,7 +142,16 @@ class Playlist extends React.Component {
}
removeTracks = (tracks_indexes) => {
this.props.coreActions.removeTracksFromPlaylist(this.props.playlist.uri, tracks_indexes);
const {
coreActions: {
removeTracksFromPlaylist,
},
playlist: {
uri,
},
} = this.props;
removeTracksFromPlaylist(uri, tracks_indexes);
}
inLibrary = () => {
@ -283,16 +271,14 @@ class Playlist extends React.Component {
render = () => {
const {
uri,
playlist: playlistProp,
playlist,
load_queue,
tracks,
users,
slim_mode,
} = this.props;
const playlist_id = getFromUri('playlistid', uri);
if (!playlistProp) {
if (!playlist) {
if (isLoading(load_queue, [`spotify_playlists/${playlist_id}?`])) {
return <Loader body loading />
}
@ -305,22 +291,11 @@ class Playlist extends React.Component {
);
}
const playlist = collate(playlistProp, { tracks, users });
let context = 'playlist';
if (playlist.can_edit) {
context = 'editable-playlist';
}
const is_loading_tracks = (
playlist.tracks_total !== 0
&& (
!playlist.tracks_uris
|| (playlist.tracks_uris && !playlist.tracks)
|| (playlist.tracks_uris.length !== playlist.tracks.length)
)
);
return (
<div className="view playlist-view content-wrapper preserve-3d">
@ -383,11 +358,6 @@ class Playlist extends React.Component {
removeTracks={this.removeTracks}
reorderTracks={this.reorderTracks}
/>
<LazyLoadListener
loadKey={playlist.tracks_more}
showLoader={is_loading_tracks || playlist.tracks_more}
loadMore={this.loadMore}
/>
</section>
</div>
);
@ -403,9 +373,7 @@ const mapStateToProps = (state, ownProps) => {
load_queue,
} = {},
core: {
users,
tracks,
playlists,
items,
} = {},
spotify: {
library_playlists: spotify_library_playlists,
@ -425,9 +393,7 @@ const mapStateToProps = (state, ownProps) => {
slim_mode,
theme,
load_queue,
users,
tracks,
playlist: (playlists[uri] !== undefined ? playlists[uri] : false),
playlist: items[uri] || null,
spotify_library_playlists,
local_library_playlists,
spotify_authorized,

View File

@ -46,34 +46,29 @@ class LibraryPlaylists extends React.Component {
getMopidyLibrary = () => {
const {
source,
mopidy_library_playlists,
mopidyActions: {
getLibraryPlaylists,
coreActions: {
loadLibrary,
},
} = this.props;
if (source !== 'local' && source !== 'all') return;
if (mopidy_library_playlists) return;
getLibraryPlaylists();
loadLibrary('mopidy:library:playlists');
};
getSpotifyLibrary = () => {
const {
source,
spotify_available,
spotify_library_playlists_status,
spotifyActions: {
getLibraryPlaylists,
coreActions: {
loadLibrary,
},
} = this.props;
if (!spotify_available) return;
if (source !== 'spotify' && source !== 'all') return;
if (spotify_library_playlists_status === 'finished') return;
if (spotify_library_playlists_status === 'started') return;
getLibraryPlaylists();
loadLibrary('spotify:library:playlists');
};
componentDidUpdate = ({ source: prevSource }) => {
@ -117,28 +112,20 @@ class LibraryPlaylists extends React.Component {
this.props.uiActions.set(data);
}
renderView() {
let playlists = [];
renderView = () => {
const {
spotify_library_playlists: {
items: spotify_playlists,
},
mopidy_library_playlists: {
items: mopidy_playlists,
},
} = this.props;
// Spotify library items
if (this.props.spotify_library_playlists && (this.props.source == 'all' || this.props.source == 'spotify')) {
for (var i = 0; i < this.props.spotify_library_playlists.length; i++) {
var uri = this.props.spotify_library_playlists[i];
if (this.props.playlists.hasOwnProperty(uri)) {
playlists.push(this.props.playlists[uri]);
}
}
}
// Mopidy library items
if (this.props.mopidy_library_playlists && (this.props.source == 'all' || this.props.source == 'local')) {
for (var i = 0; i < this.props.mopidy_library_playlists.length; i++) {
var uri = this.props.mopidy_library_playlists[i];
if (this.props.playlists.hasOwnProperty(uri)) {
playlists.push(this.props.playlists[uri]);
}
}
}
let playlists = [
...spotify_playlists,
...mopidy_playlists,
];
if (this.props.sort) {
playlists = sortItems(playlists, this.props.sort, this.props.sort_reverse);
@ -308,11 +295,9 @@ class LibraryPlaylists extends React.Component {
const mapStateToProps = (state) => ({
slim_mode: state.ui.slim_mode,
mopidy_uri_schemes: state.mopidy.uri_schemes,
mopidy_library_playlists: state.mopidy.library_playlists,
mopidy_library_playlists_status: (state.ui.processes.MOPIDY_LIBRARY_PLAYLISTS_PROCESSOR !== undefined ? state.ui.processes.MOPIDY_LIBRARY_PLAYLISTS_PROCESSOR.status : null),
spotify_available: state.spotify.access_token,
spotify_library_playlists: state.spotify.library_playlists,
spotify_library_playlists_status: (state.ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR !== undefined ? state.ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status : null),
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,