├── .github └── FUNDING.yml ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── components ├── app.jsx └── pages │ ├── AboutPage.js │ ├── ContactPage.js │ ├── HomePage.js │ └── index.js ├── core ├── app-menu │ ├── index.js │ ├── menu-mac.js │ └── menu-win.js ├── modules │ ├── devtools │ │ └── index.js │ ├── extra-pages │ │ ├── dist │ │ │ └── components │ │ │ │ ├── header.js │ │ │ │ ├── page1.js │ │ │ │ └── page2.js │ │ ├── index.js │ │ └── src │ │ │ └── components │ │ │ ├── header.jsx │ │ │ ├── page1.jsx │ │ │ └── page2.jsx │ ├── help-md │ │ ├── content │ │ │ ├── folder │ │ │ │ └── page2.md │ │ │ ├── index.md │ │ │ └── page1.md │ │ ├── dist │ │ │ └── components │ │ │ │ └── help.js │ │ ├── index.js │ │ └── src │ │ │ └── components │ │ │ └── help.jsx │ └── index.js └── runtime.js ├── dist ├── app.css └── app.js ├── img └── electron-react-osx.png ├── index.html ├── less └── app.less ├── main.js └── package.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: DenysVuika 4 | -------------------------------------------------------------------------------- /.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 | # Bower 30 | bower_components 31 | 32 | # OSX 33 | .DS_Store 34 | 35 | .module-cache 36 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "jquery": true 4 | }, 5 | "esnext": true 6 | } 7 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = function (grunt) { 4 | 5 | grunt.initConfig({ 6 | 7 | watch: { 8 | content: { 9 | files: [ 10 | 'components/**/*.js*', 11 | 'app.js', 12 | 'index.html', 13 | 'main.js' 14 | ], 15 | tasks: ['browserify'], 16 | options: { 17 | livereload: true 18 | } 19 | }, 20 | less: { 21 | files: 'less/**/*.less', 22 | tasks: ['less:dev'], 23 | options: { 24 | livereload: true 25 | } 26 | } 27 | }, 28 | 29 | less: { 30 | dev: { 31 | files: { 32 | "dist/app.css": "less/app.less" 33 | } 34 | }, 35 | dist: { 36 | options: { 37 | compress: true 38 | }, 39 | files: { 40 | "dist/app.min.css": "less/app.less" 41 | } 42 | } 43 | }, 44 | 45 | browserify: { 46 | options: { 47 | debug: true, 48 | transform: ['babelify'] 49 | }, 50 | app: { 51 | src: 'components/app.jsx', 52 | dest: 'dist/app.js' 53 | } 54 | }, 55 | 56 | uglify: { 57 | options: { 58 | mangle: true, 59 | sourceMap: true 60 | }, 61 | app: { 62 | files: { 63 | 'dist/app.min.js': 'dist/app.js' 64 | } 65 | } 66 | }, 67 | 68 | react: { 69 | 'extra-pages': { 70 | files: [ 71 | { 72 | expand: true, 73 | cwd: 'core/modules/extra-pages/src/components', 74 | src: ['**/*.jsx'], 75 | dest: 'core/modules/extra-pages/dist/components', 76 | ext: '.js' 77 | } 78 | ] 79 | }, 80 | 'help-md': { 81 | files: [ 82 | { 83 | expand: true, 84 | cwd: 'core/modules/help-md/src/components', 85 | src: ['**/*.jsx'], 86 | dest: 'core/modules/help-md/dist/components', 87 | ext: '.js' 88 | } 89 | ] 90 | } 91 | } 92 | 93 | }); 94 | 95 | grunt.loadNpmTasks('grunt-browserify'); 96 | grunt.loadNpmTasks('grunt-contrib-watch'); 97 | grunt.loadNpmTasks('grunt-contrib-less'); 98 | grunt.loadNpmTasks('grunt-contrib-uglify'); 99 | grunt.loadNpmTasks('grunt-react'); 100 | 101 | grunt.registerTask('default', [ 102 | 'less:dev', 103 | 'browserify', 104 | 'react' 105 | ]); 106 | 107 | grunt.registerTask('serve', [ 108 | 'less:dev', 109 | 'browserify', 110 | 'react', 111 | 'watch' 112 | ]); 113 | 114 | grunt.registerTask('dist', [ 115 | 'less:dist', 116 | 'browserify', 117 | 'uglify', 118 | 'react' 119 | ]); 120 | 121 | grunt.registerTask('modules', [ 122 | 'react' 123 | ]); 124 | 125 | }; 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Denis Vuyka 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 | # electron-react 2 | Project template for Electron with React support. 3 | 4 | [![Dependency Status](https://gemnasium.com/DenisVuyka/electron-react.svg)](https://gemnasium.com/DenisVuyka/electron-react) 5 | 6 | ## Introduction 7 | 8 | This template provides the following features pre-configured and ready to be developed with: 9 | 10 | - Prebuilt version of [Electron](https://github.com/atom/electron) (former Atom Shell) 11 | - React support (es6) 12 | - [React](https://facebook.github.io/react/index.html) 13 | - [React Bootstrap](eact-bootstrap.github.io) 14 | - [React Router](https://github.com/rackt/react-router) 15 | - [Font Awesome](http://fontawesome.io) 16 | - [LESS](http://lesscss.org) support 17 | 18 | Additional features for development: 19 | 20 | - [Grunt](http://gruntjs.com) tasks 21 | - Compiling LESS stylesheets 22 | - Compiling and bundling React components 23 | - Change watching and live reload of the content 24 | - [Bower](http://bower.io) settings 25 | - Useful NPM scripts 26 | 27 | Default template also provides basic application menu integration for Windows and OS X, together with navigation to demo Routes (React Router) by means of application menu items and cross-process messaging support provided by Electron shell. 28 | 29 | ![electron-react-osx](img/electron-react-osx.png) 30 | 31 | ## Getting started 32 | 33 | ### Installing dependencies 34 | 35 | ```bash 36 | bower install 37 | npm install 38 | ``` 39 | 40 | ### Bulding project 41 | 42 | Development 43 | 44 | ```bash 45 | grunt 46 | ``` 47 | 48 | Release 49 | 50 | ```bash 51 | grunt dist 52 | ``` 53 | 54 | ### Live Reload 55 | 56 | You may want running this task on a separate terminal or command prompt instance: 57 | 58 | ```bash 59 | grunt serve 60 | ``` 61 | 62 | For development purposes `index.html` already contains reference to live reload scripts. Every time React components, less stylesheets or other web-related files are changed the application will be automatically rebuilt and reloaded. 63 | 64 | ```html 65 | 66 | ``` 67 | 68 | ### Running with Electron 69 | 70 | You may want running this task on a separate terminal or command prompt instance: 71 | 72 | On Windows: 73 | 74 | ```cmd 75 | npm run win 76 | ``` 77 | 78 | On OS X: 79 | 80 | ```bash 81 | npm run osx 82 | ``` 83 | 84 | This command will launch a prebuilt version of Electron with the current project. During development process it is recommended running Live Reload feature prior to using Electron. 85 | 86 | ## Typical development process 87 | 88 | - Run `bower install` and `npm install` 89 | - Run `grunt serve` on a separate terminal tab (or command prompt) to build the project and start watching files automatically 90 | - Run `npm run win` or `npm run osx` on a separate terminal tab (or command prompt) to run Electron with the current project 91 | - Start changing or adding the code, it will be automatically rebuilt on background and your Electron window will reload once rebuilding is complete 92 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react", 3 | "main": "app.js", 4 | "version": "0.1.0", 5 | "authors": [ 6 | "Denis Vuyka " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "jquery": "~2.1.4", 18 | "bootstrap": "~3.3.4", 19 | "font-awesome": "~4.4.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Router from 'react-router'; 3 | import { RouteHandler, Route, Navigation } from 'react-router'; 4 | import { Nav, Navbar } from 'react-bootstrap'; 5 | import { NavItemLink } from 'react-router-bootstrap'; 6 | import { HomePage, AboutPage, ContactPage } from './pages'; 7 | 8 | var remote = window.require('remote'); 9 | var runtime = remote.require('./core/runtime'); 10 | 11 | var _routes = runtime.routes.map(function (r) { 12 | var handler = window.require(r.handler); 13 | return ; 14 | }); 15 | 16 | var _navbar = runtime.routes.filter(function (r) { 17 | return r.navbar; 18 | }); 19 | 20 | const App = React.createClass({ 21 | mixins: [ Navigation ], 22 | 23 | componentDidMount() { 24 | var ipc = window.require('ipc'); 25 | ipc.on('transitionTo', function(routeName) { 26 | //this.transitionTo(routeName, { the: 'params' }, { the: 'query' }); 27 | this.transitionTo(routeName); 28 | }.bind(this)); 29 | }, 30 | 31 | render() { 32 | var links = _navbar.map(function (r) { 33 | return ( 34 | {r.text} 35 | ); 36 | }); 37 | return ( 38 |
39 | 40 | 48 | 49 | 50 | 51 |
52 | ); 53 | } 54 | }); 55 | 56 | var routes = ( 57 | 58 | 59 | 60 | 61 | { _routes } 62 | 63 | ); 64 | 65 | Router.run(routes, function (Handler) { 66 | React.render(, document.body); 67 | }); 68 | -------------------------------------------------------------------------------- /components/pages/AboutPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class AboutPage extends React.Component { 4 | 5 | render() { 6 | return ( 7 |
About Page
8 | ); 9 | } 10 | 11 | } 12 | 13 | export default AboutPage; 14 | -------------------------------------------------------------------------------- /components/pages/ContactPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class ContactPage extends React.Component { 4 | 5 | render() { 6 | return ( 7 |
Contact Page
8 | ); 9 | } 10 | 11 | } 12 | 13 | export default ContactPage; 14 | -------------------------------------------------------------------------------- /components/pages/HomePage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class HomePage extends React.Component { 4 | 5 | render() { 6 | return ( 7 |
Home Page
8 | ); 9 | } 10 | 11 | } 12 | 13 | export default HomePage; 14 | -------------------------------------------------------------------------------- /components/pages/index.js: -------------------------------------------------------------------------------- 1 | import HomePage from './HomePage'; 2 | import AboutPage from './AboutPage'; 3 | import ContactPage from './ContactPage'; 4 | 5 | export default { 6 | HomePage, 7 | AboutPage, 8 | ContactPage 9 | }; 10 | -------------------------------------------------------------------------------- /core/app-menu/index.js: -------------------------------------------------------------------------------- 1 | var menu = null; 2 | 3 | if (process.platform === 'darwin') { 4 | menu = require('./menu-mac'); 5 | } else { 6 | menu = require('./menu-win'); 7 | } 8 | 9 | module.exports = menu; 10 | -------------------------------------------------------------------------------- /core/app-menu/menu-mac.js: -------------------------------------------------------------------------------- 1 | var app = require('app'), 2 | BrowserWindow = require('browser-window'), 3 | runtime = require('../runtime'); 4 | 5 | function getMainWindow () { 6 | var id = runtime.windowId; 7 | 8 | if (id) { 9 | var instance = BrowserWindow.fromId(id); 10 | return instance; 11 | } 12 | 13 | return null; 14 | } 15 | 16 | var _App = { 17 | label: 'Electron', 18 | submenu: [ 19 | { 20 | label: 'About Electron', 21 | selector: 'orderFrontStandardAboutPanel:' 22 | }, 23 | { 24 | type: 'separator' 25 | }, 26 | { 27 | label: 'Services', 28 | submenu: [] 29 | }, 30 | { 31 | type: 'separator' 32 | }, 33 | { 34 | label: 'Hide Electron', 35 | accelerator: 'Command+H', 36 | selector: 'hide:' 37 | }, 38 | { 39 | label: 'Hide Others', 40 | accelerator: 'Command+Shift+H', 41 | selector: 'hideOtherApplications:' 42 | }, 43 | { 44 | label: 'Show All', 45 | selector: 'unhideAllApplications:' 46 | }, 47 | { 48 | type: 'separator' 49 | }, 50 | { 51 | label: 'Quit', 52 | accelerator: 'Command+Q', 53 | click: function () { 54 | app.quit(); 55 | } 56 | }, 57 | ] 58 | }; 59 | 60 | var _Edit = { 61 | label: 'Edit', 62 | submenu: [ 63 | { 64 | label: 'Undo', 65 | accelerator: 'Command+Z', 66 | selector: 'undo:' 67 | }, 68 | { 69 | label: 'Redo', 70 | accelerator: 'Shift+Command+Z', 71 | selector: 'redo:' 72 | }, 73 | { 74 | type: 'separator' 75 | }, 76 | { 77 | label: 'Cut', 78 | accelerator: 'Command+X', 79 | selector: 'cut:' 80 | }, 81 | { 82 | label: 'Copy', 83 | accelerator: 'Command+C', 84 | selector: 'copy:' 85 | }, 86 | { 87 | label: 'Paste', 88 | accelerator: 'Command+V', 89 | selector: 'paste:' 90 | }, 91 | { 92 | label: 'Select All', 93 | accelerator: 'Command+A', 94 | selector: 'selectAll:' 95 | }, 96 | ] 97 | }; 98 | 99 | var _Page = { 100 | label: 'Page', 101 | submenu: [ 102 | { 103 | label: 'Home', 104 | click: function () { 105 | var mainWindow = getMainWindow(); 106 | if (mainWindow) { 107 | mainWindow.webContents.send('transitionTo', 'home'); 108 | } 109 | } 110 | }, 111 | { 112 | label: 'About', 113 | click: function () { 114 | var mainWindow = getMainWindow(); 115 | if (mainWindow) { 116 | mainWindow.webContents.send('transitionTo', 'about'); 117 | } 118 | } 119 | }, 120 | { 121 | label: 'Contact', 122 | click: function () { 123 | var mainWindow = getMainWindow(); 124 | if (mainWindow) { 125 | mainWindow.webContents.send('transitionTo', 'contact'); 126 | } 127 | } 128 | } 129 | ] 130 | }; 131 | 132 | var _View = { 133 | label: 'View', 134 | submenu: [ 135 | { 136 | label: 'Toggle Fullscreen', 137 | click: function () { 138 | var mainWindow = getMainWindow(); 139 | if (mainWindow) { 140 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 141 | } 142 | } 143 | } 144 | ] 145 | }; 146 | 147 | var _Window = { 148 | label: 'Window', 149 | submenu: [ 150 | { 151 | label: 'Minimize', 152 | accelerator: 'Command+M', 153 | selector: 'performMiniaturize:' 154 | }, 155 | { 156 | label: 'Close', 157 | accelerator: 'Command+W', 158 | selector: 'performClose:' 159 | }, 160 | { 161 | type: 'separator' 162 | }, 163 | { 164 | label: 'Bring All to Front', 165 | selector: 'arrangeInFront:' 166 | }, 167 | ] 168 | }; 169 | 170 | var menu = { 171 | App: _App, 172 | Edit: _Edit, 173 | Page: _Page, 174 | View: _View, 175 | Window: _Window, 176 | template: [ 177 | _App, 178 | _Edit, 179 | _Page, 180 | _View, 181 | _Window 182 | ] 183 | }; 184 | 185 | module.exports = menu; 186 | -------------------------------------------------------------------------------- /core/app-menu/menu-win.js: -------------------------------------------------------------------------------- 1 | var app = require('app'), 2 | BrowserWindow = require('browser-window'), 3 | runtime = require('../runtime'); 4 | 5 | function getMainWindow() { 6 | var id = runtime.windowId; 7 | 8 | if (id) { 9 | var instance = BrowserWindow.fromId(id); 10 | return instance; 11 | } 12 | 13 | return null; 14 | } 15 | 16 | var _File = { 17 | label: '&File', 18 | submenu: [ 19 | { 20 | label: '&Open', 21 | accelerator: 'Ctrl+O', 22 | }, 23 | { 24 | label: '&Close', 25 | accelerator: 'Ctrl+W', 26 | click: function() { 27 | var mainWindow = getMainWindow(); 28 | if (mainWindow) { 29 | mainWindow.close(); 30 | } 31 | } 32 | }, 33 | ] 34 | }; 35 | 36 | var _Page = { 37 | label: 'Page', 38 | submenu: [ 39 | { 40 | label: 'Home', 41 | click: function () { 42 | var mainWindow = getMainWindow(); 43 | if (mainWindow) { 44 | mainWindow.webContents.send('transitionTo', 'home'); 45 | } 46 | } 47 | }, 48 | { 49 | label: 'About', 50 | click: function () { 51 | var mainWindow = getMainWindow(); 52 | if (mainWindow) { 53 | mainWindow.webContents.send('transitionTo', 'about'); 54 | } 55 | } 56 | }, 57 | { 58 | label: 'Contact', 59 | click: function () { 60 | var mainWindow = getMainWindow(); 61 | if (mainWindow) { 62 | mainWindow.webContents.send('transitionTo', 'contact'); 63 | } 64 | } 65 | } 66 | ] 67 | }; 68 | 69 | var _View = { 70 | label: '&View', 71 | submenu: [ 72 | { 73 | label: 'Toggle &Fullscreen', 74 | click: function() { 75 | var mainWindow = getMainWindow(); 76 | if (mainWindow) { 77 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 78 | } 79 | } 80 | } 81 | ] 82 | }; 83 | 84 | var menu = { 85 | File: _File, 86 | Page: _Page, 87 | View: _View, 88 | template: [ 89 | _File, 90 | _Page, 91 | _View, 92 | ] 93 | }; 94 | 95 | module.exports = menu; 96 | -------------------------------------------------------------------------------- /core/modules/devtools/index.js: -------------------------------------------------------------------------------- 1 | function setupAppMenu (runtime, appMenu) { 2 | 3 | var mainWindow = runtime.getMainWindow(); 4 | 5 | appMenu.View.submenu.push({ 6 | label: '&Reload', 7 | accelerator: 'CmdOrCtrl+R', 8 | click: function () { 9 | mainWindow.restart(); 10 | } 11 | }); 12 | 13 | appMenu.View.submenu.push({ 14 | label: '&Toggle Developer Tools', 15 | accelerator: 'Alt+CmdOrCtrl+I', 16 | click: function () { 17 | mainWindow.toggleDevTools(); 18 | } 19 | }); 20 | } 21 | 22 | var init = function (runtime) { 23 | if (runtime) { 24 | runtime.once(runtime.events.INIT_APP_MENU, function (appMenu) { 25 | setupAppMenu(runtime, appMenu); 26 | }); 27 | } 28 | }; 29 | 30 | module.exports = init; 31 | -------------------------------------------------------------------------------- /core/modules/extra-pages/dist/components/header.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | 4 | var MyHeader = React.createClass({displayName: "MyHeader", 5 | 6 | render: function() { 7 | return ( 8 | React.createElement("h2", null, "Header") 9 | ); 10 | } 11 | 12 | }); 13 | 14 | module.exports = MyHeader; 15 | -------------------------------------------------------------------------------- /core/modules/extra-pages/dist/components/page1.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var Header = require('./header'); 5 | 6 | var Page1 = React.createClass({displayName: "Page1", 7 | 8 | render: function() { 9 | return ( 10 | React.createElement("div", null, 11 | React.createElement(Header, null), 12 | React.createElement("div", null, "Page 1 Content"), 13 | React.createElement("small", null, "This page was loaded from external module.") 14 | ) 15 | ); 16 | } 17 | 18 | }); 19 | 20 | module.exports = Page1; 21 | -------------------------------------------------------------------------------- /core/modules/extra-pages/dist/components/page2.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var Header = require('./header'); 5 | 6 | var Page2 = React.createClass({displayName: "Page2", 7 | 8 | render: function() { 9 | return ( 10 | React.createElement("div", null, 11 | React.createElement(Header, null), 12 | React.createElement("div", null, "Page 2 Content"), 13 | React.createElement("small", null, "This page was loaded from external module.") 14 | ) 15 | ); 16 | } 17 | 18 | }); 19 | 20 | module.exports = Page2; 21 | -------------------------------------------------------------------------------- /core/modules/extra-pages/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var init = function (runtime) { 4 | if (runtime) { 5 | // setup routes 6 | runtime.once(runtime.events.INIT_ROUTES, function () { 7 | runtime.routes.push({ 8 | route: 'test1', 9 | text: 'Test 1', 10 | navbar: true, 11 | handler: path.join(__dirname, '/dist/components/page1') 12 | }); 13 | runtime.routes.push({ 14 | route: 'test2', 15 | text: 'Test 2', 16 | navbar: true, 17 | handler: path.join(__dirname, '/dist/components/page2') 18 | }); 19 | }); 20 | } 21 | }; 22 | 23 | module.exports = init; 24 | -------------------------------------------------------------------------------- /core/modules/extra-pages/src/components/header.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | 4 | var MyHeader = React.createClass({ 5 | 6 | render: function() { 7 | return ( 8 |

Header

9 | ); 10 | } 11 | 12 | }); 13 | 14 | module.exports = MyHeader; 15 | -------------------------------------------------------------------------------- /core/modules/extra-pages/src/components/page1.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var Header = require('./header'); 5 | 6 | var Page1 = React.createClass({ 7 | 8 | render: function() { 9 | return ( 10 |
11 |
12 |
Page 1 Content
13 | This page was loaded from external module. 14 |
15 | ); 16 | } 17 | 18 | }); 19 | 20 | module.exports = Page1; 21 | -------------------------------------------------------------------------------- /core/modules/extra-pages/src/components/page2.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var Header = require('./header'); 5 | 6 | var Page2 = React.createClass({ 7 | 8 | render: function() { 9 | return ( 10 |
11 |
12 |
Page 2 Content
13 | This page was loaded from external module. 14 |
15 | ); 16 | } 17 | 18 | }); 19 | 20 | module.exports = Page2; 21 | -------------------------------------------------------------------------------- /core/modules/help-md/content/folder/page2.md: -------------------------------------------------------------------------------- 1 | # Page2.md 2 | _This page is stored within a folder._ 3 | 4 | Markdown content for page 2 comes here... 5 | -------------------------------------------------------------------------------- /core/modules/help-md/content/index.md: -------------------------------------------------------------------------------- 1 | ## Index.md 2 | 3 | This is just a simple example of markdown content. 4 | 5 | [Page 1](page1) 6 | 7 | [Page 2](folder/page2) 8 | -------------------------------------------------------------------------------- /core/modules/help-md/content/page1.md: -------------------------------------------------------------------------------- 1 | # Page1.md 2 | 3 | Markdown content for page 1 comes here... 4 | -------------------------------------------------------------------------------- /core/modules/help-md/dist/components/help.js: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | fs = require('fs'), 3 | path = require('path'); 4 | 5 | var md = require('markdown-it')({ 6 | replaceLink: function (link, env) { 7 | return '#/help?page=' + link; 8 | } 9 | }).use(require('markdown-it-replace-link')); 10 | 11 | var Help = React.createClass({displayName: "Help", 12 | getPageContent: function (page) { 13 | var root = path.join(__dirname, '../../content'); 14 | var content = fs.readFileSync(path.join(root, page), 'utf8'); 15 | return { 16 | __html: md.render(content) 17 | }; 18 | }, 19 | render: function() { 20 | var page = (this.props.query.page || 'index') + '.md'; 21 | return ( 22 | React.createElement("div", null, 23 | React.createElement("div", {dangerouslySetInnerHTML: this.getPageContent(page)}) 24 | ) 25 | ); 26 | } 27 | 28 | }); 29 | 30 | module.exports = Help; 31 | -------------------------------------------------------------------------------- /core/modules/help-md/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | function setupAppMenu (runtime, appMenu) { 4 | var mainWindow = runtime.getMainWindow(); 5 | 6 | appMenu.Page.submenu.push({ 7 | label: 'Help', 8 | click: function () { 9 | if (mainWindow) { 10 | mainWindow.webContents.send('transitionTo', 'help'); 11 | } 12 | } 13 | }); 14 | } 15 | 16 | function setupRoutes (runtime) { 17 | runtime.routes.push({ 18 | route: 'help', 19 | text: 'Help', 20 | navbar: true, 21 | handler: path.join(__dirname, '/dist/components/help') 22 | }); 23 | } 24 | 25 | var init = function (runtime) { 26 | if (runtime) { 27 | runtime.once(runtime.events.INIT_APP_MENU, function (appMenu) { 28 | setupAppMenu(runtime, appMenu); 29 | }); 30 | 31 | runtime.once(runtime.events.INIT_ROUTES, function () { 32 | setupRoutes(runtime); 33 | }); 34 | } 35 | }; 36 | 37 | module.exports = init; 38 | -------------------------------------------------------------------------------- /core/modules/help-md/src/components/help.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | fs = require('fs'), 3 | path = require('path'); 4 | 5 | var md = require('markdown-it')({ 6 | replaceLink: function (link, env) { 7 | return '#/help?page=' + link; 8 | } 9 | }).use(require('markdown-it-replace-link')); 10 | 11 | var Help = React.createClass({ 12 | getPageContent: function (page) { 13 | var root = path.join(__dirname, '../../content'); 14 | var content = fs.readFileSync(path.join(root, page), 'utf8'); 15 | return { 16 | __html: md.render(content) 17 | }; 18 | }, 19 | render: function() { 20 | var page = (this.props.query.page || 'index') + '.md'; 21 | return ( 22 |
23 |
24 |
25 | ); 26 | } 27 | 28 | }); 29 | 30 | module.exports = Help; 31 | -------------------------------------------------------------------------------- /core/modules/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'); 3 | 4 | var loader = { 5 | load: function (context) { 6 | //debug('Starting external modules...'); 7 | fs.readdirSync(__dirname).forEach(function(file) { 8 | var p = path.join(__dirname, file); 9 | var stats = fs.statSync(p); 10 | if (stats.isDirectory()) { 11 | //debug('Loading module ' + file); 12 | require(p)(context); 13 | } 14 | }); 15 | } 16 | }; 17 | 18 | module.exports = loader; 19 | -------------------------------------------------------------------------------- /core/runtime.js: -------------------------------------------------------------------------------- 1 | var events = require('events'), 2 | BrowserWindow = require('browser-window'); 3 | 4 | function RuntimeContext () { 5 | events.EventEmitter.call(this); 6 | 7 | this.events = { 8 | INIT_APP_MENU: 'INIT_APP_MENU', 9 | INIT_ROUTES: 'INIT_ROUTES' 10 | }; 11 | 12 | this.routes = []; 13 | 14 | // Main window id 15 | this.windowId = null; 16 | } 17 | 18 | // Extend runtime context class so that we can use on() and emit() 19 | RuntimeContext.prototype = Object.create(events.EventEmitter.prototype); 20 | 21 | RuntimeContext.prototype.getMainWindow = function () { 22 | if (this.windowId) { 23 | return BrowserWindow.fromId(this.windowId); 24 | } 25 | return null; 26 | }; 27 | 28 | module.exports = new RuntimeContext(); 29 | -------------------------------------------------------------------------------- /dist/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | .navbar-fixed-top > .container-fluid { 5 | padding-left: 0; 6 | padding-right: 0; 7 | } 8 | -------------------------------------------------------------------------------- /img/electron-react-osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenysVuika/electron-react/5452c8abf3f55c455e575980f62a5449510c866d/img/electron-react-osx.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Electron React Template 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /less/app.less: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | 5 | .navbar-fixed-top { 6 | & > .container-fluid { 7 | padding-left: 0; 8 | padding-right: 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var app = require('app'), 2 | BrowserWindow = require('browser-window'), 3 | Menu = require('menu'), 4 | runtime = require('./core/runtime'), 5 | appMenu = require('./core/app-menu'); 6 | 7 | require('crash-reporter').start(); 8 | 9 | // Load external modules 10 | var mods = require('./core/modules'); 11 | mods.load(runtime); 12 | 13 | var mainWindow = null; 14 | var menu = null; 15 | 16 | app.on('window-all-closed', function () { 17 | //if (process.platform !== 'darwin') { 18 | app.quit(); 19 | //} 20 | }); 21 | 22 | app.on('ready', function () { 23 | 24 | runtime.emit(runtime.events.INIT_ROUTES, appMenu); 25 | 26 | mainWindow = new BrowserWindow({ 27 | width: 800, 28 | height: 600 29 | }); 30 | 31 | // initialize runtime reference to main window 32 | runtime.windowId = mainWindow.id; 33 | 34 | mainWindow.loadUrl('file://' + __dirname + '/index.html'); 35 | mainWindow.focus(); 36 | 37 | mainWindow.on('closed', function () { 38 | mainWindow = null; 39 | }); 40 | 41 | // Dock Menu (Mac) 42 | if (process.platform === 'darwin') { 43 | var dockMenu = Menu.buildFromTemplate([ 44 | { label: 'New Window', click: function() { console.log('New Window'); } }, 45 | { label: 'New Window with Settings', submenu: [ 46 | { label: 'Basic' }, 47 | { label: 'Pro'}, 48 | ]}, 49 | { label: 'New Command...'}, 50 | ]); 51 | app.dock.setMenu(dockMenu); 52 | } 53 | 54 | // Application Menu 55 | runtime.emit(runtime.events.INIT_APP_MENU, appMenu); 56 | 57 | var template = appMenu.template; 58 | menu = Menu.buildFromTemplate(template); 59 | 60 | if (process.platform === 'darwin') { 61 | Menu.setApplicationMenu(menu); 62 | } else { 63 | mainWindow.setMenu(menu); 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react", 3 | "version": "0.2.0", 4 | "description": "Project template for Electron with React support", 5 | "main": "main.js", 6 | "scripts": { 7 | "win": "node_modules\\electron-prebuilt\\dist\\electron .", 8 | "osx": "node_modules/electron-prebuilt/dist/Electron.app/Contents/MacOS/Electron ." 9 | }, 10 | "author": "Denis Vuyka ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "markdown-it": "^4.2.1", 14 | "markdown-it-replace-link": "^1.0.0", 15 | "react": "^0.13.3", 16 | "react-bootstrap": "^0.25.1", 17 | "react-router": "^0.13.3", 18 | "react-router-bootstrap": "^0.18.1" 19 | }, 20 | "devDependencies": { 21 | "babelify": "^6.1.0", 22 | "browserify": "^11.0.1", 23 | "electron-prebuilt": "^0.31.2", 24 | "grunt": "^0.4.5", 25 | "grunt-browserify": "^4.0.1", 26 | "grunt-contrib-less": "^1.0.1", 27 | "grunt-contrib-uglify": "^0.9.1", 28 | "grunt-contrib-watch": "^0.6.1", 29 | "grunt-react": "^0.12.2" 30 | } 31 | } 32 | --------------------------------------------------------------------------------