From 95efdb7303f4dbb2fa45617e90d68607d795e610 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Wed, 24 Aug 2016 13:48:35 -0400 Subject: [PATCH] Update respotify tutorial --- frontendJS/respotify/.eslintrc | 7 ++ frontendJS/respotify/package.json | 20 ++++- frontendJS/respotify/src/api/musicApi.js | 17 +++++ frontendJS/respotify/src/components/Album.js | 27 +++++++ .../respotify/src/components/AlbumList.js | 36 +++++++++ .../respotify/src/components/SearchBar.js | 53 ++++++++++++++ frontendJS/respotify/src/components/Track.js | 33 +++++++++ .../respotify/src/components/TrackList.js | 23 ++++++ frontendJS/respotify/src/greeting.js | 11 --- frontendJS/respotify/src/index.html | 2 +- frontendJS/respotify/src/index.js | 73 ++++++++++++++++++- frontendJS/respotify/webpack.config.js | 10 +++ 12 files changed, 292 insertions(+), 20 deletions(-) create mode 100644 frontendJS/respotify/.eslintrc create mode 100644 frontendJS/respotify/src/api/musicApi.js create mode 100644 frontendJS/respotify/src/components/Album.js create mode 100644 frontendJS/respotify/src/components/AlbumList.js create mode 100644 frontendJS/respotify/src/components/SearchBar.js create mode 100644 frontendJS/respotify/src/components/Track.js create mode 100644 frontendJS/respotify/src/components/TrackList.js delete mode 100644 frontendJS/respotify/src/greeting.js diff --git a/frontendJS/respotify/.eslintrc b/frontendJS/respotify/.eslintrc new file mode 100644 index 0000000..09af502 --- /dev/null +++ b/frontendJS/respotify/.eslintrc @@ -0,0 +1,7 @@ +{ + "extends": "airbnb", + "rules": { + "arrow-body-style": 0, + "no-param-reassign": 0 + } +} \ No newline at end of file diff --git a/frontendJS/respotify/package.json b/frontendJS/respotify/package.json index a092c5b..4f8d775 100644 --- a/frontendJS/respotify/package.json +++ b/frontendJS/respotify/package.json @@ -1,27 +1,39 @@ { "name": "respotify", "version": "1.0.0", - "description": "", + "description": "The webapp accompanying the React tutorial on patternhatch.com", "main": "index.js", "scripts": { "start": "webpack-dev-server --hot --inline", "build": "webpack", "test": "echo \"Error: no test specified\" && exit 1" }, - "keywords": [], - "author": "", - "license": "ISC", + "keywords": [ + "react", + "redux", + "webpack", + "babel" + ], + "author": "Nitin Punjabi", + "license": "MIT", "devDependencies": { "babel-core": "^6.10.4", "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.11.1", + "eslint": "^2.13.1", + "eslint-config-airbnb": "^9.0.1", + "eslint-loader": "^1.4.0", + "eslint-plugin-import": "^1.10.2", + "eslint-plugin-jsx-a11y": "^1.5.5", + "eslint-plugin-react": "^5.2.2", "file-loader": "^0.9.0", "react-hot-loader": "^1.3.0", "webpack": "^1.13.1", "webpack-dev-server": "^1.14.1" }, "dependencies": { + "axios": "^0.13.1", "react": "^15.2.0", "react-dom": "^15.2.0" } diff --git a/frontendJS/respotify/src/api/musicApi.js b/frontendJS/respotify/src/api/musicApi.js new file mode 100644 index 0000000..20e7a3d --- /dev/null +++ b/frontendJS/respotify/src/api/musicApi.js @@ -0,0 +1,17 @@ +import axios from 'axios'; + +function fetch(request, callback) { + axios.get(request).then(response => { + callback(response.data); + }); +} + +export function getAlbums(artist, callback) { + const request = `https://api.spotify.com/v1/search?q=${artist}&type=album`; + fetch(request, callback); +} + +export function getTracks(albumId, callback) { + const request = `https://api.spotify.com/v1/albums/${albumId}`; + fetch(request, callback); +} \ No newline at end of file diff --git a/frontendJS/respotify/src/components/Album.js b/frontendJS/respotify/src/components/Album.js new file mode 100644 index 0000000..e04da4f --- /dev/null +++ b/frontendJS/respotify/src/components/Album.js @@ -0,0 +1,27 @@ +import React from 'react'; + +const Album = (props) => { + return ( +
  • + {props.album.name} props.getTracks(props.album.id)} + /> +
  • + ); +}; + +Album.propTypes = { + album: React.PropTypes.object.isRequired, + getTracks: React.PropTypes.func.isRequired, +}; + +Album.styles = { + img: { + marginBottom: '1em', + }, +}; + +export default Album; \ No newline at end of file diff --git a/frontendJS/respotify/src/components/AlbumList.js b/frontendJS/respotify/src/components/AlbumList.js new file mode 100644 index 0000000..a3b5001 --- /dev/null +++ b/frontendJS/respotify/src/components/AlbumList.js @@ -0,0 +1,36 @@ +import React from 'react'; + +import Album from './Album'; + +const AlbumList = (props) => { + const albums = props.albums.map((album) => + ); + + return ( +
    +
      + {albums} +
    +
    + ); +}; + +AlbumList.propTypes = { + albums: React.PropTypes.array.isRequired, + getTracks: React.PropTypes.func.isRequired, +}; + +AlbumList.styles = { + div: { + width: 370, + marginLeft: 30, + textAlign: 'right', + maxHeight: '85vh', + overflowY: 'auto', + }, + ul: { + listStyle: 'none', + }, +}; + +export default AlbumList; \ No newline at end of file diff --git a/frontendJS/respotify/src/components/SearchBar.js b/frontendJS/respotify/src/components/SearchBar.js new file mode 100644 index 0000000..8214e7a --- /dev/null +++ b/frontendJS/respotify/src/components/SearchBar.js @@ -0,0 +1,53 @@ +import React from 'react'; + +class SearchBar extends React.Component { + constructor(props) { + super(props); + this.state = { + searchTerm: '', + }; + this.handleInputChange = this.handleInputChange.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + } + + handleInputChange(event) { + this.setState({ + searchTerm: event.target.value, + }); + } + + handleKeyPress(event) { + if (event.key === 'Enter') { + this.props.getAlbums(this.state.searchTerm); + } + } + + render() { + return ( +
    +

    Search for an Artist

    + +
    + ); + } +} + +SearchBar.propTypes = { + getAlbums: React.PropTypes.func.isRequired, +}; + +SearchBar.styles = { + div: { + margin: 30, + textAlign: 'center', + }, + input: { + width: '60%', + }, +}; + +export default SearchBar; \ No newline at end of file diff --git a/frontendJS/respotify/src/components/Track.js b/frontendJS/respotify/src/components/Track.js new file mode 100644 index 0000000..54c7003 --- /dev/null +++ b/frontendJS/respotify/src/components/Track.js @@ -0,0 +1,33 @@ +import React from 'react'; + +const mouseOverColor = '#add8e6'; +const mouseOutColor = 'white'; + +const Track = (props) => { + return ( +
  • {e.target.style.backgroundColor = mouseOverColor}} + onMouseOut={(e) => {e.target.style.backgroundColor = mouseOutColor}} + onClick={() => props.playPreview(props.track.preview_url)} + > + {props.track.name} +
  • + ); +}; + +Track.propTypes = { + track: React.PropTypes.object.isRequired, + playPreview: React.PropTypes.func.isRequired, +}; + +Track.styles = { + li: { + fontSize: '1.5em', + padding: '0.2em', + listStyleType: 'none', + backgroundColor: mouseOutColor, + }, +}; + +export default Track; \ No newline at end of file diff --git a/frontendJS/respotify/src/components/TrackList.js b/frontendJS/respotify/src/components/TrackList.js new file mode 100644 index 0000000..eeed8e7 --- /dev/null +++ b/frontendJS/respotify/src/components/TrackList.js @@ -0,0 +1,23 @@ +import React from 'react'; + +import Track from './Track'; + +const TrackList = (props) => { + const tracks = props.tracks.map((track) => + ); + + return ( +
    +
      + {tracks} +
    +
    + ); +}; + +TrackList.propTypes = { + tracks: React.PropTypes.array.isRequired, + playPreview: React.PropTypes.func.isRequired, +}; + +export default TrackList; \ No newline at end of file diff --git a/frontendJS/respotify/src/greeting.js b/frontendJS/respotify/src/greeting.js deleted file mode 100644 index 0f3f0c9..0000000 --- a/frontendJS/respotify/src/greeting.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -export default React.createClass({ - render: function() { - return ( -
    - Hello, {this.props.name}! -
    - ) - } -}) \ No newline at end of file diff --git a/frontendJS/respotify/src/index.html b/frontendJS/respotify/src/index.html index a889f34..18cb0db 100644 --- a/frontendJS/respotify/src/index.html +++ b/frontendJS/respotify/src/index.html @@ -3,9 +3,9 @@ Respotify + -

    Respotify

    diff --git a/frontendJS/respotify/src/index.js b/frontendJS/respotify/src/index.js index 4f0df5b..dc7d2d5 100644 --- a/frontendJS/respotify/src/index.js +++ b/frontendJS/respotify/src/index.js @@ -1,8 +1,73 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import Greeting from "./greeting"; +import React from 'react'; +import ReactDOM from 'react-dom'; + +import SearchBar from './components/SearchBar'; +import AlbumList from './components/AlbumList'; +import TrackList from './components/TrackList'; +import * as musicApi from './api/musicApi'; + +class App extends React.Component { + constructor() { + super(); + this.state = ({ + albums: [], + tracks: [], + currentPreview: null, + }); + this.getAlbums = this.getAlbums.bind(this); + this.processAlbums = this.processAlbums.bind(this); + this.getTracks = this.getTracks.bind(this); + this.processTracks = this.processTracks.bind(this); + this.playPreview = this.playPreview.bind(this); + } + + getAlbums(artist) { + musicApi.getAlbums(artist, this.processAlbums); + } + + getTracks(albumId) { + musicApi.getTracks(albumId, this.processTracks); + } + + processAlbums(payload) { + this.setState({ + albums: payload.albums.items, + tracks: [], + }); + } + + processTracks(payload) { + this.setState({ + tracks: payload.tracks.items, + }); + } + + playPreview(previewUrl) { + if (this.state.currentPreview) { + const curAudioObject = this.state.currentPreview; + curAudioObject.pause(); + } + + const newAudioObject = new Audio(previewUrl); + this.setState({ + currentPreview: newAudioObject + }); + + newAudioObject.play(); + } + + render() { + return ( +
    + + + +
    + ); + } +} ReactDOM.render( - , + , document.getElementById('container') ); \ No newline at end of file diff --git a/frontendJS/respotify/webpack.config.js b/frontendJS/respotify/webpack.config.js index e1168e5..cde0139 100644 --- a/frontendJS/respotify/webpack.config.js +++ b/frontendJS/respotify/webpack.config.js @@ -20,7 +20,17 @@ module.exports = { devServer: { contentBase: PATHS.dist }, + eslint: { + emitWarning: true + }, module: { + preloaders: [ + { + test: /\.js$/, + loaders: ['eslint-loader'], + exclude: /node_modules/ + } + ], loaders: [ { test: /\.html$/,