diff --git a/js/Close.jsx b/js/Close.jsx
index 0ef471d4..a1cdcc93 100644
--- a/js/Close.jsx
+++ b/js/Close.jsx
@@ -1,6 +1,8 @@
import React from 'react';
import {connect} from 'react-redux';
+import {close} from './actionCreators';
+
class Close extends React.Component {
constructor(props) {
@@ -8,7 +10,7 @@ class Close extends React.Component {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
- this.props.dispatch({type: 'CLOSE'});
+ this.props.dispatch(close(this.props.mediaPlayer));
}
render() {
return
{
- return
+const MainWindow = (props) => {
+ const {status} = props.media;
+ const {loading, doubled, shade, closed, llama} = props.display;
+
+ const className = classnames({
+ // TODO: Handle these status changes in the individual components
+ play: status === 'PLAYING',
+ stop: status === 'STOPPED',
+ pause: status === 'PAUSED',
+ loading,
+ doubled,
+ llama,
+ shade,
+ closed
+ });
+ return
Loading...
@@ -33,11 +50,11 @@ const MainWindow = () => {
-
+
-
+
@@ -53,8 +70,8 @@ const MainWindow = () => {
-
-
+
+
@@ -64,4 +81,4 @@ const MainWindow = () => {
;
};
-module.exports = MainWindow;
+module.exports = connect(state => state)(MainWindow);
diff --git a/js/Position.jsx b/js/Position.jsx
index 1e53a0c6..ce188104 100644
--- a/js/Position.jsx
+++ b/js/Position.jsx
@@ -15,7 +15,7 @@ class Position extends React.Component {
}
onMouseUp(e) {
- this.props.dispatch({type: 'SET_POSITION', position: e.target.value});
+ this.props.mediaPlayer.seekToPercentComplete(e.target.value);
this.props.dispatch({type: 'UNSET_FOCUS'});
}
diff --git a/js/Shade.jsx b/js/Shade.jsx
new file mode 100644
index 00000000..626d5301
--- /dev/null
+++ b/js/Shade.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import {connect} from 'react-redux';
+
+class Shade extends React.Component {
+ constructor(props) {
+ super(props);
+ this.handleClick = this.handleClick.bind(this);
+ }
+ handleClick() {
+ this.props.dispatch({type: 'TOGGLE_SHADE_MODE'});
+ }
+ render() {
+ return
;
+ }
+}
+
+module.exports = connect()(Shade);
diff --git a/js/actionCreators.js b/js/actionCreators.js
new file mode 100644
index 00000000..0b91659a
--- /dev/null
+++ b/js/actionCreators.js
@@ -0,0 +1,51 @@
+import MyFile from './my-file';
+
+export function play(mediaPlayer) {
+ return (dispatch, getState) => {
+ if (getState().media.status === 'PLAYING') {
+ mediaPlayer.stop();
+ dispatch({type: 'MEDIA_IS_STOPPED'});
+ } else {
+ mediaPlayer.play();
+ dispatch({type: 'MEDIA_IS_PLAYING'});
+ }
+ };
+}
+
+export function pause(mediaPlayer) {
+ return (dispatch, getState) => {
+ const status = getState().media.status;
+ switch (status) {
+ case 'PAUSED':
+ mediaPlayer.play();
+ dispatch({type: 'MEDIA_IS_PLAYING'});
+ break;
+ case 'PLAYING':
+ mediaPlayer.pause();
+ dispatch({type: 'MEDIA_IS_PAUSED'});
+ break;
+ }
+ };
+}
+
+export function stop(mediaPlayer) {
+ return (dispatch) => {
+ mediaPlayer.stop();
+ dispatch({type: 'MEDIA_IS_STOPPED'});
+ };
+}
+
+export function close(mediaPlayer) {
+ return (dispatch) => {
+ mediaPlayer.stop();
+ dispatch({type: 'CLOSE_WINAMP'});
+ };
+}
+
+export function setSkinFromFilename(winamp, filename) {
+ const url = `https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/${filename}`;
+ const skinFile = new MyFile();
+ skinFile.setUrl(url);
+ winamp.setSkin(skinFile);
+ return {type: 'START_LOADING'};
+}
diff --git a/js/hotkeys.js b/js/hotkeys.js
index a52c6799..bf8a3e7c 100644
--- a/js/hotkeys.js
+++ b/js/hotkeys.js
@@ -1,3 +1,5 @@
+import {play, pause, stop} from './actionCreators';
+
module.exports = function(winamp, store) {
var keylog = [];
var trigger = [78, 85, 76, 27, 76, 27, 83, 79, 70, 84];
@@ -26,13 +28,16 @@ module.exports = function(winamp, store) {
store.dispatch({type: 'SET_VOLUME', volume: decrementedVolume});
break;
case 66: winamp.next(); break; // B
- case 67: winamp.pause(); break; // C
+ // C
+ case 67: winamp.dispatch(pause(winamp.media)); break;
// L
case 76: store.dispatch({type: 'OPEN_FILE_DIALOG'}); break;
case 82: winamp.toggleRepeat(); break; // R
case 83: winamp.toggleShuffle(); break; // S
- case 86: winamp.stop(); break; // V
- case 88: winamp.play(); break; // X
+ // V
+ case 86: winamp.dispatch(stop(winamp.media)); break;
+ // X
+ case 88: winamp.dispatch(play(winamp.media)); break;
case 90: winamp.previous(); break; // Z
// numpad 0
case 96: store.dispatch({type: 'OPEN_FILE_DIALOG'}); break;
@@ -40,7 +45,8 @@ module.exports = function(winamp, store) {
case 98: winamp.incrementVolumeBy(-1); break; // numpad 2
case 99: winamp.next(10); break; // numpad 3
case 100: winamp.previous(); break; // numpad 4
- case 101: winamp.play(); break; // numpad 5
+ // numpad 5
+ case 101: winamp.dispatch(play(winamp.media)); break;
case 102: winamp.next(); break; // numpad 6
case 103: winamp.seekForwardBy(-5); break; // numpad 7
case 104: winamp.incrementVolumeBy(1); break; // numpad 8
@@ -52,7 +58,7 @@ module.exports = function(winamp, store) {
keylog.push(e.keyCode);
keylog = keylog.slice(-10);
if (keylog.toString() === trigger.toString()) {
- winamp.toggleLlama();
+ winamp.dispatch({type: 'TOGGLE_LLAMA_MODE'});
}
});
};
diff --git a/js/main-window.js b/js/main-window.js
index 800ed642..0fd5a914 100644
--- a/js/main-window.js
+++ b/js/main-window.js
@@ -2,7 +2,6 @@ module.exports = {
init: function(winamp) {
this.winamp = winamp;
this.nodes = {
- shade: document.getElementById('shade'),
buttonD: document.getElementById('button-d'),
visualizer: document.getElementById('visualizer'),
workIndicator: document.getElementById('work-indicator'),
@@ -19,10 +18,6 @@ module.exports = {
_registerListeners: function() {
var self = this;
- this.nodes.shade.onclick = function() {
- self.nodes.window.classList.toggle('shade');
- };
-
this.nodes.buttonD.onmousedown = function() {
self.winamp.dispatch({type: 'SET_FOCUS', input: 'double'});
};
@@ -36,70 +31,11 @@ module.exports = {
self.winamp.toggleVisualizer();
};
- window.addEventListener('startWaiting', function() {
- self.setWorkingIndicator();
- });
- window.addEventListener('stopWaiting', function() {
- self.unsetWorkingIndicator();
- });
- window.addEventListener('startLoading', function() {
- self.setLoadingState();
- });
- window.addEventListener('stopLoading', function() {
- self.unsetLoadingState();
- });
- window.addEventListener('changeState', function() {
- self.changeState();
- });
- window.addEventListener('doubledModeToggled', function() {
- self.toggleDoubledMode();
- });
- window.addEventListener('llamaToggled', function() {
- self.toggleLlama();
- });
- window.addEventListener('close', function() {
- self.nodes.window.classList.add('closed');
- });
-
this.nodes.window.addEventListener('dragenter', this.dragenter.bind(this));
this.nodes.window.addEventListener('dragover', this.dragover.bind(this));
this.nodes.window.addEventListener('drop', this.drop.bind(this));
},
- toggleDoubledMode: function() {
- this.nodes.buttonD.classList.toggle('selected');
- this.nodes.window.classList.toggle('doubled');
- },
-
- setWorkingIndicator: function() {
- this.nodes.workIndicator.classList.add('selected');
- },
-
- unsetWorkingIndicator: function() {
- this.nodes.workIndicator.classList.remove('selected');
- },
-
- setLoadingState: function() {
- this.nodes.window.classList.add('loading');
- },
-
- unsetLoadingState: function() {
- this.nodes.window.classList.remove('loading');
- },
-
- changeState: function() {
- var state = this.winamp.getState();
- var stateOptions = ['play', 'stop', 'pause'];
- for (var i = 0; i < stateOptions.length; i++) {
- this.nodes.window.classList.remove(stateOptions[i]);
- }
- this.nodes.window.classList.add(state);
- },
-
- toggleLlama: function() {
- this.nodes.window.classList.toggle('llama');
- },
-
dragenter: function(e) {
e.stopPropagation();
e.preventDefault();
diff --git a/js/main.js b/js/main.js
index 0db50f5b..3dceeb37 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,7 +1,8 @@
import React from 'react';
import {render} from 'react-dom';
import {Provider} from 'react-redux';
-import {createStore} from 'redux';
+import {createStore, applyMiddleware} from 'redux';
+import thunk from 'redux-thunk';
import createReducer from './reducers';
@@ -14,12 +15,13 @@ if (new Browser(window).isCompatible) {
var winamp = Winamp;
let store = createStore(
createReducer(winamp),
- window.devToolsExtension && window.devToolsExtension()
+ window.devToolsExtension && window.devToolsExtension(),
+ applyMiddleware(thunk)
);
render(
-
+
,
document.getElementById('winamp2-js')
);
diff --git a/js/reducers.js b/js/reducers.js
index ba3f2644..edd84f8c 100644
--- a/js/reducers.js
+++ b/js/reducers.js
@@ -1,4 +1,3 @@
-import MyFile from './my-file';
import {combineReducers} from 'redux';
const userInput = (state, action) => {
@@ -24,15 +23,34 @@ const display = (state, action) => {
if (!state) {
return {
doubled: false,
- marqueeStep: 0
+ marqueeStep: 0,
+ loading: true,
+ llama: false,
+ closed: false,
+ shade: false,
+ working: false
};
}
switch (action.type) {
case 'TOGGLE_DOUBLESIZE_MODE':
return {...state, doubled: !state.doubled};
+ case 'TOGGLE_SHADE_MODE':
+ return {...state, shade: !state.shade};
+ case 'TOGGLE_LLAMA_MODE':
+ return {...state, llama: !state.llama};
case 'STEP_MARQUEE':
// TODO: Prevent this from becoming huge
return {...state, marqueeStep: state.marqueeStep + 1};
+ case 'STOP_WORKING':
+ return {...state, working: false};
+ case 'START_WORKING':
+ return {...state, working: true};
+ case 'STOP_LOADING':
+ return {...state, loading: false};
+ case 'START_LOADING':
+ return {...state, loading: true};
+ case 'CLOSE_WINAMP':
+ return {...state, closed: true};
default:
return state;
}
@@ -67,7 +85,9 @@ const media = (state, action) => {
name: '',
channels: null,
shuffle: false,
- repeat: false
+ repeat: false,
+ // TODO: Enforce possible values
+ status: 'STOPPED'
};
}
switch (action.type) {
@@ -94,6 +114,12 @@ const media = (state, action) => {
return {...state, repeat: !state.repeat};
case 'TOGGLE_SHUFFLE':
return {...state, shuffle: !state.shuffle};
+ case 'MEDIA_IS_PLAYING':
+ return {...state, status: 'PLAYING'};
+ case 'MEDIA_IS_PAUSED':
+ return {...state, status: 'PAUSED'};
+ case 'MEDIA_IS_STOPPED':
+ return {...state, status: 'STOPPED'};
default:
return state;
}
@@ -112,49 +138,22 @@ const createReducer = (winamp) => {
return (state, action) => {
state = reducer(state, action);
switch (action.type) {
- case 'PLAY':
- winamp.play();
- return state;
- case 'PAUSE':
- winamp.pause();
- return state;
- case 'STOP':
- winamp.stop();
- return state;
case 'SET_VOLUME':
winamp.setVolume(action.volume);
return state;
case 'SET_BALANCE':
winamp.setBalance(action.balance);
return state;
- case 'SET_POSITION':
- winamp.seekToPercentComplete(action.position);
- return state;
- case 'CLOSE_WINAMP':
- winamp.close();
- return state;
case 'OPEN_FILE_DIALOG':
// TODO: Figure out how to make this pure
winamp.openFileDialog();
return state;
- case 'SET_SKIN_FROM_URL':
- // TODO: Figure out how to make this pure
- const skinFile = new MyFile();
- skinFile.setUrl(action.url);
- winamp.setSkin(skinFile);
- return state;
- case 'TOGGLE_DOUBLESIZE_MODE':
- winamp.toggleDoubledMode();
- return state;
case 'TOGGLE_REPEAT':
winamp.toggleRepeat();
return state;
case 'TOGGLE_SHUFFLE':
winamp.toggleShuffle();
return state;
- case 'CLOSE':
- winamp.close();
- return state;
default:
return state;
}
diff --git a/js/winamp.js b/js/winamp.js
index 745c6318..4f5b0441 100755
--- a/js/winamp.js
+++ b/js/winamp.js
@@ -8,28 +8,20 @@ import MyFile from './my-file';
import '../css/winamp.css';
module.exports = {
+ media: Media.init(),
init: function(options) {
this.fileInput = document.createElement('input');
this.fileInput.type = 'file';
this.fileInput.style.display = 'none';
this.windowManager = WindowManager;
- this.media = Media.init();
this.skin = Skin.init(document.getElementById('visualizer'), this.media._analyser);
this.state = '';
this.mainWindow = MainWindow.init(this);
this.events = {
- timeUpdated: new Event('timeUpdated'),
- startWaiting: new Event('startWaiting'),
- stopWaiting: new Event('stopWaiting'),
- startLoading: new Event('startLoading'),
- stopLoading: new Event('stopLoading'),
- changeState: new Event('changeState'),
- doubledModeToggled: new Event('doubledModeToggled'),
- llamaToggled: new Event('llamaToggled'),
- close: new Event('close')
+ timeUpdated: new Event('timeUpdated')
};
this.dispatch({type: 'SET_VOLUME', volume: options.volume});
@@ -60,19 +52,19 @@ module.exports = {
this.media.addEventListener('ended', function() {
self.skin.visualizer.clear();
- self.setState('stop');
+ self.dispatch({type: 'MEDIA_IS_STOPPED'});
});
this.media.addEventListener('waiting', function() {
- window.dispatchEvent(self.events.startWaiting);
+ self.dispatch({type: 'START_WORKING'});
});
this.media.addEventListener('stopWaiting', function() {
- window.dispatchEvent(self.events.stopWaiting);
+ self.dispatch({type: 'STOP_WORKING'});
});
this.media.addEventListener('playing', function() {
- self.setState('play');
+ self.dispatch({type: 'MEDIA_IS_PLAYING'});
});
this.fileInput.onchange = function(e){
@@ -81,15 +73,6 @@ module.exports = {
},
/* Functions */
- setState: function(state) {
- this.state = state;
- window.dispatchEvent(this.events.changeState);
- },
-
- getState: function() {
- return this.state;
- },
-
getDuration: function() {
return this.media.duration();
},
@@ -110,27 +93,6 @@ module.exports = {
this.media.seekToPercentComplete(percent);
},
- play: function() {
- if (this.getState() === 'play'){
- this.media.stop();
- }
- this.media.play();
- this.setState('play');
- },
-
- pause: function() {
- if (this.getState() === 'pause'){
- this.media.play();
- } else if (this.getState() === 'play') {
- this.media.pause();
- this.setState('pause');
- }
- },
- stop: function() {
- this.media.stop();
- this.setState('stop');
- },
-
// From 0-100
setVolume: function(volume) {
// Ensure volume does not go out of bounds
@@ -140,10 +102,6 @@ module.exports = {
this.media.setVolume(volume);
},
- toggleDoubledMode: function() {
- window.dispatchEvent(this.events.doubledModeToggled);
- },
-
// From -100 to 100
setBalance: function(balance) {
this.media.setBalance(balance);
@@ -162,14 +120,9 @@ module.exports = {
this.media.toggleShuffle();
},
- toggleLlama: function() {
- window.dispatchEvent(this.events.llamaToggled);
- },
-
close: function() {
- window.dispatchEvent(this.events.close);
this.media.stop();
- this.setState('stop'); // Currently unneeded
+ this.dispatch({type: 'MEDIA_IS_STOPPED'});
},
openFileDialog: function() {
@@ -201,16 +154,8 @@ module.exports = {
},
setSkin: function(file) {
- this.setLoadingState();
- this.skin.setSkinByFile(file, this.unsetLoadingState.bind(this));
- },
-
- setLoadingState: function() {
- window.dispatchEvent(this.events.startLoading);
- },
-
- unsetLoadingState: function() {
- window.dispatchEvent(this.events.stopLoading);
+ this.dispatch({type: 'START_LOADING'});
+ this.skin.setSkinByFile(file, () => this.dispatch({type: 'STOP_LOADING'}));
},
toggleVisualizer: function() {
diff --git a/package.json b/package.json
index 61df63a9..50b391fe 100644
--- a/package.json
+++ b/package.json
@@ -49,10 +49,12 @@
"dependencies": {
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-plugin-transform-react-jsx": "^6.8.0",
+ "classnames": "^2.2.5",
"jszip": "^2.6.0",
"react": "^15.2.0",
"react-dom": "^15.2.0",
"react-redux": "^4.4.5",
- "redux": "^3.5.2"
+ "redux": "^3.5.2",
+ "redux-thunk": "^2.1.0"
}
}