├── 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 |
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 |
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 |
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 |
21 |
22 |
23 | Directors
24 |
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 |
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 |
27 |
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 | | Amout of money |
34 | 2000 |
35 |
36 |
37 | | Flash |
38 | Allans Flash |
39 |
40 | {downloads}
41 |
42 |
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 |
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 | 
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 |
--------------------------------------------------------------------------------