diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js
index a1092d6c..6c9e7c61 100755
--- a/src/js/bootstrap.js
+++ b/src/js/bootstrap.js
@@ -64,7 +64,8 @@ var initialState = {
playlists: [],
tracks: []
},
- playlists: []
+ playlists: [],
+ notifications: []
}
};
diff --git a/src/js/components/Notifications.js b/src/js/components/Notifications.js
new file mode 100755
index 00000000..7705514f
--- /dev/null
+++ b/src/js/components/Notifications.js
@@ -0,0 +1,47 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+import FontAwesome from 'react-fontawesome'
+
+import * as actions from '../services/ui/actions'
+
+class Notifications extends React.Component{
+
+ constructor(props) {
+ super(props)
+ }
+
+ render(){
+ if( !this.props.notifications ) return null
+
+ return (
+
+ {
+ this.props.notifications.map( notification => {
+ return (
+
+ this.props.actions.removeNotification(notification.id) } />
+ { notification.content }
+
+ )
+ })
+ }
+
+ )
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ notifications: state.ui.notifications
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ actions: bindActionCreators(actions, dispatch)
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Notifications)
\ No newline at end of file
diff --git a/src/js/services/localstorage/middleware.js b/src/js/services/localstorage/middleware.js
index 67bac113..3c323be8 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/ui/actions.js b/src/js/services/ui/actions.js
index d7992586..54463659 100755
--- a/src/js/services/ui/actions.js
+++ b/src/js/services/ui/actions.js
@@ -183,6 +183,13 @@ export function addTracksToPlaylist( playlist_uri, tracks_uris ){
}
}
+
+/**
+ * Modal
+ *
+ * Immersive full-screen dialog
+ **/
+
export function openModal( name, data ){
return {
type: 'OPEN_MODAL',
@@ -197,3 +204,30 @@ export function closeModal(){
return { type: 'CLOSE_MODAL' }
}
+
+/**
+ * Notifications
+ *
+ * Subtle info/tooltip messages
+ **/
+
+export function createNotification( content, type = 'default' ){
+ return {
+ type: 'CREATE_NOTIFICATION',
+ notification: {
+ id: helpers.generateGuid(),
+ type: type,
+ content: content
+ }
+ }
+}
+
+export function removeNotification( id ){
+ return {
+ type: 'REMOVE_NOTIFICATION',
+ id: id
+ }
+}
+
+
+
diff --git a/src/js/services/ui/middleware.js b/src/js/services/ui/middleware.js
index 5a7a5bb3..f48b801e 100755
--- a/src/js/services/ui/middleware.js
+++ b/src/js/services/ui/middleware.js
@@ -10,13 +10,26 @@ const UIMiddleware = (function(){
* The actual middleware inteceptor
**/
return store => next => action => {
- var state = store.getState();
switch(action.type){
+ case 'CREATE_NOTIFICATION':
+
+ // start a timeout to remove this notification
+ var timeout = setTimeout(
+ function(){
+ store.dispatch(actions.removeNotification(action.notification.id))
+ },
+ 3000
+ )
+
+ // we don't want to stop things happening as usual
+ next(action)
+ break
+
// This action is irrelevant to us, pass it on to the next middleware
default:
- return next(action);
+ return next(action)
}
}
diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js
index 16fa5497..3b6d11c6 100755
--- a/src/js/services/ui/reducer.js
+++ b/src/js/services/ui/reducer.js
@@ -396,6 +396,26 @@ export default function reducer(ui = {}, action){
return Object.assign({}, ui, { modal: false })
+ /**
+ * Notifications
+ **/
+
+ case 'CREATE_NOTIFICATION':
+ var notifications = [...ui.notifications, action.notification]
+ return Object.assign({}, ui, { notifications: notifications })
+
+ case 'REMOVE_NOTIFICATION':
+ var notifications = Object.assign([], ui.notifications)
+
+ function getByID( notification ){
+ return notification.id === action.id
+ }
+ var index = notifications.findIndex(getByID)
+ if( index > -1 ) notifications.splice(index, 1)
+
+ return Object.assign({}, ui, { notifications: notifications })
+
+
default:
return ui
}
diff --git a/src/js/views/App.js b/src/js/views/App.js
index 33dc107d..1273292e 100755
--- a/src/js/views/App.js
+++ b/src/js/views/App.js
@@ -11,6 +11,7 @@ import SidebarToggleButton from '../components/SidebarToggleButton'
import ContextMenu from '../components/ContextMenu'
import Dragger from '../components/Dragger'
import Modal from '../components/Modal/Modal'
+import Notifications from '../components/Notifications'
import * as uiActions from '../services/ui/actions'
import * as pusherActions from '../services/pusher/actions'
@@ -130,6 +131,7 @@ class App extends React.Component{
+
);
}
diff --git a/src/js/views/Debug.js b/src/js/views/Debug.js
index 29a1dcd7..8415de60 100755
--- a/src/js/views/Debug.js
+++ b/src/js/views/Debug.js
@@ -49,6 +49,7 @@ class Debug extends React.Component{
e.preventDefault()
console.info('Mopidy Debugger', this.state.mopidy_call, JSON.parse(this.state.mopidy_data) )
this.props.mopidyActions.debug( this.state.mopidy_call, JSON.parse(this.state.mopidy_data) )
+ this.props.uiActions.createNotification( 'Mopidy instruction sent' )
}
callPusher(e){
@@ -56,6 +57,7 @@ class Debug extends React.Component{
console.info('Pusher Debugger', this.state.pusher_call, JSON.parse(this.state.pusher_data) )
this.props.pusherActions.debug( this.state.pusher_call, JSON.parse(this.state.pusher_data) )
this.props.uiActions.debugResponse({ status: 1, message: 'Sent', call: this.state.pusher_call, data: this.state.pusher_data })
+ this.props.uiActions.createNotification( 'Pusher instruction sent' )
}
render(){
diff --git a/src/scss/app.scss b/src/scss/app.scss
index 34d8a4ca..bde4c461 100755
--- a/src/scss/app.scss
+++ b/src/scss/app.scss
@@ -19,6 +19,7 @@
@import 'components/lists';
@import 'components/grid';
@import 'components/header';
+@import 'components/notifications';
@import 'components/dropdown-field';
@import 'views/artist';
diff --git a/src/scss/components/_lists.scss b/src/scss/components/_lists.scss
index ee7fb6e0..74d4815a 100755
--- a/src/scss/components/_lists.scss
+++ b/src/scss/components/_lists.scss
@@ -24,7 +24,7 @@
}
&.selected {
- background: #FFF39C !important;
+ background: $yellow !important;
}
&:not(.header):hover {
diff --git a/src/scss/components/_notifications.scss b/src/scss/components/_notifications.scss
new file mode 100755
index 00000000..ab928bb7
--- /dev/null
+++ b/src/scss/components/_notifications.scss
@@ -0,0 +1,55 @@
+
+
+.notifications {
+ position: fixed;
+ top: 15px;
+ left: 0;
+ right: 0;
+ text-align: center;
+
+ .notification {
+ display: inline-block;
+ position: relative;
+ background: $dark_grey;
+ padding: 8px 28px 8px 14px;
+ margin: 0 0.5px;
+ color: #FFFFFF;
+
+ &:first-child {
+ border-top-left-radius: 50px;
+ border-bottom-left-radius: 50px;
+ }
+
+ &:last-child {
+ border-top-right-radius: 50px;
+ border-bottom-right-radius: 50px;
+ }
+
+ .close-button {
+ position: absolute;
+ top: 7px;
+ right: 8px;
+ font-size: 10px;
+ padding: 4px;
+ cursor: pointer;
+ opacity: 0.5;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+
+ &.good {
+ background: $green;
+ }
+
+ &.info {
+ background: $yellow;
+ color: #000000;
+ }
+
+ &.bad {
+ background: $red;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss
index 3b8249a3..1e79a131 100755
--- a/src/scss/global/_variables.scss
+++ b/src/scss/global/_variables.scss
@@ -9,6 +9,7 @@ $secondary_grey: #888888;
$red: #cf2d2d;
$green: #47af2a;
$blue: #32b5f2;
+$yellow: #FFF39C;
$bp_wide: 1000px;
$bp_medium: 800px;
diff --git a/src/scss/views/_artist.scss b/src/scss/views/_artist.scss
index e7fde8e3..d2a1e296 100755
--- a/src/scss/views/_artist.scss
+++ b/src/scss/views/_artist.scss
@@ -86,7 +86,7 @@
.intro {
padding: 50px 30px 0 30px;
- margin: 0 0 100px 0;
+ margin: 0 0 140px 0;
.parallax {
height: 100%;
@@ -117,10 +117,15 @@
.details-wrapper {
padding: 0;
position: absolute;
- bottom: -90px;
- left: 30px;
- width: 100%;
+ left: 0;
+ right: 0;
+ padding: 30px;
+ background: $faint_grey;
z-index: 2;
+
+ .details {
+ clear: both;
+ }
}
}