From 47b6009ec3f0a60497ca48f08c1cf9f9a8f8f63b Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Wed, 26 Oct 2016 16:55:47 +1300 Subject: [PATCH] Discover sections --- src/js/components/ArtistGrid.js | 3 +- src/js/components/CategoryGrid.js | 30 ++++++++ src/js/components/GridItem.js | 4 +- src/js/components/Parallax.js | 25 +++++-- src/js/components/PlaylistGrid.js | 30 ++++++++ src/js/components/Sidebar.js | 12 ++++ src/js/index.js | 26 ++++--- src/js/services/spotify/actions.js | 77 ++++++++++++++++++++- src/js/services/spotify/reducer.js | 20 ++++-- src/js/views/Album.js | 2 +- src/js/views/Artist.js | 6 +- src/js/views/Playlist.js | 2 +- src/js/views/Queue.js | 2 +- src/js/views/Settings.js | 2 +- src/js/views/discover/Discover.js | 58 ++++++++++++++++ src/js/views/discover/DiscoverCategories.js | 49 +++++++++++++ src/js/views/discover/DiscoverCategory.js | 58 ++++++++++++++++ src/js/views/discover/DiscoverFeatured.js | 49 +++++++++++++ src/js/views/library/LibraryAlbums.js | 21 +++--- src/js/views/library/LibraryArtists.js | 21 +++--- src/js/views/library/LibraryPlaylists.js | 6 +- src/js/views/library/LibraryTracks.js | 15 ++-- src/scss/components/_grid.scss | 17 +++++ src/scss/components/_images.scss | 8 ++- src/scss/global/_variables.scss | 8 +++ src/scss/views/_artist.scss | 13 ++++ 26 files changed, 500 insertions(+), 64 deletions(-) create mode 100755 src/js/components/CategoryGrid.js create mode 100755 src/js/components/PlaylistGrid.js create mode 100755 src/js/views/discover/Discover.js create mode 100755 src/js/views/discover/DiscoverCategories.js create mode 100755 src/js/views/discover/DiscoverCategory.js create mode 100755 src/js/views/discover/DiscoverFeatured.js diff --git a/src/js/components/ArtistGrid.js b/src/js/components/ArtistGrid.js index e9f3aaed..a5fdc8fb 100755 --- a/src/js/components/ArtistGrid.js +++ b/src/js/components/ArtistGrid.js @@ -17,8 +17,7 @@ export default class ArtistGrid extends React.Component{ { this.props.artists.map( (artist, index) => { - var link = artist.uri; - return + return } ) } diff --git a/src/js/components/CategoryGrid.js b/src/js/components/CategoryGrid.js new file mode 100755 index 00000000..38ef492b --- /dev/null +++ b/src/js/components/CategoryGrid.js @@ -0,0 +1,30 @@ + +import React, { PropTypes } from 'react' +import GridItem from './GridItem' + +export default class CategoryGrid extends React.Component{ + + constructor(props) { + super(props); + } + + render(){ + if( this.props.categories ){ + var className = "grid category-grid" + if( this.props.className ) className += ' '+this.props.className + return ( +
+ { + this.props.categories.map( + (category, index) => { + return + } + ) + } +
+ ); + } + return null; + } +} + diff --git a/src/js/components/GridItem.js b/src/js/components/GridItem.js index 973070f9..7a958c15 100755 --- a/src/js/components/GridItem.js +++ b/src/js/components/GridItem.js @@ -25,10 +25,12 @@ export default class GridItem extends React.Component{ } return (
this.handleClick(e) }> - { item.images ? : item.name } + { item.images ? : null } + { item.icons ? : null }
{ item.name }
{ item.artists ? : null } + { item.type == 'playlist' ? item.tracks.total+' tracks' : null }
); diff --git a/src/js/components/Parallax.js b/src/js/components/Parallax.js index b9330889..1b42ecf2 100755 --- a/src/js/components/Parallax.js +++ b/src/js/components/Parallax.js @@ -15,10 +15,12 @@ export default class Parallax extends React.Component{ width: 0, height: 0 }, - original: {}, image: {}, - imageObject: {} + loaded: false } + + // we need to manually bind this as eventListener doesn't work with anonymous functions + this.handleResize = this.handleResize.bind(this); } componentDidMount(){ @@ -26,14 +28,22 @@ export default class Parallax extends React.Component{ this.loadImage( url ) .then( response => { - this.setState({ image: response }) + this.setState({ image: response, loaded: true }) this.updateCanvas( response ) } ) - $(window).resize(() => this.updateCanvas( this.state.image )); + window.addEventListener("resize", this.handleResize); } + componentWillUnmount() { + window.removeEventListener("resize", this.handleResize); + } + + handleResize(){ + this.updateCanvas( this.state.image ); + } + loadImage( url ){ return new Promise( (resolve, reject) => { @@ -137,7 +147,12 @@ export default class Parallax extends React.Component{ render(){ return (
- + +
); } diff --git a/src/js/components/PlaylistGrid.js b/src/js/components/PlaylistGrid.js new file mode 100755 index 00000000..59794170 --- /dev/null +++ b/src/js/components/PlaylistGrid.js @@ -0,0 +1,30 @@ + +import React, { PropTypes } from 'react' +import GridItem from './GridItem' + +export default class PlaylistGrid extends React.Component{ + + constructor(props) { + super(props); + } + + render(){ + if( this.props.playlists ){ + var className = "grid playlist-grid" + if( this.props.className ) className += ' '+this.props.className + return ( +
+ { + this.props.playlists.map( + (playlist, index) => { + return + } + ) + } +
+ ); + } + return null; + } +} + diff --git a/src/js/components/Sidebar.js b/src/js/components/Sidebar.js index 32f73022..45f3038c 100755 --- a/src/js/components/Sidebar.js +++ b/src/js/components/Sidebar.js @@ -38,6 +38,18 @@ class Sidebar extends React.Component{
Discover + + + Discover + + + + Genre / Mood + + + + Featured playlists +
diff --git a/src/js/index.js b/src/js/index.js index 39bb8ef9..1dd82549 100755 --- a/src/js/index.js +++ b/src/js/index.js @@ -19,6 +19,11 @@ import Playlist from './views/Playlist' import Queue from './views/Queue' import Settings from './views/Settings' +import Discover from './views/discover/Discover' +import DiscoverFeatured from './views/discover/DiscoverFeatured' +import DiscoverCategories from './views/discover/DiscoverCategories' +import DiscoverCategory from './views/discover/DiscoverCategory' + import LibraryArtists from './views/library/LibraryArtists' import LibraryAlbums from './views/library/LibraryAlbums' import LibraryTracks from './views/library/LibraryTracks' @@ -32,15 +37,20 @@ ReactDOM.render( - - - - + + + + + + + + + - - - - + + + + diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 194eb416..1ccfba67 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -330,7 +330,7 @@ export function getLibraryAlbums(){ } export function getLibraryTracks(){ - return (dispatch, getState) => { + return (dispatch, getState) => { dispatch({ type: 'SPOTIFY_LIBRARY_TRACKS_LOADED', data: false }); @@ -341,6 +341,81 @@ export function getLibraryTracks(){ data: response }); }); + } +} + +export function getFeaturedPlaylists(){ + return (dispatch, getState) => { + + dispatch({ type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED', data: false }); + + var date = new Date(); + var year = date.getFullYear(); + var month = date.getMonth(); + if( month < 10 ) month = '0'+month; + var day = date.getDay(); + if( day < 10 ) day = '0'+day; + var hour = date.getHours(); + if( hour < 10 ) hour = '0'+hour; + var min = date.getMinutes(); + if( min < 10 ) min = '0'+min; + var sec = date.getSeconds(); + if( sec < 10 ) sec = '0'+sec; + + var timestamp = year+'-'+month+'-'+day+'T'+hour+':'+min+':'+sec; + + sendRequest( dispatch, getState, 'browse/featured-playlists?timestamp='+timestamp+'&country='+getState().spotify.country+'&limit=50' ) + .then( response => { + dispatch({ + type: 'SPOTIFY_FEATURED_PLAYLISTS_LOADED', + data: response + }); + }); + } +} + +export function getCategories(){ + return (dispatch, getState) => { + + dispatch({ type: 'SPOTIFY_CATEGORIES_LOADED', data: false }); + + sendRequest( dispatch, getState, 'browse/categories?limit=50' ) + .then( response => { + dispatch({ + type: 'SPOTIFY_CATEGORIES_LOADED', + data: response.categories + }); + }); + } +} + +export function getCategory( id ){ + return (dispatch, getState) => { + + dispatch({ type: 'SPOTIFY_CATEGORY_LOADED', data: false }); + + sendRequest( dispatch, getState, 'browse/categories/'+id ) + .then( response => { + dispatch({ + type: 'SPOTIFY_CATEGORY_LOADED', + data: response + }); + }); + } +} + +export function getCategoryPlaylists( id ){ + return (dispatch, getState) => { + + dispatch({ type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', data: false }); + + sendRequest( dispatch, getState, 'browse/categories/'+id+'/playlists?limit=50' ) + .then( response => { + dispatch({ + type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', + data: response.playlists + }); + }); } } diff --git a/src/js/services/spotify/reducer.js b/src/js/services/spotify/reducer.js index 972667f5..34736502 100755 --- a/src/js/services/spotify/reducer.js +++ b/src/js/services/spotify/reducer.js @@ -59,16 +59,28 @@ export default function reducer(spotify = {}, action){ return Object.assign({}, spotify, { artist_albums: action.data }); case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED': - return Object.assign({}, spotify, { libraryPlaylists: action.data }); + return Object.assign({}, spotify, { library_playlists: action.data }); case 'SPOTIFY_LIBRARY_ARTISTS_LOADED': - return Object.assign({}, spotify, { libraryArtists: action.data }); + return Object.assign({}, spotify, { library_artists: action.data }); case 'SPOTIFY_LIBRARY_ALBUMS_LOADED': - return Object.assign({}, spotify, { libraryAlbums: action.data }); + return Object.assign({}, spotify, { library_albums: action.data }); case 'SPOTIFY_LIBRARY_TRACKS_LOADED': - return Object.assign({}, spotify, { libraryTracks: action.data }); + return Object.assign({}, spotify, { library_tracks: action.data }); + + case 'SPOTIFY_FEATURED_PLAYLISTS_LOADED': + return Object.assign({}, spotify, { featured_playlists: action.data }); + + case 'SPOTIFY_CATEGORIES_LOADED': + return Object.assign({}, spotify, { categories: action.data }); + + case 'SPOTIFY_CATEGORY_LOADED': + return Object.assign({}, spotify, { category: action.data }); + + case 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED': + return Object.assign({}, spotify, { category_playlists: action.data }); default: return spotify diff --git a/src/js/views/Album.js b/src/js/views/Album.js index 199d81fc..8a42599d 100755 --- a/src/js/views/Album.js +++ b/src/js/views/Album.js @@ -30,7 +30,7 @@ class Album extends React.Component{ render(){ if( this.props.spotify.album ){ return ( -
+
+
@@ -49,11 +49,13 @@ class Artist extends React.Component{
-
+

Related artists

{ this.props.spotify.artist.related_artists ? : null }
+
+

Albums

{ this.props.spotify.artist_albums ? : null }
diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index 5dcecc92..f9b22349 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -30,7 +30,7 @@ class Playlist extends React.Component{ render(){ if( this.props.spotify.playlist ){ return ( -
+
diff --git a/src/js/views/Queue.js b/src/js/views/Queue.js index 91359b5b..3040cfaf 100755 --- a/src/js/views/Queue.js +++ b/src/js/views/Queue.js @@ -61,7 +61,7 @@ class Queue extends React.Component{ render(){ return ( -
+
+
+
+

To come

+
+ ); + } + return null; + } +} + + +/** + * Export our component + * + * We also integrate our global store, using connect() + **/ + +const mapStateToProps = (state, ownProps) => { + return state; +} + +const mapDispatchToProps = (dispatch) => { + return { + spotifyActions: bindActionCreators(spotifyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Discover) \ No newline at end of file diff --git a/src/js/views/discover/DiscoverCategories.js b/src/js/views/discover/DiscoverCategories.js new file mode 100755 index 00000000..f3d05e1e --- /dev/null +++ b/src/js/views/discover/DiscoverCategories.js @@ -0,0 +1,49 @@ + +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' + +import Header from '../../components/Header' +import CategoryGrid from '../../components/CategoryGrid' + +import * as spotifyActions from '../../services/spotify/actions' + +class DiscoverCategories extends React.Component{ + + constructor(props) { + super(props); + } + + // on render + componentDidMount(){ + this.props.spotifyActions.getCategories(); + } + + render(){ + return ( +
+
+ { this.props.spotify.categories ? : null } +
+ ); + } +} + + +/** + * Export our component + * + * We also integrate our global store, using connect() + **/ + +const mapStateToProps = (state, ownProps) => { + return state; +} + +const mapDispatchToProps = (dispatch) => { + return { + spotifyActions: bindActionCreators(spotifyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(DiscoverCategories) \ No newline at end of file diff --git a/src/js/views/discover/DiscoverCategory.js b/src/js/views/discover/DiscoverCategory.js new file mode 100755 index 00000000..d12724bd --- /dev/null +++ b/src/js/views/discover/DiscoverCategory.js @@ -0,0 +1,58 @@ + +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' + +import Header from '../../components/Header' +import PlaylistGrid from '../../components/PlaylistGrid' + +import * as spotifyActions from '../../services/spotify/actions' + +class DiscoverCategory extends React.Component{ + + constructor(props) { + super(props); + } + + componentDidMount(){ + this.props.spotifyActions.getCategory( this.props.params.id ); + this.props.spotifyActions.getCategoryPlaylists( this.props.params.id ); + } + + componentWillReceiveProps( nextProps ){ + if( nextProps.params.id != this.props.params.id ){ + this.props.spotifyActions.getCategory( nextProps.params.id ); + this.props.spotifyActions.getCategoryPlaylists( nextProps.params.id ); + } + } + + render(){ + if( !this.props.spotify.category ) return null; + + return ( +
+
+ { this.props.spotify.category_playlists ? : null } +
+ ); + } +} + + +/** + * Export our component + * + * We also integrate our global store, using connect() + **/ + +const mapStateToProps = (state, ownProps) => { + return state; +} + +const mapDispatchToProps = (dispatch) => { + return { + spotifyActions: bindActionCreators(spotifyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(DiscoverCategory) \ No newline at end of file diff --git a/src/js/views/discover/DiscoverFeatured.js b/src/js/views/discover/DiscoverFeatured.js new file mode 100755 index 00000000..3969e2e1 --- /dev/null +++ b/src/js/views/discover/DiscoverFeatured.js @@ -0,0 +1,49 @@ + +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' + +import Header from '../../components/Header' +import PlaylistGrid from '../../components/PlaylistGrid' + +import * as spotifyActions from '../../services/spotify/actions' + +class DiscoverFeatured extends React.Component{ + + constructor(props) { + super(props); + } + + // on render + componentDidMount(){ + this.props.spotifyActions.getFeaturedPlaylists(); + } + + render(){ + return ( +
+
+ { this.props.spotify.featuredPlaylists ? : null } +
+ ); + } +} + + +/** + * Export our component + * + * We also integrate our global store, using connect() + **/ + +const mapStateToProps = (state, ownProps) => { + return state; +} + +const mapDispatchToProps = (dispatch) => { + return { + spotifyActions: bindActionCreators(spotifyActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(DiscoverFeatured) \ No newline at end of file diff --git a/src/js/views/library/LibraryAlbums.js b/src/js/views/library/LibraryAlbums.js index fe0f9603..176b6fd2 100755 --- a/src/js/views/library/LibraryAlbums.js +++ b/src/js/views/library/LibraryAlbums.js @@ -22,18 +22,15 @@ class LibraryAlbums extends React.Component{ } render(){ - if( this.props.spotify.libraryAlbums ){ - return ( -
-
- -
- ); - } - return null; + return ( +
+
+ { this.props.spotify.library_albums ? : null } +
+ ); } } diff --git a/src/js/views/library/LibraryArtists.js b/src/js/views/library/LibraryArtists.js index f323843d..84b0682f 100755 --- a/src/js/views/library/LibraryArtists.js +++ b/src/js/views/library/LibraryArtists.js @@ -22,18 +22,15 @@ class LibraryArtists extends React.Component{ } render(){ - if( this.props.spotify.libraryArtists ){ - return ( -
-
- -
- ); - } - return null; + return ( +
+
+ { this.props.spotify.library_artists ? : null } +
+ ); } } diff --git a/src/js/views/library/LibraryPlaylists.js b/src/js/views/library/LibraryPlaylists.js index 744486cd..9e661aa2 100755 --- a/src/js/views/library/LibraryPlaylists.js +++ b/src/js/views/library/LibraryPlaylists.js @@ -37,8 +37,8 @@ class LibraryPlaylists extends React.Component{ Object.assign(playlists, this.props.mopidy.playlists) } - if( this.props.spotify.libraryPlaylists ){ - Object.assign(playlists, this.props.spotify.libraryPlaylists.items) + if( this.props.spotify.library_playlists ){ + Object.assign(playlists, this.props.spotify.library_playlists.items) } return playlists; @@ -67,7 +67,7 @@ class LibraryPlaylists extends React.Component{ render(){ return ( -
+
-
- -
- ); - } - return null; + return ( +
+
+ { this.props.spotify.library_tracks ? : null } +
+ ); } } diff --git a/src/scss/components/_grid.scss b/src/scss/components/_grid.scss index 54bcac5c..100cc054 100755 --- a/src/scss/components/_grid.scss +++ b/src/scss/components/_grid.scss @@ -34,4 +34,21 @@ opacity: 0.75; } } + + &.category-grid { + .grid-item { + position: relative; + + .name { + position: absolute; + bottom: 15%; + left: 0; + right: 0; + text-align: center; + font-size: 14px; + font-weight: 600; + color: #FFFFFF; + } + } + } } \ No newline at end of file diff --git a/src/scss/components/_images.scss b/src/scss/components/_images.scss index 268ca1bb..0f04bd21 100755 --- a/src/scss/components/_images.scss +++ b/src/scss/components/_images.scss @@ -28,10 +28,16 @@ height: 50vh; width: 100%; overflow: hidden; - background: $light_grey; + background: $dark_grey; canvas { width: 100%; height: 100%; + opacity: 0; + @include animate( 0.4s ); + + &.loaded { + opacity: 0.5; + } } } \ No newline at end of file diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss index 14404b1c..d020e7ac 100755 --- a/src/scss/global/_variables.scss +++ b/src/scss/global/_variables.scss @@ -1,6 +1,7 @@ $turquoise: #08d58f; $light_grey: #DDDDDD; +$dark_grey: #333333; $secondary_grey: #888888; @mixin one_line_text { @@ -15,4 +16,11 @@ $secondary_grey: #888888; clear: both; display: block; } +} + +@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; } \ No newline at end of file diff --git a/src/scss/views/_artist.scss b/src/scss/views/_artist.scss index 8b137891..25d2152b 100755 --- a/src/scss/views/_artist.scss +++ b/src/scss/views/_artist.scss @@ -1 +1,14 @@ +.artist-view { + .intro { + position: relative; + margin-bottom: 20px; + @include clearfix; + + .thumbnail { + max-width: 200px; + margin: -100px 20px 0 40px; + float: left; + } + } +}