Initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules/
|
||||||
|
/production/
|
||||||
|
*.orig
|
||||||
|
Thumbs.db
|
||||||
35
package.json
Executable file
35
package.json
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "test-reactor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "app.js",
|
||||||
|
"dependencies": {
|
||||||
|
"babel": "^5.8.29",
|
||||||
|
"babel-core": "6.14.0",
|
||||||
|
"babel-loader": "6.2.5",
|
||||||
|
"babel-preset-es2015": "*",
|
||||||
|
"babel-preset-react": "*",
|
||||||
|
"flux": "^2.1.1",
|
||||||
|
"redux": "*",
|
||||||
|
"react": "*",
|
||||||
|
"react-dom": "*",
|
||||||
|
"react-router": "*",
|
||||||
|
"redux": "*",
|
||||||
|
"react-redux": "*",
|
||||||
|
"redux-devtools": "*",
|
||||||
|
"webpack": "^1.13.2",
|
||||||
|
"webpack-strip": "*",
|
||||||
|
"node-sass": "*",
|
||||||
|
"extract-text-webpack-plugin": "*",
|
||||||
|
"jquery": "3.1.1",
|
||||||
|
"mopidy": "^0.5.0",
|
||||||
|
"expose-loader": "*",
|
||||||
|
"kioc": "1.0.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "cp src/index.html production/index.html & NODE_ENV=development webpack",
|
||||||
|
"prod": "NODE_ENV=production webpack"
|
||||||
|
},
|
||||||
|
"author": "James Barnsley",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
81
src/App.js
Executable file
81
src/App.js
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { createStore, bindActionCreators } from 'redux'
|
||||||
|
import { Link } from 'react-router'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
|
import * as actions from './actions'
|
||||||
|
import Services from './services/Services'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application 'brain'
|
||||||
|
*
|
||||||
|
* All data handling and fetching is done through this handler
|
||||||
|
**/
|
||||||
|
class App extends React.Component{
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(){
|
||||||
|
console.log('click');
|
||||||
|
//this.props.providerActions.authorizeSpotify();
|
||||||
|
Services.get('services.mopidy')
|
||||||
|
.then( function(MopidyService){
|
||||||
|
MopidyService.connection.playback.getState()
|
||||||
|
.then( function(state){
|
||||||
|
console.log('App.js > playback state', state);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
Services.get('services.mopidy')
|
||||||
|
.then( function(MopidyService){
|
||||||
|
MopidyService.connection.playback.getState()
|
||||||
|
.then( function(state){
|
||||||
|
console.log('App.js > playback state', state);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ul role="nav">
|
||||||
|
<li><Link to="/now-playing">Now playing</Link></li>
|
||||||
|
<li><Link to="/album/6N51k5TP5pSZYPf7bLffLe">808's and Heartbreak</Link></li>
|
||||||
|
<li><Link to="/album/1PgfRdl3lPyACfUGH4pquG">A million</Link></li>
|
||||||
|
<li><Link to="/library/albums">Library: Albums</Link></li>
|
||||||
|
<li><Link to="/album">Album</Link></li>
|
||||||
|
<li><a onClick={this.handleClick}>Authorize</a></li>
|
||||||
|
</ul>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(App)
|
||||||
8
src/about/index.js
Executable file
8
src/about/index.js
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default React.createClass({
|
||||||
|
render() {
|
||||||
|
return <div>About</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
32
src/actions.js
Executable file
32
src/actions.js
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Actions and Action Creators
|
||||||
|
**/
|
||||||
|
|
||||||
|
export const TOGGLE_DONE = 'TOGGLE_DONE'
|
||||||
|
export const ADD_TODO = 'ADD_TODO'
|
||||||
|
export const SET_ALBUM = 'SET_ALBUM'
|
||||||
|
|
||||||
|
export function toggleDone( id ){
|
||||||
|
return {
|
||||||
|
type: TOGGLE_DONE,
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addTodo( title ){
|
||||||
|
return {
|
||||||
|
type: ADD_TODO,
|
||||||
|
id: title,
|
||||||
|
title: title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAlbum( album ){
|
||||||
|
return {
|
||||||
|
type: SET_ALBUM,
|
||||||
|
album: album
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
84
src/common/Album.js
Executable file
84
src/common/Album.js
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
|
||||||
|
import Tracklist from '../common/Tracklist'
|
||||||
|
import * as actions from './actions'
|
||||||
|
|
||||||
|
class Album extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
album: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on render
|
||||||
|
componentDidMount(){
|
||||||
|
this.loadAlbum( this.props.params.id );
|
||||||
|
}
|
||||||
|
|
||||||
|
// when props changed
|
||||||
|
componentWillReceiveProps( nextProps ){
|
||||||
|
if( nextProps.params.id != this.props.params.id ){
|
||||||
|
this.loadAlbum( nextProps.params.id );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAlbum( id ){
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
cache: true,
|
||||||
|
url: 'https://api.spotify.com/v1/albums/'+id,
|
||||||
|
success: function(album){
|
||||||
|
self.setState({ album: album });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAlbum(){
|
||||||
|
if( this.state.album ){
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{ this.state.album.name }</h3>
|
||||||
|
<h3>{ this.state.album.label }</h3>
|
||||||
|
<Tracklist tracks={this.state.album.tracks.items} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Single album</h3>
|
||||||
|
{ this.renderAlbum() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Album)
|
||||||
42
src/common/Tracklist.js
Executable file
42
src/common/Tracklist.js
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
|
||||||
|
export default class Tracklist extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
flattenTracks(){
|
||||||
|
var tracks = [];
|
||||||
|
if( this.props.tracks ){
|
||||||
|
var originalTracks = this.props.tracks;
|
||||||
|
|
||||||
|
for( var i = 0; i < originalTracks.length; i++ ){
|
||||||
|
var track = originalTracks[i];
|
||||||
|
if( typeof(track.track) !== 'undefined' ){
|
||||||
|
track.track.tlid = track.tlid;
|
||||||
|
track = track.track;
|
||||||
|
}
|
||||||
|
tracks.push( track );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
if( this.flattenTracks ){
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
this.flattenTracks().map( track =>
|
||||||
|
<li key={track.uri}>{ track.name }</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/common/actions.js
Executable file
12
src/common/actions.js
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Actions and Action Creators
|
||||||
|
**/
|
||||||
|
|
||||||
|
export const LOAD_ALBUMS = 'LOAD_ALBUMS'
|
||||||
|
|
||||||
|
export function loadAlbums(){
|
||||||
|
return {
|
||||||
|
type: LOAD_ALBUMS
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/common/reducer.js
Executable file
16
src/common/reducer.js
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import * as actions from './actions'
|
||||||
|
|
||||||
|
export default function album(album = {}, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case actions.LOAD_ALBUM:
|
||||||
|
return album//Object.assign({}, album, action.album);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return album
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
37
src/config/Mopidy.js
Executable file
37
src/config/Mopidy.js
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import * as actions from './actions'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
|
||||||
|
class Mopidy extends React.Component{
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
let self = this;
|
||||||
|
console.log('Mopidy > constructor')
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Mopidy)
|
||||||
79
src/config/Spotify.js
Executable file
79
src/config/Spotify.js
Executable file
@ -0,0 +1,79 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import * as actions from './actions'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
|
||||||
|
class Spotify extends React.Component{
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
let self = this;
|
||||||
|
this.urlBase = 'https://api.spotify.com/v1/';
|
||||||
|
console.log('SpotifyProvider > constructor')
|
||||||
|
|
||||||
|
// listen for incoming messages from the authorization iframe
|
||||||
|
// this is triggered when authentication is granted from the popup
|
||||||
|
window.addEventListener('message', function(event){
|
||||||
|
if( !/^https?:\/\/jamesbarnsley\.co\.nz/.test(event.origin) ) return false;
|
||||||
|
self.handleMessage( event );
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage( event ){
|
||||||
|
|
||||||
|
// convert to json
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
// fire event
|
||||||
|
this.props.actions.authorizeSpotifySuccess( data );
|
||||||
|
}
|
||||||
|
|
||||||
|
test(){
|
||||||
|
console.log('test stuff');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAlbum( albumid ){
|
||||||
|
return $.when(
|
||||||
|
$.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
cache: true,
|
||||||
|
url: this.urlBase+'albums/'+albumid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
if( this.props.config.authorizingSpotify ){
|
||||||
|
var src = '//jamesbarnsley.co.nz/spotmop.php?action=authorize&app='+location.protocol+'//'+window.location.host;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Authorizing...
|
||||||
|
<iframe id="authorization-frame" src={src}></iframe>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Spotify)
|
||||||
21
src/config/actions.js
Executable file
21
src/config/actions.js
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Actions and Action Creators
|
||||||
|
**/
|
||||||
|
|
||||||
|
export const AUTHORIZE_SPOTIFY = 'AUTHORIZE_SPOTIFY'
|
||||||
|
export const AUTHORIZE_SPOTIFY_SUCCESS = 'AUTHORIZE_SPOTIFY_SUCCESS'
|
||||||
|
|
||||||
|
export function authorizeSpotify( authorize = true ){
|
||||||
|
return {
|
||||||
|
type: AUTHORIZE_SPOTIFY,
|
||||||
|
authorize: authorize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authorizeSpotifySuccess( authorization ){
|
||||||
|
return {
|
||||||
|
type: AUTHORIZE_SPOTIFY_SUCCESS,
|
||||||
|
authorization: authorization
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/config/reducer.js
Executable file
24
src/config/reducer.js
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
import * as actions from './actions'
|
||||||
|
|
||||||
|
export default function config(config = {}, action){
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case actions.AUTHORIZE_SPOTIFY:
|
||||||
|
return Object.assign({}, config, {
|
||||||
|
authorizingSpotify: action.authorize
|
||||||
|
});
|
||||||
|
|
||||||
|
case actions.AUTHORIZE_SPOTIFY_SUCCESS:
|
||||||
|
console.info('Spotify authorization successful');
|
||||||
|
var config = Object.assign({}, config, action.authorization)
|
||||||
|
config = Object.assign({}, config, { authorizingSpotify: false })
|
||||||
|
return config
|
||||||
|
|
||||||
|
default:
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
20
src/index.html
Executable file
20
src/index.html
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<title>Testing React and Redux</title>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
|
|
||||||
|
<meta base-href="/alto/production" />
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<!-- ReactJS application here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="app.js"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
54
src/index.js
Executable file
54
src/index.js
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Base-level application wrapper
|
||||||
|
**/
|
||||||
|
|
||||||
|
console.info('Application initiated');
|
||||||
|
|
||||||
|
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
import { createStore } from 'redux'
|
||||||
|
import { Router, Route, Link, hashHistory } from 'react-router'
|
||||||
|
import reducer from './reducer'
|
||||||
|
|
||||||
|
import App from './App'
|
||||||
|
import LibraryAlbums from './library/LibraryAlbums'
|
||||||
|
import Album from './common/Album'
|
||||||
|
import NowPlaying from './routes/NowPlaying'
|
||||||
|
|
||||||
|
import Services from './services/Services'
|
||||||
|
|
||||||
|
Promise.all(Services.setup())
|
||||||
|
.then(() => {
|
||||||
|
console.info('Services started...');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render our application into the real DOM
|
||||||
|
*
|
||||||
|
* Sets up all of our routes, and the relevant components for each.
|
||||||
|
* Provider facilitates global access to the store by using connect()
|
||||||
|
|
||||||
|
<Route path="playlists" component={LibraryPlaylists} />
|
||||||
|
<Route path="albums" component={LibraryAlbums} />
|
||||||
|
<Route path="artists" component={LibraryArtists} />
|
||||||
|
<Route path="tracks" component={LibraryTracks} />
|
||||||
|
**/
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={ createStore( reducer, { todos: [] } ) }>
|
||||||
|
<Router history={hashHistory}>
|
||||||
|
<Route path="/" component={App}>
|
||||||
|
|
||||||
|
<Route path="album/:id" component={Album} />
|
||||||
|
<Route path="library/albums" component={LibraryAlbums} />
|
||||||
|
|
||||||
|
<Route path="now-playing" component={NowPlaying} />
|
||||||
|
|
||||||
|
</Route>
|
||||||
|
</Router>
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('app')
|
||||||
|
);
|
||||||
68
src/library/LibraryAlbums.js
Executable file
68
src/library/LibraryAlbums.js
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import * as actions from './actions'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
|
||||||
|
class LibraryAlbums extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
albums: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// on load of this component
|
||||||
|
componentDidMount(){
|
||||||
|
|
||||||
|
var url = 'http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=4320a3ef51c9b3d69de552ac083c55e3&artist=Cher&album=Believe&format=json';
|
||||||
|
//var url = 'https://pixabay.com/api/?key=3190651-5f431f6c829ace6d75ff07701&q=yellow+flowers&image_type=photo&pretty=true';
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// url (required), options (optional)
|
||||||
|
fetch(url, {
|
||||||
|
method: 'get'
|
||||||
|
}).then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
}).then(function( json ) {
|
||||||
|
self.setState({ albums: json.hits });
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.error( err );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Library album</h3>
|
||||||
|
{
|
||||||
|
this.state.albums.map(album =>
|
||||||
|
<h4 key={album.webformatURL}>{ album.webformatURL }</h4>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(LibraryAlbums)
|
||||||
12
src/library/actions.js
Executable file
12
src/library/actions.js
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Actions and Action Creators
|
||||||
|
**/
|
||||||
|
|
||||||
|
export const LOAD_ALBUMS = 'LOAD_ALBUMS'
|
||||||
|
|
||||||
|
export function loadAlbums(){
|
||||||
|
return {
|
||||||
|
type: LOAD_ALBUMS
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/library/reducer.js
Executable file
16
src/library/reducer.js
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import * as actions from './actions'
|
||||||
|
|
||||||
|
export default function album(album = {}, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case actions.SET_ALBUM:
|
||||||
|
return Object.assign({}, album, action.album);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return album
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
21
src/reducer.js
Executable file
21
src/reducer.js
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Root reducer
|
||||||
|
*
|
||||||
|
* This combines all our application reducers into a single, top-level reducer.
|
||||||
|
* We need to combine reducers to create the unified application-level store.
|
||||||
|
**/
|
||||||
|
|
||||||
|
// import all of our application reducers
|
||||||
|
import todos from './todos/reducer'
|
||||||
|
import config from './config/reducer'
|
||||||
|
|
||||||
|
import { combineReducers } from 'redux'
|
||||||
|
|
||||||
|
// combine them into one root reducer
|
||||||
|
export default combineReducers({
|
||||||
|
todos,
|
||||||
|
config
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
67
src/routes/NowPlaying.js
Executable file
67
src/routes/NowPlaying.js
Executable file
@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
|
||||||
|
import Services from '../services/Services'
|
||||||
|
import Tracklist from '../common/Tracklist'
|
||||||
|
import * as actions from '../actions'
|
||||||
|
|
||||||
|
class NowPlaying extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
tracks: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on render
|
||||||
|
componentDidMount(){
|
||||||
|
var self = this;
|
||||||
|
Services.get('services.mopidy')
|
||||||
|
.then( function(MopidyService){
|
||||||
|
MopidyService.connection.tracklist.getTlTracks()
|
||||||
|
.then( function(tracks){
|
||||||
|
self.setState({ tracks : tracks });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTracks(){
|
||||||
|
if( this.state.tracks ){
|
||||||
|
return (
|
||||||
|
<Tracklist tracks={this.state.tracks} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Now playing</h3>
|
||||||
|
{ this.renderTracks() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(NowPlaying)
|
||||||
35
src/services/MopidyService.js
Executable file
35
src/services/MopidyService.js
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
import Mopidy from 'mopidy'
|
||||||
|
|
||||||
|
function MopidyService( connection ){
|
||||||
|
this.connection = connection;
|
||||||
|
};
|
||||||
|
|
||||||
|
MopidyService.attachKey = 'services.mopidy';
|
||||||
|
|
||||||
|
MopidyService.attach = function( app ){
|
||||||
|
console.info('MopidyService: Attaching...');
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
var mopidyhost = 'music.plasticstudio.co';//window.location.hostname;
|
||||||
|
var mopidyport = "6680";
|
||||||
|
var protocol = 'ws';
|
||||||
|
var connection = new Mopidy({
|
||||||
|
webSocketUrl: protocol+"://" + mopidyhost + ":" + mopidyport + "/mopidy/ws",
|
||||||
|
callingConvention: 'by-position-or-by-name'
|
||||||
|
});
|
||||||
|
connection.on((a, msg) => {
|
||||||
|
//console.log(msg);
|
||||||
|
// msg && msg.method ?
|
||||||
|
// console.log('<-', msg.method) :
|
||||||
|
// (msg ? console.log('->', JSON.parse(msg.data)) : console.log('->', msg));
|
||||||
|
});
|
||||||
|
connection.on('state:online', () => {
|
||||||
|
console.info('MopidyService: Attached');
|
||||||
|
var service = new MopidyService( connection );
|
||||||
|
resolve( service );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MopidyService;
|
||||||
11
src/services/Services.js
Executable file
11
src/services/Services.js
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
import ServicesContainer from 'kioc';
|
||||||
|
|
||||||
|
import MopidyService from './MopidyService';
|
||||||
|
import SpotifyService from './SpotifyService';
|
||||||
|
|
||||||
|
var Services = new ServicesContainer();
|
||||||
|
Services.use(MopidyService, true);
|
||||||
|
//Services.use(SpotifyService, true);
|
||||||
|
|
||||||
|
export default Services;
|
||||||
79
src/services/SpotifyService.js
Executable file
79
src/services/SpotifyService.js
Executable file
@ -0,0 +1,79 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import * as actions from './actions'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
|
||||||
|
class SpotifyService extends React.Component{
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
let self = this;
|
||||||
|
this.urlBase = 'https://api.spotify.com/v1/';
|
||||||
|
console.log('SpotifyProvider > constructor')
|
||||||
|
|
||||||
|
// listen for incoming messages from the authorization iframe
|
||||||
|
// this is triggered when authentication is granted from the popup
|
||||||
|
window.addEventListener('message', function(event){
|
||||||
|
if( !/^https?:\/\/jamesbarnsley\.co\.nz/.test(event.origin) ) return false;
|
||||||
|
self.handleMessage( event );
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage( event ){
|
||||||
|
|
||||||
|
// convert to json
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
// fire event
|
||||||
|
this.props.actions.authorizeSpotifySuccess( data );
|
||||||
|
}
|
||||||
|
|
||||||
|
test(){
|
||||||
|
console.log('test stuff');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAlbum( albumid ){
|
||||||
|
return $.when(
|
||||||
|
$.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
cache: true,
|
||||||
|
url: this.urlBase+'albums/'+albumid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
if( this.props.config.authorizingSpotify ){
|
||||||
|
var src = '//jamesbarnsley.co.nz/spotmop.php?action=authorize&app='+location.protocol+'//'+window.location.host;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Authorizing...
|
||||||
|
<iframe id="authorization-frame" src={src}></iframe>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SpotifyService)
|
||||||
29
src/services/actions.js
Executable file
29
src/services/actions.js
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Actions and Action Creators
|
||||||
|
**/
|
||||||
|
|
||||||
|
export const AUTHORIZE_SPOTIFY = 'AUTHORIZE_SPOTIFY'
|
||||||
|
export const AUTHORIZE_SPOTIFY_SUCCESS = 'AUTHORIZE_SPOTIFY_SUCCESS'
|
||||||
|
export const MOPIDY_ONLINE = 'MOPIDY_ONLINE'
|
||||||
|
|
||||||
|
export function authorizeSpotify( authorize = true ){
|
||||||
|
return {
|
||||||
|
type: AUTHORIZE_SPOTIFY,
|
||||||
|
authorize: authorize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authorizeSpotifySuccess( authorization ){
|
||||||
|
return {
|
||||||
|
type: AUTHORIZE_SPOTIFY_SUCCESS,
|
||||||
|
authorization: authorization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mopidyOnline( online ){
|
||||||
|
return {
|
||||||
|
type: MOPIDY_ONLINE,
|
||||||
|
online: online
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/services/reducer.js
Executable file
29
src/services/reducer.js
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
import * as actions from './actions'
|
||||||
|
|
||||||
|
export default function reducer(state = {}, action){
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case actions.AUTHORIZE_SPOTIFY:
|
||||||
|
return Object.assign({}, state.spotify, {
|
||||||
|
authorizing: action.authorize
|
||||||
|
});
|
||||||
|
|
||||||
|
case actions.AUTHORIZE_SPOTIFY_SUCCESS:
|
||||||
|
console.info('Spotify authorization successful');
|
||||||
|
var state = Object.assign({}, state.spotify, action.authorization)
|
||||||
|
state = Object.assign({}, state.spotify, { online: true, authorizingSpotify: false })
|
||||||
|
return state
|
||||||
|
|
||||||
|
case actions.MOPIDY_ONLINE:
|
||||||
|
return Object.assign({}, state.mopidy, {
|
||||||
|
online: action.online
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
158
src/todos/index.js
Executable file
158
src/todos/index.js
Executable file
@ -0,0 +1,158 @@
|
|||||||
|
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import * as actions from '../actions'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
|
||||||
|
class Todos extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
// on load of this component
|
||||||
|
componentDidMount(){
|
||||||
|
this.props.actions.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<Todos_View todos={this.props.todos} actions={this.props.actions} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Todo's presenter, or view
|
||||||
|
*
|
||||||
|
* Any UI and UX display occurs here, and is passed back to it's brain through props
|
||||||
|
* This allows for reuse of the brain for different contexts without duplicating functionality
|
||||||
|
**/
|
||||||
|
class Todos_View extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ul className="todos">
|
||||||
|
{
|
||||||
|
this.props.todos.map(todo =>
|
||||||
|
<TodoItem todo={todo} key={todo.id} actions={this.props.actions} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<AddTodoForm actions={this.props.actions} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Todo item
|
||||||
|
**/
|
||||||
|
class TodoItem extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(){
|
||||||
|
this.props.actions.toggleDone(this.props.todo.id);
|
||||||
|
this.props.actions.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
var style = {};
|
||||||
|
if( this.props.todo.completed ) style.textDecoration = 'line-through';
|
||||||
|
return <li onClick={this.handleClick} style={style}>{ this.props.todo.title }</li>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Todo item
|
||||||
|
**/
|
||||||
|
class AddTodoForm extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(){
|
||||||
|
this.props.actions.addTodo( this.state.title );
|
||||||
|
this.props.actions.save();
|
||||||
|
this.setState({ title: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange( newTitle ){
|
||||||
|
this.setState({ title: newTitle });
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return <AddTodoForm_View title={this.state.title} handleChange={this.handleChange} handleSubmit={this.handleSubmit} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddTodoForm_View extends React.Component{
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange( event ){
|
||||||
|
this.props.handleChange( event.target.value );
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit( event ){
|
||||||
|
event.preventDefault();
|
||||||
|
this.props.handleSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<form onSubmit={ this.handleSubmit }>
|
||||||
|
<input type="text" value={ this.props.title } onChange={this.handleChange} />
|
||||||
|
<input type="submit" value="Add" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Todos.propTypes = {
|
||||||
|
todos: React.PropTypes.array.isRequired
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export our component
|
||||||
|
*
|
||||||
|
* We also integrate our global store, using connect()
|
||||||
|
**/
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Todos)
|
||||||
38
src/todos/reducer.js
Executable file
38
src/todos/reducer.js
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
import * as actions from '../actions'
|
||||||
|
|
||||||
|
export default function todos(todos = [], action){
|
||||||
|
switch( action.type ){
|
||||||
|
|
||||||
|
case actions.LOAD:
|
||||||
|
if( localStorage.getItem( 'todos' ) ){
|
||||||
|
return JSON.parse( localStorage.getItem( 'todos' ) );
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
|
||||||
|
case actions.SAVE:
|
||||||
|
localStorage.setItem( 'todos', JSON.stringify(todos) );
|
||||||
|
return todos;
|
||||||
|
|
||||||
|
case actions.ADD_TODO:
|
||||||
|
return Object.assign([], todos, [...todos, { id: action.id, title: action.title, completed: false }] )
|
||||||
|
|
||||||
|
case actions.TOGGLE_DONE:
|
||||||
|
return Object.assign([], todos,
|
||||||
|
todos.map(todo => {
|
||||||
|
if( todo.id === action.id ){
|
||||||
|
return Object.assign({}, todo, {
|
||||||
|
completed: !todo.completed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return todo
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return todos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
107
webpack.config.js
Executable file
107
webpack.config.js
Executable file
@ -0,0 +1,107 @@
|
|||||||
|
var dev = process.env.NODE_ENV !== "production";
|
||||||
|
var webpack = require('webpack');
|
||||||
|
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
|
||||||
|
var node_dir = __dirname + '/node_modules';
|
||||||
|
var output_dir = __dirname +"/production"
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
|
||||||
|
context: __dirname,
|
||||||
|
entry: "./src/index.js",
|
||||||
|
|
||||||
|
output: {
|
||||||
|
path: output_dir,
|
||||||
|
filename: 'app.js'
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{ test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$' },
|
||||||
|
{
|
||||||
|
// loading JSX (aka Babel) into browser-friendly ES6
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: [
|
||||||
|
/(node_modules|bower_components)/,
|
||||||
|
'index.js',
|
||||||
|
],
|
||||||
|
loader: 'babel-loader',
|
||||||
|
query: {
|
||||||
|
presets: ['es2015', 'react']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// loading sass asset files
|
||||||
|
test: /\.scss$/,
|
||||||
|
loader: ExtractTextPlugin.extract([
|
||||||
|
'css'+(dev? '?sourceMap=true': ''),
|
||||||
|
'sass'+(dev? '?outputStyle=expanded&sourceMap=true&sourceMapContents=true': '')
|
||||||
|
])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.DedupePlugin(),
|
||||||
|
new webpack.optimize.OccurenceOrderPlugin(),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
$: "jquery",
|
||||||
|
jQuery: "jquery",
|
||||||
|
"window.jQuery": "jquery"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Development-only configuration values
|
||||||
|
**/
|
||||||
|
if( dev ){
|
||||||
|
|
||||||
|
// set compiled css location
|
||||||
|
config.plugins.push( new ExtractTextPlugin("app.css") );
|
||||||
|
|
||||||
|
// we want source maps
|
||||||
|
config.devtool = 'source-map';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Production-only configuration values
|
||||||
|
**/
|
||||||
|
}else{
|
||||||
|
|
||||||
|
// set our final output filename
|
||||||
|
config.output.filename = 'app.min.js';
|
||||||
|
|
||||||
|
// re-iterate our production value as a string (for ReactJS building)
|
||||||
|
config.plugins.push(
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env':{
|
||||||
|
'NODE_ENV': JSON.stringify('production')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove all debug and console code
|
||||||
|
config.module.loaders.push(
|
||||||
|
{
|
||||||
|
test: /\.(js|jsx)$/,
|
||||||
|
loader: "webpack-strip?strip[]=console.log,strip[]=console.info,strip[]=debug"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// set compiled css location
|
||||||
|
config.plugins.push( new ExtractTextPlugin("app.min.css") );
|
||||||
|
|
||||||
|
// uglify our js, with no sourcemaps
|
||||||
|
config.plugins.push(
|
||||||
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
|
compress: true,
|
||||||
|
mangle: false,
|
||||||
|
sourceMap: false,
|
||||||
|
comments: false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now export our collated config object
|
||||||
|
module.exports = config;
|
||||||
Reference in New Issue
Block a user