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",
|
||||
"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"
|
||||
}
|
||||
|
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>
|
||||
<title>Respotify</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Respotify</h1>
|
||||
<div id="container"></div>
|
||||
<script src="/bundle.js"></script>
|
||||
</body>
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<SearchBar getAlbums={this.getAlbums} />
|
||||
<AlbumList albums={this.state.albums} getTracks={this.getTracks} />
|
||||
<TrackList tracks={this.state.tracks} playPreview={this.playPreview} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<Greeting name="World" />,
|
||||
<App />,
|
||||
document.getElementById('container')
|
||||
);
|
@ -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$/,
|
||||
|
Loading…
Reference in New Issue
Block a user