Merge branch 'feat/libraries-support' into develop

This commit is contained in:
James Barnsley
2021-04-23 06:37:25 +12:00
25 changed files with 618 additions and 593 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ npm-debug.log
venv
docker/mopidy.conf
docker/data/
docker/jellyfin/
docker/icecast2.xml
docker-compose.yml
/.tox/

View File

@ -53,9 +53,11 @@ RUN python3.7 -m pip install --no-cache \
mopidy-local \
Mopidy-TuneIn \
Mopidy-Youtube \
Mopidy-YTMusic \
Mopidy-SoundCloud \
Mopidy-Podcast \
Mopidy-dLeyna \
Mopidy-Jellyfin \
dbus-python \
# pip not up-to-date for Mopidy-Tidal (https://github.com/tehkillerbee/mopidy-tidal/issues/14)
git+https://github.com/tehkillerbee/mopidy-tidal.git@master

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 = "1618389934";
var build = "1619116567";
var version = "3.57.6";
// Construct the script tag

View File

@ -761,8 +761,12 @@ class ContextMenu extends React.Component {
},
} = this.props;
if (submenu !== name && name === 'add-to-playlist') {
if (spotify_available && !spotify_library.length) loadLibrary('spotify:library:playlists');
if (!mopidy_library.length) loadLibrary('mopidy:library:playlists');
if (spotify_available && !spotify_library.length) {
loadLibrary('spotify:library:playlists', 'playlists');
}
if (!mopidy_library.length) {
loadLibrary('mopidy:library:playlists', 'playlists');
}
}
this.setState({ submenu: name });

View File

@ -6,16 +6,17 @@ import { get as getStorage } from '../util/storage';
import { isTouchDevice } from '../util/helpers';
import * as uiActions from '../services/ui/actions';
import { indexToArray } from '../util/arrays';
import { decodeUri } from '../util/format';
class DebugInfo extends React.Component {
constructor(props) {
super(props);
this.state = { localForageLength: 0 };
}
}
componentDidMount() {
localForage.length().then((localForageLength) => this.setState({ localForageLength }));
}
}
localStorageSize() {
let data = '';

View File

@ -1,46 +1,47 @@
import React, { memo } from 'react';
import URILink from './URILink';
const LinksSentence = memo(({ items, className, nolinks }) => {
const LinksSentence = memo(({ items, itemType, className, nolinks }) => {
if (!items) return <span className={`${className} links-sentence`}>-</span>;
return (
<span className={`${className} links-sentence`}>
{
items.map(({ name, uri, type }, index) => {
if (!name) return <span>-</span>;
items.map(({ name, uri, type }, index) => {
if (!name) return <span>-</span>;
let separator = null;
if (index == items.length - 2) {
separator = ' and ';
} else if (index < items.length - 2) {
separator = ', ';
}
let separator = null;
if (index === items.length - 2) {
separator = ' and ';
} else if (index < items.length - 2) {
separator = ', ';
}
if (!name) {
var content = <span>-</span>;
} else if (!uri || nolinks) {
var content = <span>{name}</span>;
} else {
var content = (
<URILink
className="links-sentence__item links-sentence__item--link"
uri={uri}
type={type}
>
{name}
</URILink>
);
}
let content = null;
if (!name) {
content = <span>-</span>;
} else if (!uri || nolinks) {
content = <span>{name}</span>;
} else {
content = (
<URILink
className="links-sentence__item links-sentence__item--link"
uri={uri}
type={itemType || type}
>
{name}
</URILink>
);
}
return (
<span key={`index_${uri}`}>
{content}
{separator}
</span>
);
})
}
return (
<span key={`index_${uri}`}>
{content}
{separator}
</span>
);
})
}
</span>
);
});

View File

@ -45,9 +45,11 @@ class MediaSession extends React.Component {
if (current_track.duration) {
// Only supported on Android as of Chrome 81 and later.
if ('setPositionState' in navigator.mediaSession) {
const duration = Math.round(current_track.duration / 1000);
const position = Math.round(time_position / 1000);
const newPositionState = {
duration: Math.round(current_track.duration / 1000),
position: Math.round(time_position / 1000),
duration,
position: position > duration ? duration : position, // Can't be > than duration!
playbackRate: 1,
};
try {

View File

@ -147,6 +147,8 @@ services:
title: LastFM
love: Love
unlove: Unlove
youtube:
title: YouTube
##
# Components

View File

@ -190,10 +190,11 @@ export function loadUserPlaylists(uri, options = {}) {
};
}
export function loadLibrary(uri, options = {}) {
export function loadLibrary(uri, uriType, options = {}) {
return {
type: 'LOAD_LIBRARY',
uri,
uriType,
options,
};
}

View File

@ -552,17 +552,12 @@ const CoreMiddleware = (function () {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(
spotifyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.options.forceRefetch),
);
break;
case 'google':
store.dispatch(
googleActions[`getLibrary${titleCase(uriType(action.uri))}`](),
spotifyActions[`getLibrary${titleCase(action.uriType)}`](action.options.forceRefetch),
);
break;
default:
store.dispatch(
mopidyActions[`getLibrary${titleCase(uriType(action.uri))}`](),
mopidyActions[`getLibrary${titleCase(action.uriType)}`](action.uri),
);
break;
}

View File

@ -5,7 +5,7 @@ import {
formatArtists,
formatTracks,
} from '../../util/format';
import { i18n } from '../../locale';
import { i18n } from '../../locale';
const coreActions = require('../core/actions');
const mopidyActions = require('../mopidy/actions');
@ -82,6 +82,7 @@ const GoogleMiddleware = (function () {
store.dispatch(uiActions.processFinished(action.type));
store.dispatch(coreActions.libraryLoaded({
uri: 'google:library:albums',
type: action.uriType,
items_uris: arrayOf('uri', browseResponse),
}));
}
@ -148,6 +149,7 @@ const GoogleMiddleware = (function () {
store.dispatch(uiActions.processFinished(action.type));
store.dispatch(coreActions.libraryLoaded({
uri: 'google:library:artists',
type: action.uriType,
items_uris: arrayOf('uri', browseResponse),
}));
}

View File

@ -1753,12 +1753,13 @@ const MopidyMiddleware = (function () {
case 'MOPIDY_GET_LIBRARY_ARTISTS':
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
request(store, 'library.browse', { uri: store.getState().mopidy.library_artists_uri })
request(store, 'library.browse', { uri: action.uri })
.then((raw_response) => {
// No items in our library
if (!raw_response.length) {
store.dispatch(coreActions.libraryLoaded({
uri: 'mopidy:library:artists',
type: action.uriType,
items_uris: [],
}));
store.dispatch(uiActions.processFinished(action.type));
@ -1773,7 +1774,8 @@ const MopidyMiddleware = (function () {
store.dispatch(coreActions.itemsLoaded(response));
store.dispatch(coreActions.libraryLoaded({
uri: 'mopidy:library:artists',
uri: action.uri,
type: 'artists',
items_uris: arrayOf('uri', response),
}));
store.dispatch(uiActions.processFinished(action.type));
@ -1830,6 +1832,7 @@ const MopidyMiddleware = (function () {
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));
@ -1838,7 +1841,8 @@ const MopidyMiddleware = (function () {
});
} else {
store.dispatch(coreActions.libraryLoaded({
uri: 'mopidy:library:playlists',
uri: action.uri,
type: 'playlists',
items_uris: [],
}));
store.dispatch(uiActions.stopLoading('mopidy:library:playlists'));
@ -1851,8 +1855,9 @@ const MopidyMiddleware = (function () {
case 'MOPIDY_GET_LIBRARY_ALBUMS':
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
request(store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri })
request(store, 'library.browse', { uri: action.uri })
.then((browseResponse) => {
console.debug({ action, browseResponse })
const allUris = arrayOf('uri', browseResponse);
store.dispatch(uiActions.updateProcess(
@ -1865,12 +1870,12 @@ const MopidyMiddleware = (function () {
const run = () => {
if (allUris.length) {
const uris = allUris.splice(0, 100);
const uris = allUris.splice(0, 10);
const processor = store.getState().ui.processes[action.type];
if (processor && processor.status === 'cancelling') {
store.dispatch(uiActions.processCancelled(action.type));
store.dispatch(uiActions.stopLoading('mopidy:library:albums'));
store.dispatch(uiActions.stopLoading(action.uri));
return;
}
store.dispatch(uiActions.updateProcess(action.type, { remaining: allUris.length }));
@ -1878,12 +1883,22 @@ const MopidyMiddleware = (function () {
request(store, 'library.lookup', { uris })
.then(
(lookupResponse) => {
const libraryItems = indexToArray(lookupResponse).map((tracks) => ({
artists: tracks[0].artists ? formatArtists(tracks[0].artists) : null,
tracks: formatTracks(tracks),
last_modified: tracks[0].last_modified,
...formatAlbum(tracks[0].album),
}));
const libraryItems = [];
indexToArray(lookupResponse).forEach(
(tracks) => {
if (tracks.length <= 0) {
console.info('Ignoring item; no results in lookup');
return;
}
libraryItems.push({
artists: tracks[0].artists ? formatArtists(tracks[0].artists) : null,
tracks: formatTracks(tracks),
last_modified: tracks[0].last_modified,
...formatAlbum(tracks[0].album),
});
},
);
if (libraryItems.length) {
store.dispatch(coreActions.itemsLoaded(libraryItems));
@ -1894,7 +1909,8 @@ const MopidyMiddleware = (function () {
} else {
store.dispatch(uiActions.processFinished(action.type));
store.dispatch(coreActions.libraryLoaded({
uri: 'mopidy:library:albums',
uri: action.uri,
type: 'albums',
items_uris: arrayOf('uri', browseResponse),
}));
}
@ -1907,7 +1923,7 @@ const MopidyMiddleware = (function () {
case 'MOPIDY_GET_LIBRARY_TRACKS':
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
request(store, 'library.browse', { uri: store.getState().mopidy.library_tracks_uri })
request(store, 'library.browse', { uri: action.uri })
.then((browseResponse) => {
const allUris = arrayOf('uri', browseResponse);
@ -1949,7 +1965,8 @@ const MopidyMiddleware = (function () {
} else {
store.dispatch(uiActions.processFinished(action.type));
store.dispatch(coreActions.libraryLoaded({
uri: 'mopidy:library:tracks',
uri: action.uri,
type: 'tracks',
items_uris: arrayOf('uri', browseResponse),
}));
}

View File

@ -1654,6 +1654,7 @@ export function getLibraryPlaylists(forceRefetch) {
dispatch(coreActions.itemsLoaded(libraryItems));
dispatch(coreActions.libraryLoaded({
uri: 'spotify:library:playlists',
type: 'playlists',
items_uris: arrayOf('uri', libraryItems),
}));
}
@ -1698,6 +1699,7 @@ export function getLibraryAlbums(forceRefetch) {
dispatch(coreActions.itemsLoaded(libraryItems));
dispatch(coreActions.libraryLoaded({
uri: 'spotify:library:albums',
type: 'albums',
items_uris: arrayOf('uri', libraryItems),
}));
}
@ -1744,6 +1746,7 @@ export function getLibraryArtists(forceRefetch) {
dispatch(coreActions.itemsLoaded(libraryItems));
dispatch(coreActions.libraryLoaded({
uri: 'spotify:library:artists',
type: 'artists',
items_uris: arrayOf('uri', libraryItems),
}));
}
@ -1787,6 +1790,7 @@ export function getLibraryTracks(forceRefetch) {
dispatch(coreActions.itemsLoaded(libraryItems));
dispatch(coreActions.libraryLoaded({
uri: 'spotify:library:tracks',
type: 'tracks',
items_uris: arrayOf('uri', libraryItems),
}));
}

View File

@ -215,10 +215,14 @@ const sourceIcon = function (uri, source = null) {
case 'spotify':
case 'soundcloud':
case 'lastfm':
case 'youtube':
case 'tidal':
return source;
case 'youtube':
case 'yt':
case 'ytmusic':
return 'youtube';
default:
return 'cloud';
}

View File

@ -1,6 +1,7 @@
import { createSelector } from 'reselect';
import { indexToArray } from './arrays';
import { isLoading } from './helpers';
import { i18n } from '../locale';
const getItem = (state, uri) => state.core.items[uri];
const getItems = (state) => state.core.items;
@ -54,12 +55,11 @@ const getLibrarySource = (state, name) => state.ui[`library_${name}_source`] ||
const makeLibrarySelector = (name) => createSelector(
[getLibraries, getItems, getLibrarySource],
(libraries, items, source) => {
const uris = [];
if (source === 'all' || source === 'local') uris.push(`mopidy:library:${name}`);
if (source === 'all' || source === 'spotify') uris.push(`spotify:library:${name}`);
if (source === 'all' || source === 'google') uris.push(`google:library:${name}`);
const selectedLibraries = source === 'all'
? indexToArray(libraries).filter((l) => l.type === name)
: indexToArray(libraries, [source]);
const itemUris = indexToArray(libraries, uris).reduce(
const itemUris = selectedLibraries.reduce(
(acc, library) => [...acc, ...library.items_uris],
[],
);
@ -98,6 +98,72 @@ const makeProcessProgressSelector = (keys) => createSelector(
},
);
const providers = {
albums: [
{
scheme: 'local:',
uri: 'local:directory?type=album',
title: i18n('services.mopidy.local'),
},
{
scheme: 'gmusic:',
uri: 'gmusic:album',
title: i18n('services.google.title'),
},
{
scheme: 'spotify:',
uri: 'spotify:library:albums',
title: i18n('services.spotify.title'),
},
{
scheme: 'ytmusic:',
uri: 'ytmusic:album',
title: i18n('services.youtube.title'),
},
],
artists: [
{
scheme: 'local:',
uri: 'local:directory?type=artist&role=albumartist',
title: i18n('services.mopidy.local'),
},
{
scheme: 'gmusic:',
uri: 'gmusic:artist',
title: i18n('services.google.title'),
},
{
scheme: 'spotify:',
uri: 'spotify:library:artists',
title: i18n('services.spotify.title'),
},
{
scheme: 'ytmusic:',
uri: 'ytmusic:artist',
title: i18n('services.youtube.title'),
},
],
tracks: [
{
scheme: 'local:',
uri: 'local:directory?type=track',
title: i18n('services.mopidy.local'),
},
{
scheme: 'spotify:',
uri: 'spotify:library:tracks',
title: i18n('services.spotify.title'),
},
],
};
const getUriSchemes = (state) => state.mopidy.uri_schemes;
const makeProvidersSelector = (context) => createSelector(
[getUriSchemes],
(schemes) => {
return providers[context]?.filter((p) => schemes.indexOf(p.scheme) > -1);
},
);
export {
getItem,
getLibrary,
@ -111,4 +177,5 @@ export {
makeSearchResultsSelector,
makeProcessProgressSelector,
queueHistorySelector,
makeProvidersSelector,
};

View File

@ -196,6 +196,8 @@ class Album extends React.Component {
filter,
} = this.state;
console.debug({ uri })
if (loading) {
return <Loader body loading />;
}

View File

@ -26,6 +26,7 @@ import {
import { arrayOf, indexToArray } from '../../util/arrays';
import { i18n, I18n } from '../../locale';
import Button from '../../components/Button';
import { decodeUri } from '../../util/format';
class Discover extends React.Component {
constructor(props) {
@ -222,10 +223,7 @@ class Discover extends React.Component {
},
} = this.props;
// Rejoin if we've had to uri-encode these as strings
// We'd need to do this if our URL has been encoded so the whole URL can become
// it's own URI (eg iris:discover:spotify_artist_1234) where we can't use ":"
const seeds = seedsProp.split('_').join(':').split(',');
const seeds = seedsProp.split(',').map((uri) => decodeUri(uri));
loadUris(seeds);
this.setState(

View File

@ -10,7 +10,6 @@ import Icon from '../../components/Icon';
import * as coreActions from '../../services/core/actions';
import * as uiActions from '../../services/ui/actions';
import * as mopidyActions from '../../services/mopidy/actions';
import * as googleActions from '../../services/google/actions';
import * as spotifyActions from '../../services/spotify/actions';
import { sortItems, applyFilter } from '../../util/arrays';
import Button from '../../components/Button';
@ -20,12 +19,12 @@ import {
makeLibrarySelector,
makeProcessProgressSelector,
getLibrarySource,
makeProvidersSelector,
} from '../../util/selectors';
const processKeys = [
'MOPIDY_GET_LIBRARY_ALBUMS',
'SPOTIFY_GET_LIBRARY_ALBUMS',
'GOOGLE_GET_LIBRARY_ALBUMS',
];
class LibraryAlbums extends React.Component {
@ -45,18 +44,14 @@ class LibraryAlbums extends React.Component {
} = this.props;
setWindowTitle(i18n('library.albums.title'));
this.getMopidyLibrary();
this.getGoogleLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
componentDidUpdate = ({ source: prevSource }) => {
const { source } = this.props;
if (source !== prevSource) {
this.getMopidyLibrary();
this.getGoogleLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
}
@ -64,9 +59,7 @@ class LibraryAlbums extends React.Component {
const { uiActions: { hideContextMenu } } = this.props;
hideContextMenu();
this.getMopidyLibrary(true);
this.getGoogleLibrary(true);
this.getSpotifyLibrary(true);
this.getLibraries(true);
}
cancelRefresh = () => {
@ -76,47 +69,22 @@ class LibraryAlbums extends React.Component {
cancelProcess(processKeys);
}
getMopidyLibrary = (forceRefetch = false) => {
getLibraries = (forceRefetch = false) => {
const {
source,
providers,
coreActions: {
loadLibrary,
},
} = this.props;
if (source !== 'local' && source !== 'all') return;
loadLibrary('mopidy:library:albums', { forceRefetch });
};
getGoogleLibrary = (forceRefetch = false) => {
const {
source,
google_available,
coreActions: {
loadLibrary,
},
} = this.props;
if (!google_available) return;
if (source !== 'google' && source !== 'all') return;
loadLibrary('google:library:albums', { 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:albums', { forceRefetch });
let uris = [];
if (source === 'all') {
uris = providers.map((p) => p.uri);
} else {
uris.push(source);
}
uris.forEach((uri) => loadLibrary(uri, 'albums', { forceRefetch }));
};
handleContextMenu = (e, item) => {
@ -197,11 +165,10 @@ class LibraryAlbums extends React.Component {
render = () => {
const {
spotify_available,
google_available,
sort,
view,
source,
providers,
sort_reverse,
uiActions,
loading_progress,
@ -211,31 +178,6 @@ class LibraryAlbums extends React.Component {
per_page,
} = 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'),
});
}
if (google_available) {
source_options.push({
value: 'google',
label: i18n('services.google.title'),
});
}
const view_options = [
{
value: 'thumbnails',
@ -303,7 +245,13 @@ class LibraryAlbums extends React.Component {
name={i18n('fields.source')}
value={source}
valueAsLabel
options={source_options}
options={[
{
value: 'all',
label: i18n('fields.filters.all'),
},
...providers.map((p) => ({ value: p.uri, label: p.title })),
]}
handleChange={(val) => { uiActions.set({ library_albums_source: val }); uiActions.hideContextMenu(); }}
/>
<Button
@ -332,12 +280,12 @@ class LibraryAlbums extends React.Component {
const librarySelector = makeLibrarySelector('albums');
const processProgressSelector = makeProcessProgressSelector(processKeys);
const providersSelector = makeProvidersSelector('albums');
const mapStateToProps = (state) => ({
loading_progress: processProgressSelector(state),
mopidy_uri_schemes: state.mopidy.uri_schemes,
uri_schemes: state.mopidy.uri_schemes,
albums: librarySelector(state, 'albums'),
google_available: (state.mopidy.uri_schemes && state.mopidy.uri_schemes.includes('gmusic:')),
spotify_available: state.spotify.access_token,
providers: providersSelector(state),
view: state.ui.library_albums_view,
source: getLibrarySource(state, 'albums'),
sort: state.ui.library_albums_sort,
@ -347,7 +295,6 @@ const mapDispatchToProps = (dispatch) => ({
coreActions: bindActionCreators(coreActions, dispatch),
uiActions: bindActionCreators(uiActions, dispatch),
mopidyActions: bindActionCreators(mopidyActions, dispatch),
googleActions: bindActionCreators(googleActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch),
});

View File

@ -17,12 +17,12 @@ import {
makeLibrarySelector,
makeProcessProgressSelector,
getLibrarySource,
makeProvidersSelector,
} from '../../util/selectors';
const processKeys = [
'MOPIDY_GET_LIBRARY_ARTISTS',
'SPOTIFY_GET_LIBRARY_ARTISTS',
'GOOGLE_GET_LIBRARY_ARTISTS',
];
class LibraryArtists extends React.Component {
@ -42,18 +42,14 @@ class LibraryArtists extends React.Component {
} = this.props;
setWindowTitle(i18n('library.artists.title'));
this.getMopidyLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
componentDidUpdate = ({ source: prevSource }) => {
const { source } = this.props;
if (source !== prevSource) {
this.getMopidyLibrary();
this.getGoogleLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
}
@ -61,9 +57,7 @@ class LibraryArtists extends React.Component {
const { uiActions: { hideContextMenu } } = this.props;
hideContextMenu();
this.getMopidyLibrary(true);
this.getGoogleLibrary(true);
this.getSpotifyLibrary(true);
this.getLibraries(true);
}
cancelRefresh = () => {
@ -93,47 +87,22 @@ class LibraryArtists extends React.Component {
});
}
getMopidyLibrary = (forceRefetch = false) => {
getLibraries = (forceRefetch = false) => {
const {
source,
providers,
coreActions: {
loadLibrary,
},
} = this.props;
if (source !== 'local' && source !== 'all') return;
loadLibrary('mopidy:library:artists', { forceRefetch });
};
getGoogleLibrary = (forceRefetch = false) => {
const {
source,
google_available,
coreActions: {
loadLibrary,
},
} = this.props;
if (!google_available) return;
if (source !== 'google' && source !== 'all') return;
loadLibrary('google:library:artists', { 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:artists', { forceRefetch });
let uris = [];
if (source === 'all') {
uris = providers.map((p) => p.uri);
} else {
uris.push(source);
}
uris.forEach((uri) => loadLibrary(uri, 'artists', { forceRefetch }));
};
renderView = () => {
@ -183,36 +152,10 @@ class LibraryArtists extends React.Component {
render = () => {
const {
spotify_available,
google_available,
loading_progress,
providers,
} = this.props;
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'),
});
}
if (google_available) {
source_options.push({
value: 'google',
label: i18n('services.google.title'),
});
}
const view_options = [
{
value: 'thumbnails',
@ -272,7 +215,13 @@ class LibraryArtists extends React.Component {
name={i18n('fields.source')}
value={this.props.source}
valueAsLabel
options={source_options}
options={[
{
value: 'all',
label: i18n('fields.filters.all'),
},
...providers.map((p) => ({ value: p.uri, label: p.title })),
]}
handleChange={(value) => { this.props.uiActions.set({ library_artists_source: value }); this.props.uiActions.hideContextMenu(); }}
/>
<Button
@ -301,11 +250,11 @@ class LibraryArtists extends React.Component {
const librarySelector = makeLibrarySelector('artists');
const processProgressSelector = makeProcessProgressSelector(processKeys);
const providersSelector = makeProvidersSelector('artists');
const mapStateToProps = (state) => ({
loading_progress: processProgressSelector(state),
mopidy_uri_schemes: state.mopidy.uri_schemes,
google_available: (state.mopidy.uri_schemes && state.mopidy.uri_schemes.includes('gmusic:')),
spotify_available: (state.spotify.access_token),
uri_schemes: state.mopidy.uri_schemes,
providers: providersSelector(state),
artists: librarySelector(state, 'artists'),
source: getLibrarySource(state, 'artists'),
sort: (state.ui.library_artists_sort ? state.ui.library_artists_sort : null),

View File

@ -73,7 +73,7 @@ class LibraryPlaylists extends React.Component {
if (source !== 'local' && source !== 'all') return;
loadLibrary('mopidy:library:playlists', { forceRefetch });
loadLibrary('mopidy:library:playlists', 'playlists', { forceRefetch });
};
getSpotifyLibrary = (forceRefetch = false) => {
@ -88,7 +88,7 @@ class LibraryPlaylists extends React.Component {
if (!spotify_available) return;
if (source !== 'spotify' && source !== 'all') return;
loadLibrary('spotify:library:playlists', { forceRefetch });
loadLibrary('spotify:library:playlists', 'playlists', { forceRefetch });
};
componentDidUpdate = ({ source: prevSource }) => {

View File

@ -19,6 +19,7 @@ import {
makeLibrarySelector,
makeProcessProgressSelector,
getLibrarySource,
makeProvidersSelector,
} from '../../util/selectors';
const processKeys = [
@ -43,16 +44,14 @@ class LibraryTracks extends React.Component {
} = this.props;
setWindowTitle(i18n('library.tracks.title'));
this.getMopidyLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
componentDidUpdate = ({ source: prevSource }) => {
const { source } = this.props;
if (source !== prevSource) {
this.getMopidyLibrary();
this.getSpotifyLibrary();
this.getLibraries();
}
}
@ -60,8 +59,7 @@ class LibraryTracks extends React.Component {
const { uiActions: { hideContextMenu } } = this.props;
hideContextMenu();
this.getMopidyLibrary(true);
this.getSpotifyLibrary(true);
this.getLibraries(true);
}
cancelRefresh = () => {
@ -71,32 +69,22 @@ class LibraryTracks extends React.Component {
cancelProcess(processKeys);
}
getMopidyLibrary = (forceRefetch = false) => {
getLibraries = (forceRefetch = false) => {
const {
source,
providers,
coreActions: {
loadLibrary,
},
} = this.props;
if (source !== 'local' && source !== 'all') return;
loadLibrary('mopidy:library:tracks', { 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:tracks', { forceRefetch });
let uris = [];
if (source === 'all') {
uris = providers.map((p) => p.uri);
} else {
uris.push(source);
}
uris.forEach((uri) => loadLibrary(uri, 'tracks', { forceRefetch }));
};
handleContextMenu = (e, item) => {
@ -192,9 +180,9 @@ class LibraryTracks extends React.Component {
render = () => {
const {
spotify_available,
sort,
source,
providers,
sort_reverse,
uiActions,
loading_progress,
@ -203,24 +191,6 @@ class LibraryTracks 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 sort_options = [
{
value: null,
@ -261,8 +231,19 @@ class LibraryTracks extends React.Component {
name={i18n('fields.source')}
value={source}
valueAsLabel
options={source_options}
handleChange={(val) => { uiActions.set({ library_tracks_source: val }); uiActions.hideContextMenu(); }}
options={[
{
value: 'all',
label: i18n('fields.filters.all'),
},
...providers.map((p) => ({ value: p.uri, label: p.title })),
]}
handleChange={
(val) => {
uiActions.set({ library_tracks_source: val });
uiActions.hideContextMenu();
}
}
/>
<Button
onClick={this.playAll}
@ -299,13 +280,14 @@ class LibraryTracks extends React.Component {
const librarySelector = makeLibrarySelector('tracks');
const processProgressSelector = makeProcessProgressSelector(processKeys);
const providersSelector = makeProvidersSelector('tracks');
const mapStateToProps = (state) => ({
loading_progress: processProgressSelector(state),
mopidy_uri_schemes: state.mopidy.uri_schemes,
tracks: librarySelector(state),
spotify_available: state.spotify.access_token,
tracks: librarySelector(state, 'tracks'),
view: state.ui.library_tracks_view,
source: getLibrarySource(state, 'tracks'),
providers: providersSelector(state),
sort: state.ui.library_tracks_sort,
sort_reverse: state.ui.library_tracks_sort_reverse,
});

View File

@ -39,11 +39,11 @@ class AddToPlaylist extends React.Component {
} = this.props;
if (!spotify_library.length && spotify_available) {
loadLibrary('spotify:library:playlists');
loadLibrary('spotify:library:playlists', 'playlists');
}
if (!mopidy_library.length) {
loadLibrary('mopidy:library:playlists');
loadLibrary('mopidy:library:playlists', 'playlists');
}
setWindowTitle(i18n('modal.add_to_playlist.title'));