diff --git a/package.json b/package.json index 4f5778ce..dd58d52b 100755 --- a/package.json +++ b/package.json @@ -35,9 +35,10 @@ "node-sass": "*", "extract-text-webpack-plugin": "*", "jquery": "3.1.1", - "mopidy": "^0.5.0", + "mopidy": "^0.5.0", "expose-loader": "*", - "react-fontawesome": "*" + "react-fontawesome": "*", + "react-ga": "2.1.2" }, "scripts": { "dev": "./build.sh & NODE_ENV=development webpack", diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js index a3946952..2a6371df 100755 --- a/src/js/bootstrap.js +++ b/src/js/bootstrap.js @@ -36,7 +36,7 @@ var initialState = { pusher: { connected: false, port: 6681, - username: '', + username: 'Anonymous', connections: [], version: { current: '0.0.0' diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index db4c42b7..e491630e 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -42,7 +42,7 @@ class ContextMenu extends React.Component{ playQueueItem(){ var selectedTracks = this.props.context_menu.data.selected_tracks; - this.props.mopidyActions.changeTrack( selectedTracks[0].tlid ); + this.props.mopidyActions.changeTrack( selectedTracks[0].tlid ); this.props.uiActions.hideContextMenu(); } diff --git a/src/js/components/Modal/SendAuthorizationModal.js b/src/js/components/Modal/SendAuthorizationModal.js index 71a86c85..31e4d8e8 100755 --- a/src/js/components/Modal/SendAuthorizationModal.js +++ b/src/js/components/Modal/SendAuthorizationModal.js @@ -38,6 +38,10 @@ class SendAuthorizationModal extends React.Component{
{ this.props.connections.map( (connection, index) => { + + // don't list OUR connection + if (connection.connectionid == this.props.connectionid) return null + return (
this.handleClick(e, connection.connectionid) }> { connection.username } @@ -57,6 +61,7 @@ const mapStateToProps = (state, ownProps) => { return { me: state.spotify.me, authorization: state.spotify.authorization, + connectionid: state.pusher.connectionid, connections: state.pusher.connections } } diff --git a/src/js/components/SpotifyAuthenticationFrame.js b/src/js/components/SpotifyAuthenticationFrame.js index b8a53fbc..6dec7c91 100755 --- a/src/js/components/SpotifyAuthenticationFrame.js +++ b/src/js/components/SpotifyAuthenticationFrame.js @@ -3,6 +3,7 @@ import React, { PropTypes } from 'react' import { connect } from 'react-redux' import { Link } from 'react-router' import { createStore, bindActionCreators } from 'redux' +import ReactGA from 'react-ga' import FontAwesome from 'react-fontawesome' import Thumbnail from './Thumbnail' diff --git a/src/js/index.js b/src/js/index.js index 0c763991..d80f10db 100755 --- a/src/js/index.js +++ b/src/js/index.js @@ -7,9 +7,9 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux' import { createStore } from 'redux' import { Router, Route, Link, IndexRoute, hashHistory } from 'react-router' +import ReactGA from 'react-ga' import store from './bootstrap.js' - require('../scss/app.scss'); import App from './views/App' @@ -37,11 +37,18 @@ import LibraryLocalArtists from './views/library/LibraryLocalArtists' import LibraryLocalAlbums from './views/library/LibraryLocalAlbums' import LibraryLocalDirectory from './views/library/LibraryLocalDirectory' +// setup our analytics tracking +ReactGA.initialize('UA-64701652-3'); +function logPageView() { + ReactGA.set({ page: window.location.hash }) + ReactGA.pageview(window.location.hash) +} + global.baseURL = '/' ReactDOM.render( - + diff --git a/src/js/services/spotify/middleware.js b/src/js/services/spotify/middleware.js index 26857254..7d3dc0e2 100755 --- a/src/js/services/spotify/middleware.js +++ b/src/js/services/spotify/middleware.js @@ -16,7 +16,6 @@ const SpotifyMiddleware = (function(){ case 'SPOTIFY_CONNECT': store.dispatch( spotifyActions.getMe() ) break - case 'SPOTIFY_CREATE_PLAYLIST': var playlist = state.ui.playlist @@ -28,7 +27,6 @@ const SpotifyMiddleware = (function(){ store.dispatch( spotifyActions.createPlaylist( action.name, action.is_private )) break - case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS': var playlist = state.ui.playlist diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 880f51c2..5fed3166 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -18,9 +18,10 @@ export function hideContextMenu(){ } } -export function searchStarted(){ +export function searchStarted( query ){ return { type: 'SEARCH_STARTED', + query: query, data: { artists: [], albums: [], diff --git a/src/js/services/ui/middleware.js b/src/js/services/ui/middleware.js index adb9c655..2e4137cc 100755 --- a/src/js/services/ui/middleware.js +++ b/src/js/services/ui/middleware.js @@ -1,4 +1,5 @@ +import ReactGA from 'react-ga' var uiActions = require('./actions.js') var spotifyActions = require('../spotify/actions.js') @@ -13,6 +14,122 @@ const UIMiddleware = (function(){ switch(action.type){ + case 'MOPIDY_CONNECTED': + ReactGA.event({ category: 'Mopidy', action: 'Connected', label: window.location.hostname }) + next(action) + break + + case 'PUSHER_CONNECTED': + ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.connection.username }) + next(action) + break + + case 'SPOTIFY_CONNECTED': + var label = null + if (store.getState().spotify.me) label = store.getState().spotify.me.id + ReactGA.event({ category: 'Spotify', action: 'Connected', label: label }) + next(action) + break + + case 'SPOTIFY_AUTHORIZATION_GRANTED': + ReactGA.event({ category: 'Spotify', action: 'Authorization granted' }) + next(action) + break + + case 'SPOTIFY_AUTHORIZATION_REVOKED': + var label = null + if (store.getState().spotify.me) label = store.getState().spotify.me.id + ReactGA.event({ category: 'Spotify', action: 'Authorization revoked', label: label }) + next(action) + break + + case 'SPOTIFY_ME_LOADED': + ReactGA.event({ category: 'Spotify', action: 'Authorization verified', label: action.data.id }) + next(action) + break + + case 'MOPIDY_ALBUM_LOADED': + case 'SPOTIFY_ALBUM_LOADED': + if (action.data) ReactGA.event({ category: 'Album', action: 'Load', label: action.data.uri }) + next(action) + break + + case 'MOPIDY_ARTIST_LOADED': + case 'SPOTIFY_ARTIST_LOADED': + if (action.data) ReactGA.event({ category: 'Artist', action: 'Load', label: action.data.uri }) + next(action) + break + + case 'SPOTIFY_USER_LOADED': + if (action.data) ReactGA.event({ category: 'User', action: 'Load', label: action.data.uri }) + next(action) + break + + case 'MOPIDY_DIRECTORY': + if (action.data) ReactGA.event({ category: 'Directory', action: 'Load', label: action.data.uri }) + next(action) + break + + case 'MOPIDY_PLAYLIST_LOADED': + case 'SPOTIFY_PLAYLIST_LOADED': + if (action.data) ReactGA.event({ category: 'Playlist', action: 'Load', label: action.data.uri }) + next(action) + break + + case 'MOPIDY_SAVE_PLAYLIST': + case 'SPOTIFY_SAVE_PLAYLIST': + ReactGA.event({ category: 'Playlist', action: 'Save', label: action.uri }) + next(action) + break + + case 'MOPIDY_CREATE_PLAYLIST': + ReactGA.event({ category: 'Playlist', action: 'Create', label: 'Mopidy,'+action.name }) + next(action) + break + + case 'SPOTIFY_CREATE_PLAYLIST': + ReactGA.event({ category: 'Playlist', action: 'Create', label: 'Spotify,'+action.name }) + next(action) + break + + case 'MOPIDY_REORDER_PLAYLIST_TRACKS': + case 'SPOTIFY_REORDER_PLAYLIST_TRACKS': + ReactGA.event({ category: 'Playlist', action: 'Reorder tracks', label: action.uri }) + next(action) + break + + case 'MOPIDY_ADD_PLAYLIST_TRACKS': + case 'SPOTIFY_ADD_PLAYLIST_TRACKS': + ReactGA.event({ category: 'Playlist', action: 'Add tracks', label: action.playlist_uri }) + next(action) + break + + case 'MOPIDY_REMOVE_PLAYLIST_TRACKS': + case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS': + ReactGA.event({ category: 'Playlist', action: 'Remove tracks', label: action.playlist_uri }) + next(action) + break + + case 'SEARCH_STARTED': + ReactGA.event({ category: 'Search', action: 'Started', label: action.query }) + next(action) + break + + case 'PUSHER_START_RADIO': + ReactGA.event({ category: 'Pusher', action: 'Start radio', label: action.uris.join() }) + next(action) + break + + case 'PUSHER_STOP_RADIO': + ReactGA.event({ category: 'Pusher', action: 'Stop radio' }) + next(action) + break + + case 'PUSHER_UPGRADING': + ReactGA.event({ category: 'Pusher', action: 'Upgrade', label: action.data }) + next(action) + break + case 'CREATE_NOTIFICATION': // start a timeout to remove this notification diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index b8400907..f985a768 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import { Link } from 'react-router' import FontAwesome from 'react-fontawesome' +import ReactGA from 'react-ga' import TrackList from '../components/TrackList' import Thumbnail from '../components/Thumbnail' @@ -58,28 +59,34 @@ class Playlist extends React.Component{ } play(){ + ReactGA.event({ category: 'Playlist', action: 'Play', label: this.props.playlist.uri }) this.props.mopidyActions.playURIs([this.props.playlist.uri]) } follow(){ + ReactGA.event({ category: 'Playlist', action: 'Follow', label: this.props.playlist.uri }) this.props.spotifyActions.toggleFollowingPlaylist( this.props.playlist.uri, 'PUT' ) } // TODO: Once unfollowing occurs, remove playlist from global playlists list unfollow(){ + ReactGA.event({ category: 'Playlist', action: 'Unfollow', label: this.props.playlist.uri }) this.props.spotifyActions.toggleFollowingPlaylist( this.props.playlist.uri, 'DELETE' ) } // TODO: Once deletion occurs, remove playlist from global playlists list delete(){ + ReactGA.event({ category: 'Playlist', action: 'Delete', label: this.props.playlist.uri }) this.props.mopidyActions.deletePlaylist( this.props.playlist.uri ) } reorderTracks( indexes, index ){ + ReactGA.event({ category: 'Playlist', action: 'Reorder tracks', label: this.props.playlist.uri }) this.props.uiActions.reorderPlaylistTracks( this.props.playlist.uri, indexes, index, this.props.playlist.snapshot_id ) } removeTracks( tracks_indexes ){ + ReactGA.event({ category: 'Playlist', action: 'Remove tracks', label: this.props.playlist.uri }) this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, tracks_indexes ) } diff --git a/src/js/views/Search.js b/src/js/views/Search.js index ee2b3518..70db32fb 100755 --- a/src/js/views/Search.js +++ b/src/js/views/Search.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux' import { Link } from 'react-router' import { bindActionCreators } from 'redux' import FontAwesome from 'react-fontawesome' +import ReactGA from 'react-ga' import Header from '../components/Header' import TrackList from '../components/TrackList' @@ -40,7 +41,7 @@ class Search extends React.Component{ } performSearch( props = this.props ){ - this.props.uiActions.searchStarted() + this.props.uiActions.searchStarted( props.params.query ) this.props.spotifyActions.getSearchResults( props.params.query ) this.props.mopidyActions.getSearchResults( props.params.query, props.uri_schemes ) } diff --git a/src/js/views/Settings.js b/src/js/views/Settings.js index 11b81d0c..cbe2eeb1 100755 --- a/src/js/views/Settings.js +++ b/src/js/views/Settings.js @@ -51,14 +51,6 @@ class Settings extends React.Component{ return false; } - componentWillReceiveProps(newProps){ - if( this.state.pusher_username != newProps.pusher.username ){ - this.setState({ - pusher_username: newProps.pusher.username - }) - } - } - renderConnectionStatus(service){ if( this.props[service].connected ){ return (