From 9d734419894055fac8f40461ca09cc1b8a18f0ea Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 6 Jul 2016 18:06:43 -0700 Subject: [PATCH] Introduce first taste of React Future PRs will move more code to React, but this seemed like a good place to start. --- .babelrc | 3 + .eslintrc | 178 ++++++++++++++++++++++-------------------- js/ContextMenu.jsx | 83 ++++++++++++++++++++ js/context.js | 45 ----------- js/main-window-dom.js | 26 +----- js/main.js | 8 +- package.json | 18 +++-- webpack.config.js | 7 +- 8 files changed, 200 insertions(+), 168 deletions(-) create mode 100644 .babelrc create mode 100644 js/ContextMenu.jsx delete mode 100644 js/context.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..05786794 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["react", "es2015"] +} diff --git a/.eslintrc b/.eslintrc index 8f853407..3a677e6b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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" } } diff --git a/js/ContextMenu.jsx b/js/ContextMenu.jsx new file mode 100644 index 00000000..5589e055 --- /dev/null +++ b/js/ContextMenu.jsx @@ -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
+ +
; + } +} + +module.exports = ContextMenu; diff --git a/js/context.js b/js/context.js deleted file mode 100644 index 46cfea28..00000000 --- a/js/context.js +++ /dev/null @@ -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; diff --git a/js/main-window-dom.js b/js/main-window-dom.js index 35df3654..b206fd59 100644 --- a/js/main-window-dom.js +++ b/js/main-window-dom.js @@ -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'}, ''), - 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'}), diff --git a/js/main.js b/js/main.js index 0ed1e5f9..6b69c92b 100644 --- a/js/main.js +++ b/js/main.js @@ -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(, document.getElementById('context-menu-holder')); } else { document.getElementById('winamp').style.display = 'none'; document.getElementById('browser-compatibility').style.display = 'block'; diff --git a/package.json b/package.json index 5e68e081..9c781c84 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/webpack.config.js b/webpack.config.js index 2eca6e9e..93cd3628 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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: [