Tracks using action.options; Loader progress now fully svg

This commit is contained in:
James Barnsley
2020-10-26 21:32:53 +13:00
parent df9749c677
commit c5c2e9d4bb
30 changed files with 642 additions and 922 deletions

View File

@ -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>
);
}

View File

@ -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}>

View File

@ -176,7 +176,7 @@ class Notifications extends React.Component {
remaining,
level = 'info',
content,
description = null,
description,
status,
closing,
key,

View File

@ -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:

View File

@ -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);

View File

@ -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;

View File

@ -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',

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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 })

View File

@ -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);
}

View File

@ -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,
};
}

View File

@ -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));

View File

@ -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,
};

View File

@ -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,

View File

@ -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" />

View File

@ -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" />

View File

@ -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

View File

@ -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'),
},
];

View File

@ -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}