mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 09:37:17 +00:00
Merge pull request #153 from captbaritone/redux
Begin migrating from vanilla JavaScript to React/Rexux
This commit is contained in:
commit
8ba5e1bb5b
25 changed files with 486 additions and 398 deletions
|
|
@ -23,6 +23,12 @@ supported](http://caniuse.com/#feat=audio-api).
|
|||
|
||||
Open `index.html` in your browser.
|
||||
|
||||
## Development
|
||||
|
||||
npm run serve
|
||||
|
||||
Open `http://localhost:8080/webpack-dev-server/` in your browser.
|
||||
|
||||
## Reference
|
||||
|
||||
- [skinspecs.pdf](http://members.xoom.it/skinart/tutorial/skinspecs..pdf)
|
||||
|
|
@ -56,7 +62,7 @@ for Winamp which inspired so many of us.
|
|||
|
||||
## License
|
||||
|
||||
While the Winamp name, interface, sample audio file and surely property of
|
||||
While the Winamp name, interface, and, sample audio file are surely property of
|
||||
Nullsoft, the code within this project is released under the [MIT
|
||||
License](LICENSE.txt). That being said, if you do anything interesting with
|
||||
this code, please let me know. I'd love to see it.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
position: absolute;
|
||||
height: 116px;
|
||||
width: 275px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/MAINMENU.PNG'), auto;
|
||||
cursor:url('../cursors/MAINMENU.PNG'), auto;
|
||||
/* Ask the browser to scale showing large pixels if possible */
|
||||
image-rendering: -moz-crisp-edges; /* Firefox */
|
||||
image-rendering: -o-crisp-edges; /* Opera */
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
left: 0;
|
||||
height: 14px;
|
||||
width: 275px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/TITLEBAR.PNG'), auto;
|
||||
cursor:url('../cursors/TITLEBAR.PNG'), auto;
|
||||
}
|
||||
|
||||
#winamp2-js #option,
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
height: 9px;
|
||||
width: 9px;
|
||||
top: 3px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/MAINMENU.PNG'), auto;
|
||||
cursor:url('../cursors/MAINMENU.PNG'), auto;
|
||||
}
|
||||
|
||||
#winamp2-js #title-bar #option {
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
}
|
||||
#winamp2-js #title-bar #close {
|
||||
left: 264px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/CLOSE.PNG'), auto;
|
||||
cursor:url('../cursors/CLOSE.PNG'), auto;
|
||||
}
|
||||
#winamp2-js #clutter-bar {
|
||||
position: absolute;
|
||||
|
|
@ -161,6 +161,7 @@
|
|||
}
|
||||
|
||||
#winamp2-js .status #time #minus-sign {
|
||||
/* Note that this get's augmented by the skin CSS if NUM_EX.BMP is present */
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: -1px;
|
||||
|
|
@ -168,13 +169,6 @@
|
|||
height: 1px;
|
||||
}
|
||||
|
||||
/* Alternate format for minus sign, when skin supports it */
|
||||
#winamp2-js .status #time.ex #minus-sign {
|
||||
top: 0px;
|
||||
left: -1px;
|
||||
width: 9px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
#winamp2-js .status #time #minute-first-digit {
|
||||
position: absolute;
|
||||
|
|
@ -279,19 +273,19 @@
|
|||
height: 13px;
|
||||
width: 68px;
|
||||
background-position: 0 0;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
#winamp2-js #volume::-webkit-slider-thumb {
|
||||
top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
#winamp2-js #volume::-moz-range-thumb {
|
||||
top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
|
||||
#winamp2-js #balance {
|
||||
|
|
@ -301,20 +295,20 @@
|
|||
height: 13px;
|
||||
width: 38px;
|
||||
background-position: 0 0;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
|
||||
#winamp2-js #balance::-webkit-slider-thumb {
|
||||
top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
#winamp2-js #balance::-moz-range-thumb {
|
||||
top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
|
||||
#winamp2-js .windows {
|
||||
|
|
@ -345,13 +339,13 @@
|
|||
top: 72px;
|
||||
width: 248px;
|
||||
height: 10px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
|
||||
#winamp2-js #position::-webkit-slider-thumb {
|
||||
height: 10px;
|
||||
width: 29px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
/*
|
||||
* Fix the strange bug in Safair/mobile-chrome
|
||||
* http://stackoverflow.com/questions/26727769/rendering-glitch-when-manipulating-range-input-value-via-javascript-in-webkit
|
||||
|
|
@ -363,7 +357,7 @@
|
|||
#winamp2-js #position::-moz-range-thumb {
|
||||
height: 10px;
|
||||
width: 29px;
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/POSBAR.PNG'), auto;
|
||||
cursor:url('../cursors/POSBAR.PNG'), auto;
|
||||
}
|
||||
|
||||
/* For some reason, we can't use display: none here */
|
||||
|
|
@ -450,7 +444,7 @@
|
|||
}
|
||||
|
||||
#winamp2-js #shade-time {
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/MAINMENU.PNG'), auto;
|
||||
cursor:url('../cursors/MAINMENU.PNG'), auto;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -463,7 +457,7 @@
|
|||
display: none;
|
||||
}
|
||||
#winamp2-js .shade #title-bar {
|
||||
cursor:url('https://jordaneldredge.com/projects/winamp2-js/cursors/MAINMENU.PNG'), auto;
|
||||
cursor:url('../cursors/MAINMENU.PNG'), auto;
|
||||
}
|
||||
|
||||
#winamp2-js .shade .actions div {
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
<div id='main-window' class='loading stop'>
|
||||
<div id='loading'>Loading...</div>
|
||||
<div id='title-bar' class='selected'>
|
||||
<div id='option'>
|
||||
<ul id='context-menu'>
|
||||
<li><a href='https://github.com/captbaritone/winamp2-js' target='_blank'>Winamp2-js...</a></li>
|
||||
<li class='hr'><hr /></li>
|
||||
<li id='context-play-file'>Play File...</li>
|
||||
<li class='parent'>
|
||||
<ul>
|
||||
<li id='context-load-skin'>Load Skin...</li>
|
||||
<li class='hr'><hr /></li>
|
||||
<li class='skin-select' data-skin-url='https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/base-2.91.wsz'><Base Skin></li>
|
||||
<li class='skin-select' data-skin-url='https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/MacOSXAqua1-5.wsz'>Mac OSX v1.5 (Aqua)</li>
|
||||
<li class='skin-select' data-skin-url='https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/TopazAmp1-2.wsz'>TopazAmp</li>
|
||||
<li class='skin-select' data-skin-url='https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/Vizor1-01.wsz'>Vizor</li>
|
||||
<li class='skin-select' data-skin-url='https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/XMMS-Turquoise.wsz'>XMMS Turquoise </li>
|
||||
<li class='skin-select' data-skin-url='https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/ZaxonRemake1-0.wsz'>Zaxon Remake</li>
|
||||
</ul>
|
||||
Skins
|
||||
</li>
|
||||
<li class='hr'><hr /></li>
|
||||
<li id='context-exit'>Exit</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id='shade-time'>
|
||||
<div id='shade-minus-sign'></div>
|
||||
<div id='shade-minute-first-digit' class='character'></div>
|
||||
<div id='shade-minute-second-digit' class='character'></div>
|
||||
<div id='shade-second-first-digit' class='character'></div>
|
||||
<div id='shade-second-second-digit' class='character'></div>
|
||||
</div>
|
||||
<div id='minimize'></div>
|
||||
<div id='shade'></div>
|
||||
<div id='close'></div>
|
||||
</div>
|
||||
<div class='status'>
|
||||
<div id='clutter-bar'>
|
||||
<div id='button-o'></div>
|
||||
<div id='button-a'></div>
|
||||
<div id='button-i'></div>
|
||||
<div id='button-d'></div>
|
||||
<div id='button-v'></div>
|
||||
</div>
|
||||
<div id='play-pause'></div>
|
||||
<div id='work-indicator'></div>
|
||||
<div id='time'>
|
||||
<div id='minus-sign'></div>
|
||||
<div id='minute-first-digit'></div>
|
||||
<div id='minute-second-digit'></div>
|
||||
<div id='second-first-digit'></div>
|
||||
<div id='second-second-digit'></div>
|
||||
</div>
|
||||
<canvas id='visualizer' width='152' height='32'></canvas>
|
||||
</div>
|
||||
<div class='media-info'>
|
||||
<div id='song-title' class='text'></div>
|
||||
<div id='kbps'></div>
|
||||
<div id='khz'></div>
|
||||
<div class='mono-stereo'>
|
||||
<div id='mono'></div>
|
||||
<div id='stereo'></div>
|
||||
</div>
|
||||
</div>
|
||||
<input id='volume' type='range' min='0' max='100' step='1' value='50' />
|
||||
<input id='balance' type='range' min='-100' max='100' step='2' value='0' />
|
||||
<div class='windows'>
|
||||
<div id='equalizer-button'></div>
|
||||
<div id='playlist-button'></div>
|
||||
</div>
|
||||
<input id='position' type='range' min='0' max='100' step='1' value='0' />
|
||||
<div class='actions'>
|
||||
<div id='previous'></div>
|
||||
<div id='play'></div>
|
||||
<div id='pause'></div>
|
||||
<div id='stop'></div>
|
||||
<div id='next'></div>
|
||||
</div>
|
||||
<div id='eject'></div>
|
||||
<div class='shuffle-repeat'>
|
||||
<div id="shuffle"></div>
|
||||
<div id="repeat"></div>
|
||||
</div>
|
||||
<a id='about' target='_blank' href='https://github.com/captbaritone/winamp2-js'></a>
|
||||
</div>
|
||||
32
js/Actions.jsx
Normal file
32
js/Actions.jsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
|
||||
class Actions extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.play = this.play.bind(this);
|
||||
this.pause = this.pause.bind(this);
|
||||
this.stop = this.stop.bind(this);
|
||||
}
|
||||
play() {
|
||||
this.props.dispatch({type: 'PLAY'});
|
||||
}
|
||||
pause() {
|
||||
this.props.dispatch({type: 'PAUSE'});
|
||||
}
|
||||
stop() {
|
||||
this.props.dispatch({type: 'STOP'});
|
||||
}
|
||||
render() {
|
||||
return <div className='actions'>
|
||||
<div id='previous'></div>
|
||||
<div id='play' onClick={this.play}></div>
|
||||
<div id='pause' onClick={this.pause}></div>
|
||||
<div id='stop' onClick={this.stop}></div>
|
||||
<div id='next'></div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state.media)(Actions);
|
||||
9
js/Character.jsx
Normal file
9
js/Character.jsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
|
||||
module.exports = (props) => {
|
||||
const char = '' + props.children;
|
||||
const className = 'character character-' + char.toLowerCase().charCodeAt(0);
|
||||
return <div {...props} className={className}>{props.children}</div>;
|
||||
};
|
||||
|
||||
// TODO: Require that props.children be a string
|
||||
14
js/CharacterString.jsx
Normal file
14
js/CharacterString.jsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import Character from './Character.jsx';
|
||||
|
||||
module.exports = (props) => {
|
||||
const text = '' + props.children;
|
||||
const chars = text.split('');
|
||||
return <div {...props}>
|
||||
{chars.map(character => {
|
||||
return <Character>{character}</Character>;
|
||||
})};
|
||||
</div>;
|
||||
};
|
||||
|
||||
// TODO: Require that props.children be a string
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
import React from 'react';
|
||||
import MyFile from './my-file';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import '../css/context-menu.css';
|
||||
|
||||
class ContextMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selected: false
|
||||
};
|
||||
|
||||
this.openFileDialog = this.openFileDialog.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
this.toggleMenu = this.toggleMenu.bind(this);
|
||||
|
|
@ -27,34 +23,30 @@ class ContextMenu extends React.Component {
|
|||
}
|
||||
|
||||
openFileDialog() {
|
||||
this.props.winamp.openFileDialog();
|
||||
this.props.dispatch({type: 'OPEN_FILE_DIALOG'});
|
||||
}
|
||||
|
||||
close() {
|
||||
// Close all of Winamp
|
||||
this.props.winamp.close();
|
||||
this.props.dispatch({type: 'CLOSE_WINAMP'});
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
this.setState({selected: false});
|
||||
this.props.dispatch({type: 'CLOSE_CONTEXT_MENU'});
|
||||
}
|
||||
|
||||
toggleMenu(event) {
|
||||
this.setState({selected: !this.state.selected});
|
||||
this.props.dispatch({type: 'TOGGLE_CONTEXT_MENU'});
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
setSkin(e) {
|
||||
const filename = e.target.dataset.filename;
|
||||
const url = 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/' + filename;
|
||||
const skinFile = new MyFile();
|
||||
skinFile.setUrl(url);
|
||||
this.props.winamp.setSkin(skinFile);
|
||||
this.props.dispatch({type: 'SET_SKIN_FROM_URL', url: url});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
var classes = this.state.selected ? 'selected' : '';
|
||||
var classes = this.props.selected ? 'selected' : '';
|
||||
return <div id='option' className={classes} onClick={this.toggleMenu}>
|
||||
<ul id='context-menu'>
|
||||
<li><a href='https://github.com/captbaritone/winamp2-js' target='_blank'>Winamp2-js...</a></li>
|
||||
|
|
@ -80,4 +72,4 @@ class ContextMenu extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = ContextMenu;
|
||||
module.exports = connect(state => state.contextMenu)(ContextMenu);
|
||||
|
|
|
|||
16
js/Kbps.jsx
Normal file
16
js/Kbps.jsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Single line text display that can animate and hold multiple registers
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import CharacterString from './CharacterString.jsx';
|
||||
|
||||
|
||||
class Kbps extends React.Component {
|
||||
render() {
|
||||
return <CharacterString id='kbps'>
|
||||
{this.props.kbps}
|
||||
</CharacterString>;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state.media)(Kbps);
|
||||
16
js/Khz.jsx
Normal file
16
js/Khz.jsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Single line text display that can animate and hold multiple registers
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import CharacterString from './CharacterString.jsx';
|
||||
|
||||
|
||||
class Khz extends React.Component {
|
||||
render() {
|
||||
return <CharacterString id='khz'>
|
||||
{this.props.khz}
|
||||
</CharacterString>;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state.media)(Khz);
|
||||
55
js/Marquee.jsx
Normal file
55
js/Marquee.jsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Single line text display that can animate and hold multiple registers
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import CharacterString from './CharacterString.jsx';
|
||||
|
||||
|
||||
class Marquee extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const step = () => {
|
||||
setTimeout(() => {
|
||||
this.props.dispatch({type: 'STEP_MARQUEE'});
|
||||
step();
|
||||
}, 220);
|
||||
};
|
||||
step();
|
||||
}
|
||||
selectedRegister() {
|
||||
const selected = this.props.registers.filter(register => {
|
||||
return register.id === this.props.selectedRegister;
|
||||
});
|
||||
return selected ? selected[0] : selected;
|
||||
}
|
||||
handleMouseDown() {
|
||||
this.props.dispatch({type: 'PAUSE_MARQUEE'});
|
||||
document.addEventListener('mouseup', () => {
|
||||
// TODO: Remove this listener
|
||||
setTimeout(() => {
|
||||
this.props.dispatch({type: 'START_MARQUEE'});
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const register = this.selectedRegister();
|
||||
let chars = register.text.split('');
|
||||
if (chars.length > 30 && register.step > 0) {
|
||||
const start = chars.slice(register.step);
|
||||
const end = chars.slice(0, register.step);
|
||||
// TODO: Use the spread operator
|
||||
chars = start.concat(end).slice(0, 30);
|
||||
}
|
||||
const text = chars.join('');
|
||||
return <CharacterString onMouseDown={this.handleMouseDown}>
|
||||
{text}
|
||||
</CharacterString>;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state.marquee)(Marquee);
|
||||
32
js/ShadeTime.jsx
Normal file
32
js/ShadeTime.jsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTimeObj} from './utils';
|
||||
import Character from './Character.jsx';
|
||||
|
||||
|
||||
class Time extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.toggleTimeMode = this.toggleTimeMode.bind(this);
|
||||
}
|
||||
toggleTimeMode() {
|
||||
this.props.dispatch({type: 'TOGGLE_TIME_MODE'});
|
||||
}
|
||||
render() {
|
||||
const seconds = this.props.timeMode === 'ELAPSED' ?
|
||||
this.props.timeElapsed :
|
||||
this.props.length - this.props.timeElapsed;
|
||||
|
||||
const timeObj = getTimeObj(seconds);
|
||||
return <div id='shade-time' onClick={this.toggleTimeMode} className='countdown'>
|
||||
<Character id='shade-minus-sign'>{this.props.timeMode === 'REMAINING' ? '-' : ''}</Character>
|
||||
<Character id='shade-minute-first-digit'>{timeObj.minutesFirstDigit}</Character>
|
||||
<Character id='shade-minute-second-digit'>{timeObj.minutesSecondDigit}</Character>
|
||||
<Character id='shade-second-first-digit'>{timeObj.secondsFirstDigit}</Character>
|
||||
<Character id='shade-second-second-digit'>{timeObj.secondsSecondDigit}</Character>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state.media)(Time);
|
||||
30
js/Time.jsx
Normal file
30
js/Time.jsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTimeObj} from './utils';
|
||||
|
||||
class Time extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.toggleTimeMode = this.toggleTimeMode.bind(this);
|
||||
}
|
||||
toggleTimeMode() {
|
||||
this.props.dispatch({type: 'TOGGLE_TIME_MODE'});
|
||||
}
|
||||
render() {
|
||||
const seconds = this.props.timeMode === 'ELAPSED' ?
|
||||
this.props.timeElapsed :
|
||||
this.props.length - this.props.timeElapsed;
|
||||
|
||||
const timeObj = getTimeObj(seconds);
|
||||
return <div id='time' onClick={this.toggleTimeMode} className='countdown'>
|
||||
{this.props.timeMode === 'REMAINING' && <div id='minus-sign'></div>}
|
||||
<div id='minute-first-digit' className={'digit digit-' + timeObj.minutesFirstDigit}></div>
|
||||
<div id='minute-second-digit' className={'digit digit-' + timeObj.minutesSecondDigit}></div>
|
||||
<div id='second-first-digit' className={'digit digit-' + timeObj.secondsFirstDigit}></div>
|
||||
<div id='second-second-digit' className={'digit digit-' + timeObj.secondsSecondDigit}></div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state.media)(Time);
|
||||
37
js/font.js
37
js/font.js
|
|
@ -1,37 +0,0 @@
|
|||
// Manage rendering text from this skin's text.bmp file
|
||||
module.exports = function() {
|
||||
|
||||
// Fill a node with a <div> containing character <div>s
|
||||
this.setNodeToString = function(node, string) {
|
||||
var stringElement = document.createElement('div');
|
||||
for (var i = 0, len = string.length; i < len; i++) {
|
||||
var char = string[i].toLowerCase();
|
||||
stringElement.appendChild(this.characterNode(char));
|
||||
}
|
||||
node.innerHTML = '';
|
||||
node.appendChild(stringElement);
|
||||
};
|
||||
|
||||
// Get a <div> containing char
|
||||
this.characterNode = function(char) {
|
||||
return this.displayCharacterInNode(char, document.createElement('div'));
|
||||
};
|
||||
|
||||
// Style/populate a <div> to display a character
|
||||
this.displayCharacterInNode = function(character, node) {
|
||||
character = character.toString();
|
||||
var className = 'character-' + character.charCodeAt(0);
|
||||
node.classList.add('character');
|
||||
node.classList.add(className);
|
||||
return node;
|
||||
};
|
||||
|
||||
// Get a <div> containing a digit
|
||||
this.digitNode = function(digit) {
|
||||
var div = document.createElement('div');
|
||||
div.classList.add('digit');
|
||||
div.classList.add('digit-' + digit);
|
||||
return div;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
module.exports = function(winamp) {
|
||||
var keylog = [];
|
||||
var trigger = [78, 85, 76, 27, 76, 27, 83, 79, 70, 84];
|
||||
document.addEventListener('keyup', function(e){
|
||||
document.addEventListener('keydown', function(e){
|
||||
if (e.ctrlKey) { // Is CTRL depressed?
|
||||
switch (e.keyCode) {
|
||||
case 68: winamp.toggleDoubledMode(); break; // CTRL+D
|
||||
// XXX FIXME
|
||||
case 76: winamp.openOptionMenu(); break; // CTRL+L
|
||||
case 84: winamp.toggleTimeMode(); break; // CTRL+T
|
||||
// CTRL+T
|
||||
case 84: winamp.dispath({type: 'TOGGLE_TIME_MODE'}); break;
|
||||
}
|
||||
} else {
|
||||
switch (e.keyCode) {
|
||||
|
|
@ -17,13 +18,15 @@ module.exports = function(winamp) {
|
|||
case 40: winamp.incrementVolumeBy(-1); break; // down arrow
|
||||
case 66: winamp.next(); break; // B
|
||||
case 67: winamp.pause(); break; // C
|
||||
case 76: winamp.openFileDialog(); break; // L
|
||||
// L
|
||||
case 76: winamp.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
|
||||
case 90: winamp.previous(); break; // Z
|
||||
case 96: winamp.openFileDialog(); break; // numpad 0
|
||||
// numpad 0
|
||||
case 96: winamp.dispatch({type: 'OPEN_FILE_DIALOG'}); break;
|
||||
case 97: winamp.previous(10); break; // numpad 1
|
||||
case 98: winamp.incrementVolumeBy(-1); break; // numpad 2
|
||||
case 99: winamp.next(10); break; // numpad 3
|
||||
|
|
|
|||
|
|
@ -25,13 +25,7 @@ module.exports = el('div', {id: 'main-window', class: 'loading stop'}, [
|
|||
el('div', {id: 'loading'}, 'Loading...'),
|
||||
el('div', {id: 'title-bar', class: 'selected'}, [
|
||||
el('div', {id: 'context-menu-holder'}),
|
||||
el('div', {id: 'shade-time'}, [
|
||||
el('div', {id: 'shade-minus-sign'}),
|
||||
el('div', {id: 'shade-minute-first-digit', class: 'character'}),
|
||||
el('div', {id: 'shade-minute-second-digit', class: 'character'}),
|
||||
el('div', {id: 'shade-second-first-digit', class: 'character'}),
|
||||
el('div', {id: 'shade-second-second-digit', class: 'character'})
|
||||
]),
|
||||
el('div', {id: 'shade-time-holder'}),
|
||||
el('div', {id: 'minimize'}),
|
||||
el('div', {id: 'shade'}),
|
||||
el('div', {id: 'close'})
|
||||
|
|
@ -46,19 +40,13 @@ module.exports = el('div', {id: 'main-window', class: 'loading stop'}, [
|
|||
]),
|
||||
el('div', {id: 'play-pause'}),
|
||||
el('div', {id: 'work-indicator'}),
|
||||
el('div', {id: 'time'}, [
|
||||
el('div', {id: 'minus-sign'}),
|
||||
el('div', {id: 'minute-first-digit'}),
|
||||
el('div', {id: 'minute-second-digit'}),
|
||||
el('div', {id: 'second-first-digit'}),
|
||||
el('div', {id: 'second-second-digit'})
|
||||
]),
|
||||
el('div', {id: 'time-holder'}),
|
||||
el('canvas', {id: 'visualizer', width: '152', height: '32'})
|
||||
]),
|
||||
el('div', {class: 'media-info'}, [
|
||||
el('div', {id: 'song-title', class: 'text'}),
|
||||
el('div', {id: 'kbps'}),
|
||||
el('div', {id: 'khz'}),
|
||||
el('div', {id: 'kbps-holder'}),
|
||||
el('div', {id: 'khz-holder'}),
|
||||
el('div', {class: 'mono-stereo'}, [
|
||||
el('div', {id: 'mono'}),
|
||||
el('div', {id: 'stereo'})
|
||||
|
|
@ -71,13 +59,7 @@ module.exports = el('div', {id: 'main-window', class: 'loading stop'}, [
|
|||
el('div', {id: 'playlist-button'})
|
||||
]),
|
||||
el('input', {id: 'position', type: 'range', min: '0', max: '100', step: '1', value: '0'}),
|
||||
el('div', {class: 'actions'}, [
|
||||
el('div', {id: 'previous'}),
|
||||
el('div', {id: 'play'}),
|
||||
el('div', {id: 'pause'}),
|
||||
el('div', {id: 'stop'}),
|
||||
el('div', {id: 'next'})
|
||||
]),
|
||||
el('div', {id: 'actions-holder'}),
|
||||
el('div', {id: 'eject'}),
|
||||
el('div', {class: 'shuffle-repeat'}, [
|
||||
el('div', {id: 'shuffle'}),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
import MultiDisplay from './multi-display';
|
||||
import React from 'react';
|
||||
import Marquee from './Marquee.jsx';
|
||||
import Actions from './Actions.jsx';
|
||||
import Time from './Time.jsx';
|
||||
import ShadeTime from './ShadeTime.jsx';
|
||||
import Kbps from './Kbps.jsx';
|
||||
import Khz from './Khz.jsx';
|
||||
|
||||
import '../css/main-window.css';
|
||||
|
||||
module.exports = {
|
||||
init: function(winamp) {
|
||||
|
|
@ -8,25 +16,11 @@ module.exports = {
|
|||
shade: document.getElementById('shade'),
|
||||
buttonD: document.getElementById('button-d'),
|
||||
position: document.getElementById('position'),
|
||||
volumeMessage: document.getElementById('volume-message'),
|
||||
balanceMessage: document.getElementById('balance-message'),
|
||||
positionMessage: document.getElementById('position-message'),
|
||||
songTitle: document.getElementById('song-title'),
|
||||
time: document.getElementById('time'),
|
||||
shadeTime: document.getElementById('shade-time'),
|
||||
shadeMinusSign: document.getElementById('shade-minus-sign'),
|
||||
visualizer: document.getElementById('visualizer'),
|
||||
previous: document.getElementById('previous'),
|
||||
play: document.getElementById('play'),
|
||||
pause: document.getElementById('pause'),
|
||||
stop: document.getElementById('stop'),
|
||||
next: document.getElementById('next'),
|
||||
eject: document.getElementById('eject'),
|
||||
repeat: document.getElementById('repeat'),
|
||||
shuffle: document.getElementById('shuffle'),
|
||||
volume: document.getElementById('volume'),
|
||||
kbps: document.getElementById('kbps'),
|
||||
khz: document.getElementById('khz'),
|
||||
mono: document.getElementById('mono'),
|
||||
stereo: document.getElementById('stereo'),
|
||||
balance: document.getElementById('balance'),
|
||||
|
|
@ -38,16 +32,12 @@ module.exports = {
|
|||
this.handle = document.getElementById('title-bar');
|
||||
this.body = this.nodes.window;
|
||||
|
||||
this.textDisplay = new MultiDisplay(this.nodes.songTitle);
|
||||
this.textDisplay.addRegister('songTitle');
|
||||
this.textDisplay.addRegister('position');
|
||||
this.textDisplay.addRegister('volume');
|
||||
this.textDisplay.addRegister('balance');
|
||||
this.textDisplay.addRegister('message'); // General purpose
|
||||
|
||||
this.textDisplay.showRegister('songTitle');
|
||||
|
||||
this.textDisplay.startRegisterMarquee('songTitle');
|
||||
this.winamp.renderTo(<Marquee />, document.getElementById('song-title'));
|
||||
this.winamp.renderTo(<Actions />, document.getElementById('actions-holder'));
|
||||
this.winamp.renderTo(<Time />, document.getElementById('time-holder'));
|
||||
this.winamp.renderTo(<ShadeTime />, document.getElementById('shade-time-holder'));
|
||||
this.winamp.renderTo(<Kbps />, document.getElementById('kbps-holder'));
|
||||
this.winamp.renderTo(<Khz />, document.getElementById('khz-holder'));
|
||||
|
||||
this._registerListeners();
|
||||
return this;
|
||||
|
|
@ -66,44 +56,30 @@ module.exports = {
|
|||
|
||||
this.nodes.buttonD.onmousedown = function() {
|
||||
if (self.nodes.window.classList.contains('doubled')) {
|
||||
self.textDisplay.setRegisterText('message', 'Disable doublesize mode');
|
||||
self.winamp.dispatch({type: 'SET_MARQUEE_REGISTER', register: 'message', text: 'Disable doublesize mode'});
|
||||
} else {
|
||||
self.textDisplay.setRegisterText('message', 'Enable doublesize mode');
|
||||
self.winamp.dispatch({type: 'SET_MARQUEE_REGISTER', register: 'message', text: 'Enable doublesize mode'});
|
||||
}
|
||||
self.textDisplay.showRegister('message');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'message'});
|
||||
};
|
||||
|
||||
this.nodes.buttonD.onmouseup = function() {
|
||||
self.textDisplay.showRegister('songTitle');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'songTitle'});
|
||||
};
|
||||
|
||||
this.nodes.buttonD.onclick = function() {
|
||||
self.winamp.toggleDoubledMode();
|
||||
};
|
||||
|
||||
this.nodes.play.onclick = function() {
|
||||
self.winamp.play();
|
||||
};
|
||||
|
||||
this.nodes.songTitle.onmousedown = function() {
|
||||
self.textDisplay.pauseRegisterMarquee('songTitle');
|
||||
};
|
||||
|
||||
this.nodes.songTitle.onmouseup = function() {
|
||||
setTimeout(function() {
|
||||
self.textDisplay.startRegisterMarquee('songTitle');
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
this.nodes.position.onmousedown = function() {
|
||||
if (!self.nodes.window.classList.contains('stop')){
|
||||
self.textDisplay.showRegister('position');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'position'});
|
||||
self.nodes.window.classList.add('setting-position');
|
||||
}
|
||||
};
|
||||
|
||||
this.nodes.position.onmouseup = function() {
|
||||
self.textDisplay.showRegister('songTitle');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'songTitle'});
|
||||
self.nodes.window.classList.remove('setting-position');
|
||||
};
|
||||
|
||||
|
|
@ -113,7 +89,7 @@ module.exports = {
|
|||
var newElapsed = self._timeString(self.winamp.getDuration() * newFractionComplete);
|
||||
var duration = self._timeString(self.winamp.getDuration());
|
||||
var message = 'Seek to: ' + newElapsed + '/' + duration + ' (' + newPercentComplete + '%)';
|
||||
self.textDisplay.setRegisterText('position', message);
|
||||
self.winamp.dispatch({type: 'SET_MARQUEE_REGISTER', register: 'message', text: message});
|
||||
};
|
||||
|
||||
this.nodes.position.onchange = function() {
|
||||
|
|
@ -122,24 +98,8 @@ module.exports = {
|
|||
}
|
||||
};
|
||||
|
||||
this.nodes.previous.onclick = function() {
|
||||
self.winamp.previous();
|
||||
};
|
||||
|
||||
this.nodes.next.onclick = function() {
|
||||
self.winamp.next();
|
||||
};
|
||||
|
||||
this.nodes.pause.onclick = function() {
|
||||
self.winamp.pause();
|
||||
};
|
||||
|
||||
this.nodes.stop.onclick = function() {
|
||||
self.winamp.stop();
|
||||
};
|
||||
|
||||
this.nodes.eject.onclick = function() {
|
||||
self.winamp.openFileDialog();
|
||||
self.winamp.dispatch({type: 'OPEN_FILE_DIALOG'});
|
||||
};
|
||||
|
||||
this.nodes.repeat.onclick = function() {
|
||||
|
|
@ -150,32 +110,24 @@ module.exports = {
|
|||
self.winamp.toggleShuffle();
|
||||
};
|
||||
|
||||
this.nodes.shadeTime.onclick = function() {
|
||||
self.winamp.toggleTimeMode();
|
||||
};
|
||||
|
||||
this.nodes.volume.onmousedown = function() {
|
||||
self.textDisplay.showRegister('volume');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'volume'});
|
||||
};
|
||||
|
||||
this.nodes.volume.onmouseup = function() {
|
||||
self.textDisplay.showRegister('songTitle');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'songTitle'});
|
||||
};
|
||||
|
||||
this.nodes.volume.oninput = function() {
|
||||
self.winamp.setVolume(this.value);
|
||||
};
|
||||
|
||||
this.nodes.time.onclick = function() {
|
||||
self.winamp.toggleTimeMode();
|
||||
};
|
||||
|
||||
this.nodes.balance.onmousedown = function() {
|
||||
self.textDisplay.showRegister('balance');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'balance'});
|
||||
};
|
||||
|
||||
this.nodes.balance.onmouseup = function() {
|
||||
self.textDisplay.showRegister('songTitle');
|
||||
self.winamp.dispatch({type: 'SHOW_MARQUEE_REGISTER', register: 'songTitle'});
|
||||
};
|
||||
|
||||
this.nodes.balance.oninput = function() {
|
||||
|
|
@ -204,9 +156,6 @@ module.exports = {
|
|||
window.addEventListener('stopLoading', function() {
|
||||
self.unsetLoadingState();
|
||||
});
|
||||
window.addEventListener('toggleTimeMode', function() {
|
||||
self.toggleTimeMode();
|
||||
});
|
||||
window.addEventListener('changeState', function() {
|
||||
self.changeState();
|
||||
});
|
||||
|
|
@ -271,39 +220,6 @@ module.exports = {
|
|||
updateTime: function() {
|
||||
this.updateShadePositionClass();
|
||||
this.updatePosition();
|
||||
|
||||
var shadeMinusCharacter = ' ';
|
||||
var digits = null;
|
||||
if (this.nodes.time.classList.contains('countdown')) {
|
||||
digits = this.winamp._timeObject(this.winamp.getTimeRemaining());
|
||||
shadeMinusCharacter = '-';
|
||||
} else {
|
||||
digits = this.winamp._timeObject(this.winamp.getTimeElapsed());
|
||||
}
|
||||
this.winamp.skin.font.displayCharacterInNode(shadeMinusCharacter, this.nodes.shadeMinusSign);
|
||||
|
||||
var digitNodes = [
|
||||
document.getElementById('minute-first-digit'),
|
||||
document.getElementById('minute-second-digit'),
|
||||
document.getElementById('second-first-digit'),
|
||||
document.getElementById('second-second-digit')
|
||||
];
|
||||
var shadeDigitNodes = [
|
||||
document.getElementById('shade-minute-first-digit'),
|
||||
document.getElementById('shade-minute-second-digit'),
|
||||
document.getElementById('shade-second-first-digit'),
|
||||
document.getElementById('shade-second-second-digit')
|
||||
];
|
||||
|
||||
// For each digit/node
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var digit = digits[i];
|
||||
var digitNode = digitNodes[i];
|
||||
var shadeNode = shadeDigitNodes[i];
|
||||
digitNode.innerHTML = '';
|
||||
digitNode.appendChild(this.winamp.skin.font.digitNode(digit));
|
||||
this.winamp.skin.font.displayCharacterInNode(digit, shadeNode);
|
||||
}
|
||||
},
|
||||
|
||||
setWorkingIndicator: function() {
|
||||
|
|
@ -322,11 +238,6 @@ module.exports = {
|
|||
this.nodes.window.classList.remove('loading');
|
||||
},
|
||||
|
||||
toggleTimeMode: function() {
|
||||
this.nodes.time.classList.toggle('countdown');
|
||||
this.updateTime();
|
||||
},
|
||||
|
||||
updateVolume: function() {
|
||||
var volume = this.winamp.getVolume();
|
||||
var percent = volume / 100;
|
||||
|
|
@ -335,7 +246,7 @@ module.exports = {
|
|||
this.nodes.volume.style.backgroundPosition = '0 -' + offset + 'px';
|
||||
|
||||
var message = 'Volume: ' + volume + '%';
|
||||
this.textDisplay.setRegisterText('volume', message);
|
||||
this.winamp.dispatch({type: 'SET_MARQUEE_REGISTER', register: 'volume', text: message});
|
||||
|
||||
// This shouldn't trigger an infinite loop with volume.onchange(),
|
||||
// since the value will be the same
|
||||
|
|
@ -352,7 +263,7 @@ module.exports = {
|
|||
} else {
|
||||
string = 'Balance: ' + Math.abs(balance) + '% Left';
|
||||
}
|
||||
this.textDisplay.setRegisterText('balance', string);
|
||||
this.winamp.dispatch({type: 'SET_MARQUEE_REGISTER', register: 'balance', text: string});
|
||||
balance = Math.abs(balance) / 100;
|
||||
var sprite = Math.round(balance * 28);
|
||||
var offset = (sprite - 1) * 15;
|
||||
|
|
@ -375,7 +286,7 @@ module.exports = {
|
|||
updateTitle: function() {
|
||||
var duration = this._timeString(this.winamp.getDuration());
|
||||
var name = this.winamp.fileName + ' (' + duration + ') *** ';
|
||||
this.textDisplay.setRegisterText('songTitle', name);
|
||||
this.winamp.dispatch({type: 'SET_MARQUEE_REGISTER', register: 'songTitle', text: name});
|
||||
},
|
||||
|
||||
updateChannelCount: function() {
|
||||
|
|
|
|||
30
js/main.js
30
js/main.js
|
|
@ -1,5 +1,9 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {render} from 'react-dom';
|
||||
import {Provider} from 'react-redux';
|
||||
import {createStore} from 'redux';
|
||||
|
||||
import createReducer from './reducers';
|
||||
|
||||
import Browser from './browser';
|
||||
import mainWindowDom from './main-window-dom';
|
||||
|
|
@ -7,15 +11,27 @@ import Winamp from './winamp';
|
|||
import ContextMenu from './ContextMenu.jsx';
|
||||
import Hotkeys from './hotkeys';
|
||||
|
||||
import '../css/winamp.css';
|
||||
import '../css/main-window.css';
|
||||
|
||||
if (new Browser(window).isCompatible) {
|
||||
var mainWindowElement = document.createElement('div');
|
||||
mainWindowElement.appendChild(mainWindowDom);
|
||||
document.getElementById('winamp2-js').appendChild(mainWindowElement);
|
||||
|
||||
var winamp = Winamp.init({
|
||||
var winamp = Winamp;
|
||||
let store = createStore(createReducer(winamp), window.devToolsExtension && window.devToolsExtension());
|
||||
|
||||
// TODO: Remove this workaround
|
||||
winamp.renderTo = (componant, node) => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
{componant}
|
||||
</Provider>,
|
||||
node
|
||||
);
|
||||
};
|
||||
|
||||
winamp.dispatch = store.dispatch;
|
||||
|
||||
winamp.init({
|
||||
volume: 50,
|
||||
balance: 0,
|
||||
mediaFile: {
|
||||
|
|
@ -25,8 +41,10 @@ if (new Browser(window).isCompatible) {
|
|||
skinUrl: 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/base-2.91.wsz'
|
||||
});
|
||||
|
||||
|
||||
|
||||
new Hotkeys(winamp);
|
||||
ReactDOM.render(<ContextMenu winamp={winamp} />, document.getElementById('context-menu-holder'));
|
||||
winamp.renderTo(<ContextMenu />, document.getElementById('context-menu-holder'));
|
||||
} else {
|
||||
document.getElementById('winamp').style.display = 'none';
|
||||
document.getElementById('browser-compatibility').style.display = 'block';
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
// Single line text display that can animate and hold multiple registers
|
||||
import Font from './font';
|
||||
var MultiDisplay = function(node) {
|
||||
this.font = new Font();
|
||||
this.node = node;
|
||||
this.registers = {};
|
||||
this._marqueeLoop();
|
||||
};
|
||||
|
||||
MultiDisplay.prototype.addRegister = function(key) {
|
||||
// Create element node
|
||||
var register = document.createElement('div');
|
||||
register.style.display = 'none';
|
||||
|
||||
this.node.appendChild(register);
|
||||
this.registers[key] = {
|
||||
node: register,
|
||||
text: '',
|
||||
marquee: false
|
||||
};
|
||||
};
|
||||
|
||||
// Set text of register
|
||||
MultiDisplay.prototype.setRegisterText = function(register, text) {
|
||||
this.font.setNodeToString(this.registers[register].node, text);
|
||||
};
|
||||
|
||||
MultiDisplay.prototype.showRegister = function(showKey) {
|
||||
for (var key in this.registers) {
|
||||
if (this.registers.hasOwnProperty(key)) {
|
||||
var display = (key === showKey) ? 'block' : 'none';
|
||||
this.registers[key].node.style.display = display;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MultiDisplay.prototype.startRegisterMarquee = function(key) {
|
||||
this.registers[key].marquee = true;
|
||||
};
|
||||
|
||||
MultiDisplay.prototype.pauseRegisterMarquee = function(key) {
|
||||
this.registers[key].marquee = false;
|
||||
};
|
||||
|
||||
MultiDisplay.prototype._marqueeLoop = function() {
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
for (var key in self.registers) {
|
||||
// Check every register to see if it needs to be marqueed
|
||||
if (self.registers[key].marquee) {
|
||||
var text = self.registers[key].node.firstChild;
|
||||
// Only scroll if the text is too long
|
||||
if (text && text.childNodes.length > 30) {
|
||||
var characterNode = text.firstChild;
|
||||
text.removeChild(characterNode);
|
||||
text.appendChild(characterNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
self._marqueeLoop();
|
||||
}, 220);
|
||||
};
|
||||
|
||||
module.exports = MultiDisplay;
|
||||
144
js/reducers.js
Normal file
144
js/reducers.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import MyFile from './my-file';
|
||||
import {combineReducers} from 'redux';
|
||||
|
||||
const register = (state, action) => {
|
||||
if (!state) {
|
||||
return {
|
||||
id: '',
|
||||
step: 0,
|
||||
text: ''
|
||||
};
|
||||
}
|
||||
switch (action.type) {
|
||||
case 'SET_MARQUEE_REGISTER':
|
||||
if (state.id === action.register) {
|
||||
return Object.assign({}, state, {step: 0, text: action.text});
|
||||
}
|
||||
return state;
|
||||
case 'STEP_MARQUEE':
|
||||
return Object.assign({}, state, {step: (state.step + 1) % state.text.length});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const marquee = (state, action) => {
|
||||
if (!state) {
|
||||
return {
|
||||
stepping: true,
|
||||
selectedRegister: 'songTitle',
|
||||
registers: [
|
||||
Object.assign(register(), {id: 'songTitle'}),
|
||||
Object.assign(register(), {id: 'position'}),
|
||||
Object.assign(register(), {id: 'volume'}),
|
||||
Object.assign(register(), {id: 'balance'}),
|
||||
Object.assign(register(), {id: 'message'})
|
||||
]
|
||||
};
|
||||
}
|
||||
switch (action.type) {
|
||||
case 'SET_MARQUEE_REGISTER':
|
||||
return Object.assign({}, state, {registers: state.registers.map(r => register(r, action))});
|
||||
case 'STEP_MARQUEE':
|
||||
if (state.stepping) {
|
||||
return Object.assign({}, state, {registers: state.registers.map(r => register(r, action))});
|
||||
}
|
||||
return state;
|
||||
case 'SHOW_MARQUEE_REGISTER':
|
||||
if (state.stepping) {
|
||||
return Object.assign({}, state, {selectedRegister: action.register});
|
||||
}
|
||||
return state;
|
||||
case 'PAUSE_MARQUEE':
|
||||
return Object.assign({}, state, {stepping: false});
|
||||
case 'START_MARQUEE':
|
||||
return Object.assign({}, state, {stepping: true});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const contextMenu = (state, action) => {
|
||||
if (!state) {
|
||||
return {
|
||||
selected: false
|
||||
};
|
||||
}
|
||||
switch (action.type) {
|
||||
case 'TOGGLE_CONTEXT_MENU':
|
||||
return Object.assign({}, state, {selected: !state.selected});
|
||||
case 'CLOSE_CONTEXT_MENU':
|
||||
return Object.assign({}, state, {selected: false});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const media = (state, action) => {
|
||||
if (!state) {
|
||||
return {
|
||||
timeMode: 'ELAPSED',
|
||||
timeElapsed: 0,
|
||||
length: null,
|
||||
kbps: null,
|
||||
khz: null
|
||||
};
|
||||
}
|
||||
switch (action.type) {
|
||||
case 'TOGGLE_TIME_MODE':
|
||||
const newMode = state.timeMode === 'REMAINING' ? 'ELAPSED' : 'REMAINING';
|
||||
return Object.assign({}, state, {timeMode: newMode});
|
||||
case 'UPDATE_TIME_ELAPSED':
|
||||
return Object.assign({}, state, {timeElapsed: action.elapsed});
|
||||
case 'SET_MEDIA_LENGTH':
|
||||
return Object.assign({}, state, {length: action.length});
|
||||
case 'SET_MEDIA_KBPS':
|
||||
return Object.assign({}, state, {kbps: action.kbps});
|
||||
case 'SET_MEDIA_KHZ':
|
||||
return Object.assign({}, state, {khz: action.khz});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const createReducer = (winamp) => {
|
||||
|
||||
const reducer = combineReducers({
|
||||
marquee,
|
||||
contextMenu,
|
||||
media
|
||||
});
|
||||
|
||||
// Add in the temporary actions that don't modify the state.
|
||||
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 '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;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default createReducer;
|
||||
|
|
@ -94,8 +94,8 @@ module.exports = [
|
|||
{
|
||||
img: 'NUMS_EX',
|
||||
sprites: [
|
||||
{selectors: ['#time.ex #minus-sign'], x: 90, y: 0, width: 9, height: 13},
|
||||
{selectors: ['#time.ex.countdown #minus-sign'], x: 99, y: 0, width: 9, height: 13},
|
||||
{selectors: ['#time #minus-sign'], x: 90, y: 0, width: 9, height: 13},
|
||||
{selectors: ['#time.countdown #minus-sign'], x: 99, y: 0, width: 9, height: 13},
|
||||
{selectors: ['.digit-0'], x: 0, y: 0, width: 9, height: 13},
|
||||
{selectors: ['.digit-1'], x: 9, y: 0, width: 9, height: 13},
|
||||
{selectors: ['.digit-2'], x: 18, y: 0, width: 9, height: 13},
|
||||
|
|
|
|||
18
js/skin.js
18
js/skin.js
|
|
@ -1,11 +1,9 @@
|
|||
// Dynamically set the css background images for all the sprites
|
||||
import SKIN_SPRITES from './skin-sprites';
|
||||
import Font from './font';
|
||||
import Visualizer from './visualizer';
|
||||
import JSZip from '../node_modules/jszip/dist/jszip'; // Hack
|
||||
|
||||
module.exports = {
|
||||
font: new Font(),
|
||||
init: function(visualizerNode, analyser) {
|
||||
this._createNewStyleNode();
|
||||
this.visualizer = Visualizer.init(visualizerNode, analyser);
|
||||
|
|
@ -25,18 +23,11 @@ module.exports = {
|
|||
// Gets passed as a callback, so don't have access to `this`
|
||||
_setSkinByBuffer: function(buffer) {
|
||||
var zip = new JSZip(buffer);
|
||||
document.getElementById('time').classList.remove('ex');
|
||||
|
||||
var promisedCssRules = this._skinSprites.map(function(spriteObj) {
|
||||
|
||||
var file = this._findFileInZip(spriteObj.img, zip);
|
||||
if (file) {
|
||||
// CSS has to change if this file is present
|
||||
if (spriteObj.img === 'NUMS_EX') {
|
||||
document.getElementById('time').classList.add('ex');
|
||||
}
|
||||
var src = 'data:image/bmp;base64,' + btoa(file.asBinary());
|
||||
return this._spriteCssRule(src, spriteObj);
|
||||
return this._spriteCssRule(spriteObj.img, src, spriteObj);
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
|
@ -85,7 +76,7 @@ module.exports = {
|
|||
|
||||
// Given an image URL and coordinates, returns a data url for a sub-section
|
||||
// of that image
|
||||
_spriteCssRule: function(src, spriteObj) {
|
||||
_spriteCssRule: function(img, src, spriteObj) {
|
||||
return new Promise(function(resolve) {
|
||||
var imageObj = new Image();
|
||||
imageObj.src = src;
|
||||
|
|
@ -105,6 +96,11 @@ module.exports = {
|
|||
cssRules += '#winamp2-js ' + selector + '{' + value + '}\n';
|
||||
});
|
||||
});
|
||||
if (img === 'NUMS_EX') {
|
||||
// This alternate number file requires that the minus sign be
|
||||
// formatted differently.
|
||||
cssRules += '#winamp2-js .status #time #minus-sign { top: 0px; left: -1px; width: 9px; height: 13px; }\n';
|
||||
}
|
||||
resolve(cssRules);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
16
js/utils.js
Normal file
16
js/utils.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const getTimeObj = (time) => {
|
||||
var minutes = Math.floor(time / 60);
|
||||
var seconds = time % 60;
|
||||
|
||||
return {
|
||||
minutesFirstDigit: Math.floor(minutes / 10),
|
||||
minutesSecondDigit: Math.floor(minutes % 10),
|
||||
secondsFirstDigit: Math.floor(seconds / 10),
|
||||
secondsSecondDigit: Math.floor(seconds % 10)
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getTimeObj
|
||||
};
|
||||
|
||||
14
js/winamp.js
14
js/winamp.js
|
|
@ -5,6 +5,8 @@ import Skin from './skin';
|
|||
import Media from './media';
|
||||
import MyFile from './my-file';
|
||||
|
||||
import '../css/winamp.css';
|
||||
|
||||
module.exports = {
|
||||
init: function(options) {
|
||||
this.fileInput = document.createElement('input');
|
||||
|
|
@ -24,7 +26,6 @@ module.exports = {
|
|||
stopWaiting: new Event('stopWaiting'),
|
||||
startLoading: new Event('startLoading'),
|
||||
stopLoading: new Event('stopLoading'),
|
||||
toggleTimeMode: new Event('toggleTimeMode'),
|
||||
changeState: new Event('changeState'),
|
||||
titleUpdated: new Event('titleUpdated'),
|
||||
channelCountUpdated: new Event('channelCountUpdated'),
|
||||
|
|
@ -53,6 +54,8 @@ module.exports = {
|
|||
this.windowManager.registerWindow(this.mainWindow);
|
||||
|
||||
this.media.addEventListener('timeupdate', function() {
|
||||
self.dispatch({type: 'UPDATE_TIME_ELAPSED', elapsed: self.media.timeElapsed()});
|
||||
// Legacy
|
||||
window.dispatchEvent(self.events.timeUpdated);
|
||||
});
|
||||
|
||||
|
|
@ -120,10 +123,6 @@ module.exports = {
|
|||
this.media.seekToPercentComplete(percent);
|
||||
},
|
||||
|
||||
toggleTimeMode: function() {
|
||||
window.dispatchEvent(this.events.toggleTimeMode);
|
||||
},
|
||||
|
||||
play: function() {
|
||||
if (this.getState() === 'play'){
|
||||
this.media.stop();
|
||||
|
|
@ -257,11 +256,12 @@ module.exports = {
|
|||
function setMetaData() {
|
||||
var kbps = '128';
|
||||
var khz = Math.round(this.media.sampleRate() / 1000).toString();
|
||||
this.skin.font.setNodeToString(document.getElementById('kbps'), kbps);
|
||||
this.skin.font.setNodeToString(document.getElementById('khz'), khz);
|
||||
this.dispatch({type: 'SET_MEDIA_KBPS', kbps: kbps});
|
||||
this.dispatch({type: 'SET_MEDIA_KHZ', khz: khz});
|
||||
window.dispatchEvent(this.events.channelCountUpdated);
|
||||
window.dispatchEvent(this.events.titleUpdated);
|
||||
window.dispatchEvent(this.events.timeUpdated);
|
||||
this.dispatch({type: 'SET_MEDIA_LENGTH', length: this.media.duration()});
|
||||
}
|
||||
|
||||
// Note, this will not happen right away
|
||||
|
|
|
|||
|
|
@ -34,15 +34,19 @@
|
|||
"css-loader": "^0.23.1",
|
||||
"eslint": "2.*",
|
||||
"eslint-plugin-react": "^5.2.2",
|
||||
"file-loader": "^0.9.0",
|
||||
"gzip-size-cli": "^1.0.0",
|
||||
"pretty-bytes-cli": "^1.0.0",
|
||||
"style-loader": "^0.13.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-plugin-transform-react-jsx": "^6.8.0",
|
||||
"jszip": "^2.6.0",
|
||||
"react": "^15.2.0",
|
||||
"react-dom": "^15.2.0"
|
||||
"react-dom": "^15.2.0",
|
||||
"react-redux": "^4.4.5",
|
||||
"redux": "^3.5.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ module.exports = {
|
|||
test: /\.css$/,
|
||||
loader: 'style-loader!css-loader'
|
||||
},
|
||||
{
|
||||
test: /\.png$/i,
|
||||
loader: 'url-loader?limit=100000'
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue