Push spotify auth; Fixing MIA authentication
This commit is contained in:
3
src/js/bootstrap.js
vendored
3
src/js/bootstrap.js
vendored
@ -50,8 +50,7 @@ var initialState = {
|
||||
spotify: {
|
||||
connected: false,
|
||||
country: 'NZ',
|
||||
locale: 'en_NZ',
|
||||
me: false
|
||||
locale: 'en_NZ'
|
||||
},
|
||||
ui: {
|
||||
context_menu: {
|
||||
|
||||
@ -25,11 +25,11 @@ export default class AddToPlaylistModal extends React.Component{
|
||||
return (
|
||||
<div>
|
||||
<h4>Add to playlist</h4>
|
||||
<div className="playlists">
|
||||
<div className="list playlists">
|
||||
{
|
||||
playlists.map( playlist => {
|
||||
return (
|
||||
<div className="playlist" key={playlist.uri} onClick={ () => this.playlistSelected(playlist.uri) }>
|
||||
<div className="list-item playlist" key={playlist.uri} onClick={ () => this.playlistSelected(playlist.uri) }>
|
||||
<FontAwesome className="source" name={helpers.sourceIcon(playlist.uri)} />
|
||||
|
||||
<span className="name">{ playlist.name }</span>
|
||||
|
||||
@ -8,10 +8,12 @@ import Icon from '../Icon'
|
||||
import AddToPlaylistModal from './AddToPlaylistModal'
|
||||
import CreatePlaylistModal from './CreatePlaylistModal'
|
||||
import EditPlaylistModal from './EditPlaylistModal'
|
||||
import SendAuthorizationModal from './SendAuthorizationModal'
|
||||
|
||||
import * as uiActions from '../../services/ui/actions'
|
||||
import * as mopidyActions from '../../services/mopidy/actions'
|
||||
import * as spotifyActions from '../../services/spotify/actions'
|
||||
import * as pusherActions from '../../services/pusher/actions'
|
||||
|
||||
class Modal extends React.Component{
|
||||
|
||||
@ -32,6 +34,7 @@ class Modal extends React.Component{
|
||||
{ this.props.modal.name == 'add_to_playlist' ? <AddToPlaylistModal uiActions={this.props.uiActions} playlists={this.props.playlists} tracks_uris={this.props.modal.data.tracks_uris} /> : null }
|
||||
{ this.props.modal.name == 'create_playlist' ? <CreatePlaylistModal uiActions={this.props.uiActions} /> : null }
|
||||
{ this.props.modal.name == 'edit_playlist' ? <EditPlaylistModal uiActions={this.props.uiActions} data={this.props.modal.data} /> : null }
|
||||
{ this.props.modal.name == 'send_authorization' ? <SendAuthorizationModal uiActions={this.props.uiActions} pusherActions={this.props.pusherActions} data={this.props.modal.data} /> : null }
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -52,6 +55,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
pusherActions: bindActionCreators(pusherActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch)
|
||||
}
|
||||
|
||||
70
src/js/components/Modal/SendAuthorizationModal.js
Executable file
70
src/js/components/Modal/SendAuthorizationModal.js
Executable file
@ -0,0 +1,70 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
|
||||
import * as uiActions from '../../services/ui/actions'
|
||||
import * as helpers from '../../helpers'
|
||||
|
||||
import Icon from '../Icon'
|
||||
|
||||
class SendAuthorizationModal extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleClick(e, connectionid){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
recipients: [connectionid],
|
||||
action: 'send_authorization',
|
||||
data: {
|
||||
authorization: this.props.authorization,
|
||||
me: this.props.me
|
||||
}
|
||||
}
|
||||
this.props.pusherActions.instruct( 'broadcast', data )
|
||||
this.props.uiActions.closeModal()
|
||||
return false;
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<h4 className="no-padding">Share Spotify authentication</h4>
|
||||
<h3 className="grey-text">Send your authentication tokens to another client. When the recipient client imports this, their Iris will have full access to your Spotify account.</h3>
|
||||
<div className="list pusher-connection-list">
|
||||
{
|
||||
this.props.connections.map( (connection, index) => {
|
||||
return (
|
||||
<div className='list-item connection' key={connection.connectionid} onClick={ e => this.handleClick(e, connection.connectionid) }>
|
||||
{ connection.username }
|
||||
|
||||
<span className="grey-text">({ connection.ip })</span>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
me: state.spotify.me,
|
||||
authorization: state.spotify.authorization,
|
||||
connections: state.pusher.connections
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SendAuthorizationModal)
|
||||
@ -56,7 +56,7 @@ export default class Parallax extends React.Component{
|
||||
}
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
if( ( !this.props.url || nextProps.image != this.props.url ) && !this._loading ){
|
||||
if( ( !this.state.url || nextProps.image != this.state.url ) && !this._loading ){
|
||||
this._loading = true
|
||||
this.setState({ url: nextProps.image, image: false, loading: true })
|
||||
this.loadImage( nextProps.image )
|
||||
|
||||
@ -99,12 +99,12 @@ class SpotifyAuthenticationFrame extends React.Component{
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
{ this.renderAuthorizeButton() }
|
||||
|
||||
{ this.renderRefreshButton() }
|
||||
<iframe src={this.state.frameUrl} style={{ display: 'none' }}></iframe>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,41 +6,61 @@ export default class Thumbnail extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
url: require('../../assets/no-image.svg')
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.mapImageSizes();
|
||||
}
|
||||
// TODO: ascertain whether this is improving or hindering performance
|
||||
// The UI appears to work perfectly fine without this
|
||||
shouldComponentUpdate(nextProps, nextState){
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
this.mapImageSizes( nextProps );
|
||||
// no images at all, and we already know it
|
||||
if(
|
||||
typeof(nextProps.image) == 'undefined' &&
|
||||
typeof(this.props.image) == 'undefined' &&
|
||||
typeof(nextProps.images) == 'undefined' &&
|
||||
typeof(this.props.images) == 'undefined'
|
||||
) return false
|
||||
|
||||
// image changed
|
||||
if( !this.props.image && nextProps.image ) return true
|
||||
if( this.props.image && nextProps.image ) return true
|
||||
if( this.props.image != nextProps.image ) return true
|
||||
|
||||
// images array changed
|
||||
if( typeof(this.props.images) === 'undefined' && nextProps.images ) return true
|
||||
if( this.props.images && typeof(nextProps.images) === 'undefined' ) return true
|
||||
if( this.props.images.length != nextProps.images.length ) return true
|
||||
|
||||
// image item changed
|
||||
var size = 'medium'
|
||||
var images = helpers.sizedImages( nextProps.images )
|
||||
if( this.props.size ) size = this.props.size
|
||||
if( this.props.images[size] != images[size] ) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
mapImageSizes( props = this.props ){
|
||||
|
||||
// no images
|
||||
if( !this.props.image && !this.props.images ){
|
||||
this.setState({ url: require('../../assets/no-image.svg') })
|
||||
return require('../../assets/no-image.svg')
|
||||
|
||||
// single image
|
||||
}else if( this.props.image ){
|
||||
this.setState({ url: this.props.image })
|
||||
return this.props.image
|
||||
|
||||
// multiple images
|
||||
}else if( this.props.images && this.props.images.length > 0 ){
|
||||
var images = helpers.sizedImages( this.props.images )
|
||||
var size = 'medium'
|
||||
if( this.props.size ) size = this.props.size
|
||||
this.setState({ url: images[size] })
|
||||
return images[size]
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
var style = { backgroundImage: 'url("'+this.state.url+'")' }
|
||||
var image = this.mapImageSizes()
|
||||
var style = { backgroundImage: 'url("'+image+'")' }
|
||||
var className = 'thumbnail '+this.props.size;
|
||||
if( this.props.circle ) className += ' circle';
|
||||
|
||||
|
||||
@ -20,6 +20,13 @@ export default class Track extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchEnd(e){
|
||||
var target = $(e.target);
|
||||
if( !target.is('a') && target.closest('a').length <= 0 ){
|
||||
this.props.handleTouchEnd(e);
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu(e){
|
||||
e.preventDefault();
|
||||
this.props.handleContextMenu(e);
|
||||
@ -46,7 +53,7 @@ export default class Track extends React.Component{
|
||||
<div
|
||||
className={className}
|
||||
onTouchStart={ e => this.props.handleTouchStart(e) }
|
||||
onTouchEnd={ e => this.props.handleTouchEnd(e) }
|
||||
onTouchEnd={ e => this.handleTouchEnd(e) }
|
||||
onMouseDown={ e => this.handleMouseDown(e) }
|
||||
onMouseUp={ e => this.props.handleMouseUp(e) }
|
||||
onDoubleClick={ e => this.props.handleDoubleClick(e) }
|
||||
|
||||
@ -257,24 +257,43 @@ export let createRange = function (indexes){
|
||||
export let sortItems = function (array, property, reverse = false){
|
||||
function compare(a,b) {
|
||||
|
||||
switch( typeof(a[property]) ){
|
||||
var a_value = a
|
||||
var a_property_split = property.split('.')
|
||||
for( var i = 0; i < a_property_split.length; i++ ){
|
||||
if( typeof(a_value[a_property_split[i]]) === 'undefined' ) return -1
|
||||
a_value = a_value[a_property_split[i]]
|
||||
}
|
||||
|
||||
case 'boolean':
|
||||
return a[property]
|
||||
break
|
||||
var b_value = b
|
||||
var b_property_split = property.split('.')
|
||||
for( var i = 0; i < b_property_split.length; i++ ){
|
||||
if( typeof(b_value[b_property_split[i]]) === 'undefined' ) return -1
|
||||
b_value = b_value[b_property_split[i]]
|
||||
}
|
||||
|
||||
default:
|
||||
if( typeof(a_value) === 'boolean'){
|
||||
return a_value
|
||||
|
||||
// both objects must have this property
|
||||
if( typeof(a[property]) === 'undefined' || typeof(b[property]) === 'undefined' ) return 0
|
||||
}else if( typeof(a_value) === 'string'){
|
||||
if(a_value.toLowerCase() > b_value.toLowerCase()) return 1
|
||||
return -1
|
||||
|
||||
if(a[property] > b[property]) return 1
|
||||
if(a[property] < b[property]) return -1
|
||||
return 0
|
||||
}else{
|
||||
if( parseInt(a_value) > parseInt(b_value) ) return 1
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
var sorted = array.sort(compare)
|
||||
if( reverse ) sorted.reverse()
|
||||
return sorted
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out if a value is a number
|
||||
* @param data = mixed
|
||||
* @return boolean
|
||||
**/
|
||||
export let isNumeric = function (data) {
|
||||
return !isNaN(parseFloat(data)) && isFinite(data)
|
||||
}
|
||||
@ -12,7 +12,7 @@ const localstorageMiddleware = (function(){
|
||||
|
||||
// append our state to a global variable. This gives us access to debug the store at any point
|
||||
window._store = store
|
||||
//console.log(action)
|
||||
console.log(action)
|
||||
|
||||
switch( action.type ){
|
||||
|
||||
@ -57,10 +57,14 @@ const localstorageMiddleware = (function(){
|
||||
break;
|
||||
|
||||
case 'SPOTIFY_SET_CONFIG':
|
||||
var spotify = {
|
||||
country: action.config.country,
|
||||
locale: action.config.locale
|
||||
};
|
||||
var spotify = JSON.parse( localStorage.getItem('spotify') );
|
||||
if( !spotify ) spotify = {};
|
||||
Object.assign(
|
||||
spotify,{
|
||||
country: action.config.country,
|
||||
locale: action.config.locale
|
||||
}
|
||||
);
|
||||
localStorage.setItem('spotify', JSON.stringify(spotify));
|
||||
break;
|
||||
|
||||
@ -70,6 +74,7 @@ const localstorageMiddleware = (function(){
|
||||
Object.assign(
|
||||
spotify,{
|
||||
authorized: true,
|
||||
authorization: action.data,
|
||||
access_token: action.data.access_token,
|
||||
refresh_token: action.data.refresh_token,
|
||||
token_expiry: action.data.token_expiry
|
||||
@ -109,9 +114,8 @@ const localstorageMiddleware = (function(){
|
||||
var spotify = JSON.parse( localStorage.getItem('spotify') );
|
||||
if( !spotify ) spotify = {};
|
||||
Object.assign(
|
||||
spotify,{
|
||||
me: action.data
|
||||
}
|
||||
spotify,
|
||||
{ me: action.data }
|
||||
);
|
||||
localStorage.setItem('spotify', JSON.stringify(spotify));
|
||||
break;
|
||||
|
||||
@ -43,10 +43,10 @@ export function getConnectionList(){
|
||||
}
|
||||
}
|
||||
|
||||
export function instruct( action, data = null ){
|
||||
export function instruct( message_type, data = null ){
|
||||
return {
|
||||
type: 'PUSHER_INSTRUCT',
|
||||
action: action,
|
||||
message_type: message_type,
|
||||
data: data
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ const PusherMiddleware = (function(){
|
||||
break;
|
||||
|
||||
case 'PUSHER_INSTRUCT':
|
||||
switch( action.action ){
|
||||
switch( action.message_type ){
|
||||
case 'query':
|
||||
makeRequest( action.data )
|
||||
break
|
||||
@ -150,6 +150,20 @@ const PusherMiddleware = (function(){
|
||||
var notification = new notification( title, options );
|
||||
break;
|
||||
|
||||
case 'PUSHER_SEND_AUTHORIZATION':
|
||||
if( window.confirm('Spotify authorization for user '+action.data.me.id+' received. Do you want to import?') ){
|
||||
|
||||
// remove any existing authentication
|
||||
store.dispatch({ type: 'SPOTIFY_AUTHORIZATION_REVOKED' })
|
||||
|
||||
// import our new authentication
|
||||
store.dispatch({ type: 'SPOTIFY_ME_LOADED', data: action.data.me })
|
||||
store.dispatch({ type: 'SPOTIFY_AUTHORIZATION_GRANTED', data: action.data.authorization })
|
||||
}else{
|
||||
console.log('Authorization ignored')
|
||||
}
|
||||
break
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
|
||||
@ -52,7 +52,7 @@ function getToken( dispatch, getState ){
|
||||
|
||||
// token is okay for now, so just resolve with the current token
|
||||
if( new Date().getTime() < getState().spotify.token_expiry ){
|
||||
resolve(getState().spotify.access_token);
|
||||
resolve(getState().spotify.access_token)
|
||||
return
|
||||
}
|
||||
|
||||
@ -81,16 +81,17 @@ function refreshToken( dispatch, getState ){
|
||||
})
|
||||
.then(
|
||||
response => {
|
||||
response.token_expiry = new Date().getTime() + ( response.expires_in * 1000 );
|
||||
response.source = 'spotify';
|
||||
response.token_expiry = new Date().getTime() + ( response.expires_in * 1000 )
|
||||
response.source = 'spotify'
|
||||
dispatch({
|
||||
type: 'SPOTIFY_TOKEN_REFRESHED',
|
||||
provider: 'spotify-http-api',
|
||||
data: response
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
resolve(response)
|
||||
},
|
||||
error => {
|
||||
dispatch({ type: 'SPOTIFY_DISCONNECTED' })
|
||||
console.error('Could not refresh token', error)
|
||||
reject(error)
|
||||
}
|
||||
@ -116,6 +117,7 @@ function refreshToken( dispatch, getState ){
|
||||
resolve(response);
|
||||
},
|
||||
error => {
|
||||
dispatch({ type: 'SPOTIFY_DISCONNECTED' })
|
||||
console.error('Could not refresh token', error)
|
||||
reject(error)
|
||||
}
|
||||
|
||||
@ -4,10 +4,13 @@ export default function reducer(spotify = {}, action){
|
||||
|
||||
case 'SPOTIFY_CONNECT':
|
||||
case 'SPOTIFY_CONNECTING':
|
||||
return Object.assign({}, spotify, { connected: false, connecting: true });
|
||||
return Object.assign({}, spotify, { connected: false, connecting: true })
|
||||
|
||||
case 'SPOTIFY_CONNECTED':
|
||||
return Object.assign({}, spotify, { connected: true, connecting: false });
|
||||
return Object.assign({}, spotify, { connected: true, connecting: false })
|
||||
|
||||
case 'SPOTIFY_DISCONNECTED':
|
||||
return Object.assign({}, spotify, { connected: false, connecting: false })
|
||||
|
||||
case 'PUSHER_SPOTIFY_TOKEN':
|
||||
if( spotify.authorized ) return spotify;
|
||||
@ -17,7 +20,7 @@ export default function reducer(spotify = {}, action){
|
||||
authorization: false,
|
||||
access_token: action.data.access_token,
|
||||
token_expiry: action.data.token_expiry
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_AUTHORIZATION_GRANTED':
|
||||
return Object.assign({}, spotify, {
|
||||
@ -27,7 +30,7 @@ export default function reducer(spotify = {}, action){
|
||||
access_token: action.data.access_token,
|
||||
refresh_token: action.data.refresh_token,
|
||||
token_expiry: action.data.token_expiry
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_AUTHORIZATION_REVOKED':
|
||||
return Object.assign({}, spotify, {
|
||||
@ -38,45 +41,45 @@ export default function reducer(spotify = {}, action){
|
||||
refresh_token: false,
|
||||
token_expiry: 0,
|
||||
me: false
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_TOKEN_REFRESHING':
|
||||
return Object.assign({}, spotify, { refreshing_token: true });
|
||||
return Object.assign({}, spotify, { refreshing_token: true })
|
||||
|
||||
case 'SPOTIFY_TOKEN_REFRESHED':
|
||||
return Object.assign({}, spotify, {
|
||||
connected: true,
|
||||
refreshing_token: false,
|
||||
authorization: action.data,
|
||||
access_token: action.data.access_token,
|
||||
token_expiry: action.data.token_expiry,
|
||||
provider: action.provider
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_DISCONNECTED':
|
||||
return Object.assign({}, spotify, { connected: false, connecting: false });
|
||||
return Object.assign({}, spotify, { connected: false, connecting: false })
|
||||
|
||||
case 'SPOTIFY_ME_LOADED':
|
||||
return Object.assign({}, spotify, { me: action.data });
|
||||
return Object.assign({}, spotify, { me: action.data })
|
||||
|
||||
case 'SPOTIFY_ARTISTS_LOADED':
|
||||
if( !action.data ) return Object.assign({}, spotify)
|
||||
return Object.assign({}, spotify, {
|
||||
artists: action.data.artists.items,
|
||||
artists_more: action.data.artists.next
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ARTISTS_LOADED':
|
||||
if( !action.data ) return Object.assign({}, spotify)
|
||||
return Object.assign({}, spotify, {
|
||||
library_artists: action.data.artists.items,
|
||||
library_artists_more: action.data.artists.next
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ARTISTS_LOADED_MORE':
|
||||
return Object.assign({}, spotify, {
|
||||
library_artists: [ ...spotify.library_artists, ...action.data.artists.items ],
|
||||
library_artists_more: action.data.artists.next
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED':
|
||||
if( !action.data ) return Object.assign({}, spotify)
|
||||
@ -93,7 +96,7 @@ export default function reducer(spotify = {}, action){
|
||||
return Object.assign({}, spotify, {
|
||||
library_albums: albums,
|
||||
library_albums_more: action.data.next
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED_MORE':
|
||||
var albums = []
|
||||
@ -109,7 +112,7 @@ export default function reducer(spotify = {}, action){
|
||||
return Object.assign({}, spotify, {
|
||||
library_albums: [...spotify.library_albums, ...albums ],
|
||||
library_albums_more: action.data.next
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_LIBRARY_TRACKS_LOADED':
|
||||
if( !action.data ) return Object.assign({}, spotify)
|
||||
@ -126,7 +129,7 @@ export default function reducer(spotify = {}, action){
|
||||
return Object.assign({}, spotify, {
|
||||
library_tracks: tracks,
|
||||
library_tracks_more: action.data.next
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE':
|
||||
var tracks = Object.assign([], action.data.items)
|
||||
@ -142,19 +145,19 @@ export default function reducer(spotify = {}, action){
|
||||
return Object.assign({}, spotify, {
|
||||
library_tracks: [...spotify.library_tracks, ...tracks],
|
||||
library_tracks_more: action.data.next
|
||||
});
|
||||
})
|
||||
|
||||
case 'SPOTIFY_FEATURED_PLAYLISTS_LOADED':
|
||||
return Object.assign({}, spotify, { featured_playlists: action.data });
|
||||
return Object.assign({}, spotify, { featured_playlists: action.data })
|
||||
|
||||
case 'SPOTIFY_CATEGORIES_LOADED':
|
||||
return Object.assign({}, spotify, { categories: action.data });
|
||||
return Object.assign({}, spotify, { categories: action.data })
|
||||
|
||||
case 'SPOTIFY_CATEGORY_LOADED':
|
||||
return Object.assign({}, spotify, { category: action.data });
|
||||
return Object.assign({}, spotify, { category: action.data })
|
||||
|
||||
case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED':
|
||||
return Object.assign({}, spotify, { category_playlists: action.data });
|
||||
return Object.assign({}, spotify, { category_playlists: action.data })
|
||||
|
||||
case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED_MORE':
|
||||
return Object.assign({}, spotify, { category_playlists: {
|
||||
@ -162,7 +165,7 @@ export default function reducer(spotify = {}, action){
|
||||
next: action.data.next,
|
||||
previous: action.data.previous,
|
||||
items: [ ...spotify.category_playlists.items, ...action.data.items ]
|
||||
}});
|
||||
}})
|
||||
|
||||
case 'SPOTIFY_NEW_RELEASES_LOADED':
|
||||
return Object.assign({}, spotify, { new_releases: action.data });
|
||||
@ -173,7 +176,7 @@ export default function reducer(spotify = {}, action){
|
||||
next: action.data.albums.next,
|
||||
previous: action.data.albums.previous,
|
||||
items: [ ...spotify.new_releases.items, ...action.data.albums.items ]
|
||||
}});
|
||||
}})
|
||||
|
||||
default:
|
||||
return spotify
|
||||
|
||||
@ -13,6 +13,7 @@ import VersionManager from '../components/VersionManager'
|
||||
import Header from '../components/Header'
|
||||
import Thumbnail from '../components/Thumbnail'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
import * as pusherActions from '../services/pusher/actions'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
@ -108,6 +109,18 @@ class Settings extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
renderSendAuthorizationButton(){
|
||||
if( !this.props.spotify.authorized ) return null
|
||||
|
||||
return (
|
||||
<button onClick={e => this.props.uiActions.openModal('send_authorization', {}) }>
|
||||
<FontAwesome name="share-square-o" />
|
||||
|
||||
Share authentication
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="view settings-view">
|
||||
@ -175,13 +188,13 @@ class Settings extends React.Component{
|
||||
</form>
|
||||
|
||||
<h4 className="underline">Spotify</h4>
|
||||
<form onSubmit={(e) => this.setSpotifyConfig(e)}>
|
||||
<div className="field">
|
||||
<div className="name">Status</div>
|
||||
<div className="input">
|
||||
{ this.renderConnectionStatus('spotify') }
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="name">Status</div>
|
||||
<div className="input">
|
||||
{ this.renderConnectionStatus('spotify') }
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<div className="field">
|
||||
<div className="name">Country</div>
|
||||
<div className="input">
|
||||
@ -202,21 +215,23 @@ class Settings extends React.Component{
|
||||
value={ this.state.spotify_locale } />
|
||||
</div>
|
||||
</div>
|
||||
<div className="field current-user">
|
||||
<div className="name">Current user</div>
|
||||
<div className="input">
|
||||
<div className="text">
|
||||
{ this.renderSpotifyUser() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="name">Authentication</div>
|
||||
<div className="input">
|
||||
<SpotifyAuthenticationFrame />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div className="field current-user">
|
||||
<div className="name">Current user</div>
|
||||
<div className="input">
|
||||
<div className="text">
|
||||
{ this.renderSpotifyUser() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="name">Authentication</div>
|
||||
<div className="input">
|
||||
<SpotifyAuthenticationFrame />
|
||||
|
||||
{ this.renderSendAuthorizationButton() }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 className="underline">Advanced</h4>
|
||||
|
||||
@ -278,6 +293,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
pusherActions: bindActionCreators(pusherActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
|
||||
@ -8,6 +8,7 @@ import Thumbnail from '../components/Thumbnail'
|
||||
import PlaylistGrid from '../components/PlaylistGrid'
|
||||
import FollowButton from '../components/FollowButton'
|
||||
import LazyLoadListener from '../components/LazyLoadListener'
|
||||
import Header from '../components/Header'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
@ -51,6 +52,9 @@ class User extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="view user-view">
|
||||
|
||||
<Header icon="play" title="User" />
|
||||
|
||||
<div className="intro">
|
||||
<Thumbnail circle={true} size="medium" images={ this.props.user.images } />
|
||||
|
||||
|
||||
@ -21,12 +21,23 @@ class LibraryPlaylists extends React.Component{
|
||||
super(props);
|
||||
}
|
||||
|
||||
setSort(value){
|
||||
var reverse = false
|
||||
if( this.props.sort == value ) reverse = !this.props.sort_reverse
|
||||
|
||||
var data = {
|
||||
library_playlists_sort_reverse: reverse,
|
||||
library_playlists_sort: value
|
||||
}
|
||||
this.props.uiActions.set(data)
|
||||
}
|
||||
|
||||
renderView(){
|
||||
if( !this.props.playlists ) return null
|
||||
|
||||
var playlists = this.props.playlists
|
||||
if( this.props.sort ){
|
||||
playlists = helpers.sortItems(playlists, this.props.sort)
|
||||
playlists = helpers.sortItems(playlists, this.props.sort, this.props.sort_reverse)
|
||||
}
|
||||
|
||||
if( this.props.view == 'list' ){
|
||||
@ -96,7 +107,7 @@ class LibraryPlaylists extends React.Component{
|
||||
|
||||
var actions = (
|
||||
<div>
|
||||
<DropdownField icon="sort" name="Sort" value={ this.props.sort } options={ sort_options } handleChange={ value => this.props.uiActions.set({ library_playlists_sort: value }) } />
|
||||
<DropdownField icon="sort" name="Sort" value={ this.props.sort } options={ sort_options } handleChange={ value => this.setSort(value) } />
|
||||
<DropdownField icon="eye" name="View" value={ this.props.view } options={ view_options } handleChange={ value => this.props.uiActions.set({ library_playlists_view: value }) } />
|
||||
<button onClick={ () => this.props.uiActions.openModal('create_playlist', {} ) }>
|
||||
<FontAwesome name="plus" />
|
||||
@ -125,6 +136,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
view: state.ui.library_playlists_view,
|
||||
sort: state.ui.library_playlists_sort,
|
||||
sort_reverse: state.ui.library_playlists_sort_reverse,
|
||||
playlists: state.ui.playlists
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user