URI format for libraries, but own core.libraries for better control
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -106,7 +106,7 @@
|
||||
|
||||
// Release details
|
||||
// These are automatically injected to built HTML
|
||||
var build = "1599193622";
|
||||
var build = "1599289916";
|
||||
var version = "3.52.4";
|
||||
|
||||
// Construct the script tag
|
||||
|
||||
@ -138,7 +138,6 @@ export class App extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
localForage.keys().then((r) => console.log(r))
|
||||
const {
|
||||
history,
|
||||
snapcast_enabled,
|
||||
|
||||
@ -29,6 +29,7 @@ import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import { collate } from '../util/format';
|
||||
|
||||
class ContextMenu extends React.Component {
|
||||
constructor(props) {
|
||||
@ -181,11 +182,11 @@ class ContextMenu extends React.Component {
|
||||
|
||||
switch (uriType(uri)) {
|
||||
case 'artist':
|
||||
return (spotify_library_artists && spotify_library_artists.indexOf(uri) > -1);
|
||||
return (spotify_library_artists.items_uris.indexOf(uri) > -1);
|
||||
case 'album':
|
||||
return (spotify_library_albums && spotify_library_albums.indexOf(uri) > -1);
|
||||
return (spotify_library_albums.items_uris.indexOf(uri) > -1);
|
||||
case 'playlist':
|
||||
return (spotify_library_playlists && spotify_library_playlists.indexOf(uri) > -1);
|
||||
return (spotify_library_playlists.items_uris.indexOf(uri) > -1);
|
||||
case 'track':
|
||||
return (spotify_library_tracks && spotify_library_tracks.indexOf(uri) > -1);
|
||||
default:
|
||||
@ -742,14 +743,19 @@ class ContextMenu extends React.Component {
|
||||
const { submenu } = this.state;
|
||||
const {
|
||||
spotify_available,
|
||||
spotify_library_playlists_loaded_all,
|
||||
mopidy_library_playlists_loaded_all,
|
||||
spotifyActions,
|
||||
mopidyActions,
|
||||
spotify_library_playlists: {
|
||||
items_uris: spotify_library,
|
||||
},
|
||||
mopidy_library_playlists: {
|
||||
items_uris: mopidy_library,
|
||||
},
|
||||
coreActions: {
|
||||
loadLibrary,
|
||||
},
|
||||
} = this.props;
|
||||
if (submenu !== name && name === 'add-to-playlist') {
|
||||
if (spotify_available && !spotify_library_playlists_loaded_all) spotifyActions.getLibraryPlaylists();
|
||||
if (!mopidy_library_playlists_loaded_all) mopidyActions.getLibraryPlaylists();
|
||||
if (spotify_available && !spotify_library.length) loadLibrary('spotify:library:playlists');
|
||||
if (!mopidy_library.length) loadLibrary('mopidy:library:playlists');
|
||||
}
|
||||
|
||||
this.setState({ submenu: name });
|
||||
@ -760,57 +766,57 @@ class ContextMenu extends React.Component {
|
||||
renderSubmenu = () => {
|
||||
const { submenu } = this.state;
|
||||
const {
|
||||
playlists: playlistsIndex,
|
||||
processes,
|
||||
items,
|
||||
spotify_library_playlists,
|
||||
mopidy_library_playlists,
|
||||
} = this.props;
|
||||
|
||||
let list = null;
|
||||
let isLoading = false;
|
||||
|
||||
if (submenu === 'add-to-playlist') {
|
||||
if (processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR && processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status === 'running') {
|
||||
isLoading = true;
|
||||
} else {
|
||||
let playlists = indexToArray(playlistsIndex);
|
||||
playlists = compact(playlists.map((playlist) => {
|
||||
if (!playlist.can_edit) return null;
|
||||
return {
|
||||
...playlist,
|
||||
is_pinned: this.isPinned(playlist),
|
||||
};
|
||||
}));
|
||||
playlists = sortItems(playlists, 'name');
|
||||
playlists = sortItems(playlists, 'is_pinned');
|
||||
let playlists = [
|
||||
...(collate(spotify_library_playlists, { items }).items || []),
|
||||
...(collate(mopidy_library_playlists, { items }).items || []),
|
||||
];
|
||||
playlists = compact(playlists.map((playlist) => {
|
||||
if (!playlist.can_edit) return null;
|
||||
return {
|
||||
...playlist,
|
||||
is_pinned: this.isPinned(playlist),
|
||||
};
|
||||
}));
|
||||
playlists = sortItems(playlists, 'name');
|
||||
playlists = sortItems(playlists, 'is_pinned');
|
||||
|
||||
list = (
|
||||
<span className="context-menu__item">
|
||||
<span className="context-menu__item mid_grey-text">
|
||||
<span className="context-menu__item__link context-menu__item__link--inactive">
|
||||
<I18n path="context_menu.add_to_playlist.no_playlists" />
|
||||
</span>
|
||||
list = (
|
||||
<span className="context-menu__item">
|
||||
<span className="context-menu__item mid_grey-text">
|
||||
<span className="context-menu__item__link context-menu__item__link--inactive">
|
||||
<I18n path="context_menu.add_to_playlist.no_playlists" />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
if (playlists.length) {
|
||||
list = playlists.map((playlist) => {
|
||||
</span>
|
||||
);
|
||||
if (playlists.length) {
|
||||
list = playlists.map((playlist) => {
|
||||
|
||||
// Allows us to css target last-child
|
||||
const ElementTag = playlist.is_pinned ? 'em' : 'span';
|
||||
// Allows us to css target last-child
|
||||
const ElementTag = playlist.is_pinned ? 'em' : 'span';
|
||||
|
||||
return (
|
||||
<ElementTag className="context-menu__item" key={playlist.uri}>
|
||||
<a
|
||||
className="context-menu__item__link"
|
||||
onClick={(e) => this.addTracksToPlaylist(e, playlist.uri)}
|
||||
>
|
||||
<span className="context-menu__item__label">
|
||||
{playlist.name}
|
||||
</span>
|
||||
</a>
|
||||
</ElementTag>
|
||||
);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ElementTag className="context-menu__item" key={playlist.uri}>
|
||||
<a
|
||||
className="context-menu__item__link"
|
||||
onClick={(e) => this.addTracksToPlaylist(e, playlist.uri)}
|
||||
>
|
||||
<span className="context-menu__item__label">
|
||||
{playlist.name}
|
||||
</span>
|
||||
</a>
|
||||
</ElementTag>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1305,17 +1311,16 @@ const mapStateToProps = (state) => ({
|
||||
current_tracklist: state.core.current_tracklist,
|
||||
queue_metadata: state.core.queue_metadata,
|
||||
spotify_available: state.spotify.access_token,
|
||||
spotify_library_playlists: state.spotify.library_playlists,
|
||||
spotify_library_playlists_loaded_all: state.spotify.library_playlists_loaded_all,
|
||||
spotify_library_artists: state.spotify.library_artists,
|
||||
spotify_library_albums: state.spotify.library_albums,
|
||||
spotify_library_playlists: state.core.items['spotify:library:playlists'] || { items_uris: [] },
|
||||
spotify_library_artists: state.core.items['spotify:library:artists'] || { items_uris: [] },
|
||||
spotify_library_albums: state.core.items['spotify:library:albums'] || { items_uris: [] },
|
||||
spotify_library_tracks: state.spotify.library_tracks,
|
||||
mopidy_library_playlists: state.mopidy.library_playlists,
|
||||
mopidy_library_playlists_loaded_all: state.mopidy.library_playlists_loaded_all,
|
||||
mopidy_library_artists: state.mopidy.library_artists,
|
||||
mopidy_library_albums: state.mopidy.library_albums,
|
||||
mopidy_library_playlists: state.core.items['mopidy:library:playlists'] || { items_uris: [] },
|
||||
mopidy_library_artists: state.core.items['mopidy:library:artists'] || { items_uris: [] },
|
||||
mopidy_library_albums: state.core.items['mopidy:library:albums'] || { items_uris: [] },
|
||||
playlists: state.core.playlists,
|
||||
tracks: state.core.tracks,
|
||||
items: state.core.items,
|
||||
pinned: state.pusher.pinned,
|
||||
lastfm_authorized: state.lastfm.authorization,
|
||||
});
|
||||
|
||||
@ -33,28 +33,36 @@ class Hotkeys extends React.Component {
|
||||
allow_reporting,
|
||||
} = this.props;
|
||||
let { volume } = this.props;
|
||||
const key = e.key.toLowerCase();
|
||||
let { key } = e;
|
||||
const {
|
||||
target,
|
||||
altKey,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
shiftKey,
|
||||
preventDefault,
|
||||
} = e;
|
||||
key = key.toLowerCase();
|
||||
|
||||
// Ignore text input fields
|
||||
if (
|
||||
(e.target.nodeName === 'INPUT' && (e.target.type === 'text' || e.target.type === 'number')) ||
|
||||
e.target.nodeName === 'TEXTAREA' ||
|
||||
(e.target.nodeName === 'BUTTON' && key === ' ')) {
|
||||
(target.nodeName === 'INPUT' && (target.type === 'text' || target.type === 'number')) ||
|
||||
target.nodeName === 'TEXTAREA' ||
|
||||
(target.nodeName === 'BUTTON' && key === ' ')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore when there are any key modifiers. This enables us to avoid interfering
|
||||
// with browser- and OS-default functions.
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
|
||||
if (altKey || ctrlKey || metaKey || shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let prevent = false;
|
||||
switch (key) {
|
||||
case ' ':
|
||||
case 'p': // Super-useful once you get used to it. This negates the issue where interactive elements
|
||||
// are in focus (ie slider) and <space> is reserved for that field's interactivity.
|
||||
if (play_state == 'playing') {
|
||||
if (play_state === 'playing') {
|
||||
mopidyActions.pause();
|
||||
uiActions.createNotification({ content: 'pause', type: 'shortcut' });
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Pause' });
|
||||
@ -63,13 +71,13 @@ class Hotkeys extends React.Component {
|
||||
uiActions.createNotification({ content: 'play_arrow', type: 'shortcut' });
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Play' });
|
||||
}
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
break;
|
||||
|
||||
case 's': // Stop
|
||||
mopidyActions.stop();
|
||||
uiActions.createNotification({ content: 'stop', type: 'shortcut' });
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Stop' });
|
||||
break;
|
||||
|
||||
@ -80,14 +88,14 @@ class Hotkeys extends React.Component {
|
||||
}
|
||||
mopidyActions.setTimePosition(new_position);
|
||||
uiActions.createNotification({ content: 'fast_rewind', type: 'shortcut' });
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Rewind' });
|
||||
break;
|
||||
|
||||
case 'f': // Seek forwards 30 sec
|
||||
mopidyActions.setTimePosition(play_time_position + 30000);
|
||||
uiActions.createNotification({ content: 'fast_forward', type: 'shortcut' });
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Fastforward' });
|
||||
break;
|
||||
|
||||
@ -95,7 +103,7 @@ class Hotkeys extends React.Component {
|
||||
case '<': // Previous track
|
||||
mopidyActions.previous();
|
||||
uiActions.createNotification({ content: 'skip_previous', type: 'shortcut' });
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Previous' });
|
||||
break;
|
||||
|
||||
@ -103,7 +111,7 @@ class Hotkeys extends React.Component {
|
||||
case '>': // Next track
|
||||
mopidyActions.next();
|
||||
uiActions.createNotification({ content: 'skip_next', type: 'shortcut' });
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Next' });
|
||||
break;
|
||||
|
||||
@ -120,7 +128,7 @@ class Hotkeys extends React.Component {
|
||||
}
|
||||
uiActions.createNotification({ content: 'volume_up', type: 'shortcut' });
|
||||
}
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Volume up' });
|
||||
break;
|
||||
|
||||
@ -136,7 +144,7 @@ class Hotkeys extends React.Component {
|
||||
}
|
||||
}
|
||||
uiActions.createNotification({ content: 'volume_down', type: 'shortcut' });
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Volume down' });
|
||||
break;
|
||||
|
||||
@ -150,46 +158,42 @@ class Hotkeys extends React.Component {
|
||||
uiActions.createNotification({ content: 'volume_off', type: 'shortcut' });
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Mute' });
|
||||
}
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
break;
|
||||
|
||||
case 'escape': // Cancel current action/context
|
||||
if (dragging) {
|
||||
uiActions.dragEnd();
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Dragging' });
|
||||
} else if ($('body').hasClass('modal-open')) {
|
||||
window.history.back();
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Modal' });
|
||||
}
|
||||
break;
|
||||
|
||||
case '1': // Navigation: Queue
|
||||
history.push('/queue');
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Queue' });
|
||||
break;
|
||||
|
||||
case '2': // Navigation: Search
|
||||
history.push('/search');
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Search' });
|
||||
break;
|
||||
|
||||
case '3': // Navigation: Kiosk mode
|
||||
history.push('/kiosk-mode');
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
if (allow_reporting) ReactGA.event({ category: 'Hotkey', action: key, label: 'Kiosk mode' });
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (prevent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@ -32,6 +32,15 @@ class TrackList extends React.Component {
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
const {
|
||||
key,
|
||||
altKey,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
shiftKey,
|
||||
preventDefault,
|
||||
} = e;
|
||||
|
||||
// When we're focussed on certian elements, don't fire any shortcuts
|
||||
// Typically form inputs
|
||||
const ignoreNodes = ['INPUT', 'TEXTAREA'];
|
||||
@ -41,20 +50,18 @@ class TrackList extends React.Component {
|
||||
|
||||
// Ignore when there are any key modifiers. This enables us to avoid interfering
|
||||
// with browser- and OS-default functions.
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
|
||||
if (altKey || ctrlKey || metaKey || shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tracks_keys = this.digestTracksKeys();
|
||||
|
||||
|
||||
let prevent = false;
|
||||
switch (e.key.toLowerCase()) {
|
||||
switch (key.toLowerCase()) {
|
||||
case 'enter':
|
||||
if (tracks_keys && tracks_keys.length > 0) {
|
||||
this.playTracks();
|
||||
}
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
break;
|
||||
|
||||
case 'backspace':
|
||||
@ -62,7 +69,7 @@ class TrackList extends React.Component {
|
||||
if (tracks_keys && tracks_keys.length > 0) {
|
||||
this.removeTracks();
|
||||
}
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
@ -71,14 +78,9 @@ class TrackList extends React.Component {
|
||||
all_tracks.push(this.buildTrackKey(this.props.tracks[i], i));
|
||||
}
|
||||
this.props.uiActions.setSelectedTracks(all_tracks);
|
||||
prevent = true;
|
||||
preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
if (prevent) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
handleDrag(e, track_key) {
|
||||
|
||||
@ -77,13 +77,20 @@ export function clearStorage() {
|
||||
};
|
||||
}
|
||||
|
||||
export function restoreFromColdStore(items) {
|
||||
export function restoreItemsFromColdStore(items) {
|
||||
return {
|
||||
type: 'RESTORE_FROM_COLD_STORE',
|
||||
type: 'RESTORE_ITEMS_FROM_COLD_STORE',
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
export function restoreLibraryFromColdStore(library) {
|
||||
return {
|
||||
type: 'RESTORE_LIBRARY_FROM_COLD_STORE',
|
||||
library,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateColdStore(items) {
|
||||
return {
|
||||
type: 'UPDATE_COLD_STORE',
|
||||
@ -175,29 +182,31 @@ export function loadLibrary(uri, force_reload = false) {
|
||||
* We've got a loaded record, now we just need to plug it in to our state and stores.
|
||||
* */
|
||||
|
||||
export function libraryLoaded(library) {
|
||||
return {
|
||||
type: 'LIBRARY_LOADED',
|
||||
library,
|
||||
};
|
||||
}
|
||||
export function itemsLoaded(items) {
|
||||
return {
|
||||
type: 'ITEMS_LOADED',
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
export function itemLoaded(item) {
|
||||
return {
|
||||
type: 'ITEM_LOADED',
|
||||
item,
|
||||
};
|
||||
return itemsLoaded([item]);
|
||||
}
|
||||
|
||||
export function trackLoaded(track) {
|
||||
return tracksLoaded([track]);
|
||||
}
|
||||
export function tracksLoaded(tracks) {
|
||||
return {
|
||||
type: 'TRACKS_LOADED',
|
||||
tracks,
|
||||
};
|
||||
}
|
||||
export function trackLoaded(track) {
|
||||
return tracksLoaded([track]);
|
||||
}
|
||||
|
||||
export function artistsLoaded(artists) {
|
||||
return {
|
||||
@ -206,35 +215,35 @@ export function artistsLoaded(artists) {
|
||||
};
|
||||
}
|
||||
|
||||
export function albumLoaded(album) {
|
||||
return albumsLoaded([album]);
|
||||
}
|
||||
export function albumsLoaded(albums) {
|
||||
return {
|
||||
type: 'ALBUMS_LOADED',
|
||||
albums,
|
||||
};
|
||||
}
|
||||
|
||||
export function playlistLoaded(playlist) {
|
||||
return playlistsLoaded([playlist]);
|
||||
export function albumLoaded(album) {
|
||||
return albumsLoaded([album]);
|
||||
}
|
||||
|
||||
export function playlistsLoaded(playlists) {
|
||||
return {
|
||||
type: 'PLAYLISTS_LOADED',
|
||||
playlists,
|
||||
};
|
||||
}
|
||||
|
||||
export function userLoaded(user) {
|
||||
return usersLoaded([user]);
|
||||
export function playlistLoaded(playlist) {
|
||||
return playlistsLoaded([playlist]);
|
||||
}
|
||||
|
||||
export function usersLoaded(users) {
|
||||
return {
|
||||
type: 'USERS_LOADED',
|
||||
users,
|
||||
};
|
||||
}
|
||||
export function userLoaded(user) {
|
||||
return usersLoaded([user]);
|
||||
}
|
||||
export function userPlaylistsLoaded(uri, playlists, more = null, total = null) {
|
||||
return {
|
||||
type: 'USER_PLAYLISTS_LOADED',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import ReactGA from 'react-ga';
|
||||
import localForage from 'localforage';
|
||||
import { compact } from 'lodash';
|
||||
import { arrayOf } from '../../util/arrays';
|
||||
import URILink from '../../components/URILink';
|
||||
import { uriSource, upgradeSpotifyPlaylistUris, uriType, titleCase } from '../../util/helpers';
|
||||
@ -366,12 +366,13 @@ const CoreMiddleware = (function () {
|
||||
};
|
||||
|
||||
if (action.force_reload) {
|
||||
console.info(`Force-reloading "${action.uri}"`);
|
||||
fetchAlbum();
|
||||
break;
|
||||
}
|
||||
if (
|
||||
store.getState().core.items[action.uri] &&
|
||||
store.getState().core.items[action.uri].tracks
|
||||
store.getState().core.items[action.uri]
|
||||
&& store.getState().core.items[action.uri].tracks
|
||||
) {
|
||||
console.info(`Using "${action.uri}" from index`);
|
||||
break;
|
||||
@ -380,8 +381,7 @@ const CoreMiddleware = (function () {
|
||||
localForage.getItem(action.uri).then((result) => {
|
||||
if (result) {
|
||||
console.info(`Loading "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreFromColdStore([result]));
|
||||
console.log(result);
|
||||
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
|
||||
|
||||
// We don't have the complete Album, so refetch
|
||||
if (!result.tracks) {
|
||||
@ -395,54 +395,48 @@ const CoreMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
// TODO: Relocate this
|
||||
case 'UPDATE_COLD_STORE':
|
||||
if (action.items) {
|
||||
action.items.map((item) => {
|
||||
localForage.getItem(item.uri).then((result) => {
|
||||
localForage.setItem(item.uri, { ...(result || {}), ...item });
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'RESTORE_FROM_COLD_STORE':
|
||||
if (action.items) {
|
||||
store.dispatch({
|
||||
type: 'RESTORED_FROM_COLD_STORE',
|
||||
items: action.items,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'LOAD_ARTIST':
|
||||
const fetchArtist = () => {
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
store.dispatch(spotifyActions.getArtist(action.uri, true));
|
||||
|
||||
if (spotify.me) {
|
||||
store.dispatch(spotifyActions.following(action.uri));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(mopidyActions.getArtist(action.uri));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (action.force_reload) {
|
||||
console.info(`Force-reloading "${action.uri}"`);
|
||||
fetchArtist();
|
||||
break;
|
||||
}
|
||||
if (
|
||||
!action.force_reload
|
||||
&& store.getState().core.artists[action.uri]
|
||||
&& store.getState().core.artists[action.uri].albums
|
||||
&& store.getState().core.artists[action.uri].tracks) {
|
||||
console.info(`Loading "${action.uri}" from index`);
|
||||
store.getState().core.items[action.uri]
|
||||
&& store.getState().core.items[action.uri].tracks
|
||||
&& store.getState().core.items[action.uri].albums
|
||||
&& store.getState().core.items[action.uri].images
|
||||
) {
|
||||
console.info(`Using "${action.uri}" from index`);
|
||||
break;
|
||||
}
|
||||
|
||||
// Try our cold storage
|
||||
localForage.getItem(action.uri).then((result) => {
|
||||
if (result && !action.force_reload) {
|
||||
console.info(`Loading "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreFromColdStore([result]));
|
||||
} else {
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
store.dispatch(spotifyActions.getArtist(action.uri, true));
|
||||
if (result) {
|
||||
console.info(`Restoring "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
|
||||
|
||||
if (spotify.me) {
|
||||
store.dispatch(spotifyActions.following(action.uri));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(mopidyActions.getArtist(action.uri));
|
||||
break;
|
||||
if (!result.tracks || !result.albums || !result.images) {
|
||||
fetchArtist();
|
||||
}
|
||||
} else {
|
||||
fetchArtist();
|
||||
}
|
||||
});
|
||||
|
||||
@ -467,10 +461,14 @@ const CoreMiddleware = (function () {
|
||||
};
|
||||
|
||||
if (action.force_reload) {
|
||||
console.info(`Force-reloading "${action.uri}"`);
|
||||
fetchPlaylist();
|
||||
break;
|
||||
}
|
||||
if (store.getState().core.items[action.uri]) {
|
||||
if (
|
||||
store.getState().core.items[action.uri]
|
||||
&& store.getState().core.items[action.uri].tracks
|
||||
) {
|
||||
console.info(`Using "${action.uri}" from index`);
|
||||
break;
|
||||
}
|
||||
@ -478,7 +476,11 @@ const CoreMiddleware = (function () {
|
||||
localForage.getItem(action.uri).then((result) => {
|
||||
if (result) {
|
||||
console.info(`Restoring "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreFromColdStore([result]));
|
||||
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
|
||||
|
||||
if (!result.tracks) {
|
||||
fetchPlaylist();
|
||||
}
|
||||
} else {
|
||||
fetchPlaylist();
|
||||
}
|
||||
@ -540,27 +542,28 @@ const CoreMiddleware = (function () {
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
store.dispatch(
|
||||
spotifyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
|
||||
spotifyActions[`getLibrary${titleCase(uriType(action.uri))}`](),
|
||||
);
|
||||
break;
|
||||
case 'google':
|
||||
store.dispatch(
|
||||
googleActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
|
||||
googleActions[`getLibrary${titleCase(uriType(action.uri))}`](),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
store.dispatch(
|
||||
mopidyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.uri),
|
||||
mopidyActions[`getLibrary${titleCase(uriType(action.uri))}`](),
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (action.force_reload) {
|
||||
console.info(`Force-reloading "${action.uri}"`);
|
||||
fetchLibrary();
|
||||
break;
|
||||
}
|
||||
if (store.getState().core.items[action.uri]) {
|
||||
if (store.getState().core.libraries[action.uri]) {
|
||||
console.info(`Using "${action.uri}" from index`);
|
||||
break;
|
||||
}
|
||||
@ -569,13 +572,12 @@ const CoreMiddleware = (function () {
|
||||
if (library) {
|
||||
console.info(`Restoring "${action.uri}" and ${library.items_uris.length} items from db`);
|
||||
|
||||
var promises = library.items_uris.map(function(item) { return localForage.getItem(item); });
|
||||
Promise.all(promises).then(function(libraryItems) {
|
||||
store.dispatch(coreActions.restoreFromColdStore(libraryItems));
|
||||
});
|
||||
const promises = library.items_uris.map((libraryItem) => localForage.getItem(libraryItem));
|
||||
Promise.all(promises).then(
|
||||
(libraryItems) => store.dispatch(coreActions.restoreItemsFromColdStore(compact(libraryItems))),
|
||||
);
|
||||
|
||||
//store.dispatch(coreActions.loadItems(result.items_uris));
|
||||
store.dispatch(coreActions.restoreFromColdStore([library]));
|
||||
store.dispatch(coreActions.restoreLibraryFromColdStore(library));
|
||||
} else {
|
||||
fetchLibrary();
|
||||
}
|
||||
@ -687,23 +689,22 @@ const CoreMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
// TODO: Flatten ITEM_LOADED to become an alias for ITEMS_LOADED (prefer bulk)
|
||||
case 'ITEMS_LOADED':
|
||||
action.items.forEach((item) => store.dispatch(coreActions.itemLoaded(item)));
|
||||
break;
|
||||
|
||||
case 'ITEM_LOADED':
|
||||
const mergedItem = {
|
||||
...core.items[action.item.uri] || {},
|
||||
...action.item,
|
||||
};
|
||||
store.dispatch(coreActions.updateColdStore([mergedItem]));
|
||||
const mergedItems = action.items.map((item) => ({
|
||||
...core.items[item.uri] || {},
|
||||
...item,
|
||||
}));
|
||||
store.dispatch(coreActions.updateColdStore(mergedItems));
|
||||
next({
|
||||
...action,
|
||||
item: mergedItem,
|
||||
items: mergedItems,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'LIBRARY_LOADED':
|
||||
store.dispatch(coreActions.updateColdStore([action.library]));
|
||||
break;
|
||||
|
||||
case 'ARTISTS_LOADED':
|
||||
var artists_index = { ...core.artists };
|
||||
var artists_loaded = [];
|
||||
@ -753,7 +754,7 @@ const CoreMiddleware = (function () {
|
||||
|
||||
case 'spotify':
|
||||
if (spotify.authorization && spotify.me) {
|
||||
playlist.can_edit = (playlist.owner.id == spotify.me.id);
|
||||
playlist.can_edit = (playlist.owner && playlist.owner.id == spotify.me.id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -911,6 +912,17 @@ const CoreMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
// TODO: Relocate this
|
||||
case 'UPDATE_COLD_STORE':
|
||||
if (action.items) {
|
||||
action.items.map((item) => {
|
||||
localForage.getItem(item.uri).then((result) => {
|
||||
localForage.setItem(item.uri, { ...(result || {}), ...item });
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
|
||||
@ -75,6 +75,26 @@ export default function reducer(core = {}, action) {
|
||||
},
|
||||
};
|
||||
|
||||
case 'ITEMS_LOADED':
|
||||
const mergedItems = action.items.reduce((obj, item) => (obj[item.uri] = item, obj), {});
|
||||
return {
|
||||
...core,
|
||||
items: {
|
||||
...core.items,
|
||||
...mergedItems,
|
||||
},
|
||||
};
|
||||
|
||||
case 'LIBRARY_LOADED':
|
||||
console.log(action);
|
||||
return {
|
||||
...core,
|
||||
libraries: {
|
||||
...core.libraries,
|
||||
[action.library.uri]: action.library,
|
||||
},
|
||||
};
|
||||
|
||||
case 'TRACKS_LOADED':
|
||||
var tracks = { ...core.tracks };
|
||||
for (const track of action.tracks) {
|
||||
@ -148,8 +168,18 @@ export default function reducer(core = {}, action) {
|
||||
users[action.uri] = user;
|
||||
return { ...core, users };
|
||||
|
||||
case 'RESTORE_LIBRARY_FROM_COLD_STORE':
|
||||
const { libraries } = core;
|
||||
libraries[action.library.uri] = {
|
||||
...(libraries[action.library.uri] || {}),
|
||||
...action.library,
|
||||
};
|
||||
return {
|
||||
...core,
|
||||
libraries,
|
||||
};
|
||||
|
||||
case 'RESTORED_FROM_COLD_STORE':
|
||||
case 'RESTORE_ITEMS_FROM_COLD_STORE':
|
||||
const { items } = core;
|
||||
action.items.forEach((item) => {
|
||||
items[item.uri] = {
|
||||
|
||||
@ -228,7 +228,7 @@ export function getArtist(uri, artist, mbid = false) {
|
||||
.then(
|
||||
(response) => {
|
||||
if (response.artist) {
|
||||
dispatch(coreActions.artistLoaded({
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
mbid: response.artist.mbid,
|
||||
biography: response.artist.bio.content,
|
||||
@ -255,7 +255,7 @@ export function getAlbum(uri, artist, album, mbid = false) {
|
||||
.then(
|
||||
(response) => {
|
||||
if (response.album) {
|
||||
const existing_album = getState().core.albums[uri];
|
||||
const existing_album = getState().core.items[uri];
|
||||
const album = {
|
||||
uri,
|
||||
images: response.album.image,
|
||||
@ -273,7 +273,7 @@ export function getAlbum(uri, artist, album, mbid = false) {
|
||||
delete album.images;
|
||||
}
|
||||
|
||||
dispatch(coreActions.albumLoaded(album));
|
||||
dispatch(coreActions.itemLoaded(album));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -652,7 +652,7 @@ const MopidyMiddleware = (function () {
|
||||
* */
|
||||
|
||||
case 'MOPIDY_PLAY_PLAYLIST':
|
||||
var playlist = store.getState().core.playlists[action.uri];
|
||||
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) {
|
||||
@ -700,7 +700,7 @@ const MopidyMiddleware = (function () {
|
||||
|
||||
case 'MOPIDY_ENQUEUE_PLAYLIST':
|
||||
|
||||
var playlist = store.getState().core.playlists[action.uri];
|
||||
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) {
|
||||
@ -1598,41 +1598,6 @@ const MopidyMiddleware = (function () {
|
||||
* ======================================================================================
|
||||
* */
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_PLAYLISTS':
|
||||
request(store, 'playlists.asList')
|
||||
.then((response) => {
|
||||
|
||||
// Remove any Spotify playlists. These will be handled by our Spotify API
|
||||
const playlist_uris = arrayOf('uri', response).filter(
|
||||
(uri) => uriSource(uri) !== 'spotify',
|
||||
);
|
||||
const libraryPlaylists = [];
|
||||
|
||||
// get the full playlist objects
|
||||
playlist_uris.forEach((uri, index) => {
|
||||
request(store, 'playlists.lookup', { uri })
|
||||
.then((response) => {
|
||||
libraryPlaylists.push({
|
||||
type: 'playlist',
|
||||
name: response.name,
|
||||
uri: response.uri,
|
||||
source: uriSource(response.uri),
|
||||
provider: 'mopidy',
|
||||
last_modified: response.last_modified,
|
||||
tracks: response.tracks,
|
||||
});
|
||||
|
||||
if (index === playlist_uris.length - 1) {
|
||||
store.dispatch(coreActions.itemsLoaded(libraryPlaylists));
|
||||
store.dispatch(coreActions.itemLoaded({
|
||||
uri: 'mopidy:library:playlists',
|
||||
items_uris: arrayOf('uri', libraryPlaylists),
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_PLAYLIST':
|
||||
request(store, 'playlists.lookup', action.data)
|
||||
@ -1766,7 +1731,7 @@ const MopidyMiddleware = (function () {
|
||||
// Overwrite our playlist with the response to our save
|
||||
// This is essential to get the updated URI from Mopidy
|
||||
const playlist = {
|
||||
...store.getState().core.playlists[action.key],
|
||||
...store.getState().core.items[action.key],
|
||||
uri: response.uri,
|
||||
name: response.name,
|
||||
};
|
||||
@ -1858,28 +1823,6 @@ const MopidyMiddleware = (function () {
|
||||
* */
|
||||
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_ALBUMS':
|
||||
request(store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri })
|
||||
.then((response) => {
|
||||
const uris = arrayOf('uri', response);
|
||||
request(store, 'library.lookup', { uris })
|
||||
.then((response) => {
|
||||
const libraryAlbums = indexToArray(response).map((tracks) => ({
|
||||
source: 'local',
|
||||
artists: tracks[0].artists || null,
|
||||
tracks,
|
||||
last_modified: tracks[0].last_modified,
|
||||
...tracks[0].album,
|
||||
}));
|
||||
|
||||
store.dispatch(coreActions.itemsLoaded(libraryAlbums));
|
||||
store.dispatch(coreActions.itemLoaded({
|
||||
uri: 'mopidy:library:albums',
|
||||
items_uris: arrayOf('uri', libraryAlbums),
|
||||
}));
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_ALBUM':
|
||||
request(store, 'library.lookup', { uris: [action.uri] })
|
||||
@ -1911,7 +1854,8 @@ const MopidyMiddleware = (function () {
|
||||
* =============================================================== ARTIST(S) ============
|
||||
* ======================================================================================
|
||||
**/
|
||||
case 'MOPIDY_GET_LIBRARY_ARTISTS':
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_ARTISTSXXXXXXXXXXXX':
|
||||
const uri = store.getState().mopidy.library_artists_uri;
|
||||
request(store, 'library.browse', { uri })
|
||||
.then((response) => {
|
||||
@ -1919,8 +1863,6 @@ const MopidyMiddleware = (function () {
|
||||
|
||||
const uris = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
// Convert local URI to actual artist URI
|
||||
// See https://github.com/mopidy/mopidy-local-sqlite/issues/39
|
||||
response[i].uri = response[i].uri.replace('local:directory?albumartist=', '').replace('local:directory?artist=', '');
|
||||
uris.push(response[i].uri);
|
||||
}
|
||||
@ -1934,62 +1876,6 @@ const MopidyMiddleware = (function () {
|
||||
});
|
||||
break;
|
||||
|
||||
/**
|
||||
* TODO: Fetch and process library artists
|
||||
*
|
||||
* We can't get specific artist artwork from Mopidy. Perhaps we fetch additional
|
||||
* artist metadata via LastFM? Their API limits will make this quite slow.
|
||||
case 'MOPIDY_GET_LIBRARY_ARTISTS':
|
||||
|
||||
var last_run = store.getState().ui.processes.MOPIDY_LIBRARY_ARTISTS_PROCESSOR
|
||||
|
||||
if (!last_run){
|
||||
request(store, 'library.browse', { uri: 'local:directory?type=artist' } )
|
||||
.then(response => {
|
||||
if (response.length <= 0) return;
|
||||
|
||||
var uris = arrayOf('uri',response);
|
||||
|
||||
store.dispatch({
|
||||
type: 'MOPIDY_LIBRARY_ARTISTS_LOADED',
|
||||
uris: uris
|
||||
});
|
||||
|
||||
// Start our process to load the full album objects
|
||||
store.dispatch(uiActions.startProcess('MOPIDY_LIBRARY_ARTISTS_PROCESSOR','Loading '+uris.length+' local artists', {uris: uris}));
|
||||
})
|
||||
|
||||
} else if (last_run.status == 'cancelled'){
|
||||
store.dispatch(uiActions.resumeProcess('MOPIDY_LIBRARY_ARTISTS_PROCESSOR'))
|
||||
} else if (last_run.status == 'finished'){
|
||||
// TODO: do we want to force a refresh?
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'MOPIDY_LIBRARY_ARTISTS_PROCESSOR':
|
||||
if (store.getState().ui.processes['MOPIDY_LIBRARY_ARTISTS_PROCESSOR'] !== undefined){
|
||||
var processor = store.getState().ui.processes['MOPIDY_LIBRARY_ARTISTS_PROCESSOR'];
|
||||
|
||||
if (processor.status == 'cancelling'){
|
||||
store.dispatch(uiActions.processCancelled('MOPIDY_LIBRARY_ARTISTS_PROCESSOR'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var uris = Object.assign([], action.data.uris)
|
||||
var uris_to_load = uris.splice(0,50)
|
||||
|
||||
if (uris_to_load.length > 0){
|
||||
store.dispatch(uiActions.updateProcess('MOPIDY_LIBRARY_ARTISTS_PROCESSOR', 'Loading '+uris.length+' local artists', {uris: uris}));
|
||||
store.dispatch(mopidyActions.getArtists(uris_to_load, {name: 'MOPIDY_LIBRARY_ARTISTS_PROCESSOR', data: {uris: uris}}));
|
||||
} else {
|
||||
store.dispatch(uiActions.processFinished('MOPIDY_LIBRARY_ARTISTS_PROCESSOR'));
|
||||
}
|
||||
|
||||
break;
|
||||
* */
|
||||
|
||||
case 'MOPIDY_GET_ARTIST':
|
||||
request(store, 'library.lookup', { uris: [action.uri] })
|
||||
.then((_response) => {
|
||||
@ -2016,10 +1902,9 @@ const MopidyMiddleware = (function () {
|
||||
}
|
||||
}
|
||||
if (albums) {
|
||||
store.dispatch(coreActions.albumsLoaded(albums));
|
||||
store.dispatch(coreActions.itemsLoaded(albums));
|
||||
}
|
||||
|
||||
// Start with an empty artist object
|
||||
let artist = {
|
||||
uri: action.uri,
|
||||
provider: 'mopidy',
|
||||
@ -2035,13 +1920,13 @@ const MopidyMiddleware = (function () {
|
||||
}
|
||||
|
||||
// Add our tracks and albums
|
||||
artist.albums_uris = arrayOf('uri', albums),
|
||||
artist.tracks = response;
|
||||
artist.albums_uris = arrayOf('uri', albums);
|
||||
artist.tracks = response;
|
||||
|
||||
store.dispatch(coreActions.artistLoaded(artist));
|
||||
store.dispatch(coreActions.itemLoaded(artist));
|
||||
|
||||
// Load supprting information from LastFM and Discogs
|
||||
const existing_artist = store.getState().core.artists[artist.uri];
|
||||
const existing_artist = store.getState().core.items[artist.uri];
|
||||
if (existing_artist) {
|
||||
if (!existing_artist.images) {
|
||||
if (store.getState().spotify.enabled) {
|
||||
@ -2049,7 +1934,7 @@ const MopidyMiddleware = (function () {
|
||||
} else {
|
||||
store.dispatch(discogsActions.getArtistImages(artist.uri, artist));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get biography and other stats from LastFM
|
||||
if (!existing_artist.biography) {
|
||||
@ -2380,6 +2265,88 @@ const MopidyMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
/**
|
||||
* My Music libraries
|
||||
*/
|
||||
case 'MOPIDY_GET_LIBRARY_ARTISTS':
|
||||
request(store, 'library.browse', { uri: store.getState().mopidy.library_artists_uri })
|
||||
.then((raw_response) => {
|
||||
if (raw_response.length <= 0) return;
|
||||
|
||||
// Convert local URI to actual artist URI
|
||||
// See https://github.com/mopidy/mopidy-local-sqlite/issues/39
|
||||
const response = raw_response.map((artist) => ({
|
||||
...artist,
|
||||
uri: artist.uri.replace('local:directory?albumartist=', '').replace('local:directory?artist=', '')
|
||||
}));
|
||||
|
||||
store.dispatch(coreActions.itemsLoaded(response));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'mopidy:library:artists',
|
||||
items_uris: arrayOf('uri', response),
|
||||
}));
|
||||
});
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_PLAYLISTS':
|
||||
request(store, 'playlists.asList')
|
||||
.then((response) => {
|
||||
|
||||
// Remove any Spotify playlists. These will be handled by our Spotify API
|
||||
const playlist_uris = arrayOf('uri', response).filter(
|
||||
(uri) => uriSource(uri) !== 'spotify',
|
||||
);
|
||||
const libraryPlaylists = [];
|
||||
|
||||
// get the full playlist objects
|
||||
playlist_uris.forEach((uri, index) => {
|
||||
request(store, 'playlists.lookup', { uri })
|
||||
.then((response) => {
|
||||
libraryPlaylists.push({
|
||||
type: 'playlist',
|
||||
name: response.name,
|
||||
uri: response.uri,
|
||||
source: uriSource(response.uri),
|
||||
provider: 'mopidy',
|
||||
last_modified: response.last_modified,
|
||||
tracks: response.tracks,
|
||||
});
|
||||
|
||||
if (index === playlist_uris.length - 1) {
|
||||
store.dispatch(coreActions.itemsLoaded(libraryPlaylists));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'mopidy:library:playlists',
|
||||
items_uris: arrayOf('uri', libraryPlaylists),
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_ALBUMS':
|
||||
request(store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri })
|
||||
.then((response) => {
|
||||
const uris = arrayOf('uri', response);
|
||||
request(store, 'library.lookup', { uris })
|
||||
.then((response) => {
|
||||
const libraryAlbums = indexToArray(response).map((tracks) => ({
|
||||
source: 'local',
|
||||
artists: tracks[0].artists || null,
|
||||
tracks,
|
||||
last_modified: tracks[0].last_modified,
|
||||
...tracks[0].album,
|
||||
}));
|
||||
|
||||
store.dispatch(coreActions.itemsLoaded(libraryAlbums));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'mopidy:library:albums',
|
||||
items_uris: arrayOf('uri', libraryAlbums),
|
||||
}));
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
formatArtists,
|
||||
formatAlbums,
|
||||
formatPlaylists,
|
||||
formatImages,
|
||||
} from '../../util/format';
|
||||
import URILink from '../../components/URILink';
|
||||
import { i18n } from '../../locale';
|
||||
@ -391,7 +392,6 @@ export function getFeaturedPlaylists() {
|
||||
const playlists = [];
|
||||
for (let i = 0; i < response.playlists.items.length; i++) {
|
||||
playlists.push({
|
||||
|
||||
...response.playlists.items[i],
|
||||
is_completely_loaded: false,
|
||||
can_edit: (getState().spotify.me && response.playlists.items[i].owner.id == getState().spotify.me.id),
|
||||
@ -891,38 +891,6 @@ export function resolveRadioSeeds(radio) {
|
||||
* */
|
||||
|
||||
|
||||
/**
|
||||
* Get my favorites
|
||||
*
|
||||
* @param uri string
|
||||
* */
|
||||
export function getFavorites(limit = 50, term = 'long_term') {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: 'SPOTIFY_FAVORITES_LOADED', artists: [], tracks: [] });
|
||||
|
||||
$.when(
|
||||
request(dispatch, getState, `me/top/artists?limit=${limit}&time_range=${term}`),
|
||||
request(dispatch, getState, `me/top/tracks?limit=${limit}&time_range=${term}`),
|
||||
|
||||
).then(
|
||||
(artists_response, tracks_response) => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_FAVORITES_LOADED',
|
||||
artists: artists_response.items,
|
||||
tracks: tracks_response.items,
|
||||
});
|
||||
},
|
||||
(artists_error, tracks_error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load favorites',
|
||||
{ ...artists_error, ...tracks_error },
|
||||
));
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get our recommendations
|
||||
* This is based off our 'favorites' and then we use those as seeds
|
||||
@ -993,7 +961,7 @@ export function getRecommendations(uris = [], limit = 20, tunabilities = null) {
|
||||
// is not one of the seeds
|
||||
if (!artists_uris.includes(artist.uri) && !artists_ids.includes(artist.id)) {
|
||||
artists_uris.push(artist.uri);
|
||||
dispatch(getArtist(artist.uri));
|
||||
dispatch(getArtist(artist.uri, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1136,21 +1104,14 @@ 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) {
|
||||
const updatedArtist = {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri: artist.uri,
|
||||
images: response.artists.items[0].images,
|
||||
}
|
||||
dispatch(coreActions.artistLoaded(updatedArtist));
|
||||
images: [formatImages(response.artists.items[0].images)],
|
||||
}));
|
||||
}
|
||||
},
|
||||
error => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load artists',
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@ -1724,22 +1685,18 @@ export function flushLibrary() {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Playlists
|
||||
* */
|
||||
|
||||
export function getLibraryPlaylists() {
|
||||
return (dispatch, getState) => {
|
||||
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);
|
||||
} else {
|
||||
dispatch(coreActions.itemsLoaded(libraryPlaylists));
|
||||
dispatch(coreActions.itemLoaded({
|
||||
dispatch(coreActions.libraryLoaded({
|
||||
uri: 'spotify:library:playlists',
|
||||
items_uris: arrayOf('uri', libraryPlaylists),
|
||||
}));
|
||||
@ -1760,7 +1717,7 @@ export function getLibraryAlbums() {
|
||||
fetchLibraryAlbums(response.next);
|
||||
} else {
|
||||
dispatch(coreActions.itemsLoaded(libraryAlbums));
|
||||
dispatch(coreActions.itemLoaded({
|
||||
dispatch(coreActions.libraryLoaded({
|
||||
uri: 'spotify:library:albums',
|
||||
items_uris: arrayOf('uri', libraryAlbums),
|
||||
}));
|
||||
@ -1771,70 +1728,23 @@ export function getLibraryAlbums() {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Artists
|
||||
* */
|
||||
|
||||
export function getLibraryArtists() {
|
||||
return (dispatch, getState) => {
|
||||
const last_run = getState().ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR;
|
||||
let libraryArtists = [];
|
||||
const fetchLibraryArtists = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
libraryArtists = [...libraryArtists, ...formatArtists(response.artists.items)];
|
||||
if (response.next) {
|
||||
fetchLibraryArtists(response.next);
|
||||
} else {
|
||||
dispatch(coreActions.itemsLoaded(libraryArtists));
|
||||
dispatch(coreActions.libraryLoaded({
|
||||
uri: 'spotify:library:artists',
|
||||
items_uris: arrayOf('uri', libraryArtists),
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
if (!last_run) {
|
||||
dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', 'Loading Spotify artists', { next: 'me/following?type=artist&limit=50' }));
|
||||
} else if (last_run.status === 'cancelled') {
|
||||
dispatch(uiActions.resumeProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR'));
|
||||
|
||||
// We've already finished, but the status has been flushed
|
||||
} else if (last_run.status === 'finished' && !getState().spotify.library_artists_loaded_all) {
|
||||
dispatch(uiActions.startProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', 'Loading Spotify artists', { next: 'me/following?type=artist&limit=50' }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryArtistsProcessor(data) {
|
||||
return (dispatch, getState) => {
|
||||
request(dispatch, getState, data.next)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_LIBRARY_ARTISTS_LOADED',
|
||||
artists: response.artists.items,
|
||||
});
|
||||
// We got a next link, so we've got more work to be done
|
||||
if (response.artists.next) {
|
||||
const { total } = response.artists;
|
||||
const loaded = getState().spotify.library_artists.length;
|
||||
const remaining = total - loaded;
|
||||
dispatch(uiActions.updateProcess(
|
||||
'SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR',
|
||||
`Loading ${remaining} Spotify artists`,
|
||||
{
|
||||
next: response.artists.next,
|
||||
total: response.artists.total,
|
||||
remaining,
|
||||
},
|
||||
));
|
||||
dispatch(uiActions.runProcess('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR', { next: response.artists.next }));
|
||||
} else {
|
||||
dispatch(uiActions.processFinished('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR'));
|
||||
}
|
||||
},
|
||||
() => {
|
||||
dispatch(uiActions.processFinished(
|
||||
'SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR',
|
||||
{
|
||||
content: i18n(
|
||||
'errors.could_not_load_library',
|
||||
{
|
||||
name: i18n('library.artists.title'),
|
||||
provider: i18n('services.spotify.title'),
|
||||
},
|
||||
),
|
||||
level: 'error',
|
||||
},
|
||||
));
|
||||
},
|
||||
);
|
||||
fetchLibraryArtists('me/following?type=artist&limit=50');
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ let state = {
|
||||
users: {},
|
||||
tracks: {},
|
||||
items: {},
|
||||
libraries: {},
|
||||
http_streaming_enabled: false,
|
||||
http_streaming_cachebuster: null,
|
||||
http_streaming_url: `http://${window.location.hostname}:8000/mopidy`,
|
||||
@ -143,7 +144,10 @@ state = migration(state);
|
||||
const rootPersistConfig = {
|
||||
key: 'root',
|
||||
storage: localForage,
|
||||
blacklist: ['ui', 'core'],
|
||||
blacklist: [
|
||||
'ui',
|
||||
'core',
|
||||
],
|
||||
debug: window.test_mode,
|
||||
};
|
||||
|
||||
@ -158,13 +162,23 @@ const corePersistConfig = {
|
||||
'playlists',
|
||||
'users',
|
||||
'tracks',
|
||||
'libraries', // We manually hydrate this, so we can handle the rehydration of library items
|
||||
],
|
||||
};
|
||||
|
||||
const uiPersistConfig = {
|
||||
key: 'ui',
|
||||
storage: localForage,
|
||||
blacklist: ['load_queue', 'notifications'],
|
||||
blacklist: [
|
||||
'load_queue',
|
||||
'notifications',
|
||||
'context_menu',
|
||||
'current_track_transition',
|
||||
'dragger',
|
||||
'selected_tracks',
|
||||
'sidebar_open',
|
||||
'window_focus',
|
||||
],
|
||||
debug: window.test_mode,
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
|
||||
import { compact } from 'lodash';
|
||||
import {
|
||||
isObject,
|
||||
upgradeSpotifyPlaylistUri,
|
||||
@ -438,6 +438,15 @@ const formatPlaylist = function (data) {
|
||||
if (playlist.images && !playlist.images.formatted) {
|
||||
playlist.images = formatImages(playlist.images);
|
||||
}
|
||||
if (playlist.tracks) {
|
||||
if (Array.isArray(playlist.tracks)) {
|
||||
playlist.tracks = formatTracks(playlist.tracks);
|
||||
} else if (playlist.tracks.items) {
|
||||
playlist.tracks = formatTracks(playlist.tracks.items);
|
||||
} else {
|
||||
playlist.tracks = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.last_modified_date && playlist.last_modified === undefined) {
|
||||
playlist.last_modified = data.last_modified_date;
|
||||
@ -461,9 +470,8 @@ const formatPlaylist = function (data) {
|
||||
playlist.owner = {
|
||||
id: data.owner.id,
|
||||
uri: data.owner.uri,
|
||||
name: (data.owner.display_name ? data.owner.display_name : null),
|
||||
name: (data.owner.display_name || data.owner.id),
|
||||
};
|
||||
playlist.user_uri = data.owner.uri;
|
||||
}
|
||||
|
||||
// Spotify upgraded their playlists URI to remove user component (Sept 2018)
|
||||
@ -737,6 +745,10 @@ const formatGroup = function (data) {
|
||||
};
|
||||
|
||||
|
||||
const collateLibrary = (uris, itemsIndex) => {
|
||||
return compact(uris.map((uri) => itemsIndex[uri]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Collate an object with external references into a fully self-contained object
|
||||
* We merge *_uris references (ie tracks_uris) into the main object
|
||||
@ -888,6 +900,7 @@ export {
|
||||
formatClient,
|
||||
formatGroup,
|
||||
collate,
|
||||
collateLibrary,
|
||||
};
|
||||
|
||||
export default {
|
||||
@ -910,4 +923,5 @@ export default {
|
||||
formatClient,
|
||||
formatGroup,
|
||||
collate,
|
||||
collateLibrary,
|
||||
};
|
||||
|
||||
@ -190,7 +190,7 @@ const uriType = function (uri) {
|
||||
}
|
||||
return exploded[1];
|
||||
default:
|
||||
return '';
|
||||
return exploded[1];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -138,27 +138,25 @@ class Artist extends React.Component {
|
||||
inLibrary = () => {
|
||||
const { uri } = this.props;
|
||||
const libraryName = `${uriSource(uri)}_library_artists`;
|
||||
const { [libraryName]: library = [] } = this.props;
|
||||
return library.indexOf(uri) > -1;
|
||||
const { [libraryName]: { items_uris } } = this.props;
|
||||
return items_uris.indexOf(uri) > -1;
|
||||
}
|
||||
|
||||
renderOverview = () => {
|
||||
const {
|
||||
uri,
|
||||
uiActions,
|
||||
artist: {
|
||||
tracks = [],
|
||||
related_artists = [],
|
||||
} = {},
|
||||
artist,
|
||||
items,
|
||||
sort,
|
||||
sort_reverse,
|
||||
filter,
|
||||
} = this.props;
|
||||
let {
|
||||
artist: {
|
||||
albums = [],
|
||||
} = {},
|
||||
} = this.props;
|
||||
tracks,
|
||||
related_artists,
|
||||
albums,
|
||||
} = collate(artist, { albums: items });
|
||||
|
||||
if (sort && albums) {
|
||||
albums = sortItems(albums, sort, sort_reverse);
|
||||
@ -317,7 +315,7 @@ class Artist extends React.Component {
|
||||
},
|
||||
);
|
||||
|
||||
const thumbnails = artist.images && artist.images.map(
|
||||
const thumbnails = artist.images && Array.isArray(artist.images) && artist.images.map(
|
||||
(image) => {
|
||||
if (!image.huge) return null;
|
||||
return (
|
||||
@ -532,11 +530,10 @@ const mapStateToProps = (state, ownProps) => {
|
||||
theme: state.ui.theme,
|
||||
slim_mode: state.ui.slim_mode,
|
||||
load_queue: state.ui.load_queue,
|
||||
artist: (state.core.items[uri] !== undefined ? state.core.items[uri] : false),
|
||||
tracks: state.core.tracks,
|
||||
artists: state.core.artists,
|
||||
spotify_library_artists: state.spotify.library_artists,
|
||||
local_library_artists: state.mopidy.library_artists,
|
||||
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: [] },
|
||||
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),
|
||||
|
||||
@ -318,13 +318,13 @@ class Playlist extends React.Component {
|
||||
<Icon type="fontawesome" name={sourceIcon(playlist.uri)} />
|
||||
</li>
|
||||
)}
|
||||
{playlist.user_uri && (
|
||||
{playlist.user && (
|
||||
<li>
|
||||
<URILink
|
||||
type="user"
|
||||
uri={playlist.user_uri}
|
||||
uri={playlist.user.uri}
|
||||
>
|
||||
{playlist.user ? playlist.user.name : getFromUri('userid', playlist.user_uri)}
|
||||
{playlist.user.name}
|
||||
</URILink>
|
||||
</li>
|
||||
)}
|
||||
@ -349,16 +349,20 @@ class Playlist extends React.Component {
|
||||
|
||||
{this.renderActions()}
|
||||
|
||||
<section className="list-wrapper">
|
||||
<TrackList
|
||||
uri={playlist.uri}
|
||||
className="playlist-track-list"
|
||||
track_context={context}
|
||||
tracks={playlist.tracks}
|
||||
removeTracks={this.removeTracks}
|
||||
reorderTracks={this.reorderTracks}
|
||||
/>
|
||||
</section>
|
||||
{isLoading(load_queue, [`spotify_playlists/${playlist_id}/tracks`]) ? (
|
||||
<Loader body loading />
|
||||
) : (
|
||||
<section className="list-wrapper">
|
||||
<TrackList
|
||||
uri={playlist.uri}
|
||||
className="playlist-track-list"
|
||||
track_context={context}
|
||||
tracks={playlist.tracks}
|
||||
removeTracks={this.removeTracks}
|
||||
reorderTracks={this.reorderTracks}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
uriSource,
|
||||
} from '../../util/helpers';
|
||||
import { sortItems, applyFilter } from '../../util/arrays';
|
||||
import { collate } from '../../util/format';
|
||||
import { collateLibrary, collate } from '../../util/format';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
|
||||
class LibraryAlbums extends React.Component {
|
||||
@ -355,9 +355,9 @@ const mapStateToProps = (state) => ({
|
||||
mopidy_uri_schemes: state.mopidy.uri_schemes,
|
||||
load_queue: state.ui.load_queue,
|
||||
items: state.core.items,
|
||||
mopidy_library: state.core.items['mopidy:library:albums'] || { items_uris: [] },
|
||||
spotify_library: state.core.items['spotify:library:albums'] || { items_uris: [] },
|
||||
google_library: state.core.items['google:library:albums'] || { items_uris: [] },
|
||||
mopidy_library: state.core.libraries['mopidy:library:albums'] || { items_uris: [] },
|
||||
spotify_library: state.core.libraries['spotify:library:albums'] || { items_uris: [] },
|
||||
google_library: state.core.libraries['google:library:albums'] || { items_uris: [] },
|
||||
google_available: (state.mopidy.uri_schemes && state.mopidy.uri_schemes.includes('gmusic:')),
|
||||
spotify_available: state.spotify.access_token,
|
||||
view: state.ui.library_albums_view,
|
||||
|
||||
@ -10,14 +10,13 @@ import FilterField from '../../components/Fields/FilterField';
|
||||
import LazyLoadListener from '../../components/LazyLoadListener';
|
||||
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 googleActions from '../../services/google/actions';
|
||||
import * as coreActions from '../../services/core/actions';
|
||||
import {
|
||||
uriSource,
|
||||
} from '../../util/helpers';
|
||||
import { sortItems, applyFilter } from '../../util/arrays';
|
||||
import { I18n, i18n } from '../../locale';
|
||||
import { collate, collateLibrary } from '../../util/format';
|
||||
|
||||
class LibraryArtists extends React.Component {
|
||||
constructor(props) {
|
||||
@ -65,51 +64,44 @@ class LibraryArtists extends React.Component {
|
||||
getMopidyLibrary = () => {
|
||||
const {
|
||||
source,
|
||||
mopidy_library_artists,
|
||||
mopidyActions: {
|
||||
getLibraryArtists,
|
||||
coreActions: {
|
||||
loadLibrary,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (source !== 'local' && source !== 'all') return;
|
||||
if (mopidy_library_artists) return;
|
||||
|
||||
getLibraryArtists();
|
||||
loadLibrary('mopidy:library:artists');
|
||||
};
|
||||
|
||||
getGoogleLibrary = () => {
|
||||
const {
|
||||
source,
|
||||
google_available,
|
||||
google_library_artists,
|
||||
googleActions: {
|
||||
getLibraryArtists,
|
||||
coreActions: {
|
||||
loadLibrary,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (!google_available) return;
|
||||
if (source !== 'google' && source !== 'all') return;
|
||||
if (google_library_artists) return;
|
||||
|
||||
getLibraryArtists();
|
||||
loadLibrary('google:library:artists');
|
||||
};
|
||||
|
||||
getSpotifyLibrary = () => {
|
||||
const {
|
||||
source,
|
||||
spotify_available,
|
||||
spotify_library_artists_status,
|
||||
spotifyActions: {
|
||||
getLibraryArtists,
|
||||
coreActions: {
|
||||
loadLibrary,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (!spotify_available) return;
|
||||
if (source !== 'spotify' && source !== 'all') return;
|
||||
if (spotify_library_artists_status === 'finished') return;
|
||||
if (spotify_library_artists_status === 'started') return;
|
||||
|
||||
getLibraryArtists();
|
||||
loadLibrary('spotify:library:artists');
|
||||
};
|
||||
|
||||
handleContextMenu(e, item) {
|
||||
@ -144,70 +136,41 @@ class LibraryArtists extends React.Component {
|
||||
this.props.uiActions.set(data);
|
||||
}
|
||||
|
||||
renderView() {
|
||||
let artists = [];
|
||||
renderView = () => {
|
||||
const {
|
||||
spotify_library,
|
||||
google_library,
|
||||
mopidy_library,
|
||||
items,
|
||||
source,
|
||||
sort,
|
||||
sort_reverse,
|
||||
view,
|
||||
} = this.props;
|
||||
const {
|
||||
limit,
|
||||
filter,
|
||||
} = this.state;
|
||||
|
||||
// Mopidy library items
|
||||
if (this.props.mopidy_library_artists && (this.props.source == 'all' || this.props.source == 'local')) {
|
||||
for (uri of this.props.mopidy_library_artists) {
|
||||
// Construct item placeholder. This is used as Mopidy needs to
|
||||
// lookup ref objects to get the full object which can take some time
|
||||
var source = uriSource(uri);
|
||||
var artist = {
|
||||
uri,
|
||||
source,
|
||||
};
|
||||
let artists = [
|
||||
...(source === 'all' || source === 'spotify' ? collate(spotify_library, { items }).items : []),
|
||||
...(source === 'all' || source === 'google' ? collate(google_library, { items }).items : []),
|
||||
...(source === 'all' || source === 'local' ? collate(mopidy_library, { items }).items : []),
|
||||
];
|
||||
|
||||
if (this.props.artists.hasOwnProperty(uri)) {
|
||||
artist = this.props.artists[uri];
|
||||
}
|
||||
|
||||
artists.push(artist);
|
||||
}
|
||||
if (sort) {
|
||||
artists = sortItems(artists, sort, sort_reverse);
|
||||
}
|
||||
|
||||
// Google library items
|
||||
if (this.props.google_library_artists && (this.props.source == 'all' || this.props.source == 'google')) {
|
||||
for (uri of this.props.google_library_artists) {
|
||||
// Construct item placeholder. This is used as Mopidy needs to
|
||||
// lookup ref objects to get the full object which can take some time
|
||||
var source = uriSource(uri);
|
||||
var artist = {
|
||||
uri,
|
||||
source,
|
||||
};
|
||||
|
||||
if (this.props.artists.hasOwnProperty(uri)) {
|
||||
artist = this.props.artists[uri];
|
||||
}
|
||||
|
||||
artists.push(artist);
|
||||
}
|
||||
}
|
||||
|
||||
// Spotify library items
|
||||
if (this.props.spotify_library_artists && (this.props.source == 'all' || this.props.source == 'spotify')) {
|
||||
for (let i = 0; i < this.props.spotify_library_artists.length; i++) {
|
||||
var uri = this.props.spotify_library_artists[i];
|
||||
if (this.props.artists.hasOwnProperty(uri)) {
|
||||
artists.push(this.props.artists[uri]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.sort) {
|
||||
artists = sortItems(artists, this.props.sort, this.props.sort_reverse);
|
||||
}
|
||||
|
||||
if (this.state.filter !== '') {
|
||||
artists = applyFilter('name', this.state.filter, artists);
|
||||
if (filter !== '') {
|
||||
artists = applyFilter('name', filter, artists);
|
||||
}
|
||||
|
||||
// Apply our lazy-load-rendering
|
||||
const total_artists = artists.length;
|
||||
artists = artists.slice(0, this.state.limit);
|
||||
artists = artists.slice(0, limit);
|
||||
|
||||
if (this.props.view == 'list') {
|
||||
if (view === 'list') {
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
@ -220,8 +183,8 @@ class LibraryArtists extends React.Component {
|
||||
link_prefix="/artist/"
|
||||
/>
|
||||
<LazyLoadListener
|
||||
loadKey={total_artists > this.state.limit ? this.state.limit : total_artists}
|
||||
showLoader={this.state.limit < total_artists}
|
||||
loadKey={total_artists > limit ? limit : total_artists}
|
||||
showLoader={limit < total_artists}
|
||||
loadMore={() => this.loadMore()}
|
||||
/>
|
||||
</section>
|
||||
@ -234,15 +197,20 @@ class LibraryArtists extends React.Component {
|
||||
artists={artists}
|
||||
/>
|
||||
<LazyLoadListener
|
||||
loadKey={total_artists > this.state.limit ? this.state.limit : total_artists}
|
||||
showLoader={this.state.limit < total_artists}
|
||||
loadKey={total_artists > limit ? limit : total_artists}
|
||||
showLoader={limit < total_artists}
|
||||
loadMore={() => this.loadMore()}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
render = () => {
|
||||
const {
|
||||
spotify_available,
|
||||
google_available,
|
||||
} = this.props;
|
||||
|
||||
const source_options = [
|
||||
{
|
||||
value: 'all',
|
||||
@ -254,14 +222,14 @@ class LibraryArtists extends React.Component {
|
||||
},
|
||||
];
|
||||
|
||||
if (this.props.spotify_available) {
|
||||
if (spotify_available) {
|
||||
source_options.push({
|
||||
value: 'spotify',
|
||||
label: i18n('services.spotify.title'),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.google_available) {
|
||||
if (google_available) {
|
||||
source_options.push({
|
||||
value: 'google',
|
||||
label: i18n('services.google.title'),
|
||||
@ -347,13 +315,12 @@ class LibraryArtists extends React.Component {
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
mopidy_uri_schemes: state.mopidy.uri_schemes,
|
||||
mopidy_library_artists: state.mopidy.library_artists,
|
||||
google_available: (state.mopidy.uri_schemes && state.mopidy.uri_schemes.includes('gmusic:')),
|
||||
google_library_artists: state.google.library_artists,
|
||||
spotify_available: state.spotify.access_token,
|
||||
spotify_library_artists: state.spotify.library_artists,
|
||||
spotify_library_artists_status: (state.ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR !== undefined ? state.ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR.status : null),
|
||||
artists: state.core.artists,
|
||||
items: state.core.items,
|
||||
mopidy_library: state.core.libraries['mopidy:library:artists'] || { items_uris: [] },
|
||||
spotify_library: state.core.libraries['spotify:library:artists'] || { items_uris: [] },
|
||||
google_library: state.core.libraries['google:library:artists'] || { items_uris: [] },
|
||||
source: (state.ui.library_artists_source ? state.ui.library_artists_source : 'all'),
|
||||
sort: (state.ui.library_artists_sort ? state.ui.library_artists_sort : null),
|
||||
sort_reverse: (state.ui.library_artists_sort_reverse ? state.ui.library_artists_sort_reverse : false),
|
||||
@ -362,9 +329,7 @@ const mapStateToProps = (state) => ({
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
googleActions: bindActionCreators(googleActions, dispatch),
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LibraryArtists);
|
||||
|
||||
@ -304,8 +304,8 @@ const mapStateToProps = (state) => {
|
||||
mopidy_uri_schemes: state.mopidy.uri_schemes,
|
||||
spotify_available: state.spotify.access_token,
|
||||
items: state.core.items,
|
||||
mopidy_library: state.core.items['mopidy:library:playlists'] || { items_uris: [] },
|
||||
spotify_library: state.core.items['spotify:library:playlists'] || { items_uris: [] },
|
||||
mopidy_library: state.core.libraries['mopidy:library:playlists'] || { items_uris: [] },
|
||||
spotify_library: state.core.libraries['spotify:library:playlists'] || { items_uris: [] },
|
||||
load_queue: state.ui.load_queue,
|
||||
me_id: (state.spotify.me ? state.spotify.me.id : false),
|
||||
view: state.ui.library_playlists_view,
|
||||
|
||||
Reference in New Issue
Block a user