Update respotify tutorial
This commit is contained in:
parent
a2c108af80
commit
95efdb7303
7
frontendJS/respotify/.eslintrc
Normal file
7
frontendJS/respotify/.eslintrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "airbnb",
|
||||||
|
"rules": {
|
||||||
|
"arrow-body-style": 0,
|
||||||
|
"no-param-reassign": 0
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,39 @@
|
|||||||
{
|
{
|
||||||
"name": "respotify",
|
"name": "respotify",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "The webapp accompanying the React tutorial on patternhatch.com",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --hot --inline",
|
"start": "webpack-dev-server --hot --inline",
|
||||||
"build": "webpack",
|
"build": "webpack",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [
|
||||||
"author": "",
|
"react",
|
||||||
"license": "ISC",
|
"redux",
|
||||||
|
"webpack",
|
||||||
|
"babel"
|
||||||
|
],
|
||||||
|
"author": "Nitin Punjabi",
|
||||||
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.10.4",
|
"babel-core": "^6.10.4",
|
||||||
"babel-loader": "^6.2.4",
|
"babel-loader": "^6.2.4",
|
||||||
"babel-preset-es2015": "^6.9.0",
|
"babel-preset-es2015": "^6.9.0",
|
||||||
"babel-preset-react": "^6.11.1",
|
"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",
|
"file-loader": "^0.9.0",
|
||||||
"react-hot-loader": "^1.3.0",
|
"react-hot-loader": "^1.3.0",
|
||||||
"webpack": "^1.13.1",
|
"webpack": "^1.13.1",
|
||||||
"webpack-dev-server": "^1.14.1"
|
"webpack-dev-server": "^1.14.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.13.1",
|
||||||
"react": "^15.2.0",
|
"react": "^15.2.0",
|
||||||
"react-dom": "^15.2.0"
|
"react-dom": "^15.2.0"
|
||||||
}
|
}
|
||||||
|
17
frontendJS/respotify/src/api/musicApi.js
Normal file
17
frontendJS/respotify/src/api/musicApi.js
Normal file
@ -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);
|
||||||
|
}
|
27
frontendJS/respotify/src/components/Album.js
Normal file
27
frontendJS/respotify/src/components/Album.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Album = (props) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<img
|
||||||
|
src={props.album.images[1].url}
|
||||||
|
alt={props.album.name}
|
||||||
|
style={Album.styles.img}
|
||||||
|
onClick={() => props.getTracks(props.album.id)}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Album.propTypes = {
|
||||||
|
album: React.PropTypes.object.isRequired,
|
||||||
|
getTracks: React.PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
Album.styles = {
|
||||||
|
img: {
|
||||||
|
marginBottom: '1em',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Album;
|
36
frontendJS/respotify/src/components/AlbumList.js
Normal file
36
frontendJS/respotify/src/components/AlbumList.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Album from './Album';
|
||||||
|
|
||||||
|
const AlbumList = (props) => {
|
||||||
|
const albums = props.albums.map((album) =>
|
||||||
|
<Album key={album.id} album={album} getTracks={props.getTracks} />);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-md-3" style={AlbumList.styles.div}>
|
||||||
|
<ul style={AlbumList.styles.ul}>
|
||||||
|
{albums}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
53
frontendJS/respotify/src/components/SearchBar.js
Normal file
53
frontendJS/respotify/src/components/SearchBar.js
Normal file
@ -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 (
|
||||||
|
<div style={SearchBar.styles.div}>
|
||||||
|
<h3>Search for an Artist</h3>
|
||||||
|
<input
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
style={SearchBar.styles.input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchBar.propTypes = {
|
||||||
|
getAlbums: React.PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchBar.styles = {
|
||||||
|
div: {
|
||||||
|
margin: 30,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
width: '60%',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchBar;
|
33
frontendJS/respotify/src/components/Track.js
Normal file
33
frontendJS/respotify/src/components/Track.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const mouseOverColor = '#add8e6';
|
||||||
|
const mouseOutColor = 'white';
|
||||||
|
|
||||||
|
const Track = (props) => {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
style={Track.styles.li}
|
||||||
|
onMouseOver={(e) => {e.target.style.backgroundColor = mouseOverColor}}
|
||||||
|
onMouseOut={(e) => {e.target.style.backgroundColor = mouseOutColor}}
|
||||||
|
onClick={() => props.playPreview(props.track.preview_url)}
|
||||||
|
>
|
||||||
|
{props.track.name}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
23
frontendJS/respotify/src/components/TrackList.js
Normal file
23
frontendJS/respotify/src/components/TrackList.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Track from './Track';
|
||||||
|
|
||||||
|
const TrackList = (props) => {
|
||||||
|
const tracks = props.tracks.map((track) =>
|
||||||
|
<Track key={track.key} track={track} playPreview={props.playPreview} />);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-md-3">
|
||||||
|
<ul style={{ listStyle: 'none' }}>
|
||||||
|
{tracks}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
TrackList.propTypes = {
|
||||||
|
tracks: React.PropTypes.array.isRequired,
|
||||||
|
playPreview: React.PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TrackList;
|
@ -1,11 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default React.createClass({
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div className="greeting">
|
|
||||||
Hello, {this.props.name}!
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
@ -3,9 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Respotify</title>
|
<title>Respotify</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Respotify</h1>
|
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
<script src="/bundle.js"></script>
|
<script src="/bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,8 +1,73 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from 'react-dom';
|
||||||
import Greeting from "./greeting";
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<SearchBar getAlbums={this.getAlbums} />
|
||||||
|
<AlbumList albums={this.state.albums} getTracks={this.getTracks} />
|
||||||
|
<TrackList tracks={this.state.tracks} playPreview={this.playPreview} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Greeting name="World" />,
|
<App />,
|
||||||
document.getElementById('container')
|
document.getElementById('container')
|
||||||
);
|
);
|
@ -20,7 +20,17 @@ module.exports = {
|
|||||||
devServer: {
|
devServer: {
|
||||||
contentBase: PATHS.dist
|
contentBase: PATHS.dist
|
||||||
},
|
},
|
||||||
|
eslint: {
|
||||||
|
emitWarning: true
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
|
preloaders: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loaders: ['eslint-loader'],
|
||||||
|
exclude: /node_modules/
|
||||||
|
}
|
||||||
|
],
|
||||||
loaders: [
|
loaders: [
|
||||||
{
|
{
|
||||||
test: /\.html$/,
|
test: /\.html$/,
|
||||||
|
Loading…
Reference in New Issue
Block a user