Routing all fetchers to type-specific call; Mopidy getUris to handle unknown ones
This commit is contained in:
@ -21,6 +21,7 @@ import Artist from './views/Artist';
|
||||
import Playlist from './views/Playlist';
|
||||
import User from './views/User';
|
||||
import Track from './views/Track';
|
||||
import UriRedirect from './views/UriRedirect';
|
||||
import Queue from './views/Queue';
|
||||
import QueueHistory from './views/QueueHistory';
|
||||
import Debug from './views/Debug';
|
||||
@ -338,6 +339,7 @@ export class App extends React.Component {
|
||||
<Route exact path="/playlist/:uri" component={Playlist} />
|
||||
<Route exact path="/user/:uri" component={User} />
|
||||
<Route exact path="/track/:uri" component={Track} />
|
||||
<Route exact path="/uri/:uri" component={UriRedirect} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
buildLink,
|
||||
isLoading,
|
||||
throttle,
|
||||
titleCase,
|
||||
} from '../util/helpers';
|
||||
import {
|
||||
arrayOf,
|
||||
@ -616,17 +617,26 @@ class ContextMenu extends React.Component {
|
||||
uiActions: {
|
||||
hideContextMenu,
|
||||
},
|
||||
coreActions: {
|
||||
loadItem,
|
||||
},
|
||||
coreActions: actions,
|
||||
menu: {
|
||||
uris,
|
||||
} = {},
|
||||
} = this.props;
|
||||
|
||||
const uri = uris[0];
|
||||
|
||||
loadItem(uri, { forceRefetch: true, full: true });
|
||||
let { nice_name } = this.getContext();
|
||||
switch (nice_name) {
|
||||
case 'artist':
|
||||
case 'album':
|
||||
case 'playlist':
|
||||
case 'track':
|
||||
case 'user':
|
||||
const uri = uris[0];
|
||||
actions[`load${titleCase(nice_name)}`](uri, { forceRefetch: true, full: true });
|
||||
break;
|
||||
default:
|
||||
actions.handleException(`Cannot refresh; unexpected URI: ${uri}`);
|
||||
break;
|
||||
}
|
||||
hideContextMenu();
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ export default memo((props) => {
|
||||
break;
|
||||
|
||||
default:
|
||||
to = null;
|
||||
to = `/uri/${uri}`;
|
||||
}
|
||||
|
||||
if (uri) {
|
||||
|
||||
@ -119,11 +119,12 @@ export function searchResultsLoaded(query, resultType, results) {
|
||||
* relevant service to load the record - all from one neat package.
|
||||
* */
|
||||
|
||||
export function loadItems(uris, options = {}) {
|
||||
export function loadItems(itemType, uris = [], options = {}) {
|
||||
return {
|
||||
type: 'LOAD_ITEMS',
|
||||
uris,
|
||||
options,
|
||||
itemType,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -343,20 +343,51 @@ const CoreMiddleware = (function () {
|
||||
/**
|
||||
* Asset Load commands
|
||||
*
|
||||
* These are called from views and other middleware to load
|
||||
* assets. This is where we can return already indexed records
|
||||
* where appropriate
|
||||
* These are called from views and other middleware to load assets. This is where we can
|
||||
* return already indexed records where appropriate.
|
||||
*
|
||||
* We prefer for all actions to be called with a URI and type, allowing us to direct the
|
||||
* request appropriately. Sometimes we don't know what kind of asset a URI is, in which case
|
||||
* we need to LOAD_URI to ascertain this first.
|
||||
* */
|
||||
case 'LOAD_ITEMS':
|
||||
action.uris.forEach((uri) => {
|
||||
case 'LOAD_ITEMS': {
|
||||
console.log(action);
|
||||
const { uris = [], options, itemType = 'uri' } = action;
|
||||
|
||||
uris.forEach((uri) => {
|
||||
store.dispatch(uiActions.startLoading(uri));
|
||||
store.dispatch({
|
||||
type: `LOAD_${uriType(uri).toUpperCase()}`,
|
||||
type: `LOAD_${itemType.toUpperCase()}`,
|
||||
uri,
|
||||
options: action.options,
|
||||
options,
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'LOAD_URI': {
|
||||
const { uri, options } = action;
|
||||
const source = uriSource(uri);
|
||||
|
||||
const fetch = () => {
|
||||
// We need to pull type from the URI for Spotify as we use specific HTTP endpoints for
|
||||
// each asset type, and their URIs facilitate this.
|
||||
if (source === 'spotify') {
|
||||
store.dispatch(
|
||||
coreActions[`load${titleCase(uriType(uri))}`](uri, options),
|
||||
);
|
||||
} else {
|
||||
mopidyActions.getUris([uri], options);
|
||||
}
|
||||
};
|
||||
|
||||
ensureLoaded({
|
||||
store,
|
||||
action,
|
||||
fetch,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'LOAD_TRACK': {
|
||||
const fetch = () => {
|
||||
@ -380,6 +411,7 @@ const CoreMiddleware = (function () {
|
||||
fetch,
|
||||
dependents: ['images'],
|
||||
fullDependents: ['lyrics_results'],
|
||||
type: 'track',
|
||||
});
|
||||
|
||||
next(action);
|
||||
@ -406,6 +438,7 @@ const CoreMiddleware = (function () {
|
||||
fetch,
|
||||
dependents: ['images'],
|
||||
fullDependents: ['tracks'],
|
||||
type: 'album',
|
||||
});
|
||||
next(action);
|
||||
break;
|
||||
@ -431,6 +464,7 @@ const CoreMiddleware = (function () {
|
||||
fetch,
|
||||
dependents: ['images'],
|
||||
fullDependents: ['tracks', 'albums_uris'],
|
||||
type: 'artist',
|
||||
});
|
||||
next(action);
|
||||
break;
|
||||
@ -458,6 +492,7 @@ const CoreMiddleware = (function () {
|
||||
fetch,
|
||||
dependents: ['images'],
|
||||
fullDependents: ['tracks'],
|
||||
type: 'playlist',
|
||||
});
|
||||
next(action);
|
||||
break;
|
||||
@ -482,6 +517,7 @@ const CoreMiddleware = (function () {
|
||||
action,
|
||||
fetch,
|
||||
fullDependents: ['playlists_uris'],
|
||||
type: 'user',
|
||||
});
|
||||
next(action);
|
||||
break;
|
||||
@ -503,6 +539,7 @@ const CoreMiddleware = (function () {
|
||||
action,
|
||||
fetch,
|
||||
fullDependents: ['playlists_uris'],
|
||||
type: 'category',
|
||||
});
|
||||
|
||||
next(action);
|
||||
|
||||
@ -406,11 +406,17 @@ export function shuffleTracklist() {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asset-oriented actions
|
||||
* */
|
||||
|
||||
export function getUris(uris) {
|
||||
return {
|
||||
type: 'MOPIDY_GET_URIS',
|
||||
uris,
|
||||
};
|
||||
}
|
||||
|
||||
export function getImages(uris) {
|
||||
return {
|
||||
type: 'MOPIDY_GET_IMAGES',
|
||||
|
||||
@ -823,7 +823,7 @@ const MopidyMiddleware = (function () {
|
||||
break;
|
||||
}
|
||||
store.dispatch(
|
||||
coreActions.loadItem(
|
||||
coreActions.loadPlaylist(
|
||||
action.uri,
|
||||
{ full: true, callbackAction: { name: 'play', shuffle: action.shuffle } },
|
||||
),
|
||||
@ -844,7 +844,7 @@ const MopidyMiddleware = (function () {
|
||||
break;
|
||||
}
|
||||
store.dispatch(
|
||||
coreActions.loadItem(
|
||||
coreActions.loadPlaylist(
|
||||
action.uri,
|
||||
false,
|
||||
{
|
||||
@ -1167,6 +1167,20 @@ const MopidyMiddleware = (function () {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MOPIDY_GET_URIS': {
|
||||
console.log(action);
|
||||
const { uris } = action;
|
||||
request(store, 'library.lookup', { uris })
|
||||
.then((response) => {
|
||||
if (!response) return;
|
||||
|
||||
indexToArray(response).forEach((item) => {
|
||||
console.log(item);
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MOPIDY_GET_PLAYLIST':
|
||||
request(store, 'playlists.lookup', { uri: action.uri })
|
||||
.then((response) => {
|
||||
@ -1483,7 +1497,7 @@ const MopidyMiddleware = (function () {
|
||||
track,
|
||||
uri: track.uri,
|
||||
});
|
||||
store.dispatch(coreActions.loadItem(track.uri, { full: true }));
|
||||
store.dispatch(coreActions.loadTrack(track.uri, { full: true }));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1503,7 +1517,7 @@ const MopidyMiddleware = (function () {
|
||||
uri: track.uri,
|
||||
});
|
||||
|
||||
store.dispatch(coreActions.loadItem(track.uri));
|
||||
store.dispatch(coreActions.loadTrack(track.uri));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -307,6 +307,7 @@ const decodeUri = (rawUri) => {
|
||||
// Escape unreserved characters (RFC 3986)
|
||||
// https://stackoverflow.com/questions/18251399/why-doesnt-encodeuricomponent-encode-single-quotes-apostrophes
|
||||
let id = getFromUri(`${type}id`, uri);
|
||||
const rest = uri.substring(uri.indexOf(id) + id.length);
|
||||
id = encodeURIComponent(id).replace(/[!'()*]/g, escape);
|
||||
|
||||
// Reinstate slashes for the Mopidy-Local structure
|
||||
@ -323,7 +324,7 @@ const decodeUri = (rawUri) => {
|
||||
default:
|
||||
decoded += `${type}:`;
|
||||
}
|
||||
decoded += `${id}`;
|
||||
decoded += `${id}${rest}`;
|
||||
|
||||
return decoded;
|
||||
};
|
||||
|
||||
@ -217,8 +217,15 @@ const sourceIcon = function (uri, source = null) {
|
||||
case 'dirble':
|
||||
return 'cloud';
|
||||
|
||||
default:
|
||||
case 'spotify':
|
||||
case 'soundcloud':
|
||||
case 'lastfm':
|
||||
case 'youtube':
|
||||
case 'tidal':
|
||||
return source;
|
||||
|
||||
default:
|
||||
return 'cloud';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -70,6 +70,7 @@ const ensureLoaded = ({
|
||||
fetch,
|
||||
dependents = [],
|
||||
fullDependents = [],
|
||||
type,
|
||||
}) => {
|
||||
const {
|
||||
uri,
|
||||
@ -115,7 +116,7 @@ const ensureLoaded = ({
|
||||
const uris = dependentUris(item);
|
||||
if (uris.length) {
|
||||
console.log(`Loading ${uris.length} dependents`);
|
||||
store.dispatch(coreActions.loadItems(uris));
|
||||
store.dispatch(coreActions.loadItems(type, uris));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ class Album extends React.Component {
|
||||
uri,
|
||||
album,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadAlbum,
|
||||
},
|
||||
lastfmActions: {
|
||||
getAlbum,
|
||||
@ -51,7 +51,7 @@ class Album extends React.Component {
|
||||
} = this.props;
|
||||
|
||||
this.setWindowTitle();
|
||||
loadItem(uri, { full: true });
|
||||
loadAlbum(uri, { full: true });
|
||||
|
||||
if (album) {
|
||||
if (album.artists && album.wiki === undefined) {
|
||||
@ -81,7 +81,7 @@ class Album extends React.Component {
|
||||
uri,
|
||||
album,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadAlbum,
|
||||
},
|
||||
lastfmActions: {
|
||||
getAlbum,
|
||||
@ -89,7 +89,7 @@ class Album extends React.Component {
|
||||
} = this.props;
|
||||
|
||||
if (uri !== prevUri) {
|
||||
loadItem(uri, { full: true });
|
||||
loadAlbum(uri, { full: true });
|
||||
}
|
||||
|
||||
// We have just received our full album or our album artists
|
||||
|
||||
@ -50,12 +50,12 @@ class Artist extends React.Component {
|
||||
const {
|
||||
uri,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadArtist,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
this.setWindowTitle();
|
||||
loadItem(uri, { full: true });
|
||||
loadArtist(uri, { full: true });
|
||||
}
|
||||
|
||||
componentDidUpdate = ({
|
||||
@ -66,12 +66,12 @@ class Artist extends React.Component {
|
||||
uri,
|
||||
artist,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadArtist,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (uri !== prevUri) {
|
||||
loadItem(uri, { full: true });
|
||||
loadArtist(uri, { full: true });
|
||||
}
|
||||
|
||||
if (!prevArtist && artist) this.setWindowTitle(artist);
|
||||
|
||||
@ -50,9 +50,14 @@ class Playlist extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { coreActions: { loadItem }, uri } = this.props;
|
||||
const {
|
||||
coreActions: {
|
||||
loadPlaylist,
|
||||
},
|
||||
uri,
|
||||
} = this.props;
|
||||
this.setWindowTitle();
|
||||
setTimeout(() => loadItem(uri, { full: true }), 1);
|
||||
setTimeout(() => loadPlaylist(uri, { full: true }), 1);
|
||||
}
|
||||
|
||||
componentDidUpdate = ({
|
||||
@ -63,7 +68,7 @@ class Playlist extends React.Component {
|
||||
uri,
|
||||
playlist,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadPlaylist,
|
||||
},
|
||||
history: {
|
||||
push,
|
||||
@ -75,7 +80,7 @@ class Playlist extends React.Component {
|
||||
}
|
||||
|
||||
if (uri !== prevUri) {
|
||||
loadItem(uri, { full: true });
|
||||
loadPlaylist(uri, { full: true });
|
||||
}
|
||||
|
||||
if (!prevPlaylist && playlist) this.setWindowTitle(playlist);
|
||||
|
||||
@ -33,11 +33,11 @@ class Track extends React.Component {
|
||||
uri,
|
||||
track,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadTrack,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
loadItem(decodeUri(uri));
|
||||
loadTrack(decodeUri(uri));
|
||||
|
||||
if (track) {
|
||||
this.setWindowTitle(track);
|
||||
@ -54,7 +54,7 @@ class Track extends React.Component {
|
||||
genius_authorized,
|
||||
lastfm_authorized,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadTrack,
|
||||
},
|
||||
geniusActions: {
|
||||
findTrackLyrics,
|
||||
@ -65,7 +65,7 @@ class Track extends React.Component {
|
||||
} = this.props;
|
||||
|
||||
if (prevUri !== uri) {
|
||||
loadItem(decodeUri(uri));
|
||||
loadTrack(decodeUri(uri));
|
||||
}
|
||||
|
||||
// We have just received our full track or our track artists
|
||||
|
||||
93
src/js/views/UriRedirect.js
Normal file
93
src/js/views/UriRedirect.js
Normal file
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Loader from '../components/Loader';
|
||||
import * as coreActions from '../services/core/actions';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import { makeLoadingSelector, makeItemSelector } from '../util/selectors';
|
||||
import { decodeUri } from '../util/format';
|
||||
import { uriType } from '../util/helpers';
|
||||
|
||||
class UriRedirect extends React.Component {
|
||||
componentDidMount() {
|
||||
const {
|
||||
uri,
|
||||
item,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (item) {
|
||||
this.redirect();
|
||||
} else {
|
||||
loadItem(uri);
|
||||
}
|
||||
|
||||
this.setWindowTitle();
|
||||
}
|
||||
|
||||
componentDidUpdate = ({
|
||||
uri: prevUri,
|
||||
}) => {
|
||||
const {
|
||||
uri,
|
||||
item,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (prevUri !== uri) {
|
||||
loadItem(uri);
|
||||
this.setWindowTitle();
|
||||
}
|
||||
|
||||
if (item) {
|
||||
this.redirect();
|
||||
}
|
||||
}
|
||||
|
||||
setWindowTitle = () => {
|
||||
const {
|
||||
uri,
|
||||
uiActions: {
|
||||
setWindowTitle,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
setWindowTitle(uri);
|
||||
}
|
||||
|
||||
redirect = () => {
|
||||
const {
|
||||
uri,
|
||||
item,
|
||||
history,
|
||||
} = this.props;
|
||||
|
||||
history.replace(`/${item.type || uriType(uri)}/${uri}`);
|
||||
}
|
||||
|
||||
render = () => <Loader body loading />;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { match: { params: { uri: rawUri } } } = ownProps;
|
||||
const uri = decodeUri(rawUri);
|
||||
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`]);
|
||||
const itemSelector = makeItemSelector(uri);
|
||||
|
||||
return {
|
||||
uri,
|
||||
loading: loadingSelector(state),
|
||||
item: itemSelector(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UriRedirect);
|
||||
@ -52,12 +52,12 @@ class DiscoverCategory extends React.Component {
|
||||
uri,
|
||||
category,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadCategory,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (!category) {
|
||||
loadItem(uri);
|
||||
loadCategory(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,12 +68,12 @@ class DiscoverCategory extends React.Component {
|
||||
hideContextMenu,
|
||||
},
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadCategory,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
loadItem(uri, { forceRefetch: true });
|
||||
loadCategory(uri, { forceRefetch: true });
|
||||
}
|
||||
|
||||
render = () => {
|
||||
|
||||
@ -30,13 +30,13 @@ class EditPlaylist extends React.Component {
|
||||
uri,
|
||||
playlist,
|
||||
coreActions: {
|
||||
loadItem,
|
||||
loadPlaylist,
|
||||
},
|
||||
uiActions: {
|
||||
setWindowTitle,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
|
||||
setWindowTitle(i18n('modal.edit_playlist.title'));
|
||||
|
||||
if (playlist) {
|
||||
@ -48,7 +48,7 @@ class EditPlaylist extends React.Component {
|
||||
collaborative: (playlist.collaborative === true),
|
||||
});
|
||||
} else {
|
||||
loadItem(uri);
|
||||
loadPlaylist(uri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user