Merge branch 'develop'

This commit is contained in:
James Barnsley
2018-06-26 21:14:09 +12:00
18 changed files with 195 additions and 74 deletions

View File

@ -2,8 +2,8 @@
enabled = true
country = NZ
locale = en_NZ
spotify_authorization_url = https://jamesbarnsley.co.nz/auth_spotify.php
lastfm_authorization_url = https://jamesbarnsley.co.nz/auth_lastfm.php
spotify_authorization_url = https://jamesbarnsley.co.nz/iris/auth_spotify.php
lastfm_authorization_url = https://jamesbarnsley.co.nz/iris/auth_lastfm.php
snapcast_enabled = false
snapcast_host = localhost
snapcast_port = 1705

View File

@ -794,6 +794,23 @@ var sortItems = exports.sortItems = function sortItems(array, property) {
return sorted;
};
/**
* Shuffle items in place
*
* @param Array items
* @return Array
**/
var shuffle = exports.shuffle = function shuffle(array) {
var j, x, i;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = array[i];
array[i] = array[j];
array[j] = x;
}
return array;
};
/**
* Figure out if a value is a number
* @param value = mixed
@ -3055,11 +3072,12 @@ function getLibraryTracksAndPlayProcessor(data) {
* Recursively get .next until we have all tracks
**/
function getPlaylistTracksAndPlay(uri) {
function getPlaylistTracksAndPlay(uri, shuffle) {
return function (dispatch, getState) {
dispatch(uiActions.startProcess('SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR', 'Loading playlist tracks', {
uri: uri,
next: 'users/' + helpers.getFromUri('userid', uri) + '/playlists/' + helpers.getFromUri('playlistid', uri) + '/tracks?market=' + getState().spotify.country
next: 'users/' + helpers.getFromUri('userid', uri) + '/playlists/' + helpers.getFromUri('playlistid', uri) + '/tracks?market=' + getState().spotify.country,
shuffle: shuffle
}));
};
}
@ -3095,13 +3113,20 @@ function getPlaylistTracksAndPlayProcessor(data) {
dispatch(uiActions.updateProcess('SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR', 'Loading ' + (response.total - uris.length) + ' playlist tracks', {
next: response.next,
total: response.total,
remaining: response.total - uris.length
remaining: response.total - uris.length,
shuffle: data.shuffle
}));
dispatch(uiActions.runProcess('SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR', {
next: response.next,
uris: uris
uris: uris,
shuffle: data.shuffle
}));
} else {
if (data.shuffle) {
uris = helpers.shuffle(uris);
}
dispatch(mopidyActions.playURIs(uris, data.uri));
dispatch(uiActions.processFinishing('SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR'));
}
@ -3692,9 +3717,12 @@ function enqueueURIsBatchDone() {
}
function playPlaylist(uri) {
var shuffle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return {
type: 'MOPIDY_PLAY_PLAYLIST',
uri: uri
uri: uri,
shuffle: shuffle
};
}
@ -4255,6 +4283,7 @@ exports.startSearch = startSearch;
exports.handleException = handleException;
exports.debugResponse = debugResponse;
exports.set = set;
exports.clearCurrentTrack = clearCurrentTrack;
exports.reorderPlaylistTracks = reorderPlaylistTracks;
exports.savePlaylist = savePlaylist;
exports.createPlaylist = createPlaylist;
@ -4340,6 +4369,12 @@ function set(data) {
};
}
function clearCurrentTrack() {
return {
type: 'CLEAR_CURRENT_TRACK'
};
}
/**
* Playlist manipulation
**/
@ -49403,13 +49438,13 @@ var initialState = {
},
lastfm: {
me: false,
authorization_url: 'https://jamesbarnsley.co.nz/auth_lastfm.php'
authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_lastfm.php'
},
genius: {},
spotify: {
me: false,
autocomplete_results: {},
authorization_url: 'https://jamesbarnsley.co.nz/auth_spotify.php'
authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_spotify.php'
}
};
@ -50976,6 +51011,7 @@ var CoreMiddleware = function () {
// Set our window title to the track title
helpers.setWindowTitle(null, store.getState().mopidy.play_state);
next(action);
break;
case 'QUEUE_LOADED':
@ -52826,14 +52862,14 @@ var MopidyMiddleware = function () {
// make sure we didn't get this playlist from Mopidy-Spotify
// if we did, we'd have a cached version on server so no need to fetch
if (!store.getState().core.playlists[action.uri].is_mopidy) {
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri));
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri, action.shuffle));
break;
}
// it's a spotify playlist that we haven't loaded
// we need to fetch via HTTP API to avoid timeout
} else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled) {
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri));
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri, action.shuffle));
break;
// Not in index, and Spotify HTTP not enabled, so just play it as-is
@ -52846,6 +52882,9 @@ var MopidyMiddleware = function () {
store.dispatch(uiActions.createNotification({ content: 'Failed to load playlist tracks', type: 'bad' }));
} else {
var tracks_uris = helpers.arrayOf('uri', response.tracks);
if (action.shuffle) {
tracks_uris = helpers.shuffle(tracks_uris);
}
store.dispatch(mopidyActions.playURIs(tracks_uris, action.uri));
}
}, function (error) {
@ -53036,13 +53075,16 @@ var MopidyMiddleware = function () {
break;
case 'MOPIDY_CLEAR_TRACKLIST':
request(socket, store, 'tracklist.clear');
store.dispatch(pusherActions.deliverBroadcast('notification', {
notification: {
type: 'info',
content: store.getState().pusher.username + ' cleared queue'
}
}));
request(socket, store, 'tracklist.clear').then(function (response) {
store.dispatch(coreActions.clearCurrentTrack());
store.dispatch(pusherActions.deliverBroadcast('notification', {
notification: {
type: 'info',
content: store.getState().pusher.username + ' cleared queue'
}
}));
});
break;
/**
@ -53512,8 +53554,6 @@ var MopidyMiddleware = function () {
key: playlist.uri,
playlist: playlist
});
console.log(playlist);
});
}
});
@ -53637,13 +53677,12 @@ var MopidyMiddleware = function () {
case 'MOPIDY_SAVE_PLAYLIST':
var uri = action.key;
request(socket, store, 'playlists.lookup', { uri: action.key }).then(function (response) {
var playlist = Object.assign({}, response, { name: action.name });
request(socket, store, 'playlists.save', { playlist: playlist }).then(function (response) {
store.dispatch({
type: 'PLAYLIST_UPDATED',
type: 'PLAYLIST_LOADED',
key: action.key,
playlist: playlist
});
@ -54086,7 +54125,8 @@ var MopidyMiddleware = function () {
store.dispatch({
type: 'CURRENT_TRACK_LOADED',
track: track
track: track,
uri: track.uri
});
break;
@ -59872,6 +59912,12 @@ var ContextMenu = function (_React$Component) {
this.props.uiActions.hideContextMenu();
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]);
}
}, {
key: 'shufflePlayPlaylist',
value: function shufflePlayPlaylist(e) {
this.props.uiActions.hideContextMenu();
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0], true);
}
}, {
key: 'playArtistTopTracks',
value: function playArtistTopTracks(e) {
@ -60196,6 +60242,22 @@ var ContextMenu = function (_React$Component) {
)
);
var shuffle_play_playlist = _react2.default.createElement(
'span',
{ className: 'menu-item-wrapper' },
_react2.default.createElement(
'a',
{ className: 'menu-item', onClick: function onClick(e) {
return _this3.shufflePlayPlaylist(e);
} },
_react2.default.createElement(
'span',
{ className: 'label' },
'Shuffle play'
)
)
);
var play_queue_item = _react2.default.createElement(
'span',
{ className: 'menu-item-wrapper' },
@ -60526,6 +60588,7 @@ var ContextMenu = function (_React$Component) {
'div',
null,
play_playlist,
shuffle_play_playlist,
this.canBeInLibrary() ? _react2.default.createElement('div', { className: 'divider' }) : null,
this.canBeInLibrary() ? toggle_in_library : null,
_react2.default.createElement('div', { className: 'divider' }),
@ -60539,6 +60602,7 @@ var ContextMenu = function (_React$Component) {
'div',
null,
play_playlist,
shuffle_play_playlist,
this.canBeInLibrary() ? _react2.default.createElement('div', { className: 'divider' }) : null,
this.canBeInLibrary() ? toggle_in_library : null,
_react2.default.createElement('div', { className: 'divider' }),
@ -66073,7 +66137,7 @@ var Queue = function (_React$Component) {
}
var current_track_image = null;
if (current_track) {
if (current_track && this.props.current_track_uri) {
if (current_track.images !== undefined && current_track.images) {
current_track_image = helpers.sizedImages(current_track.images).large;
}
@ -66197,6 +66261,7 @@ var mapStateToProps = function mapStateToProps(state, ownProps) {
queue: state.core.queue,
queue_tlids: state.core.queue_tlids,
queue_metadata: state.core.queue_metadata,
current_track_uri: state.core.current_track_uri,
current_track: state.core.current_track
};
};
@ -75906,7 +75971,7 @@ var LibraryArtists = function (_React$Component) {
if (newProps.mopidy_uri_schemes.includes('spotify:') && (newProps.source == 'all' || newProps.source == 'spotify')) {
// Filter changed, but we haven't got this provider's library yet
if (this.props.source != 'all' && this.props.source != 'spotify' && newProps.spotify_library_artists_status != 'finished') {
if (newProps.spotify_library_artists_status != 'finished' && newProps.spotify_library_artists_status != 'started') {
this.props.spotifyActions.getLibraryArtists();
}
}
@ -76298,7 +76363,7 @@ var LibraryAlbums = function (_React$Component) {
if (newProps.mopidy_uri_schemes.includes('spotify:') && (newProps.source == 'all' || newProps.source == 'spotify')) {
// Filter changed, but we haven't got this provider's library yet
if (this.props.source != 'all' && this.props.source != 'spotify' && newProps.spotify_library_albums_status != 'finished' && newProps.spotify_library_albums_status != 'started') {
if (newProps.spotify_library_albums_status != 'finished' && newProps.spotify_library_albums_status != 'started') {
this.props.spotifyActions.getLibraryAlbums();
}
}
@ -76886,7 +76951,7 @@ var LibraryPlaylists = function (_React$Component) {
if (newProps.mopidy_uri_schemes.includes('spotify:') && (newProps.source == 'all' || newProps.source == 'spotify')) {
// Filter changed, but we haven't got this provider's library yet
if (this.props.source != 'all' && this.props.source != 'spotify' && newProps.spotify_library_playlists_status !== 'finished') {
if (newProps.spotify_library_playlists_status != 'finished' && newProps.spotify_library_playlists_status != 'started') {
this.props.spotifyActions.getLibraryPlaylists();
}
}

File diff suppressed because one or more lines are too long

View File

@ -77,7 +77,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1529312086";
var build = "1530003896";
var version = "3.21.3";
// Construct the script tag

View File

@ -7,7 +7,7 @@ import { connect } from 'react-redux'
import ReactGA from 'react-ga'
import Sidebar from './components/Sidebar'
import PlaybackControls from './components/Fields/PlaybackControls'
import PlaybackControls from './components/PlaybackControls'
import ContextMenu from './components/ContextMenu'
import Dragger from './components/Dragger'
import Modal from './components/Modal/Modal'

4
src/js/bootstrap.js vendored
View File

@ -78,13 +78,13 @@ var initialState = {
},
lastfm: {
me: false,
authorization_url: 'https://jamesbarnsley.co.nz/auth_lastfm.php'
authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_lastfm.php'
},
genius: {},
spotify: {
me: false,
autocomplete_results: {},
authorization_url: 'https://jamesbarnsley.co.nz/auth_spotify.php'
authorization_url: 'https://jamesbarnsley.co.nz/iris/auth_spotify.php'
}
};

View File

@ -215,6 +215,11 @@ class ContextMenu extends React.Component{
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]);
}
shufflePlayPlaylist(e){
this.props.uiActions.hideContextMenu();
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0], true);
}
playArtistTopTracks(e){
this.props.uiActions.hideContextMenu();
this.props.spotifyActions.playArtistTopTracks(this.props.menu.uris[0]);
@ -454,6 +459,14 @@ class ContextMenu extends React.Component{
</span>
)
var shuffle_play_playlist = (
<span className="menu-item-wrapper">
<a className="menu-item" onClick={e => this.shufflePlayPlaylist(e)}>
<span className="label">Shuffle play</span>
</a>
</span>
)
var play_queue_item = (
<span className="menu-item-wrapper">
<a className="menu-item" onClick={e => this.playQueueItem(e)}>
@ -645,6 +658,7 @@ class ContextMenu extends React.Component{
return (
<div>
{play_playlist}
{shuffle_play_playlist}
{this.canBeInLibrary() ? <div className="divider" /> : null}
{this.canBeInLibrary() ? toggle_in_library : null}
<div className="divider" />
@ -658,6 +672,7 @@ class ContextMenu extends React.Component{
return (
<div>
{play_playlist}
{shuffle_play_playlist}
{this.canBeInLibrary() ? <div className="divider" /> : null}
{this.canBeInLibrary() ? toggle_in_library : null}
<div className="divider" />

View File

@ -4,18 +4,18 @@ import { connect } from 'react-redux'
import { Link } from 'react-router'
import { bindActionCreators } from 'redux'
import ProgressSlider from './ProgressSlider'
import VolumeControl from './VolumeControl'
import SnapcastVolumeControl from './SnapcastVolumeControl'
import Dater from './../Dater'
import ArtistSentence from './../ArtistSentence'
import Thumbnail from './../Thumbnail'
import Icon from './../Icon'
import ProgressSlider from './Fields/ProgressSlider'
import VolumeControl from './Fields/VolumeControl'
import SnapcastVolumeControl from './Fields/SnapcastVolumeControl'
import Dater from './Dater'
import ArtistSentence from './ArtistSentence'
import Thumbnail from './Thumbnail'
import Icon from './Icon'
import * as helpers from '../../helpers'
import * as uiActions from '../../services/ui/actions'
import * as coreActions from '../../services/core/actions'
import * as mopidyActions from '../../services/mopidy/actions'
import * as helpers from '../helpers'
import * as uiActions from '../services/ui/actions'
import * as coreActions from '../services/core/actions'
import * as mopidyActions from '../services/mopidy/actions'
class PlaybackControls extends React.Component{

View File

@ -715,6 +715,23 @@ export let sortItems = function (array, property, reverse = false, sort_map = nu
return sorted;
}
/**
* Shuffle items in place
*
* @param Array items
* @return Array
**/
export let shuffle = function(array){
var j, x, i;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = array[i];
array[i] = array[j];
array[j] = x;
}
return array;
}
/**
* Figure out if a value is a number
* @param value = mixed

View File

@ -66,6 +66,12 @@ export function set(data){
}
}
export function clearCurrentTrack(){
return {
type: 'CLEAR_CURRENT_TRACK'
}
}

View File

@ -349,6 +349,7 @@ const CoreMiddleware = (function(){
// Set our window title to the track title
helpers.setWindowTitle(null, store.getState().mopidy.play_state);
next(action);
break;
case 'QUEUE_LOADED':

View File

@ -246,10 +246,11 @@ export function enqueueURIsBatchDone(){
}
}
export function playPlaylist(uri){
export function playPlaylist(uri, shuffle = false){
return {
type: 'MOPIDY_PLAY_PLAYLIST',
uri: uri
uri: uri,
shuffle: shuffle
}
}

View File

@ -546,14 +546,14 @@ const MopidyMiddleware = (function(){
// make sure we didn't get this playlist from Mopidy-Spotify
// if we did, we'd have a cached version on server so no need to fetch
if (!store.getState().core.playlists[action.uri].is_mopidy){
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri))
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri, action.shuffle))
break
}
// it's a spotify playlist that we haven't loaded
// we need to fetch via HTTP API to avoid timeout
} else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled){
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri))
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri, action.shuffle))
break
// Not in index, and Spotify HTTP not enabled, so just play it as-is
@ -567,7 +567,10 @@ const MopidyMiddleware = (function(){
if (response.tracks === undefined){
store.dispatch(uiActions.createNotification({content: 'Failed to load playlist tracks', type: 'bad'}));
} else {
var tracks_uris = helpers.arrayOf('uri',response.tracks)
var tracks_uris = helpers.arrayOf('uri',response.tracks);
if (action.shuffle){
tracks_uris = helpers.shuffle(tracks_uris);
}
store.dispatch(mopidyActions.playURIs(tracks_uris, action.uri))
}
},
@ -804,16 +807,22 @@ const MopidyMiddleware = (function(){
break;
case 'MOPIDY_CLEAR_TRACKLIST':
request(socket, store, 'tracklist.clear');
store.dispatch(pusherActions.deliverBroadcast(
'notification',
{
notification: {
type: 'info',
content: store.getState().pusher.username +' cleared queue'
}
}
));
request(socket, store, 'tracklist.clear')
.then(
response => {
store.dispatch(coreActions.clearCurrentTrack());
store.dispatch(pusherActions.deliverBroadcast(
'notification',
{
notification: {
type: 'info',
content: store.getState().pusher.username +' cleared queue'
}
}
));
}
);
break;
@ -1396,8 +1405,6 @@ const MopidyMiddleware = (function(){
key: playlist.uri,
playlist: playlist
});
console.log(playlist);
})
}
})
@ -1528,19 +1535,18 @@ const MopidyMiddleware = (function(){
break
case 'MOPIDY_SAVE_PLAYLIST':
var uri = action.key
var uri = action.key;
request(socket, store, 'playlists.lookup', { uri: action.key })
.then(response => {
var playlist = Object.assign({}, response, { name: action.name })
request(socket, store, 'playlists.save', { playlist: playlist } )
request(socket, store, 'playlists.save', { playlist: playlist })
.then(response => {
store.dispatch({
type: 'PLAYLIST_UPDATED',
type: 'PLAYLIST_LOADED',
key: action.key,
playlist: playlist
})
});
// When we rename a playlist, the URI also changes to reflect the name change
// We need to update our index, as well as redirect our current page URL
@ -2050,7 +2056,8 @@ const MopidyMiddleware = (function(){
store.dispatch({
type: 'CURRENT_TRACK_LOADED',
track: track
track: track,
uri: track.uri
});
break;

View File

@ -1655,14 +1655,15 @@ export function getLibraryTracksAndPlayProcessor(data){
* Recursively get .next until we have all tracks
**/
export function getPlaylistTracksAndPlay(uri){
export function getPlaylistTracksAndPlay(uri, shuffle){
return (dispatch, getState) => {
dispatch(uiActions.startProcess(
'SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR',
'Loading playlist tracks',
{
uri: uri,
next: 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country
next: 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country,
shuffle: shuffle
}
))
}
@ -1704,17 +1705,24 @@ export function getPlaylistTracksAndPlayProcessor(data){
{
next: response.next,
total: response.total,
remaining: response.total - uris.length
remaining: response.total - uris.length,
shuffle: data.shuffle
}
))
dispatch(uiActions.runProcess(
'SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR',
{
next: response.next,
uris: uris
uris: uris,
shuffle: data.shuffle
}
))
} else {
if (data.shuffle){
uris = helpers.shuffle(uris);
}
dispatch(mopidyActions.playURIs(uris, data.uri))
dispatch(uiActions.processFinishing('SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR'))
}

View File

@ -131,7 +131,7 @@ class Queue extends React.Component{
}
var current_track_image = null;
if (current_track){
if (current_track && this.props.current_track_uri){
if (current_track.images !== undefined && current_track.images){
current_track_image = helpers.sizedImages(current_track.images).large;
}
@ -206,6 +206,7 @@ const mapStateToProps = (state, ownProps) => {
queue: state.core.queue,
queue_tlids: state.core.queue_tlids,
queue_metadata: state.core.queue_metadata,
current_track_uri: state.core.current_track_uri,
current_track: state.core.current_track
}
}

View File

@ -60,7 +60,7 @@ class LibraryAlbums extends React.Component{
if (newProps.mopidy_uri_schemes.includes('spotify:') && (newProps.source == 'all' || newProps.source == 'spotify')){
// Filter changed, but we haven't got this provider's library yet
if (this.props.source != 'all' && this.props.source != 'spotify' && newProps.spotify_library_albums_status != 'finished' && newProps.spotify_library_albums_status != 'started'){
if (newProps.spotify_library_albums_status != 'finished' && newProps.spotify_library_albums_status != 'started'){
this.props.spotifyActions.getLibraryAlbums();
}
}

View File

@ -56,7 +56,7 @@ class LibraryArtists extends React.Component{
if (newProps.mopidy_uri_schemes.includes('spotify:') && (newProps.source == 'all' || newProps.source == 'spotify')){
// Filter changed, but we haven't got this provider's library yet
if (this.props.source != 'all' && this.props.source != 'spotify' && newProps.spotify_library_artists_status != 'finished'){
if (newProps.spotify_library_artists_status != 'finished' && newProps.spotify_library_artists_status != 'started'){
this.props.spotifyActions.getLibraryArtists();
}
}

View File

@ -58,7 +58,7 @@ class LibraryPlaylists extends React.Component{
if (newProps.mopidy_uri_schemes.includes('spotify:') && (newProps.source == 'all' || newProps.source == 'spotify')){
// Filter changed, but we haven't got this provider's library yet
if (this.props.source != 'all' && this.props.source != 'spotify' && newProps.spotify_library_playlists_status !== 'finished'){
if (newProps.spotify_library_playlists_status != 'finished' && newProps.spotify_library_playlists_status != 'started'){
this.props.spotifyActions.getLibraryPlaylists()
}
}