Parallax refinement; Album responsive; Pusher browser notifications

This commit is contained in:
James Barnsley
2016-11-29 15:36:01 +13:00
parent 6101ef264b
commit 83a899f24e
13 changed files with 216 additions and 48 deletions

View File

@ -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(){

View File

@ -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) => {

View File

@ -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 }
&nbsp;(<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() }
&nbsp;&nbsp;
{ 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
}
}

View File

@ -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';

View File

@ -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
}
/**

View File

@ -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 ){

View File

@ -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

View File

@ -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);

View File

@ -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 />

View File

@ -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">

View File

@ -73,4 +73,20 @@
}
}
}
@include responsive( $bp_medium ){
.main {
padding-left: 0;
}
.intro {
position: static;
width: auto;
.artist-grid {
display: none;
}
}
}
}

View File

@ -91,8 +91,7 @@
.parallax {
height: 100%;
canvas {
@include blur();
margin: -20px;
@include blur( 30px );
}
}

View File

@ -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;
}
}