Ditching load_queue flags, instead using simple loading prop on item itself (less capable but greater performance and predictability) and fixes #774
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
@ -215,6 +215,14 @@ export function removeFromLibrary(uri, itemUri) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setLoading(uri, loading) {
|
||||
return {
|
||||
type: 'SET_LOADING',
|
||||
uri,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Record loaders
|
||||
*
|
||||
|
||||
@ -360,6 +360,7 @@ const CoreMiddleware = (function () {
|
||||
options,
|
||||
});
|
||||
});
|
||||
next(action);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -649,8 +650,10 @@ const CoreMiddleware = (function () {
|
||||
|
||||
case 'QUEUE_LOADED':
|
||||
store.dispatch(coreActions.tracksLoaded(action.tracks));
|
||||
action.tracks = formatTracks(action.tracks);
|
||||
next(action);
|
||||
next({
|
||||
...action,
|
||||
tracks: formatTracks(action.tracks),
|
||||
});
|
||||
break;
|
||||
|
||||
case 'ITEMS_LOADED':
|
||||
@ -658,11 +661,12 @@ const CoreMiddleware = (function () {
|
||||
action.items.forEach((item) => {
|
||||
mergedItems.push({
|
||||
...core.items[item.uri] || {},
|
||||
loading: false, // Action can still override this
|
||||
...item,
|
||||
});
|
||||
});
|
||||
|
||||
store.dispatch(uiActions.stopLoading(arrayOf('uri', action.items)));
|
||||
store.dispatch(uiActions.stopLoading(arrayOf('uri', action.items))); // TODO Deprecate
|
||||
store.dispatch(coreActions.updateColdStore(mergedItems));
|
||||
next({
|
||||
...action,
|
||||
|
||||
@ -60,6 +60,32 @@ export default function reducer(core = {}, action) {
|
||||
* These actions are only ever called by middleware after we've digested one more many assets
|
||||
* and appended to their relevant index.
|
||||
* */
|
||||
case 'LOAD_ITEMS': {
|
||||
const items = { ...core.items };
|
||||
action.uris.forEach((uri) => {
|
||||
items[uri] = {
|
||||
...core.items[uri] || { uri },
|
||||
loading: true,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...core,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET_LOADING': {
|
||||
return {
|
||||
...core,
|
||||
items: {
|
||||
...core.items,
|
||||
[action.uri]: {
|
||||
...core.items[action.uri] || { uri: action.uri },
|
||||
loading: action.loading,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'ITEM_LOADED':
|
||||
return {
|
||||
@ -101,17 +127,14 @@ export default function reducer(core = {}, action) {
|
||||
};
|
||||
}
|
||||
|
||||
case 'USER_PLAYLISTS_LOADED':
|
||||
var users = { ...core.users };
|
||||
var existing_playlists_uris = [];
|
||||
case 'USER_PLAYLISTS_LOADED': {
|
||||
const users = { ...core.users };
|
||||
let existing_playlists_uris = [];
|
||||
if (users[action.uri] && users[action.uri].playlists_uris) {
|
||||
existing_playlists_uris = users[action.uri].playlists_uris;
|
||||
existing_playlists_uris = users[action.uri].playlists_uris;
|
||||
}
|
||||
|
||||
var playlists_uris = [...existing_playlists_uris, ...arrayOf('uri', action.playlists)];
|
||||
|
||||
var user = {
|
||||
|
||||
const playlists_uris = [...existing_playlists_uris, ...arrayOf('uri', action.playlists)];
|
||||
const user = {
|
||||
...users[action.uri],
|
||||
playlists_uris,
|
||||
playlists_more: action.more,
|
||||
@ -119,6 +142,7 @@ export default function reducer(core = {}, action) {
|
||||
};
|
||||
users[action.uri] = user;
|
||||
return { ...core, users };
|
||||
}
|
||||
|
||||
case 'RESTORE_LIBRARY_FROM_COLD_STORE':
|
||||
const libraries = { ...core.libraries };
|
||||
|
||||
@ -1242,6 +1242,12 @@ const MopidyMiddleware = (function () {
|
||||
type: 'playlist',
|
||||
});
|
||||
if (response.tracks && fetchTracks) {
|
||||
store.dispatch(coreActions.itemLoaded({
|
||||
...playlist,
|
||||
tracks: undefined,
|
||||
loading: 'tracks',
|
||||
}));
|
||||
|
||||
request(store, 'library.lookup', { uris: arrayOf('uri', response.tracks) })
|
||||
.then((tracksResponse) => {
|
||||
const tracks = response.tracks.map((simpleTrack) => {
|
||||
|
||||
@ -950,7 +950,10 @@ export function getArtist(uri, { full, forceRefetch } = {}) {
|
||||
}).then(
|
||||
(response) => {
|
||||
const artist = formatArtist(response);
|
||||
dispatch(coreActions.itemLoaded(artist));
|
||||
dispatch(coreActions.itemLoaded({
|
||||
...artist,
|
||||
loading: full ? 'albums' : false,
|
||||
}));
|
||||
dispatch(lastfmActions.getArtist(uri, artist.name, artist.mbid));
|
||||
},
|
||||
);
|
||||
@ -959,16 +962,20 @@ export function getArtist(uri, { full, forceRefetch } = {}) {
|
||||
if (full) {
|
||||
// All albums (gets all pages, may take some time to iterate them all)
|
||||
let albums = [];
|
||||
let loading = 'albums';
|
||||
const fetchAlbums = (endpoint) => request({
|
||||
dispatch, getState, endpoint, uri,
|
||||
})
|
||||
.then((response) => {
|
||||
albums = [...albums, ...formatAlbums(response.items)];
|
||||
if (response.next) {
|
||||
loading = 'albums';
|
||||
fetchAlbums(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
} else {
|
||||
loading = false;
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
loading,
|
||||
albums_uris: arrayOf('uri', albums),
|
||||
}));
|
||||
dispatch(coreActions.itemsLoaded(albums));
|
||||
@ -987,6 +994,7 @@ export function getArtist(uri, { full, forceRefetch } = {}) {
|
||||
(response) => {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
loading,
|
||||
tracks: formatTracks(response.tracks),
|
||||
}));
|
||||
},
|
||||
@ -1002,6 +1010,7 @@ export function getArtist(uri, { full, forceRefetch } = {}) {
|
||||
(response) => {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
loading,
|
||||
related_artists: formatArtists(response.artists),
|
||||
}));
|
||||
},
|
||||
@ -1389,6 +1398,7 @@ export function getPlaylist(uri, options = {}) {
|
||||
// to accurately identify whether we've loaded *ALL* the tracks. Without this, it
|
||||
// doesn't know if we've loaded all tracks, or just the first page.
|
||||
tracks: null,
|
||||
loading: full ? 'tracks' : null,
|
||||
}));
|
||||
|
||||
if (full) {
|
||||
|
||||
@ -315,6 +315,7 @@ const formatAlbum = function (data) {
|
||||
const album = { type: 'album' };
|
||||
const fields = [
|
||||
'uri',
|
||||
'loading',
|
||||
'in_library',
|
||||
'provider',
|
||||
'name',
|
||||
@ -388,6 +389,7 @@ const formatArtist = function (data) {
|
||||
const artist = { type: 'artist' };
|
||||
const fields = [
|
||||
'uri',
|
||||
'loading',
|
||||
'in_library',
|
||||
'provider',
|
||||
'mbid',
|
||||
@ -448,6 +450,7 @@ const formatPlaylist = function (data) {
|
||||
const playlist = { type: 'playlist' };
|
||||
const fields = [
|
||||
'uri',
|
||||
'loading',
|
||||
'in_library',
|
||||
'snapshot_id',
|
||||
'provider',
|
||||
@ -535,6 +538,7 @@ const formatUser = function (data) {
|
||||
const user = { type: 'user' };
|
||||
const fields = [
|
||||
'id',
|
||||
'loading',
|
||||
'in_library',
|
||||
'uri',
|
||||
'provider',
|
||||
@ -589,8 +593,8 @@ const formatUser = function (data) {
|
||||
const formatTrack = function (data) {
|
||||
const track = { type: 'track' };
|
||||
const fields = [
|
||||
'loading',
|
||||
'uri',
|
||||
'loading',
|
||||
'in_library',
|
||||
'is_playable',
|
||||
'tlid',
|
||||
|
||||
@ -90,6 +90,7 @@ const ensureLoaded = ({
|
||||
// Forced refetch bypasses everything
|
||||
if (forceRefetch) {
|
||||
console.info(`Force-refetching "${uri}"`);
|
||||
store.dispatch(coreActions.setLoading(uri, true));
|
||||
fetch();
|
||||
return;
|
||||
}
|
||||
@ -125,6 +126,7 @@ const ensureLoaded = ({
|
||||
// What about in the coldstore?
|
||||
localForage.getItem(uri).then((restoredItem) => {
|
||||
if (!restoredItem || missingDependents(restoredItem).length > 0) {
|
||||
store.dispatch(coreActions.setLoading(uri, true));
|
||||
fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ const getLibraries = (state) => state.core.libraries;
|
||||
const getSearchResults = (state) => state.core.search_results;
|
||||
const getGridGlowEnabled = (state) => state.ui.grid_glow_enabled;
|
||||
const getDragger = (state) => state.ui.dragger;
|
||||
const getSorts = (state) => state.ui.sort;
|
||||
|
||||
const makeItemSelector = (uri) => createSelector(
|
||||
[getItems],
|
||||
@ -192,6 +193,14 @@ const makeProvidersSelector = (context) => createSelector(
|
||||
return providers[context].filter((p) => schemes.indexOf(p.scheme) > -1);
|
||||
},
|
||||
);
|
||||
const makeSortSelector = (key, defaultField = 'sort_id') => createSelector(
|
||||
[getSorts],
|
||||
(sorts) => [
|
||||
sorts[key]?.field || defaultField,
|
||||
sorts[key]?.reverse || false,
|
||||
],
|
||||
);
|
||||
|
||||
const getSortSelector = (state, key, defaultField = 'sort_id') => {
|
||||
const result = state.ui.sort[key];
|
||||
return [
|
||||
@ -215,5 +224,6 @@ export {
|
||||
queueHistorySelector,
|
||||
makeProvidersSelector,
|
||||
getProvider,
|
||||
makeSortSelector,
|
||||
getSortSelector,
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import ErrorMessage from '../components/ErrorMessage';
|
||||
import TrackList from '../components/TrackList';
|
||||
@ -18,41 +18,37 @@ import { i18n, I18n } from '../locale';
|
||||
import * as coreActions from '../services/core/actions';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import { uriSource } from '../util/helpers';
|
||||
import Button from '../components/Button';
|
||||
import { makeLoadingSelector, makeItemSelector, getSortSelector } from '../util/selectors';
|
||||
import { makeItemSelector, makeSortSelector } from '../util/selectors';
|
||||
import { applyFilter, sortItems } from '../util/arrays';
|
||||
import { decodeUri } from '../util/format';
|
||||
|
||||
const SORT_KEY = 'album_tracks';
|
||||
|
||||
const Album = ({
|
||||
uri,
|
||||
album: albumProp,
|
||||
sortField,
|
||||
sortReverse,
|
||||
loading,
|
||||
slim_mode,
|
||||
coreActions: {
|
||||
loadAlbum,
|
||||
},
|
||||
lastfmActions: {
|
||||
getAlbum,
|
||||
},
|
||||
uiActions: {
|
||||
const Album = () => {
|
||||
const { loadAlbum } = coreActions;
|
||||
const { getAlbum } = lastfmActions;
|
||||
const { playURIs } = mopidyActions;
|
||||
const {
|
||||
setSort,
|
||||
setWindowTitle,
|
||||
showContextMenu,
|
||||
hideContextMenu,
|
||||
},
|
||||
mopidyActions: {
|
||||
playURIs,
|
||||
},
|
||||
}) => {
|
||||
} = uiActions;
|
||||
const dispatch = useDispatch();
|
||||
const { uri: encodedUri } = useParams();
|
||||
const uri = decodeUri(encodedUri);
|
||||
|
||||
const itemSelector = makeItemSelector(uri);
|
||||
const album = useSelector(itemSelector);
|
||||
|
||||
const sortSelector = makeSortSelector(SORT_KEY, 'disc_track');
|
||||
const [sortField, sortReverse] = useSelector(sortSelector);
|
||||
const slim_mode = useSelector((state) => state.ui.slim_mode);
|
||||
|
||||
const [filter, setFilter] = useState('');
|
||||
const [album, setAlbum] = useState({});
|
||||
|
||||
let tracks = album?.tracks || [];
|
||||
if (sortField && tracks) tracks = sortItems(tracks, sortField, sortReverse);
|
||||
@ -60,21 +56,16 @@ const Album = ({
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (uri) {
|
||||
loadAlbum(uri, { full: true });
|
||||
}
|
||||
if (uri) dispatch(loadAlbum(uri, { full: true }));
|
||||
},
|
||||
[uri],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (albumProp && !album) {
|
||||
if (albumProp.artists && albumProp.wiki === undefined) {
|
||||
getAlbum(albumProp.uri, albumProp.artists[0].name, albumProp.name);
|
||||
}
|
||||
};
|
||||
setAlbum(albumProp);
|
||||
}, [albumProp]);
|
||||
if (album?.artists && album?.wiki === undefined) {
|
||||
dispatch(getAlbum(album.uri, album.artists[0].name, album.name));
|
||||
}
|
||||
}, [album]);
|
||||
|
||||
useEffect(() => {
|
||||
if (album) {
|
||||
@ -87,7 +78,7 @@ const Album = ({
|
||||
}
|
||||
}, [album]);
|
||||
|
||||
if (loading) {
|
||||
if (album?.loading) {
|
||||
return <Loader body loading />;
|
||||
}
|
||||
|
||||
@ -101,12 +92,14 @@ const Album = ({
|
||||
);
|
||||
}
|
||||
|
||||
const handleContextMenu = (e) => showContextMenu({
|
||||
e,
|
||||
context: 'album',
|
||||
items: [album],
|
||||
uris: [uri],
|
||||
});
|
||||
const handleContextMenu = (e) => dispatch(
|
||||
showContextMenu({
|
||||
e,
|
||||
context: 'album',
|
||||
items: [album],
|
||||
uris: [uri],
|
||||
}),
|
||||
);
|
||||
|
||||
const onChangeSort = (field) => {
|
||||
let reverse = false;
|
||||
@ -114,8 +107,8 @@ const Album = ({
|
||||
reverse = !sortReverse;
|
||||
}
|
||||
|
||||
setSort(SORT_KEY, field, reverse);
|
||||
hideContextMenu();
|
||||
dispatch(setSort(SORT_KEY, field, reverse));
|
||||
dispatch(hideContextMenu());
|
||||
};
|
||||
|
||||
const sort_options = [
|
||||
@ -189,7 +182,7 @@ const Album = ({
|
||||
<div className="actions">
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => playURIs([uri], uri)}
|
||||
onClick={() => dispatch(playURIs([uri], uri))}
|
||||
tracking={{ category: 'Album', action: 'Play' }}
|
||||
>
|
||||
<I18n path="actions.play" />
|
||||
@ -247,39 +240,4 @@ const Album = ({
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const uri = decodeUri(ownProps.match.params.uri);
|
||||
const itemSelector = makeItemSelector(uri);
|
||||
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!me\/albums).)*$', '^((?!followers).)*$']);
|
||||
const [sortField, sortReverse] = getSortSelector(state, SORT_KEY, 'disc_track');
|
||||
|
||||
return {
|
||||
uri,
|
||||
slim_mode: state.ui.slim_mode,
|
||||
theme: state.ui.theme,
|
||||
album: itemSelector(state),
|
||||
loading: loadingSelector(state),
|
||||
spotify_library_albums: state.spotify.library_albums,
|
||||
local_library_albums: state.mopidy.library_albums,
|
||||
spotify_authorized: state.spotify.authorization,
|
||||
sortField,
|
||||
sortReverse,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
lastfmActions: bindActionCreators(lastfmActions, dispatch),
|
||||
});
|
||||
|
||||
export {
|
||||
Album,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Album);
|
||||
export default Album;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { Route, Switch, useParams, useHistory } from 'react-router-dom';
|
||||
import ErrorMessage from '../components/ErrorMessage';
|
||||
import Link from '../components/Link';
|
||||
import Thumbnail from '../components/Thumbnail';
|
||||
@ -23,46 +23,43 @@ import { encodeUri, decodeUri } from '../util/format';
|
||||
import { arrayOf } from '../util/arrays';
|
||||
import { i18n, I18n } from '../locale';
|
||||
import Button from '../components/Button';
|
||||
import {
|
||||
makeItemSelector,
|
||||
makeLoadingSelector,
|
||||
} from '../util/selectors';
|
||||
import { makeItemSelector } from '../util/selectors';
|
||||
|
||||
const Artist = ({
|
||||
uri,
|
||||
loading,
|
||||
history,
|
||||
artist: artistProp,
|
||||
albums,
|
||||
coreActions: {
|
||||
loadArtist,
|
||||
},
|
||||
uiActions: {
|
||||
const Artist = () => {
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const { uri: encodedUri } = useParams();
|
||||
const uri = decodeUri(encodedUri);
|
||||
|
||||
const artistSelector = makeItemSelector(uri);
|
||||
const artist = useSelector(artistSelector);
|
||||
const albumSelector = makeItemSelector(artist?.albums_uris || []);
|
||||
const albums = useSelector(albumSelector);
|
||||
|
||||
const loading = artist?.loading && artist.loading !== 'albums';
|
||||
|
||||
const { loadArtist } = coreActions;
|
||||
const { playURIs } = mopidyActions;
|
||||
const {
|
||||
setWindowTitle,
|
||||
showContextMenu,
|
||||
createNotification,
|
||||
},
|
||||
mopidyActions: {
|
||||
playURIs,
|
||||
},
|
||||
}) => {
|
||||
const [artist, setArtist] = useState({});
|
||||
} = uiActions;
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (uri) loadArtist(uri, { full: true });
|
||||
if (uri) dispatch(loadArtist(uri, { full: true }));
|
||||
},
|
||||
[uri],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (artistProp) {
|
||||
setWindowTitle(i18n('artist.title_window', { name: artistProp.name }));
|
||||
if (artist) {
|
||||
dispatch(setWindowTitle(i18n('artist.title_window', { name: artist.name })));
|
||||
} else {
|
||||
setWindowTitle(i18n('artist.title'));
|
||||
dispatch(setWindowTitle(i18n('artist.title')));
|
||||
}
|
||||
setArtist(artistProp);
|
||||
}, [artistProp]);
|
||||
}, [artist]);
|
||||
|
||||
const onPlayAll = () => {
|
||||
const {
|
||||
@ -71,20 +68,22 @@ const Artist = ({
|
||||
} = artist;
|
||||
|
||||
if ((!albums_uris || !albums_uris.length) && (!tracks || !tracks.length)) {
|
||||
createNotification({ content: i18n('errors.no_results'), level: 'warning' });
|
||||
dispatch(createNotification({ content: i18n('errors.no_results'), level: 'warning' }));
|
||||
return;
|
||||
}
|
||||
|
||||
playURIs(arrayOf('uri', tracks) || albums_uris, uri);
|
||||
}
|
||||
dispatch(playURIs(arrayOf('uri', tracks) || albums_uris, uri));
|
||||
};
|
||||
|
||||
const handleContextMenu = (e) => showContextMenu({
|
||||
e,
|
||||
context: 'artist',
|
||||
items: [artist],
|
||||
uris: [uri],
|
||||
tracklist_uri: uri,
|
||||
});
|
||||
const handleContextMenu = (e) => dispatch(
|
||||
showContextMenu({
|
||||
e,
|
||||
context: 'artist',
|
||||
items: [artist],
|
||||
uris: [uri],
|
||||
tracklist_uri: uri,
|
||||
}),
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <Loader body loading />;
|
||||
@ -202,32 +201,4 @@ const Artist = ({
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const uri = decodeUri(props.match.params.uri);
|
||||
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!albums).)*$', '^((?!related-artists).)*$', '^((?!top-tracks).)*$', '^((?!following).)*$']);
|
||||
const artistSelector = makeItemSelector(uri);
|
||||
const artist = artistSelector(state);
|
||||
let albums = null;
|
||||
if (artist && artist.albums_uris) {
|
||||
const albumsSelector = makeItemSelector(artist.albums_uris);
|
||||
albums = albumsSelector(state);
|
||||
}
|
||||
|
||||
return {
|
||||
uri,
|
||||
artist,
|
||||
albums,
|
||||
loading: loadingSelector(state),
|
||||
theme: state.ui.theme,
|
||||
slim_mode: state.ui.slim_mode,
|
||||
spotify_authorized: state.spotify.authorization,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Artist);
|
||||
export default Artist;
|
||||
|
||||
@ -9,11 +9,12 @@ import RelatedArtists from '../../components/RelatedArtists';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import { sortItems, applyFilter } from '../../util/arrays';
|
||||
import { encodeUri } from '../../util/format';
|
||||
import { getSortSelector } from '../../util/selectors';
|
||||
import { makeSortSelector } from '../../util/selectors';
|
||||
import {
|
||||
hideContextMenu,
|
||||
setSort,
|
||||
} from '../../services/ui/actions';
|
||||
import Loader from '../../components/Loader';
|
||||
|
||||
const SORT_KEY = 'artist_albums';
|
||||
|
||||
@ -23,16 +24,16 @@ export default ({
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [search, setSearch] = useState('');
|
||||
const [type, setType] = useState('');
|
||||
const [sortField, sortReverse] = useSelector(
|
||||
(state) => getSortSelector(state, SORT_KEY, null),
|
||||
);
|
||||
const [type, setType] = useState(null);
|
||||
const sortSelector = makeSortSelector(SORT_KEY, null);
|
||||
const [sortField, sortReverse] = useSelector(sortSelector);
|
||||
const {
|
||||
uri,
|
||||
tracks,
|
||||
related_artists,
|
||||
} = artist;
|
||||
let albums = albumsProp;
|
||||
const loadingAlbums = artist?.loading && artist.loading === 'albums';
|
||||
|
||||
const onSortChange = (field) => {
|
||||
let reverse = false;
|
||||
@ -125,6 +126,7 @@ export default ({
|
||||
<div className="albums">
|
||||
<h4>
|
||||
<I18n path="artist.overview.albums" count={albums ? albums.length : 0} />
|
||||
{loadingAlbums && <Loader loading mini />}
|
||||
<div className="actions-wrapper">
|
||||
<FilterField
|
||||
initialValue={search}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import ErrorMessage from '../components/ErrorMessage';
|
||||
import Button from '../components/Button';
|
||||
@ -21,7 +22,7 @@ import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import { uriSource } from '../util/helpers';
|
||||
import { i18n, I18n } from '../locale';
|
||||
import { makeItemSelector, makeLoadingSelector, getSortSelector } from '../util/selectors';
|
||||
import { makeItemSelector, makeSortSelector } from '../util/selectors';
|
||||
import { sortItems, applyFilter } from '../util/arrays';
|
||||
import { decodeUri, encodeUri } from '../util/format';
|
||||
|
||||
@ -118,16 +119,7 @@ const Actions = ({
|
||||
}
|
||||
|
||||
const Playlist = ({
|
||||
loading,
|
||||
loading_tracks,
|
||||
slim_mode,
|
||||
history,
|
||||
uri,
|
||||
encodedUri,
|
||||
name,
|
||||
playlist: playlistProp,
|
||||
sortField,
|
||||
sortReverse,
|
||||
coreActions: {
|
||||
loadPlaylist,
|
||||
reorderPlaylistTracks,
|
||||
@ -144,8 +136,16 @@ const Playlist = ({
|
||||
playPlaylist,
|
||||
},
|
||||
}) => {
|
||||
const [playlist, setPlaylist] = useState({});
|
||||
const history = useHistory();
|
||||
const { uri: encodedUri, name } = useParams();
|
||||
const uri = decodeUri(encodedUri);
|
||||
const [filter, setFilter] = useState('');
|
||||
const sortSelector = makeSortSelector(SORT_KEY);
|
||||
const [sortField, sortReverse] = useSelector(sortSelector);
|
||||
const itemSelector = makeItemSelector(uri);
|
||||
const playlist = useSelector(itemSelector);
|
||||
const loading = playlist?.loading && playlist?.loading !== 'tracks';
|
||||
const loadingTracks = playlist?.loading === 'tracks';
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@ -155,19 +155,18 @@ const Playlist = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (playlistProp && playlist && playlistProp.moved_to !== playlist.moved_to) {
|
||||
history.push(`/playlist/${encodeUri(playlistProp.moved_to)}`);
|
||||
if (playlist && playlist.moved_to) {
|
||||
history.push(`/playlist/${encodeUri(playlist.moved_to)}/${encodeURIComponent(playlist.name.replace('%', ''))}`);
|
||||
}
|
||||
}, [playlistProp])
|
||||
}, [playlist]);
|
||||
|
||||
useEffect(() => {
|
||||
if (playlistProp) {
|
||||
setWindowTitle(i18n('playlist.title_window', { name: playlistProp.name }));
|
||||
if (playlist) {
|
||||
setWindowTitle(i18n('playlist.title_window', { name: playlist.name }));
|
||||
} else {
|
||||
setWindowTitle(i18n('playlist.title'));
|
||||
}
|
||||
setPlaylist(playlistProp);
|
||||
}, [playlistProp]);
|
||||
}, [playlist]);
|
||||
|
||||
const handleContextMenu = (e) => showContextMenu({
|
||||
e,
|
||||
@ -312,7 +311,7 @@ const Playlist = ({
|
||||
|
||||
<h4 className="no-bottom-margin">
|
||||
<I18n path="playlist.tracks.title" />
|
||||
{loading_tracks && <Loader loading mini />}
|
||||
{loadingTracks && <Loader loading mini />}
|
||||
<div className="actions-wrapper">
|
||||
<FilterField
|
||||
initialValue={filter}
|
||||
@ -362,28 +361,15 @@ const mapStateToProps = (state, ownProps) => {
|
||||
} = {},
|
||||
} = state;
|
||||
|
||||
const uri = decodeUri(ownProps.match.params.uri);
|
||||
const itemSelector = makeItemSelector(uri);
|
||||
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!tracks).)*$', '^((?!followers).)*$']);
|
||||
const loadingTracksSelector = makeLoadingSelector([`(.*)${uri}(.*)tracks(.*)`]);
|
||||
const [sortField, sortReverse] = getSortSelector(state, SORT_KEY);
|
||||
|
||||
return {
|
||||
uri,
|
||||
encodedUri: ownProps.match.params.uri,
|
||||
name: ownProps.match.params.name,
|
||||
allow_reporting,
|
||||
slim_mode,
|
||||
theme,
|
||||
loading: loadingSelector(state),
|
||||
loading_tracks: loadingTracksSelector(state),
|
||||
playlist: itemSelector(state),
|
||||
spotify_library_playlists,
|
||||
local_library_playlists,
|
||||
spotify_authorized,
|
||||
spotify_userid: (me && me.id) || null,
|
||||
sortField,
|
||||
sortReverse,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user