diff --git a/src/js/components/Parallax.js b/src/js/components/Parallax.js index 6fb7d1e1..662b26cf 100755 --- a/src/js/components/Parallax.js +++ b/src/js/components/Parallax.js @@ -31,6 +31,22 @@ export default class Parallax extends React.Component{ this._mounted = true window.addEventListener("resize", this.handleResize); window.addEventListener("scroll", this.handleScroll); + + if( this.props.image ){ + this._loading = true + this.setState({ url: this.props.image, image: false, loading: true }) + this.loadImage( this.props.image ) + .then( + response => { + if( this._mounted ){ + this._loading = false + this._loaded = true + this.setState({ url: this.props.image, image: response, loading: false }) + this.updateCanvas( response ) + } + } + ) + } } componentWillUnmount(){ diff --git a/src/js/components/PusherConnectionList.js b/src/js/components/PusherConnectionList.js index 57b544ce..30191be4 100755 --- a/src/js/components/PusherConnectionList.js +++ b/src/js/components/PusherConnectionList.js @@ -15,30 +15,30 @@ class PusherConnectionList extends React.Component{ } componentDidMount(){ - if( this.props.pusher.connected ){ + if( this.props.connected ){ this.props.pusherActions.getConnectionList(); } } componentWillReceiveProps(newProps){ - if( !this.props.pusher.connected && newProps.pusher.connected ){ + if( !this.props.connected && newProps.connected ){ this.props.pusherActions.getConnectionList(); } } render(){ - if( !this.props.pusher.connected ) return null; - if( typeof(this.props.pusher.connections) == 'undefined' || this.props.pusher.connections.length <= 0 ) return null; + if( !this.props.connected ) return null; + if( typeof(this.props.connections) == 'undefined' || this.props.connections.length <= 0 ) return null; return (
{ - this.props.pusher.connections.map( (connection, index) => { - var isMe = false; - if( connection.connectionid == this.props.pusher.connectionid ) isMe = true; + this.props.connections.map( (connection, index) => { + var is_me = false; + if( connection.connectionid == this.props.connectionid ) is_me = true; return ( -
-
{ connection.username } { isMe ? (you) : null }
+
+
{ connection.username } { is_me ? (you) : null }
{ connection.ip } ({ connection.connectionid }) @@ -53,7 +53,11 @@ class PusherConnectionList extends React.Component{ } const mapStateToProps = (state, ownProps) => { - return state; + return { + connected: state.pusher.connected, + connectionid: state.pusher.connectionid, + connections: state.pusher.connections + } } const mapDispatchToProps = (dispatch) => { diff --git a/src/js/components/SpotifyAuthenticationFrame.js b/src/js/components/SpotifyAuthenticationFrame.js index e731fa67..d70369bf 100755 --- a/src/js/components/SpotifyAuthenticationFrame.js +++ b/src/js/components/SpotifyAuthenticationFrame.js @@ -59,20 +59,6 @@ class SpotifyAuthenticationFrame extends React.Component{ }) } - renderMe(){ - if( !this.props.authorized || !this.props.me ) return null; - - return ( -
- -
- Logged in as {this.props.me.display_name ? this.props.me.display_name : null } -  ({ this.props.me.id }) -
-
- ) - } - renderAuthorizeButton(){ if( this.state.authorizing ){ return ( @@ -114,7 +100,6 @@ class SpotifyAuthenticationFrame extends React.Component{ render(){ return (
- { this.renderMe() } { this.renderAuthorizeButton() }    { this.renderRefreshButton() } @@ -128,8 +113,7 @@ const mapStateToProps = (state, ownProps) => { return { authorized: state.spotify.authorized, authorizing: state.spotify.authorizing, - refreshing_token: state.spotify.refreshing_token, - me: state.spotify.me + refreshing_token: state.spotify.refreshing_token } } diff --git a/src/js/components/Thumbnail.js b/src/js/components/Thumbnail.js index 616e4609..66dc631c 100755 --- a/src/js/components/Thumbnail.js +++ b/src/js/components/Thumbnail.js @@ -20,10 +20,14 @@ export default class Thumbnail extends React.Component{ this.mapImageSizes( nextProps ); } - mapImageSizes( props = this.props ){ + mapImageSizes( props = this.props ){ + + // no images + if( !this.props.image && !this.props.images ){ + this.setState({ url: require('../../assets/no-image.svg') }) // single image - if( this.props.image ){ + }else if( this.props.image ){ this.setState({ url: this.props.image }) // multiple images @@ -36,9 +40,7 @@ export default class Thumbnail extends React.Component{ } render(){ - var style = { - backgroundImage: 'url("'+this.state.url+'")' - } + var style = { backgroundImage: 'url("'+this.state.url+'")' } var className = 'thumbnail '+this.props.size; if( this.props.circle ) className += ' circle'; diff --git a/src/js/helpers.js b/src/js/helpers.js index f1b3b804..790fb625 100755 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -80,6 +80,19 @@ export let getCurrentPusherConnection = function( connections, connectionid ){ } +/** + * Get a track's icon + * @param track object + * @return string + **/ +export let getTrackIcon = function( track = false ){ + if( !track ) return false + if( !track.album ) return false + if( !track.album.images ) return false + return sizedImages( track.album.images ).small +} + + /** diff --git a/src/js/services/localstorage/middleware.js b/src/js/services/localstorage/middleware.js index 87d5ca69..7180f5c3 100755 --- a/src/js/services/localstorage/middleware.js +++ b/src/js/services/localstorage/middleware.js @@ -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 ){ diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index cd744138..5b135963 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -161,6 +161,47 @@ const MopidyMiddleware = (function(){ instruct( socket, store, action.call, action.value ) break; + // send an instruction to the websocket + case 'MOPIDY_NEXT': + var icon = false + if( store.getState().ui.current_track ){ + icon = helpers.getTrackIcon( store.getState().ui.current_track ) + } + store.dispatch({ + type: 'PUSHER_SEND_BROADCAST', + action: 'broadcast', + ignore_self: true, + data: { + type: 'notification', + data: { + title: 'Track skipped', + body: store.getState().pusher.username +' skipped this track', + icon: icon + } + } + }) + break; + + case 'MOPIDY_STOP': + var icon = false + if( store.getState().ui.current_track ){ + icon = helpers.getTrackIcon( store.getState().ui.current_track ) + } + store.dispatch({ + type: 'PUSHER_SEND_BROADCAST', + action: 'broadcast', + ignore_self: true, + data: { + type: 'notification', + data: { + title: 'Playback stopped', + body: store.getState().pusher.username +' stopped playback', + icon: icon + } + } + }) + break; + // send an instruction to the websocket case 'MOPIDY_URISCHEMES': var uri_schemes = action.data diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index d3cecb59..d3015351 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -13,7 +13,7 @@ const PusherMiddleware = (function(){ default: var name = 'unspecified' if( message.action ) name = message.action - name = name.replace('GET_','').toUpperCase() + name = name.replace('get_','').toUpperCase() store.dispatch({ type: 'PUSHER_'+name, data: message.data }) } } @@ -24,6 +24,12 @@ const PusherMiddleware = (function(){ socket.send( JSON.stringify(data) ); } + const broadcast = (data) => { + data.type = 'broadcast'; + socket.send( JSON.stringify(data) ); + } + + /** * Middleware * @@ -84,6 +90,38 @@ const PusherMiddleware = (function(){ makeRequest({ action: action.action, data: action.data }); break; + case 'PUSHER_SEND_BROADCAST': + broadcast({ action: action.action, data: action.data }); + break; + + case 'PUSHER_NOTIFICATION': + case 'PUSHER_BROADCAST': + + var notification = window.Notification || window.mozNotification || window.webkitNotification; + if ('undefined' === typeof notification) return false; + if ('undefined' !== typeof notification) notification.requestPermission(function(permission){}); + + // handle nested data objects + var data = {} + if( typeof(action.data) ) data = action.data + if( typeof(data.data) ) data = Object.assign({}, data, data.data) + + // construct our browser notification + var title = ''; + var options = { + body: '', + dir: 'auto', + lang: 'EN', + tag: 'iris' + }; + if( data.title ) title = data.title; + if( data.body ) options.body = data.body; + if( data.icon ) options.icon = data.icon; + + // make it so + var notification = new notification( title, options ); + break; + // This action is irrelevant to us, pass it on to the next middleware default: return next(action); diff --git a/src/js/views/Queue.js b/src/js/views/Queue.js index a9a277c9..ff27091e 100755 --- a/src/js/views/Queue.js +++ b/src/js/views/Queue.js @@ -45,7 +45,7 @@ class Queue extends React.Component{ return (
- +
diff --git a/src/js/views/Settings.js b/src/js/views/Settings.js index d7a933de..c7d549fd 100755 --- a/src/js/views/Settings.js +++ b/src/js/views/Settings.js @@ -1,15 +1,17 @@ import React, { PropTypes } from 'react' import { connect } from 'react-redux' +import { Link } 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 pusherActions from '../services/pusher/actions' import * as mopidyActions from '../services/mopidy/actions' @@ -84,6 +86,28 @@ class Settings extends React.Component{ } } + renderSpotifyUser(){ + if( this.props.spotify.me && this.props.spotify.authorized ){ + return ( + + + + { this.props.spotify.me.display_name ? this.props.spotify.me.display_name : this.props.spotify.me.id } + + + ) + }else{ + return ( + + + + Default user (As defined in mopidy.config) + + + ) + } + } + render(){ return (
@@ -178,6 +202,14 @@ class Settings extends React.Component{ value={ this.state.spotify_locale } />
+
+
Current user
+
+
+ { this.renderSpotifyUser() } +
+
+
Authentication
@@ -188,7 +220,7 @@ class Settings extends React.Component{

Advanced

-
+
Connections
diff --git a/src/scss/views/_album.scss b/src/scss/views/_album.scss index de6f964e..f2bab1db 100755 --- a/src/scss/views/_album.scss +++ b/src/scss/views/_album.scss @@ -73,4 +73,20 @@ } } } + + @include responsive( $bp_medium ){ + + .main { + padding-left: 0; + } + + .intro { + position: static; + width: auto; + + .artist-grid { + display: none; + } + } + } } diff --git a/src/scss/views/_artist.scss b/src/scss/views/_artist.scss index b45b6501..d86261e4 100755 --- a/src/scss/views/_artist.scss +++ b/src/scss/views/_artist.scss @@ -91,8 +91,7 @@ .parallax { height: 100%; canvas { - @include blur(); - margin: -20px; + @include blur( 30px ); } } diff --git a/src/scss/views/_settings.scss b/src/scss/views/_settings.scss index 6a4e620c..5406955d 100755 --- a/src/scss/views/_settings.scss +++ b/src/scss/views/_settings.scss @@ -4,18 +4,41 @@ padding: 40px; } - .me { - display: block; - float: left; + .field.current-user { + .text { + padding: 14px 14px 14px 0px; - .thumbnail { - float: left; - margin-right: 10px; - max-width: 40px; + .user { + border: 0; + position: relative; + + .thumbnail { + @include animate(); + position: absolute; + display: block; + top: -14px; + left: 0; + max-width: 40px; + border: 2px solid transparent; + } + + .user-name { + padding: 2px 14px 2px 55px; + } + + &[href]:hover { + .thumbnail { + cursor: pointer; + border-color: $turquoise; + } + } + } } + } - .user-name { - padding: 2px 14px; + .field.pusher-connections { + .connection.me { + font-weight: bold; } }