Parallax refinement; Album responsive; Pusher browser notifications
This commit is contained in:
@ -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(){
|
||||
|
||||
@ -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 (
|
||||
<div className="pusher-connection-list">
|
||||
{
|
||||
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 (
|
||||
<div className="connection cf" key={connection.connectionid}>
|
||||
<div className="col w30">{ connection.username } { isMe ? <span>(you)</span> : null }</div>
|
||||
<div className={ is_me ? 'connection cf me': 'connection cf'} key={connection.connectionid}>
|
||||
<div className="col w30">{ connection.username } { is_me ? <span>(you)</span> : null }</div>
|
||||
<div className="col w70">
|
||||
{ connection.ip }
|
||||
<span className="grey-text"> ({ connection.connectionid })</span>
|
||||
@ -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) => {
|
||||
|
||||
@ -59,20 +59,6 @@ class SpotifyAuthenticationFrame extends React.Component{
|
||||
})
|
||||
}
|
||||
|
||||
renderMe(){
|
||||
if( !this.props.authorized || !this.props.me ) return null;
|
||||
|
||||
return (
|
||||
<div className="me">
|
||||
<Thumbnail circle={true} size="small" images={this.props.me.images} />
|
||||
<div className="user-name">
|
||||
Logged in as {this.props.me.display_name ? this.props.me.display_name : null }
|
||||
(<Link to={'/user/'+this.props.me.uri}>{ this.props.me.id }</Link>)
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderAuthorizeButton(){
|
||||
if( this.state.authorizing ){
|
||||
return (
|
||||
@ -114,7 +100,6 @@ class SpotifyAuthenticationFrame extends React.Component{
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
{ 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -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 ){
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -45,7 +45,7 @@ class Queue extends React.Component{
|
||||
return (
|
||||
<div className="view queue-view">
|
||||
|
||||
<SidebarToggleButton className="dark-text" />
|
||||
<Header icon="play" title="Now playing" />
|
||||
|
||||
<FullPlayer />
|
||||
|
||||
|
||||
@ -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 (
|
||||
<Link className="user" to={'/user/'+this.props.spotify.me.uri}>
|
||||
<Thumbnail circle={true} size="small" images={this.props.spotify.me.images} />
|
||||
<span className="user-name">
|
||||
{ this.props.spotify.me.display_name ? this.props.spotify.me.display_name : this.props.spotify.me.id }
|
||||
</span>
|
||||
</Link>
|
||||
)
|
||||
}else{
|
||||
return (
|
||||
<Link className="user">
|
||||
<Thumbnail circle={true} size="small" />
|
||||
<span className="user-name">
|
||||
Default user <span className="grey-text">(As defined in mopidy.config)</span>
|
||||
</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="view settings-view">
|
||||
@ -178,6 +202,14 @@ 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">
|
||||
@ -188,7 +220,7 @@ class Settings extends React.Component{
|
||||
|
||||
<h4 className="underline">Advanced</h4>
|
||||
|
||||
<div className="field">
|
||||
<div className="field pusher-connections">
|
||||
<div className="name">Connections</div>
|
||||
<div className="input">
|
||||
<span className="text">
|
||||
|
||||
@ -73,4 +73,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include responsive( $bp_medium ){
|
||||
|
||||
.main {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.intro {
|
||||
position: static;
|
||||
width: auto;
|
||||
|
||||
.artist-grid {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,8 +91,7 @@
|
||||
.parallax {
|
||||
height: 100%;
|
||||
canvas {
|
||||
@include blur();
|
||||
margin: -20px;
|
||||
@include blur( 30px );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user