Moving tracks into 'items' database; in_library now pre-populated; Next to add/remove from library

This commit is contained in:
James Barnsley
2020-09-11 22:13:06 +12:00
parent 5953fc84cf
commit f254170b2f
23 changed files with 297 additions and 318 deletions

View File

@ -57,11 +57,11 @@ class ContextMenu extends React.Component {
componentDidUpdate = (prevProps) => {
const {
menu: prevMenu,
tracks: prevTracks,
items: prevItems,
} = prevProps;
const {
menu,
tracks,
items,
lastfm_authorized,
spotify_available,
spotifyActions,
@ -93,8 +93,8 @@ class ContextMenu extends React.Component {
}
// if we're able to be in the LastFM library, run a check
if (lastfm_authorized && context.is_track && context.items_count == 1) {
if (menu.items[0].uri && prevTracks[menu.items[0].uri] !== undefined && tracks[menu.items[0].uri].userloved === undefined) {
if (lastfm_authorized && context.is_track && context.items_count === 1) {
if (menu.items[0].uri && prevItems[menu.items[0].uri] !== undefined && items[menu.items[0].uri].userloved === undefined) {
lastfmActions.getTrack(menu.items[0].uri);
}
}
@ -200,13 +200,13 @@ class ContextMenu extends React.Component {
* */
isLoved = ({ uri } = {}) => {
const {
tracks,
items,
} = this.props;
if (!uri) return false;
if (tracks[uri] === undefined) return false;
if (items[uri] === undefined) return false;
const track = tracks[uri];
const track = items[uri];
return (track.userloved !== undefined && track.userloved === '1');
}
@ -1304,8 +1304,6 @@ const mapStateToProps = (state) => ({
mopidy_library_playlists: state.core.libraries['mopidy:library:playlists'] || { items_uris: [] },
mopidy_library_artists: state.core.libraries['mopidy:library:artists'] || { items_uris: [] },
mopidy_library_albums: state.core.libraries['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,

View File

@ -51,7 +51,7 @@ class ProgressSlider extends React.Component {
* */
const mapStateToProps = (state, ownProps) => ({
current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null),
current_track: (state.core.current_track && state.core.items[state.core.current_track.uri] !== undefined ? state.core.items[state.core.current_track.uri] : null),
connected: state.mopidy.connected,
time_position: state.mopidy.time_position,
play_state: state.mopidy.play_state,

View File

@ -315,6 +315,7 @@ const CoreMiddleware = (function () {
* */
case 'LOAD_ITEMS':
action.uris.forEach((uri) => {
store.dispatch(uiActions.startLoading(uri, `load_item_${uri}`));
store.dispatch({
type: `LOAD_${uriType(uri).toUpperCase()}`,
uri,
@ -574,11 +575,12 @@ const CoreMiddleware = (function () {
break;
case 'LOAD_LIBRARY':
store.dispatch(uiActions.startLoading(action.uri, `load_library_${action.uri}`));
const fetchLibrary = () => {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(
spotifyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.forceRefetch),
spotifyActions[`getLibrary${titleCase(uriType(action.uri))}`](action.options.forceRefetch),
);
break;
case 'google':
@ -730,10 +732,14 @@ const CoreMiddleware = (function () {
break;
case 'ITEMS_LOADED':
const mergedItems = action.items.map((item) => ({
...core.items[item.uri] || {},
...item,
}));
const mergedItems = [];
action.items.forEach((item) => {
mergedItems.push({
...core.items[item.uri] || {},
...item,
});
store.dispatch(uiActions.stopLoading(item.uri));
});
store.dispatch(coreActions.updateColdStore(mergedItems));
next({
...action,
@ -742,10 +748,21 @@ const CoreMiddleware = (function () {
break;
case 'LIBRARY_LOADED':
store.dispatch(uiActions.stopLoading(action.library.uri));
store.dispatch(coreActions.updateColdStore([action.library]));
next(action);
break;
case 'RESTORE_ITEMS_FROM_COLD_STORE':
action.items.forEach((item) => store.dispatch(uiActions.stopLoading(item.uri)));
next(action);
break;
case 'RESTORE_LIBRARY_FROM_COLD_STORE':
store.dispatch(uiActions.stopLoading(action.library.uri));
next(action);
break;
case 'ARTISTS_LOADED':
var artists_index = { ...core.artists };
var artists_loaded = [];

View File

@ -147,7 +147,7 @@ export function getMe() {
* */
export function getTrackLyrics(uri, path) {
return (dispatch, getState) => {
dispatch(coreActions.trackLoaded({
dispatch(coreActions.itemLoaded({
uri,
lyrics: null,
lyrics_path: null,
@ -187,7 +187,7 @@ export function getTrackLyrics(uri, path) {
lyrics_html = lyrics_html.replace(/(\[)/g, '<span class="mid_grey-text">[');
lyrics_html = lyrics_html.replace(/(\])/g, ']</span>');
dispatch(coreActions.trackLoaded({
dispatch(coreActions.itemLoaded({
uri,
lyrics: lyrics_html,
lyrics_path: path,
@ -232,7 +232,7 @@ export function findTrackLyrics(track = null) {
path: response.hits[i].result.path,
});
}
dispatch(coreActions.trackLoaded({
dispatch(coreActions.itemLoaded({
uri: track.uri,
lyrics_results,
}));

View File

@ -1,5 +1,10 @@
import { formatImages, collate } from '../../util/format';
import {
collate,
formatImages,
formatTrack,
formatArtist,
formatAlbum,
} from '../../util/format';
import { generateGuid } from '../../util/helpers';
const coreActions = require('../core/actions');
@ -173,21 +178,12 @@ export function getMe() {
export function getTrack(uri) {
return (dispatch, getState) => {
if (getState().core.tracks[uri] !== undefined) {
var track = getState().core.tracks[uri];
if (!track.artists) {
dispatch(coreActions.handleException(
'Could not get LastFM track',
{},
'Track has no artists',
));
return;
}
} else {
const track = getState().core.items[uri];
if (!track || !track.artists) {
dispatch(coreActions.handleException(
'Could not get LastFM track',
{},
'Could not find track in index',
'Not in index or has no artists',
));
return;
}
@ -202,13 +198,15 @@ export function getTrack(uri) {
.then(
(response) => {
if (response.track) {
const merged_track = {
uri: track.uri,
...response.track,
...track,
};
dispatch(coreActions.trackLoaded(merged_track));
dispatch(
coreActions.itemLoaded(
formatTrack({
uri: track.uri,
...response.track,
...track,
}),
),
);
}
},
);
@ -228,14 +226,18 @@ export function getArtist(uri, artist, mbid = false) {
.then(
(response) => {
if (response.artist) {
dispatch(coreActions.itemLoaded({
uri,
mbid: response.artist.mbid,
biography: response.artist.bio.content,
biography_publish_date: response.artist.bio.published,
biography_link: response.artist.bio.links.link.href,
listeners: parseInt(response.artist.stats.listeners),
}));
dispatch(
coreActions.itemLoaded(
formatArtist({
uri,
mbid: response.artist.mbid,
biography: response.artist.bio.content,
biography_publish_date: response.artist.bio.published,
biography_link: response.artist.bio.links.link.href,
listeners: parseInt(response.artist.stats.listeners),
}),
),
);
}
},
);
@ -273,7 +275,7 @@ export function getAlbum(uri, artist, album, mbid = false) {
delete album.images;
}
dispatch(coreActions.itemLoaded(album));
dispatch(coreActions.itemLoaded(formatAlbum(album)));
}
},
);
@ -305,7 +307,7 @@ export function getImages(context, uri) {
(response) => {
if (response.album) {
const images = formatImages(response.album.image);
dispatch(coreActions.trackLoaded({ uri, images }));
dispatch(coreActions.itemLoaded(formatAlbum({ uri, images })));
}
},
);
@ -351,8 +353,8 @@ export function getImages(context, uri) {
export function loveTrack(uri) {
return (dispatch, getState) => {
if (getState().core.tracks[uri] !== undefined) {
var track = getState().core.tracks[uri];
if (getState().core.items[uri] !== undefined) {
var track = getState().core.items[uri];
if (!track.artists) {
dispatch(coreActions.handleException(
'Could not love LastFM track',
@ -375,15 +377,10 @@ export function loveTrack(uri) {
sendSignedRequest(dispatch, getState, params)
.then(
(response) => {
track = {
...track,
dispatch(coreActions.itemLoaded({
uri,
userloved: true,
};
dispatch({
type: 'TRACKS_LOADED',
tracks: [track],
});
}));
},
);
};
@ -391,8 +388,8 @@ export function loveTrack(uri) {
export function unloveTrack(uri) {
return (dispatch, getState) => {
if (getState().core.tracks[uri] !== undefined) {
var track = getState().core.tracks[uri];
if (getState().core.items[uri] !== undefined) {
var track = getState().core.items[uri];
if (!track.artists) {
dispatch(coreActions.handleException(
'Could not unlove LastFM track',
@ -415,15 +412,10 @@ export function unloveTrack(uri) {
sendSignedRequest(dispatch, getState, params)
.then(
(response) => {
track = {
...track,
dispatch(coreActions.itemLoaded({
uri,
userloved: false,
};
dispatch({
type: 'TRACKS_LOADED',
tracks: [track],
});
}));
},
);
};

View File

@ -57,6 +57,7 @@ const MopidyMiddleware = (function () {
case 'state:online':
store.dispatch({ type: 'MOPIDY_CONNECTED' });
store.dispatch(mopidyActions.getCurrentTrack());
store.dispatch(mopidyActions.getPlayState());
store.dispatch(mopidyActions.getVolume());
store.dispatch(mopidyActions.getMute());
@ -64,15 +65,14 @@ const MopidyMiddleware = (function () {
store.dispatch(mopidyActions.getRandom());
store.dispatch(mopidyActions.getRepeat());
store.dispatch(mopidyActions.getQueue());
store.dispatch(mopidyActions.getCurrentTrack());
store.dispatch(mopidyActions.getTimePosition());
store.dispatch(mopidyActions.getUriSchemes());
// Every 1s update our play position (when playing)
progress_interval = setInterval(() => {
if (store.getState().mopidy.play_state == 'playing') {
if (store.getState().mopidy.play_state === 'playing') {
// Every 10s get real position from server, provided we're in-focus
if (progress_interval_counter % 5 == 0 && store.getState().ui.window_focus === true) {
if (progress_interval_counter % 5 === 0 && store.getState().ui.window_focus === true) {
store.dispatch(mopidyActions.getTimePosition());
// Otherwise we just assume to add 1000ms every 1000ms of play time
@ -80,7 +80,7 @@ const MopidyMiddleware = (function () {
store.dispatch(mopidyActions.timePosition(store.getState().mopidy.time_position + 1000));
}
progress_interval_counter++;
progress_interval_counter += 1;
}
}, 1000);
@ -326,13 +326,13 @@ const MopidyMiddleware = (function () {
// Focus has just been regained
if (action.window_focus === true) {
store.dispatch(mopidyActions.getCurrentTrack());
store.dispatch(mopidyActions.getPlayState());
store.dispatch(mopidyActions.getVolume());
store.dispatch(mopidyActions.getMute());
store.dispatch(mopidyActions.getConsume());
store.dispatch(mopidyActions.getRandom());
store.dispatch(mopidyActions.getRepeat());
store.dispatch(mopidyActions.getCurrentTrack());
store.dispatch(mopidyActions.getTimePosition());
}
break;
@ -1980,7 +1980,7 @@ const MopidyMiddleware = (function () {
const tracks = indexToArray(_response);
store.dispatch(coreActions.tracksLoaded(tracks));
store.dispatch(coreActions.itemsLoaded(formatTracks(tracks)));
if (action.get_images) {
store.dispatch(mopidyActions.getImages(arrayOf('uri', tracks)));

View File

@ -1725,14 +1725,14 @@ export function getLibraryPlaylists(forceRefetch) {
.then((response) => {
const items = response.items.map(
(item) => ({
...item,
...formatPlaylist(item),
in_library: true,
can_edit: (getState().spotify.me && item.owner.id === getState().spotify.me.id),
}),
);
libraryItems = [...libraryItems, ...formatPlaylists(items)];
libraryItems = [...libraryItems, ...items];
if (response.next) {
fetchLibraryPlaylists(response.next);
fetchLibraryPlaylists(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
} else {
dispatch(coreActions.itemsLoaded(libraryItems));
dispatch(coreActions.libraryLoaded({
@ -1752,11 +1752,14 @@ export function getLibraryAlbums(forceRefetch) {
const fetchLibraryAlbums = (endpoint) => request(dispatch, getState, endpoint)
.then((response) => {
const items = response.items.map(
(item) => ({ ...item, in_library: true }),
(item) => ({
...formatAlbum(item),
in_library: true,
}),
);
libraryItems = [...libraryItems, ...formatAlbums(items)];
libraryItems = [...libraryItems, ...items];
if (response.next) {
fetchLibraryAlbums(response.next);
fetchLibraryAlbums(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
} else {
dispatch(coreActions.itemsLoaded(libraryItems));
dispatch(coreActions.libraryLoaded({
@ -1776,11 +1779,14 @@ export function getLibraryArtists(forceRefetch) {
const fetchLibraryArtists = (endpoint) => request(dispatch, getState, endpoint)
.then((response) => {
const items = response.artists.items.map(
(item) => ({ ...item, in_library: true }),
(item) => ({
...formatArtist(item),
in_library: true,
}),
);
libraryItems = [...libraryItems, ...formatArtists(items)];
libraryItems = [...libraryItems, ...items];
if (response.next) {
fetchLibraryArtists(response.next);
fetchLibraryArtists(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
} else {
dispatch(coreActions.itemsLoaded(libraryItems));
dispatch(coreActions.libraryLoaded({

View File

@ -263,8 +263,8 @@ export function removeNotification(key, manual = false) {
export function startLoading(key, source) {
return {
type: 'START_LOADING',
source,
key,
source,
};
}

View File

@ -167,6 +167,28 @@ const corePersistConfig = {
],
};
const mopidyPersistConfig = {
key: 'mopidy',
storage: localForage,
debug: window.test_mode,
whitelist: [
'consume',
'current_server',
'host',
'port',
'library_albums_uri',
'library_artists_uri',
'mute',
'play_state',
'random',
'repeat',
'servers',
'ssl',
'uri_schemes',
'volume',
],
};
const spotifyPersistConfig = {
key: 'spotify',
storage: localForage,
@ -203,9 +225,9 @@ const uiPersistConfig = {
const rootReducer = combineReducers({
core: persistReducer(corePersistConfig, core),
ui: persistReducer(uiPersistConfig, ui),
mopidy: persistReducer(mopidyPersistConfig, mopidy),
spotify: persistReducer(spotifyPersistConfig, spotify),
pusher,
mopidy,
lastfm,
genius,
google,

View File

@ -38,8 +38,8 @@ const getTrackIcon = function (current_track = false, core = false) {
if (!core) return false;
if (!current_track) return false;
if (typeof (current_track.uri) === 'undefined') return false;
if (typeof (core.tracks[current_track.uri]) === 'undefined') return false;
const track = core.tracks[current_track.uri];
if (typeof (core.items[current_track.uri]) === 'undefined') return false;
const track = core.items[current_track.uri];
if (!track.images) return false;
return formatImages(track.images).small;
};
@ -277,6 +277,7 @@ const formatAlbum = function (data) {
const album = {};
const fields = [
'uri',
'in_library',
'provider',
'name',
'type',
@ -351,6 +352,7 @@ const formatArtist = function (data) {
const artist = {};
const fields = [
'uri',
'in_library',
'provider',
'mbid',
'name',
@ -412,6 +414,7 @@ const formatPlaylist = function (data) {
const playlist = {};
const fields = [
'uri',
'in_library',
'snapshot_id',
'provider',
'type',
@ -500,6 +503,7 @@ const formatUser = function (data) {
const user = {};
const fields = [
'id',
'in_library',
'uri',
'provider',
'name',
@ -555,6 +559,7 @@ const formatTrack = function (data) {
const track = {};
const fields = [
'uri',
'in_library',
'tlid',
'provider',
'name',

View File

@ -292,10 +292,7 @@ const mapStateToProps = (state, ownProps) => {
slim_mode: state.ui.slim_mode,
theme: state.ui.theme,
load_queue: state.ui.load_queue,
tracks: state.core.tracks,
artists: state.core.artists,
album: (state.core.items[uri] || null),
albums: state.core.albums,
spotify_library_albums: state.spotify.library_albums,
local_library_albums: state.mopidy.library_albums,
spotify_authorized: state.spotify.authorization,

View File

@ -19,10 +19,6 @@ class QueueHistory extends React.Component {
this.loadHistory();
}
componentDidUpdate = () => {
this.loadHistory();
}
loadHistory = () => {
const {
mopidyActions: {
@ -30,6 +26,8 @@ class QueueHistory extends React.Component {
},
} = this.props;
console.log('get queue history');
getQueueHistory();
}
@ -41,7 +39,7 @@ class QueueHistory extends React.Component {
render = () => {
const {
queue_history,
tracks: tracksProp,
items,
uiActions,
} = this.props;
@ -58,9 +56,11 @@ class QueueHistory extends React.Component {
</Button>
);
if (!queue_history) return null;
const tracks = queue_history.map((item) => ({
...item,
...(tracksProp[item.uri] || {}),
...(items[item.uri] || {}),
}));
return (
@ -86,8 +86,8 @@ class QueueHistory extends React.Component {
}
const mapStateToProps = (state) => ({
tracks: (state.core.tracks ? state.core.tracks : {}),
queue_history: (state.mopidy.queue_history ? state.mopidy.queue_history : []),
items: state.core.items,
queue_history: state.mopidy.queue_history,
});
const mapDispatchToProps = (dispatch) => ({

View File

@ -587,7 +587,6 @@ class Discover extends React.Component {
const mapStateToProps = (state) => ({
theme: state.ui.theme,
tracks: state.core.tracks,
items: state.core.items,
genres: (state.core.genres ? state.core.genres : []),
authorized: state.spotify.authorization,

View File

@ -72,7 +72,7 @@ class LibraryAlbums extends React.Component {
this.getSpotifyLibrary(true);
}
getMopidyLibrary = (forceRefresh = false) => {
getMopidyLibrary = (forceRefetch = false) => {
const {
source,
coreActions: {
@ -82,10 +82,10 @@ class LibraryAlbums extends React.Component {
if (source !== 'local' && source !== 'all') return;
loadLibrary('mopidy:library:albums', forceRefresh);
loadLibrary('mopidy:library:albums', { forceRefetch });
};
getGoogleLibrary = (forceRefresh = false) => {
getGoogleLibrary = (forceRefetch = false) => {
const {
source,
google_available,
@ -97,10 +97,10 @@ class LibraryAlbums extends React.Component {
if (!google_available) return;
if (source !== 'google' && source !== 'all') return;
loadLibrary('google:library:albums', forceRefresh);
loadLibrary('google:library:albums', { forceRefetch });
};
getSpotifyLibrary = (forceRefresh = false) => {
getSpotifyLibrary = (forceRefetch = false) => {
const {
source,
spotify_available,
@ -112,7 +112,7 @@ class LibraryAlbums extends React.Component {
if (!spotify_available) return;
if (source !== 'spotify' && source !== 'all') return;
loadLibrary('spotify:library:albums', forceRefresh);
loadLibrary('spotify:library:albums', { forceRefetch });
};
handleContextMenu = (e, item) => {

View File

@ -16,23 +16,12 @@ import Button from '../../components/Button';
const UriListItem = ({
uri,
tracks,
items,
random_tracks,
albums,
remove,
}) => {
const type = uriType(uri);
let item = null;
switch (type) {
case 'track':
item = tracks[uri];
break;
case 'album':
item = albums[uri];
break;
default:
break;
}
let item = items[uri];
if (!item) {
item = random_tracks.find((track) => track.uri === uri);
}
@ -163,8 +152,7 @@ class AddToQueue extends React.Component {
render = () => {
const {
tracks,
albums,
items,
view: {
uris = [],
random_tracks = [],
@ -214,9 +202,8 @@ class AddToQueue extends React.Component {
{uris.map((uri, index) => (
<UriListItem
uri={uri}
tracks={tracks}
items={items}
random_tracks={random_tracks}
albums={albums}
remove={this.removeUri}
key={`${uri}_${index}`}
/>
@ -286,8 +273,7 @@ class AddToQueue extends React.Component {
}
const mapStateToProps = (state) => ({
albums: state.core.albums,
tracks: state.core.tracks,
items: state.core.items,
view: state.core.view ? state.core.view : {},
});

View File

@ -185,39 +185,14 @@ class EditRadio extends React.Component {
}
mapSeeds = () => {
const {
seeds,
} = this.state;
const {
tracks = {},
artists = {},
} = this.props;
const { seeds } = this.state;
const { items } = this.props;
if (!seeds) {
return [];
}
return seeds.slice(0, 5).map((uri) => {
switch (uriType(uri)) {
case 'artist':
if (artists[uri]) {
return artists[uri];
}
break;
case 'track':
if (tracks[uri]) {
return tracks[uri];
}
break;
default:
break;
}
return {
unresolved: true,
uri,
};
});
return seeds.slice(0, 5).map((uri) => items[uri] || { uri, unresolved: true });
}
render = () => {
@ -281,7 +256,7 @@ class EditRadio extends React.Component {
</div>
<div className="actions centered-text">
{this.state.enabled && (
{enabled && (
<Button
type="destructive"
size="large"
@ -292,7 +267,7 @@ class EditRadio extends React.Component {
</Button>
)}
{this.state.enabled ? (
{enabled ? (
<Button
type="primary"
size="large"
@ -318,10 +293,9 @@ class EditRadio extends React.Component {
}
}
const mapStateToProps = (state, ownProps) => ({
const mapStateToProps = (state) => ({
radio: state.core.radio,
artists: state.core.artists,
tracks: state.core.tracks,
items: state.core.items,
});
const mapDispatchToProps = (dispatch) => ({

View File

@ -237,7 +237,7 @@ class KioskMode extends React.Component {
const mapStateToProps = (state) => ({
play_state: state.mopidy.play_state,
current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null),
current_track: (state.core.current_track && state.core.items[state.core.current_track.uri] !== undefined ? state.core.items[state.core.current_track.uri] : null),
time_position: state.mopidy.time_position,
load_queue: state.ui.load_queue,
show_lyrics: state.ui.show_lyrics,

View File

@ -74,9 +74,17 @@
display: none;
}
&.sidebar__menu__item--active {
background: transparent;
color: colour(turquoise) !important;
&.sidebar__menu__item {
color: colour(mid_grey);
@include theme('light') {
color: colour(soft_grey);
}
&--active {
background: transparent;
color: colour(turquoise) !important;
}
}
}