Moving tracks into items (need to consider performance knock-ons)

This commit is contained in:
James Barnsley
2020-09-06 21:13:53 +12:00
parent 1bc7f2b372
commit 60326d9de6
13 changed files with 739 additions and 876 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -106,7 +106,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1599289916";
var build = "1599362472";
var version = "3.52.4";
// Construct the script tag

View File

@ -333,10 +333,17 @@ class PlaybackControls extends React.Component {
}
}
const mapStateToProps = (state) => ({
const mapStateToProps = (state) => {
const {
items,
next_track_uri,
current_track,
} = state.core;
return {
snapcast_enabled: state.pusher.config.snapcast_enabled,
current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null),
next_track: (state.core.next_track_uri && state.core.tracks[state.core.next_track_uri] !== undefined ? state.core.tracks[state.core.next_track_uri] : null),
current_track: current_track ? items[current_track.uri] || current_track : null,
next_track: items[next_track_uri],
radio_enabled: (!!(state.ui.radio && state.ui.radio.enabled)),
play_state: state.mopidy.play_state,
time_position: state.mopidy.time_position,
@ -348,7 +355,8 @@ const mapStateToProps = (state) => ({
sidebar_open: state.ui.sidebar_open,
slim_mode: state.ui.slim_mode,
touch_enabled: state.ui.playback_controls_touch_enabled,
});
};
};
const mapDispatchToProps = (dispatch) => ({
coreActions: bindActionCreators(coreActions, dispatch),

View File

@ -107,71 +107,74 @@ export function updateColdStore(items) {
* relevant service to load the record - all from one neat package.
* */
export function loadItems(uris, force_reload = false) {
export function loadItems(uris, forceReload = false) {
return {
type: 'LOAD_ITEMS',
uris,
force_reload,
forceReload,
};
}
export function loadItem(uri, force_reload = false) {
return loadItems([uri], force_reload);
export function loadItem(uri, forceReload = false) {
return loadItems([uri], forceReload);
}
export function loadTrack(uri, force_reload = false) {
export function loadTrack(uri, forceReload = false) {
return {
type: 'LOAD_TRACK',
uri,
force_reload,
forceReload,
};
}
export function loadAlbum(uri, force_reload = false) {
export function loadAlbum(uri, forceReload = false, callbackAction) {
return {
type: 'LOAD_ALBUM',
uri,
force_reload,
forceReload,
callbackAction,
};
}
export function loadArtist(uri, force_reload = false) {
export function loadArtist(uri, forceReload = false, callbackAction) {
return {
type: 'LOAD_ARTIST',
uri,
force_reload,
forceReload,
callbackAction,
};
}
export function loadPlaylist(uri, force_reload = false) {
export function loadPlaylist(uri, forceReload = false, callbackAction) {
return {
type: 'LOAD_PLAYLIST',
uri,
force_reload,
forceReload,
callbackAction,
};
}
export function loadUser(uri, force_reload = false) {
export function loadUser(uri, forceReload = false) {
return {
type: 'LOAD_USER',
uri,
force_reload,
forceReload,
};
}
export function loadUserPlaylists(uri, force_reload = false) {
export function loadUserPlaylists(uri, forceReload = false) {
return {
type: 'LOAD_USER_PLAYLISTS',
uri,
force_reload,
forceReload,
};
}
export function loadLibrary(uri, force_reload = false) {
export function loadLibrary(uri, forceReload = false) {
return {
type: 'LOAD_LIBRARY',
uri,
force_reload,
forceReload,
};
}

View File

@ -318,19 +318,13 @@ const CoreMiddleware = (function () {
store.dispatch({
type: `LOAD_${uriType(uri).toUpperCase()}`,
uri,
force_reload: action.force_reload,
forceReload: action.forceReload,
});
});
break;
case 'LOAD_TRACK':
if (
!action.force_reload
&& store.getState().core.tracks[action.uri]) {
console.info(`Loading "${action.uri}" from index`);
break;
}
case 'LOAD_TRACK': {
const fetchTrack = () => {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(spotifyActions.getTrack(action.uri));
@ -344,9 +338,30 @@ const CoreMiddleware = (function () {
store.dispatch(mopidyActions.getTrack(action.uri));
break;
}
};
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchTrack();
break;
}
if (store.getState().core.items[action.uri]) {
console.info(`Using "${action.uri}" from index`);
break;
}
localForage.getItem(action.uri).then((result) => {
if (result) {
console.info(`Loading "${action.uri}" from database`);
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
} else {
fetchTrack();
}
});
next(action);
break;
}
case 'LOAD_ALBUM':
const fetchAlbum = () => {
@ -365,7 +380,7 @@ const CoreMiddleware = (function () {
};
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchAlbum();
break;
@ -412,7 +427,7 @@ const CoreMiddleware = (function () {
}
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchArtist();
break;
@ -460,7 +475,7 @@ const CoreMiddleware = (function () {
}
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchPlaylist();
break;
@ -477,7 +492,6 @@ const CoreMiddleware = (function () {
if (result) {
console.info(`Restoring "${action.uri}" from database`);
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
if (!result.tracks) {
fetchPlaylist();
}
@ -491,7 +505,7 @@ const CoreMiddleware = (function () {
case 'LOAD_USER':
if (
!action.force_reload
!action.forceReload
&& store.getState().core.users[action.uri]
&& store.getState().core.users[action.uri].playlists_uris) {
console.info(`Loading "${action.uri}" from index`);
@ -517,7 +531,7 @@ const CoreMiddleware = (function () {
case 'LOAD_USER_PLAYLISTS':
if (
!action.force_reload
!action.forceReload
&& store.getState().core.users[action.uri]
&& store.getState().core.users[action.uri].playlists_uris) {
console.info(`Loading "${action.uri}" playlists from index`);
@ -558,7 +572,7 @@ const CoreMiddleware = (function () {
}
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchLibrary();
break;
@ -592,11 +606,15 @@ const CoreMiddleware = (function () {
* These modify our asset indexes, which are used globally
* */
case 'CURRENT_TRACK_LOADED':
store.dispatch(coreActions.trackLoaded(action.track));
action.track = formatTrack(action.track);
next(action);
case 'CURRENT_TRACK_LOADED': {
const track = formatTrack(action.track);
store.dispatch(coreActions.itemLoaded(track));
next({
...action,
track,
});
break;
}
case 'QUEUE_LOADED':
store.dispatch(coreActions.tracksLoaded(action.tracks));

View File

@ -651,109 +651,65 @@ const MopidyMiddleware = (function () {
* Advanced playback events
* */
case 'MOPIDY_PLAY_PLAYLIST':
var playlist = store.getState().core.items[action.uri];
// We have the playlist loaded already, and we've got at least 1 track to start playing
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0) {
// We've got all of the tracks, so just play those; no further action required
if (playlist.tracks_total == playlist.tracks_uris.length) {
store.dispatch(mopidyActions.playURIs(playlist.tracks_uris, action.uri, action.shuffle));
break;
}
// Spotify-provided playlists need to be handled by the Spotify service
if (playlist.provider == 'spotify') {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play'));
break;
}
// It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it
} else if (uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled) {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play'));
break;
}
// Not in index, and Spotify HTTP not enabled, so just play it as-is
// Fetch the playlist tracks via backend and add each track by URI
request(store, 'playlists.lookup', { uri: action.uri })
.then(
(response) => {
if (!response || response.tracks === undefined || !response.tracks) {
store.dispatch(uiActions.createNotification({ content: 'Failed to load playlist tracks', level: 'error' }));
} else {
let tracks_uris = arrayOf('uri', response.tracks);
if (action.shuffle) {
tracks_uris = shuffle(tracks_uris);
}
store.dispatch(mopidyActions.playURIs(tracks_uris, action.uri));
}
},
(error) => {
store.dispatch(coreActions.handleException(
`Mopidy: ${error.message ? error.message : 'Lookup failed'}`,
error,
));
},
case 'MOPIDY_PLAY_PLAYLIST': {
const playlist = store.getState().core.items[action.uri];
if (playlist && playlist.tracks) {
store.dispatch(
mopidyActions.playURIs(
arrayOf('uri', playlist.tracks),
action.uri,
action.shuffle,
),
);
break;
case 'MOPIDY_ENQUEUE_PLAYLIST':
var playlist = store.getState().core.items[action.uri];
// We have the playlist loaded already, and we've got at least 1 track to start playing
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0) {
// We've got all of the tracks, so just play those; no further action required
if (playlist.tracks_total == playlist.tracks_uris.length) {
let tracks_uris = Object.assign([], playlist.tracks_uris);
if (action.shuffle) {
tracks_uris = shuffle(tracks_uris);
}
store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset));
break;
}
// Spotify-provided playlists need to be handled by the Spotify service
if (playlist.provider == 'spotify') {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next));
break;
}
// It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it
} else if (uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled) {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next));
break;
}
// Not in index, and Spotify HTTP not enabled, so just play it as-is
// Fetch the playlist tracks via backend and add each track by URI
request(store, 'playlists.lookup', { uri: action.uri })
.then(
(response) => {
if (response.tracks === undefined) {
store.dispatch(uiActions.createNotification({ content: 'Failed to load playlist tracks', level: 'error' }));
} else {
let tracks_uris = arrayOf('uri', response.tracks);
if (action.shuffle) {
tracks_uris = shuffle(tracks_uris);
}
store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset));
}
store.dispatch(
coreActions.loadPlaylist(
action.uri,
false,
{
name: 'play',
shuffle: action.shuffle,
},
(error) => {
store.dispatch(coreActions.handleException(
`Mopidy: ${error.message ? error.message : 'Lookup failed'}`,
error,
));
},
),
);
break;
}
case 'MOPIDY_ENQUEUE_URIS':
case 'MOPIDY_ENQUEUE_PLAYLIST': {
const playlist = store.getState().core.items[action.uri];
if (playlist && playlist.tracks) {
store.dispatch(
mopidyActions.enqueueURIs(
arrayOf('uri', playlist.tracks),
action.uri,
action.shuffle,
),
);
break;
}
store.dispatch(
coreActions.loadPlaylist(
action.uri,
false,
{
name: 'enqueue',
shuffle: action.shuffle,
play_next: action.play_next,
at_position: action.at_position,
offset: action.offset,
},
),
);
break;
}
case 'MOPIDY_ENQUEUE_URIS': {
if (!action.uris || action.uris.length <= 0) {
this.props.uiActions.createNotification({ content: 'No URIs to enqueue', level: 'warning' });
this.props.uiActions.createNotification({
content: 'No URIs to enqueue',
level: 'warning',
});
break;
}
@ -762,15 +718,18 @@ const MopidyMiddleware = (function () {
{
notification: {
content: `${store.getState().pusher.username} is adding ${action.uris.length} URIs to queue`,
icon: (store.getState().core.current_track ? getTrackIcon(store.getState().core.current_track, store.getState().core) : false),
icon: (
store.getState().core.current_track
? getTrackIcon(store.getState().core.current_track, store.getState().core)
: false
),
},
},
));
// split into batches
var uris = Object.assign([], action.uris);
var batches = [];
var batch_size = 5;
const uris = Object.assign([], action.uris);
const batches = [];
const batch_size = 5;
while (uris.length > 0) {
batches.push({
uris: uris.splice(0, batch_size),
@ -781,11 +740,11 @@ const MopidyMiddleware = (function () {
});
}
// pass this modified action to the reducer (and other middleware)
action.batches = batches;
next(action);
next({
...action,
batches,
});
// start our processor
store.dispatch(uiActions.startProcess(
'MOPIDY_ENQUEUE_URIS_PROCESSOR',
i18n('services.mopidy.adding_uris', { count: action.uris.length }),
@ -796,17 +755,14 @@ const MopidyMiddleware = (function () {
},
));
break;
}
case 'MOPIDY_ENQUEUE_URIS_PROCESSOR':
case 'MOPIDY_ENQUEUE_URIS_PROCESSOR': {
const last_run = store.getState().ui.processes.MOPIDY_ENQUEUE_URIS_PROCESSOR;
var last_run = store.getState().ui.processes.MOPIDY_ENQUEUE_URIS_PROCESSOR;
// Cancelling
if (last_run && last_run.status == 'cancelling') {
store.dispatch(uiActions.processCancelled('MOPIDY_ENQUEUE_URIS_PROCESSOR'));
return;
// make sure we have some uris in the queue
} if (action.data.batches && action.data.batches.length > 0) {
var batches = Object.assign([], action.data.batches);
var batch = batches[0];
@ -829,9 +785,11 @@ const MopidyMiddleware = (function () {
break;
}
var { current_track } = store.getState().core;
var { queue } = store.getState().core;
var current_track_index = -1;
const {
current_track,
queue,
} = store.getState().core;
let current_track_index = -1;
if (current_track) {
for (var i = 0; i < queue.length; i++) {
@ -888,8 +846,8 @@ const MopidyMiddleware = (function () {
));
},
);
break;
}
case 'MOPIDY_PLAY_URIS':
const { from_uri } = action;
@ -2039,7 +1997,7 @@ const MopidyMiddleware = (function () {
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks', [track.uri]));
store.dispatch(mopidyActions.getImages([track.uri]));
}
}
}
@ -2060,17 +2018,7 @@ const MopidyMiddleware = (function () {
uri: track.uri,
});
// We don't have the track (including images) already in our index
if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined) {
// We've got Spotify running, and it's a spotify track - go straight to the source!
if (store.getState().spotify.enabled && uriSource(track.uri) == 'spotify') {
store.dispatch(spotifyActions.getTrack(track.uri));
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks', [track.uri]));
}
}
store.dispatch(coreActions.loadItem(track.uri));
}
}
},
@ -2088,7 +2036,7 @@ const MopidyMiddleware = (function () {
store.dispatch(coreActions.tracksLoaded(tracks));
if (action.get_images) {
store.dispatch(mopidyActions.getImages('tracks', arrayOf('uri', tracks)));
store.dispatch(mopidyActions.getImages(arrayOf('uri', tracks)));
}
},
(error) => {

View File

@ -946,7 +946,7 @@ export function getRecommendations(uris = [], limit = 20, tunabilities = null) {
request(dispatch, getState, endpoint)
.then(
(response) => {
const tracks = Object.assign([], response.tracks);
const tracks = Object.assign([], formatTracks(response.tracks));
// We only get simple artist objects, so we need to
// get the full object. We'll add URIs to our recommendations
@ -983,11 +983,11 @@ export function getRecommendations(uris = [], limit = 20, tunabilities = null) {
}
if (albums.length > 0) {
dispatch(coreActions.albumsLoaded(albums));
dispatch(coreActions.itemsLoaded(albums));
}
if (tracks.length > 0) {
dispatch(coreActions.tracksLoaded(tracks));
dispatch(coreActions.itemsLoaded(tracks));
}
dispatch({
@ -1104,7 +1104,6 @@ export function getArtistImages(artist) {
return (dispatch, getState) => {
request(dispatch, getState, `search?q=${artist.name}&type=artist`)
.then(response => {
console.log('images', response);
if (response.artists.items.length > 0) {
dispatch(coreActions.itemLoaded({
uri: artist.uri,
@ -1117,17 +1116,22 @@ export function getArtistImages(artist) {
export function playArtistTopTracks(uri) {
return (dispatch, getState) => {
const { artists } = getState().core;
const {
items: {
[uri]: artist,
},
} = getState().core;
// Do we have this artist (and their tracks) in our index already?
if (typeof (artists[uri]) !== 'undefined' && typeof (artists[uri].tracks) !== 'undefined') {
const uris = arrayOf('uri', artists[uri].tracks);
if (artist && artist.tracks) {
const uris = arrayOf('uri', artist.tracks);
dispatch(mopidyActions.playURIs(uris, uri));
// We need to load the artist's top tracks first
} else {
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`)
.then(
request(
dispatch,
getState,
`artists/${getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`,
).then(
(response) => {
const uris = arrayOf('uri', response.tracks);
dispatch(mopidyActions.playURIs(uris, uri));
@ -1359,9 +1363,8 @@ export function savePlaylist(uri, name, description, is_public, is_collaborative
};
}
export function getPlaylist(uri) {
export function getPlaylist(uri, callbackAction = null) {
return (dispatch, getState) => {
// get the main playlist object
request(dispatch, getState, `playlists/${getFromUri('playlistid', uri)}?market=${getState().spotify.country}`)
.then(
(response) => {
@ -1393,6 +1396,29 @@ export function getPlaylist(uri) {
uri,
tracks,
}));
if (callbackAction) {
switch (callbackAction.name) {
case 'enqueue':
dispatch(mopidyActions.enqueueURIs(
arrayOf('uri', tracks),
uri,
callbackAction.play_next,
callbackAction.at_position,
callbackAction.offset,
));
break;
case 'play':
dispatch(mopidyActions.playURIs(
arrayOf('uri', tracks),
uri,
callbackAction.shuffle,
));
break;
default:
break;
}
}
}
});
@ -1498,8 +1524,26 @@ export function getLibraryTracksAndPlayProcessor(data) {
* Recursively get .next until we have all tracks
* */
export function getAllPlaylistTracks(uri, shuffle = false, callback_action = null, play_next = false, at_position = null, offset = 0) {
export function getAllPlaylistTracks(
uri,
shuffle = false,
callback_action = null,
play_next = false,
at_position = null,
offset = 0,
) {
return (dispatch, getState) => {
if (data.callback_action == 'enqueue') {
dispatch(mopidyActions.enqueueURIs(uris, data.uri, data.play_next, data.at_position, data.offset));
} else {
dispatch(mopidyActions.playURIs(uris, data.uri));
}
dispatch(uiActions.startProcess(
'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR',
'Loading playlist tracks',
@ -1690,7 +1734,6 @@ export function getLibraryPlaylists() {
let libraryPlaylists = [];
const fetchLibraryPlaylists = (endpoint) => request(dispatch, getState, endpoint)
.then((response) => {
console.log('spot', response);
libraryPlaylists = [...libraryPlaylists, ...formatPlaylists(response.items)];
if (response.next) {
fetchLibraryPlaylists(response.next);

View File

@ -172,6 +172,7 @@ const uiPersistConfig = {
blacklist: [
'load_queue',
'notifications',
'processes',
'context_menu',
'current_track_transition',
'dragger',

View File

@ -12,9 +12,9 @@ const indexToArray = (index, keys) => {
if (!index) return [];
if (keys) {
return keys.map((key) => index[key]);
return compact(keys.map((key) => index[key]));
}
return Object.keys(index).map((key) => index[key]);
return compact(Object.keys(index).map((key) => index[key]));
};
/**

View File

@ -31,7 +31,7 @@ import {
titleCase,
} from '../util/helpers';
import { collate } from '../util/format';
import { sortItems, applyFilter } from '../util/arrays';
import { sortItems, applyFilter, arrayOf } from '../util/arrays';
import { i18n, I18n } from '../locale';
import Button from '../components/Button';
import { trackEvent } from '../components/Trackable';
@ -102,12 +102,14 @@ class Artist extends React.Component {
const {
artist: {
uri,
tracks_uris,
tracks,
albums_uris,
},
mopidyActions: { playURIs },
mopidyActions: {
playURIs,
},
} = this.props;
playURIs(tracks_uris || albums_uris, uri);
playURIs(arrayOf('uri', tracks) || albums_uris, uri);
}
setWindowTitle = (artist = this.props.artist) => {
@ -532,8 +534,8 @@ const mapStateToProps = (state, ownProps) => {
load_queue: state.ui.load_queue,
artist: (state.core.items[uri] || null),
items: state.core.items,
spotify_library_artists: state.core.items['spotify:library:artists'] || { items_uris: [] },
mopidy_library_artists: state.core.items['mopidy:library:artists'] || { items_uris: [] },
spotify_library_artists: state.core.libraries['spotify:library:artists'] || { items_uris: [] },
mopidy_library_artists: state.core.libraries['mopidy:library:artists'] || { items_uris: [] },
albums: (state.core.albums ? state.core.albums : []),
filter: (state.ui.artist_albums_filter ? state.ui.artist_albums_filter : null),
sort: (state.ui.artist_albums_sort ? state.ui.artist_albums_sort : null),

View File

@ -22,6 +22,7 @@ import {
} from '../util/helpers';
import { i18n, I18n } from '../locale';
import Button from '../components/Button';
import { indexToArray } from '../util/arrays';
const Artwork = ({
image,
@ -79,28 +80,13 @@ class Queue extends React.Component {
}) => {
const {
coreActions: {
loadAlbum,
loadArtist,
loadPlaylist,
loadItem,
},
added_from_uri,
} = this.props;
if (added_from_uri && added_from_uri !== prev_added_from_uri) {
const item_type = uriType(added_from_uri);
switch (item_type) {
case 'album':
loadAlbum(added_from_uri);
break;
case 'artist':
loadArtist(added_from_uri);
break;
case 'playlist':
loadPlaylist(added_from_uri);
break;
default:
break;
}
loadItem(added_from_uri);
}
}
@ -120,7 +106,12 @@ class Queue extends React.Component {
}
removeTracks = (track_indexes) => {
const { queue_tracks, mopidyActions } = this.props;
const {
queue_tracks,
mopidyActions: {
removeTracks: doRemoveTracks,
},
} = this.props;
const tlids = [];
for (let i = 0; i < track_indexes.length; i++) {
const track = queue_tracks[track_indexes[i]];
@ -130,7 +121,7 @@ class Queue extends React.Component {
}
if (tlids.length > 0) {
mopidyActions.removeTracks(tlids);
doRemoveTracks(tlids);
}
}
@ -150,61 +141,51 @@ class Queue extends React.Component {
}
renderAddedFrom = () => {
const { added_from_uri } = this.props;
const {
items,
added_from_uri,
} = this.props;
if (!added_from_uri) return null;
const uri_type = uriType(added_from_uri);
const items = [];
let addedFromItems = [];
// Radio nests it's seed URIs in an encoded URI format
switch (uri_type){
case 'radio':
const radio_seeds = getFromUri('seeds', added_from_uri);
for (let seed of radio_seeds) {
let item_type = uriType(seed);
let item_library = this.props[`${item_type}s`];
if (item_library && item_library[seed]) {
items.push(item_library[seed]);
}
}
addedFromItems = indexToArray(items, getFromUri('seeds', added_from_uri));
break;
case 'search':
items.push({
addedFromItems = [{
uri: added_from_uri,
name: `"${getFromUri('searchterm', added_from_uri)}" search`,
})
}];
break;
default:
const item_library = this.props[`${uri_type}s`];
if (item_library && item_library[added_from_uri]) {
items.push(item_library[added_from_uri]);
}
addedFromItems = indexToArray(items, [added_from_uri]);
break;
}
if (items.length <= 0) return null;
if (!addedFromItems.length) return null;
return (
<div className="current-track__added-from">
{items[0].images && (
{addedFromItems[0].images && (
<URILink
uri={items[0].uri}
uri={addedFromItems[0].uri}
className="current-track__added-from__thumbnail"
>
<Thumbnail
images={items[0].images}
images={addedFromItems[0].images}
size="small"
circle={uriType(items[0].uri) === 'artist'}
circle={uriType(addedFromItems[0].uri) === 'artist'}
type="artist"
/>
</URILink>
)}
<div className="current-track__added-from__text">
{'Playing from '}
<LinksSentence items={items} />
<LinksSentence items={addedFromItems} />
{uri_type === 'radio' && (
<span className="flag flag--blue">
{i18n('now_playing.current_track.radio')}
@ -221,6 +202,12 @@ class Queue extends React.Component {
queue_tracks,
theme,
current_track_uri,
spotify_enabled,
uiActions,
mopidyActions: {
clearTracklist,
shuffleTracklist,
},
} = this.props;
const { limit } = this.state;
const total_queue_tracks = queue_tracks.length;
@ -235,7 +222,7 @@ class Queue extends React.Component {
const options = (
<span>
{this.props.spotify_enabled && (
{spotify_enabled && (
<Button noHover discrete to="/queue/radio">
<Icon name="radio" />
<I18n path="now_playing.context_actions.radio" />
@ -254,7 +241,7 @@ class Queue extends React.Component {
return (
<div className="view queue-view preserve-3d">
<Header options={options} uiActions={this.props.uiActions}>
<Header options={options} uiActions={uiActions}>
<Icon name="play_arrow" type="material" />
<I18n path="now_playing.title" />
</Header>
@ -293,7 +280,7 @@ class Queue extends React.Component {
<li><Dater type="total-time" data={queue_tracks} /></li>
{queue_tracks.length > 0 && (
<li>
<a onClick={this.props.mopidyActions.shuffleTracklist}>
<a onClick={shuffleTracklist}>
<Icon name="shuffle" />
<I18n path="now_playing.current_track.shuffle" />
</a>
@ -301,7 +288,7 @@ class Queue extends React.Component {
)}
{queue_tracks.length > 0 && (
<li>
<a onClick={this.props.mopidyActions.clearTracklist}>
<a onClick={clearTracklist}>
<Icon name="delete_sweep" />
<I18n path="now_playing.current_track.clear" />
</a>
@ -341,12 +328,17 @@ class Queue extends React.Component {
}
}
const mapStateToProps = (state, ownProps) => {
const mapStateToProps = (state) => {
let { current_track } = state.core;
const {
items,
queue,
queue_metadata,
} = state.core;
const queue_tracks = [];
if (state.core.queue && state.core.tracks) {
for (const queue_track of state.core.queue) {
if (queue && items) {
for (const queue_track of queue) {
let track = {
...queue_track,
playing: current_track && current_track.tlid == queue_track.tlid,
@ -355,18 +347,18 @@ const mapStateToProps = (state, ownProps) => {
// If we have the track in our index, merge it in.
// We prioritise queue track over index track as queue has unique data, like which track
// is playing and tlids.
if (state.core.tracks.hasOwnProperty(track.uri)) {
if (items[track.uri]) {
track = {
...state.core.tracks[track.uri],
...items[track.uri],
...track,
};
}
// Now merge in our queue metadata
if (state.core.queue_metadata[`tlid_${track.tlid}`] !== undefined) {
if (queue_metadata[`tlid_${track.tlid}`] !== undefined) {
track = {
...track,
...state.core.queue_metadata[`tlid_${track.tlid}`],
...queue_metadata[`tlid_${track.tlid}`],
};
}
@ -389,6 +381,7 @@ const mapStateToProps = (state, ownProps) => {
albums: state.core.albums,
playlists: state.core.playlists,
tracks: state.core.tracks,
items,
queue_tracks,
current_track_uri: state.core.current_track_uri,
current_track,

View File

@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { compact } from 'lodash';
import InputRange from 'react-input-range';
import AlbumGrid from '../../components/AlbumGrid';
import TrackList from '../../components/TrackList';
@ -16,6 +16,7 @@ import Icon from '../../components/Icon';
import * as uiActions from '../../services/ui/actions';
import * as mopidyActions from '../../services/mopidy/actions';
import * as spotifyActions from '../../services/spotify/actions';
import * as coreActions from '../../services/core/actions';
import {
isLoading,
getFromUri,
@ -195,10 +196,10 @@ class Discover extends React.Component {
uiActions: {
showContextMenu,
},
tracks: tracksProp,
items: itemsProp,
} = this.props;
const tracks = indexToArray(tracksProp, tracks_uris);
const tracks = indexToArray(itemsProp, tracks_uris);
showContextMenu({
e,
@ -211,9 +212,8 @@ class Discover extends React.Component {
handleURLSeeds = () => {
const {
spotifyActions: {
getArtist,
getTrack,
coreActions: {
loadItems,
},
match: {
params: {
@ -226,19 +226,7 @@ class Discover extends React.Component {
// 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(',');
for (let i = 0; i < seeds.length; i++) {
switch (uriType(seeds[i])) {
case 'artist':
getArtist(seeds[i]);
break;
case 'track':
getTrack(seeds[i]);
break;
default:
break;
}
}
loadItems(seeds);
this.setState(
{ seeds },
@ -251,7 +239,7 @@ class Discover extends React.Component {
seeds,
} = this.state;
var uri = 'iris:discover';
let uri = 'iris:discover';
if (seeds) {
uri += ':';
for (var i = 0; i < seeds.length; i++) {
@ -325,8 +313,7 @@ class Discover extends React.Component {
renderSeeds = () => {
const {
tracks,
artists,
items,
} = this.props;
const {
seeds,
@ -338,34 +325,22 @@ class Discover extends React.Component {
const uri = seeds[i];
switch (uriType(uri)) {
case 'track':
if (typeof (tracks[uri]) !== 'undefined') {
seeds_objects.push(tracks[uri]);
} else {
seeds_objects.push({
name: 'Loading...',
uri,
});
}
break;
case 'artist':
if (typeof (artists[uri]) !== 'undefined') {
seeds_objects.push(artists[uri]);
} else {
seeds_objects.push({
name: 'Loading...',
uri,
});
}
break;
case 'genre':
var name = getFromUri('genreid', uri);
const name = getFromUri('genreid', uri);
seeds_objects.push({
name: (name.charAt(0).toUpperCase() + name.slice(1)).replace('-', ' '),
uri,
});
break;
default:
if (typeof (items[uri]) !== 'undefined') {
seeds_objects.push(items[uri]);
} else {
seeds_objects.push({
name: 'Loading...',
uri,
});
}
break;
}
}
@ -475,44 +450,16 @@ class Discover extends React.Component {
albums_uris,
artists_uris,
} = {},
tracks: tracksProp,
artists: artistsProp,
albums: albumsProp,
items: itemsProp,
} = this.props;
if (!albums_uris === undefined || artists_uris === undefined) {
return <div className="content-wrapper recommendations-results" />;
}
const tracks = [];
if (tracks_uris && tracksProp) {
for (var i = 0; i < tracks_uris.length; i++) {
var uri = tracks_uris[i];
if (tracksProp.hasOwnProperty(uri)) {
tracks.push(tracksProp[uri]);
}
}
}
const artists = [];
if (artists_uris && artistsProp) {
for (var i = 0; i < artists_uris.length; i++) {
var uri = artists_uris[i];
if (artistsProp.hasOwnProperty(uri)) {
artists.push(artistsProp[uri]);
}
}
}
const albums = [];
if (albums_uris && albumsProp) {
for (var i = 0; i < albums_uris.length; i++) {
var uri = albums_uris[i];
if (albumsProp.hasOwnProperty(uri)) {
albums.push(albumsProp[uri]);
}
}
}
const tracks = compact(tracks_uris.map((uri) => itemsProp[uri]));
const artists = compact(artists_uris.map((uri) => itemsProp[uri]));
const albums = compact(albums_uris.map((uri) => itemsProp[uri]));
// Complete records not yet in our index
if (tracks.length <= 0 && artists.length <= 0 && albums.length <= 0) {
@ -640,9 +587,8 @@ class Discover extends React.Component {
const mapStateToProps = (state) => ({
theme: state.ui.theme,
albums: state.core.albums,
artists: state.core.artists,
tracks: state.core.tracks,
items: state.core.items,
genres: (state.core.genres ? state.core.genres : []),
authorized: state.spotify.authorization,
load_queue: state.ui.load_queue,
@ -654,6 +600,7 @@ const mapDispatchToProps = (dispatch) => ({
uiActions: bindActionCreators(uiActions, dispatch),
mopidyActions: bindActionCreators(mopidyActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch),
coreActions: bindActionCreators(coreActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Discover);