Merge branch 'feat/libraries-support' into develop
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,7 @@ npm-debug.log
|
||||
venv
|
||||
docker/mopidy.conf
|
||||
docker/data/
|
||||
docker/jellyfin/
|
||||
docker/icecast2.xml
|
||||
docker-compose.yml
|
||||
/.tox/
|
||||
|
||||
@ -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
26
mopidy_iris/static/app.min.js
vendored
26
mopidy_iris/static/app.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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 = '';
|
||||
|
||||
@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -147,6 +147,8 @@ services:
|
||||
title: LastFM
|
||||
love: Love
|
||||
unlove: Unlove
|
||||
youtube:
|
||||
title: YouTube
|
||||
|
||||
##
|
||||
# Components
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}));
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}));
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}));
|
||||
}
|
||||
|
||||
@ -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';
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -196,6 +196,8 @@ class Album extends React.Component {
|
||||
filter,
|
||||
} = this.state;
|
||||
|
||||
console.debug({ uri })
|
||||
|
||||
if (loading) {
|
||||
return <Loader body loading />;
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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),
|
||||
});
|
||||
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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'));
|
||||
|
||||
Reference in New Issue
Block a user