├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── actions
│ └── .gitkeep
├── api
│ └── .gitkeep
├── app.global.css
├── app.html
├── app.icns
├── components
│ ├── Footer.css
│ ├── Footer.js
│ ├── Header.css
│ ├── Header.js
│ ├── HeaderLeft.css
│ ├── HeaderLeft.js
│ ├── HeaderMiddle.css
│ ├── HeaderMiddle.js
│ ├── HeaderRight.css
│ ├── HeaderRight.js
│ ├── Home.css
│ ├── Home.js
│ ├── WorkArea.css
│ ├── WorkArea.js
│ ├── WorkAreaCanvas.css
│ ├── WorkAreaCanvas.js
│ ├── WorkAreaCanvasLeft.css
│ ├── WorkAreaCanvasLeft.js
│ ├── WorkAreaCanvasRight.css
│ ├── WorkAreaCanvasRight.js
│ ├── WorkAreaNav.css
│ ├── WorkAreaNav.js
│ ├── WorkAreaNavBottom.css
│ ├── WorkAreaNavBottom.js
│ ├── WorkAreaNavTop.css
│ └── WorkAreaNavTop.js
├── containers
│ ├── App.js
│ └── HomePage.js
├── index.js
├── reducers
│ └── index.js
├── routes.js
├── store
│ ├── configureStore.development.js
│ ├── configureStore.js
│ └── configureStore.production.js
└── utils
│ └── .gitkeep
├── appveyor.yml
├── demo.gif
├── main.development.js
├── package.js
├── package.json
├── server.js
├── test
├── .eslintrc
├── actions
│ └── counter.spec.js
├── components
│ └── Counter.spec.js
├── containers
│ └── CounterPage.spec.js
├── e2e.js
├── example.js
├── reducers
│ └── counter.spec.js
└── setup.js
├── webpack.config.base.js
├── webpack.config.development.js
├── webpack.config.electron.js
├── webpack.config.eslint.js
├── webpack.config.node.js
└── webpack.config.production.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"],
3 | "plugins": ["add-module-exports"],
4 | "env": {
5 | "production": {
6 | "presets": ["react-optimize"],
7 | "plugins": [
8 | "babel-plugin-dev-expression"
9 | ]
10 | },
11 | "development": {
12 | "presets": ["react-hmre"]
13 | },
14 | "test": {
15 | "plugins": [
16 | ["webpack-loaders", { "config": "webpack.config.node.js", "verbose": false }]
17 | ]
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | main.js
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "env": {
5 | "browser": true,
6 | "mocha": true,
7 | "node": true
8 | },
9 | "rules": {
10 | "arrow-body-style": 0,
11 | "consistent-return": 0,
12 | "comma-dangle": 0,
13 | "no-use-before-define": 0,
14 | "import/no-unresolved": [2, { "ignore": ["electron"] }],
15 | "import/no-extraneous-dependencies": 0,
16 | "react/jsx-no-bind": 0,
17 | "react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx"] }],
18 | "react/prefer-stateless-function": 0,
19 | # https://github.com/eslint/eslint/issues/6274
20 | "generator-star-spacing": 0
21 | },
22 | "plugins": [
23 | "import",
24 | "react"
25 | ],
26 | "settings": {
27 | "import/resolver": {
28 | "webpack": {
29 | "config": "webpack.config.eslint.js"
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | # OSX
30 | .DS_Store
31 |
32 | # App packaged
33 | dist
34 | release
35 | main.js
36 | main.js.map
37 |
38 | jsconfig.json
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - 6
7 | - 5
8 | - 4
9 |
10 | cache:
11 | directories:
12 | - node_modules
13 |
14 | addons:
15 | apt:
16 | sources:
17 | - ubuntu-toolchain-r-test
18 | packages:
19 | - g++-4.8
20 | - icnsutils
21 | - graphicsmagick
22 | - xz-utils
23 | - xorriso
24 |
25 | install:
26 | - export CXX="g++-4.8"
27 | - npm install
28 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
29 |
30 | before_script:
31 | - export DISPLAY=:99.0
32 | - sh -e /etc/init.d/xvfb start &
33 | - sleep 3
34 |
35 | script:
36 | - npm run lint
37 | - npm run test
38 | - npm run build
39 | - npm run test-e2e
40 | - npm run package
41 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.10.0 (2016.4.18)
2 |
3 | #### Improvements
4 |
5 | - **Use Babel in main process with Webpack build:** [#201](https://github.com/chentsulin/electron-react-boilerplate/pull/201)
6 | - **Change targets to built-in support by webpack:** [#197](https://github.com/chentsulin/electron-react-boilerplate/pull/197)
7 | - **use es2015 syntax for webpack configs:** [#195](https://github.com/chentsulin/electron-react-boilerplate/pull/195)
8 | - **Open application when webcontent is loaded:** [#192](https://github.com/chentsulin/electron-react-boilerplate/pull/192)
9 | - **Upgraded dependencies**
10 |
11 | #### Bug fixed
12 |
13 | - **Fix `npm list electron-prebuilt` in package.js:** [#188](https://github.com/chentsulin/electron-react-boilerplate/pull/188)
14 |
15 |
16 | # 0.9.0 (2016.3.23)
17 |
18 | #### Improvements
19 |
20 | - **Added [redux-logger](https://github.com/fcomb/redux-logger)**
21 | - **Upgraded [react-router-redux](https://github.com/reactjs/react-router-redux) to v4**
22 | - **Upgraded dependencies**
23 | - **Added `npm run dev` command:** [#162](https://github.com/chentsulin/electron-react-boilerplate/pull/162)
24 | - **electron to v0.37.2**
25 |
26 | #### Breaking Changes
27 |
28 | - **css module as default:** [#154](https://github.com/chentsulin/electron-react-boilerplate/pull/154).
29 | - **set default NODE_ENV to production:** [#140](https://github.com/chentsulin/electron-react-boilerplate/issues/140)
30 |
31 |
32 | # 0.8.0 (2016.2.17)
33 |
34 | #### Bug fixed
35 |
36 | - **Fix lint errors**
37 | - **Fix Webpack publicPath for production builds**: [#119](https://github.com/chentsulin/electron-react-boilerplate/issues/119).
38 | - **package script now chooses correct OS icon extension**
39 |
40 | #### Improvements
41 |
42 | - **babel 6**
43 | - **Upgrade Dependencies**
44 | - **Enable CSS source maps**
45 | - **Add json-loader**: [#128](https://github.com/chentsulin/electron-react-boilerplate/issues/128).
46 | - **react-router 2.0 and react-router-redux 3.0**
47 |
48 |
49 | # 0.7.1 (2015.12.27)
50 |
51 | #### Bug fixed
52 |
53 | - **Fixed npm script on windows 10:** [#103](https://github.com/chentsulin/electron-react-boilerplate/issues/103).
54 | - **history and react-router version bump**: [#109](https://github.com/chentsulin/electron-react-boilerplate/issues/109), [#110](https://github.com/chentsulin/electron-react-boilerplate/pull/110).
55 |
56 | #### Improvements
57 |
58 | - **electron 0.36**
59 |
60 |
61 |
62 | # 0.7.0 (2015.12.16)
63 |
64 | #### Bug fixed
65 |
66 | - **Fixed process.env.NODE_ENV variable in webpack:** [#74](https://github.com/chentsulin/electron-react-boilerplate/pull/74).
67 | - **add missing object-assign**: [#76](https://github.com/chentsulin/electron-react-boilerplate/pull/76).
68 | - **packaging in npm@3:** [#77](https://github.com/chentsulin/electron-react-boilerplate/pull/77).
69 | - **compatibility in windows:** [#100](https://github.com/chentsulin/electron-react-boilerplate/pull/100).
70 | - **disable chrome debugger in production env:** [#102](https://github.com/chentsulin/electron-react-boilerplate/pull/102).
71 |
72 | #### Improvements
73 |
74 | - **redux**
75 | - **css-modules**
76 | - **upgrade to react-router 1.x**
77 | - **unit tests**
78 | - **e2e tests**
79 | - **travis-ci**
80 | - **upgrade to electron 0.35.x**
81 | - **use es2015**
82 | - **check dev engine for node and npm**
83 |
84 |
85 | # 0.6.5 (2015.11.7)
86 |
87 | #### Improvements
88 |
89 | - **Bump style-loader to 0.13**
90 | - **Bump css-loader to 0.22**
91 |
92 |
93 | # 0.6.4 (2015.10.27)
94 |
95 | #### Improvements
96 |
97 | - **Bump electron-debug to 0.3**
98 |
99 |
100 | # 0.6.3 (2015.10.26)
101 |
102 | #### Improvements
103 |
104 | - **Initialize ExtractTextPlugin once:** [#64](https://github.com/chentsulin/electron-react-boilerplate/issues/64).
105 |
106 |
107 | # 0.6.2 (2015.10.18)
108 |
109 | #### Bug fixed
110 |
111 | - **Babel plugins production env not be set properly:** [#57](https://github.com/chentsulin/electron-react-boilerplate/issues/57).
112 |
113 |
114 | # 0.6.1 (2015.10.17)
115 |
116 | #### Improvements
117 |
118 | - **Bump electron to v0.34.0**
119 |
120 |
121 | # 0.6.0 (2015.10.16)
122 |
123 | #### Breaking Changes
124 |
125 | - **From react-hot-loader to react-transform**
126 |
127 |
128 | # 0.5.2 (2015.10.15)
129 |
130 | #### Improvements
131 |
132 | - **Run tests with babel-register:** [#29](https://github.com/chentsulin/electron-react-boilerplate/issues/29).
133 |
134 |
135 | # 0.5.1 (2015.10.12)
136 |
137 | #### Bug fixed
138 |
139 | - **Fix #51:** use `path.join(__dirname` instead of `./`.
140 |
141 |
142 | # 0.5.0 (2015.10.11)
143 |
144 | #### Improvements
145 |
146 | - **Simplify webpack config** see [#50](https://github.com/chentsulin/electron-react-boilerplate/pull/50).
147 |
148 | #### Breaking Changes
149 |
150 | - **webpack configs**
151 | - **port changed:** changed default port from 2992 to 3000.
152 | - **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`.
153 |
154 |
155 | # 0.4.3 (2015.9.22)
156 |
157 | #### Bug fixed
158 |
159 | - **Fix #45 zeromq crash:** bump version of `electron-prebuilt`.
160 |
161 |
162 | # 0.4.2 (2015.9.15)
163 |
164 | #### Bug fixed
165 |
166 | - **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1`
167 |
168 |
169 | # 0.4.1 (2015.9.11)
170 |
171 | #### Improvements
172 |
173 | - **use electron-prebuilt version for packaging (#33)**
174 |
175 |
176 | # 0.4.0 (2015.9.5)
177 |
178 | #### Improvements
179 |
180 | - **update dependencies**
181 |
182 |
183 | # 0.3.0 (2015.8.31)
184 |
185 | #### Improvements
186 |
187 | - **eslint-config-airbnb**
188 |
189 |
190 | # 0.2.10 (2015.8.27)
191 |
192 | #### Features
193 |
194 | - **custom placeholder icon**
195 |
196 | #### Improvements
197 |
198 | - **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer)
199 |
200 |
201 | # 0.2.9 (2015.8.18)
202 |
203 | #### Bug fixed
204 |
205 | - **Fix hot-reload**
206 |
207 |
208 | # 0.2.8 (2015.8.13)
209 |
210 | #### Improvements
211 |
212 | - **bump electron-debug**
213 | - **babelrc**
214 | - **organize webpack scripts**
215 |
216 |
217 | # 0.2.7 (2015.7.9)
218 |
219 | #### Bug fixed
220 |
221 | - **defaultProps:** fix typos.
222 |
223 |
224 | # 0.2.6 (2015.7.3)
225 |
226 | #### Features
227 |
228 | - **menu**
229 |
230 | #### Bug fixed
231 |
232 | - **package.js:** include webpack build.
233 |
234 |
235 | # 0.2.5 (2015.7.1)
236 |
237 | #### Features
238 |
239 | - **NPM Script:** support multi-platform
240 | - **package:** `--all` option
241 |
242 |
243 | # 0.2.4 (2015.6.9)
244 |
245 | #### Bug fixed
246 |
247 | - **Eslint:** typo, [#17](https://github.com/chentsulin/electron-react-boilerplate/issues/17) and improve `.eslintrc`
248 |
249 |
250 | # 0.2.3 (2015.6.3)
251 |
252 | #### Features
253 |
254 | - **Package Version:** use latest release electron version as default
255 | - **Ignore Large peerDependencies**
256 |
257 | #### Bug fixed
258 |
259 | - **Npm Script:** typo, [#6](https://github.com/chentsulin/electron-react-boilerplate/pull/6)
260 | - **Missing css:** [#7](https://github.com/chentsulin/electron-react-boilerplate/pull/7)
261 |
262 |
263 | # 0.2.2 (2015.6.2)
264 |
265 | #### Features
266 |
267 | - **electron-debug**
268 |
269 | #### Bug fixed
270 |
271 | - **Webpack:** add `.json` and `.node` to extensions for imitating node require.
272 | - **Webpack:** set `node_modules` to externals for native module support.
273 |
274 |
275 | # 0.2.1 (2015.5.30)
276 |
277 | #### Bug fixed
278 |
279 | - **Webpack:** #1, change build target to `atom`.
280 |
281 |
282 | # 0.2.0 (2015.5.30)
283 |
284 | #### Features
285 |
286 | - **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`.
287 | - **Support asar**
288 | - **Support icon**
289 |
290 |
291 | # 0.1.0 (2015.5.27)
292 |
293 | #### Features
294 |
295 | - **Webpack:** babel, react-hot, ...
296 | - **Flux:** actions, api, components, containers, stores..
297 | - **Package:** darwin (osx), linux and win32 (windows) platform.
298 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present 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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Common Electron App Layout (with React) using Flexbox
2 |
3 | *Note: this repo is meant to show guidance on how to structure the UI in an Electron/React app using flexbox with a common approach. Use it as a guide to quickly get a UI together for you to modify to your app*
4 |
5 | ## What does it look like?
6 |
7 | 
8 |
9 | ## What should you do with this?
10 |
11 | Make it your own! :boom: Use this as a guide to structure the UI for you Electron/React app using flexbox.
12 |
13 | ## Credit
14 |
15 | This app was created from a clone of the amazing [electron-react-boilerplate](https://github.com/chentsulin/electron-react-boilerplate) repo. So many thanks to [chentsulin](https://github.com/chentsulin) for creating and maintaining this wonderful boilerplate!
16 |
--------------------------------------------------------------------------------
/app/actions/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trstringer/electron-flexbox-ui-layout/98d3c36e853ff2df17fc116a9fc15abfb46c82ea/app/actions/.gitkeep
--------------------------------------------------------------------------------
/app/api/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trstringer/electron-flexbox-ui-layout/98d3c36e853ff2df17fc116a9fc15abfb46c82ea/app/api/.gitkeep
--------------------------------------------------------------------------------
/app/app.global.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0 0 0 0;
3 | font-family: Arial, Helvetica, Helvetica Neue;
4 | }
5 |
--------------------------------------------------------------------------------
/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello Electron React!
6 |
7 |
15 |
16 |
17 |
18 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/app.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trstringer/electron-flexbox-ui-layout/98d3c36e853ff2df17fc116a9fc15abfb46c82ea/app/app.icns
--------------------------------------------------------------------------------
/app/components/Footer.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: row;
4 | background-color: #DCEDC8;
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Footer.css';
3 |
4 | const Footer = () => {
5 | return (
6 |
7 | Footer
8 |
9 | );
10 | };
11 |
12 | export default Footer;
13 |
--------------------------------------------------------------------------------
/app/components/Header.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: row;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Header.css';
3 | import HeaderRight from './HeaderRight';
4 | import HeaderMiddle from './HeaderMiddle';
5 | import HeaderLeft from './HeaderLeft';
6 |
7 | const Header = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default Header;
18 |
--------------------------------------------------------------------------------
/app/components/HeaderLeft.css:
--------------------------------------------------------------------------------
1 | .container {
2 | background-color: #9CCC65;
3 | }
4 |
--------------------------------------------------------------------------------
/app/components/HeaderLeft.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './HeaderLeft.css';
3 |
4 | const HeaderLeft = () => {
5 | return (
6 |
7 | Header Left
8 |
9 | );
10 | };
11 |
12 | export default HeaderLeft;
13 |
--------------------------------------------------------------------------------
/app/components/HeaderMiddle.css:
--------------------------------------------------------------------------------
1 | .container {
2 | background-color: #DCEDC8;
3 | flex: 1;
4 | text-align: center;
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/HeaderMiddle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './HeaderMiddle.css';
3 |
4 | const HeaderMiddle = () => {
5 | return (
6 |
7 | Header Middle
8 |
9 | );
10 | };
11 |
12 | export default HeaderMiddle;
13 |
--------------------------------------------------------------------------------
/app/components/HeaderRight.css:
--------------------------------------------------------------------------------
1 | .container {
2 | background-color: #8BC34A;
3 | }
4 |
--------------------------------------------------------------------------------
/app/components/HeaderRight.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './HeaderRight.css';
3 |
4 | const HeaderRight = () => {
5 | return (
6 |
7 | Header Right
8 |
9 | );
10 | };
11 |
12 | export default HeaderRight;
13 |
--------------------------------------------------------------------------------
/app/components/Home.css:
--------------------------------------------------------------------------------
1 | .container {
2 | position: fixed;
3 | display: flex;
4 | flex-direction: column;
5 | height: 100vh;
6 | width: 100vw;
7 | }
8 |
--------------------------------------------------------------------------------
/app/components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styles from './Home.css';
3 | import Header from './Header';
4 | import WorkArea from './WorkArea';
5 | import Footer from './Footer';
6 |
7 | export default class Home extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/components/WorkArea.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: row;
4 | flex: 1;
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/WorkArea.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WorkArea.css';
3 | import WorkAreaNav from './WorkAreaNav';
4 | import WorkAreaCanvas from './WorkAreaCanvas';
5 |
6 | const WorkArea = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default WorkArea;
16 |
--------------------------------------------------------------------------------
/app/components/WorkAreaCanvas.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: row;
4 | flex: 1;
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/WorkAreaCanvas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WorkAreaCanvas.css';
3 | import WorkAreaCanvasLeft from './WorkAreaCanvasLeft';
4 | import WorkAreaCanvasRight from './WorkAreaCanvasRight';
5 |
6 | const WorkAreaCanvas = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default WorkAreaCanvas;
16 |
--------------------------------------------------------------------------------
/app/components/WorkAreaCanvasLeft.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 1;
3 | background-color: #9CCC65;
4 | }
5 |
--------------------------------------------------------------------------------
/app/components/WorkAreaCanvasLeft.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WorkAreaCanvasLeft.css';
3 |
4 | const WorkAreaCanvasLeft = () => {
5 | return (
6 |
7 | Canvas Left
8 |
9 | );
10 | };
11 |
12 | export default WorkAreaCanvasLeft;
13 |
--------------------------------------------------------------------------------
/app/components/WorkAreaCanvasRight.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 1;
3 | background-color: #AED581;
4 | }
5 |
--------------------------------------------------------------------------------
/app/components/WorkAreaCanvasRight.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WorkAreaCanvasRight.css';
3 |
4 | const WorkAreaCanvasRight = () => {
5 | return (
6 |
7 | Canvas right
8 |
9 | );
10 | };
11 |
12 | export default WorkAreaCanvasRight;
13 |
--------------------------------------------------------------------------------
/app/components/WorkAreaNav.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | width: 180px;
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/WorkAreaNav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WorkAreaNav.css';
3 | import WorkAreaNavTop from './WorkAreaNavTop';
4 | import WorkAreaNavBottom from './WorkAreaNavBottom';
5 |
6 | const WorkAreaNav = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default WorkAreaNav;
16 |
--------------------------------------------------------------------------------
/app/components/WorkAreaNavBottom.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 1;
3 | background-color: #AED581;
4 | }
5 |
--------------------------------------------------------------------------------
/app/components/WorkAreaNavBottom.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WorkAreaNavBottom.css';
3 |
4 | const WorkAreaNavBottom = () => {
5 | return (
6 |
7 | Nav Bottom
8 |
9 | );
10 | };
11 |
12 | export default WorkAreaNavBottom;
13 |
--------------------------------------------------------------------------------
/app/components/WorkAreaNavTop.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 1;
3 | background-color: #7CB342;
4 | }
5 |
--------------------------------------------------------------------------------
/app/components/WorkAreaNavTop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WorkAreaNavTop.css';
3 |
4 | const WorkAreaNavTop = () => {
5 | return (
6 |
7 | Nav Top
8 |
9 | );
10 | };
11 |
12 | export default WorkAreaNavTop;
13 |
--------------------------------------------------------------------------------
/app/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | export default class App extends Component {
4 | static propTypes = {
5 | children: PropTypes.element.isRequired
6 | };
7 |
8 | render() {
9 | return (
10 |
11 | {this.props.children}
12 |
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/containers/HomePage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Home from '../components/Home';
3 |
4 | export default class HomePage extends Component {
5 | render() {
6 | return (
7 |
8 | );
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { Router, hashHistory } from 'react-router';
5 | import { syncHistoryWithStore } from 'react-router-redux';
6 | import routes from './routes';
7 | import configureStore from './store/configureStore';
8 | import './app.global.css';
9 |
10 | const store = configureStore();
11 | const history = syncHistoryWithStore(hashHistory, store);
12 |
13 | render(
14 |
15 |
16 | ,
17 | document.getElementById('root')
18 | );
19 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerReducer as routing } from 'react-router-redux';
3 |
4 | const rootReducer = combineReducers({
5 | routing
6 | });
7 |
8 | export default rootReducer;
9 |
--------------------------------------------------------------------------------
/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 |
6 |
7 | export default (
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/app/store/configureStore.development.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import createLogger from 'redux-logger';
4 | import { hashHistory } from 'react-router';
5 | import { routerMiddleware, push } from 'react-router-redux';
6 | import rootReducer from '../reducers';
7 |
8 | const actionCreators = {
9 | push,
10 | };
11 |
12 | const logger = createLogger({
13 | level: 'info',
14 | collapsed: true,
15 | });
16 |
17 | const router = routerMiddleware(hashHistory);
18 |
19 | const enhancer = compose(
20 | applyMiddleware(thunk, router, logger),
21 | window.devToolsExtension ?
22 | window.devToolsExtension({ actionCreators }) :
23 | noop => noop
24 | );
25 |
26 | export default function configureStore(initialState) {
27 | const store = createStore(rootReducer, initialState, enhancer);
28 |
29 | if (window.devToolsExtension) {
30 | window.devToolsExtension.updateStore(store);
31 | }
32 |
33 | if (module.hot) {
34 | module.hot.accept('../reducers', () =>
35 | store.replaceReducer(require('../reducers')) // eslint-disable-line global-require
36 | );
37 | }
38 |
39 | return store;
40 | }
41 |
--------------------------------------------------------------------------------
/app/store/configureStore.js:
--------------------------------------------------------------------------------
1 | if (process.env.NODE_ENV === 'production') {
2 | module.exports = require('./configureStore.production'); // eslint-disable-line global-require
3 | } else {
4 | module.exports = require('./configureStore.development'); // eslint-disable-line global-require
5 | }
6 |
--------------------------------------------------------------------------------
/app/store/configureStore.production.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { hashHistory } from 'react-router';
4 | import { routerMiddleware } from 'react-router-redux';
5 | import rootReducer from '../reducers';
6 |
7 | const router = routerMiddleware(hashHistory);
8 |
9 | const enhancer = applyMiddleware(thunk, router);
10 |
11 | export default function configureStore(initialState) {
12 | return createStore(rootReducer, initialState, enhancer);
13 | }
14 |
--------------------------------------------------------------------------------
/app/utils/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trstringer/electron-flexbox-ui-layout/98d3c36e853ff2df17fc116a9fc15abfb46c82ea/app/utils/.gitkeep
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | os: unstable
2 | cache:
3 | - node_modules
4 | environment:
5 | matrix:
6 | - nodejs_version: 6
7 | - nodejs_version: 5
8 | - nodejs_version: 4
9 | install:
10 | - ps: Install-Product node $env:nodejs_version
11 | - set CI=true
12 | - npm install -g npm@latest
13 | - set PATH=%APPDATA%\npm;%PATH%
14 | - npm install
15 | matrix:
16 | fast_finish: true
17 | build: off
18 | version: '{build}'
19 | shallow_clone: true
20 | clone_depth: 1
21 | test_script:
22 | - node --version
23 | - npm --version
24 | - npm run lint
25 | - npm run test
26 | - npm run build
27 | - npm run test-e2e
28 | - npm run package
29 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trstringer/electron-flexbox-ui-layout/98d3c36e853ff2df17fc116a9fc15abfb46c82ea/demo.gif
--------------------------------------------------------------------------------
/main.development.js:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, Menu, shell } from 'electron';
2 |
3 | let menu;
4 | let template;
5 | let mainWindow = null;
6 |
7 |
8 | if (process.env.NODE_ENV === 'development') {
9 | require('electron-debug')(); // eslint-disable-line global-require
10 | }
11 |
12 |
13 | app.on('window-all-closed', () => {
14 | if (process.platform !== 'darwin') app.quit();
15 | });
16 |
17 |
18 | const installExtensions = async () => {
19 | if (process.env.NODE_ENV === 'development') {
20 | const installer = require('electron-devtools-installer'); // eslint-disable-line global-require
21 |
22 | const extensions = [
23 | 'REACT_DEVELOPER_TOOLS',
24 | 'REDUX_DEVTOOLS'
25 | ];
26 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
27 | for (const name of extensions) {
28 | try {
29 | await installer.default(installer[name], forceDownload);
30 | } catch (e) {} // eslint-disable-line
31 | }
32 | }
33 | };
34 |
35 | app.on('ready', async () => {
36 | await installExtensions();
37 |
38 | mainWindow = new BrowserWindow({
39 | show: false,
40 | width: 1024,
41 | height: 728
42 | });
43 |
44 | mainWindow.loadURL(`file://${__dirname}/app/app.html`);
45 |
46 | mainWindow.webContents.on('did-finish-load', () => {
47 | mainWindow.show();
48 | mainWindow.focus();
49 | });
50 |
51 | mainWindow.on('closed', () => {
52 | mainWindow = null;
53 | });
54 |
55 | if (process.env.NODE_ENV === 'development') {
56 | mainWindow.openDevTools();
57 | mainWindow.webContents.on('context-menu', (e, props) => {
58 | const { x, y } = props;
59 |
60 | Menu.buildFromTemplate([{
61 | label: 'Inspect element',
62 | click() {
63 | mainWindow.inspectElement(x, y);
64 | }
65 | }]).popup(mainWindow);
66 | });
67 | }
68 |
69 | if (process.platform === 'darwin') {
70 | template = [{
71 | label: 'Electron',
72 | submenu: [{
73 | label: 'About ElectronReact',
74 | selector: 'orderFrontStandardAboutPanel:'
75 | }, {
76 | type: 'separator'
77 | }, {
78 | label: 'Services',
79 | submenu: []
80 | }, {
81 | type: 'separator'
82 | }, {
83 | label: 'Hide ElectronReact',
84 | accelerator: 'Command+H',
85 | selector: 'hide:'
86 | }, {
87 | label: 'Hide Others',
88 | accelerator: 'Command+Shift+H',
89 | selector: 'hideOtherApplications:'
90 | }, {
91 | label: 'Show All',
92 | selector: 'unhideAllApplications:'
93 | }, {
94 | type: 'separator'
95 | }, {
96 | label: 'Quit',
97 | accelerator: 'Command+Q',
98 | click() {
99 | app.quit();
100 | }
101 | }]
102 | }, {
103 | label: 'Edit',
104 | submenu: [{
105 | label: 'Undo',
106 | accelerator: 'Command+Z',
107 | selector: 'undo:'
108 | }, {
109 | label: 'Redo',
110 | accelerator: 'Shift+Command+Z',
111 | selector: 'redo:'
112 | }, {
113 | type: 'separator'
114 | }, {
115 | label: 'Cut',
116 | accelerator: 'Command+X',
117 | selector: 'cut:'
118 | }, {
119 | label: 'Copy',
120 | accelerator: 'Command+C',
121 | selector: 'copy:'
122 | }, {
123 | label: 'Paste',
124 | accelerator: 'Command+V',
125 | selector: 'paste:'
126 | }, {
127 | label: 'Select All',
128 | accelerator: 'Command+A',
129 | selector: 'selectAll:'
130 | }]
131 | }, {
132 | label: 'View',
133 | submenu: (process.env.NODE_ENV === 'development') ? [{
134 | label: 'Reload',
135 | accelerator: 'Command+R',
136 | click() {
137 | mainWindow.webContents.reload();
138 | }
139 | }, {
140 | label: 'Toggle Full Screen',
141 | accelerator: 'Ctrl+Command+F',
142 | click() {
143 | mainWindow.setFullScreen(!mainWindow.isFullScreen());
144 | }
145 | }, {
146 | label: 'Toggle Developer Tools',
147 | accelerator: 'Alt+Command+I',
148 | click() {
149 | mainWindow.toggleDevTools();
150 | }
151 | }] : [{
152 | label: 'Toggle Full Screen',
153 | accelerator: 'Ctrl+Command+F',
154 | click() {
155 | mainWindow.setFullScreen(!mainWindow.isFullScreen());
156 | }
157 | }]
158 | }, {
159 | label: 'Window',
160 | submenu: [{
161 | label: 'Minimize',
162 | accelerator: 'Command+M',
163 | selector: 'performMiniaturize:'
164 | }, {
165 | label: 'Close',
166 | accelerator: 'Command+W',
167 | selector: 'performClose:'
168 | }, {
169 | type: 'separator'
170 | }, {
171 | label: 'Bring All to Front',
172 | selector: 'arrangeInFront:'
173 | }]
174 | }, {
175 | label: 'Help',
176 | submenu: [{
177 | label: 'Learn More',
178 | click() {
179 | shell.openExternal('http://electron.atom.io');
180 | }
181 | }, {
182 | label: 'Documentation',
183 | click() {
184 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme');
185 | }
186 | }, {
187 | label: 'Community Discussions',
188 | click() {
189 | shell.openExternal('https://discuss.atom.io/c/electron');
190 | }
191 | }, {
192 | label: 'Search Issues',
193 | click() {
194 | shell.openExternal('https://github.com/atom/electron/issues');
195 | }
196 | }]
197 | }];
198 |
199 | menu = Menu.buildFromTemplate(template);
200 | Menu.setApplicationMenu(menu);
201 | } else {
202 | template = [{
203 | label: '&File',
204 | submenu: [{
205 | label: '&Open',
206 | accelerator: 'Ctrl+O'
207 | }, {
208 | label: '&Close',
209 | accelerator: 'Ctrl+W',
210 | click() {
211 | mainWindow.close();
212 | }
213 | }]
214 | }, {
215 | label: '&View',
216 | submenu: (process.env.NODE_ENV === 'development') ? [{
217 | label: '&Reload',
218 | accelerator: 'Ctrl+R',
219 | click() {
220 | mainWindow.webContents.reload();
221 | }
222 | }, {
223 | label: 'Toggle &Full Screen',
224 | accelerator: 'F11',
225 | click() {
226 | mainWindow.setFullScreen(!mainWindow.isFullScreen());
227 | }
228 | }, {
229 | label: 'Toggle &Developer Tools',
230 | accelerator: 'Alt+Ctrl+I',
231 | click() {
232 | mainWindow.toggleDevTools();
233 | }
234 | }] : [{
235 | label: 'Toggle &Full Screen',
236 | accelerator: 'F11',
237 | click() {
238 | mainWindow.setFullScreen(!mainWindow.isFullScreen());
239 | }
240 | }]
241 | }, {
242 | label: 'Help',
243 | submenu: [{
244 | label: 'Learn More',
245 | click() {
246 | shell.openExternal('http://electron.atom.io');
247 | }
248 | }, {
249 | label: 'Documentation',
250 | click() {
251 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme');
252 | }
253 | }, {
254 | label: 'Community Discussions',
255 | click() {
256 | shell.openExternal('https://discuss.atom.io/c/electron');
257 | }
258 | }, {
259 | label: 'Search Issues',
260 | click() {
261 | shell.openExternal('https://github.com/atom/electron/issues');
262 | }
263 | }]
264 | }];
265 | menu = Menu.buildFromTemplate(template);
266 | mainWindow.setMenu(menu);
267 | }
268 | });
269 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | /* eslint strict: 0, no-shadow: 0, no-unused-vars: 0, no-console: 0 */
2 | 'use strict';
3 |
4 | require('babel-polyfill');
5 | const os = require('os');
6 | const webpack = require('webpack');
7 | const electronCfg = require('./webpack.config.electron');
8 | const cfg = require('./webpack.config.production');
9 | const packager = require('electron-packager');
10 | const del = require('del');
11 | const exec = require('child_process').exec;
12 | const argv = require('minimist')(process.argv.slice(2));
13 | const pkg = require('./package.json');
14 |
15 | const deps = Object.keys(pkg.dependencies);
16 | const devDeps = Object.keys(pkg.devDependencies);
17 |
18 | const appName = argv.name || argv.n || pkg.productName;
19 | const shouldUseAsar = argv.asar || argv.a || false;
20 | const shouldBuildAll = argv.all || false;
21 |
22 |
23 | const DEFAULT_OPTS = {
24 | dir: './',
25 | name: appName,
26 | asar: shouldUseAsar,
27 | ignore: [
28 | '^/test($|/)',
29 | '^/release($|/)',
30 | '^/main.development.js'
31 | ].concat(devDeps.map(name => `/node_modules/${name}($|/)`))
32 | .concat(
33 | deps.filter(name => !electronCfg.externals.includes(name))
34 | .map(name => `/node_modules/${name}($|/)`)
35 | )
36 | };
37 |
38 | const icon = argv.icon || argv.i || 'app/app';
39 |
40 | if (icon) {
41 | DEFAULT_OPTS.icon = icon;
42 | }
43 |
44 | const version = argv.version || argv.v;
45 |
46 | if (version) {
47 | DEFAULT_OPTS.version = version;
48 | startPack();
49 | } else {
50 | // use the same version as the currently-installed electron-prebuilt
51 | exec('npm list electron --dev', (err, stdout) => {
52 | if (err) {
53 | DEFAULT_OPTS.version = '1.2.0';
54 | } else {
55 | DEFAULT_OPTS.version = stdout.split('electron@')[1].replace(/\s/g, '');
56 | }
57 |
58 | startPack();
59 | });
60 | }
61 |
62 |
63 | function build(cfg) {
64 | return new Promise((resolve, reject) => {
65 | webpack(cfg, (err, stats) => {
66 | if (err) return reject(err);
67 | resolve(stats);
68 | });
69 | });
70 | }
71 |
72 | async function startPack() {
73 | console.log('start pack...');
74 |
75 | try {
76 | await build(electronCfg);
77 | await build(cfg);
78 | const paths = await del('release');
79 |
80 | if (shouldBuildAll) {
81 | // build for all platforms
82 | const archs = ['ia32', 'x64'];
83 | const platforms = ['linux', 'win32', 'darwin'];
84 |
85 | platforms.forEach((plat) => {
86 | archs.forEach((arch) => {
87 | pack(plat, arch, log(plat, arch));
88 | });
89 | });
90 | } else {
91 | // build for current platform only
92 | pack(os.platform(), os.arch(), log(os.platform(), os.arch()));
93 | }
94 | } catch (error) {
95 | console.error(error);
96 | }
97 | }
98 |
99 | function pack(plat, arch, cb) {
100 | // there is no darwin ia32 electron
101 | if (plat === 'darwin' && arch === 'ia32') return;
102 |
103 | const iconObj = {
104 | icon: DEFAULT_OPTS.icon + (() => {
105 | let extension = '.png';
106 | if (plat === 'darwin') {
107 | extension = '.icns';
108 | } else if (plat === 'win32') {
109 | extension = '.ico';
110 | }
111 | return extension;
112 | })()
113 | };
114 |
115 | const opts = Object.assign({}, DEFAULT_OPTS, iconObj, {
116 | platform: plat,
117 | arch,
118 | prune: true,
119 | 'app-version': pkg.version || DEFAULT_OPTS.version,
120 | out: `release/${plat}-${arch}`
121 | });
122 |
123 | packager(opts, cb);
124 | }
125 |
126 |
127 | function log(plat, arch) {
128 | return (err, filepath) => {
129 | if (err) return console.error(err);
130 | console.log(`${plat}-${arch} finished!`);
131 | };
132 | }
133 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-react-boilerplate",
3 | "productName": "ElectronReact",
4 | "version": "0.10.0",
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": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha --compilers js:babel-register --recursive --require ./test/setup.js test/**/*.spec.js",
9 | "test-watch": "npm test -- --watch",
10 | "test-e2e": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha --compilers js:babel-register --require ./test/setup.js ./test/e2e.js",
11 | "lint": "eslint app test *.js",
12 | "hot-server": "node -r babel-register server.js",
13 | "build-main": "cross-env NODE_ENV=production node -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.electron.js --progress --profile --colors",
14 | "build-renderer": "cross-env NODE_ENV=production node -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.production.js --progress --profile --colors",
15 | "build": "npm run build-main && npm run build-renderer",
16 | "start": "cross-env NODE_ENV=production electron ./",
17 | "start-hot": "cross-env HOT=1 NODE_ENV=development electron -r babel-register -r babel-polyfill ./main.development",
18 | "package": "cross-env NODE_ENV=production node -r babel-register -r babel-polyfill package.js",
19 | "package-all": "npm run package -- --all",
20 | "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
21 | "dev": "concurrently --kill-others \"npm run hot-server\" \"npm run start-hot\""
22 | },
23 | "bin": {
24 | "electron": "./node_modules/.bin/electron"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/chentsulin/electron-react-boilerplate.git"
29 | },
30 | "author": {
31 | "name": "C. T. Lin",
32 | "email": "chentsulin@gmail.com",
33 | "url": "https://github.com/chentsulin"
34 | },
35 | "license": "MIT",
36 | "bugs": {
37 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues"
38 | },
39 | "keywords": [
40 | "electron",
41 | "boilerplate",
42 | "react",
43 | "react-router",
44 | "flux",
45 | "webpack",
46 | "react-hot"
47 | ],
48 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme",
49 | "devDependencies": {
50 | "asar": "^0.12.2",
51 | "babel-core": "^6.14.0",
52 | "babel-eslint": "^6.1.2",
53 | "babel-loader": "^6.2.5",
54 | "babel-plugin-add-module-exports": "^0.2.1",
55 | "babel-plugin-dev-expression": "^0.2.1",
56 | "babel-plugin-webpack-loaders": "^0.7.1",
57 | "babel-polyfill": "^6.13.0",
58 | "babel-preset-es2015": "^6.14.0",
59 | "babel-preset-react": "^6.11.1",
60 | "babel-preset-react-hmre": "^1.1.1",
61 | "babel-preset-react-optimize": "^1.0.1",
62 | "babel-preset-stage-0": "^6.5.0",
63 | "babel-register": "^6.14.0",
64 | "chai": "^3.5.0",
65 | "concurrently": "^2.2.0",
66 | "cross-env": "^2.0.0",
67 | "css-loader": "^0.24.0",
68 | "del": "^2.2.2",
69 | "devtron": "^1.3.0",
70 | "electron": "^1.3.4",
71 | "electron-devtools-installer": "^2.0.1",
72 | "electron-packager": "^7.7.0",
73 | "electron-rebuild": "^1.2.0",
74 | "eslint": "^3.3.1",
75 | "eslint-config-airbnb": "^10.0.1",
76 | "eslint-import-resolver-webpack": "^0.5.1",
77 | "eslint-plugin-import": "^1.14.0",
78 | "eslint-plugin-jsx-a11y": "^2.1.0",
79 | "eslint-plugin-react": "^6.1.2",
80 | "express": "^4.14.0",
81 | "extract-text-webpack-plugin": "^1.0.1",
82 | "fbjs-scripts": "^0.7.1",
83 | "jsdom": "^9.4.2",
84 | "json-loader": "^0.5.4",
85 | "minimist": "^1.2.0",
86 | "mocha": "^3.0.2",
87 | "node-libs-browser": "^1.0.0",
88 | "react-addons-test-utils": "^15.3.1",
89 | "redux-logger": "^2.6.1",
90 | "sinon": "^1.17.5",
91 | "spectron": "^3.3.0",
92 | "style-loader": "^0.13.1",
93 | "webpack": "^1.13.2",
94 | "webpack-dev-middleware": "^1.6.1",
95 | "webpack-hot-middleware": "^2.12.2",
96 | "webpack-merge": "^0.14.1"
97 | },
98 | "dependencies": {
99 | "css-modules-require-hook": "^4.0.2",
100 | "electron-debug": "^1.0.1",
101 | "font-awesome": "^4.6.3",
102 | "postcss": "^5.1.2",
103 | "react": "^15.3.1",
104 | "react-dom": "^15.3.1",
105 | "react-redux": "^4.4.5",
106 | "react-router": "^2.7.0",
107 | "react-router-redux": "^4.0.5",
108 | "redux": "^3.5.2",
109 | "redux-thunk": "^2.1.0",
110 | "source-map-support": "^0.4.2"
111 | },
112 | "devEngines": {
113 | "node": "4.x || 5.x || 6.x",
114 | "npm": "2.x || 3.x"
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console: 0 */
2 |
3 | import express from 'express';
4 | import webpack from 'webpack';
5 | import webpackDevMiddleware from 'webpack-dev-middleware';
6 | import webpackHotMiddleware from 'webpack-hot-middleware';
7 |
8 | import config from './webpack.config.development';
9 |
10 | const app = express();
11 | const compiler = webpack(config);
12 | const PORT = process.env.PORT || 3000;
13 |
14 | const wdm = webpackDevMiddleware(compiler, {
15 | publicPath: config.output.publicPath,
16 | stats: {
17 | colors: true
18 | }
19 | });
20 |
21 | app.use(wdm);
22 |
23 | app.use(webpackHotMiddleware(compiler));
24 |
25 | const server = app.listen(PORT, 'localhost', err => {
26 | if (err) {
27 | console.error(err);
28 | return;
29 | }
30 |
31 | console.log(`Listening at http://localhost:${PORT}`);
32 | });
33 |
34 | process.on('SIGTERM', () => {
35 | console.log('Stopping dev server');
36 | wdm.close();
37 | server.close(() => {
38 | process.exit(0);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "rules": {
6 | "no-unused-expressions": 0
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/actions/counter.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/counter';
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 |
--------------------------------------------------------------------------------
/test/components/Counter.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint no-unused-expressions: 0 */
2 | import { expect } from 'chai';
3 | import { spy } from 'sinon';
4 | import React from 'react';
5 | import {
6 | renderIntoDocument,
7 | scryRenderedDOMComponentsWithTag,
8 | findRenderedDOMComponentWithClass,
9 | Simulate
10 | } from 'react-addons-test-utils';
11 | import Counter from '../../app/components/Counter';
12 |
13 |
14 | function setup() {
15 | const actions = {
16 | increment: spy(),
17 | incrementIfOdd: spy(),
18 | incrementAsync: spy(),
19 | decrement: spy()
20 | };
21 | const component = renderIntoDocument();
22 | return {
23 | component,
24 | actions,
25 | buttons: scryRenderedDOMComponentsWithTag(component, 'button').map(button => button),
26 | p: findRenderedDOMComponentWithClass(component, 'counter')
27 | };
28 | }
29 |
30 |
31 | describe('Counter component', () => {
32 | it('should display count', () => {
33 | const { p } = setup();
34 | expect(p.textContent).to.match(/^1$/);
35 | });
36 |
37 | it('first button should call increment', () => {
38 | const { buttons, actions } = setup();
39 | Simulate.click(buttons[0]);
40 | expect(actions.increment.called).to.be.true;
41 | });
42 |
43 | it('second button should call decrement', () => {
44 | const { buttons, actions } = setup();
45 | Simulate.click(buttons[1]);
46 | expect(actions.decrement.called).to.be.true;
47 | });
48 |
49 | it('third button should call incrementIfOdd', () => {
50 | const { buttons, actions } = setup();
51 | Simulate.click(buttons[2]);
52 | expect(actions.incrementIfOdd.called).to.be.true;
53 | });
54 |
55 | it('fourth button should call incrementAsync', () => {
56 | const { buttons, actions } = setup();
57 | Simulate.click(buttons[3]);
58 | expect(actions.incrementAsync.called).to.be.true;
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/test/containers/CounterPage.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import React from 'react';
3 | import {
4 | renderIntoDocument,
5 | scryRenderedDOMComponentsWithTag,
6 | findRenderedDOMComponentWithClass,
7 | Simulate
8 | } from 'react-addons-test-utils';
9 | import { Provider } from 'react-redux';
10 | import CounterPage from '../../app/containers/CounterPage';
11 | import configureStore from '../../app/store/configureStore';
12 |
13 |
14 | function setup(initialState) {
15 | const store = configureStore(initialState);
16 | const app = renderIntoDocument(
17 |
18 |
19 |
20 | );
21 | return {
22 | app,
23 | buttons: scryRenderedDOMComponentsWithTag(app, 'button').map(button => button),
24 | p: findRenderedDOMComponentWithClass(app, 'counter')
25 | };
26 | }
27 |
28 |
29 | describe('containers', () => {
30 | describe('App', () => {
31 | it('should display initial count', () => {
32 | const { p } = setup();
33 | expect(p.textContent).to.match(/^0$/);
34 | });
35 |
36 | it('should display updated count after increment button click', () => {
37 | const { buttons, p } = setup();
38 | Simulate.click(buttons[0]);
39 | expect(p.textContent).to.match(/^1$/);
40 | });
41 |
42 | it('should display updated count after descrement button click', () => {
43 | const { buttons, p } = setup();
44 | Simulate.click(buttons[1]);
45 | expect(p.textContent).to.match(/^-1$/);
46 | });
47 |
48 | it('shouldnt change if even and if odd button clicked', () => {
49 | const { buttons, p } = setup();
50 | Simulate.click(buttons[2]);
51 | expect(p.textContent).to.match(/^0$/);
52 | });
53 |
54 | it('should change if odd and if odd button clicked', () => {
55 | const { buttons, p } = setup({ counter: 1 });
56 | Simulate.click(buttons[2]);
57 | expect(p.textContent).to.match(/^2$/);
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/test/e2e.js:
--------------------------------------------------------------------------------
1 | import { Application } from 'spectron';
2 | import { expect } from 'chai';
3 | import electronPath from 'electron';
4 | import homeStyles from '../app/components/Home.css';
5 | import counterStyles from '../app/components/Counter.css';
6 |
7 | const delay = time => new Promise(resolve => setTimeout(resolve, time));
8 |
9 | describe('main window', function spec() {
10 | this.timeout(5000);
11 |
12 | before(async () => {
13 | this.app = new Application({
14 | path: electronPath,
15 | args: ['.'],
16 | });
17 | return this.app.start();
18 | });
19 |
20 | after(() => {
21 | if (this.app && this.app.isRunning()) {
22 | return this.app.stop();
23 | }
24 | });
25 |
26 | const findCounter = () => this.app.client.element(`.${counterStyles.counter}`);
27 |
28 | const findButtons = async () => {
29 | const { value } = await this.app.client.elements(`.${counterStyles.btn}`);
30 | return value.map(btn => btn.ELEMENT);
31 | };
32 |
33 | it('should open window', async () => {
34 | const { client, browserWindow } = this.app;
35 |
36 | await client.waitUntilWindowLoaded();
37 | await delay(500);
38 | const title = await browserWindow.getTitle();
39 | expect(title).to.equal('Hello Electron React!');
40 | });
41 |
42 | it('should to Counter with click "to Counter" link', async () => {
43 | const { client } = this.app;
44 |
45 | await client.click(`.${homeStyles.container} > a`);
46 | expect(await findCounter().getText()).to.equal('0');
47 | });
48 |
49 | it('should display updated count after increment button click', async () => {
50 | const { client } = this.app;
51 |
52 | const buttons = await findButtons();
53 | await client.elementIdClick(buttons[0]); // +
54 | expect(await findCounter().getText()).to.equal('1');
55 | });
56 |
57 | it('should display updated count after descrement button click', async () => {
58 | const { client } = this.app;
59 |
60 | const buttons = await findButtons();
61 | await client.elementIdClick(buttons[1]); // -
62 | expect(await findCounter().getText()).to.equal('0');
63 | });
64 |
65 | it('shouldnt change if even and if odd button clicked', async () => {
66 | const { client } = this.app;
67 |
68 | const buttons = await findButtons();
69 | await client.elementIdClick(buttons[2]); // odd
70 | expect(await findCounter().getText()).to.equal('0');
71 | });
72 |
73 | it('should change if odd and if odd button clicked', async () => {
74 | const { client } = this.app;
75 |
76 | const buttons = await findButtons();
77 | await client.elementIdClick(buttons[0]); // +
78 | await client.elementIdClick(buttons[2]); // odd
79 | expect(await findCounter().getText()).to.equal('2');
80 | });
81 |
82 | it('should change if async button clicked and a second later', async () => {
83 | const { client } = this.app;
84 |
85 | const buttons = await findButtons();
86 | await client.elementIdClick(buttons[3]); // async
87 | expect(await findCounter().getText()).to.equal('2');
88 | await delay(1000);
89 | expect(await findCounter().getText()).to.equal('3');
90 | });
91 |
92 | it('should back to home if back button clicked', async () => {
93 | const { client } = this.app;
94 | await client.element(
95 | `.${counterStyles.backButton} > a`
96 | ).click();
97 |
98 | expect(
99 | await client.isExisting(`.${homeStyles.container}`)
100 | ).to.be.true;
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/test/example.js:
--------------------------------------------------------------------------------
1 | /* eslint func-names: 0 */
2 | import { expect } from 'chai';
3 |
4 |
5 | describe('description', () => {
6 | it('description', () => {
7 | expect(1 + 2).to.equal(3);
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/test/reducers/counter.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import counter from '../../app/reducers/counter';
3 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../../app/actions/counter';
4 |
5 |
6 | describe('reducers', () => {
7 | describe('counter', () => {
8 | it('should handle initial state', () => {
9 | expect(counter(undefined, {})).to.equal(0);
10 | });
11 |
12 | it('should handle INCREMENT_COUNTER', () => {
13 | expect(counter(1, { type: INCREMENT_COUNTER })).to.equal(2);
14 | });
15 |
16 | it('should handle DECREMENT_COUNTER', () => {
17 | expect(counter(1, { type: DECREMENT_COUNTER })).to.equal(0);
18 | });
19 |
20 | it('should handle unknown action type', () => {
21 | expect(counter(1, { type: 'unknown' })).to.equal(1);
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import { jsdom } from 'jsdom';
3 |
4 | global.document = jsdom('');
5 | global.window = document.defaultView;
6 | global.navigator = global.window.navigator;
7 | window.localStorage = window.sessionStorage = {
8 | getItem(key) {
9 | return this[key];
10 | },
11 | setItem(key, value) {
12 | this[key] = value;
13 | },
14 | removeItem(key) {
15 | this[key] = undefined;
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | export default {
4 | module: {
5 | loaders: [{
6 | test: /\.jsx?$/,
7 | loaders: ['babel-loader'],
8 | exclude: /node_modules/
9 | }, {
10 | test: /\.json$/,
11 | loader: 'json-loader'
12 | }]
13 | },
14 | output: {
15 | path: path.join(__dirname, 'dist'),
16 | filename: 'bundle.js',
17 | libraryTarget: 'commonjs2'
18 | },
19 | resolve: {
20 | extensions: ['', '.js', '.jsx', '.json'],
21 | packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main']
22 | },
23 | plugins: [
24 |
25 | ],
26 | externals: [
27 | // put your node 3rd party libraries which can't be built with webpack here
28 | // (mysql, mongodb, and so on..)
29 | ]
30 | };
31 |
--------------------------------------------------------------------------------
/webpack.config.development.js:
--------------------------------------------------------------------------------
1 | /* eslint max-len: 0 */
2 | import webpack from 'webpack';
3 | import merge from 'webpack-merge';
4 | import baseConfig from './webpack.config.base';
5 |
6 | const port = process.env.PORT || 3000;
7 |
8 | export default merge(baseConfig, {
9 | debug: true,
10 |
11 | devtool: 'cheap-module-eval-source-map',
12 |
13 | entry: [
14 | `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
15 | 'babel-polyfill',
16 | './app/index'
17 | ],
18 |
19 | output: {
20 | publicPath: `http://localhost:${port}/dist/`
21 | },
22 |
23 | module: {
24 | loaders: [
25 | {
26 | test: /\.global\.css$/,
27 | loaders: [
28 | 'style-loader',
29 | 'css-loader?sourceMap'
30 | ]
31 | },
32 |
33 | {
34 | test: /^((?!\.global).)*\.css$/,
35 | loaders: [
36 | 'style-loader',
37 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
38 | ]
39 | }
40 | ]
41 | },
42 |
43 | plugins: [
44 | new webpack.HotModuleReplacementPlugin(),
45 | new webpack.NoErrorsPlugin(),
46 | new webpack.DefinePlugin({
47 | 'process.env.NODE_ENV': JSON.stringify('development')
48 | })
49 | ],
50 |
51 | target: 'electron-renderer'
52 | });
53 |
--------------------------------------------------------------------------------
/webpack.config.electron.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import merge from 'webpack-merge';
3 | import baseConfig from './webpack.config.base';
4 |
5 | export default merge(baseConfig, {
6 | devtool: 'source-map',
7 |
8 | entry: ['babel-polyfill', './main.development'],
9 |
10 | output: {
11 | path: __dirname,
12 | filename: './main.js'
13 | },
14 |
15 | plugins: [
16 | new webpack.optimize.UglifyJsPlugin({
17 | compressor: {
18 | warnings: false
19 | }
20 | }),
21 | new webpack.BannerPlugin(
22 | 'require("source-map-support").install();',
23 | { raw: true, entryOnly: false }
24 | ),
25 | new webpack.DefinePlugin({
26 | 'process.env': {
27 | NODE_ENV: JSON.stringify('production')
28 | }
29 | })
30 | ],
31 |
32 | target: 'electron-main',
33 |
34 | node: {
35 | __dirname: false,
36 | __filename: false
37 | },
38 |
39 | externals: [
40 | 'font-awesome',
41 | 'source-map-support'
42 | ]
43 | });
44 |
--------------------------------------------------------------------------------
/webpack.config.eslint.js:
--------------------------------------------------------------------------------
1 | require('babel-register');
2 |
3 | module.exports = require('./webpack.config.development');
4 |
--------------------------------------------------------------------------------
/webpack.config.node.js:
--------------------------------------------------------------------------------
1 | // for babel-plugin-webpack-loaders
2 | require('babel-register');
3 | const devConfigs = require('./webpack.config.development');
4 |
5 | module.exports = {
6 | output: {
7 | libraryTarget: 'commonjs2'
8 | },
9 | module: {
10 | loaders: devConfigs.module.loaders.slice(1) // remove babel-loader
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
3 | import merge from 'webpack-merge';
4 | import baseConfig from './webpack.config.base';
5 |
6 | const config = merge(baseConfig, {
7 | devtool: 'cheap-module-source-map',
8 |
9 | entry: [
10 | 'babel-polyfill',
11 | './app/index'
12 | ],
13 |
14 | output: {
15 | publicPath: '../dist/'
16 | },
17 |
18 | module: {
19 | loaders: [
20 | {
21 | test: /\.global\.css$/,
22 | loader: ExtractTextPlugin.extract(
23 | 'style-loader',
24 | 'css-loader'
25 | )
26 | },
27 |
28 | {
29 | test: /^((?!\.global).)*\.css$/,
30 | loader: ExtractTextPlugin.extract(
31 | 'style-loader',
32 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
33 | )
34 | }
35 | ]
36 | },
37 |
38 | plugins: [
39 | new webpack.optimize.OccurrenceOrderPlugin(),
40 | new webpack.DefinePlugin({
41 | 'process.env.NODE_ENV': JSON.stringify('production')
42 | }),
43 | new webpack.optimize.UglifyJsPlugin({
44 | compressor: {
45 | screw_ie8: true,
46 | warnings: false
47 | }
48 | }),
49 | new ExtractTextPlugin('style.css', { allChunks: true })
50 | ],
51 |
52 | target: 'electron-renderer'
53 | });
54 |
55 | export default config;
56 |
--------------------------------------------------------------------------------