Tracks using action.options; Loader progress now fully svg
This commit is contained in:
@ -567,9 +567,9 @@ class ContextMenu extends React.Component {
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (!items || items.length <= 0 || !items[0].user_uri) return null;
|
||||
if (!items || items.length <= 0 || !items[0].user) return null;
|
||||
hideContextMenu();
|
||||
push(buildLink(items[0].user_uri));
|
||||
push(buildLink(items[0].user.uri));
|
||||
}
|
||||
|
||||
goToTrack = () => {
|
||||
@ -1241,6 +1241,7 @@ class ContextMenu extends React.Component {
|
||||
{context.items_count === 1 && go_to_track}
|
||||
<div className="context-menu__divider" />
|
||||
{copy_uris}
|
||||
{refresh}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,24 +2,47 @@ import React, { memo } from 'react';
|
||||
import Icon from './Icon';
|
||||
import { I18n } from '../locale';
|
||||
|
||||
const ProgressRing = ({ radius, stroke, progress }) => {
|
||||
const LoaderRing = ({ radius, stroke, progress }) => {
|
||||
const normalizedRadius = radius - stroke * 2;
|
||||
const circumference = normalizedRadius * 2 * Math.PI;
|
||||
const strokeDashoffset = circumference - progress * circumference;
|
||||
|
||||
return (
|
||||
<svg
|
||||
className="loader__spinner__progress"
|
||||
className="loader__ring"
|
||||
height={radius * 2}
|
||||
width={radius * 2}
|
||||
>
|
||||
{progress === null ? (
|
||||
<circle
|
||||
className="loader__ring__background"
|
||||
stroke="transparent"
|
||||
fill="transparent"
|
||||
strokeWidth={stroke}
|
||||
r={normalizedRadius}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
/>
|
||||
) : (
|
||||
<circle
|
||||
className="loader__ring__progress"
|
||||
stroke="transparent"
|
||||
fill="transparent"
|
||||
strokeWidth={stroke}
|
||||
strokeDasharray={`${circumference} ${circumference}`}
|
||||
strokeDashoffset={strokeDashoffset || 0}
|
||||
r={normalizedRadius}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
/>
|
||||
)}
|
||||
<circle
|
||||
className="loader__spinner__progress__circle"
|
||||
stroke="white"
|
||||
className="loader__ring__foreground"
|
||||
stroke="transparent"
|
||||
fill="transparent"
|
||||
strokeWidth={stroke}
|
||||
strokeDasharray={`${circumference} ${circumference}`}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeDashoffset={(circumference - 0.25 * circumference) || 0}
|
||||
r={normalizedRadius}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
@ -85,21 +108,16 @@ export default memo((props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (progress !== undefined) {
|
||||
classNameString += ' loader--progress';
|
||||
return (
|
||||
<div className={classNameString}>
|
||||
<div className="loader__spinner">
|
||||
<ProgressRing
|
||||
stroke="2"
|
||||
radius={mini ? '13' : '60'}
|
||||
progress={progress}
|
||||
/>
|
||||
<div className="loader__spinner__foreground" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
classNameString += ' loader--progress';
|
||||
return (
|
||||
<div className={classNameString}>
|
||||
<LoaderRing
|
||||
stroke={mini ? 2 : 3}
|
||||
radius={mini ? 11 : 60}
|
||||
progress={progress}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNameString}>
|
||||
|
||||
@ -176,7 +176,7 @@ class Notifications extends React.Component {
|
||||
remaining,
|
||||
level = 'info',
|
||||
content,
|
||||
description = null,
|
||||
description,
|
||||
status,
|
||||
closing,
|
||||
key,
|
||||
|
||||
@ -68,6 +68,7 @@ fields:
|
||||
as_loaded: As loaded
|
||||
name: Name
|
||||
artist: Artist
|
||||
album: Album
|
||||
updated: Updated
|
||||
tracks: Tracks
|
||||
source: Source
|
||||
@ -360,6 +361,9 @@ settings:
|
||||
album_uri:
|
||||
label: Album library URI
|
||||
description: URI used for collecting library albums
|
||||
track_uri:
|
||||
label: Track library URI
|
||||
description: URI used for collecting library tracks
|
||||
connections:
|
||||
label: Connections
|
||||
version:
|
||||
|
||||
@ -81,13 +81,14 @@ const ensureLoaded = ({
|
||||
// Item already in our index?
|
||||
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));
|
||||
console.info(`"${uri}" already in index`);
|
||||
|
||||
const dependentUris = getDependentUris(item);
|
||||
if (dependentUris.length) {
|
||||
console.log(`Loading ${dependentUris.length} dependents`);
|
||||
store.dispatch(coreActions.loadItems(dependentUris));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -479,6 +480,7 @@ const CoreMiddleware = (function () {
|
||||
action,
|
||||
fetch,
|
||||
dependents: ['images'],
|
||||
fullDependents: ['lyrics_results'],
|
||||
});
|
||||
|
||||
next(action);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
|
||||
import { generateGuid } from '../../util/helpers';
|
||||
import { makeItemSelector } from '../../util/selectors';
|
||||
|
||||
const coreActions = require('../core/actions');
|
||||
const uiActions = require('../ui/actions');
|
||||
@ -187,7 +188,7 @@ export function getTrackLyrics(uri, path) {
|
||||
lyrics_html = lyrics_html.replace(/(\[)/g, '<span class="mid_grey-text">[');
|
||||
lyrics_html = lyrics_html.replace(/(\])/g, ']</span>');
|
||||
|
||||
console.debug(lyrics_html);
|
||||
//console.debug(lyrics_html);
|
||||
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
@ -211,9 +212,19 @@ export function getTrackLyrics(uri, path) {
|
||||
};
|
||||
}
|
||||
|
||||
export function findTrackLyrics(track = null) {
|
||||
export function findTrackLyrics(uri) {
|
||||
return (dispatch, getState) => {
|
||||
if (!track) return;
|
||||
const selector = makeItemSelector(uri);
|
||||
const track = selector(getState());
|
||||
if (!track || !track.artists) {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not get Genius lyrics',
|
||||
{},
|
||||
'Not in index or has no artists',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
let query = '';
|
||||
query += `${track.artists[0].name} `;
|
||||
query += track.name;
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
formatAlbum,
|
||||
} from '../../util/format';
|
||||
import { generateGuid } from '../../util/helpers';
|
||||
import { makeItemSelector } from '../../util/selectors';
|
||||
|
||||
const coreActions = require('../core/actions');
|
||||
const uiActions = require('../ui/actions');
|
||||
@ -178,7 +179,8 @@ export function getMe() {
|
||||
|
||||
export function getTrack(uri) {
|
||||
return (dispatch, getState) => {
|
||||
const track = getState().core.items[uri];
|
||||
const selector = makeItemSelector(uri);
|
||||
const track = selector(getState());
|
||||
if (!track || !track.artists) {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not get LastFM track',
|
||||
|
||||
@ -454,16 +454,16 @@ export function getDirectory(uri) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getTracks(uris, get_images) {
|
||||
export function getTracks(uris, options) {
|
||||
return {
|
||||
type: 'MOPIDY_GET_TRACKS',
|
||||
uris,
|
||||
get_images,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTrack(uri) {
|
||||
return getTracks([uri], true);
|
||||
export function getTrack(uri, options) {
|
||||
return getTracks([uri], options);
|
||||
}
|
||||
|
||||
export function getLibraryArtists(uri = null) {
|
||||
|
||||
@ -15,7 +15,6 @@ import {
|
||||
digestMopidyImages,
|
||||
formatImages,
|
||||
formatAlbum,
|
||||
formatAlbums,
|
||||
formatTrack,
|
||||
formatTracks,
|
||||
formatSimpleObject,
|
||||
@ -23,12 +22,10 @@ import {
|
||||
formatArtists,
|
||||
formatArtist,
|
||||
formatPlaylist,
|
||||
formatPlaylists,
|
||||
} from '../../util/format';
|
||||
import {
|
||||
arrayOf,
|
||||
shuffle,
|
||||
removeDuplicates,
|
||||
applyFilter,
|
||||
sortItems,
|
||||
indexToArray,
|
||||
@ -41,6 +38,7 @@ const spotifyActions = require('../spotify/actions.js');
|
||||
const pusherActions = require('../pusher/actions.js');
|
||||
const googleActions = require('../google/actions.js');
|
||||
const lastfmActions = require('../lastfm/actions.js');
|
||||
const geniusActions = require('../genius/actions.js');
|
||||
const discogsActions = require('../discogs/actions.js');
|
||||
|
||||
const MopidyMiddleware = (function () {
|
||||
@ -814,11 +812,7 @@ const MopidyMiddleware = (function () {
|
||||
store.dispatch(
|
||||
coreActions.loadItem(
|
||||
action.uri,
|
||||
false,
|
||||
{
|
||||
name: 'play',
|
||||
shuffle: action.shuffle,
|
||||
},
|
||||
{ full: true, callbackAction: { name: 'play', shuffle: action.shuffle } },
|
||||
),
|
||||
);
|
||||
break;
|
||||
@ -1506,7 +1500,8 @@ const MopidyMiddleware = (function () {
|
||||
);
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_TRACKS':
|
||||
case 'MOPIDY_GET_TRACKS': {
|
||||
const { options: { full } } = action;
|
||||
request(store, 'library.lookup', { uris: action.uris })
|
||||
.then(
|
||||
(_response) => {
|
||||
@ -1516,9 +1511,17 @@ const MopidyMiddleware = (function () {
|
||||
));
|
||||
|
||||
store.dispatch(coreActions.itemsLoaded(tracks));
|
||||
store.dispatch(mopidyActions.getImages(arrayOf('uri', tracks)));
|
||||
|
||||
if (action.get_images) {
|
||||
store.dispatch(mopidyActions.getImages(arrayOf('uri', tracks)));
|
||||
if (full) {
|
||||
tracks.forEach((track) => {
|
||||
if (store.getState().lastfm.authorization) {
|
||||
store.dispatch(lastfmActions.getTrack(track.uri));
|
||||
}
|
||||
if (store.getState().genius.authorization) {
|
||||
store.dispatch(geniusActions.findTrackLyrics(track.uri));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
@ -1529,6 +1532,7 @@ const MopidyMiddleware = (function () {
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'VIEW__GET_RANDOM_TRACKS':
|
||||
request(store, 'library.browse', { uri: 'local:directory?type=track' })
|
||||
@ -1786,41 +1790,61 @@ const MopidyMiddleware = (function () {
|
||||
|
||||
request(store, 'library.browse', { uri: store.getState().mopidy.library_albums_uri })
|
||||
.then((browseResponse) => {
|
||||
const uris = arrayOf('uri', browseResponse);
|
||||
const allUris = arrayOf('uri', browseResponse);
|
||||
|
||||
store.dispatch(
|
||||
uiActions.updateProcess(
|
||||
action.type,
|
||||
{
|
||||
total: uris.length,
|
||||
remaining: uris.length,
|
||||
},
|
||||
),
|
||||
);
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
action.type,
|
||||
{
|
||||
remaining: allUris.length,
|
||||
total: allUris.length,
|
||||
},
|
||||
));
|
||||
|
||||
request(store, 'library.lookup', { uris })
|
||||
.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),
|
||||
}));
|
||||
const run = () => {
|
||||
if (allUris.length) {
|
||||
const uris = allUris.splice(0, 100);
|
||||
const processor = store.getState().ui.processes[action.type];
|
||||
|
||||
store.dispatch(coreActions.itemsLoaded(libraryAlbums));
|
||||
if (processor && processor.status === 'cancelling') {
|
||||
store.dispatch(uiActions.processCancelled(action.type));
|
||||
store.dispatch(uiActions.stopLoading('mopidy:library:albums'));
|
||||
return;
|
||||
}
|
||||
store.dispatch(uiActions.updateProcess(action.type, { remaining: allUris.length }));
|
||||
|
||||
request(store, 'library.lookup', { uris })
|
||||
.then(
|
||||
(lookupResponse) => {
|
||||
const libraryItems = 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),
|
||||
}));
|
||||
|
||||
if (libraryItems.length) {
|
||||
store.dispatch(coreActions.itemsLoaded(libraryItems));
|
||||
}
|
||||
run();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
store.dispatch(coreActions.libraryLoaded({
|
||||
uri: 'mopidy:library:albums',
|
||||
items_uris: arrayOf('uri', libraryAlbums),
|
||||
items_uris: arrayOf('uri', browseResponse),
|
||||
}));
|
||||
store.dispatch(uiActions.processFinished(action.type));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
});
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_LIBRARY_TRACKS':
|
||||
store.dispatch(uiActions.startProcess(action.type, { notification: false }));
|
||||
|
||||
request(store, 'library.browse', { uri: 'local:directory?type=track' })
|
||||
request(store, 'library.browse', { uri: store.getState().mopidy.library_tracks_uri })
|
||||
.then((browseResponse) => {
|
||||
const allUris = arrayOf('uri', browseResponse);
|
||||
|
||||
|
||||
@ -119,16 +119,27 @@ const PusherMiddleware = (function () {
|
||||
|
||||
// Local scan
|
||||
case 'local_scan_started':
|
||||
store.dispatch(uiActions.updateProcess('local_scan', 'Scanning local library'));
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'local_scan', { content: 'Scanning local library' },
|
||||
));
|
||||
break;
|
||||
case 'local_scan_updated':
|
||||
store.dispatch(uiActions.updateProcess('local_scan', 'Scanning local library', {}, params.output));
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'local_scan',
|
||||
{
|
||||
content: 'Scanning local library',
|
||||
data: {},
|
||||
description: params.output,
|
||||
},
|
||||
));
|
||||
break;
|
||||
case 'local_scan_finished':
|
||||
store.dispatch(uiActions.processFinished(
|
||||
'local_scan',
|
||||
{
|
||||
content: 'Local scan finished', description: params.output, sticky: true,
|
||||
content: 'Local scan finished',
|
||||
description: params.output,
|
||||
sticky: true,
|
||||
},
|
||||
));
|
||||
break;
|
||||
@ -143,13 +154,17 @@ const PusherMiddleware = (function () {
|
||||
|
||||
// Upgrade
|
||||
case 'upgrade_started':
|
||||
store.dispatch(uiActions.updateProcess('upgrade', 'Upgrading'));
|
||||
store.dispatch(uiActions.updateProcess('upgrade', { content: 'Upgrading' }));
|
||||
break;
|
||||
case 'upgrade_updated':
|
||||
store.dispatch(uiActions.updateProcess('upgrade', 'Upgrading', {}, params.output));
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'upgrade', { content: 'Upgrading', data: {}, description: params.output },
|
||||
));
|
||||
break;
|
||||
case 'upgrade_finished':
|
||||
store.dispatch(uiActions.updateProcess('upgrade', 'Restarting to complete upgrade'));
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'upgrade', { content: 'Restarting to complete upgrade' },
|
||||
));
|
||||
break;
|
||||
case 'upgrade_error':
|
||||
store.dispatch(uiActions.processFinished(
|
||||
@ -162,10 +177,12 @@ const PusherMiddleware = (function () {
|
||||
|
||||
// Restart
|
||||
case 'restart_started':
|
||||
store.dispatch(uiActions.removeProcess('upgrade', 'Restarting'));
|
||||
store.dispatch(uiActions.removeProcess('upgrade', { content: 'Restarting' }));
|
||||
break;
|
||||
case 'restart_updated':
|
||||
store.dispatch(uiActions.updateProcess('upgrade', 'Restarting', {}, params.output));
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'upgrade', { content: 'Restarting', data: {}, description: params.output },
|
||||
));
|
||||
break;
|
||||
case 'restart_error':
|
||||
store.dispatch(uiActions.processFinished(
|
||||
@ -178,16 +195,20 @@ const PusherMiddleware = (function () {
|
||||
|
||||
// Test
|
||||
case 'test_started':
|
||||
store.dispatch(uiActions.updateProcess('test', 'Running test', {}, params.output));
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
'test', { content: 'Running test', data: {}, description: params.output },
|
||||
));
|
||||
break;
|
||||
case 'test_updated':
|
||||
store.dispatch(uiActions.updateProcess('test', 'Running test'));
|
||||
store.dispatch(uiActions.updateProcess('test', { content: 'Running test' }));
|
||||
break;
|
||||
case 'test_finished':
|
||||
store.dispatch(uiActions.processFinished(
|
||||
'test',
|
||||
{
|
||||
content: 'Test finished', description: params.output, sticky: true,
|
||||
content: 'Test finished',
|
||||
description: params.output,
|
||||
sticky: true,
|
||||
},
|
||||
));
|
||||
break;
|
||||
|
||||
@ -31,6 +31,7 @@ const coreActions = require('../../services/core/actions');
|
||||
const uiActions = require('../../services/ui/actions');
|
||||
const mopidyActions = require('../../services/mopidy/actions');
|
||||
const lastfmActions = require('../../services/lastfm/actions');
|
||||
const geniusActions = require('../../services/genius/actions');
|
||||
|
||||
/**
|
||||
* Send an ajax request to the Spotify API
|
||||
@ -326,43 +327,28 @@ export function getMe() {
|
||||
};
|
||||
}
|
||||
|
||||
export function getTrack(uri, { forceRefetch, full }) {
|
||||
return (dispatch, getState) => {
|
||||
let endpoint = `tracks/${getFromUri('trackid', uri)}`;
|
||||
if (forceRefetch) endpoint += `?refetch=${Date.now()}`;
|
||||
|
||||
/**
|
||||
* Get a single track
|
||||
*
|
||||
* @param uri string
|
||||
* */
|
||||
export function getTrack(uri) {
|
||||
return (dispatch, getState) => {
|
||||
request(dispatch, getState, `tracks/${getFromUri('trackid', uri)}`)
|
||||
request(dispatch, getState, endpoint)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch(coreActions.itemLoaded(formatTrack(response)));
|
||||
const track = formatTrack(response);
|
||||
dispatch(coreActions.itemLoaded(track));
|
||||
if (full) {
|
||||
if (getState().lastfm.authorization) {
|
||||
dispatch(lastfmActions.getTrack(uri));
|
||||
}
|
||||
if (getState().genius.authorization) {
|
||||
dispatch(geniusActions.findTrackLyrics(uri));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
/*
|
||||
export function getLibraryTracks() {
|
||||
return (dispatch, getState) => {
|
||||
request(dispatch, getState, 'me/tracks?limit=50')
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_LIBRARY_TRACKS_LOADED',
|
||||
data: response,
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not get library tracks',
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
export function getFeaturedPlaylists(forceRefetch = false) {
|
||||
return (dispatch, getState) => {
|
||||
@ -422,7 +408,12 @@ export function getFeaturedPlaylists(forceRefetch = false) {
|
||||
|
||||
export function getCategories() {
|
||||
return (dispatch, getState) => {
|
||||
request(dispatch, getState, `browse/categories?limit=50&country=${getState().spotify.country}&locale=${getState().spotify.locale}`)
|
||||
let endpoint = 'browse/categories';
|
||||
endpoint += '?limit=50';
|
||||
endpoint += `&country=${getState().spotify.country}`;
|
||||
endpoint += `&locale=${getState().spotify.locale}`;
|
||||
|
||||
request(dispatch, getState, endpoint)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch({
|
||||
@ -1328,6 +1319,12 @@ export function getPlaylist(uri, { full, forceRefetch, callbackAction } = {}) {
|
||||
endpoint += `?market=${getState().spotify.country}`;
|
||||
if (forceRefetch) endpoint += `&refetch=${Date.now()}`;
|
||||
|
||||
// TODO
|
||||
// When we have a callbackAction, start the process. To do this:
|
||||
// 1. Create unified callbackAction naming convention
|
||||
// 2. Start process here
|
||||
// 3. Update process as tracks are loaded and enqueue occurs
|
||||
|
||||
request(dispatch, getState, endpoint)
|
||||
.then(
|
||||
(response) => {
|
||||
@ -1404,89 +1401,6 @@ export function getPlaylist(uri, { full, forceRefetch, callbackAction } = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all library tracks
|
||||
*
|
||||
* Recursively get .next until we have all tracks
|
||||
* */
|
||||
|
||||
export function getLibraryTracksAndPlay(uri) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(uiActions.startProcess(
|
||||
'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR',
|
||||
'Loading library tracks',
|
||||
{
|
||||
uri,
|
||||
next: 'me/tracks',
|
||||
},
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
export function getLibraryTracksAndPlayProcessor(data) {
|
||||
return (dispatch, getState) => {
|
||||
request(dispatch, getState, data.next)
|
||||
.then(
|
||||
(response) => {
|
||||
// Check to see if we've been cancelled
|
||||
if (getState().ui.processes.SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR !== undefined) {
|
||||
const processor = getState().ui.processes.SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR;
|
||||
|
||||
if (processor.status == 'cancelling') {
|
||||
dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add on our new batch of loaded tracks
|
||||
let uris = [];
|
||||
const new_uris = [];
|
||||
for (let i = 0; i < response.items.length; i++) {
|
||||
new_uris.push(response.items[i].track.uri);
|
||||
}
|
||||
if (data.uris) {
|
||||
uris = [...data.uris, ...new_uris];
|
||||
} else {
|
||||
uris = new_uris;
|
||||
}
|
||||
|
||||
// We got a next link, so we've got more work to be done
|
||||
if (response.next) {
|
||||
dispatch(uiActions.updateProcess(
|
||||
'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR',
|
||||
`Loading ${response.total - uris.length} library tracks`,
|
||||
{
|
||||
next: response.next,
|
||||
total: response.total,
|
||||
remaining: response.total - uris.length,
|
||||
},
|
||||
));
|
||||
dispatch(uiActions.runProcess(
|
||||
'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR',
|
||||
{
|
||||
next: response.next,
|
||||
uris,
|
||||
},
|
||||
));
|
||||
} else {
|
||||
dispatch(mopidyActions.playURIs(uris, data.uri));
|
||||
dispatch(uiActions.processFinished('SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR'));
|
||||
}
|
||||
},
|
||||
() => {
|
||||
dispatch(uiActions.processFinished(
|
||||
'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR',
|
||||
{
|
||||
content: 'Could not load library tracks',
|
||||
level: 'error',
|
||||
},
|
||||
));
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function addTracksToPlaylist(uri, tracks_uris) {
|
||||
return (dispatch, getState) => {
|
||||
request(dispatch, getState, `playlists/${getFromUri('playlistid', uri)}/tracks`, 'POST', { uris: tracks_uris })
|
||||
|
||||
@ -80,7 +80,7 @@ const SpotifyMiddleware = (function () {
|
||||
|
||||
case 'SPOTIFY_USER_LOADED':
|
||||
if (store.getState().ui.allow_reporting && action.data) {
|
||||
ReactGA.event({ category: 'User', action: 'Load', label: action.data.uri });
|
||||
ReactGA.event({ category: 'User', action: 'Load', label: action.data.uri });
|
||||
}
|
||||
next(action);
|
||||
break;
|
||||
@ -154,142 +154,6 @@ const SpotifyMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR':
|
||||
const playlistProcessor = store.getState().ui.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR || {};
|
||||
switch (playlistProcessor.status) {
|
||||
case 'cancelling':
|
||||
store.dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR'));
|
||||
break;
|
||||
|
||||
case 'cancelled':
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(spotifyActions.getLibraryPlaylistsProcessor(action.data));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED':
|
||||
var playlists = [];
|
||||
for (var playlist of action.playlists) {
|
||||
Object.assign(
|
||||
playlist,
|
||||
{
|
||||
uri: playlist.uri.replace(/spotify:user:([^:]*?):/i, 'spotify:'),
|
||||
can_edit: (store.getState().spotify.me && store.getState().spotify.me.id == playlist.owner.id),
|
||||
source: 'spotify',
|
||||
in_library: true, // assumed because we asked for library items
|
||||
tracks_total: playlist.tracks.total,
|
||||
},
|
||||
);
|
||||
|
||||
// remove our tracklist. It'll overwrite any full records otherwise
|
||||
delete playlist.tracks;
|
||||
|
||||
playlists.push(playlist);
|
||||
}
|
||||
|
||||
store.dispatch({
|
||||
type: 'PLAYLISTS_LOADED',
|
||||
playlists,
|
||||
});
|
||||
|
||||
// Append our action with the uris. This gets handed down to subsequent middleware and our reducer.
|
||||
action.uris = arrayOf('uri', playlists);
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR':
|
||||
const artistsProcessor = store.getState().ui.processes.SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR || {};
|
||||
switch (artistsProcessor.status) {
|
||||
case 'cancelling':
|
||||
store.dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_ARTISTS_PROCESSOR'));
|
||||
break;
|
||||
|
||||
case 'cancelled':
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(spotifyActions.getLibraryArtistsProcessor(action.data));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ARTISTS_LOADED':
|
||||
var artists = [];
|
||||
for (var i = 0; i < action.artists.length; i++) {
|
||||
artists.push(
|
||||
{
|
||||
|
||||
...action.artists[i],
|
||||
source: 'spotify',
|
||||
in_library: true, // assumed because we asked for library items
|
||||
|
||||
},
|
||||
);
|
||||
}
|
||||
store.dispatch({
|
||||
type: 'ARTISTS_LOADED',
|
||||
artists,
|
||||
});
|
||||
|
||||
// Append our action with the uris. This gets handed down to subsequent middleware and our reducer.
|
||||
action.uris = arrayOf('uri', artists);
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR':
|
||||
const albumsProcessor = store.getState().ui.processes.SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR || {};
|
||||
switch (albumsProcessor.status) {
|
||||
case 'cancelling':
|
||||
store.dispatch(uiActions.processCancelled('SPOTIFY_GET_LIBRARY_ALBUMS_PROCESSOR'));
|
||||
break;
|
||||
|
||||
case 'cancelled':
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(spotifyActions.getLibraryAlbumsProcessor(action.data));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR':
|
||||
store.dispatch(spotifyActions.getLibraryTracksAndPlayProcessor(action.data));
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR':
|
||||
store.dispatch(spotifyActions.getAllPlaylistTracksProcessor(action.data));
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED':
|
||||
var albums = [];
|
||||
for (var i = 0; i < action.albums.length; i++) {
|
||||
albums.push(
|
||||
{
|
||||
|
||||
...action.albums[i].album,
|
||||
in_library: true, // assumed because we asked for library items
|
||||
source: 'spotify',
|
||||
added_at: action.albums[i].added_at,
|
||||
tracks: action.albums[i].album.tracks.items,
|
||||
tracks_more: action.albums[i].album.tracks.next,
|
||||
tracks_total: action.albums[i].album.tracks.total,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
store.dispatch({
|
||||
type: 'ALBUMS_LOADED',
|
||||
albums,
|
||||
});
|
||||
|
||||
// Append our action with the uris. This gets handed down to subsequent middleware and our reducer.
|
||||
action.uris = arrayOf('uri', albums);
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_FAVORITES_LOADED':
|
||||
if (action.artists.length > 0) {
|
||||
store.dispatch({
|
||||
@ -308,17 +172,6 @@ const SpotifyMiddleware = (function () {
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_LIBRARY_TRACKS_LOADED':
|
||||
case 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE':
|
||||
if (action.data) {
|
||||
store.dispatch({
|
||||
type: 'TRACKS_LOADED',
|
||||
tracks: action.data.items,
|
||||
});
|
||||
}
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_TRACK_LOADED':
|
||||
store.dispatch({
|
||||
type: 'TRACKS_LOADED',
|
||||
@ -336,17 +189,18 @@ const SpotifyMiddleware = (function () {
|
||||
}
|
||||
|
||||
if (store.getState().ui.allow_reporting) {
|
||||
const hashed_username = sha256(me.id);
|
||||
ReactGA.set({ userId: hashed_username });
|
||||
ReactGA.event({ category: 'Spotify', action: 'Authorization verified', label: hashed_username });
|
||||
}
|
||||
const hashed_username = sha256(me.id);
|
||||
ReactGA.set({ userId: hashed_username });
|
||||
ReactGA.event({ category: 'Spotify', action: 'Authorization verified', label: hashed_username });
|
||||
}
|
||||
|
||||
store.dispatch(coreActions.userLoaded(me));
|
||||
action.me = me;
|
||||
next(action);
|
||||
next({
|
||||
...action,
|
||||
me,
|
||||
});
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@ -312,11 +312,11 @@ export function processCancelled(key) {
|
||||
};
|
||||
}
|
||||
|
||||
export function processFinished(key, completionMessage = null) {
|
||||
export function processFinished(key, completionNotification) {
|
||||
return {
|
||||
type: 'PROCESS_FINISHED',
|
||||
key,
|
||||
completionMessage,
|
||||
completionNotification,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -195,17 +195,14 @@ const UIMiddleware = (function () {
|
||||
type: `${action.key}_FINISHED`,
|
||||
});
|
||||
|
||||
// start a timeout to remove this notification
|
||||
// Start a timeout to remove this notification
|
||||
// This gives us time to animate out the notification before we remove the data
|
||||
if (action.completionMessage) {
|
||||
if (action.completionNotification) {
|
||||
store.dispatch(uiActions.updateProcess(
|
||||
action.key,
|
||||
action.completionMessage.content,
|
||||
{},
|
||||
action.completionMessage.description,
|
||||
action.completionMessage.level,
|
||||
action.completionNotification,
|
||||
));
|
||||
if (!action.completionMessage.sticky) {
|
||||
if (!action.completionNotification.sticky) {
|
||||
setTimeout(
|
||||
() => {
|
||||
store.dispatch(uiActions.closeProcess(action.key));
|
||||
|
||||
@ -139,7 +139,7 @@ export default function reducer(ui = {}, action) {
|
||||
const processes = { ...(ui.processes || []) };
|
||||
processes[action.key] = {
|
||||
key: action.key,
|
||||
...processes[action.key] || { notification: true },
|
||||
...processes[action.key] || { status: 'running', notification: true },
|
||||
...(action.type === 'START_PROCESS' ? { status: 'running', remaining: 0, total: 0 } : {}),
|
||||
...action.process,
|
||||
};
|
||||
|
||||
@ -85,6 +85,7 @@ let state = {
|
||||
uri_schemes: [],
|
||||
library_albums_uri: 'local:directory?type=album',
|
||||
library_artists_uri: 'local:directory?type=artist&role=albumartist',
|
||||
library_tracks_uri: 'local:directory?type=track',
|
||||
},
|
||||
pusher: {
|
||||
connected: false,
|
||||
|
||||
@ -186,7 +186,12 @@ class Debug extends React.Component {
|
||||
<I18n path="debug.create_notification" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => this.props.uiActions.startProcess('test_process', "Test process", {remaining: 68, total: 100})}
|
||||
onClick={
|
||||
() => this.props.uiActions.startProcess(
|
||||
'TEST_PROCESS',
|
||||
{ content: 'Test process', remaining: 68, total: 100 },
|
||||
)
|
||||
}
|
||||
tracking={{ category: 'Debug', action: 'Test process' }}
|
||||
>
|
||||
<I18n path="debug.create_process" />
|
||||
|
||||
@ -32,6 +32,7 @@ class Settings extends React.Component {
|
||||
mopidy_port: this.props.mopidy.port,
|
||||
mopidy_library_artists_uri: this.props.mopidy.library_artists_uri,
|
||||
mopidy_library_albums_uri: this.props.mopidy.library_albums_uri,
|
||||
mopidy_library_tracks_uri: this.props.mopidy.library_tracks_uri,
|
||||
pusher_username: this.props.pusher.username,
|
||||
input_in_focus: null,
|
||||
};
|
||||
@ -485,6 +486,23 @@ class Settings extends React.Component {
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="field">
|
||||
<div className="name">
|
||||
<I18n path="settings.advanced.track_uri.label" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
type="text"
|
||||
value={this.state.mopidy_library_tracks_uri}
|
||||
onChange={(value) => this.onMopidySettingChanged('mopidy_library_tracks_uri', value)}
|
||||
autosave
|
||||
/>
|
||||
<div className="description">
|
||||
<I18n path="settings.advanced.track_uri.description" />
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div className="field pusher-connections">
|
||||
<div className="name">
|
||||
<I18n path="settings.advanced.connections.label" />
|
||||
|
||||
@ -38,18 +38,12 @@ class Track extends React.Component {
|
||||
coreActions: {
|
||||
loadItem,
|
||||
},
|
||||
genius_authorized,
|
||||
geniusActions: { findTrackLyrics },
|
||||
} = this.props;
|
||||
|
||||
loadItem(uri);
|
||||
|
||||
if (track) {
|
||||
this.setWindowTitle(track);
|
||||
|
||||
if (genius_authorized && track.artists && !track.lyrics_results) {
|
||||
findTrackLyrics(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,10 +69,6 @@ class Track extends React.Component {
|
||||
|
||||
if (prevUri !== uri) {
|
||||
loadItem(uri);
|
||||
|
||||
if (genius_authorized && track.artists) {
|
||||
findTrackLyrics(track);
|
||||
}
|
||||
}
|
||||
|
||||
// We have just received our full track or our track artists
|
||||
|
||||
@ -248,16 +248,8 @@ class LibraryTracks extends React.Component {
|
||||
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'),
|
||||
value: 'album.name',
|
||||
label: i18n('fields.filters.album'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -32,7 +32,6 @@ const LyricsScroller = ({ content = '', time_position = 1, duration = 100 }) =>
|
||||
const Lyrics = ({
|
||||
show_lyrics,
|
||||
load_queue,
|
||||
genius_authorized,
|
||||
time_position = null,
|
||||
current_track,
|
||||
}) => {
|
||||
@ -49,17 +48,6 @@ const Lyrics = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!genius_authorized) {
|
||||
return (
|
||||
<p className="no-results">
|
||||
<I18n path="services.genius.want_lyrics" />
|
||||
<Link to="/settings/genius" scrollTo="#services-menu">
|
||||
<I18n path="settings.title" />
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
if (lyrics) {
|
||||
return (
|
||||
<LyricsScroller
|
||||
@ -144,6 +132,10 @@ class KioskMode extends React.Component {
|
||||
current_track,
|
||||
} = this.props;
|
||||
|
||||
if (!genius_authorized) {
|
||||
uiActions.createNotification({ level: 'warning', content: `${i18n('track.want_lyrics')} ${i18n('settings.title')}` });
|
||||
return;
|
||||
}
|
||||
uiActions.set({ show_lyrics: !show_lyrics });
|
||||
if (
|
||||
!show_lyrics
|
||||
@ -171,6 +163,7 @@ class KioskMode extends React.Component {
|
||||
genius_authorized,
|
||||
time_position,
|
||||
} = this.props;
|
||||
const lyrics_enabled = show_lyrics && genius_authorized;
|
||||
if (current_track && current_track.images) {
|
||||
var { images } = current_track;
|
||||
} else {
|
||||
@ -179,7 +172,7 @@ class KioskMode extends React.Component {
|
||||
|
||||
const extraControls = (
|
||||
<div className="control" onClick={this.toggleLyrics}>
|
||||
{show_lyrics ? <Icon name="toggle_on" className="turquoise-text" />
|
||||
{lyrics_enabled ? <Icon name="toggle_on" className="turquoise-text" />
|
||||
: <Icon name="toggle_off" />}
|
||||
<div style={{ paddingLeft: '6px', fontWeight: 'bold' }}>
|
||||
<I18n path="modal.kiosk.lyrics" />
|
||||
@ -194,7 +187,7 @@ class KioskMode extends React.Component {
|
||||
>
|
||||
<Thumbnail className="background" images={images} placeholder={false} />
|
||||
|
||||
<div className={`player player--${show_lyrics ? 'with' : 'without'}-lyrics`}>
|
||||
<div className={`player player--${lyrics_enabled ? 'with' : 'without'}-lyrics`}>
|
||||
|
||||
<div className="track">
|
||||
<div className="track__artwork">
|
||||
@ -224,7 +217,7 @@ class KioskMode extends React.Component {
|
||||
</div>
|
||||
|
||||
<Lyrics
|
||||
show_lyrics={show_lyrics}
|
||||
show_lyrics={lyrics_enabled}
|
||||
load_queue={load_queue}
|
||||
genius_authorized={genius_authorized}
|
||||
time_position={time_position}
|
||||
|
||||
Reference in New Issue
Block a user