diff --git a/README.md b/README.md
index e648c9f..2c4cc57 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,9 @@ $ npm i && make browser
$ cd ../../..
$ webpack -w
-$ DEBUG=1 electron app/
+$ electron app/
+$ DEBUG=1 electron app/ # with Developer toolbar
+$ DEBUG=1 REDUX=1 electron app/ # with Redux Developer toolbar
```
diff --git a/app/package.json b/app/package.json
index dda4f2e..3191292 100644
--- a/app/package.json
+++ b/app/package.json
@@ -21,5 +21,10 @@
"react-router": "^2.0.1",
"redux": "^3.3.1",
"sqlite3": "^3.1.3"
+ },
+ "devDependencies": {
+ "redux-devtools": "^3.2.0",
+ "redux-devtools-dock-monitor": "^1.1.1",
+ "redux-devtools-log-monitor": "^1.0.9"
}
}
diff --git a/package.json b/package.json
index 95aa201..9cc4597 100644
--- a/package.json
+++ b/package.json
@@ -50,16 +50,13 @@
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"css-loader": "^0.23.1",
+ "electron-builder": "^3.5.0",
+ "electron-prebuilt": "^0.37.5",
"node-sass": "^3.4.2",
- "redux-devtools": "^3.1.1",
- "redux-devtools-dock-monitor": "^1.1.0",
- "redux-devtools-log-monitor": "^1.0.5",
"sass-loader": "^3.2.0",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
- "webpack": "^1.12.14",
- "electron-prebuilt": "^0.37.5",
- "electron-builder": "^3.3.1"
+ "webpack": "^1.12.14"
},
"scripts": {
"postinstall": "./node_modules/.bin/install-app-deps",
diff --git a/src/app.js b/src/app.js
index 530ed1b..2ca10f8 100644
--- a/src/app.js
+++ b/src/app.js
@@ -12,12 +12,17 @@ import {Provider} from 'react-redux'
import MainLayout from './components/layout/Main'
import MainPage from './components/page/Main'
-// import DevTools from './assets/js/DevTools'
+import DevTools from './assets/js/DevTools'
import Player from './context/Player';
-// window._store = createStore(reducer, DevTools.instrument());
-window._store = createStore(reducer);
+console.log(process.env.REDUX);
+
+if (process.env.REDUX) {
+ window._store = createStore(reducer, DevTools.instrument());
+} else {
+ window._store = createStore(reducer);
+}
Player.getInstance();
diff --git a/src/assets/js/DevTools.js b/src/assets/js/DevTools.js
index 4c20d69..7571939 100644
--- a/src/assets/js/DevTools.js
+++ b/src/assets/js/DevTools.js
@@ -1,4 +1,5 @@
import React from 'react';
+import Immutable from 'immutable'
// Exported from redux-devtools
import { createDevTools } from 'redux-devtools';
@@ -7,6 +8,8 @@ import { createDevTools } from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';
+let selectDevToolsState = (state = {}) => Immutable.fromJS(state).toJS();
+
// createDevTools takes a monitor and produces a DevTools component
const DevTools = createDevTools(
// Monitors are individually adjustable with props.
@@ -14,7 +17,7 @@ const DevTools = createDevTools(
// Here, we put LogMonitor inside a DockMonitor.
-
+
);
diff --git a/src/components/layout/Main/index.js b/src/components/layout/Main/index.js
index 891a02b..61d8f04 100644
--- a/src/components/layout/Main/index.js
+++ b/src/components/layout/Main/index.js
@@ -1,7 +1,7 @@
import './styles/style.scss'
import React from 'react'
import Controls from '../../widget/Controls'
-// import DevTools from '../../../assets/js/DevTools'
+import DevTools from '../../../assets/js/DevTools'
class Layout extends React.Component {
constructor() {
@@ -22,12 +22,11 @@ class Layout extends React.Component {
+ {process.env.REDUX ? : false}
)
}
}
-//
-
export default Layout;
diff --git a/src/components/widget/AlbumList/index.js b/src/components/widget/AlbumList/index.js
index be287c5..6de7876 100644
--- a/src/components/widget/AlbumList/index.js
+++ b/src/components/widget/AlbumList/index.js
@@ -1,6 +1,7 @@
//import './styles/style.scss'
import React from 'react'
import {connect} from 'react-redux'
+import I from 'immutable'
import database from '../../../context/db'
import utils from '../../../assets/js/Utils'
@@ -8,13 +9,6 @@ import utils from '../../../assets/js/Utils'
class AlbumList extends React.Component {
constructor() {
super();
-
- this.state = {
- albums: []
- };
-
- // cache
- this.albumsCovers = {};
}
/**
@@ -22,7 +16,7 @@ class AlbumList extends React.Component {
*
* @returns {Promise}
*/
- getAlbumList(props) {
+ getAlbumList(selectedArtist) {
const sql = `SELECT
playlist.album,
COUNT(playlist.title) AS tracks,
@@ -37,12 +31,9 @@ class AlbumList extends React.Component {
GROUP BY playlist.album`;
return new Promise((resolve, reject) => {
- const selectedArtist = props.store.selected.artist;
-
database.open((db) => {
db.all(sql, {$artist: selectedArtist}, function(error, results) {
if (results) {
- console.debug(results);
resolve(results);
} else {
console.error(error);
@@ -63,17 +54,17 @@ class AlbumList extends React.Component {
}
render() {
- let albumList = this.state.albums.map((value, index) => {
- const classList = 'list-group-item ' + (this.props.store.selected.album === value.album ? 'active' : '');
- const coverArt = this.getCoverAsURL(value.id, value.coverArt);
+ let albumList = this.props.library.get('albums').map((value, index) => {
+ const classList = 'list-group-item ' + (this.props.selected.get('album') === value.get('album') ? 'active' : '');
+ const coverArt = utils.getURLfromBlob(value.get('coverArt'));
return (
-
+
-
{value.album}
-
{value.tracks} songs
+
{value.get('album')}
+
{value.get('tracks')} songs
);
@@ -86,29 +77,29 @@ class AlbumList extends React.Component {
)
}
- getCoverAsURL(id, coverData) {
- return this.albumsCovers[id] || (this.albumsCovers[id] = utils.getURLfromBlob(coverData));
- }
-
componentWillReceiveProps(nextProps) {
- if (this.props.store.selected.artist !== nextProps.store.selected.artist) {
- this.getAlbumList(nextProps).then((data) => {
- this.setState({
- albums: data
+ const selectedArtist = nextProps.selected.get('artist');
+
+ if (this.props.selected.get('artist') !== selectedArtist) {
+ this.getAlbumList(selectedArtist).then((data) => {
+ this.props.dispatch({
+ type: 'SET_LIBRARY_ALBUMS',
+ value: data
});
});
}
}
shouldComponentUpdate(nextProps) {
- return this.props.store.selected.artist ||
- (this.props.store.selected.artist !== nextProps.store.selected.artist);
+ return !I.is(this.props.library.get('albums'), nextProps.library.get('albums')) ||
+ !I.is(this.props.selected.get('album'), nextProps.selected.get('album'));
}
}
const mapStatesToProps = (store) => {
return {
- store: store
+ selected: store.get('selected'),
+ library: store.get('library')
}
};
diff --git a/src/components/widget/ArtistList/AlbumCover.js b/src/components/widget/ArtistList/AlbumCover.js
index c0fdcf8..00e29d3 100644
--- a/src/components/widget/ArtistList/AlbumCover.js
+++ b/src/components/widget/ArtistList/AlbumCover.js
@@ -4,47 +4,33 @@ import utils from '../../../assets/js/Utils'
class AlbumCover extends React.Component {
constructor() {
super();
-
- this.albumsCovers = {};
- this.state = {
- covers: null
- };
}
render() {
- const covers = this.state.covers && this.state.covers.length ? this.state.covers : [null];
+ let covers = [];
+
+ for (let i = 1; i < 5; ++i) {
+ let cover = this.props.data.get(`coverArt${i}`);
+
+ if (cover) {
+ covers.push(cover);
+ }
+ }
+
+ covers = covers && covers.length ? covers : [null];
return (
{covers.map((value, index) => {
- const id = value ? `${this.props.id}${index}` : 0;
- const cover = this.getCoverAsURL(id, value);
+ const cover = utils.getURLfromBlob(value);
return
})}
)
}
- getCoverAsURL(id, coverData) {
- return this.albumsCovers[id] || (this.albumsCovers[id] = utils.getURLfromBlob(coverData));
- }
-
- componentWillReceiveProps() {
- }
-
- componentDidMount() {
- let covers = [];
- for (let i = 1; i < 5; ++i) {
- let cover = this.props[`coverArt${i}`];
-
- if (cover) {
- covers.push(cover);
- }
- }
-
- this.setState({
- covers: covers
- });
+ shouldComponentUpdate(nextProps) {
+ return !nextProps.data.equals(this.props.data);
}
}
diff --git a/src/components/widget/ArtistList/index.js b/src/components/widget/ArtistList/index.js
index 1e5f5de..641717a 100644
--- a/src/components/widget/ArtistList/index.js
+++ b/src/components/widget/ArtistList/index.js
@@ -2,6 +2,7 @@ import './styles/style.scss'
import React from 'react'
import {connect} from 'react-redux'
+import I from 'immutable'
import AlbumCover from './AlbumCover'
@@ -10,10 +11,6 @@ import database from '../../../context/db'
class ArtistList extends React.Component {
constructor() {
super();
-
- this.state = {
- artists: []
- };
}
/**
@@ -37,7 +34,6 @@ class ArtistList extends React.Component {
database.open((db) => {
db.all(sql, (error, results) => {
if (results) {
- console.debug(results);
resolve(results);
} else {
console.error(error);
@@ -58,16 +54,16 @@ class ArtistList extends React.Component {
}
render() {
- let artistsList = this.state.artists.map((value, index) => {
- let classList = 'list-group-item ' + (this.props.store.selected.artist === value.artist ? 'active' : '');
+ let artistsList = this.props.library.get('artists').map((value, index) => {
+ let classList = 'list-group-item ' + (this.props.selected.get('artist') === value.get('artist') ? 'active' : '');
return (
-
-
+
+
-
{value.artist}
-
{value.albums} albums
+
{value.get('artist')}
+
{value.get('albums')} albums
);
@@ -80,11 +76,14 @@ class ArtistList extends React.Component {
)
}
- getListOfArtists() {
+ getListOfArtists(callback) {
this.getPlayList().then((data) => {
- this.setState({
- artists: data
+ this.props.dispatch({
+ type: 'SET_LIBRARY_ARTISTS',
+ value: data
});
+
+ callback && callback();
});
}
@@ -93,20 +92,26 @@ class ArtistList extends React.Component {
}
componentWillReceiveProps(nextProps) {
- if (nextProps.store.libraryUpdated) {
- this.getListOfArtists();
+ if (nextProps.libraryUpdated) {
+ this.getListOfArtists(() => {
+ this.props.dispatch({
+ type: 'LIBRARY_DID_UPDATE'
+ });
+ });
}
}
shouldComponentUpdate(nextProps) {
- return (!this.props.store.selected.artist ||
- (this.props.store.selected.artist !== nextProps.store.selected.artist));
+ return !I.is(this.props.library.get('artists'), nextProps.library.get('artists')) ||
+ !I.is(this.props.selected.get('artist'), nextProps.selected.get('artist'));
}
}
const mapStatesToProps = (store) => {
return {
- store: store
+ selected: store.get('selected'),
+ library: store.get('library'),
+ libraryUpdated: store.get('libraryUpdated')
}
};
diff --git a/src/components/widget/Controls/index.js b/src/components/widget/Controls/index.js
index 36209ea..4622375 100644
--- a/src/components/widget/Controls/index.js
+++ b/src/components/widget/Controls/index.js
@@ -81,7 +81,7 @@ class Controls extends React.Component {
const mapStatesToProps = (store) => {
return {
- isPlaying: store.isPlaying
+ isPlaying: store.get('isPlaying')
}
};
diff --git a/src/components/widget/LibraryProcess/index.js b/src/components/widget/LibraryProcess/index.js
index c7b5808..a0a761f 100644
--- a/src/components/widget/LibraryProcess/index.js
+++ b/src/components/widget/LibraryProcess/index.js
@@ -71,7 +71,6 @@ class LibraryProcess extends React.Component {
fse.walk(files)
.on('data', (item) => {
- console.log(item);
if (!item.stats.isDirectory() && item.path.match(this.extensions)) {
items.push(item.path);
}
@@ -103,8 +102,6 @@ class LibraryProcess extends React.Component {
};
let onMetadata = (metadata) => {
- console.log(metadata);
-
const covertArt = metadata.coverArt ? metadata.coverArt.data : undefined;
trackInfoCallback({
@@ -116,15 +113,13 @@ class LibraryProcess extends React.Component {
diskNumber: metadata.diskNumber,
trackNumber: metadata.trackNumber,
coverArt: covertArt
- });
+ }, callback);
this.releaseResource(asset, {
onError,
onMetadata,
onData
});
-
- callback();
};
/**
@@ -139,8 +134,6 @@ class LibraryProcess extends React.Component {
}, 300);
};
- console.log(file, asset);
-
asset.on('error', onError);
asset.get('metadata', onMetadata);
asset.once('data', onData);
@@ -164,7 +157,7 @@ class LibraryProcess extends React.Component {
asset = null;
}
- writeToDB(fileInfo) {
+ writeToDB(fileInfo, callback) {
this.db.run('INSERT INTO `playlist` VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[null, fileInfo.artist, fileInfo.albumArtist, fileInfo.album, fileInfo.title, fileInfo.file, fileInfo.diskNumber,
fileInfo.trackNumber], (error) => {
@@ -178,8 +171,9 @@ class LibraryProcess extends React.Component {
if (error) {
console.error(error);
}
- });
+ callback();
+ });
});
}
diff --git a/src/components/widget/SongList/SongListComponent.js b/src/components/widget/SongList/SongListComponent.js
index bbf3381..5d65b42 100644
--- a/src/components/widget/SongList/SongListComponent.js
+++ b/src/components/widget/SongList/SongListComponent.js
@@ -32,16 +32,15 @@ class SongListComponent extends React.Component {
setTimeout(() => {
this.player.play(file);
+ }, 300);
- this.props.dispatch({
- type: 'PLAY',
- value: {
- title: title,
- file: file
- }
- });
- }, 500);
-
+ this.props.dispatch({
+ type: 'PLAY',
+ value: {
+ title: title,
+ file: file
+ }
+ });
}
render() {
@@ -55,7 +54,7 @@ class SongListComponent extends React.Component {
diff --git a/src/components/widget/SongList/TrackItem.js b/src/components/widget/SongList/TrackItem.js
index 449b827..a94af8f 100644
--- a/src/components/widget/SongList/TrackItem.js
+++ b/src/components/widget/SongList/TrackItem.js
@@ -4,11 +4,11 @@ const TrackItem = (props) => {
let classList = () => {
var output = [];
- if (props.selectedFile === props.trackInfo.file) {
+ if (props.selectedFile === props.trackInfo.get('file')) {
output.push('selected');
}
- if (props.playingFile === props.trackInfo.file) {
+ if (props.playingFile === props.trackInfo.get('file')) {
output.push('active');
}
@@ -17,8 +17,8 @@ const TrackItem = (props) => {
return (
- {props.trackInfo.trackNumber} |
- {props.trackInfo.title} |
+ {props.trackInfo.get('trackNumber')} |
+ {props.trackInfo.get('title')} |
);
};
diff --git a/src/components/widget/SongList/TrackList.js b/src/components/widget/SongList/TrackList.js
index 6d11f64..e2cb85e 100644
--- a/src/components/widget/SongList/TrackList.js
+++ b/src/components/widget/SongList/TrackList.js
@@ -6,14 +6,14 @@ import TrackItem from './TrackItem'
const TrackList = (props) => {
return (
- {props.trackList.map((value, index) => {
+ {props.data.trackList.map((value, index) => {
return ();
})}
diff --git a/src/components/widget/SongList/index.js b/src/components/widget/SongList/index.js
index f3970ee..88c563b 100644
--- a/src/components/widget/SongList/index.js
+++ b/src/components/widget/SongList/index.js
@@ -1,5 +1,6 @@
import React from 'react'
import {connect} from 'react-redux'
+import I from 'immutable'
import database from '../../../context/db'
@@ -9,19 +10,14 @@ import SongListComponent from './songListComponent'
class SongList extends React.Component {
constructor() {
super();
-
- this.state = {
- tracks: []
- };
}
getPlayList(props) {
return new Promise((resolve, reject) => {
database.open((db) => {
db.all('SELECT * FROM playlist WHERE albumArtist = ? and album = ?',
- [props.selected.artist, props.selected.album], (error, results) => {
+ [props.selected.get('artist'), props.selected.get('album')], (error, results) => {
if (results) {
- console.debug(results);
resolve(results);
} else {
console.error(error);
@@ -36,29 +32,38 @@ class SongList extends React.Component {
render() {
return ()
}
componentWillReceiveProps(nextProps) {
- if (this.props.selected.artist !== nextProps.selected.artist ||
- this.props.selected.album !== nextProps.selected.album) {
+ if ((this.props.selected.get('album') && this.props.selected.get('artist')) &&
+ this.props.selected.get('artist') !== nextProps.selected.get('artist') ||
+ this.props.selected.get('album') !== nextProps.selected.get('album')) {
this.getPlayList(nextProps).then((data) => {
- this.setState({
- tracks: data
+ this.props.dispatch({
+ type: 'SET_LIBRARY_TRACKS',
+ value: data
});
});
}
}
+ shouldComponentUpdate(nextState) {
+ return !I.is(nextState.library.get('tracks'), this.props.library.get('tracks')) ||
+ nextState.selected.get('file') !== this.props.selected.get('file') ||
+ nextState.playing.get('file') !== this.props.playing.get('file');
+ }
+
}
const mapStatesToProps = (store) => {
return {
- selected: store.selected,
- playing: store.playing
+ selected: store.get('selected'),
+ playing: store.get('playing'),
+ library: store.get('library')
}
};
diff --git a/src/context/Player.js b/src/context/Player.js
index 6f6b186..3e0cc09 100644
--- a/src/context/Player.js
+++ b/src/context/Player.js
@@ -36,7 +36,7 @@ class Player {
if (!this.state.isPlaying) {
if (!this.player || this.player.isStop) {
- let track = file || store.playing.track || store.selected.track;
+ let track = file || store.getIn(['playing', 'file']) || store.getIn(['selected', 'file']);
if (!track) {
throw new Error('Select a file first!');
@@ -68,7 +68,7 @@ class Player {
toggle() {
let store = this.store.getState();
- if (store.isPlaying) {
+ if (store.get('isPlaying')) {
this.pause();
} else {
this.play();
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 46bb45d..e8f5b80 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -1,4 +1,6 @@
-const initObject = {
+import Immutable from 'immutable';
+
+const initObject = Immutable.fromJS({
isPlaying: false,
libraryUpdated: false,
playing: {
@@ -12,68 +14,60 @@ const initObject = {
album: null,
title: null,
file: null
+ },
+ library: {
+ artists: [],
+ albums: [],
+ tracks: []
}
-};
+});
function mainReducer(state = initObject, action) {
switch (action.type) {
case 'PLAY':
- return Object.assign({}, state, {
- isPlaying: true,
- playing: Object.assign({}, state.playing, {
- file: action.value.file,
- title: action.value.title
- }),
- selected: Object.assign({}, state.selected, {
- file: action.value.file,
- title: action.value.title
- })
- });
+ return state.set('isPlaying', true).
+ setIn(['playing', 'file'], action.value.file).
+ setIn(['playing', 'title'], action.value.title).
+ setIn(['selected', 'file'], action.value.file).
+ setIn(['selected', 'title'], action.value.title);
case 'STOP':
- return Object.assign({}, state, {
- isPlaying: false
- });
+ return state.set('isPlaying', false);
case 'TOGGLE_PLAY':
- return Object.assign({}, state, {
- isPlaying: !state.isPlaying
- });
+ return state.set('isPlaying', !state.get('isPlaying'));
case 'SET_SELECTED_ARTIST':
- return Object.assign({}, state, {
- selected: Object.assign({}, state.selected, {
- artist: action.value
- })
- });
+ return state.setIn(['selected', 'artist'], action.value);
case 'SET_SELECTED_ALBUM':
- return Object.assign({}, state, {
- selected: Object.assign({}, state.selected, {
- album: action.value
- })
- });
+ return state.setIn(['selected', 'album'], action.value);
case 'SET_SELECTED_TRACK':
- return Object.assign({}, state, {
- selected: Object.assign({}, state.selected, {
- title: action.value.title,
- file: action.value.file
- })
- });
+ return state.setIn(['selected', 'title'], action.value.title).
+ setIn(['selected', 'file'], action.value.file);
case 'SET_PLAYING_TRACK':
- return Object.assign({}, state, {
- playing: Object.assign({}, state.playing, {
- title: action.value.title,
- file: action.value.file
- })
- });
+ return state.setIn(['playing', 'title'], action.value.title).
+ setIn(['playing', 'file'], action.value.file);
case 'LIBRARY_UPDATED':
- return Object.assign({}, state, {
- libraryUpdated: true
- });
+ return state.set('libraryUpdated', true).
+ set('selected', Immutable.Map());
+
+ case 'LIBRARY_DID_UPDATE':
+ return state.set('libraryUpdated', false);
+
+ case 'SET_LIBRARY_ARTISTS':
+ return state.setIn(['library', 'artists'], Immutable.fromJS(action.value)).
+ setIn(['library', 'albums'], Immutable.List());
+
+ case 'SET_LIBRARY_ALBUMS':
+ return state.setIn(['library', 'albums'], Immutable.fromJS(action.value)).
+ setIn(['library', 'tracks'], Immutable.List());
+
+ case 'SET_LIBRARY_TRACKS':
+ return state.setIn(['library', 'tracks'], Immutable.fromJS(action.value));
default:
return state;