Upgrading process calls; Tracks library now works like aother asset libraries (huge libraries may kill react-persist?)
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
22
mopidy_iris/static/app.min.js
vendored
22
mopidy_iris/static/app.min.js
vendored
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 = "1603178804";
|
||||
var build = "1603447492";
|
||||
var version = "3.53.0";
|
||||
|
||||
// Construct the script tag
|
||||
|
||||
@ -32,12 +32,12 @@ export default memo((props) => {
|
||||
const {
|
||||
body,
|
||||
loading,
|
||||
finished = false,
|
||||
finished,
|
||||
mini,
|
||||
lazy,
|
||||
white,
|
||||
className = '',
|
||||
progress = null,
|
||||
progress,
|
||||
} = props;
|
||||
|
||||
if (!loading && !finished) {
|
||||
@ -85,7 +85,7 @@ export default memo((props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
if (progress !== undefined) {
|
||||
classNameString += ' loader--progress';
|
||||
return (
|
||||
<div className={classNameString}>
|
||||
|
||||
@ -38,6 +38,7 @@ actions:
|
||||
confirm: Are you sure?
|
||||
delete: Delete
|
||||
remove: Remove
|
||||
cancel: Cancel
|
||||
common:
|
||||
popularity: Popularity
|
||||
name: Name
|
||||
@ -117,6 +118,7 @@ services:
|
||||
title: Mopidy
|
||||
local: Local
|
||||
adding_uris: 'Adding %{count} URI(s)'
|
||||
adding_uris_broadcast: '%{username} is adding %{count} URI(s)'
|
||||
searching: 'Searching %{provider} %{type}'
|
||||
searching_providers: 'Searching %{count} Mopidy providers'
|
||||
loading_albums: 'Loading %{count} local albums'
|
||||
|
||||
@ -82,6 +82,10 @@ const ensureLoaded = ({
|
||||
if (item) {
|
||||
if (getMissingDependents(item).length === 0) {
|
||||
console.info(`"${uri}" and all dependents already in index`);
|
||||
|
||||
// TODO
|
||||
// When an item is in the index, we assume the dependents are loaded as well, which in the
|
||||
// case of local artists is not true. Ie a local artist's albums are not auto-loaded.
|
||||
store.dispatch(uiActions.stopLoading(uri));
|
||||
coreActions.loadItems(getDependentUris(item));
|
||||
return;
|
||||
@ -373,6 +377,7 @@ const CoreMiddleware = (function () {
|
||||
|
||||
switch (uriSource(key)) {
|
||||
case 'spotify':
|
||||
console.log("GETTING", key)
|
||||
store.dispatch(spotifyActions.getPlaylist(key));
|
||||
break;
|
||||
case 'm3u':
|
||||
|
||||
@ -26,9 +26,7 @@ const GoogleMiddleware = (function () {
|
||||
return (store) => (next) => (action) => {
|
||||
switch (action.type) {
|
||||
case 'GOOGLE_GET_LIBRARY_ALBUMS': {
|
||||
store.dispatch(
|
||||
uiActions.updateProcess(action.type, { notification: false }),
|
||||
);
|
||||
store.dispatch(uiActions.startProcess(action.type, { notification: false }) );
|
||||
|
||||
request(
|
||||
store,
|
||||
@ -38,7 +36,7 @@ const GoogleMiddleware = (function () {
|
||||
const allUris = arrayOf('uri', browseResponse);
|
||||
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'GOOGLE_GET_LIBRARY_ALBUMS',
|
||||
action.type,
|
||||
{
|
||||
remaining: allUris.length,
|
||||
total: allUris.length,
|
||||
@ -83,7 +81,7 @@ const GoogleMiddleware = (function () {
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'google:library:albums',
|
||||
items_uris: arrayOf('uri', allUris),
|
||||
items_uris: arrayOf('uri', browseResponse),
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -95,7 +93,7 @@ const GoogleMiddleware = (function () {
|
||||
}
|
||||
|
||||
case 'GOOGLE_GET_LIBRARY_ARTISTS': {
|
||||
store.dispatch(uiActions.updateProcess(action.type, { notification: false }));
|
||||
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
|
||||
|
||||
request(
|
||||
store,
|
||||
@ -149,7 +147,7 @@ const GoogleMiddleware = (function () {
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'google:library:artists',
|
||||
items_uris: arrayOf('uri', allUris),
|
||||
items_uris: arrayOf('uri', browseResponse),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@ -473,9 +473,17 @@ export function getLibraryArtists(uri = null) {
|
||||
};
|
||||
}
|
||||
|
||||
export function clearLibraryArtists() {
|
||||
export function getLibraryAlbums(uri = null) {
|
||||
return {
|
||||
type: 'MOPIDY_CLEAR_LIBRARY_ARTISTS',
|
||||
type: 'MOPIDY_GET_LIBRARY_ALBUMS',
|
||||
uri,
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryTracks(uri = null) {
|
||||
return {
|
||||
type: 'MOPIDY_GET_LIBRARY_TRACKS',
|
||||
uri,
|
||||
};
|
||||
}
|
||||
|
||||
@ -495,39 +503,6 @@ export function getAlbum(uri, options) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getAlbums(uris, processor = null) {
|
||||
return {
|
||||
type: 'MOPIDY_GET_ALBUMS',
|
||||
uris,
|
||||
processor,
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryAlbums(uri = null) {
|
||||
return {
|
||||
type: 'MOPIDY_GET_LIBRARY_ALBUMS',
|
||||
uri,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearLibraryAlbums() {
|
||||
return {
|
||||
type: 'MOPIDY_CLEAR_LIBRARY_ALBUMS',
|
||||
};
|
||||
}
|
||||
|
||||
export function runProcessor(processor) {
|
||||
return {
|
||||
type: processor,
|
||||
};
|
||||
}
|
||||
|
||||
export function cancelProcessor(processor) {
|
||||
return {
|
||||
type: `${processor}_CANCEL`,
|
||||
};
|
||||
}
|
||||
|
||||
export function view_getRandomTracks(limit = 100) {
|
||||
return {
|
||||
type: 'VIEW__GET_RANDOM_TRACKS',
|
||||
@ -535,11 +510,6 @@ export function view_getRandomTracks(limit = 100) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searching
|
||||
* */
|
||||
|
||||
export function clearSearchResults() {
|
||||
return {
|
||||
type: 'MOPIDY_CLEAR_SEARCH_RESULTS',
|
||||
@ -555,11 +525,6 @@ export function getSearchResults(query, limit = 100, uri_schemes) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Other general actions
|
||||
* */
|
||||
|
||||
export function getQueueHistory() {
|
||||
return {
|
||||
type: 'MOPIDY_GET_QUEUE_HISTORY',
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import ReactGA from 'react-ga';
|
||||
import Mopidy from 'mopidy';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import { sampleSize, compact } from 'lodash';
|
||||
import { sampleSize, compact, chunk } from 'lodash';
|
||||
import { i18n } from '../../locale';
|
||||
import {
|
||||
generateGuid,
|
||||
@ -257,7 +257,8 @@ const MopidyMiddleware = (function () {
|
||||
method = 'library.search',
|
||||
data,
|
||||
} = queue.shift();
|
||||
const processor = store.getState().ui.processes.MOPIDY_GET_SEARCH_RESULTS;
|
||||
const processKey = 'MOPIDY_GET_SEARCH_RESULTS';
|
||||
const processor = store.getState().ui.processes[processKey];
|
||||
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
store.dispatch(uiActions.processCancelled('MOPIDY_GET_SEARCH_RESULTS'));
|
||||
@ -265,7 +266,7 @@ const MopidyMiddleware = (function () {
|
||||
}
|
||||
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'MOPIDY_GET_SEARCH_RESULTS',
|
||||
processKey,
|
||||
i18n(
|
||||
'services.mopidy.searching',
|
||||
{
|
||||
@ -369,9 +370,7 @@ const MopidyMiddleware = (function () {
|
||||
if (queue.length) {
|
||||
processSearchQueue(store, queue);
|
||||
} else {
|
||||
store.dispatch(uiActions.processFinished(
|
||||
'MOPIDY_GET_SEARCH_RESULTS',
|
||||
));
|
||||
store.dispatch(uiActions.processFinished(processKey));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -862,11 +861,42 @@ const MopidyMiddleware = (function () {
|
||||
break;
|
||||
}
|
||||
|
||||
let remaining = action.uris.length;
|
||||
let at_position = action.at_position;
|
||||
|
||||
// Uris are to go immediately after the currently-playing track (which could be paused)
|
||||
if (action.play_next) {
|
||||
const {
|
||||
current_track,
|
||||
queue,
|
||||
} = store.getState().core;
|
||||
if (current_track) {
|
||||
for (let i = 0; i < queue.length; i += 1) {
|
||||
if (queue[i].tlid === current_track.tlid) {
|
||||
at_position = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatch(uiActions.startProcess(
|
||||
action.type,
|
||||
{
|
||||
content: i18n('services.mopidy.adding_uris', { count: remaining }),
|
||||
remaining,
|
||||
total: remaining,
|
||||
},
|
||||
));
|
||||
|
||||
store.dispatch(pusherActions.deliverBroadcast(
|
||||
'notification',
|
||||
{
|
||||
notification: {
|
||||
content: `${store.getState().pusher.username} is adding ${action.uris.length} URIs to queue`,
|
||||
content: i18n(
|
||||
'services.mopidy.adding_uris_broadcast',
|
||||
{ username: store.getState().pusher.username, count: remaining },
|
||||
),
|
||||
icon: (
|
||||
store.getState().core.current_track
|
||||
? getTrackIcon(store.getState().core.current_track, store.getState().core)
|
||||
@ -876,125 +906,65 @@ const MopidyMiddleware = (function () {
|
||||
},
|
||||
));
|
||||
|
||||
const uris = Object.assign([], action.uris);
|
||||
const batches = [];
|
||||
const batch_size = 5;
|
||||
while (uris.length > 0) {
|
||||
batches.push({
|
||||
uris: uris.splice(0, batch_size),
|
||||
at_position: action.at_position,
|
||||
play_next: action.play_next,
|
||||
offset: action.offset + (batch_size * batches.length),
|
||||
from_uri: action.from_uri,
|
||||
});
|
||||
}
|
||||
// Prepare our URIS into batches. This allows performance gain of several URIs at once
|
||||
// (which is a blocking request), but not so many that it locks the Mopidy server.
|
||||
// It allows a window of opportunity for other requests to complete before we proceed to
|
||||
// the next batch.
|
||||
const batchSize = 5;
|
||||
const batches = chunk(action.uris, batchSize);
|
||||
|
||||
next({
|
||||
...action,
|
||||
batches,
|
||||
});
|
||||
|
||||
store.dispatch(uiActions.startProcess(
|
||||
'MOPIDY_ENQUEUE_URIS_PROCESSOR',
|
||||
i18n('services.mopidy.adding_uris', { count: action.uris.length }),
|
||||
{
|
||||
batches,
|
||||
remaining: action.uris.length,
|
||||
total: action.uris.length,
|
||||
},
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MOPIDY_ENQUEUE_URIS_PROCESSOR': {
|
||||
const last_run = store.getState().ui.processes.MOPIDY_ENQUEUE_URIS_PROCESSOR;
|
||||
|
||||
if (last_run && last_run.status == 'cancelling') {
|
||||
store.dispatch(uiActions.processCancelled('MOPIDY_ENQUEUE_URIS_PROCESSOR'));
|
||||
return;
|
||||
} if (action.data.batches && action.data.batches.length > 0) {
|
||||
var batches = Object.assign([], action.data.batches);
|
||||
var batch = batches[0];
|
||||
let total_uris = 0;
|
||||
for (var i = 0; i < batches.length; i++) {
|
||||
total_uris += batches[i].uris.length;
|
||||
}
|
||||
batches.shift();
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'MOPIDY_ENQUEUE_URIS_PROCESSOR',
|
||||
`Adding ${total_uris} URI(s)`,
|
||||
{
|
||||
remaining: total_uris,
|
||||
},
|
||||
));
|
||||
|
||||
// no batches means we're done here
|
||||
} else {
|
||||
store.dispatch(uiActions.processFinished('MOPIDY_ENQUEUE_URIS_PROCESSOR'));
|
||||
break;
|
||||
}
|
||||
|
||||
const {
|
||||
current_track,
|
||||
queue,
|
||||
} = store.getState().core;
|
||||
let current_track_index = -1;
|
||||
|
||||
if (current_track) {
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
if (queue[i].tlid == current_track.tlid) {
|
||||
current_track_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var params = { uris: batch.uris };
|
||||
|
||||
// Play this batch next
|
||||
if (batch.play_next) {
|
||||
// Make sure we're playing something first
|
||||
if (current_track_index > -1) {
|
||||
params.at_position = current_track_index + batch.offset + 1;
|
||||
|
||||
// Default to top of queue if we're not playing
|
||||
} else {
|
||||
params.at_position = 0 + batch.offset;
|
||||
// This is our process iterator
|
||||
const run = () => {
|
||||
const processor = store.getState().ui.processes[action.type];
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
store.dispatch(uiActions.processCancelled(action.type));
|
||||
return;
|
||||
}
|
||||
|
||||
// A specific position has been defined
|
||||
// NOTE: This is likely to be wrong as the original action is unaware of batches or other client requests
|
||||
} else if (batch.at_position) {
|
||||
params.at_position = batch.at_position + batch.offset;
|
||||
}
|
||||
const uris = batches.shift();
|
||||
|
||||
request(store, 'tracklist.add', params)
|
||||
.then(
|
||||
(response) => {
|
||||
// add metadata to queue
|
||||
const tlids = [];
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
tlids.push(response[i].tlid);
|
||||
}
|
||||
store.dispatch(pusherActions.addQueueMetadata(tlids, batch.from_uri));
|
||||
request(store, 'tracklist.add', { uris, at_position })
|
||||
.then(
|
||||
(response) => {
|
||||
const tlids = response.map((track) => track.tlid);
|
||||
store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri));
|
||||
|
||||
// Re-run the batch checker in 100ms. This allows a small window for other
|
||||
// server requests before our next batch. It's a little crude but it means the server isn't
|
||||
// locked until we're completely done.
|
||||
setTimeout(
|
||||
() => {
|
||||
store.dispatch(uiActions.runProcess(action.type, { batches }));
|
||||
},
|
||||
100,
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
store.dispatch(coreActions.handleException(
|
||||
`Mopidy: ${error.message ? error.message : 'Adding tracks failed'}`,
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
// Re-run the batch checker in 100ms. This allows a small window for other server
|
||||
// requests before our next batch. A little crude but it means the server isn't
|
||||
// locked until we're completely done.
|
||||
setTimeout(
|
||||
() => {
|
||||
if (!batches.length) {
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
} else {
|
||||
remaining -= uris.length;
|
||||
if (at_position !== null) at_position += uris.length;
|
||||
store.dispatch(
|
||||
uiActions.updateProcess(
|
||||
action.type,
|
||||
{
|
||||
remaining,
|
||||
content: i18n('services.mopidy.adding_uris', { count: remaining }),
|
||||
},
|
||||
),
|
||||
);
|
||||
run();
|
||||
}
|
||||
},
|
||||
100,
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
store.dispatch(coreActions.handleException(
|
||||
`Mopidy: ${error.message || 'Adding tracks failed'}`,
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
run(); // Kick off the loop
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1724,14 +1694,8 @@ const MopidyMiddleware = (function () {
|
||||
* My Music libraries
|
||||
*/
|
||||
case 'MOPIDY_GET_LIBRARY_ARTISTS':
|
||||
store.dispatch(
|
||||
uiActions.startProcess(
|
||||
'MOPIDY_GET_LIBRARY_ARTISTS',
|
||||
{
|
||||
notification: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
|
||||
|
||||
request(store, 'library.browse', { uri: store.getState().mopidy.library_artists_uri })
|
||||
.then((raw_response) => {
|
||||
if (raw_response.length <= 0) return;
|
||||
@ -1748,19 +1712,12 @@ const MopidyMiddleware = (function () {
|
||||
uri: 'mopidy:library:artists',
|
||||
items_uris: arrayOf('uri', response),
|
||||
}));
|
||||
store.dispatch(uiActions.processFinished('MOPIDY_GET_LIBRARY_ARTISTS'));
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
});
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_PLAYLISTS': {
|
||||
store.dispatch(
|
||||
uiActions.startProcess(
|
||||
'MOPIDY_GET_LIBRARY_PLAYLISTS',
|
||||
{
|
||||
notification: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
|
||||
|
||||
request(store, 'playlists.asList')
|
||||
.then((listResponse) => {
|
||||
@ -1799,7 +1756,7 @@ const MopidyMiddleware = (function () {
|
||||
|
||||
store.dispatch(
|
||||
uiActions.updateProcess(
|
||||
'MOPIDY_GET_LIBRARY_PLAYLISTS',
|
||||
action.type,
|
||||
{
|
||||
remaining: playlist_uris.length - index - 1,
|
||||
},
|
||||
@ -1812,35 +1769,28 @@ const MopidyMiddleware = (function () {
|
||||
uri: 'mopidy:library:playlists',
|
||||
items_uris: arrayOf('uri', libraryPlaylists),
|
||||
}));
|
||||
store.dispatch(uiActions.processFinished('MOPIDY_GET_LIBRARY_PLAYLISTS'));
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
store.dispatch(uiActions.stopLoading('mopidy:library:playlists'));
|
||||
store.dispatch(uiActions.processFinished('MOPIDY_GET_LIBRARY_PLAYLISTS'));
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_ALBUMS':
|
||||
store.dispatch(
|
||||
uiActions.startProcess(
|
||||
'MOPIDY_GET_LIBRARY_ALBUMS',
|
||||
{
|
||||
notification: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
|
||||
|
||||
request(store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri })
|
||||
.then((response) => {
|
||||
const uris = arrayOf('uri', response);
|
||||
.then((browseResponse) => {
|
||||
const uris = arrayOf('uri', browseResponse);
|
||||
|
||||
store.dispatch(
|
||||
uiActions.updateProcess(
|
||||
'MOPIDY_GET_LIBRARY_ALBUMS',
|
||||
action.type,
|
||||
{
|
||||
total: uris.length,
|
||||
remaining: uris.length,
|
||||
@ -1849,29 +1799,82 @@ const MopidyMiddleware = (function () {
|
||||
);
|
||||
|
||||
request(store, 'library.lookup', { uris })
|
||||
.then((response) => {
|
||||
const libraryAlbums = indexToArray(response).map((tracks) => ({
|
||||
.then((lookupResponse) => {
|
||||
const libraryAlbums = indexToArray(lookupResponse).map((tracks) => ({
|
||||
artists: tracks[0].artists ? formatArtists(tracks[0].artists) : null,
|
||||
tracks: formatTracks(tracks),
|
||||
last_modified: tracks[0].last_modified,
|
||||
...formatAlbum(tracks[0].album),
|
||||
}));
|
||||
|
||||
console.log('LOADED', response);
|
||||
|
||||
store.dispatch(coreActions.itemsLoaded(libraryAlbums));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'mopidy:library:albums',
|
||||
items_uris: arrayOf('uri', libraryAlbums),
|
||||
}));
|
||||
store.dispatch(uiActions.processFinished('MOPIDY_GET_LIBRARY_ALBUMS'));
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
case 'MOPIDY_GET_LIBRARY_TRACKS':
|
||||
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
|
||||
|
||||
request(store, 'library.browse', { uri: 'local:directory?type=track' })
|
||||
.then((browseResponse) => {
|
||||
const allUris = arrayOf('uri', browseResponse);
|
||||
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
action.type,
|
||||
{
|
||||
remaining: allUris.length,
|
||||
total: allUris.length,
|
||||
},
|
||||
));
|
||||
|
||||
const run = () => {
|
||||
if (allUris.length) {
|
||||
const uris = allUris.splice(0, 100);
|
||||
const processor = store.getState().ui.processes[action.type];
|
||||
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
store.dispatch(uiActions.processCancelled(action.type));
|
||||
store.dispatch(uiActions.stopLoading('mopidy:library:tracks'));
|
||||
return;
|
||||
}
|
||||
store.dispatch(uiActions.updateProcess(action.type, { remaining: allUris.length }));
|
||||
|
||||
request(store, 'library.lookup', { uris })
|
||||
.then(
|
||||
(lookupResponse) => {
|
||||
const libraryItems = compact(
|
||||
indexToArray(lookupResponse).map(
|
||||
(results) => (results.length ? formatTrack(results[0]) : null),
|
||||
),
|
||||
);
|
||||
|
||||
if (libraryItems.length) {
|
||||
store.dispatch(coreActions.itemsLoaded(libraryItems));
|
||||
}
|
||||
run();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'mopidy:library:tracks',
|
||||
items_uris: arrayOf('uri', browseResponse),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
return next(action);
|
||||
next(action);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
|
||||
import ReactGA from 'react-ga';
|
||||
import { uriType, generateGuid } from '../../util/helpers';
|
||||
import { trackEvent } from '../../components/Trackable';
|
||||
|
||||
const coreActions = require('../core/actions');
|
||||
const uiActions = require('../ui/actions');
|
||||
@ -532,15 +533,14 @@ const PusherMiddleware = (function () {
|
||||
|
||||
/**
|
||||
* Commands
|
||||
**/
|
||||
|
||||
case 'PUSHER_GET_COMMANDS':
|
||||
* */
|
||||
case 'PUSHER_GET_COMMANDS': {
|
||||
request(store, 'get_commands')
|
||||
.then(
|
||||
(response) => {
|
||||
store.dispatch(pusherActions.commandsUpdated(response.commands));
|
||||
},
|
||||
(error) => {
|
||||
() => {
|
||||
// We're not too worried about capturing errors here
|
||||
// It's also likely to fail where UI has been updated but
|
||||
// server hasn't been restarted yet.
|
||||
@ -549,24 +549,25 @@ const PusherMiddleware = (function () {
|
||||
);
|
||||
next(action);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PUSHER_SET_COMMAND':
|
||||
var commands = { ...pusher.commands };
|
||||
|
||||
if (commands[action.command.id]) {
|
||||
var command = { ...commands[action.command.id], ...action.command };
|
||||
} else {
|
||||
var { command } = action;
|
||||
}
|
||||
commands[action.command.id] = command;
|
||||
case 'PUSHER_SET_COMMAND': {
|
||||
const commands = {
|
||||
...pusher.commands,
|
||||
[action.command.id]: {
|
||||
...(pusher.commands[action.command.id] || {}),
|
||||
...action.command,
|
||||
},
|
||||
};
|
||||
|
||||
store.dispatch(pusherActions.setCommands(commands));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PUSHER_SET_COMMANDS':
|
||||
case 'PUSHER_SET_COMMANDS': {
|
||||
request(store, 'set_commands', { commands: action.commands })
|
||||
.then(
|
||||
(response) => {
|
||||
() => {
|
||||
// No action required, the change will be broadcast
|
||||
},
|
||||
(error) => {
|
||||
@ -579,14 +580,15 @@ const PusherMiddleware = (function () {
|
||||
|
||||
next(action);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PUSHER_REMOVE_COMMAND':
|
||||
var commands_index = { ...pusher.commands };
|
||||
case 'PUSHER_REMOVE_COMMAND': {
|
||||
const commands_index = { ...pusher.commands };
|
||||
delete commands_index[action.id];
|
||||
|
||||
request(store, 'set_commands', { commands: commands_index })
|
||||
.then(
|
||||
(response) => {
|
||||
() => {
|
||||
// No action required, the change will be broadcast
|
||||
},
|
||||
(error) => {
|
||||
@ -599,32 +601,34 @@ const PusherMiddleware = (function () {
|
||||
|
||||
next(action);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PUSHER_RUN_COMMAND':
|
||||
var command = { ...pusher.commands[action.id] };
|
||||
var notification_key = `command_${action.id}`;
|
||||
case 'PUSHER_RUN_COMMAND': {
|
||||
const notification_key = `command_${action.id}`;
|
||||
|
||||
if (action.notify) {
|
||||
store.dispatch(uiActions.startProcess(notification_key, 'Running command'));
|
||||
}
|
||||
|
||||
request(store, 'run_command', { id: action.id })
|
||||
.then((response) => {
|
||||
console.log('Command response', response);
|
||||
.then(() => {
|
||||
store.dispatch(uiActions.removeProcess(notification_key));
|
||||
if (action.notify) {
|
||||
store.dispatch(uiActions.createNotification({ key: notification_key, level: 'warning', content: 'Command sent' }));
|
||||
store.dispatch(uiActions.createNotification({
|
||||
key: notification_key,
|
||||
content: 'Command sent',
|
||||
}));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
store.dispatch(uiActions.removeProcess(notification_key));
|
||||
store.dispatch(coreActions.handleException(
|
||||
'Could not run command',
|
||||
error,
|
||||
));
|
||||
});
|
||||
|
||||
(error) => {
|
||||
store.dispatch(uiActions.removeProcess(notification_key));
|
||||
store.dispatch(coreActions.handleException(
|
||||
'Could not run command',
|
||||
error,
|
||||
));
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@ -654,26 +658,26 @@ const PusherMiddleware = (function () {
|
||||
break;
|
||||
|
||||
case 'PUSHER_START_RADIO':
|
||||
case 'PUSHER_UPDATE_RADIO':
|
||||
if (store.getState().ui.allow_reporting) {
|
||||
ReactGA.event({ category: 'Pusher', action: 'Start radio', label: action.uris.join() });
|
||||
}
|
||||
case 'PUSHER_UPDATE_RADIO': {
|
||||
trackEvent({ category: 'Pusher', action: 'Start radio', label: action.uris.join() });
|
||||
|
||||
// start our UI process notification
|
||||
if (action.type == 'PUSHER_UPDATE_RADIO') {
|
||||
store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Updating radio'));
|
||||
} else {
|
||||
store.dispatch(uiActions.startProcess('PUSHER_RADIO_PROCESS', 'Starting radio'));
|
||||
}
|
||||
store.dispatch(
|
||||
uiActions.startProcess(
|
||||
action.type,
|
||||
{
|
||||
content: `${action.type === 'PUSHER_UPDATE_RADIO' ? 'Updating' : 'Starting'} radio`,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
var data = {
|
||||
reset: (action.type == 'PUSHER_START_RADIO'),
|
||||
const data = {
|
||||
reset: (action.type === 'PUSHER_START_RADIO'),
|
||||
seed_artists: [],
|
||||
seed_genres: [],
|
||||
seed_tracks: [],
|
||||
};
|
||||
|
||||
for (let i = 0; i < action.uris.length; i++) {
|
||||
for (let i = 0; i < action.uris.length; i += 1) {
|
||||
switch (uriType(action.uris[i])) {
|
||||
case 'artist':
|
||||
data.seed_artists.push(action.uris[i]);
|
||||
@ -684,15 +688,16 @@ const PusherMiddleware = (function () {
|
||||
case 'genre':
|
||||
data.seed_genres.push(action.uris[i]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type == 'PUSHER_START_RADIO') {
|
||||
if (action.type === 'PUSHER_START_RADIO') {
|
||||
store.dispatch(pusherActions.deliverBroadcast(
|
||||
'notification',
|
||||
{
|
||||
notification: {
|
||||
level: 'warning',
|
||||
content: `${pusher.username} is starting radio mode`,
|
||||
},
|
||||
},
|
||||
@ -702,14 +707,14 @@ const PusherMiddleware = (function () {
|
||||
request(store, 'change_radio', data)
|
||||
.then(
|
||||
(response) => {
|
||||
store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'));
|
||||
if (response.status == 0) {
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
if (response.status === 0) {
|
||||
store.dispatch(uiActions.createNotification({ content: response.message, level: 'error' }));
|
||||
}
|
||||
store.dispatch(pusherActions.radioChanged(response.radio));
|
||||
},
|
||||
(error) => {
|
||||
store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'));
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
store.dispatch(coreActions.handleException(
|
||||
'Could not change radio',
|
||||
error,
|
||||
@ -717,25 +722,22 @@ const PusherMiddleware = (function () {
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PUSHER_STOP_RADIO':
|
||||
case 'PUSHER_STOP_RADIO': {
|
||||
store.dispatch(uiActions.createNotification({ content: 'Stopping radio' }));
|
||||
|
||||
if (store.getState().ui.allow_reporting) {
|
||||
ReactGA.event({ category: 'Pusher', action: 'Stop radio' });
|
||||
}
|
||||
trackEvent({ category: 'Pusher', action: 'Stop radio' });
|
||||
|
||||
store.dispatch(pusherActions.deliverBroadcast(
|
||||
'notification',
|
||||
{
|
||||
notification: {
|
||||
level: 'warning',
|
||||
content: `${pusher.username} stopped radio mode`,
|
||||
},
|
||||
},
|
||||
));
|
||||
|
||||
var data = {
|
||||
const data = {
|
||||
seed_artists: [],
|
||||
seed_genres: [],
|
||||
seed_tracks: [],
|
||||
@ -753,29 +755,18 @@ const PusherMiddleware = (function () {
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
||||
|
||||
/**
|
||||
* Notifications and alerts
|
||||
* */
|
||||
}
|
||||
|
||||
case 'PUSHER_BROWSER_NOTIFICATION':
|
||||
store.dispatch(uiActions.createBrowserNotification(action));
|
||||
break;
|
||||
|
||||
case 'PUSHER_NOTIFICATION':
|
||||
var data = {
|
||||
|
||||
store.dispatch(uiActions.createNotification({
|
||||
...action, type: action.notification_type,
|
||||
};
|
||||
store.dispatch(uiActions.createNotification(data));
|
||||
}));
|
||||
break;
|
||||
|
||||
|
||||
/**
|
||||
* Server actions
|
||||
* */
|
||||
|
||||
case 'PUSHER_RELOAD':
|
||||
// Hard reload. This doesn't strictly clear the cache, but our compiler's
|
||||
// cache buster should handle that
|
||||
@ -812,12 +803,11 @@ const PusherMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'PUSHER_CONFIG':
|
||||
|
||||
case 'PUSHER_CONFIG': {
|
||||
// Set default country/locale (unless we've already been configured)
|
||||
var { spotify } = store.getState();
|
||||
var spotify_updated = false;
|
||||
var spotify_updates = {};
|
||||
const { spotify } = store.getState();
|
||||
const spotify_updates = {};
|
||||
let spotify_updated = false;
|
||||
|
||||
if (!spotify.country && action.config.country) {
|
||||
spotify_updates.country = action.config.country;
|
||||
@ -848,6 +838,7 @@ const PusherMiddleware = (function () {
|
||||
|
||||
next(action);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PUSHER_DEBUG':
|
||||
request(store, action.message.method, action.message.data)
|
||||
@ -867,13 +858,9 @@ const PusherMiddleware = (function () {
|
||||
|
||||
case 'PUSHER_ERROR':
|
||||
store.dispatch(uiActions.createNotification(action.message, 'bad'));
|
||||
if (store.getState().ui.allow_reporting) {
|
||||
ReactGA.event({ category: 'Pusher', action: 'Error', label: action.message });
|
||||
}
|
||||
trackEvent({ category: 'Pusher', action: 'Error', label: action.message });
|
||||
break;
|
||||
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@ -342,7 +342,7 @@ export function getTrack(uri) {
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
export function getLibraryTracks() {
|
||||
return (dispatch, getState) => {
|
||||
request(dispatch, getState, 'me/tracks?limit=50')
|
||||
@ -362,6 +362,7 @@ export function getLibraryTracks() {
|
||||
);
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
export function getFeaturedPlaylists(forceRefetch = false) {
|
||||
return (dispatch, getState) => {
|
||||
@ -564,8 +565,9 @@ export function getMore(url, core_action = null, custom_action = null, extra_dat
|
||||
}
|
||||
|
||||
export function getSearchResults({ type, term }, limit = 50, offset = 0) {
|
||||
const processKey = 'SPOTIFY_GET_SEARCH_RESULTS';
|
||||
return (dispatch, getState) => {
|
||||
dispatch(uiActions.startProcess('SPOTIFY_GET_SEARCH_RESULTS_PROCESSOR', 'Searching Spotify'));
|
||||
dispatch(uiActions.startProcess(processKey, 'Searching Spotify'));
|
||||
|
||||
let typeString = type.replace(/s+$/, '');
|
||||
if (typeString === 'all') {
|
||||
@ -617,7 +619,7 @@ export function getSearchResults({ type, term }, limit = 50, offset = 0) {
|
||||
));
|
||||
}
|
||||
|
||||
dispatch(uiActions.processFinished('SPOTIFY_GET_SEARCH_RESULTS_PROCESSOR'));
|
||||
dispatch(uiActions.processFinished(processKey));
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
@ -1320,7 +1322,7 @@ export function savePlaylist(uri, name, description, is_public, is_collaborative
|
||||
};
|
||||
}
|
||||
|
||||
export function getPlaylist(uri, { full, forceRefetch, callbackAction }) {
|
||||
export function getPlaylist(uri, { full, forceRefetch, callbackAction } = {}) {
|
||||
return (dispatch, getState) => {
|
||||
let endpoint = `playlists/${getFromUri('playlistid', uri)}`;
|
||||
endpoint += `?market=${getState().spotify.country}`;
|
||||
@ -1566,24 +1568,18 @@ export function reorderPlaylistTracks(uri, range_start, range_length, insert_bef
|
||||
* ======================================================================================
|
||||
* */
|
||||
|
||||
export function flushLibrary() {
|
||||
return {
|
||||
type: 'SPOTIFY_FLUSH_LIBRARY',
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryPlaylists(forceRefetch) {
|
||||
return (dispatch, getState) => {
|
||||
const processKey = 'SPOTIFY_GET_LIBRARY_PLAYLISTS';
|
||||
dispatch(uiActions.updateProcess(processKey, { notification: false }));
|
||||
dispatch(uiActions.startProcess(processKey, { notification: false }));
|
||||
|
||||
let libraryItems = [];
|
||||
const fetchLibraryPlaylists = (endpoint) => request(dispatch, getState, endpoint)
|
||||
const fetch = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
const processor = getState().ui.processes[processKey];
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
dispatch(uiActions.stopLoading('spotify:library:playlists'));
|
||||
dispatch(uiActions.processCancelled(processKey));
|
||||
dispatch(uiActions.stopLoading('spotify:library:albums'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1601,7 +1597,7 @@ export function getLibraryPlaylists(forceRefetch) {
|
||||
);
|
||||
libraryItems = [...libraryItems, ...items];
|
||||
if (response.next) {
|
||||
fetchLibraryPlaylists(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
fetch(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
} else {
|
||||
dispatch(uiActions.processFinished(processKey));
|
||||
dispatch(coreActions.itemsLoaded(libraryItems));
|
||||
@ -1612,17 +1608,17 @@ export function getLibraryPlaylists(forceRefetch) {
|
||||
}
|
||||
});
|
||||
|
||||
fetchLibraryPlaylists(`me/playlists?limit=50${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
fetch(`me/playlists?limit=50${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryAlbums(forceRefetch) {
|
||||
return (dispatch, getState) => {
|
||||
const processKey = 'SPOTIFY_GET_LIBRARY_ALBUMS';
|
||||
dispatch(uiActions.updateProcess(processKey, { notification: false }));
|
||||
dispatch(uiActions.startProcess(processKey, { notification: false }));
|
||||
|
||||
let libraryItems = [];
|
||||
const fetchLibraryAlbums = (endpoint) => request(dispatch, getState, endpoint)
|
||||
const fetch = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
const processor = getState().ui.processes[processKey];
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
@ -1645,7 +1641,7 @@ export function getLibraryAlbums(forceRefetch) {
|
||||
libraryItems = [...libraryItems, ...items];
|
||||
|
||||
if (response.next) {
|
||||
fetchLibraryAlbums(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
fetch(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
} else {
|
||||
dispatch(uiActions.processFinished(processKey));
|
||||
dispatch(coreActions.itemsLoaded(libraryItems));
|
||||
@ -1656,17 +1652,17 @@ export function getLibraryAlbums(forceRefetch) {
|
||||
}
|
||||
});
|
||||
|
||||
fetchLibraryAlbums(`me/albums?limit=50${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
fetch(`me/albums?limit=50${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryArtists(forceRefetch) {
|
||||
return (dispatch, getState) => {
|
||||
const processKey = 'SPOTIFY_GET_LIBRARY_ARTISTS';
|
||||
dispatch(uiActions.updateProcess(processKey, { notification: false }));
|
||||
dispatch(uiActions.startProcess(processKey, { notification: false }));
|
||||
|
||||
let libraryItems = [];
|
||||
const fetchLibraryArtists = (endpoint) => request(dispatch, getState, endpoint)
|
||||
const fetch = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
const processor = getState().ui.processes[processKey];
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
@ -1677,7 +1673,10 @@ export function getLibraryArtists(forceRefetch) {
|
||||
|
||||
dispatch(uiActions.updateProcess(
|
||||
processKey,
|
||||
{ total: response.artists.total, remaining: response.artists.total - libraryItems.length },
|
||||
{
|
||||
total: response.artists.total,
|
||||
remaining: response.artists.total - libraryItems.length,
|
||||
},
|
||||
));
|
||||
|
||||
const items = response.artists.items.map(
|
||||
@ -1688,7 +1687,7 @@ export function getLibraryArtists(forceRefetch) {
|
||||
);
|
||||
libraryItems = [...libraryItems, ...items];
|
||||
if (response.next) {
|
||||
fetchLibraryArtists(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
fetch(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
} else {
|
||||
dispatch(uiActions.processFinished(processKey));
|
||||
dispatch(coreActions.itemsLoaded(libraryItems));
|
||||
@ -1699,6 +1698,49 @@ export function getLibraryArtists(forceRefetch) {
|
||||
}
|
||||
});
|
||||
|
||||
fetchLibraryArtists(`me/following?type=artist&limit=50${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
fetch(`me/following?type=artist&limit=50${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryTracks(forceRefetch) {
|
||||
return (dispatch, getState) => {
|
||||
const processKey = 'SPOTIFY_GET_LIBRARY_TRACKS';
|
||||
dispatch(uiActions.startProcess(processKey, { notification: false }));
|
||||
|
||||
let libraryItems = [];
|
||||
const fetch = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
const processor = getState().ui.processes[processKey];
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
dispatch(uiActions.processCancelled(processKey));
|
||||
dispatch(uiActions.stopLoading('spotify:library:tracks'));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(uiActions.updateProcess(
|
||||
processKey,
|
||||
{ total: response.total, remaining: response.total - libraryItems.length },
|
||||
));
|
||||
|
||||
const items = response.items.map(
|
||||
(item) => ({
|
||||
...formatTrack(item),
|
||||
in_library: true,
|
||||
}),
|
||||
);
|
||||
libraryItems = [...libraryItems, ...items];
|
||||
if (response.next) {
|
||||
fetch(`${response.next}${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
} else {
|
||||
dispatch(uiActions.processFinished(processKey));
|
||||
dispatch(coreActions.itemsLoaded(libraryItems));
|
||||
dispatch(coreActions.libraryLoaded({
|
||||
uri: 'spotify:library:tracks',
|
||||
items_uris: arrayOf('uri', libraryItems),
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
fetch(`me/tracks?limit=50${forceRefetch ? `&refetch=${Date.now()}` : ''}`);
|
||||
};
|
||||
}
|
||||
|
||||
@ -298,10 +298,10 @@ export function runProcess(key, data = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export function cancelProcess(key) {
|
||||
export function cancelProcess(keys) {
|
||||
return {
|
||||
type: 'CANCEL_PROCESS',
|
||||
key,
|
||||
keys: Array.isArray(keys) ? keys : [keys],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -148,9 +148,16 @@ export default function reducer(ui = {}, action) {
|
||||
|
||||
case 'CANCEL_PROCESS': {
|
||||
const processes = { ...(ui.processes ? ui.processes : {}) };
|
||||
if (processes[action.key]) {
|
||||
processes[action.key] = { ...processes[action.key], status: 'cancelling' };
|
||||
}
|
||||
action.keys.forEach((key) => {
|
||||
if (processes[key]) {
|
||||
processes[key] = {
|
||||
...processes[key],
|
||||
status: 'cancelling',
|
||||
total: undefined,
|
||||
remaining: undefined,
|
||||
};
|
||||
}
|
||||
});
|
||||
return { ...ui, processes };
|
||||
}
|
||||
|
||||
|
||||
@ -56,15 +56,17 @@ const makeSearchResultsSelector = (term, type) => createSelector(
|
||||
const makeProcessProgressSelector = (keys) => createSelector(
|
||||
[getProcesses],
|
||||
(processes) => {
|
||||
const selectedProcesses = keys.map((key) => processes[key] || {});
|
||||
const selectedProcesses = keys
|
||||
.map((key) => processes[key] || {})
|
||||
.filter((i) => i.status === 'running');
|
||||
|
||||
let total = 0;
|
||||
let remaining = 0;
|
||||
selectedProcesses.forEach((process) => {
|
||||
if (process.total) total += process.total;
|
||||
if (process.remaining) remaining += process.remaining;
|
||||
});
|
||||
console.log({ total, remaining })
|
||||
return total && remaining ? ((total - remaining) / total).toFixed(4) : 0;
|
||||
return total && remaining ? ((total - remaining) / total).toFixed(4) : 1;
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -20,10 +20,15 @@ import { i18n, I18n } from '../../locale';
|
||||
import Loader from '../../components/Loader';
|
||||
import {
|
||||
makeLibrarySelector,
|
||||
makeLoadingSelector,
|
||||
makeProcessProgressSelector,
|
||||
} from '../../util/selectors';
|
||||
|
||||
const processKeys = [
|
||||
'MOPIDY_GET_LIBRARY_ALBUMS',
|
||||
'SPOTIFY_GET_LIBRARY_ALBUMS',
|
||||
'GOOGLE_GET_LIBRARY_ALBUMS',
|
||||
];
|
||||
|
||||
class LibraryAlbums extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -68,7 +73,7 @@ class LibraryAlbums extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onRefresh = () => {
|
||||
refresh = () => {
|
||||
const { uiActions: { hideContextMenu } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
@ -77,6 +82,13 @@ class LibraryAlbums extends React.Component {
|
||||
this.getSpotifyLibrary(true);
|
||||
}
|
||||
|
||||
cancelRefresh = () => {
|
||||
const { uiActions: { hideContextMenu, cancelProcess } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
cancelProcess(processKeys);
|
||||
}
|
||||
|
||||
getMopidyLibrary = (forceRefetch = false) => {
|
||||
const {
|
||||
source,
|
||||
@ -175,7 +187,6 @@ class LibraryAlbums extends React.Component {
|
||||
sort,
|
||||
sort_reverse,
|
||||
view,
|
||||
loading,
|
||||
loading_progress,
|
||||
} = this.props;
|
||||
const {
|
||||
@ -184,9 +195,8 @@ class LibraryAlbums extends React.Component {
|
||||
} = this.state;
|
||||
let { albums } = this.props;
|
||||
|
||||
if (loading) {
|
||||
console.log(loading_progress);
|
||||
return <Loader body loading progress={loading_progress} />
|
||||
if (loading_progress < 1) {
|
||||
return <Loader body loading progress={loading_progress} />;
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
@ -245,11 +255,13 @@ class LibraryAlbums extends React.Component {
|
||||
source,
|
||||
sort_reverse,
|
||||
uiActions,
|
||||
loading_progress,
|
||||
} = this.props;
|
||||
const {
|
||||
filter,
|
||||
per_page,
|
||||
} = this.state;
|
||||
const loading = loading_progress < 1;
|
||||
|
||||
const source_options = [
|
||||
{
|
||||
@ -349,11 +361,11 @@ class LibraryAlbums extends React.Component {
|
||||
<Button
|
||||
noHover
|
||||
discrete
|
||||
onClick={this.onRefresh}
|
||||
onClick={loading ? this.cancelRefresh : this.refresh}
|
||||
tracking={{ category: 'LibraryAlbums', action: 'Refresh' }}
|
||||
>
|
||||
<Icon name="refresh" />
|
||||
<I18n path="actions.refresh" />
|
||||
{loading ? <Icon name="close" /> : <Icon name="refresh" /> }
|
||||
{loading ? <I18n path="actions.cancel" /> : <I18n path="actions.refresh" /> }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@ -372,21 +384,15 @@ class LibraryAlbums extends React.Component {
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const source = state.ui.library_albums_source ? state.ui.library_albums_source : 'all';
|
||||
const loadingSelector = makeLoadingSelector(['(.*):library:albums']);
|
||||
|
||||
const libraryUris = [];
|
||||
if (source === 'all' || source === 'local') libraryUris.push('mopidy:library:albums');
|
||||
if (source === 'all' || source === 'spotify') libraryUris.push('spotify:library:albums');
|
||||
if (source === 'all' || source === 'google') libraryUris.push('google:library:albums');
|
||||
const librarySelector = makeLibrarySelector(libraryUris);
|
||||
const processProgressSelector = makeProcessProgressSelector([
|
||||
'MOPIDY_GET_LIBRARY_ALBUMS',
|
||||
'SPOTIFY_GET_LIBRARY_ALBUMS',
|
||||
'GOOGLE_GET_LIBRARY_ALBUMS',
|
||||
]);
|
||||
const processProgressSelector = makeProcessProgressSelector(processKeys);
|
||||
|
||||
return {
|
||||
loading: loadingSelector(state),
|
||||
loading_progress: processProgressSelector(state),
|
||||
mopidy_uri_schemes: state.mopidy.uri_schemes,
|
||||
albums: librarySelector(state),
|
||||
|
||||
@ -21,6 +21,12 @@ import {
|
||||
makeProcessProgressSelector,
|
||||
} from '../../util/selectors';
|
||||
|
||||
const processKeys = [
|
||||
'MOPIDY_GET_LIBRARY_ARTISTS',
|
||||
'SPOTIFY_GET_LIBRARY_ARTISTS',
|
||||
'GOOGLE_GET_LIBRARY_ARTISTS',
|
||||
];
|
||||
|
||||
class LibraryArtists extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -63,7 +69,7 @@ class LibraryArtists extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onRefresh = () => {
|
||||
refresh = () => {
|
||||
const { uiActions: { hideContextMenu } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
@ -72,6 +78,13 @@ class LibraryArtists extends React.Component {
|
||||
this.getSpotifyLibrary(true);
|
||||
}
|
||||
|
||||
cancelRefresh = () => {
|
||||
const { uiActions: { hideContextMenu, cancelProcess } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
cancelProcess(processKeys);
|
||||
}
|
||||
|
||||
setSort = (value) => {
|
||||
const { sort, sort_reverse, uiActions: { set } } = this.props;
|
||||
let reverse = false;
|
||||
@ -151,7 +164,6 @@ class LibraryArtists extends React.Component {
|
||||
sort,
|
||||
sort_reverse,
|
||||
view,
|
||||
loading,
|
||||
loading_progress,
|
||||
} = this.props;
|
||||
const {
|
||||
@ -160,7 +172,7 @@ class LibraryArtists extends React.Component {
|
||||
} = this.state;
|
||||
let { artists } = this.props;
|
||||
|
||||
if (loading) {
|
||||
if (loading_progress < 1) {
|
||||
return <Loader body loading progress={loading_progress} />
|
||||
}
|
||||
|
||||
@ -215,7 +227,9 @@ class LibraryArtists extends React.Component {
|
||||
const {
|
||||
spotify_available,
|
||||
google_available,
|
||||
loading_progress,
|
||||
} = this.props;
|
||||
const loading = loading_progress < 1;
|
||||
|
||||
const source_options = [
|
||||
{
|
||||
@ -307,11 +321,11 @@ class LibraryArtists extends React.Component {
|
||||
<Button
|
||||
noHover
|
||||
discrete
|
||||
onClick={this.onRefresh}
|
||||
onClick={loading ? this.cancelRefresh : this.refresh}
|
||||
tracking={{ category: 'LibraryArtists', action: 'Refresh' }}
|
||||
>
|
||||
<Icon name="refresh" />
|
||||
<I18n path="actions.refresh" />
|
||||
{loading ? <Icon name="close" /> : <Icon name="refresh" /> }
|
||||
{loading ? <I18n path="actions.cancel" /> : <I18n path="actions.refresh" /> }
|
||||
</Button>
|
||||
</span>
|
||||
);
|
||||
@ -330,18 +344,13 @@ class LibraryArtists extends React.Component {
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const source = state.ui.library_artists_source || 'all';
|
||||
const loadingSelector = makeLoadingSelector(['(.*):library:artists']);
|
||||
|
||||
const libraryUris = [];
|
||||
if (source === 'all' || source === 'local') libraryUris.push('mopidy:library:artists');
|
||||
if (source === 'all' || source === 'spotify') libraryUris.push('spotify:library:artists');
|
||||
if (source === 'all' || source === 'google') libraryUris.push('google:library:artists');
|
||||
const librarySelector = makeLibrarySelector(libraryUris);
|
||||
const processProgressSelector = makeProcessProgressSelector([
|
||||
'MOPIDY_GET_LIBRARY_ARTISTS',
|
||||
'SPOTIFY_GET_LIBRARY_ARTISTS',
|
||||
'GOOGLE_GET_LIBRARY_ARTISTS',
|
||||
]);
|
||||
const processProgressSelector = makeProcessProgressSelector(processKeys);
|
||||
|
||||
return {
|
||||
loading_progress: processProgressSelector(state),
|
||||
@ -349,7 +358,6 @@ const mapStateToProps = (state) => {
|
||||
google_available: (state.mopidy.uri_schemes && state.mopidy.uri_schemes.includes('gmusic:')),
|
||||
spotify_available: (state.spotify.access_token),
|
||||
artists: librarySelector(state),
|
||||
loading: loadingSelector(state),
|
||||
source,
|
||||
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),
|
||||
|
||||
@ -16,10 +16,17 @@ import * as mopidyActions from '../../services/mopidy/actions';
|
||||
import * as spotifyActions from '../../services/spotify/actions';
|
||||
import { applyFilter, removeDuplicates, sortItems } from '../../util/arrays';
|
||||
import { I18n, i18n } from '../../locale';
|
||||
import { collate } from '../../util/format';
|
||||
import { isLoading } from '../../util/helpers';
|
||||
import Loader from '../../components/Loader';
|
||||
import { makeLibrarySelector, makeLoadingSelector } from '../../util/selectors';
|
||||
import {
|
||||
makeLibrarySelector,
|
||||
makeLoadingSelector,
|
||||
makeProcessProgressSelector,
|
||||
} from '../../util/selectors';
|
||||
|
||||
const processKeys = [
|
||||
'MOPIDY_GET_LIBRARY_PLAYLISTS',
|
||||
'SPOTIFY_GET_LIBRARY_PLAYLISTS',
|
||||
];
|
||||
|
||||
class LibraryPlaylists extends React.Component {
|
||||
constructor(props) {
|
||||
@ -55,7 +62,7 @@ class LibraryPlaylists extends React.Component {
|
||||
this.getSpotifyLibrary();
|
||||
}
|
||||
|
||||
onRefresh = () => {
|
||||
refresh = () => {
|
||||
const { uiActions: { hideContextMenu } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
@ -63,6 +70,13 @@ class LibraryPlaylists extends React.Component {
|
||||
this.getSpotifyLibrary(true);
|
||||
}
|
||||
|
||||
cancelRefresh = () => {
|
||||
const { uiActions: { hideContextMenu, cancelProcess } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
cancelProcess(processKeys);
|
||||
}
|
||||
|
||||
getMopidyLibrary = (forceRefetch = false) => {
|
||||
const {
|
||||
source,
|
||||
@ -137,7 +151,7 @@ class LibraryPlaylists extends React.Component {
|
||||
sort,
|
||||
sort_reverse,
|
||||
view,
|
||||
loading,
|
||||
loading_progress,
|
||||
playlists: playlistsProp,
|
||||
} = this.props;
|
||||
const {
|
||||
@ -145,8 +159,8 @@ class LibraryPlaylists extends React.Component {
|
||||
limit,
|
||||
} = this.state;
|
||||
|
||||
if (loading) {
|
||||
return <Loader body loading />;
|
||||
if (loading_progress < 1) {
|
||||
return <Loader body loading progress={loading_progress} />;
|
||||
}
|
||||
let playlists = [...playlistsProp];
|
||||
|
||||
@ -206,10 +220,12 @@ class LibraryPlaylists extends React.Component {
|
||||
sort,
|
||||
sort_reverse,
|
||||
view,
|
||||
loading_progress,
|
||||
} = this.props;
|
||||
const {
|
||||
filter,
|
||||
} = this.state;
|
||||
const loading = loading_progress < 1;
|
||||
|
||||
const source_options = [
|
||||
{
|
||||
@ -306,11 +322,11 @@ class LibraryPlaylists extends React.Component {
|
||||
<Button
|
||||
noHover
|
||||
discrete
|
||||
onClick={this.onRefresh}
|
||||
onClick={loading ? this.cancelRefresh : this.refresh}
|
||||
tracking={{ category: 'LibraryAlbums', action: 'Refresh' }}
|
||||
>
|
||||
<Icon name="refresh" />
|
||||
<I18n path="actions.refresh" />
|
||||
{loading ? <Icon name="close" /> : <Icon name="refresh" /> }
|
||||
{loading ? <I18n path="actions.cancel" /> : <I18n path="actions.refresh" /> }
|
||||
</Button>
|
||||
<Button
|
||||
to="/playlist/create"
|
||||
@ -338,20 +354,20 @@ class LibraryPlaylists extends React.Component {
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const source = state.ui.library_playlists_source || 'all';
|
||||
const loadingSelector = makeLoadingSelector(['(.*):library:playlists']);
|
||||
|
||||
const libraryUris = [];
|
||||
if (source === 'all' || source === 'local') libraryUris.push('mopidy:library:playlists');
|
||||
if (source === 'all' || source === 'spotify') libraryUris.push('spotify:library:playlists');
|
||||
if (source === 'all' || source === 'google') libraryUris.push('google:library:playlists');
|
||||
const librarySelector = makeLibrarySelector(libraryUris);
|
||||
const processProgressSelector = makeProcessProgressSelector(processKeys);
|
||||
|
||||
return {
|
||||
slim_mode: state.ui.slim_mode,
|
||||
mopidy_uri_schemes: state.mopidy.uri_schemes,
|
||||
spotify_available: state.spotify.access_token,
|
||||
playlists: librarySelector(state),
|
||||
loading: loadingSelector(state),
|
||||
loading_progress: processProgressSelector(state),
|
||||
source,
|
||||
me_id: (state.spotify.me ? state.spotify.me.id : false),
|
||||
view: state.ui.library_playlists_view,
|
||||
|
||||
@ -2,116 +2,336 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Loader from '../../components/Loader';
|
||||
import TrackList from '../../components/TrackList';
|
||||
import Header from '../../components/Header';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
import LazyLoadListener from '../../components/LazyLoadListener';
|
||||
import Icon from '../../components/Icon';
|
||||
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 { isLoading } from '../../util/helpers';
|
||||
import { I18n, i18n } from '../../locale';
|
||||
import { sortItems, applyFilter } from '../../util/arrays';
|
||||
import Button from '../../components/Button';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import Loader from '../../components/Loader';
|
||||
import {
|
||||
makeLibrarySelector,
|
||||
makeProcessProgressSelector,
|
||||
} from '../../util/selectors';
|
||||
|
||||
const processKeys = [
|
||||
'MOPIDY_GET_LIBRARY_TRACKS',
|
||||
'SPOTIFY_GET_LIBRARY_TRACKS',
|
||||
];
|
||||
|
||||
class LibraryTracks extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.uiActions.setWindowTitle(i18n('library.tracks.title'));
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (!this.props.spotify_available) {
|
||||
this.props.uiActions.createNotification({
|
||||
level: 'warning',
|
||||
content: i18n('errors.enable_first', { provider: i18n('services.spotify.title') }),
|
||||
this.state = {
|
||||
filter: '',
|
||||
limit: 50,
|
||||
per_page: 50,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
location: {
|
||||
state = {},
|
||||
},
|
||||
uiActions: {
|
||||
setWindowTitle,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
// Restore any limit defined in our location state
|
||||
if (state.limit) {
|
||||
this.setState({
|
||||
limit: state.limit,
|
||||
});
|
||||
} else if (this.props.library_tracks === undefined) {
|
||||
this.props.spotifyActions.getLibraryTracks();
|
||||
}
|
||||
|
||||
setWindowTitle(i18n('library.tracks.title'));
|
||||
this.getMopidyLibrary();
|
||||
this.getSpotifyLibrary();
|
||||
}
|
||||
|
||||
componentDidUpdate = ({ source: prevSource }) => {
|
||||
const { source } = this.props;
|
||||
|
||||
if (source !== prevSource) {
|
||||
this.getMopidyLibrary();
|
||||
this.getSpotifyLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
this.props.spotifyActions.getMore(
|
||||
this.props.library_tracks_more,
|
||||
null,
|
||||
{
|
||||
type: 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE',
|
||||
refresh = () => {
|
||||
const { uiActions: { hideContextMenu } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
this.getMopidyLibrary(true);
|
||||
this.getSpotifyLibrary(true);
|
||||
}
|
||||
|
||||
cancelRefresh = () => {
|
||||
const { uiActions: { hideContextMenu, cancelProcess } } = this.props;
|
||||
|
||||
hideContextMenu();
|
||||
cancelProcess(processKeys);
|
||||
}
|
||||
|
||||
getMopidyLibrary = (forceRefetch = false) => {
|
||||
const {
|
||||
source,
|
||||
coreActions: {
|
||||
loadLibrary,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (source !== 'local' && source !== 'all') return;
|
||||
|
||||
loadLibrary('mopidy:library:tracks', { forceRefetch });
|
||||
};
|
||||
|
||||
getSpotifyLibrary = (forceRefetch = false) => {
|
||||
const {
|
||||
source,
|
||||
spotify_available,
|
||||
coreActions: {
|
||||
loadLibrary,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (!spotify_available) return;
|
||||
if (source !== 'spotify' && source !== 'all') return;
|
||||
|
||||
loadLibrary('spotify:library:tracks', { forceRefetch });
|
||||
};
|
||||
|
||||
handleContextMenu = (e, item) => {
|
||||
const {
|
||||
uiActions: {
|
||||
showContextMenu,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
showContextMenu({
|
||||
e,
|
||||
context: 'album',
|
||||
uris: [item.uri],
|
||||
items: [item],
|
||||
});
|
||||
}
|
||||
|
||||
loadMore = () => {
|
||||
const {
|
||||
limit,
|
||||
per_page,
|
||||
} = this.state;
|
||||
const {
|
||||
location: {
|
||||
state,
|
||||
},
|
||||
history,
|
||||
} = this.props;
|
||||
|
||||
const new_limit = limit + per_page;
|
||||
this.setState({ limit: new_limit });
|
||||
history.replace({ state: { ...state, limit: new_limit } });
|
||||
}
|
||||
|
||||
setSort = (value) => {
|
||||
const {
|
||||
sort,
|
||||
sort_reverse,
|
||||
uiActions: {
|
||||
set,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
let reverse = false;
|
||||
if (sort === value) reverse = !sort_reverse;
|
||||
|
||||
set({
|
||||
library_tracks_sort_reverse: reverse,
|
||||
library_tracks_sort: value,
|
||||
});
|
||||
}
|
||||
|
||||
renderView = () => {
|
||||
const {
|
||||
sort,
|
||||
sort_reverse,
|
||||
loading_progress,
|
||||
} = this.props;
|
||||
const {
|
||||
limit,
|
||||
filter,
|
||||
} = this.state;
|
||||
let { tracks } = this.props;
|
||||
|
||||
if (loading_progress < 1) {
|
||||
return <Loader body loading progress={loading_progress} />;
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
tracks = sortItems(tracks, sort, sort_reverse);
|
||||
}
|
||||
|
||||
if (filter && filter !== '') {
|
||||
tracks = applyFilter('name', filter, tracks);
|
||||
}
|
||||
|
||||
// Apply our lazy-load-rendering
|
||||
const total_tracks = tracks.length;
|
||||
tracks = tracks.slice(0, limit);
|
||||
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<TrackList tracks={tracks} />
|
||||
<LazyLoadListener
|
||||
loadKey={total_tracks > limit ? limit : total_tracks}
|
||||
showLoader={limit < total_tracks}
|
||||
loadMore={this.loadMore}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
playAll() {
|
||||
this.props.spotifyActions.getLibraryTracksAndPlay();
|
||||
}
|
||||
render = () => {
|
||||
const {
|
||||
spotify_available,
|
||||
sort,
|
||||
source,
|
||||
sort_reverse,
|
||||
uiActions,
|
||||
loading_progress,
|
||||
} = this.props;
|
||||
const {
|
||||
filter,
|
||||
per_page,
|
||||
} = this.state;
|
||||
const loading = loading_progress < 1;
|
||||
|
||||
render() {
|
||||
// Note trailing "?" makes sure our context menu in_library checks doesn't interfere
|
||||
if (isLoading(this.props.load_queue, ['spotify_me/tracks?'])) {
|
||||
return (
|
||||
<div className="view library-tracks-view">
|
||||
<Header icon="music" title={i18n('library.tracks.title')} />
|
||||
<Loader body loading />
|
||||
</div>
|
||||
);
|
||||
const source_options = [
|
||||
{
|
||||
value: 'all',
|
||||
label: i18n('fields.filters.all'),
|
||||
},
|
||||
{
|
||||
value: 'local',
|
||||
label: i18n('services.mopidy.local'),
|
||||
},
|
||||
];
|
||||
|
||||
if (spotify_available) {
|
||||
source_options.push({
|
||||
value: 'spotify',
|
||||
label: i18n('services.spotify.title'),
|
||||
});
|
||||
}
|
||||
|
||||
const tracks = [];
|
||||
if (this.props.library_tracks && this.props.tracks) {
|
||||
for (let i = 0; i < this.props.library_tracks.length; i++) {
|
||||
const uri = this.props.library_tracks[i];
|
||||
if (this.props.tracks.hasOwnProperty(uri)) {
|
||||
tracks.push(this.props.tracks[uri]);
|
||||
}
|
||||
}
|
||||
}
|
||||
const sort_options = [
|
||||
{
|
||||
value: null,
|
||||
label: i18n('fields.filters.as_loaded'),
|
||||
},
|
||||
{
|
||||
value: 'name',
|
||||
label: i18n('fields.filters.name'),
|
||||
},
|
||||
{
|
||||
value: 'artists.first.name',
|
||||
label: i18n('fields.filters.artist'),
|
||||
},
|
||||
{
|
||||
value: 'last_modified',
|
||||
label: i18n('fields.filters.updated'),
|
||||
},
|
||||
{
|
||||
value: 'tracks_uris.length',
|
||||
label: i18n('fields.filters.tracks'),
|
||||
},
|
||||
{
|
||||
value: 'uri',
|
||||
label: i18n('fields.filters.source'),
|
||||
},
|
||||
];
|
||||
|
||||
const options = (
|
||||
<div className="header__options__wrapper">
|
||||
<FilterField
|
||||
initialValue={filter}
|
||||
handleChange={(value) => this.setState({ filter: value, limit: per_page })}
|
||||
onSubmit={() => uiActions.hideContextMenu()}
|
||||
/>
|
||||
<DropdownField
|
||||
icon="swap_vert"
|
||||
name={i18n('fields.sort')}
|
||||
value={sort}
|
||||
valueAsLabel
|
||||
options={sort_options}
|
||||
selected_icon={sort ? (sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
|
||||
handleChange={(val) => { this.setSort(val); uiActions.hideContextMenu(); }}
|
||||
/>
|
||||
<DropdownField
|
||||
icon="cloud"
|
||||
name={i18n('fields.source')}
|
||||
value={source}
|
||||
valueAsLabel
|
||||
options={source_options}
|
||||
handleChange={(val) => { uiActions.set({ library_tracks_source: val }); uiActions.hideContextMenu(); }}
|
||||
/>
|
||||
<Button
|
||||
onClick={(e) => this.playAll(e)}
|
||||
tracking={{ category: 'LibraryTracks', action: 'Play' }}
|
||||
noHover
|
||||
discrete
|
||||
onClick={loading ? this.cancelRefresh : this.refresh}
|
||||
tracking={{ category: 'LibraryTracks', action: 'Refresh' }}
|
||||
>
|
||||
<Icon name="play_circle_filled" />
|
||||
<I18n path="actions.play_all" />
|
||||
{loading ? <Icon name="close" /> : <Icon name="refresh" /> }
|
||||
{loading ? <I18n path="actions.cancel" /> : <I18n path="actions.refresh" /> }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="view library-tracks-view">
|
||||
<Header options={options} uiActions={this.props.uiActions}>
|
||||
<Icon name="music_note" type="material" />
|
||||
<I18n path="library.tracks.title" />
|
||||
<Header options={options} uiActions={uiActions}>
|
||||
<Icon name="album" type="material" />
|
||||
<I18n path="library.tracks.title" />
|
||||
</Header>
|
||||
<section className="content-wrapper">
|
||||
<TrackList tracks={tracks} />
|
||||
<LazyLoadListener
|
||||
loadKey={this.props.library_tracks_more}
|
||||
showLoader={this.props.library_tracks_more}
|
||||
loadMore={() => this.loadMore()}
|
||||
/>
|
||||
</section>
|
||||
{this.renderView()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const source = state.ui.library_tracks_source ? state.ui.library_tracks_source : 'all';
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
* */
|
||||
const libraryUris = [];
|
||||
if (source === 'all' || source === 'local') libraryUris.push('mopidy:library:tracks');
|
||||
if (source === 'all' || source === 'spotify') libraryUris.push('spotify:library:tracks');
|
||||
const librarySelector = makeLibrarySelector(libraryUris);
|
||||
const processProgressSelector = makeProcessProgressSelector(processKeys);
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
load_queue: state.ui.load_queue,
|
||||
tracks: state.core.tracks,
|
||||
spotify_available: state.spotify.access_token,
|
||||
library_tracks: state.spotify.library_tracks,
|
||||
library_tracks_more: state.spotify.library_tracks_more,
|
||||
});
|
||||
return {
|
||||
loading_progress: processProgressSelector(state),
|
||||
mopidy_uri_schemes: state.mopidy.uri_schemes,
|
||||
tracks: librarySelector(state),
|
||||
spotify_available: state.spotify.access_token,
|
||||
view: state.ui.library_tracks_view,
|
||||
source,
|
||||
sort: state.ui.library_tracks_sort,
|
||||
sort_reverse: state.ui.library_tracks_sort_reverse,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
|
||||
Reference in New Issue
Block a user