├── app ├── api │ └── .gitkeep ├── components │ ├── Video │ │ ├── video.module.css │ │ └── index.js │ ├── MediaList │ │ ├── mediaList.module.css │ │ └── index.js │ ├── MediaSingle │ │ ├── single.module.css │ │ └── index.js │ ├── Checkout │ │ ├── checkout.module.css │ │ └── index.js │ ├── SearchBar │ │ ├── searchBar.module.css │ │ └── index.js │ ├── MediaInfo │ │ ├── mediaInfo.module.css │ │ └── index.js │ ├── MediaItem │ │ ├── item.module.css │ │ └── index.js │ ├── ShoppingWindow │ │ ├── shoppingWindow.module.css │ │ └── index.js │ ├── TopHeaderBar │ │ ├── topHeaderBar.module.css │ │ └── index.js │ ├── MediaActions │ │ ├── mediaActions.module.css │ │ └── index.js │ ├── Header │ │ ├── header.module.css │ │ └── index.js │ └── ProgressLoader │ │ └── index.js ├── app.icns ├── imgs │ ├── logo.png │ └── TvChickenlogo.png ├── styles │ ├── effects.module.css │ ├── typography.module.css │ ├── colors.module.css │ └── layout.module.css ├── app.css ├── store │ ├── configureStore.js │ ├── configureStore.production.js │ └── configureStore.development.js ├── hot-dev-app.html ├── constants │ └── index.js ├── app.html ├── containers │ ├── DevTools.js │ ├── SinglePage.js │ ├── App.js │ └── HomePage.js ├── routes.js ├── utils │ └── copy.js ├── index.js ├── reducers │ └── index.js └── actions │ └── index.js ├── .gitattributes ├── test ├── .eslintrc ├── setup.js ├── actions │ └── media.spec.js └── e2e.js ├── erb-logo.png ├── .babelrc ├── .editorconfig ├── .eslintrc ├── webpack.config.base.js ├── .travis.yml ├── .gitignore ├── server.js ├── LICENSE ├── webpack.config.development.js ├── webpack.config.production.js ├── package.js ├── README.md ├── CHANGELOG.md ├── package.json └── main.js /app/api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /app/components/Video/video.module.css: -------------------------------------------------------------------------------- 1 | .video{ 2 | width:40em; 3 | } 4 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicallan/tv-chicken/HEAD/app/app.icns -------------------------------------------------------------------------------- /erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicallan/tv-chicken/HEAD/erb-logo.png -------------------------------------------------------------------------------- /app/components/MediaList/mediaList.module.css: -------------------------------------------------------------------------------- 1 | .media { 2 | padding-top: 1em; 3 | } 4 | -------------------------------------------------------------------------------- /app/components/MediaSingle/single.module.css: -------------------------------------------------------------------------------- 1 | .single{ 2 | padding-top: 6em; 3 | } 4 | -------------------------------------------------------------------------------- /app/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicallan/tv-chicken/HEAD/app/imgs/logo.png -------------------------------------------------------------------------------- /app/styles/effects.module.css: -------------------------------------------------------------------------------- 1 | .bevel{ border-radius: 4px} 2 | .bevel-lg{border-radius: 8px} 3 | -------------------------------------------------------------------------------- /app/imgs/TvChickenlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epicallan/tv-chicken/HEAD/app/imgs/TvChickenlogo.png -------------------------------------------------------------------------------- /app/components/Checkout/checkout.module.css: -------------------------------------------------------------------------------- 1 | .checkout{ 2 | composes: blue from '../../styles/colors.module.css'; 3 | } 4 | -------------------------------------------------------------------------------- /app/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow-x: hidden; 3 | color: white; 4 | background-color: #370F2A; 5 | overflow-x: hidden; 6 | width: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /app/styles/typography.module.css: -------------------------------------------------------------------------------- 1 | .font{ 2 | font-family: "Open Sans"; 3 | font-smoothing: antialiased; 4 | } 5 | .subTitle { 6 | text-transform: capitalize; 7 | font-size: 1.1em; 8 | } 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["add-module-exports"], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /app/components/SearchBar/searchBar.module.css: -------------------------------------------------------------------------------- 1 | .navbar-form{ 2 | color: #B19898; 3 | margin-left: -10%; 4 | margin-top: 5em; 5 | margin-bottom: 3em; 6 | } 7 | .navbar-form input{ 8 | width: 115% 9 | } 10 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.production'); 3 | } else { 4 | module.exports = require('./configureStore.development'); 5 | } 6 | -------------------------------------------------------------------------------- /app/styles/colors.module.css: -------------------------------------------------------------------------------- 1 | .color{ color: #fff } 2 | .primary{ color: #370F2A } 3 | .secondry {color: #B5D100} 4 | .blue {color: #316FA8} 5 | .background-primary {background-color: #370F2A} 6 | .background-secondry {background-color: #B5D100} 7 | -------------------------------------------------------------------------------- /app/components/MediaInfo/mediaInfo.module.css: -------------------------------------------------------------------------------- 1 | .title{ 2 | composes: secondry from '../../styles/colors.module.css'; 3 | text-transform: capitalize; 4 | } 5 | .heading{ 6 | composese:title; 7 | font-size: 1.4em; 8 | } 9 | .subHeading{ 10 | composese:title; 11 | font-size: 1.2em; 12 | } 13 | -------------------------------------------------------------------------------- /app/styles/layout.module.css: -------------------------------------------------------------------------------- 1 | .ontop{ 2 | position: absolute; 3 | z-index: 999 4 | } 5 | .circular{ 6 | border-radius: 0px 27px 121px 83px; 7 | } 8 | .thumbnail { 9 | display: block; 10 | height: auto; 11 | width: auto; 12 | max-width: 300px; 13 | max-height: 300px; 14 | margin: 0 auto; 15 | } 16 | -------------------------------------------------------------------------------- /app/components/MediaItem/item.module.css: -------------------------------------------------------------------------------- 1 | .mediaImage { 2 | composes: thumbnail from '../../styles/layout.module.css'; 3 | composes: bevel from '../../styles/effects.module.css'; 4 | background: #fff; 5 | } 6 | .title { 7 | composes: subTitle from '../../styles/typography.module.css'; 8 | font-size: 1.1em; 9 | } 10 | -------------------------------------------------------------------------------- /app/hot-dev-app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tv Chicken 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/constants/index.js: -------------------------------------------------------------------------------- 1 | export const RECEIVE_DATA = 'RECEIVE_DATA'; 2 | export const DOWNLOAD = 'DOWNLOAD'; 3 | export const REQUEST_DATA = 'REQUEST_DATA'; 4 | export const VIEW_ALL = 'VIEW_ALL'; 5 | export const VIEW_SELECTED = 'VIEW_SELECTED'; 6 | export const AddToCart = 'AddToCart'; 7 | export const RemoveFromCart = 'RemoveFromCart'; 8 | export const ShowCheckout = 'ShowCheckout'; 9 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import { jsdom } from 'jsdom'; 3 | import hook from 'css-modules-require-hook'; 4 | 5 | hook({ 6 | generateScopedName: '[name]__[local]___[hash:base64:5]' 7 | }); 8 | 9 | global.document = jsdom(''); 10 | global.window = document.defaultView; 11 | global.navigator = global.window.navigator; 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{json,js,jsx,html,css}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [.eslintrc] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /app/store/configureStore.production.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import rootReducer from '../reducers'; 4 | 5 | const finalCreateStore = compose( 6 | applyMiddleware(thunk) 7 | )(createStore); 8 | 9 | export default function configureStore(initialState) { 10 | return finalCreateStore(rootReducer, initialState); 11 | } 12 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tv Chicken 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/containers/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | import LogMonitor from 'redux-devtools-log-monitor'; 4 | import DockMonitor from 'redux-devtools-dock-monitor'; 5 | 6 | export default createDevTools( 7 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import App from './containers/App'; 4 | import HomePage from './containers/HomePage'; 5 | import SinglePage from './containers/SinglePage'; 6 | 7 | export default ( 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "mocha": true, 7 | "node": true 8 | }, 9 | "rules": { 10 | "react/jsx-uses-react": 2, 11 | "react/jsx-uses-vars": 2, 12 | "react/react-in-jsx-scope": 2, 13 | "no-var": 0, 14 | "vars-on-top": 0, 15 | "comma-dangle": 0, 16 | "no-use-before-define": 0 17 | }, 18 | "plugins": [ 19 | "react" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /app/components/ShoppingWindow/shoppingWindow.module.css: -------------------------------------------------------------------------------- 1 | .window{ 2 | position: fixed; 3 | width: 10em; 4 | top: 5em; 5 | right: 1em; 6 | text-align: center; 7 | composes: background-secondry from '../../styles/colors.module.css'; 8 | border: solid #370F2A; 9 | composes: bevel-lg from '../../styles/effects.module.css'; 10 | } 11 | .window h4{ 12 | font-size: 1em; 13 | } 14 | .media { 15 | composes: blue from '../../styles/colors.module.css'; 16 | } 17 | -------------------------------------------------------------------------------- /app/components/TopHeaderBar/topHeaderBar.module.css: -------------------------------------------------------------------------------- 1 | .strip { 2 | composes: color background-secondry from '../../styles/colors.module.css'; 3 | composes: ontop from '../../styles/colors.module.css'; 4 | width: 100%; 5 | height: 7px; 6 | } 7 | .circle { 8 | composes: color background-secondry from '../../styles/colors.module.css'; 9 | composes: ontop from '../../styles/colors.module.css'; 10 | height: 64px; 11 | width: 69px; 12 | border-radius: 0px 7px 128px 79px; 13 | } 14 | .image{ 15 | width: 60%; 16 | margin-left: 1em; 17 | } 18 | -------------------------------------------------------------------------------- /app/components/TopHeaderBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import styles from './topHeaderBar.module.css'; 4 | 5 | const topHeaderBar = () => { 6 | return ( 7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 |
15 |
16 |
17 | ); 18 | }; 19 | export default topHeaderBar; 20 | -------------------------------------------------------------------------------- /app/components/MediaSingle/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import MediaInfo from '../MediaInfo'; 3 | import Video from '../Video'; 4 | import styles from './single.module.css'; 5 | // import callForAction from '../CallForAction'; 6 | 7 | export default function MediaSingle(props) { 8 | return ( 9 |
10 |
11 |
14 |
15 | ); 16 | } 17 | MediaSingle.propTypes = { 18 | media: PropTypes.object.isRequired 19 | }; 20 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | 6 | module.exports = { 7 | module: { 8 | loaders: [{ 9 | test: /\.jsx?$/, 10 | loaders: ['babel-loader'], 11 | exclude: /node_modules/ 12 | }] 13 | }, 14 | output: { 15 | path: path.join(__dirname, 'dist'), 16 | filename: 'bundle.js', 17 | libraryTarget: 'commonjs2' 18 | }, 19 | resolve: { 20 | extensions: ['', '.js', '.jsx'], 21 | packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'] 22 | }, 23 | plugins: [ 24 | 25 | ], 26 | externals: [ 27 | 'bunyan' 28 | ] 29 | }; 30 | -------------------------------------------------------------------------------- /app/components/MediaActions/mediaActions.module.css: -------------------------------------------------------------------------------- 1 | .action { 2 | composes: color background-secondry from '../../styles/colors.module.css'; 3 | composes: bevel-lg from '../../styles/effects.module.css'; 4 | margin-bottom: 1em; 5 | } 6 | 7 | .action :global a { 8 | color: #fff; 9 | border-right: solid 2px #B19898; 10 | } 11 | 12 | /** 13 | * bootstrap overides 14 | */ 15 | .nav > li > a { 16 | padding: 8px 8px; 17 | } 18 | 19 | .nav li a:hover, .nav li a:focus { 20 | text-decoration: none; 21 | background-color: transparent; 22 | color: #370F2A; 23 | } 24 | 25 | .nav-pills > li > a { 26 | border-radius: 0; 27 | } 28 | 29 | .lastPill a { 30 | border: 0; 31 | } 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "4" 5 | - "5" 6 | 7 | cache: 8 | directories: 9 | - node_modules 10 | 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - g++-4.8 17 | 18 | install: 19 | - export CXX="g++-4.8" 20 | - npm install 21 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 22 | 23 | before_script: 24 | - export DISPLAY=:99.0 25 | - sh -e /etc/init.d/xvfb start & 26 | - sleep 3 27 | 28 | script: 29 | - npm run lint 30 | - npm run test 31 | - npm run build 32 | - npm run test-e2e 33 | -------------------------------------------------------------------------------- /app/components/Video/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import styles from './video.module.css'; 3 | import cx from 'classnames'; 4 | 5 | export default class Video extends Component { 6 | 7 | render() { 8 | return ( 9 |
10 |
11 | 16 |
17 |
18 | ); 19 | } 20 | } 21 | 22 | Video.propTypes = { 23 | url: PropTypes.string.isRequired 24 | }; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | .todo.md 5 | movies 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # App packaged 34 | dist 35 | release 36 | -------------------------------------------------------------------------------- /app/utils/copy.js: -------------------------------------------------------------------------------- 1 | import childProcess from 'child_process'; 2 | 3 | export default function (source, target, callback) { 4 | const copy = childProcess.spawn('rsync', ['--progress', '-a', source, target]); 5 | copy.stdout.on('data', (data) => { 6 | // ignore the rest of the output and take only that with %s 7 | const dataString = String(data); 8 | if (data.indexOf('%') !== -1) { 9 | // filter out all empty array fields 10 | const progress = dataString.split(' ') .filter(value => value !== ''); 11 | callback(progress); 12 | } 13 | }); 14 | copy.stderr.on('data', (data) => { 15 | throw new Error(data); 16 | // console.log(`stderr: ${data}`); 17 | }); 18 | copy.on('close', (code) => { 19 | console.log(`child process exited with code ${code}`); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0, no-console: 0 */ 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | const express = require('express'); 6 | const webpack = require('webpack'); 7 | const config = require('./webpack.config.development'); 8 | 9 | const app = express(); 10 | const compiler = webpack(config); 11 | 12 | const PORT = 3000; 13 | 14 | app.use(require('webpack-dev-middleware')(compiler, { 15 | publicPath: config.output.publicPath, 16 | stats: { 17 | colors: true 18 | } 19 | })); 20 | 21 | app.use(require('webpack-hot-middleware')(compiler)); 22 | 23 | app.get('*', (req, res) => { 24 | res.sendFile(path.join(__dirname, 'app', 'hot-dev-app.html')); 25 | }); 26 | 27 | app.listen(PORT, 'localhost', err => { 28 | if (err) { 29 | console.log(err); 30 | return; 31 | } 32 | 33 | console.log(`Listening at http://localhost:${PORT}`); 34 | }); 35 | -------------------------------------------------------------------------------- /app/store/configureStore.development.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import { persistState } from 'redux-devtools'; 3 | import thunk from 'redux-thunk'; 4 | import rootReducer from '../reducers'; 5 | import DevTools from '../containers/DevTools'; 6 | import createLogger from 'redux-logger'; 7 | 8 | const logger = createLogger(); 9 | const finalCreateStore = compose( 10 | applyMiddleware(thunk, logger), 11 | DevTools.instrument(), 12 | persistState( 13 | window.location.href.match( 14 | /[?&]debug_session=([^&]+)\b/ 15 | ) 16 | ), 17 | 18 | )(createStore); 19 | 20 | export default function configureStore(initialState) { 21 | const store = finalCreateStore(rootReducer, initialState); 22 | if (module.hot) { 23 | module.hot.accept('../reducers', () => 24 | store.replaceReducer(require('../reducers')) 25 | ); 26 | } 27 | 28 | return store; 29 | } 30 | -------------------------------------------------------------------------------- /app/components/SearchBar/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import styles from './searchBar.module.css'; 3 | import cx from 'classnames'; 4 | 5 | export default class SearchBar extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | searchHandler = () => { 12 | var input = this.refs.searchKey; 13 | console.log(input); 14 | // this.props.searchAction(input.value); 15 | } 16 | 17 | render() { 18 | return ( 19 |
20 |
21 | 22 |
23 |
24 | ); 25 | } 26 | } 27 | 28 | /* SearchBar.propTypes = { 29 | searchAction: PropTypes.func.isRequired, 30 | }; 31 | */ 32 | -------------------------------------------------------------------------------- /app/components/MediaItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import styles from './item.module.css'; 3 | import cx from 'classnames'; 4 | import MediaActions from '../MediaActions'; 5 | 6 | const MediaItem = (props) => { 7 | // console.log(props); 8 | return ( 9 |
10 | 11 |
12 |

{props.name}

13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | MediaItem.propTypes = { 20 | src: PropTypes.string.isRequired, 21 | id: PropTypes.string.isRequired, 22 | location: PropTypes.string.isRequired, 23 | dispatch: PropTypes.func.isRequired, 24 | name: PropTypes.string.isRequired, 25 | }; 26 | export default MediaItem; 27 | -------------------------------------------------------------------------------- /app/containers/SinglePage.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import MediaSingle from '../components/MediaSingle'; 4 | 5 | 6 | class SinglePage extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | getMedia(id) { 13 | return this.props.items.filter(item => item._id === id)[0]; 14 | } 15 | 16 | render = () => { 17 | const { params, type } = this.props; 18 | const item = this.getMedia(params.id); 19 | return ( 20 |
21 | 22 |
23 | ); 24 | } 25 | } 26 | 27 | SinglePage.propTypes = { 28 | params: PropTypes.object.isRequired, 29 | type: PropTypes.number.isRequired, 30 | items: PropTypes.array.isRequired, 31 | }; 32 | 33 | function mapStateToProps(state) { 34 | return { type: state.type, items: state.items }; 35 | } 36 | 37 | export default connect(mapStateToProps)(SinglePage); 38 | -------------------------------------------------------------------------------- /app/components/Header/header.module.css: -------------------------------------------------------------------------------- 1 | .nav { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | z-index: 99; 6 | left: 0; 7 | padding: 16px; 8 | background-color: rgba(0,0,0,0.4); 9 | border-right: 2px solid #161e23; 10 | } 11 | 12 | .nav > header { 13 | font-weight: 700; 14 | font-size: 1.2rem; 15 | text-transform: uppercase; 16 | } 17 | .nav section { 18 | font-weight: 600; 19 | } 20 | .nav section header { 21 | padding-top: 30px; 22 | } 23 | .nav section ul { 24 | list-style: none; 25 | padding: 0px; 26 | margin-left: 10%; 27 | } 28 | .nav section ul li { 29 | position: relative; 30 | padding: 10px 0px; 31 | } 32 | .nav section ul li.active,.nav section ul li a:hover { 33 | /*color: $secondry;*/ 34 | } 35 | .nav section ul li:after { 36 | content: attr(data-value); 37 | position: absolute; 38 | right: 0px; 39 | width: 19px; 40 | background-color: #738491; 41 | font-size: 0.9rem; 42 | /*color: $font-color;*/ 43 | -moz-border-radius: 19px; 44 | -webkit-border-radius: 19px; 45 | border-radius: 19px; 46 | text-align: center; 47 | } 48 | -------------------------------------------------------------------------------- /app/components/ProgressLoader/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import { ProgressBar } from 'react-bootstrap'; 3 | import copy from '../../utils/copy'; 4 | 5 | 6 | export default class ProgressLoader extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { progress: 0 }; 11 | } 12 | componentDidMount = () => { 13 | this.downloadMedia(this.props.source); 14 | } 15 | downloadMedia(movieName) { 16 | const date = new Date().getTime(); 17 | const source = `/Users/allanlukwago/apps/tv-engine/public/${movieName}.mkv`; 18 | const target = `/Users/allanlukwago/apps/tv-chicken/movies/${date}-${movieName}.mkv`; 19 | copy(source, target, (progress) => { 20 | const rate = progress[1].replace('%', ''); 21 | this.setState({ progress: parseInt(rate, 10) }); 22 | }); 23 | } 24 | 25 | render() { 26 | return ( 27 | 28 | ); 29 | } 30 | } 31 | 32 | ProgressLoader.propTypes = { 33 | source: PropTypes.string.isRequired, 34 | }; 35 | export default ProgressLoader; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 C. T. Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import { Provider } from 'react-redux'; 5 | import { Router } from 'react-router'; 6 | import routes from './routes'; 7 | import configureStore from './store/configureStore'; 8 | import './app.css'; 9 | // import createBrowserHistory from 'history/lib/createBrowserHistory'; 10 | // const history = createBrowserHistory(); 11 | 12 | const store = configureStore(); 13 | // Log the initial state 14 | // console.log(store.getState()); 15 | 16 | // Every time the state changes, log it 17 | // Note that subscribe() returns a function for unregistering the listener 18 | // store.subscribe(() => console.log(store.getState())); 19 | 20 | render( 21 | 22 | 23 | {routes} 24 | 25 | , 26 | document.getElementById('root') 27 | ); 28 | 29 | if (process.env.NODE_ENV !== 'production') { 30 | // Use require because imports can't be conditional. 31 | // In production, you should ensure process.env.NODE_ENV 32 | // is envified so that Uglify can eliminate this 33 | // module and its dependencies as dead code. 34 | // require('./createDevToolsWindow')(store); 35 | } 36 | -------------------------------------------------------------------------------- /app/components/MediaInfo/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import styles from './mediaInfo.module.css'; 3 | // import cx from 'classnames'; 4 | 5 | export default class MediaInfo extends Component { 6 | 7 | render() { 8 | const actors = this.props.info.actors.map(actor =>
  • {actor}
  • ); 9 | const directors = this.props.info.director.map(dir =>
  • {dir}
  • ); 10 | return ( 11 |
    12 |

    {this.props.info.title}

    13 |
    14 |
    15 |

    Plot

    16 |

    {this.props.info.plot}

    17 |
    18 |
    19 |

    Actors

    20 |
      {actors}
    21 |
    22 |
    23 |

    Directors

    24 |
      {directors}
    25 |
    26 |
    27 | ); 28 | } 29 | } 30 | MediaInfo.propTypes = { 31 | info: PropTypes.object.isRequired 32 | }; 33 | -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | const webpack = require('webpack'); 5 | const webpackTargetElectronRenderer = require('webpack-target-electron-renderer'); 6 | const baseConfig = require('./webpack.config.base'); 7 | 8 | 9 | const config = Object.create(baseConfig); 10 | 11 | config.debug = true; 12 | 13 | config.devtool = 'cheap-module-eval-source-map'; 14 | 15 | config.entry = [ 16 | 'webpack-hot-middleware/client?path=http://localhost:3000/__webpack_hmr', 17 | './app/index' 18 | ]; 19 | 20 | config.output.publicPath = 'http://localhost:3000/dist/'; 21 | 22 | config.module.loaders.push({ 23 | test: /^((?!\.module).)*\.css$/, 24 | loaders: [ 25 | 'style-loader', 26 | 'css-loader?sourceMap' 27 | ] 28 | }, { 29 | test: /\.module\.css$/, 30 | loaders: [ 31 | 'style-loader', 32 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!' 33 | ] 34 | }); 35 | 36 | config.plugins.push( 37 | new webpack.HotModuleReplacementPlugin(), 38 | new webpack.NoErrorsPlugin(), 39 | new webpack.DefinePlugin({ 40 | '__DEV__': true, 41 | 'process.env': { 42 | 'NODE_ENV': JSON.stringify('development') 43 | } 44 | }) 45 | ); 46 | 47 | config.target = webpackTargetElectronRenderer(config); 48 | 49 | module.exports = config; 50 | -------------------------------------------------------------------------------- /app/components/MediaList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import MediaItem from '../MediaItem'; 3 | // import cx from 'classnames'; 4 | import styles from './mediaList.module.css'; 5 | import _ from 'lodash'; 6 | 7 | export default class MediaList extends Component { 8 | 9 | getRowCount() { 10 | let chunkSize = 4; 11 | const windowWidth = window.innerWidth; 12 | if (windowWidth < 1300) { 13 | chunkSize = 3; 14 | } else if (windowWidth < 900) { 15 | chunkSize = 2; 16 | } else { 17 | chunkSize = 4; 18 | } 19 | return chunkSize; 20 | } 21 | 22 | render() { 23 | const items = this.props.items.map((item) => { 24 | return (); 25 | }); 26 | 27 | const chunks = _.chunk(items, this.getRowCount()); 28 | const rows = chunks.map((chunk, index) => { 29 | return ( 30 |
    {chunk}
    31 | ); 32 | }); 33 | return ( 34 |
    35 | {rows} 36 |
    37 | ); 38 | } 39 | 40 | } 41 | MediaList.propTypes = { 42 | items: PropTypes.array.isRequired, 43 | dispatch: PropTypes.func.isRequired, 44 | }; 45 | -------------------------------------------------------------------------------- /app/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import * as MediaActions from '../actions'; 4 | import Header from '../components/Header'; 5 | import TopHeaderBar from '../components/TopHeaderBar'; 6 | 7 | class App extends Component { 8 | 9 | constructor(props, context) { 10 | super(props, context); 11 | } 12 | 13 | componentDidMount() { 14 | const { isFetching, dispatch } = this.props; 15 | if (!isFetching) dispatch(MediaActions.fetchInitialData({ type: 1, rating: 5 })); 16 | } 17 | 18 | render = () => { 19 | const { children } = this.props; 20 | return ( 21 |
    22 | 23 |
    24 |
    25 |
    26 |
    27 |
    28 | {children} 29 |
    30 |
    31 |
    32 | ); 33 | } 34 | } 35 | 36 | App.propTypes = { 37 | children: PropTypes.node, 38 | isFetching: PropTypes.bool.isRequired, 39 | dispatch: PropTypes.func.isRequired, 40 | }; 41 | 42 | function mapStateToProps(state) { 43 | return { isFetching: state.isFetching }; 44 | } 45 | 46 | export default connect(mapStateToProps)(App); 47 | -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import * as type from '../constants'; 2 | 3 | const initialState = { 4 | isFetching: false, 5 | viewAll: true, 6 | type: 1, 7 | items: [], 8 | inCart: [], 9 | showCheckout: false, 10 | }; 11 | 12 | export default function media(state = initialState, action) { 13 | switch (action.type) { 14 | case type.REQUEST_DATA: 15 | return Object.assign({}, state, { 16 | isFetching: true 17 | }); 18 | case type.ShowCheckout: 19 | return Object.assign({}, state, { 20 | showCheckout: action.show 21 | }); 22 | case type.AddToCart: 23 | return Object.assign({}, state, { 24 | inCart: [action.title, ...state.inCart], 25 | }); 26 | case type.RemoveFromCart: 27 | const newCart = state.inCart.filter(title => title !== action.title); 28 | return Object.assign({}, state, { 29 | inCart: newCart, 30 | }); 31 | case type.RECEIVE_DATA: 32 | return Object.assign({}, state, { 33 | items: [...action.items, ...state.items], 34 | isFetching: false 35 | }); 36 | case type.VIEW_ALL: 37 | return Object.assign({}, state, { 38 | viewAll: true 39 | }); 40 | case type.VIEW_SELECTED: 41 | return Object.assign({}, state, { 42 | viewAll: false 43 | }); 44 | default: 45 | return state; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/containers/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import MediaList from '../components/MediaList'; 4 | import ShoppingWindow from '../components/ShoppingWindow'; 5 | 6 | class HomePage extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | indexView() { 13 | const { items, type } = this.props; 14 | if (items.length) return (); 15 | } 16 | render = () => { 17 | return ( 18 |
    19 | {this.indexView()} 20 | 25 |
    26 | ); 27 | } 28 | } 29 | 30 | HomePage.propTypes = { 31 | items: PropTypes.array.isRequired, 32 | type: PropTypes.number, 33 | dispatch: PropTypes.func.isRequired, 34 | showCheckout: PropTypes.bool.isRequired, 35 | inCart: PropTypes.array.isRequired, 36 | }; 37 | 38 | function mapStateToProps(state) { 39 | return { 40 | items: state.items, 41 | type: state.type, 42 | inCart: state.inCart, 43 | dispatch: state.dispatch, 44 | showCheckout: state.showCheckout, 45 | }; 46 | } 47 | 48 | export default connect(mapStateToProps)(HomePage); 49 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | const webpack = require('webpack'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const webpackTargetElectronRenderer = require('webpack-target-electron-renderer'); 7 | const baseConfig = require('./webpack.config.base'); 8 | 9 | 10 | const config = Object.create(baseConfig); 11 | 12 | config.devtool = 'source-map'; 13 | 14 | config.entry = './app/index'; 15 | 16 | config.output.publicPath = '/dist/'; 17 | 18 | config.module.loaders.push({ 19 | test: /^((?!\.module).)*\.css$/, 20 | loader: ExtractTextPlugin.extract( 21 | 'style-loader', 22 | 'css-loader' 23 | ) 24 | }, { 25 | test: /\.module\.css$/, 26 | loader: ExtractTextPlugin.extract( 27 | 'style-loader', 28 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 29 | ) 30 | }); 31 | 32 | config.plugins.push( 33 | new webpack.optimize.OccurenceOrderPlugin(), 34 | new webpack.DefinePlugin({ 35 | '__DEV__': false, 36 | 'process.env': { 37 | 'NODE_ENV': JSON.stringify('production') 38 | } 39 | }), 40 | new webpack.optimize.UglifyJsPlugin({ 41 | compressor: { 42 | screw_ie8: true, 43 | warnings: false 44 | } 45 | }), 46 | new ExtractTextPlugin('style.css', { allChunks: true }) 47 | ); 48 | 49 | config.target = webpackTargetElectronRenderer(config); 50 | 51 | module.exports = config; 52 | -------------------------------------------------------------------------------- /test/actions/media.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-expressions: 0 */ 2 | import { expect } from 'chai'; 3 | import { spy } from 'sinon'; 4 | import * as actions from '../../app/actions'; 5 | 6 | 7 | describe('actions', () => { 8 | it('increment should create increment action', () => { 9 | expect(actions.increment()).to.deep.equal({ type: actions.INCREMENT_COUNTER }); 10 | }); 11 | 12 | it('decrement should create decrement action', () => { 13 | expect(actions.decrement()).to.deep.equal({ type: actions.DECREMENT_COUNTER }); 14 | }); 15 | 16 | it('incrementIfOdd should create increment action', () => { 17 | const fn = actions.incrementIfOdd(); 18 | expect(fn).to.be.a('function'); 19 | const dispatch = spy(); 20 | const getState = () => ({ counter: 1 }); 21 | fn(dispatch, getState); 22 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).to.be.true; 23 | }); 24 | 25 | it('incrementIfOdd shouldnt create increment action if counter is even', () => { 26 | const fn = actions.incrementIfOdd(); 27 | const dispatch = spy(); 28 | const getState = () => ({ counter: 2 }); 29 | fn(dispatch, getState); 30 | expect(dispatch.called).to.be.false; 31 | }); 32 | 33 | // There's no nice way to test this at the moment... 34 | it('incrementAsync', (done) => { 35 | const fn = actions.incrementAsync(1); 36 | expect(fn).to.be.a('function'); 37 | const dispatch = spy(); 38 | fn(dispatch); 39 | setTimeout(() => { 40 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).to.be.true; 41 | done(); 42 | }, 5); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /app/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants'; 2 | import fetch from 'isomorphic-fetch'; 3 | 4 | // TODO move api constant to a config file 5 | const ROOT_API = 'http://localhost:5000/api/'; 6 | 7 | export function requestData(request) { 8 | return { 9 | type: types.REQUEST_DATA, 10 | request 11 | }; 12 | } 13 | 14 | export function searchForMedia(search) { 15 | // TODO first look through available data 16 | return fetchData(search); 17 | } 18 | 19 | export function addToCart(title) { 20 | return { 21 | type: types.AddToCart, 22 | title, 23 | }; 24 | } 25 | 26 | export function showCheckout(show) { 27 | return { 28 | type: types.ShowCheckout, 29 | show, 30 | }; 31 | } 32 | 33 | export function removeFromCart(title) { 34 | return { 35 | type: types.RemoveFromCart, 36 | title, 37 | }; 38 | } 39 | 40 | export function receiveData(request, json) { 41 | return { 42 | type: types.RECEIVE_DATA, 43 | items: json, 44 | request, 45 | receivedAt: Date.now() 46 | }; 47 | } 48 | 49 | export function fetchInitialData(request) { 50 | return (dispatch) => { 51 | return dispatch(fetchData(request)); 52 | }; 53 | } 54 | 55 | function fetchData(request) { 56 | return async (dispatch) => { 57 | dispatch(requestData(request)); 58 | const api = ROOT_API + 'media'; 59 | const response = await fetch(api, { 60 | method: 'post', 61 | headers: { 62 | 'Accept': 'application/json', 63 | 'Content-Type': 'application/json' 64 | }, 65 | body: JSON.stringify(request) 66 | }); 67 | const json = await response.json(); 68 | dispatch(receiveData(request, json)); 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /app/components/ShoppingWindow/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import { ButtonGroup, Button } from 'react-bootstrap'; 3 | import Checkout from '../Checkout'; 4 | import { showCheckout } from '../../actions'; 5 | // import cx from 'classnames'; 6 | import styles from './shoppingWindow.module.css'; 7 | 8 | export default class shoppingWindow extends Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.state = { shopped: this.props.inCart, show: false }; 13 | } 14 | 15 | buyMovie = (title) => { 16 | this.setState({ shopped: [title] }); 17 | this.props.dispatch(showCheckout(true)); 18 | } 19 | 20 | buyAll = () => { 21 | this.setState({ shopped: this.props.inCart }); 22 | this.props.dispatch(showCheckout(true)); 23 | } 24 | 25 | render = () => { 26 | const items = this.props.inCart.map((title, index) => 27 | ); 28 | return ( 29 |
    30 |
    31 |

    Shopping Cart

    32 | 33 | {items} 34 | 35 | 36 |
    37 | 38 |
    39 | ); 40 | } 41 | } 42 | shoppingWindow.propTypes = { 43 | inCart: PropTypes.array.isRequired, 44 | dispatch: PropTypes.func.isRequired, 45 | showCheckout: PropTypes.bool.isRequired, 46 | }; 47 | -------------------------------------------------------------------------------- /app/components/Checkout/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import styles from './checkout.module.css'; 3 | import ProgressLoader from '../ProgressLoader'; 4 | import { Modal, Table } from 'react-bootstrap'; 5 | import { showCheckout } from '../../actions'; 6 | 7 | export default class Checkout extends Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | close = () => { 12 | this.props.dispatch(showCheckout(false)); 13 | } 14 | render() { 15 | const downloads = this.props.shopped.map(item => { 16 | return ( 17 | 18 | {item} 19 | 20 | 21 | ); 22 | }); 23 | return ( 24 |
    25 | 26 | 27 | Check Out 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {downloads} 41 | 42 |
    Amout of money2000
    FlashAllans Flash
    43 |
    44 |
    45 |
    46 | ); 47 | } 48 | } 49 | 50 | Checkout.propTypes = { 51 | shopped: PropTypes.array.isRequired, 52 | show: PropTypes.bool.isRequired, 53 | dispatch: PropTypes.func.isRequired, 54 | }; 55 | -------------------------------------------------------------------------------- /app/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import SearchBar from '../SearchBar'; 3 | import styles from './header.module.css'; 4 | import cx from 'classnames'; 5 | 6 | export default class Header extends Component { 7 | render() { 8 | return ( 9 | 58 | ); 59 | } 60 | } 61 | /* Header.propTypes = { 62 | searchAction: PropTypes.func.isRequired 63 | }; 64 | */ 65 | -------------------------------------------------------------------------------- /app/components/MediaActions/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import cx from 'classnames'; 3 | import styles from './mediaActions.module.css'; 4 | import { Link } from 'react-router'; 5 | import { addToCart, removeFromCart } from '../../actions'; 6 | 7 | export default class MediaActions extends Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | this.state = { addedTocart: false }; 12 | } 13 | 14 | addToCartHandler = (args, event) => { 15 | event.preventDefault(); 16 | const { title, dispatch } = args; 17 | if (this.state.addedTocart) { 18 | this.setState({ addedTocart: false }); 19 | dispatch(removeFromCart(title)); 20 | } else { 21 | dispatch(addToCart(title)); 22 | this.setState({ addedTocart: true }); 23 | } 24 | } 25 | 26 | render = () => { 27 | const cartBtnLable = this.state.addedTocart ? 'Remove' : 'Add'; 28 | const addToCartHandlerArgs = { title: this.props.title, dispatch: this.props.dispatch }; 29 | return ( 30 |
    31 | 42 |
    43 | ); 44 | } 45 | } 46 | 47 | MediaActions.propTypes = { 48 | id: PropTypes.string.isRequired, 49 | title: PropTypes.string.isRequired, 50 | dispatch: PropTypes.func.isRequired, 51 | }; 52 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0, no-shadow: 0, no-unused-vars: 0, no-console: 0 */ 2 | 'use strict'; 3 | 4 | const os = require('os'); 5 | const webpack = require('webpack'); 6 | const cfg = require('./webpack.config.production.js'); 7 | const packager = require('electron-packager'); 8 | const del = require('del'); 9 | const exec = require('child_process').exec; 10 | const argv = require('minimist')(process.argv.slice(2)); 11 | const pkg = require('./package.json'); 12 | const devDeps = Object.keys(pkg.devDependencies); 13 | 14 | const appName = argv.name || argv.n || pkg.productName; 15 | const shouldUseAsar = argv.asar || argv.a || false; 16 | const shouldBuildAll = argv.all || false; 17 | 18 | 19 | const DEFAULT_OPTS = { 20 | dir: './', 21 | name: appName, 22 | asar: shouldUseAsar, 23 | ignore: [ 24 | '/test($|/)', 25 | '/tools($|/)', 26 | '/release($|/)' 27 | ].concat(devDeps.map(name => `/node_modules/${name}($|/)`)) 28 | }; 29 | 30 | const icon = argv.icon || argv.i || 'app/app.icns'; 31 | 32 | if (icon) { 33 | DEFAULT_OPTS.icon = icon; 34 | } 35 | 36 | const version = argv.version || argv.v; 37 | 38 | if (version) { 39 | DEFAULT_OPTS.version = version; 40 | startPack(); 41 | } else { 42 | // use the same version as the currently-installed electron-prebuilt 43 | exec('npm list electron-prebuilt', (err, stdout) => { 44 | if (err) { 45 | DEFAULT_OPTS.version = '0.36.2'; 46 | } else { 47 | DEFAULT_OPTS.version = stdout.split('electron-prebuilt@')[1].replace(/\s/g, ''); 48 | } 49 | 50 | startPack(); 51 | }); 52 | } 53 | 54 | 55 | function startPack() { 56 | console.log('start pack...'); 57 | webpack(cfg, (err, stats) => { 58 | if (err) return console.error(err); 59 | del('release') 60 | .then(paths => { 61 | if (shouldBuildAll) { 62 | // build for all platforms 63 | const archs = ['ia32', 'x64']; 64 | const platforms = ['linux', 'win32', 'darwin']; 65 | 66 | platforms.forEach(plat => { 67 | archs.forEach(arch => { 68 | pack(plat, arch, log(plat, arch)); 69 | }); 70 | }); 71 | } else { 72 | // build for current platform only 73 | pack(os.platform(), os.arch(), log(os.platform(), os.arch())); 74 | } 75 | }) 76 | .catch(err => { 77 | console.error(err); 78 | }); 79 | }); 80 | } 81 | 82 | function pack(plat, arch, cb) { 83 | // there is no darwin ia32 electron 84 | if (plat === 'darwin' && arch === 'ia32') return; 85 | 86 | const opts = Object.assign({}, DEFAULT_OPTS, { 87 | platform: plat, 88 | arch, 89 | prune: true, 90 | out: `release/${plat}-${arch}` 91 | }); 92 | 93 | packager(opts, cb); 94 | } 95 | 96 | 97 | function log(plat, arch) { 98 | return (err, filepath) => { 99 | if (err) return console.error(err); 100 | console.log(`${plat}-${arch} finished!`); 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /test/e2e.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import chromedriver from 'chromedriver'; 3 | import webdriver from 'selenium-webdriver'; 4 | import { expect } from 'chai'; 5 | import electronPath from 'electron-prebuilt'; 6 | import homeStyles from '../app/components/Home.module.css'; 7 | import counterStyles from '../app/components/Counter.module.css'; 8 | 9 | chromedriver.start(); // on port 9515 10 | process.on('exit', chromedriver.stop); 11 | 12 | const delay = time => new Promise(resolve => setTimeout(resolve, time)); 13 | 14 | describe.skip('main window', function spec() { 15 | this.timeout(5000); 16 | 17 | before(async () => { 18 | await delay(1000); // wait chromedriver start time 19 | this.driver = new webdriver.Builder() 20 | .usingServer('http://localhost:9515') 21 | .withCapabilities({ 22 | chromeOptions: { 23 | binary: electronPath, 24 | args: ['app=' + path.resolve()] 25 | } 26 | }) 27 | .forBrowser('electron') 28 | .build(); 29 | }); 30 | 31 | after(async () => { 32 | await this.driver.quit(); 33 | }); 34 | 35 | const findCounter = () => { 36 | return this.driver.findElement(webdriver.By.className(counterStyles.counter)); 37 | }; 38 | 39 | const findButtons = () => { 40 | return this.driver.findElements(webdriver.By.className(counterStyles.btn)); 41 | }; 42 | 43 | it('should open window', async () => { 44 | const title = await this.driver.getTitle(); 45 | expect(title).to.equal('Hello Electron React!'); 46 | }); 47 | 48 | it('should to Counter with click "to Counter" link', async () => { 49 | const link = await this.driver.findElement(webdriver.By.css(`.${homeStyles.container} > a`)); 50 | link.click(); 51 | 52 | const counter = await findCounter(); 53 | expect(await counter.getText()).to.equal('0'); 54 | }); 55 | 56 | it('should display updated count after increment button click', async () => { 57 | const buttons = await findButtons(); 58 | buttons[0].click(); 59 | 60 | const counter = await findCounter(); 61 | expect(await counter.getText()).to.equal('1'); 62 | }); 63 | 64 | it('should display updated count after descrement button click', async () => { 65 | const buttons = await findButtons(); 66 | const counter = await findCounter(); 67 | 68 | buttons[1].click(); // - 69 | 70 | expect(await counter.getText()).to.equal('0'); 71 | }); 72 | 73 | it('shouldnt change if even and if odd button clicked', async () => { 74 | const buttons = await findButtons(); 75 | const counter = await findCounter(); 76 | buttons[2].click(); // odd 77 | 78 | expect(await counter.getText()).to.equal('0'); 79 | }); 80 | 81 | it('should change if odd and if odd button clicked', async () => { 82 | const buttons = await findButtons(); 83 | const counter = await findCounter(); 84 | 85 | buttons[0].click(); // + 86 | buttons[2].click(); // odd 87 | 88 | expect(await counter.getText()).to.equal('2'); 89 | }); 90 | 91 | it('should change if async button clicked and a second later', async () => { 92 | const buttons = await findButtons(); 93 | const counter = await findCounter(); 94 | buttons[3].click(); // async 95 | 96 | expect(await counter.getText()).to.equal('2'); 97 | 98 | await this.driver.wait(() => 99 | counter.getText().then(text => text === '3') 100 | , 1000, 'count not as expected'); 101 | }); 102 | 103 | it('should back to home if back button clicked', async () => { 104 | const link = await this.driver.findElement(webdriver.By.css(`.${counterStyles.backButton} > a`)); 105 | link.click(); 106 | 107 | await this.driver.findElement(webdriver.By.className(homeStyles.container)); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TV-chicken 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Build Status][travis-image]][travis-url] 5 | [![Dependency Status][david_img]][david_site] 6 | 7 | ![](./erb-logo.png) 8 | 9 | > Live editing development on desktop app 10 | 11 | [Electron](http://electron.atom.io/) application based on [React](https://facebook.github.io/react/), [Redux](https://github.com/rackt/redux), [React Router](https://github.com/rackt/react-router), [Webpack](http://webpack.github.io/docs/), [React Transform HMR](https://github.com/gaearon/react-transform-hmr) for rapid application development 12 | 13 | ## Install 14 | 15 | Install dependencies. 16 | 17 | ```bash 18 | $ npm install 19 | ``` 20 | 21 | 22 | ## Run 23 | 24 | Run this two commands __simultaneously__ in different console tabs. 25 | 26 | ```bash 27 | $ npm run hot-server 28 | $ npm run start-hot 29 | ``` 30 | 31 | *Note: requires a node version >= 4 and an npm version >= 2.* 32 | 33 | #### Toggle Chrome DevTools 34 | 35 | - OS X: Cmd Alt I or F12 36 | - Linux: Ctrl Shift I or F12 37 | - Windows: Ctrl Shift I or F12 38 | 39 | *See [electron-debug](https://github.com/sindresorhus/electron-debug) for more information.* 40 | 41 | #### Toggle Redux DevTools 42 | 43 | - All platforms: Ctrl+H 44 | 45 | *See [redux-devtools-dock-monitor](https://github.com/gaearon/redux-devtools-dock-monitor) for more information.* 46 | 47 | 48 | ## Externals 49 | 50 | If you use any 3rd party libraries which can't be built with webpack, you must list them in your `webpack.config.base.js`: 51 | 52 | ```javascript 53 | externals: [ 54 | // put your node 3rd party libraries which can't be built with webpack here (mysql, mongodb, and so on..) 55 | ] 56 | ``` 57 | 58 | You can find those lines in the file. 59 | 60 | 61 | ## CSS Modules support 62 | 63 | Import css file as [css-modules](https://github.com/css-modules/css-modules) using `.module.css`. 64 | 65 | 66 | ## Package 67 | 68 | ```bash 69 | $ npm run package 70 | ``` 71 | 72 | To package apps for all platforms: 73 | 74 | ```bash 75 | $ npm run package-all 76 | ``` 77 | 78 | #### Options 79 | 80 | - --name, -n: Application name (default: ElectronReact) 81 | - --version, -v: Electron version (default: latest version) 82 | - --asar, -a: [asar](https://github.com/atom/asar) support (default: false) 83 | - --icon, -i: Application icon 84 | - --all: pack for all platforms 85 | 86 | Use `electron-packager` to pack your app with `--all` options for darwin (osx), linux and win32 (windows) platform. After build, you will find them in `release` folder. Otherwise, you will only find one for your os. 87 | 88 | `test`, `tools`, `release` folder and devDependencies in `package.json` will be ignored by default. 89 | 90 | #### Default Ignore modules 91 | 92 | We add some module's `peerDependencies` to ignore option as default for application size reduction. 93 | 94 | - `babel-core` is required by `babel-loader` and its size is ~19 MB 95 | - `node-libs-browser` is required by `webpack` and its size is ~3MB. 96 | 97 | > **Note:** If you want to use any above modules in runtime, for example: `require('babel/register')`, you should move them form `devDependencies` to `dependencies`. 98 | 99 | ## License 100 | 101 | [npm-image]: https://img.shields.io/npm/v/electron-react-boilerplate.svg?style=flat-square 102 | [npm-url]: https://npmjs.org/package/electron-react-boilerplate 103 | [travis-image]: https://travis-ci.org/chentsulin/electron-react-boilerplate.svg?branch=master 104 | [travis-url]: https://travis-ci.org/chentsulin/electron-react-boilerplate 105 | [david_img]: https://img.shields.io/david/chentsulin/electron-react-boilerplate.svg 106 | [david_site]: https://david-dm.org/chentsulin/electron-react-boilerplate 107 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.7.1 (2015.12.27) 2 | 3 | #### Bug fixed 4 | 5 | - **Fixed npm script on windows 10:** #103. 6 | - **history and react-router version bump**: #109, #110. 7 | 8 | #### Improvements 9 | 10 | - **electron 0.36** 11 | 12 | 13 | 14 | # 0.7.0 (2015.12.16) 15 | 16 | #### Bug fixed 17 | 18 | - **Fixed process.env.NODE_ENV variable in webpack:** #74. 19 | - **add missing object-assign**: #76. 20 | - **packaging in npm@3:** #77. 21 | - **compatibility in windows:** #100. 22 | - **disable chrome debugger in production env:** #102. 23 | 24 | #### Improvements 25 | 26 | - **redux** 27 | - **css-modules** 28 | - **upgrade to react-router 1.x** 29 | - **unit tests** 30 | - **e2e tests** 31 | - **travis-ci** 32 | - **upgrade to electron 0.35.x** 33 | - **use es2015** 34 | - **check dev engine for node and npm** 35 | 36 | 37 | # 0.6.5 (2015.11.7) 38 | 39 | #### Improvements 40 | 41 | - **Bump style-loader to 0.13** 42 | - **Bump css-loader to 0.22** 43 | 44 | 45 | # 0.6.4 (2015.10.27) 46 | 47 | #### Improvements 48 | 49 | - **Bump electron-debug to 0.3** 50 | 51 | 52 | # 0.6.3 (2015.10.26) 53 | 54 | #### Improvements 55 | 56 | - **Initialize ExtractTextPlugin once:** #64. 57 | 58 | 59 | # 0.6.2 (2015.10.18) 60 | 61 | #### Bug fixed 62 | 63 | - **Babel plugins production env not be set properly:** #57. 64 | 65 | 66 | # 0.6.1 (2015.10.17) 67 | 68 | #### Improvements 69 | 70 | - **Bump electron to v0.34.0** 71 | 72 | 73 | # 0.6.0 (2015.10.16) 74 | 75 | #### Breaking Changes 76 | 77 | - **From react-hot-loader to react-transform** 78 | 79 | 80 | # 0.5.2 (2015.10.15) 81 | 82 | #### Improvements 83 | 84 | - **Run tests with babel-register:** #29. 85 | 86 | 87 | # 0.5.1 (2015.10.12) 88 | 89 | #### Bug fixed 90 | 91 | - **Fix #51:** use `path.join(__dirname` instead of `./`. 92 | 93 | 94 | # 0.5.0 (2015.10.11) 95 | 96 | #### Improvements 97 | 98 | - **Simplify webpack config** see [#50](https://github.com/chentsulin/electron-react-boilerplate/pull/50). 99 | 100 | #### Breaking Changes 101 | 102 | - **webpack configs** 103 | - **port changed:** changed default port from 2992 to 3000. 104 | - **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`. 105 | 106 | 107 | # 0.4.3 (2015.9.22) 108 | 109 | #### Bug fixed 110 | 111 | - **Fix #45 zeromq crash:** bump version of `electron-prebuilt`. 112 | 113 | 114 | # 0.4.2 (2015.9.15) 115 | 116 | #### Bug fixed 117 | 118 | - **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1` 119 | 120 | 121 | # 0.4.1 (2015.9.11) 122 | 123 | #### Improvements 124 | 125 | - **use electron-prebuilt version for packaging (#33)** 126 | 127 | 128 | # 0.4.0 (2015.9.5) 129 | 130 | #### Improvements 131 | 132 | - **update dependencies** 133 | 134 | 135 | # 0.3.0 (2015.8.31) 136 | 137 | #### Improvements 138 | 139 | - **eslint-config-airbnb** 140 | 141 | 142 | # 0.2.10 (2015.8.27) 143 | 144 | #### Features 145 | 146 | - **custom placeholder icon** 147 | 148 | #### Improvements 149 | 150 | - **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer) 151 | 152 | 153 | # 0.2.9 (2015.8.18) 154 | 155 | #### Bug fixed 156 | 157 | - **Fix hot-reload** 158 | 159 | 160 | # 0.2.8 (2015.8.13) 161 | 162 | #### Improvements 163 | 164 | - **bump electron-debug** 165 | - **babelrc** 166 | - **organize webpack scripts** 167 | 168 | 169 | # 0.2.7 (2015.7.9) 170 | 171 | #### Bug fixed 172 | 173 | - **defaultProps:** fix typos. 174 | 175 | 176 | # 0.2.6 (2015.7.3) 177 | 178 | #### Features 179 | 180 | - **menu** 181 | 182 | #### Bug fixed 183 | 184 | - **package.js:** include webpack build. 185 | 186 | 187 | # 0.2.5 (2015.7.1) 188 | 189 | #### Features 190 | 191 | - **NPM Script:** support multi-platform 192 | - **package:** `--all` option 193 | 194 | 195 | # 0.2.4 (2015.6.9) 196 | 197 | #### Bug fixed 198 | 199 | - **Eslint:** typo, [#17](https://github.com/chentsulin/electron-react-boilerplate/issues/17) and improve `.eslintrc` 200 | 201 | 202 | # 0.2.3 (2015.6.3) 203 | 204 | #### Features 205 | 206 | - **Package Version:** use latest release electron version as default 207 | - **Ignore Large peerDependencies** 208 | 209 | #### Bug fixed 210 | 211 | - **Npm Script:** typo, [#6](https://github.com/chentsulin/electron-react-boilerplate/pull/6) 212 | - **Missing css:** [#7](https://github.com/chentsulin/electron-react-boilerplate/pull/7) 213 | 214 | 215 | # 0.2.2 (2015.6.2) 216 | 217 | #### Features 218 | 219 | - **electron-debug** 220 | 221 | #### Bug fixed 222 | 223 | - **Webpack:** add `.json` and `.node` to extensions for imitating node require. 224 | - **Webpack:** set `node_modules` to externals for native module support. 225 | 226 | 227 | # 0.2.1 (2015.5.30) 228 | 229 | #### Bug fixed 230 | 231 | - **Webpack:** #1, change build target to `atom`. 232 | 233 | 234 | # 0.2.0 (2015.5.30) 235 | 236 | #### Features 237 | 238 | - **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`. 239 | - **Support asar** 240 | - **Support icon** 241 | 242 | 243 | # 0.1.0 (2015.5.27) 244 | 245 | #### Features 246 | 247 | - **Webpack:** babel, react-hot, ... 248 | - **Flux:** actions, api, components, containers, stores.. 249 | - **Package:** darwin (osx), linux and win32 (windows) platform. 250 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "productName": "ElectronReact", 4 | "version": "0.7.1", 5 | "description": "Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development", 6 | "main": "main.js", 7 | "scripts": { 8 | "test": "better-npm-run test", 9 | "test-watch": "npm test -- --watch", 10 | "test-e2e": "better-npm-run test-e2e", 11 | "lint": "eslint app test *.js", 12 | "hot-server": "node server.js", 13 | "build": "better-npm-run build", 14 | "start": "better-npm-run start", 15 | "start-hot": "better-npm-run start-hot", 16 | "package": "better-npm-run package", 17 | "package-all": "npm run package -- --all", 18 | "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json" 19 | }, 20 | "betterScripts": { 21 | "start": { 22 | "command": "electron ./", 23 | "env": { 24 | "NODE_ENV": "production" 25 | } 26 | }, 27 | "start-hot": { 28 | "command": "electron ./", 29 | "env": { 30 | "HOT": 1, 31 | "NODE_ENV": "development" 32 | } 33 | }, 34 | "package": { 35 | "command": "node package.js", 36 | "env": { 37 | "NODE_ENV": "production" 38 | } 39 | }, 40 | "build": { 41 | "command": "webpack --config webpack.config.production.js --progress --profile --colors", 42 | "env": { 43 | "NODE_ENV": "production" 44 | } 45 | }, 46 | "test": { 47 | "command": "mocha --compilers js:babel-core/register --recursive --require ./test/setup.js test/**/*.spec.js", 48 | "env": { 49 | "NODE_ENV": "test" 50 | } 51 | }, 52 | "test-e2e": { 53 | "command": "mocha --compilers js:babel-core/register --require ./test/setup.js --require co-mocha ./test/e2e.js", 54 | "env": { 55 | "NODE_ENV": "test" 56 | } 57 | } 58 | }, 59 | "bin": { 60 | "electron": "./node_modules/.bin/electron" 61 | }, 62 | "repository": { 63 | "type": "git", 64 | "url": "git+https://github.com/chentsulin/electron-react-boilerplate.git" 65 | }, 66 | "author": { 67 | "name": "C. T. Lin", 68 | "email": "chentsulin@gmail.com", 69 | "url": "https://github.com/chentsulin" 70 | }, 71 | "license": "MIT", 72 | "bugs": { 73 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues" 74 | }, 75 | "keywords": [ 76 | "electron", 77 | "boilerplate", 78 | "react", 79 | "react-router", 80 | "flux", 81 | "webpack", 82 | "react-hot" 83 | ], 84 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme", 85 | "devDependencies": { 86 | "asar": "^0.9.0", 87 | "babel-core": "^6.3.26", 88 | "babel-eslint": "^5.0.0-beta6", 89 | "babel-loader": "^6.2.0", 90 | "babel-plugin-add-module-exports": "^0.1.2", 91 | "babel-polyfill": "^6.3.14", 92 | "babel-preset-es2015": "^6.3.13", 93 | "babel-preset-react": "^6.3.13", 94 | "babel-preset-react-hmre": "^1.0.1", 95 | "babel-preset-stage-0": "^6.3.13", 96 | "better-npm-run": "0.0.5", 97 | "bunyan": "^1.7.1", 98 | "chai": "^3.3.0", 99 | "chromedriver": "^2.19.0", 100 | "co-mocha": "^1.1.2", 101 | "css-loader": "^0.23.1", 102 | "css-modules-require-hook": "^2.0.2", 103 | "del": "^2.0.1", 104 | "electron-packager": "^5.2.0", 105 | "electron-prebuilt": "^0.36.9", 106 | "electron-rebuild": "^1.0.0", 107 | "electron-reload": "^0.2.0", 108 | "eslint": "^1.3.1", 109 | "eslint-config-airbnb": "^2.1.1", 110 | "eslint-plugin-react": "^3.13.1", 111 | "express": "^4.13.3", 112 | "extract-text-webpack-plugin": "^0.9.1", 113 | "fbjs-scripts": "^0.5.0", 114 | "file-loader": "^0.8.5", 115 | "jsdom": "^7.2.2", 116 | "minimist": "^1.2.0", 117 | "mocha": "^2.3.0", 118 | "node-libs-browser": ">= 0.4.0 <=0.6.0", 119 | "node-sass": "^3.4.2", 120 | "postcss": "^5.0.13", 121 | "postcss-modules-extract-imports": "^1.0.0", 122 | "postcss-modules-local-by-default": "^1.0.1", 123 | "postcss-modules-scope": "^1.0.0", 124 | "postcss-modules-values": "^1.1.1", 125 | "react-addons-test-utils": "^0.14.2", 126 | "redux-devtools": "^3.0.1", 127 | "redux-devtools-dock-monitor": "^1.0.1", 128 | "redux-devtools-log-monitor": "^1.0.1", 129 | "redux-logger": "^2.3.1", 130 | "sass-loader": "^3.1.2", 131 | "selenium-webdriver": "^2.48.2", 132 | "sinon": "^1.17.2", 133 | "style-loader": "^0.13.0", 134 | "url-loader": "^0.5.7", 135 | "webpack": "^1.12.1", 136 | "webpack-dev-middleware": "^1.2.0", 137 | "webpack-hot-middleware": "^2.4.1", 138 | "webpack-target-electron-renderer": "^0.3.0" 139 | }, 140 | "dependencies": { 141 | "bootstrap": "^3.3.6", 142 | "chromedriver": "^2.21.2", 143 | "classnames": "^2.2.3", 144 | "electron-debug": "^0.5.1", 145 | "font-awesome": "^4.4.0", 146 | "history": "^1.17.0", 147 | "isomorphic-fetch": "^2.2.1", 148 | "react": "^0.14.2", 149 | "react-bootstrap": "^0.28.3", 150 | "react-dom": "^0.14.2", 151 | "react-redux": "^4.0.5", 152 | "react-router": "^1.0.3", 153 | "redux": "^3.3.1", 154 | "redux-logger": "^2.5.0", 155 | "redux-promise": "^0.5.0", 156 | "redux-thunk": "^1.0.2" 157 | }, 158 | "devEngines": { 159 | "node": "4.x || 5.x", 160 | "npm": "2.x || 3.x" 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0 */ 2 | 'use strict'; 3 | 4 | const electron = require('electron'); 5 | const app = electron.app; 6 | const BrowserWindow = electron.BrowserWindow; 7 | const Menu = electron.Menu; 8 | const crashReporter = electron.crashReporter; 9 | const shell = electron.shell; 10 | let menu; 11 | let template; 12 | let mainWindow = null; 13 | 14 | 15 | crashReporter.start(); 16 | 17 | if (process.env.NODE_ENV === 'development') { 18 | require('electron-debug')(); 19 | } 20 | 21 | 22 | app.on('window-all-closed', () => { 23 | if (process.platform !== 'darwin') app.quit(); 24 | }); 25 | 26 | require('electron-reload')(`${__dirname}/app`, { 27 | electron: require('electron-prebuilt') 28 | }); 29 | 30 | app.on('ready', () => { 31 | mainWindow = new BrowserWindow({ width: 1280, height: 800 }); 32 | 33 | if (process.env.HOT) { 34 | mainWindow.loadURL(`file://${__dirname}/app/hot-dev-app.html`); 35 | } else { 36 | mainWindow.loadURL(`file://${__dirname}/app/app.html`); 37 | } 38 | 39 | mainWindow.on('closed', () => { 40 | mainWindow = null; 41 | }); 42 | 43 | if (process.env.NODE_ENV === 'development') { 44 | mainWindow.openDevTools(); 45 | } 46 | 47 | if (process.platform === 'darwin') { 48 | template = [{ 49 | label: 'Electron', 50 | submenu: [{ 51 | label: 'About ElectronReact', 52 | selector: 'orderFrontStandardAboutPanel:' 53 | }, { 54 | type: 'separator' 55 | }, { 56 | label: 'Services', 57 | submenu: [] 58 | }, { 59 | type: 'separator' 60 | }, { 61 | label: 'Hide ElectronReact', 62 | accelerator: 'Command+H', 63 | selector: 'hide:' 64 | }, { 65 | label: 'Hide Others', 66 | accelerator: 'Command+Shift+H', 67 | selector: 'hideOtherApplications:' 68 | }, { 69 | label: 'Show All', 70 | selector: 'unhideAllApplications:' 71 | }, { 72 | type: 'separator' 73 | }, { 74 | label: 'Quit', 75 | accelerator: 'Command+Q', 76 | click() { 77 | app.quit(); 78 | } 79 | }] 80 | }, { 81 | label: 'Edit', 82 | submenu: [{ 83 | label: 'Undo', 84 | accelerator: 'Command+Z', 85 | selector: 'undo:' 86 | }, { 87 | label: 'Redo', 88 | accelerator: 'Shift+Command+Z', 89 | selector: 'redo:' 90 | }, { 91 | type: 'separator' 92 | }, { 93 | label: 'Cut', 94 | accelerator: 'Command+X', 95 | selector: 'cut:' 96 | }, { 97 | label: 'Copy', 98 | accelerator: 'Command+C', 99 | selector: 'copy:' 100 | }, { 101 | label: 'Paste', 102 | accelerator: 'Command+V', 103 | selector: 'paste:' 104 | }, { 105 | label: 'Select All', 106 | accelerator: 'Command+A', 107 | selector: 'selectAll:' 108 | }] 109 | }, { 110 | label: 'View', 111 | submenu: (process.env.NODE_ENV === 'development') ? [{ 112 | label: 'Reload', 113 | accelerator: 'Command+R', 114 | click() { 115 | mainWindow.restart(); 116 | } 117 | }, { 118 | label: 'Toggle Full Screen', 119 | accelerator: 'Ctrl+Command+F', 120 | click() { 121 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 122 | } 123 | }, { 124 | label: 'Toggle Developer Tools', 125 | accelerator: 'Alt+Command+I', 126 | click() { 127 | mainWindow.toggleDevTools(); 128 | } 129 | }] : [{ 130 | label: 'Toggle Full Screen', 131 | accelerator: 'Ctrl+Command+F', 132 | click() { 133 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 134 | } 135 | }] 136 | }, { 137 | label: 'Window', 138 | submenu: [{ 139 | label: 'Minimize', 140 | accelerator: 'Command+M', 141 | selector: 'performMiniaturize:' 142 | }, { 143 | label: 'Close', 144 | accelerator: 'Command+W', 145 | selector: 'performClose:' 146 | }, { 147 | type: 'separator' 148 | }, { 149 | label: 'Bring All to Front', 150 | selector: 'arrangeInFront:' 151 | }] 152 | }, { 153 | label: 'Help', 154 | submenu: [{ 155 | label: 'Learn More', 156 | click() { 157 | shell.openExternal('http://electron.atom.io'); 158 | } 159 | }, { 160 | label: 'Documentation', 161 | click() { 162 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 163 | } 164 | }, { 165 | label: 'Community Discussions', 166 | click() { 167 | shell.openExternal('https://discuss.atom.io/c/electron'); 168 | } 169 | }, { 170 | label: 'Search Issues', 171 | click() { 172 | shell.openExternal('https://github.com/atom/electron/issues'); 173 | } 174 | }] 175 | }]; 176 | 177 | menu = Menu.buildFromTemplate(template); 178 | Menu.setApplicationMenu(menu); 179 | } else { 180 | template = [{ 181 | label: '&File', 182 | submenu: [{ 183 | label: '&Open', 184 | accelerator: 'Ctrl+O' 185 | }, { 186 | label: '&Close', 187 | accelerator: 'Ctrl+W', 188 | click() { 189 | mainWindow.close(); 190 | } 191 | }] 192 | }, { 193 | label: '&View', 194 | submenu: (process.env.NODE_ENV === 'development') ? [{ 195 | label: '&Reload', 196 | accelerator: 'Ctrl+R', 197 | click() { 198 | mainWindow.restart(); 199 | } 200 | }, { 201 | label: 'Toggle &Full Screen', 202 | accelerator: 'F11', 203 | click() { 204 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 205 | } 206 | }, { 207 | label: 'Toggle &Developer Tools', 208 | accelerator: 'Alt+Ctrl+I', 209 | click() { 210 | mainWindow.toggleDevTools(); 211 | } 212 | }] : [{ 213 | label: 'Toggle &Full Screen', 214 | accelerator: 'F11', 215 | click() { 216 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 217 | } 218 | }] 219 | }, { 220 | label: 'Help', 221 | submenu: [{ 222 | label: 'Learn More', 223 | click() { 224 | shell.openExternal('http://electron.atom.io'); 225 | } 226 | }, { 227 | label: 'Documentation', 228 | click() { 229 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 230 | } 231 | }, { 232 | label: 'Community Discussions', 233 | click() { 234 | shell.openExternal('https://discuss.atom.io/c/electron'); 235 | } 236 | }, { 237 | label: 'Search Issues', 238 | click() { 239 | shell.openExternal('https://github.com/atom/electron/issues'); 240 | } 241 | }] 242 | }]; 243 | menu = Menu.buildFromTemplate(template); 244 | mainWindow.setMenu(menu); 245 | } 246 | }); 247 | --------------------------------------------------------------------------------