diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js index a456374e..651f19db 100755 --- a/src/js/bootstrap.js +++ b/src/js/bootstrap.js @@ -61,9 +61,8 @@ var initialState = { track: {} }, spotify: { + enabled: true, connected: false, - access: 'none', - authentication_provider: 'backend', me: false, autocomplete_results: {}, authorization_url: 'https://jamesbarnsley.co.nz/auth_v2.php' diff --git a/src/js/components/FollowButton.js b/src/js/components/FollowButton.js index ed159449..659efbf7 100755 --- a/src/js/components/FollowButton.js +++ b/src/js/components/FollowButton.js @@ -15,7 +15,7 @@ class FollowButton extends React.Component{ } componentDidMount(){ - if (this.props.spotify_access == 'full' && this.props.uri){ + if (this.props.spotify_authorized && this.props.uri){ this.props.spotifyActions.following(this.props.uri) } } @@ -29,7 +29,7 @@ class FollowButton extends React.Component{ } render(){ - if (this.props.spotify_access !== 'full' || !this.props.uri){ + if (!this.props.spotify_authorized || !this.props.uri){ return false } @@ -56,7 +56,6 @@ class FollowButton extends React.Component{ const mapStateToProps = (state, ownProps) => { return { load_queue: state.ui.load_queue, - spotify_access: state.spotify.access, spotify_authorized: state.spotify.authorization } } diff --git a/src/js/components/Settings/Advanced.js b/src/js/components/Settings/Advanced.js new file mode 100755 index 00000000..394727ea --- /dev/null +++ b/src/js/components/Settings/Advanced.js @@ -0,0 +1,99 @@ + +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { Link, hashHistory } from 'react-router' +import { bindActionCreators } from 'redux' +import FontAwesome from 'react-fontawesome' + +import ConfirmationButton from '../ConfirmationButton' +import PusherConnectionList from '../PusherConnectionList' +import URISchemesList from '../URISchemesList' +import VersionManager from '../VersionManager' + +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' + +class Advanced extends React.Component{ + + constructor(props) { + super(props) + } + + render(){ + return ( +
+ +

Pusher

+ +
+
Connections
+
+ + + +
+
+ +

Mopidy extensions

+ + +

Core

+ +
+
Customise behavior
+
+ +
+
+ +
+
Version
+
+ +
+
+ +
+
Reset
+
+ this.resetAllSettings()} /> +
+
+ +
+ ); + } +} + + +/** + * Export our component + * + * We also integrate our global store, using connect() + **/ + +const mapStateToProps = (state, ownProps) => { + return { + ui: state.ui + } +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Advanced) \ No newline at end of file diff --git a/src/js/components/Settings/Services.js b/src/js/components/Settings/Services.js deleted file mode 100755 index a67c3097..00000000 --- a/src/js/components/Settings/Services.js +++ /dev/null @@ -1,224 +0,0 @@ - -import React, { PropTypes } from 'react' -import { connect } from 'react-redux' -import { Link, hashHistory } from 'react-router' -import { bindActionCreators } from 'redux' -import FontAwesome from 'react-fontawesome' - -import SpotifyAuthenticationFrame from '../SpotifyAuthenticationFrame' -import ConfirmationButton from '../ConfirmationButton' -import PusherConnectionList from '../PusherConnectionList' -import URISchemesList from '../URISchemesList' -import VersionManager from '../VersionManager' -import Header from '../Header' -import Thumbnail from '../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' - -class Services extends React.Component{ - - constructor(props) { - super(props) - - this.state = { - spotify_authentication_provider: this.props.spotify.authentication_provider, - spotify_country: this.props.spotify.country, - spotify_locale: this.props.spotify.locale, - input_in_focus: null - } - } - - componentWillReceiveProps(newProps){ - var changed = false - var state = this.state - - if (newProps.spotify.authentication_provider != this.state.spotify_authentication_provider && this.state.input_in_focus != 'spotify_authentication_provider'){ - state.spotify_authentication_provider = newProps.spotify.authentication_provider - changed = true - } - - if (newProps.spotify.country != this.state.spotify_country && this.state.input_in_focus != 'spotify_country'){ - state.spotify_country = newProps.spotify.country - changed = true - } - - if (newProps.spotify.locale != this.state.spotify_locale && this.state.input_in_focus != 'spotify_locale'){ - state.spotify_locale = newProps.spotify.locale - changed = true - } - - if (changed){ - this.setState(state) - } - } - - setSpotifyConfig(state = this.state){ - this.props.spotifyActions.setConfig({ - authentication_provider: state.spotify_authentication_provider, - country: state.spotify_country, - locale: state.spotify_locale - }); - - this.setState({input_in_focus: null}) - } - - setProvider(provider){ - let state = this.state - state.spotify_authentication_provider = provider - this.setSpotifyConfig(state) - this.setState(state) - } - - renderSpotifyUser(){ - - var user = null - if (this.props.spotify.me && this.props.spotify.authorization && this.props.spotify.authentication_provider == 'http_api'){ - user = this.props.spotify.me - } else if (this.props.spotify.backend_username){ - if (this.props.core.users && this.props.core.users['spotify:user:'+this.props.spotify.backend_username] !== undefined){ - user = this.props.core.users['spotify:user:'+this.props.spotify.backend_username] - } - } - - if (user){ - return ( - - - - {user.display_name ? user.display_name : user.id} - - - ) - } else if (this.props.spotify.backend_username){ - return ( - - - - {this.props.spotify.backend_username} - - - ) - } else { - return ( - - - - Unknown - - - ) - } - } - - renderSendAuthorizationButton(){ - if (!this.props.spotify.authorization) return null - - return ( - - ) - } - - render(){ - return ( -
-

Spotify (access {this.props.spotify.access})

- {!this.props.uri_schemes.includes('spotify:') ? Mopidy-Spotify not running : null} - -
-
Country
-
- this.setState({ spotify_country: e.target.value })} - onFocus={e => this.setState({input_in_focus: 'spotify_country'})} - onBlur={e => this.setSpotifyConfig() } - value={ this.state.spotify_country } /> -
-
-
-
Locale
-
- this.setState({ spotify_locale: e.target.value })} - onFocus={e => this.setState({input_in_focus: 'spotify_locale'})} - onBlur={e => this.setSpotifyConfig() } - value={this.state.spotify_locale} /> -
-
- -
- Authentication provider -
- - -
-
- {this.props.spotify.authentication_provider == 'http_api' ?
-
Authentication
-
- - { this.renderSendAuthorizationButton() } -
-
: null} -
-
Current user
-
-
- { this.renderSpotifyUser() } -
-
-
-
- ); - } -} - - -/** - * Export our component - * - * We also integrate our global store, using connect() - **/ - -const mapStateToProps = (state, ownProps) => { - return { - uri_schemes: (state.mopidy.uri_schemes ? state.mopidy.uri_schemes : []), - core: state.core, - spotify: state.spotify - } -} - -const mapDispatchToProps = (dispatch) => { - return { - uiActions: bindActionCreators(uiActions, dispatch), - pusherActions: bindActionCreators(pusherActions, dispatch), - mopidyActions: bindActionCreators(mopidyActions, dispatch), - spotifyActions: bindActionCreators(spotifyActions, dispatch) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(Services) \ No newline at end of file diff --git a/src/js/components/Settings/System.js b/src/js/components/Settings/System.js index 2cb0b61a..ecfd7188 100755 --- a/src/js/components/Settings/System.js +++ b/src/js/components/Settings/System.js @@ -6,11 +6,9 @@ import { bindActionCreators } from 'redux' import FontAwesome from 'react-fontawesome' import SpotifyAuthenticationFrame from '../SpotifyAuthenticationFrame' -import ConfirmationButton from '../ConfirmationButton' -import PusherConnectionList from '../PusherConnectionList' -import URISchemesList from '../URISchemesList' -import VersionManager from '../VersionManager' +import Thumbnail from '../Thumbnail' +import * as coreActions from '../../services/core/actions' import * as uiActions from '../../services/ui/actions' import * as pusherActions from '../../services/pusher/actions' import * as mopidyActions from '../../services/mopidy/actions' @@ -21,6 +19,8 @@ class System extends React.Component{ constructor(props) { super(props); this.state = { + country: this.props.core.country, + locale: this.props.core.locale, mopidy_host: this.props.mopidy.host, mopidy_port: this.props.mopidy.port, pusher_username: this.props.pusher.username, @@ -57,6 +57,13 @@ class System extends React.Component{ return false; } + handleBlur(name, value){ + this.setState({input_in_focus: null}) + var data = {} + data[name] = value + this.props.coreActions.set(data) + } + handleUsernameChange(username){ this.setState({pusher_username: username.replace(/\W/g, '')}) } @@ -79,29 +86,102 @@ class System extends React.Component{ ) } + renderStatus(server){ + if (this.props[server].connecting){ + return + } else if (this.props[server].connected){ + return + } else { + return + } + } + + renderSpotifyUser(){ + + var user = null + if (this.props.spotify.me && this.props.spotify.authorization){ + user = this.props.spotify.me + } else if (this.props.spotify.backend_username){ + if (this.props.core.users && this.props.core.users['spotify:user:'+this.props.spotify.backend_username] !== undefined){ + user = this.props.core.users['spotify:user:'+this.props.spotify.backend_username] + } + } + + if (user){ + return ( + + + + {user.display_name ? user.display_name : user.id} + + + ) + } else if (this.props.spotify.backend_username){ + return ( + + + + {this.props.spotify.backend_username} + + + ) + } else { + return ( + + + + Unknown + + + ) + } + } + + renderSendAuthorizationButton(){ + if (!this.props.spotify.authorization) return null + + return ( + + ) + } + render(){ return (
-

Server

-
this.setMopidyConfig(e)}> -
-
Username
-
- this.handleUsernameChange(e.target.value)} - onFocus={e => this.setState({input_in_focus: 'pusher_username'})} - onBlur={e => this.handleUsernameBlur(e)} - value={this.state.pusher_username } /> +

Servers

+ +
+
Status
+
+
+ {this.renderStatus('mopidy')}  Mopidy    + {this.renderStatus('pusher')}  Pusher
+
+ +
+
Username
+
+ this.handleUsernameChange(e.target.value)} + onFocus={e => this.setState({input_in_focus: 'pusher_username'})} + onBlur={e => this.handleUsernameBlur(e)} + value={this.state.pusher_username } /> +
+
+ + this.setMopidyConfig(e)}>
Host
this.setState({ mopidy_host: e.target.value })} + onChange={ e => this.setState({mopidy_host: e.target.value})} onFocus={e => this.setState({input_in_focus: 'mopidy_host'})} onBlur={e => this.setState({input_in_focus: null})} value={ this.state.mopidy_host } /> @@ -112,7 +192,7 @@ class System extends React.Component{
this.setState({ mopidy_port: e.target.value })} + onChange={ e => this.setState({mopidy_port: e.target.value})} onFocus={e => this.setState({input_in_focus: 'mopidy_port'})} onBlur={e => this.setState({input_in_focus: null})} value={ this.state.mopidy_port } /> @@ -121,47 +201,59 @@ class System extends React.Component{ {this.renderApplyButton()} -

Advanced

+

Localization

-
-
Customise behavior
+
+
Country
- + this.setState({country: e.target.value})} + onFocus={e => this.setState({input_in_focus: 'country'})} + onBlur={e => this.handleBlur('country',e.target.value)} + value={ this.state.country } /> +
+
+
+
Locale
+
+ this.setState({locale: e.target.value})} + onFocus={e => this.setState({input_in_focus: 'locale'})} + onBlur={e => this.handleBlur('locale',e.target.value)} + value={this.state.locale} />
-
-
Connections
+

Spotify

+ +
+
Status
- - - -
-
- +
+ {!this.props.mopidy.uri_schemes || !this.props.mopidy.uri_schemes.includes('spotify:') ?
  Mopidy-Spotify not available
: null} + + {this.props.spotify.authorization ? Authorized  All Spotify functionality available : Limited access  Authorize Iris for full functionality} +
+
+
-
Backends
+
Authorization
- - - -
-
- -
-
System
+ + { this.renderSendAuthorizationButton() } +
+
+
+
Current user
- this.resetAllSettings()} /> - -
-
+
+ { this.renderSpotifyUser() } +
+
+
+
); } @@ -176,14 +268,17 @@ class System extends React.Component{ const mapStateToProps = (state, ownProps) => { return { + core: state.core, ui: state.ui, mopidy: state.mopidy, - pusher: state.pusher + pusher: state.pusher, + spotify: state.spotify } } const mapDispatchToProps = (dispatch) => { return { + coreActions: bindActionCreators(coreActions, dispatch), uiActions: bindActionCreators(uiActions, dispatch), pusherActions: bindActionCreators(pusherActions, dispatch), mopidyActions: bindActionCreators(mopidyActions, dispatch), diff --git a/src/js/components/Sidebar.js b/src/js/components/Sidebar.js index 17d4c9dc..595ac8fb 100755 --- a/src/js/components/Sidebar.js +++ b/src/js/components/Sidebar.js @@ -68,14 +68,14 @@ class Sidebar extends React.Component{ Playlists - + {this.props.spotify_enabled && this.props.spotify_authorized ? Artists - - + : null} + {this.props.spotify_enabled && this.props.spotify_authorized ? Albums - + : null} Browse @@ -112,7 +112,6 @@ const mapStateToProps = (state, ownProps) => { mopidy_connected: state.mopidy.connected, pusher_connected: state.pusher.connected, spotify_enabled: state.spotify.enabled, - spotify_access: state.spotify.access, spotify_connected: state.spotify.connected, spotify_authorized: state.spotify.authorization, dragger: state.ui.dragger diff --git a/src/js/index.js b/src/js/index.js index 38fc3748..1ca63716 100755 --- a/src/js/index.js +++ b/src/js/index.js @@ -20,6 +20,7 @@ import User from './views/User' import Queue from './views/Queue' import QueueHistory from './views/QueueHistory' import Settings from './views/Settings' +import Debug from './views/Debug' import Search from './views/Search' import DiscoverRecommendations from './views/discover/DiscoverRecommendations' @@ -55,7 +56,8 @@ ReactDOM.render( - + + diff --git a/src/js/services/core/actions.js b/src/js/services/core/actions.js index 58e42a81..4b2b9d23 100755 --- a/src/js/services/core/actions.js +++ b/src/js/services/core/actions.js @@ -44,6 +44,13 @@ export function startServices(){ } } +export function set( data ){ + return { + type: 'CORE_SET', + data: data + } +} + diff --git a/src/js/services/core/reducer.js b/src/js/services/core/reducer.js index a3291281..a5bbf1f2 100755 --- a/src/js/services/core/reducer.js +++ b/src/js/services/core/reducer.js @@ -4,6 +4,9 @@ import * as helpers from '../../helpers' export default function reducer(core = {}, action){ switch (action.type) { + case 'CORE_SET': + return Object.assign({}, core, action.data) + /** * Current track and tracklist **/ diff --git a/src/js/services/localstorage/middleware.js b/src/js/services/localstorage/middleware.js index 8eb06e72..c4569e94 100755 --- a/src/js/services/localstorage/middleware.js +++ b/src/js/services/localstorage/middleware.js @@ -136,6 +136,13 @@ const localstorageMiddleware = (function(){ localStorage.setItem('spotify', JSON.stringify(spotify)); break; + case 'CORE_SET': + var core = JSON.parse( localStorage.getItem('core') ); + if( !core ) core = {}; + Object.assign( core, action.data ); + localStorage.setItem('core', JSON.stringify(core)); + break + case 'UI_SET': var ui = JSON.parse( localStorage.getItem('ui') ); if( !ui ) ui = {}; diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index 9b38b3b4..c17b51de 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -2,6 +2,7 @@ import ReactGA from 'react-ga' var helpers = require('../../helpers.js') +var coreActions = require('../core/actions.js') var uiActions = require('../ui/actions.js') var pusherActions = require('./actions.js') var spotifyActions = require('../spotify/actions.js') @@ -112,9 +113,12 @@ const PusherMiddleware = (function(){ if (response.config.spotify_username && store.getState().spotify.enabled){ store.dispatch(spotifyActions.getUser('spotify:user:'+response.config.spotify_username)) } - var spotify = store.getState().spotify - if (!spotify.country || !spotify.locale){ - store.dispatch({ type: 'SPOTIFY_SET_CONFIG', config: response.config }) + var core = store.getState().core + if (!core.country || !core.locale){ + store.dispatch(coreActions.set({ + country: response.config.country, + locale: response.config.locale + })) } } ) diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 5774be93..77082198 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -112,7 +112,7 @@ function getToken( dispatch, getState ){ function refreshToken( dispatch, getState ){ return new Promise( (resolve, reject) => { - if (getState().spotify.authorization && getState().spotify.authentication_provider == 'http_api'){ + if (getState().spotify.authorization){ $.ajax({ method: 'GET', @@ -309,7 +309,7 @@ export function getFeaturedPlaylists(){ var timestamp = year+'-'+month+'-'+day+'T'+hour+':'+min+':'+sec; - sendRequest( dispatch, getState, 'browse/featured-playlists?timestamp='+timestamp+'&country='+getState().spotify.country+'&limit=50&locale='+getState().spotify.locale ) + sendRequest( dispatch, getState, 'browse/featured-playlists?timestamp='+timestamp+'&country='+getState().core.country+'&limit=50&locale='+getState().core.locale ) .then( response => { var playlists = [] @@ -346,7 +346,7 @@ export function getFeaturedPlaylists(){ export function getCategories(){ return (dispatch, getState) => { - sendRequest( dispatch, getState, 'browse/categories?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale ) + sendRequest( dispatch, getState, 'browse/categories?limit=50&country='+getState().core.country+'&locale='+getState().core.locale ) .then( response => { dispatch({ type: 'CATEGORIES_LOADED', @@ -368,7 +368,7 @@ export function getCategory( id ){ }); // get the category - sendRequest( dispatch, getState, 'browse/categories/'+id+'?country='+getState().spotify.country+'&locale='+getState().spotify.locale ) + sendRequest( dispatch, getState, 'browse/categories/'+id+'?country='+getState().core.country+'&locale='+getState().core.locale ) .then( response => { var category = Object.assign({}, response) dispatch({ @@ -379,7 +379,7 @@ export function getCategory( id ){ }) // and the category's playlists - sendRequest( dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50&country='+getState().spotify.country+'&locale='+getState().spotify.locale ) + sendRequest( dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50&country='+getState().core.country+'&locale='+getState().core.locale ) .then( response => { var playlists = [] @@ -411,7 +411,7 @@ export function getCategory( id ){ export function getNewReleases(){ return (dispatch, getState) => { - sendRequest( dispatch, getState, 'browse/new-releases?country='+getState().spotify.country+'&limit=50' ) + sendRequest( dispatch, getState, 'browse/new-releases?country='+getState().core.country+'&limit=50' ) .then( response => { dispatch({ type: 'SPOTIFY_NEW_RELEASES_LOADED', @@ -439,7 +439,7 @@ export function getSearchResults(query, type = 'album,artist,playlist,track', li var url = 'search?q='+query url += '&type='+type - url += '&country='+getState().spotify.country + url += '&country='+getState().core.country url += '&limit='+limit url += '&offset='+offset @@ -500,7 +500,7 @@ export function getAutocompleteResults(field_id, query, types = ['album','artist var endpoint = 'search?q='+query endpoint += '&type='+types.join(',') - endpoint += '&country='+getState().spotify.country + endpoint += '&country='+getState().core.country sendRequest(dispatch, getState, endpoint) .then(response => { @@ -824,7 +824,7 @@ export function getArtist(uri, full = false){ // Do we want a full artist, with all supporting material? if (full){ requests.push( - sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country ) + sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().core.country ) .then( response => { Object.assign(artist, response); }) @@ -858,7 +858,7 @@ export function getArtist(uri, full = false){ // Now go get our artist albums if (full){ - sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/albums?market='+getState().spotify.country ) + sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/albums?market='+getState().core.country ) .then( response => { dispatch({ type: 'SPOTIFY_ARTIST_ALBUMS_LOADED', @@ -927,7 +927,7 @@ export function playArtistTopTracks(uri){ // We need to load the artist's top tracks first } else { - sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().spotify.country ) + sendRequest( dispatch, getState, 'artists/'+ helpers.getFromUri('artistid', uri) +'/top-tracks?country='+getState().core.country ) .then( response => { const uris = helpers.arrayOf('uri',response.tracks) dispatch(mopidyActions.playURIs(uris, uri)) @@ -1165,7 +1165,7 @@ export function getPlaylist(uri){ return (dispatch, getState) => { // get the main playlist object - sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'?market='+getState().spotify.country ) + sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'?market='+getState().core.country ) .then( response => { // convert links in description @@ -1221,7 +1221,7 @@ function loadNextPlaylistTracksBatch(dispatch, getState, uri, tracks, lastRespon export function getAllPlaylistTracks(uri){ return (dispatch, getState) => { - sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country) + sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().core.country) .then( response => { loadNextPlaylistTracksBatch(dispatch, getState, uri, response.items, response) }); diff --git a/src/js/services/spotify/reducer.js b/src/js/services/spotify/reducer.js index 4eb0b4d7..ba03370f 100755 --- a/src/js/services/spotify/reducer.js +++ b/src/js/services/spotify/reducer.js @@ -15,19 +15,10 @@ export default function reducer(spotify = {}, action){ return Object.assign({}, spotify, { connected: false, connecting: false }) case 'SPOTIFY_SET_CONFIG': - var spotify = Object.assign({},spotify,action.config) - - let access = 'none' - if (spotify.authentication_provider == 'http_api' && spotify.authorization){ - access = 'full' - } else if (spotify.authentication_provider == 'backend' && spotify.backend_username){ - access = 'limited' - } - spotify.access = access - return spotify + return Object.assign({},spotify,action.config) case 'PUSHER_SPOTIFY_TOKEN': - if( spotify.authorization ) return spotify; + if (spotify.authorization) return spotify; return Object.assign({}, spotify, { authorizing: false, authorization: false, @@ -37,7 +28,7 @@ export default function reducer(spotify = {}, action){ case 'SPOTIFY_AUTHORIZATION_GRANTED': return Object.assign({}, spotify, { - access: (spotify.authentication_provider == 'http_api' ? 'full' : 'limited'), + enabled: true, authorizing: false, authorization: action.data, access_token: action.data.access_token, @@ -47,7 +38,6 @@ export default function reducer(spotify = {}, action){ case 'SPOTIFY_AUTHORIZATION_REVOKED': return Object.assign({}, spotify, { - access: (spotify.authentication_provider == 'http_api' ? 'none' : 'limited'), authorizing: false, authorization: false, access_token: false, @@ -57,9 +47,7 @@ export default function reducer(spotify = {}, action){ }) case 'SPOTIFY_IMPORT_AUTHORIZATION': - return Object.assign({}, spotify, { - authentication_provider: 'http_api', - access: 'full', + return Object.assign({}, spotify, { authorizing: false, authorization: action.authorization, access_token: action.authorization.access_token, @@ -76,8 +64,7 @@ export default function reducer(spotify = {}, action){ connected: true, refreshing_token: false, access_token: action.data.access_token, - token_expiry: action.data.token_expiry, - access_token_provider: action.access_token_provider + token_expiry: action.data.token_expiry }) case 'SPOTIFY_DISCONNECTED': diff --git a/src/js/views/Debug.js b/src/js/views/Debug.js new file mode 100755 index 00000000..d6998416 --- /dev/null +++ b/src/js/views/Debug.js @@ -0,0 +1,228 @@ + +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { Link, hashHistory } from 'react-router' +import { bindActionCreators } from 'redux' +import FontAwesome from 'react-fontawesome' + +import SpotifyAuthenticationFrame from '../components/SpotifyAuthenticationFrame' +import ConfirmationButton from '../components/ConfirmationButton' +import PusherConnectionList from '../components/PusherConnectionList' +import URISchemesList from '../components/URISchemesList' +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' + +class Debug extends React.Component{ + + constructor(props) { + super(props); + this.state = { + mopidy_call: 'playlists.asList', + mopidy_data: '{}', + pusher_data: '{"method":"get_config"}', + access_token: this.props.access_token + } + } + + callMopidy(e){ + e.preventDefault() + this.props.mopidyActions.debug( this.state.mopidy_call, JSON.parse(this.state.mopidy_data) ) + } + + callPusher(e){ + e.preventDefault() + this.props.pusherActions.debug( JSON.parse(this.state.pusher_data) ) + } + + render(){ + + var options = ( + + + + ) + + return ( +
+
+ +
+ +

User interface

+
+
+
Debug
+
+ +
+
+
+
Logging
+
+ + + +
+
+ +
+ +

Spotify

+
+
Access token
+
+ this.props.spotifyActions.authorizationGranted({access_token: e.target.value})} + value={this.state.access_token} /> +
+
+ +

Mopidy

+
this.callMopidy(e)}> +
+
Call
+
+ this.setState({ mopidy_call: e.target.value })} + value={ this.state.mopidy_call } /> +
+
+
+
Data
+
+ +
+
+
+
+
+ +
+
+
+ +

Pusher

+
this.callPusher(e)}> +
+
Examples
+
+ +
+
+
+
Data
+
+ +
+
+
+
+
+ +
+
+
+ +

Response

+
+						{ this.props.debug_response ? JSON.stringify(this.props.debug_response, null, 2) : null }
+					
+ +
+
+ ); + } +} + + +/** + * Export our component + * + * We also integrate our global store, using connect() + **/ + +const mapStateToProps = (state, ownProps) => { + return { + connection_id: state.pusher.connection_id, + access_token: (state.spotify.access_token ? state.spotify.access_token : ''), + log_actions: (state.ui.log_actions ? state.ui.log_actions : false), + log_pusher: (state.ui.log_pusher ? state.ui.log_pusher : false), + log_mopidy: (state.ui.log_mopidy ? state.ui.log_mopidy : false), + debug_info: (state.ui.debug_info ? state.ui.debug_info : false), + debug_response: state.ui.debug_response + } +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Debug) \ No newline at end of file diff --git a/src/js/views/Settings.js b/src/js/views/Settings.js index 0baf6d91..c007958e 100755 --- a/src/js/views/Settings.js +++ b/src/js/views/Settings.js @@ -1,97 +1,394 @@ import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { hashHistory, Link } from 'react-router' +import { bindActionCreators } from 'redux' import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router' +import SpotifyAuthenticationFrame from '../components/SpotifyAuthenticationFrame' +import ConfirmationButton from '../components/ConfirmationButton' +import PusherConnectionList from '../components/PusherConnectionList' +import URISchemesList from '../components/URISchemesList' +import VersionManager from '../components/VersionManager' import Header from '../components/Header' import Parallax from '../components/Parallax' -import System from '../components/Settings/System' -import Services from '../components/Settings/Services' -import Debug from '../components/Settings/Debug' +import Icon from '../components/Icon' +import Thumbnail from '../components/Thumbnail' -export default class Settings extends React.Component { +import * as coreActions from '../services/core/actions' +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' + +class Settings extends React.Component { + + constructor(props) { + super(props); + this.state = { + country: this.props.core.country, + locale: this.props.core.locale, + mopidy_host: this.props.mopidy.host, + mopidy_port: this.props.mopidy.port, + pusher_username: this.props.pusher.username, + input_in_focus: null + } + } + + componentWillReceiveProps(newProps){ + var changed = false + var state = this.state + + if (newProps.spotify.country != this.state.spotify_country && this.state.input_in_focus != 'spotify_country'){ + state.spotify_country = newProps.spotify.country + changed = true + } + + if (newProps.spotify.locale != this.state.spotify_locale && this.state.input_in_focus != 'spotify_locale'){ + state.spotify_locale = newProps.spotify.locale + changed = true + } + + if (newProps.pusher.username != this.state.pusher_username && this.state.input_in_focus != 'pusher_username'){ + state.pusher_username = newProps.pusher.username + changed = true + } + + if (changed){ + this.setState(state) + } + } + + resetAllSettings(){ + localStorage.clear(); + window.location = '#' + window.location.reload(true) + return false; + } + + setMopidyConfig(e){ + this.setState({input_in_focus: null}) + e.preventDefault(); + this.props.mopidyActions.setConfig({ host: this.state.mopidy_host, port: this.state.mopidy_port }); + window.location.reload(true); + return false; + } + + handleBlur(name, value){ + this.setState({input_in_focus: null}) + var data = {} + data[name] = value + this.props.coreActions.set(data) + } + + handleUsernameChange(username){ + this.setState({pusher_username: username.replace(/\W/g, '')}) + } + + handleUsernameBlur(e){ + this.setState({input_in_focus: null}) + this.props.pusherActions.setUsername(this.state.pusher_username) + } + + renderApplyButton(){ + if (this.props.mopidy.host == this.state.mopidy_host && this.props.mopidy.port == this.state.mopidy_port) return null - renderSubViewMenu(){ return ( -
-

System

-

Services

-

Debug

-

About

+
+
+
+ +
) } - renderSubView(){ - switch (this.props.params.sub_view){ + renderSpotifyUser(){ - case 'services': - return ( -
- -
- ) + var user = null + if (this.props.spotify.me && this.props.spotify.authorization){ + user = this.props.spotify.me + } else if (this.props.spotify.backend_username){ + if (this.props.core.users && this.props.core.users['spotify:user:'+this.props.spotify.backend_username] !== undefined){ + user = this.props.core.users['spotify:user:'+this.props.spotify.backend_username] + } + } - case 'debug': - return ( -
- -
- ) + if (user){ + return ( + + + + {user.display_name ? user.display_name : user.id} + + + ) + } else if (this.props.spotify.backend_username){ + return ( + + + + {this.props.spotify.backend_username} + + + ) + } else { + return ( + + + + Unknown + + + ) + } + } - case 'about': - return ( -
-
-
- Iris is an open-source project by James Barnsley. It is provided free and with absolutely no warranty. If you paid someone for this software, please let me know. -
-
- Google Analytics is used to help trace issues and provide valuable insight into how we can continue to make improvements. -
-
-

-
- -  Donate - -    - -  GitHub - -    - Creative Commons License -
-
-
- ) + renderSendAuthorizationButton(){ + if (!this.props.spotify.authorization) return null - default: - return ( -
- -
- ) + return ( + + ) + } + + renderStatus(server){ + if (this.props[server].connecting){ + return   Connecting + } else if (server == 'spotify' && !this.props[server].authorization){ + return   Limited access + } else if (this.props[server].connected){ + return   Connected + } else { + return   Disconnected } } render(){ + + var options = ( + + + +   + Help + + + ) + return (
-
+
- {this.renderSubViewMenu()}
- {this.renderSubView()} -
+
+
+

Mopidy

+ {this.renderStatus('mopidy')} +
+
+

Pusher

+ {this.renderStatus('pusher')} +
+
+

Spotify

+ {this.renderStatus('spotify')} +
+
+ +

System

+ +
+
Username
+
+ this.handleUsernameChange(e.target.value)} + onFocus={e => this.setState({input_in_focus: 'pusher_username'})} + onBlur={e => this.handleUsernameBlur(e)} + value={this.state.pusher_username } /> +
+
+ +
this.setMopidyConfig(e)}> +
+
Host
+
+ this.setState({mopidy_host: e.target.value})} + onFocus={e => this.setState({input_in_focus: 'mopidy_host'})} + onBlur={e => this.setState({input_in_focus: null})} + value={ this.state.mopidy_host } /> +
+
+
+
Port
+
+ this.setState({mopidy_port: e.target.value})} + onFocus={e => this.setState({input_in_focus: 'mopidy_port'})} + onBlur={e => this.setState({input_in_focus: null})} + value={ this.state.mopidy_port } /> +
+
+ {this.renderApplyButton()} +
+ +

Localization

+ +
+
Country
+
+ this.setState({country: e.target.value})} + onFocus={e => this.setState({input_in_focus: 'country'})} + onBlur={e => this.handleBlur('country',e.target.value)} + value={ this.state.country } /> +
+
+
+
Locale
+
+ this.setState({locale: e.target.value})} + onFocus={e => this.setState({input_in_focus: 'locale'})} + onBlur={e => this.handleBlur('locale',e.target.value)} + value={this.state.locale} /> +
+
+ +

Spotify

+ +
+
Status
+
+
+ {!this.props.mopidy.uri_schemes || !this.props.mopidy.uri_schemes.includes('spotify:') ?
  Mopidy-Spotify not available
: null} + + {this.props.spotify.authorization ? Authorized  All Spotify functionality available : Limited access  Authorize Iris for full functionality} +
+
+
+
+
Authorization
+
+ + { this.renderSendAuthorizationButton() } +
+
+
+
Current user
+
+
+ { this.renderSpotifyUser() } +
+
+
+ +

Advanced

+ +
+
UI behavior
+
+ +
+
+ +
+
Version
+
+ +
+
+ +
+
Connections
+
+ + + +
+
+ +
+
Extensions
+
+ +
+
+ +
+
Reset
+
+ this.resetAllSettings()} /> +
+
+ +

About

+ +
+
+ Iris is an open-source project by James Barnsley. It is provided free and with absolutely no warranty. If you paid someone for this software, please let me know. +
+
+ Google Analytics is used to help trace issues and provide valuable insight into how we can continue to make improvements. +
+
+

+
+ +  Donate + +    + +  GitHub + +    + Creative Commons License +
+
+ +
); } -} \ No newline at end of file +} + +const mapStateToProps = (state, ownProps) => { + return state; +} + +const mapDispatchToProps = (dispatch) => { + return { + coreActions: bindActionCreators(coreActions, dispatch), + uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), + mopidyActions: bindActionCreators(mopidyActions, dispatch), + spotifyActions: bindActionCreators(spotifyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Settings) \ No newline at end of file diff --git a/src/scss/components/_grid.scss b/src/scss/components/_grid.scss index d212a3f0..b6d7ea15 100755 --- a/src/scss/components/_grid.scss +++ b/src/scss/components/_grid.scss @@ -186,17 +186,19 @@ &.artist-stats { .tile { - .fa { - display: block; - position: absolute; - top: 50%; - left: 10%; - right: 10%; - text-align: center; - margin-top: -27px; - font-size: 60px; - z-index: -1; - opacity: 0.1; + .content { + .fa { + display: block; + position: absolute; + top: 50%; + left: 10%; + right: 10%; + text-align: center; + margin-top: -27px; + font-size: 60px; + z-index: -1; + opacity: 0.1; + } } } diff --git a/src/scss/components/_images.scss b/src/scss/components/_images.scss index 16a43431..c3ac69c0 100755 --- a/src/scss/components/_images.scss +++ b/src/scss/components/_images.scss @@ -24,10 +24,10 @@ bottom: 5px; right: 5px; z-index: 1; - padding: 6px; + padding: 10px; border-radius: 100%; background: rgba(0,0,0,0.4); - font-size: 10px; + font-size: 12px; cursor: pointer; color: #FFFFFF; diff --git a/src/scss/components/_sub-views.scss b/src/scss/components/_sub-views.scss index 10a8d264..7107fa0b 100755 --- a/src/scss/components/_sub-views.scss +++ b/src/scss/components/_sub-views.scss @@ -1,5 +1,4 @@ - .sub-views { .option { @include feature_font(); @@ -23,4 +22,10 @@ border-color: rgba(255,255,255,0.2); } } + + @include responsive( $bp_medium ){ + .option { + margin-right: 15px; + } + } } \ No newline at end of file diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss index 3783fd33..def1d122 100755 --- a/src/scss/global/_core.scss +++ b/src/scss/global/_core.scss @@ -262,6 +262,7 @@ button.placeholder { @include clearfix; } +.pulse { @include pulse(); } .one-liner { @include one_line_text; } .grey-text { color: $mid_grey !important; } .red-text { color: $red !important; } diff --git a/src/scss/views/_settings.scss b/src/scss/views/_settings.scss index 3df44719..c9aec706 100755 --- a/src/scss/views/_settings.scss +++ b/src/scss/views/_settings.scss @@ -26,58 +26,30 @@ } } + .content-wrapper { + padding-top: 0; + } + .services { @include clearfix(); .service { - background: lighten($dark_grey,2%); - padding: 12px 15px; width: 24%; margin-right: 1.3334%; - float: left; border-radius: 3px; + padding: 16px; box-sizing: border-box; - - &:nth-child(4n){ - margin-right: 0; - } - - &.disabled { - color: $grey; - } - - &.connecting { - .status { - color: $grey; - } - } - - &.connected, - &.available { - .status { - color: $green; - } - } - - &.disconnected, - &.unavailable { - .status { - color: $red; - } - } + background: lighten($dark_grey, 5%); + position: relative; + float: left; .title { - text-transform: capitalize; - margin: 2px 0 5px 0; - } - - .status { - text-transform: capitalize; - font-size: 14px; + margin: 0; + padding: 0 0 4px 0; } } } - + .field.current-user { .text { padding: 14px 14px 14px 0px; @@ -113,30 +85,22 @@ .field.pusher-connections { .connection.me { - font-weight: bold; + color: $turquoise; } } @include responsive( $bp_medium ){ - .services { - .service { - padding: 0 0 0 30px; - margin-bottom: 0; - min-width: 0px; + .intro { + .liner { + padding: 10vh 20px 0; + } + } - h4 { - padding-top: 1px; - } - - .icon { - height: 15px; - width: 20px; - padding-top: 5px; - font-size: 10px; - } - - .text { - display: none; + .field.pusher-connections { + .connection { + .col { + float: none; + width: 100%; } } }