Introduce first taste of React

Future PRs will move more code to React, but this seemed like a good place to
start.
This commit is contained in:
Jordan Eldredge 2016-07-06 18:06:43 -07:00
parent 6a3e0bf214
commit 9d73441989
8 changed files with 200 additions and 168 deletions

3
.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": ["react", "es2015"]
}

178
.eslintrc
View file

@ -1,9 +1,18 @@
{
"parserOptions": {
"ecmaVersion": 6
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
},
},
"ecmaFeatures": {
"modules": true
"plugins": [
"react"
],
"settings": {
"react": {
"version": "15.2"
}
},
"env": {
"browser": true,
@ -13,86 +22,87 @@
},
"rules": {
"block-scoped-var": 1,
"brace-style": [1, "1tbs"],
"camelcase": 2,
"comma-dangle": [2, "never"],
"comma-spacing": 2,
"consistent-return": 1,
"dot-notation": [2, { "allowKeywords": false }],
"eol-last": 2,
"eqeqeq": [2, "smart"],
"guard-for-in": 2,
"indent": [2, 2, {"SwitchCase": 1}],
"key-spacing": 1,
"linebreak-style": 2,
"max-depth": [1, 4],
"max-params": [1, 5],
"new-cap": 2,
"no-alert": 2,
"no-caller": 2,
"no-catch-shadow": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 1,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 1,
"no-empty-character-class": 2,
"no-empty-label": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 1,
"no-extra-boolean-cast": 2,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inner-declarations": 2,
"no-irregular-whitespace": 2,
"no-label-var": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-multi-spaces": 1,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-negated-in-lhs": 1,
"no-nested-ternary": 2,
"no-new-object": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-shadow": 2,
"no-spaced-func": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-undefined": 2,
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-expressions": 2,
"no-unused-vars": 2,
"no-use-before-define": [2, "nofunc"],
"no-with": 2,
"quote-props": [1, "consistent-as-needed"],
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": 2,
"space-after-keywords": [2, "always"],
"space-before-keywords": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
"object-curly-spacing": [2, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"use-isnan": 2,
"valid-typeof": 2,
"wrap-iife": 2
"block-scoped-var": "warn",
"brace-style": ["warn", "1tbs"],
"camelcase": "error",
"comma-dangle": ["error", "never"],
"comma-spacing": "error",
"consistent-return": "warn",
"dot-notation": ["error", { "allowKeywords": false }],
"eol-last": "error",
"eqeqeq": ["error", "smart"],
"guard-for-in": "error",
"indent": ["error", 2, {"SwitchCase": 1}],
"key-spacing": "warn",
"linebreak-style": "error",
"max-depth": ["warn", 4],
"max-params": ["warn", 5],
"new-cap": "error",
"no-alert": "error",
"no-caller": "error",
"no-catch-shadow": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-div-regex": "warn",
"no-dupe-args": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-else-return": "warn",
"no-empty-character-class": "error",
"no-labels": "error",
"no-eval": "error",
"no-ex-assign": "error",
"no-extend-native": "warn",
"no-extra-boolean-cast": "error",
"no-extra-semi": "error",
"no-fallthrough": "error",
"no-floating-decimal": "error",
"no-func-assign": "error",
"no-implied-eval": "error",
"no-inner-declarations": "error",
"no-irregular-whitespace": "error",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-multi-spaces": "warn",
"no-multi-str": "error",
"no-native-reassign": "error",
"no-negated-in-lhs": "warn",
"no-nested-ternary": "error",
"no-new-object": "error",
"no-new-wrappers": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-proto": "error",
"no-redeclare": "error",
"no-shadow": "error",
"no-spaced-func": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-undef-init": "error",
"no-undefined": "error",
"no-unneeded-ternary": "error",
"no-unreachable": "error",
"no-unused-expressions": "error",
"no-unused-vars": "error",
"no-use-before-define": ["error", "nofunc"],
"no-with": "error",
"object-curly-spacing": ["error", "never"],
"quote-props": ["warn", "consistent-as-needed"],
"quotes": ["error", "single", "avoid-escape"],
"radix": "error",
"react/jsx-uses-react": "error",
"react/jsx-no-bind": "error",
"react/jsx-uses-vars": "error",
"semi": "error",
"keyword-spacing": "error",
"space-before-function-paren": ["error", {"anonymous": "never", "named": "never"}],
"space-infix-ops": "error",
"space-unary-ops": ["error", { "words": true, "nonwords": false }],
"use-isnan": "error",
"valid-typeof": "error",
"wrap-iife": "error"
}
}

83
js/ContextMenu.jsx Normal file
View file

@ -0,0 +1,83 @@
import React from 'react';
import MyFile from './my-file';
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);
this.closeMenu = this.closeMenu.bind(this);
this.setSkin = this.setSkin.bind(this);
}
componantWillMount() {
// Clicking anywhere outside the context menu will close the window
document.addEventListener('click', this.closeMenu);
}
componantWillUnmount() {
document.removeEventListener('click', this.closeMenu);
}
openFileDialog() {
this.props.winamp.openFileDialog();
}
close() {
// Close all of Winamp
this.props.winamp.close();
}
closeMenu() {
this.setState({selected: false});
}
toggleMenu(event) {
this.setState({selected: !this.state.selected});
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);
}
render() {
var classes = this.state.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>
<li className='hr'><hr /></li>
<li id='context-play-file' onClick={this.openFileDialog}>Play File...</li>
<li className='parent'>
<ul>
<li id='context-load-skin' onClick={this.openFileDialog}>Load Skin...</li>
<li className='hr'><hr /></li>
<li data-filename='base-2.91.wsz' onClick={this.setSkin} >{'<Base Skin>'}</li>
<li data-filename='MacOSXAqua1-5.wsz' onClick={this.setSkin} >{'Mac OSX v1.5 (Aqua)'}</li>
<li data-filename='TopazAmp1-2.wsz' onClick={this.setSkin} >{'TopazAmp'}</li>
<li data-filename='Vizor1-01.wsz' onClick={this.setSkin} >{'Vizor'}</li>
<li data-filename='XMMS-Turquoise.wsz' onClick={this.setSkin} >{'XMMS Turquoise '}</li>
<li data-filename='ZaxonRemake1-0.wsz' onClick={this.setSkin} >{'Zaxon Remake'}</li>
</ul>
Skins
</li>
<li className='hr'><hr /></li>
<li id='context-exit' onClick={this.close}>Exit</li>
</ul>
</div>;
}
}
module.exports = ContextMenu;

View file

@ -1,45 +0,0 @@
import MyFile from './my-file';
var Context = function(winamp) {
this.winamp = winamp;
var el = {
option: document.getElementById('option'),
playFile: document.getElementById('context-play-file'),
loadSkin: document.getElementById('context-load-skin'),
exit: document.getElementById('context-exit'),
skinOptions: document.getElementsByClassName('skin-select')
};
// Click anywhere to close the context menu
document.addEventListener('click', function() {
el.option.classList.remove('selected');
});
// Click the context menu to close it
el.option.addEventListener('click', function(e) {
el.option.classList.toggle('selected');
e.stopPropagation();
});
// Bind to each of the various skin options
for (var i = 0; i < el.skinOptions.length; i++) {
el.skinOptions[i].addEventListener('click', function(e) {
this.loadSkin(e.target.dataset.skinUrl);
}.bind(this));
}
// Play file and load skin both just spawn the file opener
el.playFile.addEventListener('click', winamp.openFileDialog);
el.loadSkin.addEventListener('click', winamp.openFileDialog);
// Close all of Winamp
el.exit.addEventListener('click', winamp.close);
};
Context.prototype.loadSkin = function(url) {
var skinFile = new MyFile();
skinFile.setUrl(url);
this.winamp.setSkin(skinFile);
};
module.exports = Context;

View file

@ -24,31 +24,7 @@ function el(tagName, attributes, content) {
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: 'option'}, [
el('ul', {id: 'context-menu'}, [
el('li', {}, [
el('a', {href: 'https://github.com/captbaritone/winamp2-js', target: '_blank'}, 'Winamp2-js...')
]),
el('li', {class: 'hr'}, [ el('hr') ]),
el('li', {id: 'context-play-file'}, 'Play File...'),
el('li', {class: 'parent'}, [
el('ul', {}, [
el('li', {id: 'context-load-skin'}, 'Load Skin...'),
el('li', {class: 'hr'}, [ el('hr') ]),
el('li', {'class': 'skin-select', 'data-skin-url': 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/base-2.91.wsz'}, '<Base Skin>'),
el('li', {'class': 'skin-select', 'data-skin-url': 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/MacOSXAqua1-5.wsz'}, 'Mac OSX v1.5 (Aqua)'),
el('li', {'class': 'skin-select', 'data-skin-url': 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/TopazAmp1-2.wsz'}, 'TopazAmp'),
el('li', {'class': 'skin-select', 'data-skin-url': 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/Vizor1-01.wsz'}, 'Vizor'),
el('li', {'class': 'skin-select', 'data-skin-url': 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/XMMS-Turquoise.wsz'}, 'XMMS Turquoise '),
el('li', {'class': 'skin-select', 'data-skin-url': 'https://cdn.rawgit.com/captbaritone/winamp-skins/master/v2/ZaxonRemake1-0.wsz'}, 'Zaxon Remake')
// More here
]),
'Skins'
]),
el('li', {class: 'hr'}, [ el('hr') ]),
el('li', {id: 'context-exit'}, 'Exit')
])
]),
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'}),

View file

@ -1,12 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Browser from './browser';
import mainWindowDom from './main-window-dom';
import Winamp from './winamp';
import Context from './context';
import ContextMenu from './ContextMenu.jsx';
import Hotkeys from './hotkeys';
import '../css/winamp.css';
import '../css/main-window.css';
import '../css/context-menu.css';
if (new Browser(window).isCompatible) {
var mainWindowElement = document.createElement('div');
@ -24,7 +26,7 @@ if (new Browser(window).isCompatible) {
});
new Hotkeys(winamp);
new Context(winamp);
ReactDOM.render(<ContextMenu winamp={winamp} />, document.getElementById('context-menu-holder'));
} else {
document.getElementById('winamp').style.display = 'none';
document.getElementById('browser-compatibility').style.display = 'block';

View file

@ -4,7 +4,7 @@
"description": "Winamp 2 implemented in HTML5 and JavaScript",
"main": "index.html",
"scripts": {
"lint": "eslint js/*.js",
"lint": "eslint js/*.js js/*.jsx",
"build": "webpack --optimize-minimize",
"serve": "webpack-dev-server",
"weight": "npm run build && gzip-size built/winamp.js | pretty-bytes"
@ -26,17 +26,23 @@
},
"homepage": "https://github.com/captbaritone/winamp2-js#readme",
"devDependencies": {
"babel-cli": "^6.10.1",
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.11.1",
"css-loader": "^0.23.1",
"eslint": "1.*",
"style-loader": "^0.13.1",
"webpack": "^1.13.0",
"eslint": "2.*",
"eslint-plugin-react": "^5.2.2",
"gzip-size-cli": "^1.0.0",
"pretty-bytes-cli": "^1.0.0"
"pretty-bytes-cli": "^1.0.0",
"style-loader": "^0.13.1",
"webpack": "^1.13.0"
},
"dependencies": {
"jszip": "^2.6.0"
"babel-plugin-transform-react-jsx": "^6.8.0",
"jszip": "^2.6.0",
"react": "^15.2.0",
"react-dom": "^15.2.0"
}
}

View file

@ -11,12 +11,9 @@ module.exports = {
loader: 'style-loader!css-loader'
},
{
test: /\.js$/,
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel', // 'babel-loader' is also a legal name to reference
query: {
presets: ['es2015']
}
loader: 'babel' // 'babel-loader' is also a legal name to reference
}
],
noParse: [