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