Pusher part one
This commit is contained in:
14
src/js/bootstrap.js
vendored
14
src/js/bootstrap.js
vendored
@ -4,16 +4,19 @@ console.info('Bootstrapping...');
|
||||
import { createStore, applyMiddleware, combineReducers } from 'redux'
|
||||
|
||||
import ui from './services/ui/reducer'
|
||||
import pusher from './services/pusher/reducer'
|
||||
import mopidy from './services/mopidy/reducer'
|
||||
import spotify from './services/spotify/reducer'
|
||||
|
||||
import thunk from 'redux-thunk'
|
||||
import pusherMiddleware from './services/pusher/middleware'
|
||||
import mopidyMiddleware from './services/mopidy/middleware'
|
||||
import spotifyMiddleware from './services/spotify/middleware'
|
||||
import localstorageMiddleware from './services/localstorage/middleware'
|
||||
|
||||
let reducers = combineReducers({
|
||||
ui,
|
||||
pusher,
|
||||
mopidy,
|
||||
spotify
|
||||
});
|
||||
@ -26,6 +29,9 @@ var initialState = {
|
||||
volume: 0,
|
||||
progress: 0
|
||||
},
|
||||
pusher: {
|
||||
port: 6681
|
||||
},
|
||||
spotify: {
|
||||
country: 'NZ',
|
||||
locale: 'en_NZ',
|
||||
@ -44,6 +50,12 @@ if( localStorage.getItem('mopidy') ){
|
||||
initialState.mopidy = Object.assign(initialState.mopidy, storedMopidy );
|
||||
}
|
||||
|
||||
// if we've got a stored version of pusher state, load and merge
|
||||
if( localStorage.getItem('pusher') ){
|
||||
var storedPusher = JSON.parse( localStorage.getItem('pusher') );
|
||||
initialState.pusher = Object.assign(initialState.pusher, storedPusher );
|
||||
}
|
||||
|
||||
// if we've got a stored version of spotify state, load and merge
|
||||
if( localStorage.getItem('spotify') ){
|
||||
var storedSpotify = JSON.parse( localStorage.getItem('spotify') );
|
||||
@ -53,7 +65,7 @@ if( localStorage.getItem('spotify') ){
|
||||
let store = createStore(
|
||||
reducers,
|
||||
initialState,
|
||||
applyMiddleware( thunk, localstorageMiddleware, mopidyMiddleware, spotifyMiddleware )
|
||||
applyMiddleware( thunk, localstorageMiddleware, pusherMiddleware, mopidyMiddleware, spotifyMiddleware )
|
||||
);
|
||||
|
||||
export default store;
|
||||
|
||||
@ -7,6 +7,7 @@ export default class ConfirmationButton extends React.Component{
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
timing_out: false,
|
||||
confirming: false
|
||||
}
|
||||
this.confirming = false;
|
||||
@ -27,16 +28,18 @@ export default class ConfirmationButton extends React.Component{
|
||||
}
|
||||
|
||||
handleMouseEnter(e){
|
||||
this.setState({ timing_out: false });
|
||||
clearTimeout( this.unconfirmTimer );
|
||||
}
|
||||
|
||||
handleMouseLeave(e){
|
||||
if( this.state.confirming ){
|
||||
this.setState({ timing_out: true });
|
||||
this.unconfirmTimer = setTimeout(
|
||||
function(){
|
||||
this.setState({ confirming: false });
|
||||
}.bind(this),
|
||||
1500
|
||||
2000
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -49,6 +52,7 @@ export default class ConfirmationButton extends React.Component{
|
||||
if( this.state.confirming ){
|
||||
className += ' confirming';
|
||||
content = this.props.confirmingContent;
|
||||
if( this.state.timing_out ) className += ' timing-out';
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -58,7 +58,7 @@ class SpotifyAuthenticationFrame extends React.Component{
|
||||
<div>
|
||||
<Thumbnail circle={true} size="small" images={this.props.spotify.me.images} />
|
||||
Logged in as {this.props.spotify.me.display_name ? this.props.spotify.me.display_name : null }
|
||||
<Link to={'/user/'+this.props.spotify.me.id}>{ this.props.spotify.me.id }</Link>
|
||||
(<Link to={'/user/'+this.props.spotify.me.id}>{ this.props.spotify.me.id }</Link>)
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
31
src/js/services/pusher/actions.js
Executable file
31
src/js/services/pusher/actions.js
Executable file
@ -0,0 +1,31 @@
|
||||
|
||||
/**
|
||||
* Actions and Action Creators
|
||||
**/
|
||||
|
||||
export function setConfig( config ){
|
||||
return {
|
||||
type: 'PUSHER_SET_CONFIG',
|
||||
config: config
|
||||
}
|
||||
}
|
||||
|
||||
export function connect(){
|
||||
return {
|
||||
type: 'PUSHER_CONNECT'
|
||||
}
|
||||
}
|
||||
|
||||
export function disconnect(){
|
||||
return {
|
||||
type: 'PUSHER_DISCONNECT'
|
||||
}
|
||||
}
|
||||
|
||||
export function instruct( call, value ){
|
||||
return {
|
||||
type: 'PUSHER_INSTRUCT',
|
||||
call: call,
|
||||
value: value
|
||||
}
|
||||
}
|
||||
58
src/js/services/pusher/middleware.js
Executable file
58
src/js/services/pusher/middleware.js
Executable file
@ -0,0 +1,58 @@
|
||||
|
||||
var actions = require('./actions.js')
|
||||
|
||||
const PusherMiddleware = (function(){
|
||||
|
||||
// container for the actual Mopidy socket
|
||||
var socket = null;
|
||||
|
||||
// handle all manner of socket messages
|
||||
const handleMessage = (ws, store, message) => {
|
||||
switch( message.action ){
|
||||
default:
|
||||
store.dispatch({ type: 'PUSHER_'+message.action.toUpperCase(), data: message.data })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware
|
||||
*
|
||||
* This behaves like an action interceptor. We listen for specific actions
|
||||
* and handle special functionality. If the action is not in our switch, then
|
||||
* it just proceeds to the next middleware, or default functionality
|
||||
**/
|
||||
return store => next => action => {
|
||||
|
||||
switch(action.type) {
|
||||
|
||||
case 'PUSHER_CONNECT':
|
||||
|
||||
if(socket != null) socket.close();
|
||||
store.dispatch({ type: 'PUSHER_CONNECTING' });
|
||||
|
||||
var state = store.getState();
|
||||
|
||||
socket = new WebSocket(
|
||||
'ws://'+state.mopidy.host+':'+state.pusher.port+'/pusher'
|
||||
);
|
||||
|
||||
socket.onopen = function(){
|
||||
store.dispatch({ type: 'PUSHER_CONNECTED' });
|
||||
};
|
||||
|
||||
socket.onmessage = function(message){
|
||||
var message = JSON.parse(message.data);
|
||||
handleMessage( socket, store, message )
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
export default PusherMiddleware
|
||||
23
src/js/services/pusher/reducer.js
Executable file
23
src/js/services/pusher/reducer.js
Executable file
@ -0,0 +1,23 @@
|
||||
|
||||
export default function reducer(pusher = {}, action){
|
||||
switch (action.type) {
|
||||
|
||||
case 'PUSHER_CONNECTED':
|
||||
return Object.assign({}, pusher, { connected: true, connecting: false });
|
||||
|
||||
case 'PUSHER_DISCONNECTED':
|
||||
return Object.assign({}, pusher, { connected: false, connecting: false });
|
||||
|
||||
case 'PUSHER_SET_CONFIG':
|
||||
return Object.assign({}, pusher, {
|
||||
host: action.host,
|
||||
port: action.port
|
||||
});
|
||||
|
||||
default:
|
||||
return pusher
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import Sidebar from '../components/Sidebar'
|
||||
import ContextMenu from '../components/ContextMenu'
|
||||
|
||||
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'
|
||||
|
||||
@ -25,7 +26,9 @@ class App extends React.Component{
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
this.props.pusherActions.connect();
|
||||
this.props.mopidyActions.connect();
|
||||
|
||||
window.addEventListener("keyup", this.handleKeyUp, false);
|
||||
window.addEventListener("keydown", this.handleKeyDown, false);
|
||||
}
|
||||
@ -94,6 +97,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)
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ class Settings extends React.Component{
|
||||
this.state = {
|
||||
mopidy_host: this.props.mopidy.host,
|
||||
mopidy_port: this.props.mopidy.port,
|
||||
pusher_port: this.props.pusher.port,
|
||||
spotify_country: this.props.spotify.country,
|
||||
spotify_locale: this.props.spotify.locale
|
||||
};
|
||||
@ -38,6 +39,10 @@ class Settings extends React.Component{
|
||||
this.props.spotifyActions.setConfig({ country: this.state.spotify_country, locale: this.state.spotify_locale });
|
||||
}
|
||||
|
||||
setPusherConfig(){
|
||||
this.props.pusherActions.setConfig({ port: this.state.pusher_port });
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="view settings-view">
|
||||
@ -64,7 +69,19 @@ class Settings extends React.Component{
|
||||
onChange={ e => this.setState({ mopidy_port: e.target.value })}
|
||||
value={ this.state.mopidy_port } />
|
||||
</label>
|
||||
<button type="submit">Apply</button>
|
||||
<button type="submit" className="secondary">Apply</button>
|
||||
</form>
|
||||
|
||||
<h3 className="underline">Pusher</h3>
|
||||
<form onSubmit={() => this.setPusherConfig()}>
|
||||
<label>
|
||||
<span className="label">Port</span>
|
||||
<input
|
||||
type="text"
|
||||
onChange={ e => this.setState({ pusher_port: e.target.value })}
|
||||
value={ this.state.pusher_port } />
|
||||
</label>
|
||||
<button type="submit" className="secondary">Apply</button>
|
||||
</form>
|
||||
|
||||
<h3 className="underline">Spotify</h3>
|
||||
@ -83,7 +100,7 @@ class Settings extends React.Component{
|
||||
onChange={ e => this.setState({ spotify_locale: e.target.value })}
|
||||
value={ this.state.spotify_locale } />
|
||||
</label>
|
||||
<button type="submit">Apply</button>
|
||||
<button type="submit" className="secondary">Apply</button>
|
||||
</form>
|
||||
|
||||
<SpotifyAuthenticationFrame />
|
||||
|
||||
@ -36,6 +36,14 @@ input[type="submit"] {
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: #000000;
|
||||
color: #FFFFFF;
|
||||
&:hover {
|
||||
background: lighten( #000000, 20% );
|
||||
}
|
||||
}
|
||||
|
||||
&.confirming,
|
||||
&.destructive {
|
||||
background: $red;
|
||||
@ -45,6 +53,20 @@ input[type="submit"] {
|
||||
}
|
||||
}
|
||||
|
||||
&.timing-out {
|
||||
position: relative;
|
||||
&::before {
|
||||
@include animate_timeout(2s);
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&[disabled="disabled"] {
|
||||
background: $light_grey !important;
|
||||
|
||||
@ -20,11 +20,22 @@ $red: #cf2d2d;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin animate( $speed: 0.2s ){
|
||||
-webkit-transition: all $speed ease-in-out;
|
||||
-moz-transition: all $speed ease-in-out;
|
||||
-o-transition: all $speed ease-in-out;
|
||||
transition: all $speed ease-in-out;
|
||||
@mixin animate( $duration: 0.2s, $easing: ease-in-out ){
|
||||
-webkit-transition: all $duration $easing;
|
||||
-moz-transition: all $duration $easing;
|
||||
-o-transition: all $duration $easing;
|
||||
transition: all $duration $easing;
|
||||
}
|
||||
|
||||
@keyframes fill_from_left {
|
||||
from { width: 0%; }
|
||||
to { width: 100%; }
|
||||
}
|
||||
|
||||
@mixin animate_timeout( $duration: 0.2s ){
|
||||
animation-name: fill_from_left;
|
||||
animation-duration: $duration;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@mixin blur( $size: 10px ) {
|
||||
|
||||
Reference in New Issue
Block a user