Upgrading other playlist views to use playlist providers selector

This commit is contained in:
James Barnsley
2021-05-05 21:20:00 +12:00
parent e186e51730
commit a26611261d
11 changed files with 520 additions and 435 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -115,7 +115,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1619945300";
var build = "1620206341";
var version = "3.57.6";
// Construct the script tag

View File

@ -15,7 +15,6 @@ import {
import {
arrayOf,
sortItems,
indexToArray,
} from '../util/arrays';
import Link from './Link';
import Icon from './Icon';
@ -28,8 +27,18 @@ import * as pusherActions from '../services/pusher/actions';
import * as mopidyActions from '../services/mopidy/actions';
import * as lastfmActions from '../services/lastfm/actions';
import * as spotifyActions from '../services/spotify/actions';
import { I18n, i18n } from '../locale';
import { collate, encodeUri } from '../util/format';
import { I18n } from '../locale';
import { encodeUri } from '../util/format';
import {
makeProcessProgressSelector,
makeProvidersSelector,
makeLibrarySelector,
} from '../util/selectors';
const processKeys = [
'MOPIDY_GET_LIBRARY_PLAYLISTS',
'SPOTIFY_GET_LIBRARY_PLAYLISTS',
];
class ContextMenu extends React.Component {
constructor(props) {
@ -750,28 +759,21 @@ class ContextMenu extends React.Component {
setSubmenu = (name) => {
const { submenu } = this.state;
if (submenu !== name && name === 'add-to-playlist') {
this.getLibraries();
}
this.setState({ submenu: name });
}
getLibraries = () => {
const {
spotify_available,
spotify_library_playlists: {
items_uris: spotify_library,
},
mopidy_library_playlists: {
items_uris: mopidy_library,
},
providers,
coreActions: {
loadLibrary,
},
} = this.props;
if (submenu !== name && name === 'add-to-playlist') {
if (spotify_available && !spotify_library.length) {
loadLibrary('spotify:library:playlists', 'playlists');
}
if (!mopidy_library.length) {
loadLibrary('mopidy:library:playlists', 'playlists');
}
}
this.setState({ submenu: name });
providers.forEach((provider) => loadLibrary(provider.uri, 'playlists'));
}
closeSubmenu = () => this.setState({ submenu: null });
@ -779,20 +781,13 @@ class ContextMenu extends React.Component {
renderSubmenu = () => {
const { submenu } = this.state;
const {
items,
spotify_library_playlists,
mopidy_library_playlists,
playlists: allPlaylists,
loading_progress,
} = this.props;
let list = null;
const isLoading = false;
if (submenu === 'add-to-playlist') {
let playlists = [
...(collate(spotify_library_playlists, { items }).items || []),
...(collate(mopidy_library_playlists, { items }).items || []),
];
playlists = compact(playlists.map((playlist) => {
let playlists = compact(allPlaylists.map((playlist) => {
if (!playlist.can_edit) return null;
return {
...playlist,
@ -847,7 +842,7 @@ class ContextMenu extends React.Component {
</span>
</a>
</div>
{isLoading ? (
{loading_progress ? (
<div className="context-menu__item context-menu__item--loader">
<Loader className="context-menu__item" mini loading />
</div>
@ -1345,13 +1340,23 @@ class ContextMenu extends React.Component {
}
}
const librarySelector = makeLibrarySelector('playlists');
const providersSelector = makeProvidersSelector('playlists');
const processProgressSelector = makeProcessProgressSelector(processKeys);
const mapStateToProps = (state) => ({
providers: providersSelector(state),
playlists: librarySelector(state, 'playlists'),
loading_progress: processProgressSelector(state),
menu: state.ui.context_menu,
load_queue: state.ui.load_queue,
processes: state.ui.processes,
current_track: state.core.current_track,
current_tracklist: state.core.current_tracklist,
queue_metadata: state.core.queue_metadata,
pinned: state.pusher.pinned,
lastfm_authorized: state.lastfm.authorization,
// TODO: Delete all of the below
spotify_available: state.spotify.access_token,
spotify_library_playlists: state.core.libraries['spotify:library:playlists'] || { items_uris: [] },
spotify_library_artists: state.core.libraries['spotify:library:artists'] || { items_uris: [] },
@ -1361,8 +1366,6 @@ const mapStateToProps = (state) => ({
mopidy_library_artists: state.core.libraries['mopidy:library:artists'] || { items_uris: [] },
mopidy_library_albums: state.core.libraries['mopidy:library:albums'] || { items_uris: [] },
items: state.core.items,
pinned: state.pusher.pinned,
lastfm_authorized: state.lastfm.authorization,
});
const mapDispatchToProps = (dispatch) => ({

View File

@ -149,6 +149,8 @@ services:
unlove: Unlove
youtube:
title: YouTube
tidal:
title: Tidal
##
# Components

View File

@ -437,8 +437,11 @@ export function deletePlaylist(uri) {
};
}
export function getLibraryPlaylists() {
return { type: 'MOPIDY_GET_LIBRARY_PLAYLISTS' };
export function getLibraryPlaylists(uri) {
return {
type: 'MOPIDY_GET_LIBRARY_PLAYLISTS',
uri,
};
}
export function getPlaylist(uri, options) {

View File

@ -1668,9 +1668,19 @@ const MopidyMiddleware = (function () {
subdirectories.push(formatArtist({ ...item, loading: true }));
subdirectoryImagesToLoad.push(item.uri);
} else if (item.__model__ === 'Ref' && item.type === 'playlist') {
console.debug('playlist', { item })
subdirectories.push(formatPlaylist({ ...item, loading: true }));
subdirectoryImagesToLoad.push(item.uri);
// Tidal moods and genres incorrectly marked as Playlist type
if (
item.uri.indexOf('tidal:mood') > -1 ||
item.uri.indexOf('tidal:genre') > -1
) {
subdirectories.push({
...item,
type: 'directory',
});
} else {
subdirectories.push(formatPlaylist({ ...item, loading: true }));
subdirectoryImagesToLoad.push(item.uri);
}
} else {
subdirectories.push(item);
}
@ -1770,7 +1780,7 @@ const MopidyMiddleware = (function () {
if (!raw_response.length) {
store.dispatch(coreActions.libraryLoaded({
uri: 'mopidy:library:artists',
type: action.uriType,
type: 'artists',
items_uris: [],
}));
store.dispatch(uiActions.processFinished(action.type));
@ -1796,70 +1806,134 @@ const MopidyMiddleware = (function () {
case 'MOPIDY_GET_LIBRARY_PLAYLISTS': {
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
request(store, 'playlists.asList')
.then((listResponse) => {
// Remove any Spotify playlists. These will be handled by our Spotify API
const playlist_uris = arrayOf('uri', listResponse).filter(
(playlistUri) => uriSource(playlistUri) !== 'spotify',
);
const libraryPlaylists = [];
// Built-in playlist support works differently to other providers
if (action.uri === 'm3u:playlists') {
request(store, 'playlists.asList')
.then((listResponse) => {
const libraryPlaylists = [];
const playlist_uris = arrayOf('uri', listResponse).filter(
(pUri) => (pUri.indexOf('m3u') > -1),
);
store.dispatch(
uiActions.updateProcess(
action.type,
{
total: playlist_uris.length,
remaining: playlist_uris.length,
},
),
);
store.dispatch(
uiActions.updateProcess(
action.type,
{
total: playlist_uris.length,
remaining: playlist_uris.length,
},
),
);
if (playlist_uris.length) {
playlist_uris.forEach((uri, index) => {
request(store, 'playlists.lookup', { uri })
.then((response) => {
if (response) {
libraryPlaylists.push(
formatPlaylist({
name: response.name,
uri: response.uri,
can_edit: uriSource(response.uri) === 'm3u',
last_modified: response.last_modified,
// By not including actual tracks they will be fetched when needed. We don't
// want these simple tracks because they don't contain duration, artist, etc.
tracks_total: response.tracks ? response.tracks.length : null,
}),
);
}
if (playlist_uris.length) {
playlist_uris.forEach((uri, index) => {
request(store, 'playlists.lookup', { uri })
.then((response) => {
libraryPlaylists.push(
formatPlaylist({
name: response.name,
uri: response.uri,
can_edit: uriSource(response.uri) === 'm3u',
last_modified: response.last_modified,
// By not including actual tracks they will be fetched when needed. We don't
// want these simple tracks because they don't contain duration, artist, etc.
tracks_total: response.tracks ? response.tracks.length : null,
}),
);
store.dispatch(
uiActions.updateProcess(
action.type,
{
remaining: playlist_uris.length - index - 1,
},
),
);
store.dispatch(
uiActions.updateProcess(
action.type,
{
remaining: playlist_uris.length - index - 1,
},
),
);
if (index === playlist_uris.length - 1) {
store.dispatch(coreActions.itemsLoaded(libraryPlaylists));
store.dispatch(coreActions.libraryLoaded({
uri: action.uri,
type: 'playlists',
items_uris: arrayOf('uri', libraryPlaylists),
}));
store.dispatch(uiActions.processFinished(action.type));
}
});
});
} else {
store.dispatch(coreActions.libraryLoaded({
uri: action.uri,
type: 'playlists',
items_uris: [],
}));
store.dispatch(uiActions.stopLoading('mopidy:library:playlists'));
store.dispatch(uiActions.processFinished(action.type));
}
});
} else {
request(store, 'library.browse', { uri: action.uri })
.then((browseResponse) => {
const libraryPlaylists = [];
if (index === playlist_uris.length - 1) {
store.dispatch(coreActions.itemsLoaded(libraryPlaylists));
store.dispatch(coreActions.libraryLoaded({
uri: 'mopidy:library:playlists',
type: action.uriType,
items_uris: arrayOf('uri', libraryPlaylists),
}));
store.dispatch(uiActions.processFinished(action.type));
}
});
});
} else {
store.dispatch(coreActions.libraryLoaded({
uri: action.uri,
type: 'playlists',
items_uris: [],
}));
store.dispatch(uiActions.stopLoading('mopidy:library:playlists'));
store.dispatch(uiActions.processFinished(action.type));
}
});
store.dispatch(
uiActions.updateProcess(
action.type,
{
total: browseResponse.length,
remaining: browseResponse.length,
},
),
);
if (browseResponse.length) {
browseResponse.forEach((playlist, index) => {
request(store, 'library.lookup', { uris: [playlist.uri] })
.then((response) => {
if (response) {
libraryPlaylists.push(
formatPlaylist({
name: playlist.name,
uri: playlist.uri,
can_edit: uriSource(playlist.uri) === 'm3u',
last_modified: playlist.last_modified,
tracks: formatTracks(response[playlist.uri]),
}),
);
}
store.dispatch(
uiActions.updateProcess(
action.type,
{
remaining: browseResponse.length - index - 1,
},
),
);
if (index === browseResponse.length - 1) {
store.dispatch(coreActions.itemsLoaded(libraryPlaylists));
store.dispatch(coreActions.libraryLoaded({
uri: action.uri,
type: 'playlists',
items_uris: arrayOf('uri', libraryPlaylists),
}));
store.dispatch(uiActions.processFinished(action.type));
}
});
});
} else {
store.dispatch(coreActions.libraryLoaded({
uri: action.uri,
type: 'playlists',
items_uris: [],
}));
store.dispatch(uiActions.stopLoading('mopidy:library:playlists'));
store.dispatch(uiActions.processFinished(action.type));
}
});
}
break;
}
@ -1868,7 +1942,6 @@ const MopidyMiddleware = (function () {
request(store, 'library.browse', { uri: action.uri })
.then((browseResponse) => {
console.debug({ action, browseResponse })
const allUris = arrayOf('uri', browseResponse);
store.dispatch(uiActions.updateProcess(

View File

@ -99,6 +99,23 @@ const makeProcessProgressSelector = (keys) => createSelector(
);
const providers = {
playlists: [
{
scheme: 'm3u:',
uri: 'm3u:playlists',
title: i18n('services.mopidy.local'),
},
{
scheme: 'spotify:',
uri: 'spotify:library:playlists',
title: i18n('services.spotify.title'),
},
{
scheme: 'tidal:',
uri: 'tidal:my_playlists',
title: i18n('services.tidal.title'),
},
],
albums: [
{
scheme: 'local:',
@ -115,6 +132,11 @@ const providers = {
uri: 'spotify:library:albums',
title: i18n('services.spotify.title'),
},
{
scheme: 'tidal:',
uri: 'tidal:my_albums',
title: i18n('services.tidal.title'),
},
{
scheme: 'ytmusic:',
uri: 'ytmusic:album',
@ -137,6 +159,11 @@ const providers = {
uri: 'spotify:library:artists',
title: i18n('services.spotify.title'),
},
{
scheme: 'tidal:',
uri: 'tidal:my_artists',
title: i18n('services.tidal.title'),
},
{
scheme: 'ytmusic:',
uri: 'ytmusic:artist',

View File

@ -18,6 +18,7 @@ import Loader from '../../components/Loader';
import {
makeLibrarySelector,
makeProcessProgressSelector,
makeProvidersSelector,
getLibrarySource,
} from '../../util/selectors';
@ -44,16 +45,14 @@ class LibraryPlaylists extends React.Component {
setWindowTitle(i18n('library.playlists.title'));
this.getMopidyLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
refresh = () => {
const { uiActions: { hideContextMenu } } = this.props;
hideContextMenu();
this.getMopidyLibrary(true);
this.getSpotifyLibrary(true);
this.getLibraries(true);
}
cancelRefresh = () => {
@ -63,46 +62,35 @@ class LibraryPlaylists extends React.Component {
cancelProcess(processKeys);
}
getMopidyLibrary = (forceRefetch = false) => {
const {
source,
coreActions: {
loadLibrary,
},
} = this.props;
if (source !== 'local' && source !== 'all') return;
loadLibrary('mopidy:library:playlists', 'playlists', { forceRefetch });
};
getSpotifyLibrary = (forceRefetch = false) => {
const {
source,
spotify_available,
coreActions: {
loadLibrary,
},
} = this.props;
if (!spotify_available) return;
if (source !== 'spotify' && source !== 'all') return;
loadLibrary('spotify:library:playlists', 'playlists', { forceRefetch });
};
componentDidUpdate = ({ source: prevSource }) => {
const { source } = this.props;
if (source !== prevSource) {
this.getMopidyLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
}
getLibraries = (forceRefetch = false) => {
const {
source,
providers,
coreActions: {
loadLibrary,
},
} = this.props;
let uris = [];
if (source === 'all') {
uris = providers.map((p) => p.uri);
} else {
uris.push(source);
}
uris.forEach((uri) => loadLibrary(uri, 'playlists', { forceRefetch }));
};
setSort(value) {
let reverse = false;
if (this.props.sort == value) reverse = !this.props.sort_reverse;
if (this.props.sort === value) reverse = !this.props.sort_reverse;
const data = {
library_playlists_sort_reverse: reverse,
@ -169,7 +157,7 @@ class LibraryPlaylists extends React.Component {
render = () => {
const {
uiActions,
spotify_available,
providers,
source,
sort,
sort_reverse,
@ -180,24 +168,6 @@ class LibraryPlaylists extends React.Component {
filter,
} = this.state;
const source_options = [
{
value: 'all',
label: i18n('fields.filters.all'),
},
{
value: 'local',
label: i18n('services.mopidy.local'),
},
];
if (spotify_available) {
source_options.push({
value: 'spotify',
label: i18n('services.spotify.title'),
});
}
const view_options = [
{
value: 'thumbnails',
@ -269,7 +239,13 @@ class LibraryPlaylists extends React.Component {
name={i18n('fields.source')}
valueAsLabel
value={source}
options={source_options}
options={[
{
value: 'all',
label: i18n('fields.filters.all'),
},
...providers.map((p) => ({ value: p.uri, label: p.title })),
]}
handleChange={(value) => { uiActions.set({ library_playlists_source: value }); uiActions.hideContextMenu(); }}
/>
<Button
@ -307,6 +283,7 @@ class LibraryPlaylists extends React.Component {
const librarySelector = makeLibrarySelector('playlists');
const processProgressSelector = makeProcessProgressSelector(processKeys);
const providersSelector = makeProvidersSelector('playlists');
const mapStateToProps = (state) => {
const {
spotify: {
@ -318,8 +295,7 @@ const mapStateToProps = (state) => {
return {
slim_mode: state.ui.slim_mode,
mopidy_uri_schemes: state.mopidy.uri_schemes,
spotify_available: state.spotify.access_token,
providers: providersSelector(state),
playlists: librarySelector(state, 'playlists'),
loading_progress: processProgressSelector(state),
source: getLibrarySource(state, 'playlists'),

View File

@ -12,8 +12,12 @@ import * as spotifyActions from '../../services/spotify/actions';
import { sourceIcon } from '../../util/helpers';
import { sortItems } from '../../util/arrays';
import { I18n, i18n } from '../../locale';
import { collate, decodeUri } from '../../util/format';
import { makeProcessProgressSelector } from '../../util/selectors';
import { decodeUri } from '../../util/format';
import {
makeProcessProgressSelector,
makeProvidersSelector,
makeLibrarySelector,
} from '../../util/selectors';
const processKeys = [
'MOPIDY_GET_LIBRARY_PLAYLISTS',
@ -23,32 +27,26 @@ const processKeys = [
class AddToPlaylist extends React.Component {
componentDidMount = () => {
const {
spotify_library: {
items_uris: spotify_library,
},
mopidy_library: {
items_uris: mopidy_library,
},
spotify_available,
coreActions: {
loadLibrary,
},
uiActions: {
setWindowTitle,
},
} = this.props;
if (!spotify_library.length && spotify_available) {
loadLibrary('spotify:library:playlists', 'playlists');
}
if (!mopidy_library.length) {
loadLibrary('mopidy:library:playlists', 'playlists');
}
this.getLibraries();
setWindowTitle(i18n('modal.add_to_playlist.title'));
}
getLibraries = () => {
const {
providers,
coreActions: {
loadLibrary,
},
} = this.props;
providers.forEach((provider) => loadLibrary(provider.uri, 'playlists'));
};
playlistSelected = (playlist_uri) => {
const {
coreActions: {
@ -62,9 +60,7 @@ class AddToPlaylist extends React.Component {
renderList = () => {
const {
items,
spotify_library = { items_uris: [] },
mopidy_library = { items_uris: [] },
playlists: allPlaylists,
loading_progress,
} = this.props;
@ -72,11 +68,7 @@ class AddToPlaylist extends React.Component {
return <Loader body loading progress={loading_progress} />;
}
let playlists = [
...(collate(spotify_library, { items }).items),
...(collate(mopidy_library, { items }).items),
].filter((playlist) => playlist.can_edit);
let playlists = allPlaylists.filter((playlist) => playlist.can_edit);
playlists = sortItems(playlists, 'name');
if (playlists.length > 0) {
@ -128,6 +120,9 @@ class AddToPlaylist extends React.Component {
}
}
const librarySelector = makeLibrarySelector('playlists');
const processProgressSelector = makeProcessProgressSelector(processKeys);
const providersSelector = makeProvidersSelector('playlists');
const mapStateToProps = (state, ownProps) => {
const {
spotify: {
@ -136,29 +131,18 @@ const mapStateToProps = (state, ownProps) => {
} = {},
access_token: spotify_available,
},
core: {
playlists,
items,
libraries,
},
mopidy: {
uri_schemes: mopidy_uri_schemes,
},
} = state;
const processProgressSelector = makeProcessProgressSelector(processKeys);
const unencodedUris = ownProps.match.params?.uris.split(',');
const uris = unencodedUris ? unencodedUris.map((uri) => decodeUri(uri)) : [];
const unencodedUris = ownProps.match.params?.uris;
const uris = unencodedUris ? decodeUri(unencodedUris).split(',') : [];
return {
uris,
mopidy_uri_schemes,
items,
mopidy_library: libraries['mopidy:library:playlists'] || { items_uris: [] },
spotify_library: libraries['spotify:library:playlists'] || { items_uris: [] },
providers: providersSelector(state),
playlists: librarySelector(state, 'playlists'),
spotify_available,
loading_progress: processProgressSelector(state),
me_id,
playlists,
};
};