├── internals ├── mocks │ └── fileMock.js ├── flow │ ├── WebpackAsset.js.flow │ └── CSSModule.js.flow ├── img │ ├── js.png │ ├── npm.png │ ├── flow.png │ ├── jest.png │ ├── react.png │ ├── redux.png │ ├── yarn.png │ ├── eslint.png │ ├── webpack.png │ ├── js-padded.png │ ├── eslint-padded.png │ ├── flow-padded.png │ ├── jest-padded.png │ ├── react-padded.png │ ├── react-router.png │ ├── redux-padded.png │ ├── yarn-padded.png │ ├── flow-padded-90.png │ ├── jest-padded-90.png │ ├── react-padded-90.png │ ├── redux-padded-90.png │ ├── webpack-padded.png │ ├── yarn-padded-90.png │ ├── eslint-padded-90.png │ ├── webpack-padded-90.png │ ├── react-router-padded.png │ └── react-router-padded-90.png └── scripts │ ├── CheckNodeEnv.js │ └── CheckBuiltsExist.js ├── .stylelintrc ├── .gitattributes ├── resources ├── icon.ico ├── icon.png ├── icon.icns └── icons │ ├── 16x16.png │ ├── 24x24.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 96x96.png │ ├── 128x128.png │ ├── 256x256.png │ ├── 512x512.png │ └── 1024x1024.png ├── app ├── renderer │ ├── app.icns │ ├── containers │ │ ├── App.js │ │ ├── Root.js │ │ ├── Home.css │ │ ├── GetData.css │ │ ├── Home.js │ │ └── GetData.js │ ├── routes.js │ ├── components │ │ ├── DimensionList.css │ │ ├── ChartList.css │ │ ├── DimensionList.js │ │ └── ChartList.js │ ├── index.js │ ├── app.global.css │ └── app.html ├── package-lock.json ├── yarn.lock ├── shared │ ├── store │ │ ├── configureStore.js │ │ ├── configureStore.prod.js │ │ └── configureStore.dev.js │ ├── utils │ │ └── index.js │ ├── reducers │ │ ├── index.js │ │ └── vocab.js │ └── actions │ │ └── index.js ├── package.json ├── .eslintrc └── main │ ├── load-templates.js │ ├── auto-update.js │ ├── build-project.js │ ├── post-build.js │ ├── manage-vvt-repo.js │ ├── main.dev.js │ └── menu.js ├── flow-typed └── module_vx.x.x.js ├── webpack.config.eslint.js ├── .gitmodules ├── test ├── example.js ├── .eslintrc ├── actions │ ├── __snapshots__ │ │ └── counter.spec.js.snap │ └── counter.spec.js ├── reducers │ ├── __snapshots__ │ │ └── counter.spec.js.snap │ └── counter.spec.js ├── runTests.js ├── components │ ├── __snapshots__ │ │ └── Counter.spec.js.snap │ └── Counter.spec.js ├── containers │ └── CounterPage.spec.js └── e2e │ └── e2e.spec.js ├── .editorconfig ├── .vscode └── settings.json ├── appveyor.yml ├── .babelrc ├── .travis.yml ├── .gitignore ├── .flowconfig ├── LICENSE ├── .eslintignore ├── .eslintrc ├── webpack.config.base.js ├── webpack.config.main.prod.js ├── webpack.config.renderer.prod.js ├── webpack.config.renderer.dev.dll.js ├── README.md ├── webpack.config.renderer.dev.js ├── package.json └── CHANGELOG.md /internals/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /internals/flow/WebpackAsset.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare export default string 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.ico binary 4 | *.icns binary 5 | -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icon.png -------------------------------------------------------------------------------- /app/renderer/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/app/renderer/app.icns -------------------------------------------------------------------------------- /flow-typed/module_vx.x.x.js: -------------------------------------------------------------------------------- 1 | declare module 'module' { 2 | declare module.exports: any; 3 | } 4 | -------------------------------------------------------------------------------- /internals/flow/CSSModule.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare export default { [key: string]: string } -------------------------------------------------------------------------------- /internals/img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/js.png -------------------------------------------------------------------------------- /internals/img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/npm.png -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icon.icns -------------------------------------------------------------------------------- /internals/img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/flow.png -------------------------------------------------------------------------------- /internals/img/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/jest.png -------------------------------------------------------------------------------- /internals/img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/react.png -------------------------------------------------------------------------------- /internals/img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/redux.png -------------------------------------------------------------------------------- /internals/img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/yarn.png -------------------------------------------------------------------------------- /internals/img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/eslint.png -------------------------------------------------------------------------------- /internals/img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/webpack.png -------------------------------------------------------------------------------- /resources/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/16x16.png -------------------------------------------------------------------------------- /resources/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/24x24.png -------------------------------------------------------------------------------- /resources/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/32x32.png -------------------------------------------------------------------------------- /resources/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/48x48.png -------------------------------------------------------------------------------- /resources/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/64x64.png -------------------------------------------------------------------------------- /resources/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/96x96.png -------------------------------------------------------------------------------- /app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vocab2", 3 | "version": "2.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /internals/img/js-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/js-padded.png -------------------------------------------------------------------------------- /resources/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/128x128.png -------------------------------------------------------------------------------- /resources/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/256x256.png -------------------------------------------------------------------------------- /resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/512x512.png -------------------------------------------------------------------------------- /app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /internals/img/eslint-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/eslint-padded.png -------------------------------------------------------------------------------- /internals/img/flow-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/flow-padded.png -------------------------------------------------------------------------------- /internals/img/jest-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/jest-padded.png -------------------------------------------------------------------------------- /internals/img/react-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/react-padded.png -------------------------------------------------------------------------------- /internals/img/react-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/react-router.png -------------------------------------------------------------------------------- /internals/img/redux-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/redux-padded.png -------------------------------------------------------------------------------- /internals/img/yarn-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/yarn-padded.png -------------------------------------------------------------------------------- /resources/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/resources/icons/1024x1024.png -------------------------------------------------------------------------------- /internals/img/flow-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/flow-padded-90.png -------------------------------------------------------------------------------- /internals/img/jest-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/jest-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/react-padded-90.png -------------------------------------------------------------------------------- /internals/img/redux-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/redux-padded-90.png -------------------------------------------------------------------------------- /internals/img/webpack-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/webpack-padded.png -------------------------------------------------------------------------------- /internals/img/yarn-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/yarn-padded-90.png -------------------------------------------------------------------------------- /webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | 3 | module.exports = require('./webpack.config.renderer.dev'); 4 | -------------------------------------------------------------------------------- /internals/img/eslint-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/eslint-padded-90.png -------------------------------------------------------------------------------- /internals/img/webpack-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/webpack-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-router-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/react-router-padded.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "templates"] 2 | path = templates 3 | url = git@github.com:ft-interactive/visual-vocabulary-templates.git 4 | -------------------------------------------------------------------------------- /internals/img/react-router-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ft-interactive/vocab/HEAD/internals/img/react-router-padded-90.png -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | describe('description', () => { 2 | it('should have description', () => { 3 | expect(1 + 2).toBe(3); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest/globals": true 4 | }, 5 | "plugins": [ 6 | "jest" 7 | ], 8 | "rules": { 9 | "jest/no-disabled-tests": "warn", 10 | "jest/no-focused-tests": "error", 11 | "jest/no-identical-title": "error" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/shared/store/configureStore.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | if (process.env.NODE_ENV === 'production') { 3 | module.exports = require('./configureStore.prod'); // eslint-disable-line global-require 4 | } else { 5 | module.exports = require('./configureStore.dev'); // eslint-disable-line global-require 6 | } 7 | -------------------------------------------------------------------------------- /app/renderer/containers/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import type { Node } from 'react'; 4 | 5 | type Props = { 6 | children: Node 7 | }; 8 | 9 | export default class App extends Component { 10 | props: Props; 11 | 12 | render() { 13 | return
{this.props.children}
; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/actions/__snapshots__/counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`actions should decrement should create decrement action 1`] = ` 4 | Object { 5 | "type": "DECREMENT_COUNTER", 6 | } 7 | `; 8 | 9 | exports[`actions should increment should create increment action 1`] = ` 10 | Object { 11 | "type": "INCREMENT_COUNTER", 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /app/shared/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Various utility functions 3 | */ 4 | // @flow 5 | export const slug = (str: string) => str.toLowerCase().replace(/\s/g, '-'); 6 | 7 | export const bool = (str: string) => { 8 | if (str.toLowerCase().trim() === 'true') return true; 9 | else if (str.toLowerCase().trim() === 'false') return false; 10 | throw new Error('Invalid value detected'); 11 | }; 12 | -------------------------------------------------------------------------------- /test/reducers/__snapshots__/counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`reducers counter should handle DECREMENT_COUNTER 1`] = `0`; 4 | 5 | exports[`reducers counter should handle INCREMENT_COUNTER 1`] = `2`; 6 | 7 | exports[`reducers counter should handle initial state 1`] = `0`; 8 | 9 | exports[`reducers counter should handle unknown action type 1`] = `1`; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,js,jsx,html,css,yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [.eslintrc] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /test/runTests.js: -------------------------------------------------------------------------------- 1 | const spawn = require('cross-spawn'); 2 | const path = require('path'); 3 | 4 | const s = `\\${path.sep}`; 5 | const pattern = process.argv[2] === 'e2e' 6 | ? `test${s}e2e${s}.+\\.spec\\.js` 7 | : `test${s}(?!e2e${s})[^${s}]+${s}.+\\.spec\\.js$`; 8 | 9 | const result = spawn.sync(path.normalize('./node_modules/.bin/jest'), [pattern], { stdio: 'inherit' }); 10 | 11 | process.exit(result.status); 12 | -------------------------------------------------------------------------------- /internals/scripts/CheckNodeEnv.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk'; 3 | 4 | export default function CheckNodeEnv(expectedEnv: string) { 5 | if (!expectedEnv) { 6 | throw new Error('"expectedEnv" not set'); 7 | } 8 | 9 | if (process.env.NODE_ENV !== expectedEnv) { 10 | console.log(chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | )); 13 | process.exit(2); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/shared/reducers/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { combineReducers } from 'redux'; 3 | import { routerReducer as routing } from 'react-router-redux'; 4 | import vocabApp from './vocab'; 5 | 6 | export default function getRootReducer(scope: string = 'main') { 7 | let reducers = { 8 | vocabApp 9 | }; 10 | 11 | if (scope === 'renderer') { 12 | reducers = { 13 | ...reducers, 14 | routing 15 | }; 16 | } 17 | 18 | return combineReducers({ ...reducers }); 19 | } 20 | -------------------------------------------------------------------------------- /app/renderer/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * React Router routes 4 | */ 5 | 6 | import React from 'react'; 7 | import { Switch, Route } from 'react-router'; 8 | import App from './containers/App'; 9 | import GetData from './containers/GetData'; 10 | import Home from './containers/Home'; 11 | 12 | export default () => ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /app/renderer/components/DimensionList.css: -------------------------------------------------------------------------------- 1 | .dimension-list__unordered-list { 2 | list-style-type: none; 3 | padding: 0; 4 | margin: 0; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: flex-start; 8 | height: 100%; 9 | } 10 | 11 | .dimension-list__list-item { 12 | list-style-type: none; 13 | font-size: 1.5em; 14 | text-align: center; 15 | border-top: 0.5em solid; 16 | padding: 0.25em; 17 | flex: 1; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | "flow.useNPMPackagedFlow": true, 4 | "search.exclude": { 5 | ".git": true, 6 | ".eslintcache": true, 7 | "app/dist": true, 8 | "app/main.prod.js": true, 9 | "app/main.prod.js.map": true, 10 | "bower_components": true, 11 | "dll": true, 12 | "flow-typed": true, 13 | "release": true, 14 | "node_modules": true, 15 | "npm-debug.log.*": true, 16 | "test/**/__snapshots__": true, 17 | "yarn.lock": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/renderer/containers/Root.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import { Provider } from 'react-redux'; 4 | import { ConnectedRouter } from 'react-router-redux'; 5 | import Routes from '../routes'; 6 | 7 | type RootType = { 8 | store: {}, 9 | history: {} 10 | }; 11 | 12 | export default function Root({ store, history }: RootType) { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /app/renderer/containers/Home.css: -------------------------------------------------------------------------------- 1 | .app__title { 2 | font-size: 1.5em; 3 | } 4 | 5 | .app__intro { 6 | font-size: large; 7 | } 8 | 9 | .app__main { 10 | display: flex; 11 | flex: 1; 12 | flex-direction: row; 13 | } 14 | 15 | .app__sidebar { 16 | flex: 1; 17 | } 18 | 19 | .app__content { 20 | flex: 4; 21 | display: flex; 22 | padding: 0 2em 2em 2em; 23 | overflow: scroll; 24 | } 25 | 26 | .app { 27 | text-align: center; 28 | min-height: 100vh; 29 | display: flex; 30 | flex-direction: column; 31 | } 32 | 33 | .app__header { 34 | background-color: #222; 35 | padding: 0.75em; 36 | color: white; 37 | } 38 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vocab2", 3 | "productName": "vocab", 4 | "version": "2.0.0", 5 | "description": "Electron + Redux + React app for rapidly scaffolding Visual Vocabulary Template projects", 6 | "main": "./main/main.prod.js", 7 | "author": { 8 | "name": "Ændrew Rininsland", 9 | "email": "andrew.rininsland@ft.com", 10 | "url": "https://github.com/aendrew" 11 | }, 12 | "scripts": { 13 | "postinstall": "npm rebuild --runtime=electron --target=1.6.6 --disturl=https://atom.io/download/atom-shell --build-from-source" 14 | }, 15 | "license": "MIT", 16 | "dependencies": {}, 17 | "devDependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: unstable 2 | 3 | environment: 4 | matrix: 5 | - nodejs_version: 8 6 | - nodejs_version: 7 7 | 8 | cache: 9 | - "%LOCALAPPDATA%/Yarn" 10 | - node_modules -> package.json 11 | - app/node_modules -> app/package.json 12 | 13 | matrix: 14 | fast_finish: true 15 | 16 | build: off 17 | 18 | version: '{build}' 19 | 20 | shallow_clone: true 21 | 22 | clone_depth: 1 23 | 24 | install: 25 | - ps: Install-Product node $env:nodejs_version 26 | - set CI=true 27 | - yarn 28 | - cd app && yarn 29 | 30 | test_script: 31 | - node --version 32 | - yarn lint 33 | - yarn package 34 | - yarn test 35 | - yarn test-e2e 36 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { "node": 7 }, 7 | "useBuiltIns": true 8 | } 9 | ], 10 | "stage-0", 11 | "react" 12 | ], 13 | "plugins": ["add-module-exports", "dynamic-import-webpack", "transform-class-properties"], 14 | "env": { 15 | "production": { 16 | "presets": ["react-optimize"], 17 | "plugins": ["babel-plugin-dev-expression"] 18 | }, 19 | "development": { 20 | "plugins": [ 21 | "transform-class-properties", 22 | "transform-es2015-classes", 23 | [ 24 | "flow-runtime", 25 | { 26 | "assert": true, 27 | "annotate": true 28 | } 29 | ] 30 | ] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/renderer/components/ChartList.css: -------------------------------------------------------------------------------- 1 | .chart-list__ul { 2 | list-style-type: none; 3 | display: flex; 4 | flex-wrap: wrap; 5 | } 6 | 7 | .chart-list__li { 8 | width: 50%; 9 | cursor: pointer; 10 | } 11 | 12 | .chart-list__button { 13 | background: none; 14 | border: none; 15 | } 16 | 17 | .chart-list__button:not(.chart-list__button--disabled):hover { 18 | background: #ccc; 19 | } 20 | 21 | .chart-list__button--disabled { 22 | opacity: 0.7; 23 | cursor: default; 24 | } 25 | 26 | .chart-list__img { 27 | max-width: 100px; 28 | } 29 | 30 | .chart-list__description { 31 | margin: 0 auto; 32 | max-width: 80%; 33 | color: white; 34 | font-weight: lighter; 35 | font-size: 1em; 36 | } 37 | 38 | .chart-list__header { 39 | text-align: center; 40 | width: 100%; 41 | } 42 | -------------------------------------------------------------------------------- /app/renderer/containers/GetData.css: -------------------------------------------------------------------------------- 1 | .get-data__wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | flex: 1; 5 | } 6 | 7 | .get-data__dropzone { 8 | border: 3px dashed white; 9 | padding: 1em; 10 | text-align: center; 11 | flex: 1; 12 | margin: 2em; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | } 18 | 19 | .get-data__dropzone--header-big { 20 | font-family: "Helvetica Neue"; 21 | font-weight: lighter; 22 | font-size: 5em; 23 | } 24 | 25 | .get-data__dropzone--header-small { 26 | font-family: "Helvetica Neue"; 27 | font-weight: lighter; 28 | font-size: 2.5em; 29 | } 30 | 31 | .get-data__back-button { 32 | font-size: 2em; 33 | border: 0; 34 | background: rgba(255, 255, 255, 0.7); 35 | } 36 | -------------------------------------------------------------------------------- /test/reducers/counter.spec.js: -------------------------------------------------------------------------------- 1 | import counter from '../../app/reducers/counter'; 2 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../../app/actions/counter'; 3 | 4 | describe('reducers', () => { 5 | describe('counter', () => { 6 | it('should handle initial state', () => { 7 | expect(counter(undefined, {})).toMatchSnapshot(); 8 | }); 9 | 10 | it('should handle INCREMENT_COUNTER', () => { 11 | expect(counter(1, { type: INCREMENT_COUNTER })).toMatchSnapshot(); 12 | }); 13 | 14 | it('should handle DECREMENT_COUNTER', () => { 15 | expect(counter(1, { type: DECREMENT_COUNTER })).toMatchSnapshot(); 16 | }); 17 | 18 | it('should handle unknown action type', () => { 19 | expect(counter(1, { type: 'unknown' })).toMatchSnapshot(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 8 7 | - 7 8 | 9 | cache: 10 | yarn: true 11 | directories: 12 | - node_modules 13 | - app/node_modules 14 | 15 | addons: 16 | apt: 17 | sources: 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - g++-4.8 21 | - icnsutils 22 | - graphicsmagick 23 | - xz-utils 24 | - xorriso 25 | 26 | install: 27 | - export CXX="g++-4.8" 28 | - yarn 29 | - cd app && yarn && cd .. 30 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 31 | 32 | before_script: 33 | - export DISPLAY=:99.0 34 | - sh -e /etc/init.d/xvfb start & 35 | - sleep 3 36 | 37 | script: 38 | - node --version 39 | - yarn lint 40 | - yarn package 41 | - yarn test 42 | - yarn test-e2e 43 | -------------------------------------------------------------------------------- /app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "flowtype/boolean-style": [2, "boolean"], 4 | "flowtype/define-flow-type": 1, 5 | "flowtype/delimiter-dangle": [2, "never"], 6 | "flowtype/generic-spacing": [2, "never"], 7 | "flowtype/no-primitive-constructor-types": 2, 8 | "flowtype/no-weak-types": 1, 9 | "flowtype/object-type-delimiter": [2, "comma"], 10 | "flowtype/require-parameter-type": 0, 11 | "flowtype/require-return-type": 0, 12 | "flowtype/require-valid-file-annotation": 0, 13 | "flowtype/semi": [2, "always"], 14 | "flowtype/space-after-type-colon": [2, "always"], 15 | "flowtype/space-before-generic-bracket": [2, "never"], 16 | "flowtype/space-before-type-colon": [2, "never"], 17 | "flowtype/union-intersection-spacing": [2, "always"], 18 | "flowtype/use-flow-type": 2, 19 | "flowtype/valid-syntax": 2, 20 | "flowtype-errors/show-errors": 2 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internals/scripts/CheckBuiltsExist.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // Check if the renderer and main bundles are built 3 | import path from 'path'; 4 | import chalk from 'chalk'; 5 | import fs from 'fs'; 6 | 7 | function CheckBuildsExist() { 8 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main', 'main.prod.js'); 9 | const rendererPath = path.join(__dirname, '..', '..', 'app', 'dist', 'renderer.prod.js'); 10 | 11 | if (!fs.existsSync(mainPath)) { 12 | throw new Error( 13 | chalk.whiteBright.bgRed.bold( 14 | 'The main process is not built yet. Build it by running "npm run build-main"' 15 | ) 16 | ); 17 | } 18 | 19 | if (!fs.existsSync(rendererPath)) { 20 | throw new Error( 21 | chalk.whiteBright.bgRed.bold( 22 | 'The renderer process is not built yet. Build it by running "npm run build-renderer"' 23 | ) 24 | ); 25 | } 26 | } 27 | 28 | CheckBuildsExist(); 29 | -------------------------------------------------------------------------------- /app/main/load-templates.js: -------------------------------------------------------------------------------- 1 | import Papa from 'papaparse'; 2 | import { readFileSync } from 'fs'; 3 | import { homedir } from 'os'; 4 | import { join } from 'path'; 5 | 6 | export default async function () { 7 | const HOME = homedir(); 8 | const vvtPath = join(HOME, '.vocab/', 'visual-vocabulary-templates/'); 9 | const categories = 10 | process.env.NODE_ENV === 'production' 11 | ? __non_webpack_require__(join(vvtPath, 'docs', 'categories')) 12 | : require(join(vvtPath, 'docs', 'categories')); 13 | 14 | const templates = await new Promise((resolve, reject) => { 15 | Papa.parse(readFileSync(join(vvtPath, 'docs/', 'chartTypes.csv'), 'utf-8'), { 16 | header: true, 17 | complete: ({ data }) => resolve(data), 18 | error: err => reject(err) 19 | }); 20 | }); 21 | 22 | return { 23 | templates: templates.filter(d => d.chartName), 24 | categories: categories.filter(d => d.category), 25 | docsPath: join(vvtPath, 'docs/') 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /app/renderer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import { getInitialStateRenderer } from 'electron-redux'; 5 | import Root from './containers/Root'; 6 | import configureStore from '../shared/store/configureStore'; 7 | import './app.global.css'; 8 | 9 | const initialState = getInitialStateRenderer(); 10 | const { store, history } = configureStore(initialState, 'renderer'); 11 | 12 | render( 13 | 14 | 15 | , 16 | document.getElementById('root') 17 | ); 18 | 19 | if (module.hot) { 20 | module.hot.accept('./containers/Root', () => { 21 | const NextRoot = require('./containers/Root'); // eslint-disable-line global-require 22 | render( 23 | 24 | 25 | , 26 | document.getElementById('root') 27 | ); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /app/renderer/app.global.css: -------------------------------------------------------------------------------- 1 | @import "~font-awesome/css/font-awesome.css"; 2 | 3 | html, body { 4 | height: 100%; 5 | width: 100%; 6 | margin: 0; 7 | } 8 | 9 | body { 10 | color: white; 11 | background-color: #232c39; 12 | background-image: linear-gradient(45deg, rgba(0, 216, 255, 0.5) 10%, rgba(0, 1, 127, 0.7)); 13 | font-family: Arial, Helvetica, Helvetica Neue, serif; 14 | overflow-y: hidden; 15 | } 16 | 17 | h2 { 18 | margin: 0; 19 | font-size: 2.25rem; 20 | font-weight: bold; 21 | letter-spacing: -0.025em; 22 | color: #fff; 23 | } 24 | 25 | p { 26 | font-size: 24px; 27 | } 28 | 29 | li { 30 | list-style: none; 31 | } 32 | 33 | a { 34 | color: white; 35 | opacity: 0.75; 36 | text-decoration: none; 37 | } 38 | 39 | a:hover { 40 | opacity: 1; 41 | text-decoration: none; 42 | cursor: pointer; 43 | } 44 | 45 | #root { 46 | display: flex; 47 | flex-direction: column; 48 | height: 100%; 49 | } 50 | 51 | #root > div { 52 | flex: 1; 53 | display: flex; 54 | } 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/main/main.prod.* 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | .eslintcache 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | app/node_modules 32 | 33 | # OSX 34 | .DS_Store 35 | 36 | # flow-typed 37 | flow-typed/npm/* 38 | !flow-typed/npm/module_vx.x.x.js 39 | 40 | # App packaged 41 | release 42 | app/main.prod.js 43 | app/main.prod.js.map 44 | app/renderer.prod.js 45 | app/renderer.prod.js.map 46 | app/style.css 47 | app/style.css.map 48 | dist 49 | dll 50 | main.js 51 | main.js.map 52 | 53 | .idea 54 | npm-debug.log.* 55 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/* 3 | /app/main.prod.js 4 | /app/main.prod.js.map 5 | /app/dist/.* 6 | /resources/.* 7 | /release/.* 8 | /dll/.* 9 | /release/.* 10 | /git/.* 11 | /templates/* 12 | /templates/docs/* 13 | [include] 14 | 15 | [libs] 16 | 17 | [options] 18 | esproposal.class_static_fields=enable 19 | esproposal.class_instance_fields=enable 20 | esproposal.export_star_as=enable 21 | module.name_mapper.extension='css' -> '/internals/flow/CSSModule.js.flow' 22 | module.name_mapper.extension='styl' -> '/internals/flow/CSSModule.js.flow' 23 | module.name_mapper.extension='scss' -> '/internals/flow/CSSModule.js.flow' 24 | module.name_mapper.extension='png' -> '/internals/flow/WebpackAsset.js.flow' 25 | module.name_mapper.extension='jpg' -> '/internals/flow/WebpackAsset.js.flow' 26 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe 27 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 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 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main/main.prod.js 41 | app/main/main.prod.js.map 42 | app/renderer/renderer.prod.js 43 | app/renderer/renderer.prod.js.map 44 | app/renderer/style.css 45 | app/renderer/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | __snapshots__ 54 | 55 | # VVT 56 | templates 57 | templates/* 58 | 59 | # Prettier breaks these files 60 | app/renderer/index.html 61 | -------------------------------------------------------------------------------- /app/renderer/containers/Home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Home page informational page 4 | * 5 | * @flow 6 | */ 7 | 8 | import React from 'react'; 9 | import { Route, Switch } from 'react-router-dom'; 10 | import DimensionList from '../components/DimensionList'; 11 | import ChartList from '../components/ChartList'; 12 | import styles from './Home.css'; 13 | 14 | export default () => ( 15 |
16 | 17 |
18 | 19 | 20 | ( 22 |
23 |

24 | Welcome to Vocab v2 25 |

26 |

27 | Please select the dimensions of your data you wish to depict from the sidebar on the 28 | left. 29 |

30 |

31 | You'll then be given a few different chart types to compare and ultimately use 32 | to create your project. 33 |

34 |
35 | )} 36 | /> 37 |
38 |
39 |
40 | ); 41 | -------------------------------------------------------------------------------- /app/renderer/components/DimensionList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** 3 | * Side-bar container for Visual Vocabulary dimensions 4 | */ 5 | 6 | import React from 'react'; 7 | import { Link } from 'react-router-dom'; 8 | import { connect } from 'react-redux'; 9 | import { slug } from '../../shared/utils'; 10 | import styles from './DimensionList.css'; 11 | import type { categoryType } from '../../shared/reducers/vocab'; 12 | 13 | type Props = { 14 | className: string, 15 | categories: categoryType[] 16 | }; 17 | 18 | const DimensionList = ({ 19 | className, 20 | categories 21 | }: Props) => ( 22 | 37 | ); 38 | 39 | const mapStateToProps = state => ({ 40 | categories: state.vocabApp.categories 41 | }); 42 | 43 | const mapDispatchToProps = () => ({}); 44 | 45 | export default connect(mapStateToProps, mapDispatchToProps)(DimensionList); 46 | -------------------------------------------------------------------------------- /app/renderer/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vocab v2 6 | 17 | 18 | 19 |
20 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/components/__snapshots__/Counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Counter component should match exact snapshot 1`] = ` 4 |
5 |
6 |
10 | 14 | 17 | 18 |
19 |
23 | 1 24 |
25 |
28 | 37 | 46 | 53 | 60 |
61 |
62 |
63 | `; 64 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "allowImportExportEverywhere": true 6 | }, 7 | "extends": "airbnb", 8 | "env": { 9 | "browser": true, 10 | "node": true 11 | }, 12 | "root": true, 13 | "rules": { 14 | "arrow-parens": ["off"], 15 | "compat/compat": "error", 16 | "consistent-return": "off", 17 | "comma-dangle": "off", 18 | "flowtype-errors/show-errors": "error", 19 | "generator-star-spacing": "off", 20 | "import/no-unresolved": "error", 21 | "import/no-extraneous-dependencies": "off", 22 | "no-console": "off", 23 | "no-use-before-define": "off", 24 | "no-multi-assign": "off", 25 | "promise/param-names": "error", 26 | "promise/always-return": "error", 27 | "promise/catch-or-return": "error", 28 | "promise/no-native": "off", 29 | "react/sort-comp": ["error", { 30 | "order": ["type-annotations", "static-methods", "lifecycle", "everything-else", "render"] 31 | }], 32 | "react/jsx-no-bind": "off", 33 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }], 34 | "react/prefer-stateless-function": "off" 35 | }, 36 | "plugins": [ 37 | "flowtype", 38 | "flowtype-errors", 39 | "import", 40 | "promise", 41 | "compat", 42 | "react" 43 | ], 44 | "settings": { 45 | "import/resolver": { 46 | "webpack": { 47 | "config": "webpack.config.eslint.js" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { homedir } from 'os'; 8 | import { dependencies as externals } from './app/package.json'; 9 | 10 | const vvtPath = path.join(homedir(), '.vocab/', 'visual-vocabulary-templates/'); 11 | const categoriesDataPath = path.join(vvtPath, 'docs', 'categories'); 12 | 13 | export default { 14 | externals: [...Object.keys(externals || {}), categoriesDataPath], 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.jsx?$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: 'babel-loader', 23 | options: { 24 | cacheDirectory: true 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | output: { 32 | path: path.join(__dirname, 'app', 'renderer'), 33 | filename: 'renderer.dev.js', 34 | // https://github.com/webpack/webpack/issues/1114 35 | libraryTarget: 'commonjs2' 36 | }, 37 | 38 | /** 39 | * Determine the array of extensions that should be used to resolve modules. 40 | */ 41 | resolve: { 42 | extensions: ['.js', '.jsx', '.json'], 43 | modules: [path.join(__dirname, 'app'), 'node_modules'] 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DefinePlugin({ 48 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') 49 | }), 50 | 51 | new webpack.NamedModulesPlugin() 52 | ] 53 | }; 54 | -------------------------------------------------------------------------------- /test/actions/counter.spec.js: -------------------------------------------------------------------------------- 1 | import { spy } from 'sinon'; 2 | import * as actions from '../../app/actions/counter'; 3 | 4 | describe('actions', () => { 5 | it('should increment should create increment action', () => { 6 | expect(actions.increment()).toMatchSnapshot(); 7 | }); 8 | 9 | it('should decrement should create decrement action', () => { 10 | expect(actions.decrement()).toMatchSnapshot(); 11 | }); 12 | 13 | it('should incrementIfOdd should create increment action', () => { 14 | const fn = actions.incrementIfOdd(); 15 | expect(fn).toBeInstanceOf(Function); 16 | const dispatch = spy(); 17 | const getState = () => ({ counter: 1 }); 18 | fn(dispatch, getState); 19 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true); 20 | }); 21 | 22 | it('should incrementIfOdd shouldnt create increment action if counter is even', () => { 23 | const fn = actions.incrementIfOdd(); 24 | const dispatch = spy(); 25 | const getState = () => ({ counter: 2 }); 26 | fn(dispatch, getState); 27 | expect(dispatch.called).toBe(false); 28 | }); 29 | 30 | // There's no nice way to test this at the moment... 31 | it('should incrementAsync', done => { 32 | const fn = actions.incrementAsync(1); 33 | expect(fn).toBeInstanceOf(Function); 34 | const dispatch = spy(); 35 | fn(dispatch); 36 | setTimeout(() => { 37 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true); 38 | done(); 39 | }, 5); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /app/shared/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import thunk from 'redux-thunk'; 4 | import { createBrowserHistory } from 'history'; 5 | import { 6 | forwardToMain, 7 | forwardToRenderer, 8 | triggerAlias, 9 | replayActionMain, 10 | replayActionRenderer 11 | } from 'electron-redux'; 12 | import { routerMiddleware } from 'react-router-redux'; 13 | import getRootReducer from '../reducers'; 14 | import type { vocabStateType } from '../reducers/vocab'; 15 | 16 | function configureStore(initialState?: { vocabApp: vocabStateType }, scope: string = 'main') { 17 | // Redux Configuration 18 | let middleware = []; 19 | 20 | // Thunk Middleware 21 | middleware.push(thunk); 22 | 23 | let history; 24 | 25 | if (scope === 'renderer') { 26 | history = createBrowserHistory(); 27 | middleware = [ 28 | forwardToMain, // electron-redux 29 | routerMiddleware(history), // router 30 | ...middleware 31 | ]; 32 | } 33 | 34 | if (scope === 'main') { 35 | middleware = [ 36 | triggerAlias, // electron-redux 37 | ...middleware, 38 | forwardToRenderer // electron-redux 39 | ]; 40 | } 41 | 42 | const rootReducer = getRootReducer(scope); 43 | const enhancer = applyMiddleware(...middleware); 44 | const store = createStore(rootReducer, initialState, enhancer); 45 | 46 | if (scope === 'main') { 47 | replayActionMain(store); 48 | } else { 49 | replayActionRenderer(store); 50 | } 51 | 52 | return { store, history }; 53 | } 54 | 55 | export default configureStore; 56 | -------------------------------------------------------------------------------- /app/main/auto-update.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles auto-updating 3 | */ 4 | 5 | import { autoUpdater, app } from 'electron'; 6 | import { platform as platformShort, arch } from 'os'; 7 | 8 | const platform = `${platformShort()}_${arch()}`; 9 | const version = app.getVersion(); 10 | 11 | export default function runAutoUpdate(mainWindow) { 12 | mainWindow.webContents.on('did-finish-load', () => { 13 | if (process.env.NODE_ENV === 'production') { 14 | try { 15 | autoUpdater.setFeedURL( 16 | `https://ft-ig-vocab-updates.herokuapp.com/update/${platform}/${version}` 17 | ); 18 | } catch (e) { 19 | console.error(e); 20 | } 21 | 22 | autoUpdater.on('checking-for-update', () => { 23 | if (mainWindow.webContents) mainWindow.webContents.send('checking-for-update'); 24 | }); 25 | 26 | autoUpdater.on('update-downloaded', () => { 27 | if (mainWindow.webContents) mainWindow.webContents.send('new-release'); 28 | }); 29 | 30 | autoUpdater.on('update-not-available', () => { 31 | if (mainWindow.webContents) mainWindow.webContents.send('no-update'); 32 | }); 33 | } else if (mainWindow) { 34 | console.info('in dev mode, simulating update...'); 35 | mainWindow.webContents.send('running-updater'); 36 | 37 | const t = setTimeout(() => { 38 | mainWindow.webContents.send('no-update'); 39 | }, 5000); 40 | 41 | mainWindow.webContents.on('close', () => clearTimeout(t)); 42 | } 43 | }); 44 | 45 | mainWindow.webContents.on('update-app', () => { 46 | autoUpdater.quitAndInstall(); 47 | }); 48 | } 49 | 50 | module.exports = runAutoUpdate; 51 | -------------------------------------------------------------------------------- /app/renderer/containers/GetData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Data-loading dropzone route 4 | */ 5 | 6 | import React from 'react'; 7 | import { connect } from 'react-redux'; 8 | import { push } from 'react-router-redux'; 9 | import Dropzone from 'react-dropzone'; 10 | import { loadUserData } from '../../shared/actions'; 11 | import styles from './GetData.css'; 12 | import type { templateType } from '../../shared/reducers/vocab'; 13 | 14 | type Props = { 15 | onDrop: () => void, 16 | selectedTemplate: string | null, 17 | redirect: () => void, 18 | templates: templateType[] 19 | }; 20 | 21 | const dropped = ({ onDrop, selectedTemplate, templates, redirect }: Props) => { 22 | if (!selectedTemplate) { 23 | redirect('/'); 24 | return null; 25 | } 26 | 27 | return ( 28 |
29 | onDrop(files, templates.find(t => t.chartName === selectedTemplate))} 32 | > 33 |
drag your data file here
34 |
(or click to browse)
35 |
36 | 39 |
40 | ); 41 | }; 42 | 43 | export default connect( 44 | state => ({ 45 | selectedTemplate: state.vocabApp.selectedTemplate, 46 | templates: state.vocabApp.templates 47 | }), 48 | dispatch => ({ 49 | onDrop: (files, selectedTemplate) => { 50 | dispatch(loadUserData(files, selectedTemplate)); 51 | }, 52 | redirect: path => dispatch(push(path)) 53 | }) 54 | )(dropped); 55 | -------------------------------------------------------------------------------- /webpack.config.main.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import merge from 'webpack-merge'; 7 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import baseConfig from './webpack.config.base'; 10 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 11 | 12 | CheckNodeEnv('production'); 13 | 14 | export default merge.smart(baseConfig, { 15 | devtool: 'source-map', 16 | 17 | target: 'electron-main', 18 | 19 | entry: './app/main/main.dev', 20 | 21 | // 'main.js' in root 22 | output: { 23 | path: __dirname, 24 | filename: './app/main/main.prod.js' 25 | }, 26 | 27 | plugins: [ 28 | new UglifyJSPlugin({ 29 | parallel: true, 30 | sourceMap: true 31 | }), 32 | 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 35 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 36 | }), 37 | 38 | /** 39 | * Create global constants which can be configured at compile time. 40 | * 41 | * Useful for allowing different behaviour between development builds and 42 | * release builds 43 | * 44 | * NODE_ENV should be production so that modules do not perform certain 45 | * development checks 46 | */ 47 | new webpack.DefinePlugin({ 48 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'), 49 | 'process.env.DEBUG_PROD': JSON.stringify(process.env.DEBUG_PROD || 'false') 50 | }) 51 | ], 52 | 53 | /** 54 | * Disables webpack processing of __dirname and __filename. 55 | * If you run the bundle in node.js it falls back to these values of node.js. 56 | * https://github.com/webpack/webpack/issues/2010 57 | */ 58 | node: { 59 | __dirname: false, 60 | __filename: false 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /test/containers/CounterPage.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | import { Provider } from 'react-redux'; 4 | import { createBrowserHistory } from 'history'; 5 | import { ConnectedRouter } from 'react-router-redux'; 6 | import CounterPage from '../../app/containers/CounterPage'; 7 | import { configureStore } from '../../app/store/configureStore'; 8 | 9 | function setup(initialState) { 10 | const store = configureStore(initialState); 11 | const history = createBrowserHistory(); 12 | const app = mount( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | return { 20 | app, 21 | buttons: app.find('button'), 22 | p: app.find('.counter') 23 | }; 24 | } 25 | 26 | describe('containers', () => { 27 | describe('App', () => { 28 | it('should display initial count', () => { 29 | const { p } = setup(); 30 | expect(p.text()).toMatch(/^0$/); 31 | }); 32 | 33 | it('should display updated count after increment button click', () => { 34 | const { buttons, p } = setup(); 35 | buttons.at(0).simulate('click'); 36 | expect(p.text()).toMatch(/^1$/); 37 | }); 38 | 39 | it('should display updated count after descrement button click', () => { 40 | const { buttons, p } = setup(); 41 | buttons.at(1).simulate('click'); 42 | expect(p.text()).toMatch(/^-1$/); 43 | }); 44 | 45 | it('shouldnt change if even and if odd button clicked', () => { 46 | const { buttons, p } = setup(); 47 | buttons.at(2).simulate('click'); 48 | expect(p.text()).toMatch(/^0$/); 49 | }); 50 | 51 | it('should change if odd and if odd button clicked', () => { 52 | const { buttons, p } = setup({ counter: 1 }); 53 | buttons.at(2).simulate('click'); 54 | expect(p.text()).toMatch(/^2$/); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /app/main/build-project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This does the heavy lifting once the app starts copying charts over. 3 | */ 4 | 5 | import { dialog } from 'electron'; 6 | import { copy, outputFile, readFile, writeFile } from 'fs-extra'; 7 | import { join } from 'path'; 8 | import { unparse } from 'papaparse'; 9 | import { homedir } from 'os'; 10 | import sed from 'parse-sed'; 11 | 12 | const HOME = homedir(); 13 | 14 | export default async function buildProject(templateData, spreadsheetData) { 15 | try { 16 | const csvData = unparse(spreadsheetData, { quotes: true }); 17 | const savePath = dialog.showSaveDialog({ 18 | title: 'Choose a directory to save your project...' 19 | }); 20 | 21 | console.log(`Saving to: ${savePath}`); 22 | 23 | const templatePath = join(HOME, '.vocab/', 'visual-vocabulary-templates/'); 24 | 25 | await copy(join(templatePath, templateData.templatePath), savePath); 26 | await outputFile(join(savePath, 'data.csv'), csvData); 27 | 28 | // Load index.js as string for replacements 29 | const indexJs = await readFile(`${savePath}/index.js`, 'utf-8'); 30 | 31 | // Create array of substitutions from "replace_x" columns 32 | const replacements = Object.entries(templateData) 33 | .sort(([key1], [key2]) => key1 > key2) 34 | .filter(([key]) => key.includes('replace_')) 35 | .map(([, val]) => val); 36 | 37 | // Make replacements 38 | const replacedIndexJs = replacements.reduce((str, subStr) => { 39 | const { commands } = sed(subStr); 40 | if (commands && commands.length) { 41 | const sub = commands.find(c => c.verb === 's'); 42 | return str.replace(sub.re, sub.replacement); 43 | } 44 | 45 | return str; 46 | }, indexJs); 47 | 48 | await writeFile(`${savePath}/index.js`, replacedIndexJs, 'utf-8'); 49 | 50 | return savePath; 51 | } catch (e) { 52 | console.error(e); 53 | // @TODO Handle errors here more elegantly 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | import { spy } from 'sinon'; 2 | import React from 'react'; 3 | import { shallow } from 'enzyme'; 4 | import { BrowserRouter as Router } from 'react-router-dom'; 5 | import renderer from 'react-test-renderer'; 6 | import Counter from '../../app/components/Counter'; 7 | 8 | function setup() { 9 | const actions = { 10 | increment: spy(), 11 | incrementIfOdd: spy(), 12 | incrementAsync: spy(), 13 | decrement: spy() 14 | }; 15 | const component = shallow(); 16 | return { 17 | component, 18 | actions, 19 | buttons: component.find('button'), 20 | p: component.find('.counter') 21 | }; 22 | } 23 | 24 | describe('Counter component', () => { 25 | it('should should display count', () => { 26 | const { p } = setup(); 27 | expect(p.text()).toMatch(/^1$/); 28 | }); 29 | 30 | it('should first button should call increment', () => { 31 | const { buttons, actions } = setup(); 32 | buttons.at(0).simulate('click'); 33 | expect(actions.increment.called).toBe(true); 34 | }); 35 | 36 | it('should match exact snapshot', () => { 37 | const { actions } = setup(); 38 | const tree = renderer 39 | .create( 40 |
41 | 42 | 43 | 44 |
45 | ) 46 | .toJSON(); 47 | 48 | expect(tree).toMatchSnapshot(); 49 | }); 50 | 51 | it('should second button should call decrement', () => { 52 | const { buttons, actions } = setup(); 53 | buttons.at(1).simulate('click'); 54 | expect(actions.decrement.called).toBe(true); 55 | }); 56 | 57 | it('should third button should call incrementIfOdd', () => { 58 | const { buttons, actions } = setup(); 59 | buttons.at(2).simulate('click'); 60 | expect(actions.incrementIfOdd.called).toBe(true); 61 | }); 62 | 63 | it('should fourth button should call incrementAsync', () => { 64 | const { buttons, actions } = setup(); 65 | buttons.at(3).simulate('click'); 66 | expect(actions.incrementAsync.called).toBe(true); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /app/main/post-build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This handles post-build tasks 3 | */ 4 | 5 | import { spawn } from 'child_process'; 6 | import exists from 'command-exists'; 7 | 8 | export default function postBuild(path) { 9 | try { 10 | exists('srvlr', (err, srvlr) => { 11 | if (srvlr) { 12 | // Open up new Terminal and run srvlr if it exists 13 | /* eslint-disable quotes */ 14 | spawn( 15 | 'osascript', 16 | [ 17 | '-e', 18 | `'tell application "terminal"'`, 19 | '-e', 20 | `'do script "cd ${path} && srvlr"'`, 21 | '-e', 22 | `'end tell'` 23 | ], 24 | { 25 | detached: true, 26 | shell: true 27 | } 28 | ); 29 | } else { 30 | // Or just open a Terminal if not. 31 | spawn( 32 | 'osascript', 33 | [ 34 | '-e', 35 | `'tell application "terminal"'`, 36 | '-e', 37 | `'do script "cd ${path}"'`, 38 | '-e', 39 | `'end tell'` 40 | ], 41 | { 42 | detached: true, 43 | shell: true 44 | } 45 | ); 46 | /* eslint-disable-end */ 47 | } 48 | }); 49 | } catch (err) { 50 | console.error(err); 51 | } 52 | 53 | // @TODO enable method for launching editor (needs a settings panel) 54 | // storage.get('preferredEditor', (err, { editor }) => { 55 | // if (err) throw err; 56 | // 57 | // try { 58 | // switch (editor) { 59 | // case 'default': 60 | // spawn('open', path, { 61 | // detached: true, 62 | // shell: true 63 | // }); 64 | // break; 65 | // case 'none': 66 | // break; 67 | // default: 68 | // spawn('open', ['-a', editor.replace(/\s/g, '\\ '), path], { 69 | // detached: true, 70 | // shell: true 71 | // }); 72 | // break; 73 | // } 74 | // } catch (error) { 75 | // console.error(error); 76 | // } 77 | // }); 78 | return path; 79 | } 80 | -------------------------------------------------------------------------------- /app/shared/reducers/vocab.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { 4 | LOAD_TEMPLATE_DATA, 5 | SELECT_CHART_TEMPLATE, 6 | LOAD_USER_DATA, 7 | SAVE_SPREADSHEET, 8 | SYNC_REPO 9 | } from '../actions'; 10 | 11 | export type sheetDataType = { 12 | rows: (?(string[]))[] 13 | }; 14 | 15 | export type vocabStateType = { 16 | templates: templateType[], 17 | categories: categoryType[], 18 | selectedTemplate: string | null, 19 | userData: string[][], 20 | sheetData: ?sheetDataType 21 | }; 22 | 23 | export type templateType = { 24 | chartName: string, 25 | category: string, 26 | img: string, 27 | avail: string, 28 | description: string, 29 | disabled: ?boolean | void 30 | }; 31 | 32 | export type categoryType = { 33 | category: string, 34 | colour: string, 35 | description: string, 36 | example: string 37 | }; 38 | 39 | type actionType = { 40 | +type: string, 41 | selectedTemplate?: string, 42 | userData?: (?(string[]))[], 43 | sheetData?: sheetDataType, 44 | templates?: templateType[], 45 | categories?: categoryType[], 46 | docsPath?: string 47 | }; 48 | 49 | export const initialState: vocabStateType = { 50 | templates: [], 51 | categories: [], 52 | selectedTemplate: null, 53 | userData: [], 54 | sheetData: { 55 | rows: [] 56 | }, 57 | docsPath: '', 58 | }; 59 | 60 | export default function vocabApp(state: vocabStateType = initialState, action: actionType) { 61 | switch (action.type) { 62 | case SYNC_REPO: 63 | return { 64 | ...state, 65 | }; 66 | case LOAD_TEMPLATE_DATA: 67 | return { 68 | ...state, 69 | templates: action.templates, 70 | categories: action.categories, 71 | docsPath: action.docsPath, 72 | }; 73 | case SELECT_CHART_TEMPLATE: 74 | return { 75 | ...state, 76 | selectedTemplate: action.selectedTemplate 77 | }; 78 | case LOAD_USER_DATA: 79 | return { 80 | ...state, 81 | userData: state.userData.concat(action.userData) 82 | }; 83 | case SAVE_SPREADSHEET: 84 | return { 85 | ...state, 86 | sheetData: action.sheetData 87 | }; 88 | default: 89 | return state; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/main/manage-vvt-repo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This updates the visual-vocabulary repo on app launch 3 | * 4 | * @flow 5 | */ 6 | 7 | import simpleGit from 'simple-git/promise'; 8 | import { statSync, mkdirSync } from 'fs'; 9 | import { join } from 'path'; 10 | import { homedir } from 'os'; 11 | 12 | const HOME = homedir(); 13 | const VVTUrl = 'https://github.com/ft-interactive/visual-vocabulary-templates.git'; 14 | 15 | export default async function syncVVTRepo() { 16 | const path = join(HOME, '.vocab/', 'visual-vocabulary-templates/'); 17 | const Git = simpleGit(); 18 | 19 | // Create parent config dir if doesn't exist 20 | try { 21 | statSync(join(HOME, '.vocab')); 22 | } catch (e) { 23 | if (e.code === 'ENOENT') { 24 | try { 25 | mkdirSync(join(HOME, '.vocab')); 26 | } catch (ee) { 27 | throw e; 28 | } 29 | } else { 30 | throw e; 31 | } 32 | } 33 | 34 | // If Visual Vocabulary directory doesn't exist, clone fresh. 35 | try { 36 | statSync(join(path, '/.git')); 37 | } catch (e) { 38 | if (e.code === 'ENOENT') { 39 | try { 40 | await Git.clone(VVTUrl, path, { '--depth': 1 }); 41 | console.info(`Visual Vocabulary cloned to ${path}`); 42 | } catch (ee) { 43 | throw ee; 44 | } 45 | } else { 46 | throw e; 47 | } 48 | } 49 | 50 | try { 51 | // Set Git path to visual-vocabulary repo. 52 | Git.cwd(path); 53 | 54 | // Pull from GitHub 55 | await Git.pull('origin', 'master'); 56 | console.info('Update done'); 57 | } catch (e) { 58 | // This is likely a merge conflict due to weirdness in the Visual Vocab dir 59 | if (e.message.includes('overwritten by merge')) { 60 | try { 61 | await Git.reset('hard'); 62 | await Git.clean('f', ['-d']); 63 | } catch (ee) { 64 | try { 65 | await Git.pull(); 66 | } catch (eee) { 67 | throw eee; 68 | } 69 | } 70 | } else if (e.message.includes('Permission denied (publickey).')) { 71 | try { 72 | await Git.removeRemote('origin'); 73 | await Git.addRemote('origin', VVTUrl); 74 | await Git.pull('origin', 'master'); 75 | } catch (ee) { 76 | throw ee; 77 | } 78 | } else { 79 | throw e; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/renderer/components/ChartList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Chart List 3 | * Displays the chart types 4 | * @flow 5 | */ 6 | 7 | import React from 'react'; 8 | import { connect } from 'react-redux'; 9 | import { Link } from 'react-router-dom'; 10 | import type { Match } from 'react-router-dom'; 11 | import { selectChartTemplate } from '../../shared/actions'; 12 | import { slug, bool } from '../../shared/utils'; 13 | import styles from './ChartList.css'; 14 | import type { templateType } from '../../shared/reducers/vocab'; 15 | 16 | type Props = { 17 | templates: Array, 18 | onTemplateClick: (chartName: string) => void, 19 | match: Match, 20 | docsPath: string 21 | }; 22 | 23 | const chartList = ({ templates, onTemplateClick, match, docsPath }: Props) => ( 24 |
    25 | {templates.filter(t => slug(t.category) === match.params.dimension && bool(t.avail)).map(t => ( 26 |
  • 27 | 28 | 40 | 41 |
  • 42 | ))} 43 |

    44 | The following are currently disabled because we are still building templates for them 45 |

    46 | {templates.filter(t => slug(t.category) === match.params.dimension && !bool(t.avail)).map(t => ( 47 |
  • 48 | 60 |
  • 61 | ))} 62 |
63 | ); 64 | 65 | const mapStateToProps = state => ({ 66 | templates: state.vocabApp.templates, 67 | docsPath: state.vocabApp.docsPath, 68 | }); 69 | 70 | const mapDispatchToProps = dispatch => ({ 71 | onTemplateClick: id => dispatch(selectChartTemplate(id)) 72 | }); 73 | 74 | export default connect(mapStateToProps, mapDispatchToProps)(chartList); 75 | -------------------------------------------------------------------------------- /app/shared/actions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Redux actions 3 | * @flow 4 | */ 5 | 6 | import { push } from 'react-router-redux'; 7 | import { createAliasedAction } from 'electron-redux'; 8 | import Papa from 'papaparse'; 9 | import buildProjectMain from '../../main/build-project'; 10 | import postBuild from '../../main/post-build'; 11 | 12 | export const LOAD_TEMPLATE_DATA = 'LOAD_TEMPLATE_DATA'; 13 | export const SELECT_CHART_TEMPLATE = 'SELECT_CHART_TEMPLATE'; 14 | export const LOAD_USER_DATA = 'LOAD_USER_DATA'; 15 | export const BUILD_PROJECT = 'BUILD_PROJECT'; 16 | export const POST_BUILD_PROJECT = 'POST_BUILD_PROJECT'; 17 | export const SYNC_REPO = 'SYNC_REPO'; 18 | 19 | export const syncRepo = createAliasedAction(SYNC_REPO, status => ({ 20 | type: SYNC_REPO, 21 | payload: status 22 | })); 23 | 24 | export const loadTemplateData = createAliasedAction( 25 | LOAD_TEMPLATE_DATA, 26 | ({ templates, categories, docsPath }) => ({ 27 | type: LOAD_TEMPLATE_DATA, 28 | templates, 29 | categories, 30 | docsPath 31 | }) 32 | ); 33 | 34 | export const selectChartTemplate = templateName => ({ 35 | type: SELECT_CHART_TEMPLATE, 36 | selectedTemplate: templateName 37 | }); 38 | 39 | export const loadUserData = (files, selectedTemplate) => dispatch => 40 | new Promise((resolve, reject) => { 41 | Papa.parse(files[0], { 42 | header: false, 43 | complete: ({ data: userData }) => resolve(userData), 44 | error: err => reject(err) 45 | }); 46 | }) 47 | .then(result => 48 | dispatch({ 49 | type: LOAD_USER_DATA, 50 | userData: result 51 | }) 52 | ) 53 | .then(({ userData }) => { 54 | if (userData && userData.length > 0) { 55 | const headerRow = userData[0]; 56 | 57 | if (!headerRow.includes('highlight')) { 58 | headerRow.push('highlight'); 59 | } 60 | 61 | if (!headerRow.includes('annotate')) { 62 | headerRow.push('annotate'); 63 | } 64 | 65 | const parsed = userData 66 | .slice(1) 67 | .filter(d => d.join('') !== '') 68 | .map((el) => { 69 | const origLen = el.length; 70 | el.length = headerRow.length; // eslint-disable-line no-param-reassign 71 | return el.fill('', origLen); 72 | }, []); 73 | 74 | return { 75 | sheetData: [ 76 | headerRow, 77 | ...parsed, 78 | ], 79 | selectedTemplate 80 | }; 81 | } 82 | return null; 83 | }) 84 | .then(({ sheetData }) => dispatch(buildProject(selectedTemplate, sheetData))); 85 | 86 | export const buildProject = createAliasedAction( 87 | BUILD_PROJECT, 88 | (chartType, spreadsheetData) => dispatch => 89 | buildProjectMain(chartType, spreadsheetData) 90 | .then(postBuild) 91 | .then(payload => 92 | dispatch({ 93 | type: BUILD_PROJECT, 94 | payload 95 | }) 96 | ) 97 | .then(() => dispatch(push('/'))) 98 | ); 99 | -------------------------------------------------------------------------------- /app/shared/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { createBrowserHistory } from 'history'; 4 | import { routerMiddleware, routerActions } from 'react-router-redux'; 5 | import { createLogger } from 'redux-logger'; 6 | import { 7 | forwardToMain, 8 | forwardToRenderer, 9 | triggerAlias, 10 | replayActionMain, 11 | replayActionRenderer 12 | } from 'electron-redux'; 13 | import getRootReducer from '../reducers'; 14 | import * as vocabActions from '../actions'; 15 | import type { vocabStateType } from '../reducers/vocab'; 16 | 17 | /** 18 | * @param {Object} initialState 19 | * @param {String} [scope='main|renderer'] 20 | * @return {Object} store 21 | */ 22 | const configureStore = (initialState?: { vocabApp: vocabStateType }, scope = 'main') => { 23 | let history; 24 | // Redux Configuration 25 | let middleware = []; 26 | 27 | // Thunk Middleware 28 | middleware.push(thunk); 29 | 30 | // Logging Middleware 31 | const logger = createLogger({ 32 | level: scope === 'main' ? undefined : 'info', 33 | collapsed: true 34 | }); 35 | middleware.push(logger); 36 | 37 | if (scope === 'renderer') { 38 | history = createBrowserHistory(); 39 | middleware = [ 40 | forwardToMain, // electron-redux 41 | routerMiddleware(history), // router 42 | ...middleware 43 | ]; 44 | } 45 | 46 | if (scope === 'main') { 47 | middleware = [ 48 | triggerAlias, // electron-redux 49 | ...middleware, 50 | forwardToRenderer // electron-redux 51 | ]; 52 | } 53 | 54 | const enhanced = [applyMiddleware(...middleware)]; 55 | 56 | let enhancer; 57 | 58 | if (/*! process.env.NODE_ENV && */ scope === 'renderer') { 59 | // Redux DevTools Configuration 60 | const actionCreators = { 61 | ...vocabActions, 62 | ...routerActions 63 | }; 64 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose 65 | /* eslint-disable no-underscore-dangle */ 66 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 67 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 68 | // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html 69 | actionCreators 70 | }) 71 | : compose; 72 | /* eslint-enable no-underscore-dangle */ 73 | enhancer = composeEnhancers(...enhanced); 74 | } else { 75 | enhancer = compose(...enhanced); 76 | } 77 | 78 | const rootReducer = getRootReducer(scope); 79 | const store = createStore(rootReducer, initialState, enhancer); 80 | 81 | if (!process.env.NODE_ENV && module.hot) { 82 | module.hot.accept( 83 | '../reducers', 84 | () => store.replaceReducer(require('../reducers')) // eslint-disable-line global-require 85 | ); 86 | } 87 | 88 | if (scope === 'main') { 89 | replayActionMain(store); 90 | } else { 91 | replayActionRenderer(store); 92 | } 93 | 94 | return { store, history }; 95 | }; 96 | 97 | export default configureStore; 98 | -------------------------------------------------------------------------------- /app/main/main.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 1, flowtype-errors/show-errors: 0 */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build-main`, this file is compiled to 9 | * `./app/main/main.prod.js` using webpack. This gives us some performance wins. 10 | * 11 | * @flow 12 | */ 13 | import { app, BrowserWindow } from 'electron'; 14 | import MenuBuilder from './menu'; 15 | import templatesPromise from './load-templates'; 16 | import configureStore from '../shared/store/configureStore'; 17 | import { loadTemplateData } from '../shared/actions'; 18 | // import runAutoUpdate from './auto-update'; 19 | import syncVVTRepo from './manage-vvt-repo'; 20 | 21 | let mainWindow = null; 22 | 23 | if (process.env.NODE_ENV === 'production') { 24 | const sourceMapSupport = require('source-map-support'); // eslint-disable-line global-require 25 | sourceMapSupport.install(); 26 | } 27 | 28 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { 29 | require('electron-debug')(); // eslint-disable-line global-require 30 | const path = require('path'); // eslint-disable-line global-require 31 | const p = path.join(__dirname, '..', 'app', 'node_modules'); 32 | require('module').globalPaths.push(p); // eslint-disable-line global-require 33 | } 34 | 35 | const installExtensions = async () => { 36 | const installer = require('electron-devtools-installer'); // eslint-disable-line global-require 37 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 38 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; 39 | 40 | return Promise.all( 41 | extensions.map(name => installer.default(installer[name], forceDownload)) 42 | ).catch(console.log); 43 | }; 44 | 45 | async function start() { 46 | const { store } = configureStore(global.state, 'main'); 47 | 48 | try { 49 | await syncVVTRepo(); 50 | } catch (e) { 51 | console.error('Error synchronising VVT repo'); 52 | throw e; 53 | } 54 | 55 | try { 56 | const templates = await templatesPromise(); 57 | store.dispatch(loadTemplateData(templates)); 58 | } catch (e) { 59 | console.error(e); 60 | } 61 | 62 | // runAutoUpdate(mainWindow, store); 63 | 64 | app.on('window-all-closed', () => { 65 | // Respect the OSX convention of having the application in memory even 66 | // after all windows have been closed 67 | if (process.platform !== 'darwin') { 68 | app.quit(); 69 | } 70 | }); 71 | } 72 | 73 | app.on('ready', async () => { 74 | try { 75 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { 76 | await installExtensions(); 77 | } 78 | await start(); 79 | } catch (e) { 80 | console.error(e); 81 | } 82 | 83 | mainWindow = new BrowserWindow({ 84 | show: false, 85 | width: 1024, 86 | height: 728 87 | }); 88 | 89 | mainWindow.loadURL(`file://${__dirname}/../renderer/app.html`); 90 | 91 | // @TODO: Use 'ready-to-show' event 92 | // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event 93 | mainWindow.webContents.on('did-finish-load', () => { 94 | if (!mainWindow) { 95 | throw new Error('"mainWindow" is not defined'); 96 | } 97 | mainWindow.show(); 98 | mainWindow.focus(); 99 | }); 100 | 101 | mainWindow.on('closed', () => { 102 | mainWindow = null; 103 | }); 104 | 105 | const menuBuilder = new MenuBuilder(mainWindow); 106 | menuBuilder.buildMenu(); 107 | }); 108 | -------------------------------------------------------------------------------- /test/e2e/e2e.spec.js: -------------------------------------------------------------------------------- 1 | import { Application } from 'spectron'; 2 | import electronPath from 'electron'; 3 | import path from 'path'; 4 | 5 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; 6 | 7 | const delay = time => new Promise(resolve => setTimeout(resolve, time)); 8 | 9 | describe('main window', function spec() { 10 | beforeAll(async () => { 11 | this.app = new Application({ 12 | path: electronPath, 13 | args: [path.join(__dirname, '..', '..', 'app')], 14 | }); 15 | 16 | return this.app.start(); 17 | }); 18 | 19 | afterAll(() => { 20 | if (this.app && this.app.isRunning()) { 21 | return this.app.stop(); 22 | } 23 | }); 24 | 25 | const findCounter = () => this.app.client.element('[data-tid="counter"]'); 26 | 27 | const findButtons = async () => { 28 | const { value } = await this.app.client.elements('[data-tclass="btn"]'); 29 | return value.map(btn => btn.ELEMENT); 30 | }; 31 | 32 | it('should open window', async () => { 33 | const { client, browserWindow } = this.app; 34 | 35 | await client.waitUntilWindowLoaded(); 36 | await delay(500); 37 | const title = await browserWindow.getTitle(); 38 | expect(title).toBe('Hello Electron React!'); 39 | }); 40 | 41 | it('should haven\'t any logs in console of main window', async () => { 42 | const { client } = this.app; 43 | const logs = await client.getRenderProcessLogs(); 44 | // Print renderer process logs 45 | logs.forEach(log => { 46 | console.log(log.message); 47 | console.log(log.source); 48 | console.log(log.level); 49 | }); 50 | expect(logs).toHaveLength(0); 51 | }); 52 | 53 | it('should to Counter with click "to Counter" link', async () => { 54 | const { client } = this.app; 55 | 56 | await client.click('[data-tid=container] > a'); 57 | expect(await findCounter().getText()).toBe('0'); 58 | }); 59 | 60 | it('should display updated count after increment button click', async () => { 61 | const { client } = this.app; 62 | 63 | const buttons = await findButtons(); 64 | await client.elementIdClick(buttons[0]); // + 65 | expect(await findCounter().getText()).toBe('1'); 66 | }); 67 | 68 | it('should display updated count after descrement button click', async () => { 69 | const { client } = this.app; 70 | 71 | const buttons = await findButtons(); 72 | await client.elementIdClick(buttons[1]); // - 73 | expect(await findCounter().getText()).toBe('0'); 74 | }); 75 | 76 | it('shouldnt change if even and if odd button clicked', async () => { 77 | const { client } = this.app; 78 | 79 | const buttons = await findButtons(); 80 | await client.elementIdClick(buttons[2]); // odd 81 | expect(await findCounter().getText()).toBe('0'); 82 | }); 83 | 84 | it('should change if odd and if odd button clicked', async () => { 85 | const { client } = this.app; 86 | 87 | const buttons = await findButtons(); 88 | await client.elementIdClick(buttons[0]); // + 89 | await client.elementIdClick(buttons[2]); // odd 90 | expect(await findCounter().getText()).toBe('2'); 91 | }); 92 | 93 | it('should change if async button clicked and a second later', async () => { 94 | const { client } = this.app; 95 | 96 | const buttons = await findButtons(); 97 | await client.elementIdClick(buttons[3]); // async 98 | expect(await findCounter().getText()).toBe('2'); 99 | await delay(1500); 100 | expect(await findCounter().getText()).toBe('3'); 101 | }); 102 | 103 | it('should back to home if back button clicked', async () => { 104 | const { client } = this.app; 105 | await client.element( 106 | '[data-tid="backButton"] > a' 107 | ).click(); 108 | 109 | expect( 110 | await client.isExisting('[data-tid="container"]') 111 | ).toBe(true); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /webpack.config.renderer.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import merge from 'webpack-merge'; 10 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin'; 11 | import baseConfig from './webpack.config.base'; 12 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 13 | 14 | CheckNodeEnv('production'); 15 | 16 | export default merge.smart(baseConfig, { 17 | devtool: 'source-map', 18 | 19 | target: 'electron-renderer', 20 | 21 | entry: './app/renderer/index', 22 | 23 | output: { 24 | path: path.join(__dirname, 'app/dist'), 25 | publicPath: '../dist/', 26 | filename: 'renderer.prod.js' 27 | }, 28 | 29 | module: { 30 | rules: [ 31 | // Extract all .global.css to style.css as is 32 | { 33 | test: /\.global\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader' 37 | }) 38 | }, 39 | // Pipe other styles through css modules and append to style.css 40 | { 41 | test: /^((?!\.global).)*\.css$/, 42 | use: ExtractTextPlugin.extract({ 43 | use: { 44 | loader: 'css-loader', 45 | options: { 46 | modules: true, 47 | importLoaders: 1, 48 | localIdentName: '[name]__[local]__[hash:base64:5]' 49 | } 50 | } 51 | }) 52 | }, 53 | // Add SASS support - compile all .global.scss files and pipe it to style.css 54 | { 55 | test: /\.global\.scss$/, 56 | use: ExtractTextPlugin.extract({ 57 | use: [ 58 | { 59 | loader: 'css-loader' 60 | }, 61 | { 62 | loader: 'sass-loader' 63 | } 64 | ], 65 | fallback: 'style-loader' 66 | }) 67 | }, 68 | // Add SASS support - compile all other .scss files and pipe it to style.css 69 | { 70 | test: /^((?!\.global).)*\.scss$/, 71 | use: ExtractTextPlugin.extract({ 72 | use: [ 73 | { 74 | loader: 'css-loader', 75 | options: { 76 | modules: true, 77 | importLoaders: 1, 78 | localIdentName: '[name]__[local]__[hash:base64:5]' 79 | } 80 | }, 81 | { 82 | loader: 'sass-loader' 83 | } 84 | ] 85 | }) 86 | }, 87 | // WOFF Font 88 | { 89 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 90 | use: { 91 | loader: 'url-loader', 92 | options: { 93 | limit: 10000, 94 | mimetype: 'application/font-woff' 95 | } 96 | } 97 | }, 98 | // WOFF2 Font 99 | { 100 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 101 | use: { 102 | loader: 'url-loader', 103 | options: { 104 | limit: 10000, 105 | mimetype: 'application/font-woff' 106 | } 107 | } 108 | }, 109 | // TTF Font 110 | { 111 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 112 | use: { 113 | loader: 'url-loader', 114 | options: { 115 | limit: 10000, 116 | mimetype: 'application/octet-stream' 117 | } 118 | } 119 | }, 120 | // EOT Font 121 | { 122 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 123 | use: 'file-loader' 124 | }, 125 | // SVG Font 126 | { 127 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 128 | use: { 129 | loader: 'url-loader', 130 | options: { 131 | limit: 10000, 132 | mimetype: 'image/svg+xml' 133 | } 134 | } 135 | }, 136 | // Common Image Formats 137 | { 138 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 139 | use: 'url-loader' 140 | } 141 | ] 142 | }, 143 | 144 | plugins: [ 145 | /** 146 | * Create global constants which can be configured at compile time. 147 | * 148 | * Useful for allowing different behaviour between development builds and 149 | * release builds 150 | * 151 | * NODE_ENV should be production so that modules do not perform certain 152 | * development checks 153 | */ 154 | new webpack.DefinePlugin({ 155 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') 156 | }), 157 | 158 | new UglifyJSPlugin({ 159 | parallel: true, 160 | sourceMap: true 161 | }), 162 | 163 | new ExtractTextPlugin('style.css'), 164 | 165 | new BundleAnalyzerPlugin({ 166 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 167 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 168 | }) 169 | ] 170 | }); 171 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.dll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import merge from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import { dependencies } from './package.json'; 10 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 11 | 12 | CheckNodeEnv('development'); 13 | 14 | const dist = path.resolve(process.cwd(), 'dll'); 15 | 16 | export default merge.smart(baseConfig, { 17 | context: process.cwd(), 18 | 19 | devtool: 'eval', 20 | 21 | target: 'electron-renderer', 22 | 23 | externals: ['fsevents', 'crypto-browserify'], 24 | 25 | /** 26 | * @HACK: Copy and pasted from renderer dev config. Consider merging these 27 | * rules into the base config. May cause breaking changes. 28 | */ 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.global\.css$/, 33 | use: [ 34 | { 35 | loader: 'style-loader' 36 | }, 37 | { 38 | loader: 'css-loader', 39 | options: { 40 | sourceMap: true 41 | } 42 | } 43 | ] 44 | }, 45 | { 46 | test: /^((?!\.global).)*\.css$/, 47 | use: [ 48 | { 49 | loader: 'style-loader' 50 | }, 51 | { 52 | loader: 'css-loader', 53 | options: { 54 | modules: true, 55 | sourceMap: true, 56 | importLoaders: 1, 57 | localIdentName: '[name]__[local]__[hash:base64:5]' 58 | } 59 | } 60 | ] 61 | }, 62 | // Add SASS support - compile all .global.scss files and pipe it to style.css 63 | { 64 | test: /\.global\.scss$/, 65 | use: [ 66 | { 67 | loader: 'style-loader' 68 | }, 69 | { 70 | loader: 'css-loader', 71 | options: { 72 | sourceMap: true 73 | } 74 | }, 75 | { 76 | loader: 'sass-loader' 77 | } 78 | ] 79 | }, 80 | // Add SASS support - compile all other .scss files and pipe it to style.css 81 | { 82 | test: /^((?!\.global).)*\.scss$/, 83 | use: [ 84 | { 85 | loader: 'style-loader' 86 | }, 87 | { 88 | loader: 'css-loader', 89 | options: { 90 | modules: true, 91 | sourceMap: true, 92 | importLoaders: 1, 93 | localIdentName: '[name]__[local]__[hash:base64:5]' 94 | } 95 | }, 96 | { 97 | loader: 'sass-loader' 98 | } 99 | ] 100 | }, 101 | // WOFF Font 102 | { 103 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 104 | use: { 105 | loader: 'url-loader', 106 | options: { 107 | limit: 10000, 108 | mimetype: 'application/font-woff' 109 | } 110 | } 111 | }, 112 | // WOFF2 Font 113 | { 114 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 115 | use: { 116 | loader: 'url-loader', 117 | options: { 118 | limit: 10000, 119 | mimetype: 'application/font-woff' 120 | } 121 | } 122 | }, 123 | // TTF Font 124 | { 125 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 126 | use: { 127 | loader: 'url-loader', 128 | options: { 129 | limit: 10000, 130 | mimetype: 'application/octet-stream' 131 | } 132 | } 133 | }, 134 | // EOT Font 135 | { 136 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 137 | use: 'file-loader' 138 | }, 139 | // SVG Font 140 | { 141 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 142 | use: { 143 | loader: 'url-loader', 144 | options: { 145 | limit: 10000, 146 | mimetype: 'image/svg+xml' 147 | } 148 | } 149 | }, 150 | // Common Image Formats 151 | { 152 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 153 | use: 'url-loader' 154 | } 155 | ] 156 | }, 157 | 158 | resolve: { 159 | modules: ['app'] 160 | }, 161 | 162 | entry: { 163 | renderer: Object.keys(dependencies || {}).filter(dependency => dependency !== 'font-awesome') 164 | }, 165 | 166 | output: { 167 | library: 'renderer', 168 | path: dist, 169 | filename: '[name].dev.dll.js', 170 | libraryTarget: 'var' 171 | }, 172 | 173 | plugins: [ 174 | new webpack.DllPlugin({ 175 | path: path.join(dist, '[name].json'), 176 | name: '[name]' 177 | }), 178 | 179 | /** 180 | * Create global constants which can be configured at compile time. 181 | * 182 | * Useful for allowing different behaviour between development builds and 183 | * release builds 184 | * 185 | * NODE_ENV should be production so that modules do not perform certain 186 | * development checks 187 | */ 188 | new webpack.DefinePlugin({ 189 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 190 | }), 191 | 192 | new webpack.LoaderOptionsPlugin({ 193 | debug: true, 194 | options: { 195 | context: path.resolve(process.cwd(), 'app', 'renderer'), 196 | output: { 197 | path: path.resolve(process.cwd(), 'dll') 198 | } 199 | } 200 | }) 201 | ] 202 | }); 203 | -------------------------------------------------------------------------------- /app/main/menu.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { app, Menu, shell, BrowserWindow } from 'electron'; 3 | 4 | export default class MenuBuilder { 5 | mainWindow: BrowserWindow; 6 | 7 | constructor(mainWindow: BrowserWindow) { 8 | this.mainWindow = mainWindow; 9 | } 10 | 11 | buildMenu() { 12 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { 13 | this.setupDevelopmentEnvironment(); 14 | } 15 | 16 | let template; 17 | 18 | if (process.platform === 'darwin') { 19 | template = this.buildDarwinTemplate(); 20 | } else { 21 | template = this.buildDefaultTemplate(); 22 | } 23 | 24 | const menu = Menu.buildFromTemplate(template); 25 | Menu.setApplicationMenu(menu); 26 | 27 | return menu; 28 | } 29 | 30 | setupDevelopmentEnvironment() { 31 | this.mainWindow.openDevTools(); 32 | this.mainWindow.webContents.on('context-menu', (e, props) => { 33 | const { x, y } = props; 34 | 35 | Menu 36 | .buildFromTemplate([{ 37 | label: 'Inspect element', 38 | click: () => { 39 | this.mainWindow.inspectElement(x, y); 40 | } 41 | }]) 42 | .popup(this.mainWindow); 43 | }); 44 | } 45 | 46 | buildDarwinTemplate() { 47 | const subMenuAbout = { 48 | label: 'Electron', 49 | submenu: [ 50 | { label: 'About ElectronReact', selector: 'orderFrontStandardAboutPanel:' }, 51 | { type: 'separator' }, 52 | { label: 'Services', submenu: [] }, 53 | { type: 'separator' }, 54 | { label: 'Hide ElectronReact', accelerator: 'Command+H', selector: 'hide:' }, 55 | { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }, 56 | { label: 'Show All', selector: 'unhideAllApplications:' }, 57 | { type: 'separator' }, 58 | { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit(); } } 59 | ] 60 | }; 61 | const subMenuEdit = { 62 | label: 'Edit', 63 | submenu: [ 64 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }, 65 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' }, 66 | { type: 'separator' }, 67 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }, 68 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }, 69 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }, 70 | { label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' } 71 | ] 72 | }; 73 | const subMenuViewDev = { 74 | label: 'View', 75 | submenu: [ 76 | { label: 'Reload', accelerator: 'Command+R', click: () => { this.mainWindow.webContents.reload(); } }, 77 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } }, 78 | { label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: () => { this.mainWindow.toggleDevTools(); } } 79 | ] 80 | }; 81 | const subMenuViewProd = { 82 | label: 'View', 83 | submenu: [ 84 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } } 85 | ] 86 | }; 87 | const subMenuWindow = { 88 | label: 'Window', 89 | submenu: [ 90 | { label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' }, 91 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 92 | { type: 'separator' }, 93 | { label: 'Bring All to Front', selector: 'arrangeInFront:' } 94 | ] 95 | }; 96 | const subMenuHelp = { 97 | label: 'Help', 98 | submenu: [ 99 | { label: 'Learn More', click() { shell.openExternal('http://electron.atom.io'); } }, 100 | { label: 'Documentation', click() { shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); } }, 101 | { label: 'Community Discussions', click() { shell.openExternal('https://discuss.atom.io/c/electron'); } }, 102 | { label: 'Search Issues', click() { shell.openExternal('https://github.com/atom/electron/issues'); } } 103 | ] 104 | }; 105 | 106 | const subMenuView = process.env.NODE_ENV === 'development' 107 | ? subMenuViewDev 108 | : subMenuViewProd; 109 | 110 | return [ 111 | subMenuAbout, 112 | subMenuEdit, 113 | subMenuView, 114 | subMenuWindow, 115 | subMenuHelp 116 | ]; 117 | } 118 | 119 | buildDefaultTemplate() { 120 | const templateDefault = [{ 121 | label: '&File', 122 | submenu: [{ 123 | label: '&Open', 124 | accelerator: 'Ctrl+O' 125 | }, { 126 | label: '&Close', 127 | accelerator: 'Ctrl+W', 128 | click: () => { 129 | this.mainWindow.close(); 130 | } 131 | }] 132 | }, { 133 | label: '&View', 134 | submenu: (process.env.NODE_ENV === 'development') ? [{ 135 | label: '&Reload', 136 | accelerator: 'Ctrl+R', 137 | click: () => { 138 | this.mainWindow.webContents.reload(); 139 | } 140 | }, { 141 | label: 'Toggle &Full Screen', 142 | accelerator: 'F11', 143 | click: () => { 144 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 145 | } 146 | }, { 147 | label: 'Toggle &Developer Tools', 148 | accelerator: 'Alt+Ctrl+I', 149 | click: () => { 150 | this.mainWindow.toggleDevTools(); 151 | } 152 | }] : [{ 153 | label: 'Toggle &Full Screen', 154 | accelerator: 'F11', 155 | click: () => { 156 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 157 | } 158 | }] 159 | }, { 160 | label: 'Help', 161 | submenu: [{ 162 | label: 'Learn More', 163 | click() { 164 | shell.openExternal('http://electron.atom.io'); 165 | } 166 | }, { 167 | label: 'Documentation', 168 | click() { 169 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 170 | } 171 | }, { 172 | label: 'Community Discussions', 173 | click() { 174 | shell.openExternal('https://discuss.atom.io/c/electron'); 175 | } 176 | }, { 177 | label: 'Search Issues', 178 | click() { 179 | shell.openExternal('https://github.com/atom/electron/issues'); 180 | } 181 | }] 182 | }]; 183 | 184 | return templateDefault; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## vocab 2 | 3 | Rapidly scaffold out visual-vocabulary projects 4 | 5 | ## Install 6 | 7 | * **Note: requires a node version >= 6 and an npm version >= 3.** 8 | * **If you have installation or compilation issues with this project, please see [our debugging guide](https://github.com/chentsulin/electron-react-boilerplate/issues/400)** 9 | 10 | First, clone the repo via git: 11 | 12 | ```bash 13 | git clone --depth=1 https://github.com/ft-interactive/vocab.git 14 | ``` 15 | 16 | And then install dependencies. 17 | **ProTip**: Install with [yarn](https://github.com/yarnpkg/yarn) for faster and safer installation: 18 | 19 | ```bash 20 | $ cd vocab && npm install 21 | ``` 22 | 23 | ## Run 24 | 25 | Start the app in the `dev` environment. This starts the renderer process in [**hot-module-replacement**](https://webpack.js.org/guides/hmr-react/) mode and starts a server sends hot updates to the renderer process: 26 | 27 | ```bash 28 | $ npm run dev 29 | ``` 30 | 31 | You Run these two commands __simultaneously__ in different console tabs: 32 | 33 | ```bash 34 | $ npm run start-renderer-dev 35 | $ npm run start-main-dev 36 | ``` 37 | 38 | ## Editor Configuration 39 | **Atom** 40 | ```bash 41 | apm install editorconfig es6-javascript atom-ternjs javascript-snippets linter linter-eslint language-babel autocomplete-modules file-icons 42 | ``` 43 | 44 | **VSCode** 45 | * [Editorconfig](https://github.com/editorconfig/editorconfig-vscode) 46 | * [ESLint](https://github.com/Microsoft/vscode-eslint) 47 | * [Flow](https://github.com/flowtype/flow-for-vscode) 48 | * [Babel](https://github.com/dzannotti/vscode-babel) 49 | * [Jest](https://github.com/orta/vscode-jest) 50 | * [ES6 Snippets](https://marketplace.visualstudio.com/items?itemName=xabikos.JavaScriptSnippets) 51 | * [React Snippets](https://marketplace.visualstudio.com/items?itemName=xabikos.ReactSnippets) 52 | :bulb: *If you are using the `flow-for-vscode` plugin, make sure to disable the `flowtype-errors/show-errors` eslint rule in the `.eslintrc` by setting it to `0`* 53 | 54 | **Sublime** 55 | * [Editorconfig Integration](https://github.com/sindresorhus/editorconfig-sublime#readme) 56 | * [Linting](https://github.com/SublimeLinter/SublimeLinter3) 57 | * [ESLint Integration](https://github.com/roadhump/SublimeLinter-eslint) 58 | * [Syntax Highlighting](https://github.com/babel/babel-sublime) 59 | * [Autocompletion](https://github.com/ternjs/tern_for_sublime) 60 | * [Node Snippets](https://packagecontrol.io/packages/JavaScript%20%26%20NodeJS%20Snippets) 61 | * [ES6 Snippets](https://packagecontrol.io/packages/ES6-Toolkit) 62 | 63 | **Others** 64 | * [Editorconfig](http://editorconfig.org/#download) 65 | * [ESLint](http://eslint.org/docs/user-guide/integrations#editors) 66 | * Babel Syntax Plugin 67 | 68 | ## DevTools 69 | 70 | #### Toggle Chrome DevTools 71 | 72 | - OS X: Cmd Alt I or F12 73 | - Linux: Ctrl Shift I or F12 74 | - Windows: Ctrl Shift I or F12 75 | 76 | *See [electron-debug](https://github.com/sindresorhus/electron-debug) for more information.* 77 | 78 | #### DevTools extension 79 | 80 | This boilerplate is included following DevTools extensions: 81 | 82 | * [Devtron](https://github.com/electron/devtron) - Install via [electron-debug](https://github.com/sindresorhus/electron-debug). 83 | * [React Developer Tools](https://github.com/facebook/react-devtools) - Install via [electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer). 84 | * [Redux DevTools](https://github.com/zalmoxisus/redux-devtools-extension) - Install via [electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer). 85 | 86 | You can find the tabs on Chrome DevTools. 87 | 88 | If you want to update extensions version, please set `UPGRADE_EXTENSIONS` env, just run: 89 | 90 | ```bash 91 | $ UPGRADE_EXTENSIONS=1 npm run dev 92 | # For Windows 93 | $ set UPGRADE_EXTENSIONS=1 && npm run dev 94 | ``` 95 | 96 | :bulb: You can debug your production build with devtools by simply setting the `DEBUG_PROD` env variable: 97 | ``` 98 | DEBUG_PROD=true npm run package 99 | ``` 100 | 101 | 102 | ## CSS Modules 103 | 104 | This boilerplate out of the box is configured to use [css-modules](https://github.com/css-modules/css-modules). 105 | 106 | All `.css` file extensions will use css-modules unless it has `.global.css`. 107 | 108 | If you need global styles, stylesheets with `.global.css` will not go through the 109 | css-modules loader. e.g. `app.global.css` 110 | 111 | If you want to import global css libraries (like `bootstrap`), you can just write the following code in `.global.css`: 112 | 113 | ```css 114 | @import "~bootstrap/dist/css/bootstrap.css"; 115 | ``` 116 | 117 | ## Sass support 118 | 119 | If you want to use Sass in your app, you only need to import `.sass` files instead of `.css` once: 120 | ```js 121 | import './app.global.scss'; 122 | ``` 123 | 124 | 125 | ## Packaging 126 | 127 | To package apps for the local platform: 128 | 129 | ```bash 130 | $ npm run package 131 | ``` 132 | 133 | To package apps for all platforms: 134 | 135 | First, refer to [Multi Platform Build](https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build) for dependencies. 136 | 137 | Then, 138 | ```bash 139 | $ npm run package-all 140 | ``` 141 | 142 | To package apps with options: 143 | 144 | ```bash 145 | $ npm run package -- --[option] 146 | ``` 147 | 148 | ## Further commands 149 | 150 | To run the application without packaging run 151 | 152 | ```bash 153 | $ npm run build 154 | $ npm start 155 | ``` 156 | 157 | To run End-to-End Test 158 | 159 | ```bash 160 | $ npm run build 161 | $ npm run test-e2e 162 | ``` 163 | 164 | #### Options 165 | 166 | See [electron-builder CLI Usage](https://github.com/electron-userland/electron-builder#cli-usage) 167 | 168 | #### Module Structure 169 | 170 | This boilerplate uses a [two package.json structure](https://github.com/electron-userland/electron-builder/wiki/Two-package.json-Structure). For an example app that uses this boilerplate and packages native dependencies, see [erb-sqlite-example](https://github.com/amilajack/erb-sqlite-example). 171 | 172 | 1. If the module is native to a platform or otherwise should be included with the published package (i.e. bcrypt, openbci), it should be listed under `dependencies` in `./app/package.json`. 173 | 2. If a module is `import`ed by another module, include it in `dependencies` in `./package.json`. See [this ESLint rule](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md). 174 | 3. Otherwise, modules used for building, testing and debugging should be included in `devDependencies` in `./package.json`. 175 | 176 | ## Static Type Checking 177 | This project comes with Flow support out of the box! You can annotate your code with types, [get Flow errors as ESLint errors](https://github.com/amilajack/eslint-plugin-flowtype-errors), and get [type errors during runtime](https://github.com/codemix/flow-runtime) during development. Types are completely optional. 178 | 179 | ## Native-like UI 180 | 181 | If you want to have native-like User Interface (OS X El Capitan and Windows 10), [react-desktop](https://github.com/gabrielbull/react-desktop) may perfect suit for you. 182 | 183 | ## Dispatching redux actions from main process 184 | 185 | see discusses in [#118](https://github.com/chentsulin/electron-react-boilerplate/issues/118) and [#108](https://github.com/chentsulin/electron-react-boilerplate/issues/108) 186 | 187 | ## How to keep the boilerplate updated 188 | 189 | If your application is a fork from this repo, you can add this repo to another git remote: 190 | 191 | ```sh 192 | git remote add upstream https://github.com/chentsulin/electron-react-boilerplate.git 193 | ``` 194 | 195 | Then, use git to merge some latest commits: 196 | 197 | ```sh 198 | git pull upstream master 199 | ``` 200 | 201 | ## Maintainers 202 | 203 | - [C. T. Lin](https://github.com/chentsulin) 204 | - [Jhen-Jie Hong](https://github.com/jhen0409) 205 | - [Amila Welihinda](https://github.com/amilajack) 206 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, import/no-dynamic-require: 0 */ 2 | 3 | /** 4 | * Build config for development electron renderer process that uses 5 | * Hot-Module-Replacement 6 | * 7 | * https://webpack.js.org/concepts/hot-module-replacement/ 8 | */ 9 | 10 | import path from 'path'; 11 | import fs from 'fs'; 12 | import webpack from 'webpack'; 13 | import chalk from 'chalk'; 14 | import merge from 'webpack-merge'; 15 | import { spawn, execSync } from 'child_process'; 16 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 17 | import baseConfig from './webpack.config.base'; 18 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 19 | 20 | CheckNodeEnv('development'); 21 | 22 | const port = process.env.PORT || 1212; 23 | const publicPath = `http://localhost:${port}/dist`; 24 | const dll = path.resolve(process.cwd(), 'dll'); 25 | const manifest = path.resolve(dll, 'renderer.json'); 26 | 27 | /** 28 | * Warn if the DLL is not built 29 | */ 30 | if (!(fs.existsSync(dll) && fs.existsSync(manifest))) { 31 | console.log( 32 | chalk.black.bgYellow.bold( 33 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 34 | ) 35 | ); 36 | execSync('npm run build-dll'); 37 | } 38 | 39 | export default merge.smart(baseConfig, { 40 | devtool: 'inline-source-map', 41 | 42 | target: 'electron-renderer', 43 | 44 | entry: [ 45 | 'react-hot-loader/patch', 46 | `webpack-dev-server/client?http://localhost:${port}/`, 47 | 'webpack/hot/only-dev-server', 48 | path.join(__dirname, 'app/renderer/index.js') 49 | ], 50 | 51 | output: { 52 | publicPath: `http://localhost:${port}/dist/` 53 | }, 54 | 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.jsx?$/, 59 | exclude: /node_modules/, 60 | use: { 61 | loader: 'babel-loader', 62 | options: { 63 | cacheDirectory: true, 64 | plugins: [ 65 | // Here, we include babel plugins that are only required for the 66 | // renderer process. The 'transform-*' plugins must be included 67 | // before react-hot-loader/babel 68 | 'transform-class-properties', 69 | 'transform-es2015-classes', 70 | 'react-hot-loader/babel' 71 | ] 72 | } 73 | } 74 | }, 75 | { 76 | test: /\.global\.css$/, 77 | use: [ 78 | { 79 | loader: 'style-loader' 80 | }, 81 | { 82 | loader: 'css-loader', 83 | options: { 84 | sourceMap: true 85 | } 86 | } 87 | ] 88 | }, 89 | { 90 | test: /^((?!\.global).)*\.css$/, 91 | use: [ 92 | { 93 | loader: 'style-loader' 94 | }, 95 | { 96 | loader: 'css-loader', 97 | options: { 98 | modules: true, 99 | sourceMap: true, 100 | importLoaders: 1, 101 | localIdentName: '[name]__[local]__[hash:base64:5]' 102 | } 103 | } 104 | ] 105 | }, 106 | // Add SASS support - compile all .global.scss files and pipe it to style.css 107 | { 108 | test: /\.global\.scss$/, 109 | use: [ 110 | { 111 | loader: 'style-loader' 112 | }, 113 | { 114 | loader: 'css-loader', 115 | options: { 116 | sourceMap: true 117 | } 118 | }, 119 | { 120 | loader: 'sass-loader' 121 | } 122 | ] 123 | }, 124 | // Add SASS support - compile all other .scss files and pipe it to style.css 125 | { 126 | test: /^((?!\.global).)*\.scss$/, 127 | use: [ 128 | { 129 | loader: 'style-loader' 130 | }, 131 | { 132 | loader: 'css-loader', 133 | options: { 134 | modules: true, 135 | sourceMap: true, 136 | importLoaders: 1, 137 | localIdentName: '[name]__[local]__[hash:base64:5]' 138 | } 139 | }, 140 | { 141 | loader: 'sass-loader' 142 | } 143 | ] 144 | }, 145 | // WOFF Font 146 | { 147 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 148 | use: { 149 | loader: 'url-loader', 150 | options: { 151 | limit: 10000, 152 | mimetype: 'application/font-woff' 153 | } 154 | } 155 | }, 156 | // WOFF2 Font 157 | { 158 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 159 | use: { 160 | loader: 'url-loader', 161 | options: { 162 | limit: 10000, 163 | mimetype: 'application/font-woff' 164 | } 165 | } 166 | }, 167 | // TTF Font 168 | { 169 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 170 | use: { 171 | loader: 'url-loader', 172 | options: { 173 | limit: 10000, 174 | mimetype: 'application/octet-stream' 175 | } 176 | } 177 | }, 178 | // EOT Font 179 | { 180 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 181 | use: 'file-loader' 182 | }, 183 | // SVG Font 184 | { 185 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 186 | use: { 187 | loader: 'url-loader', 188 | options: { 189 | limit: 10000, 190 | mimetype: 'image/svg+xml' 191 | } 192 | } 193 | }, 194 | // Common Image Formats 195 | { 196 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 197 | use: 'url-loader' 198 | } 199 | ] 200 | }, 201 | 202 | plugins: [ 203 | new webpack.DllReferencePlugin({ 204 | context: process.cwd(), 205 | manifest: require(manifest), 206 | sourceType: 'var' 207 | }), 208 | 209 | /** 210 | * https://webpack.js.org/concepts/hot-module-replacement/ 211 | */ 212 | new webpack.HotModuleReplacementPlugin({ 213 | // @TODO: Waiting on https://github.com/jantimon/html-webpack-plugin/issues/533 214 | // multiStep: true 215 | }), 216 | 217 | new webpack.NoEmitOnErrorsPlugin(), 218 | 219 | /** 220 | * Create global constants which can be configured at compile time. 221 | * 222 | * Useful for allowing different behaviour between development builds and 223 | * release builds 224 | * 225 | * NODE_ENV should be production so that modules do not perform certain 226 | * development checks 227 | * 228 | * By default, use 'development' as NODE_ENV. This can be overriden with 229 | * 'staging', for example, by changing the ENV variables in the npm scripts 230 | */ 231 | new webpack.DefinePlugin({ 232 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 233 | }), 234 | 235 | new webpack.LoaderOptionsPlugin({ 236 | debug: true 237 | }), 238 | 239 | new ExtractTextPlugin({ 240 | filename: '[name].css' 241 | }) 242 | ], 243 | 244 | node: { 245 | __dirname: false, 246 | __filename: false 247 | }, 248 | 249 | devServer: { 250 | port, 251 | publicPath, 252 | compress: true, 253 | noInfo: true, 254 | stats: 'errors-only', 255 | inline: true, 256 | lazy: false, 257 | hot: true, 258 | headers: { 'Access-Control-Allow-Origin': '*' }, 259 | contentBase: path.join(__dirname, 'dist'), 260 | watchOptions: { 261 | aggregateTimeout: 300, 262 | ignored: /node_modules/, 263 | poll: 100 264 | }, 265 | historyApiFallback: { 266 | verbose: true, 267 | disableDotRule: false 268 | }, 269 | before() { 270 | if (process.env.START_HOT) { 271 | console.log('Staring Main Process...'); 272 | spawn('npm', ['run', 'start-main-dev'], { shell: true, env: process.env, stdio: 'inherit' }) 273 | .on('close', code => process.exit(code)) 274 | .on('error', spawnError => console.error(spawnError)); 275 | } 276 | } 277 | } 278 | }); 279 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vocab2", 3 | "productName": "vocab", 4 | "version": "2.0.0", 5 | "description": 6 | "Electron + Redux + React app for rapidly scaffolding Visual Vocabulary Template projects", 7 | "scripts": { 8 | "build": "concurrently \"npm run build-main\" \"npm run build-renderer\"", 9 | "build-dll": 10 | "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors", 11 | "build-main": 12 | "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors", 13 | "build-renderer": 14 | "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors", 15 | "dev": "cross-env START_HOT=1 npm run start-renderer-dev", 16 | "flow": "flow", 17 | "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true", 18 | "lint": "eslint --cache --format=node_modules/eslint-formatter-pretty .", 19 | "lint-fix": "npm run lint -- --fix", 20 | "lint-styles": "stylelint app/*.css app/renderer/components/*.css --syntax scss", 21 | "lint-styles-fix": "stylefmt -r app/*.css app/renderer/components/*.css", 22 | "package": "npm run build && build --publish never", 23 | "package-mac": "npm run build && build -m", 24 | "package-all": "npm run build && build -mwl", 25 | "package-linux": "npm run build && build --linux", 26 | "package-win": "npm run build && build --win --x64", 27 | "postinstall": 28 | "concurrently \"npm run flow-typed\" \"npm run build-dll\" \"electron-builder install-app-deps\" \"node node_modules/fbjs-scripts/node/check-dev-engines.js package.json\"", 29 | "prestart": "npm run build", 30 | "start": "cross-env NODE_ENV=production electron ./app/", 31 | "start-main-dev": 32 | "cross-env HOT=1 NODE_ENV=development electron -r babel-register ./app/main/main.dev", 33 | "start-renderer-dev": 34 | "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.renderer.dev.js", 35 | "test": 36 | "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/runTests.js", 37 | "test-all": "npm run lint && npm run flow && npm run build && npm run test && npm run test-e2e", 38 | "test-e2e": 39 | "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/runTests.js e2e", 40 | "test-watch": "npm test -- --watch" 41 | }, 42 | "browserslist": "electron 1.6", 43 | "build": { 44 | "productName": "Vocab2", 45 | "appId": "org.ft.vocab2", 46 | "files": [ 47 | "dist/", 48 | "node_modules/", 49 | "renderer/app.html", 50 | "main/main.prod.js", 51 | "main/main.prod.js.map", 52 | "package.json" 53 | ], 54 | "dmg": { 55 | "contents": [ 56 | { 57 | "x": 130, 58 | "y": 220 59 | }, 60 | { 61 | "x": 410, 62 | "y": 220, 63 | "type": "link", 64 | "path": "/Applications" 65 | } 66 | ] 67 | }, 68 | "win": { 69 | "target": ["nsis"] 70 | }, 71 | "linux": { 72 | "target": ["deb", "AppImage"], 73 | "category": "Graphics" 74 | }, 75 | "directories": { 76 | "buildResources": "resources", 77 | "output": "release" 78 | } 79 | }, 80 | "repository": { 81 | "type": "git", 82 | "url": "git+https://github.com/ft-interactive/vocab" 83 | }, 84 | "author": { 85 | "name": "Ændrew Rininsland", 86 | "email": "andrew.rininsland@ft.com", 87 | "url": "https://github.com/aendrew" 88 | }, 89 | "license": "MIT", 90 | "bugs": { 91 | "url": "https://github.com/ft-interactive/vocab/issues" 92 | }, 93 | "keywords": [ 94 | "electron", 95 | "react", 96 | "redux", 97 | "visual vocabulary", 98 | "visual vocabulary templates", 99 | "financial times", 100 | "charts" 101 | ], 102 | "homepage": "https://ft-interactive.github.com/visual-vocabulary", 103 | "jest": { 104 | "moduleNameMapper": { 105 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": 106 | "/internals/mocks/fileMock.js", 107 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 108 | }, 109 | "moduleFileExtensions": ["js"], 110 | "moduleDirectories": ["node_modules", "app/node_modules"], 111 | "transform": { 112 | "^.+\\.js$": "babel-jest" 113 | }, 114 | "setupFiles": ["./internals/scripts/CheckBuiltsExist.js"] 115 | }, 116 | "devDependencies": { 117 | "asar": "^0.13.0", 118 | "babel-core": "^6.24.1", 119 | "babel-eslint": "^7.2.3", 120 | "babel-jest": "^20.0.3", 121 | "babel-loader": "^7.1.0", 122 | "babel-plugin-add-module-exports": "^0.2.1", 123 | "babel-plugin-dev-expression": "^0.2.1", 124 | "babel-plugin-dynamic-import-webpack": "^1.0.1", 125 | "babel-plugin-flow-runtime": "^0.11.1", 126 | "babel-plugin-transform-class-properties": "^6.24.1", 127 | "babel-plugin-transform-es2015-classes": "^6.24.1", 128 | "babel-preset-env": "^1.5.1", 129 | "babel-preset-react": "^6.24.1", 130 | "babel-preset-react-hmre": "^1.1.1", 131 | "babel-preset-react-optimize": "^1.0.1", 132 | "babel-preset-stage-0": "^6.24.1", 133 | "babel-register": "^6.24.1", 134 | "chalk": "^2.0.1", 135 | "concurrently": "^3.5.0", 136 | "cross-env": "^5.0.0", 137 | "cross-spawn": "^5.1.0", 138 | "css-loader": "^0.28.3", 139 | "electron": "^1.6.10", 140 | "electron-builder": "^19.8.0", 141 | "electron-devtools-installer": "^2.2.0", 142 | "enzyme": "^2.9.1", 143 | "enzyme-to-json": "^1.5.1", 144 | "eslint": "^4.13.1", 145 | "eslint-config-airbnb": "^15.0.1", 146 | "eslint-formatter-pretty": "^1.1.0", 147 | "eslint-import-resolver-webpack": "^0.8.3", 148 | "eslint-plugin-compat": "^1.0.4", 149 | "eslint-plugin-flowtype": "^2.33.0", 150 | "eslint-plugin-flowtype-errors": "^3.3.0", 151 | "eslint-plugin-html": "^4.0.1", 152 | "eslint-plugin-import": "^2.9.0", 153 | "eslint-plugin-jest": "^20.0.3", 154 | "eslint-plugin-jsx-a11y": "^5.1.1", 155 | "eslint-plugin-promise": "^3.5.0", 156 | "eslint-plugin-react": "^7.1.0", 157 | "express": "^4.15.3", 158 | "extract-text-webpack-plugin": "^2.1.0", 159 | "fbjs-scripts": "^0.8.0", 160 | "file-loader": "^0.11.1", 161 | "flow-bin": "^0.61.0", 162 | "flow-runtime": "^0.13.0", 163 | "flow-typed": "^2.1.2", 164 | "html-webpack-plugin": "^2.29.0", 165 | "identity-obj-proxy": "^3.0.0", 166 | "jest": "^20.0.4", 167 | "jsdom": "^11.0.0", 168 | "minimist": "^1.2.0", 169 | "node-sass": "^4.5.3", 170 | "prettier-eslint": "^8.8.1", 171 | "react-addons-test-utils": "^15.6.0", 172 | "react-test-renderer": "^15.6.1", 173 | "redux-logger": "^3.0.6", 174 | "rimraf": "^2.6.1", 175 | "sass-loader": "^6.0.6", 176 | "sinon": "^2.3.5", 177 | "spectron": "^3.7.0", 178 | "style-loader": "^0.18.1", 179 | "stylefmt": "^6.0.0", 180 | "stylelint": "^7.12.0", 181 | "stylelint-config-standard": "^16.0.0", 182 | "uglifyjs-webpack-plugin": "^1.2.4", 183 | "url-loader": "^0.5.8", 184 | "webpack": "^2.7.0", 185 | "webpack-bundle-analyzer": "^2.8.2", 186 | "webpack-dev-server": "^2.5.0", 187 | "webpack-merge": "^4.1.0" 188 | }, 189 | "dependencies": { 190 | "command-exists": "^1.2.2", 191 | "d3": "^4.12.0", 192 | "devtron": "^1.4.0", 193 | "electron-debug": "^1.2.0", 194 | "electron-redux": "^1.3.1", 195 | "font-awesome": "^4.7.0", 196 | "fs-extra": "^5.0.0", 197 | "history": "^4.6.3", 198 | "papaparse": "^4.3.6", 199 | "parse-sed": "github:aendrew/parse-sed#0.1.0", 200 | "prop-types": "^15.6.0", 201 | "react": "^15.6.1", 202 | "react-dom": "^15.6.1", 203 | "react-dropzone": "^4.2.3", 204 | "react-hot-loader": "3.0.0-beta.6", 205 | "react-redux": "^5.0.5", 206 | "react-router": "^4.1.1", 207 | "react-router-dom": "^4.1.1", 208 | "react-router-redux": "^5.0.0-alpha.6", 209 | "react-spreadsheet-component": "^1.0.2", 210 | "redux": "^3.7.1", 211 | "redux-thunk": "^2.2.0", 212 | "simple-git": "^1.92.0", 213 | "source-map-support": "^0.4.15" 214 | }, 215 | "devEngines": { 216 | "node": ">=7.x", 217 | "npm": ">=4.x", 218 | "yarn": ">=0.21.3" 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.12.0 (2017.7.8) 2 | 3 | #### Misc 4 | - Removed `babel-polyfill` 5 | - Renamed and alphabetized npm scripts 6 | 7 | #### Breaking 8 | - Changed node dev `__dirname` and `__filename` to node built in fn's (https://github.com/chentsulin/electron-react-boilerplate/pull/1035) 9 | - Renamed `app/bundle.js` to `app/renderer.prod.js` for consistency 10 | - Renamed `dll/vendor.js` to `dll/renderer.dev.dll.js` for consistency 11 | 12 | #### Additions 13 | - Enable node_modules cache on CI 14 | 15 | # 0.11.2 (2017.5.1) 16 | 17 | Yay! Another patch release. This release mostly includes refactorings and router bug fixes. Huge thanks to @anthonyraymond! 18 | 19 | ⚠️ Windows electron builds are failing because of [this issue](https://github.com/electron/electron/issues/9321). This is not an issue with the boilerplate ⚠️ 20 | 21 | #### Breaking 22 | - **Renamed `./app/main.development.js` => `./app/main.{dev,prod}.js`:** [#963](https://github.com/chentsulin/electron-react-boilerplate/pull/963) 23 | 24 | #### Fixes 25 | - **Fixed reloading when not on `/` path:** [#958](https://github.com/chentsulin/electron-react-boilerplate/pull/958) [#949](https://github.com/chentsulin/electron-react-boilerplate/pull/949) 26 | 27 | #### Additions 28 | - **Added support for stylefmt:** [#960](https://github.com/chentsulin/electron-react-boilerplate/pull/960) 29 | 30 | # 0.11.1 (2017.4.23) 31 | 32 | You can now debug the production build with devtools like so: 33 | ``` 34 | DEBUG_PROD=true npm run package 35 | ``` 36 | 37 | 🎉🎉🎉 38 | 39 | #### Additions 40 | - **Added support for debugging production build:** [#fab245a](https://github.com/chentsulin/electron-react-boilerplate/pull/941/commits/fab245a077d02a09630f74270806c0c534a4ff95) 41 | 42 | #### Bug Fixes 43 | - **Fixed bug related to importing native dependencies:** [#933](https://github.com/chentsulin/electron-react-boilerplate/pull/933) 44 | 45 | #### Improvements 46 | - **Updated all deps to latest semver** 47 | 48 | # 0.11.0 (2017.4.19) 49 | 50 | Here's the most notable changes since `v0.10.0`. Its been about a year since a release has been pushed. Expect a new release to be published every 3-4 weeks. 51 | 52 | #### Breaking Changes 53 | 54 | - **Dropped support for node < 6** 55 | - **Refactored webpack config files** 56 | - **Migrate to two-package.json project structure** 57 | - **Updated all devDeps to latest semver** 58 | - **Migrated to Jest:** [#768](https://github.com/chentsulin/electron-react-boilerplate/pull/768) 59 | - **Migrated to `react-router@4`** 60 | - **Migrated to `electron-builder@4`** 61 | - **Migrated to `webpack@2`** 62 | - **Migrated to `react-hot-loader@3`** 63 | - **Changed default live reload server PORT to `1212` from `3000`** 64 | 65 | #### Additions 66 | 67 | - **Added support for Yarn:** [#451](https://github.com/chentsulin/electron-react-boilerplate/pull/451) 68 | - **Added support for Flow:** [#425](https://github.com/chentsulin/electron-react-boilerplate/pull/425) 69 | - **Added support for stylelint:** [#911](https://github.com/chentsulin/electron-react-boilerplate/pull/911) 70 | - **Added support for electron-builder:** [#876](https://github.com/chentsulin/electron-react-boilerplate/pull/876) 71 | - **Added optional support for SASS:** [#880](https://github.com/chentsulin/electron-react-boilerplate/pull/880) 72 | - **Added support for eslint-plugin-flowtype:** [#911](https://github.com/chentsulin/electron-react-boilerplate/pull/911) 73 | - **Added support for appveyor:** [#280](https://github.com/chentsulin/electron-react-boilerplate/pull/280) 74 | - **Added support for webpack dlls:** [#860](https://github.com/chentsulin/electron-react-boilerplate/pull/860) 75 | - **Route based code splitting:** [#884](https://github.com/chentsulin/electron-react-boilerplate/pull/884) 76 | - **Added support for Webpack Bundle Analyzer:** [#922](https://github.com/chentsulin/electron-react-boilerplate/pull/922) 77 | 78 | #### Improvements 79 | 80 | - **Parallelize renderer and main build processes when running `npm run build`** 81 | - **Dynamically generate electron app menu** 82 | - **Improved vscode integration:** [#856](https://github.com/chentsulin/electron-react-boilerplate/pull/856) 83 | 84 | #### Bug Fixes 85 | 86 | - **Fixed hot module replacement race condition bug:** [#917](https://github.com/chentsulin/electron-react-boilerplate/pull/917) [#920](https://github.com/chentsulin/electron-react-boilerplate/pull/920) 87 | 88 | # 0.10.0 (2016.4.18) 89 | 90 | #### Improvements 91 | 92 | - **Use Babel in main process with Webpack build:** [#201](https://github.com/chentsulin/electron-react-boilerplate/pull/201) 93 | - **Change targets to built-in support by webpack:** [#197](https://github.com/chentsulin/electron-react-boilerplate/pull/197) 94 | - **use es2015 syntax for webpack configs:** [#195](https://github.com/chentsulin/electron-react-boilerplate/pull/195) 95 | - **Open application when webcontent is loaded:** [#192](https://github.com/chentsulin/electron-react-boilerplate/pull/192) 96 | - **Upgraded dependencies** 97 | 98 | #### Bug fixed 99 | 100 | - **Fix `npm list electron-prebuilt` in package.js:** [#188](https://github.com/chentsulin/electron-react-boilerplate/pull/188) 101 | 102 | 103 | # 0.9.0 (2016.3.23) 104 | 105 | #### Improvements 106 | 107 | - **Added [redux-logger](https://github.com/fcomb/redux-logger)** 108 | - **Upgraded [react-router-redux](https://github.com/reactjs/react-router-redux) to v4** 109 | - **Upgraded dependencies** 110 | - **Added `npm run dev` command:** [#162](https://github.com/chentsulin/electron-react-boilerplate/pull/162) 111 | - **electron to v0.37.2** 112 | 113 | #### Breaking Changes 114 | 115 | - **css module as default:** [#154](https://github.com/chentsulin/electron-react-boilerplate/pull/154). 116 | - **set default NODE_ENV to production:** [#140](https://github.com/chentsulin/electron-react-boilerplate/issues/140) 117 | 118 | 119 | # 0.8.0 (2016.2.17) 120 | 121 | #### Bug fixed 122 | 123 | - **Fix lint errors** 124 | - **Fix Webpack publicPath for production builds**: [#119](https://github.com/chentsulin/electron-react-boilerplate/issues/119). 125 | - **package script now chooses correct OS icon extension** 126 | 127 | #### Improvements 128 | 129 | - **babel 6** 130 | - **Upgrade Dependencies** 131 | - **Enable CSS source maps** 132 | - **Add json-loader**: [#128](https://github.com/chentsulin/electron-react-boilerplate/issues/128). 133 | - **react-router 2.0 and react-router-redux 3.0** 134 | 135 | 136 | # 0.7.1 (2015.12.27) 137 | 138 | #### Bug fixed 139 | 140 | - **Fixed npm script on windows 10:** [#103](https://github.com/chentsulin/electron-react-boilerplate/issues/103). 141 | - **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). 142 | 143 | #### Improvements 144 | 145 | - **electron 0.36** 146 | 147 | 148 | 149 | # 0.7.0 (2015.12.16) 150 | 151 | #### Bug fixed 152 | 153 | - **Fixed process.env.NODE_ENV variable in webpack:** [#74](https://github.com/chentsulin/electron-react-boilerplate/pull/74). 154 | - **add missing object-assign**: [#76](https://github.com/chentsulin/electron-react-boilerplate/pull/76). 155 | - **packaging in npm@3:** [#77](https://github.com/chentsulin/electron-react-boilerplate/pull/77). 156 | - **compatibility in windows:** [#100](https://github.com/chentsulin/electron-react-boilerplate/pull/100). 157 | - **disable chrome debugger in production env:** [#102](https://github.com/chentsulin/electron-react-boilerplate/pull/102). 158 | 159 | #### Improvements 160 | 161 | - **redux** 162 | - **css-modules** 163 | - **upgrade to react-router 1.x** 164 | - **unit tests** 165 | - **e2e tests** 166 | - **travis-ci** 167 | - **upgrade to electron 0.35.x** 168 | - **use es2015** 169 | - **check dev engine for node and npm** 170 | 171 | 172 | # 0.6.5 (2015.11.7) 173 | 174 | #### Improvements 175 | 176 | - **Bump style-loader to 0.13** 177 | - **Bump css-loader to 0.22** 178 | 179 | 180 | # 0.6.4 (2015.10.27) 181 | 182 | #### Improvements 183 | 184 | - **Bump electron-debug to 0.3** 185 | 186 | 187 | # 0.6.3 (2015.10.26) 188 | 189 | #### Improvements 190 | 191 | - **Initialize ExtractTextPlugin once:** [#64](https://github.com/chentsulin/electron-react-boilerplate/issues/64). 192 | 193 | 194 | # 0.6.2 (2015.10.18) 195 | 196 | #### Bug fixed 197 | 198 | - **Babel plugins production env not be set properly:** [#57](https://github.com/chentsulin/electron-react-boilerplate/issues/57). 199 | 200 | 201 | # 0.6.1 (2015.10.17) 202 | 203 | #### Improvements 204 | 205 | - **Bump electron to v0.34.0** 206 | 207 | 208 | # 0.6.0 (2015.10.16) 209 | 210 | #### Breaking Changes 211 | 212 | - **From react-hot-loader to react-transform** 213 | 214 | 215 | # 0.5.2 (2015.10.15) 216 | 217 | #### Improvements 218 | 219 | - **Run tests with babel-register:** [#29](https://github.com/chentsulin/electron-react-boilerplate/issues/29). 220 | 221 | 222 | # 0.5.1 (2015.10.12) 223 | 224 | #### Bug fixed 225 | 226 | - **Fix #51:** use `path.join(__dirname` instead of `./`. 227 | 228 | 229 | # 0.5.0 (2015.10.11) 230 | 231 | #### Improvements 232 | 233 | - **Simplify webpack config** see [#50](https://github.com/chentsulin/electron-react-boilerplate/pull/50). 234 | 235 | #### Breaking Changes 236 | 237 | - **webpack configs** 238 | - **port changed:** changed default port from 2992 to 3000. 239 | - **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`. 240 | 241 | 242 | # 0.4.3 (2015.9.22) 243 | 244 | #### Bug fixed 245 | 246 | - **Fix #45 zeromq crash:** bump version of `electron-prebuilt`. 247 | 248 | 249 | # 0.4.2 (2015.9.15) 250 | 251 | #### Bug fixed 252 | 253 | - **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1` 254 | 255 | 256 | # 0.4.1 (2015.9.11) 257 | 258 | #### Improvements 259 | 260 | - **use electron-prebuilt version for packaging (#33)** 261 | 262 | 263 | # 0.4.0 (2015.9.5) 264 | 265 | #### Improvements 266 | 267 | - **update dependencies** 268 | 269 | 270 | # 0.3.0 (2015.8.31) 271 | 272 | #### Improvements 273 | 274 | - **eslint-config-airbnb** 275 | 276 | 277 | # 0.2.10 (2015.8.27) 278 | 279 | #### Features 280 | 281 | - **custom placeholder icon** 282 | 283 | #### Improvements 284 | 285 | - **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer) 286 | 287 | 288 | # 0.2.9 (2015.8.18) 289 | 290 | #### Bug fixed 291 | 292 | - **Fix hot-reload** 293 | 294 | 295 | # 0.2.8 (2015.8.13) 296 | 297 | #### Improvements 298 | 299 | - **bump electron-debug** 300 | - **babelrc** 301 | - **organize webpack scripts** 302 | 303 | 304 | # 0.2.7 (2015.7.9) 305 | 306 | #### Bug fixed 307 | 308 | - **defaultProps:** fix typos. 309 | 310 | 311 | # 0.2.6 (2015.7.3) 312 | 313 | #### Features 314 | 315 | - **menu** 316 | 317 | #### Bug fixed 318 | 319 | - **package.js:** include webpack build. 320 | 321 | 322 | # 0.2.5 (2015.7.1) 323 | 324 | #### Features 325 | 326 | - **NPM Script:** support multi-platform 327 | - **package:** `--all` option 328 | 329 | 330 | # 0.2.4 (2015.6.9) 331 | 332 | #### Bug fixed 333 | 334 | - **Eslint:** typo, [#17](https://github.com/chentsulin/electron-react-boilerplate/issues/17) and improve `.eslintrc` 335 | 336 | 337 | # 0.2.3 (2015.6.3) 338 | 339 | #### Features 340 | 341 | - **Package Version:** use latest release electron version as default 342 | - **Ignore Large peerDependencies** 343 | 344 | #### Bug fixed 345 | 346 | - **Npm Script:** typo, [#6](https://github.com/chentsulin/electron-react-boilerplate/pull/6) 347 | - **Missing css:** [#7](https://github.com/chentsulin/electron-react-boilerplate/pull/7) 348 | 349 | 350 | # 0.2.2 (2015.6.2) 351 | 352 | #### Features 353 | 354 | - **electron-debug** 355 | 356 | #### Bug fixed 357 | 358 | - **Webpack:** add `.json` and `.node` to extensions for imitating node require. 359 | - **Webpack:** set `node_modules` to externals for native module support. 360 | 361 | 362 | # 0.2.1 (2015.5.30) 363 | 364 | #### Bug fixed 365 | 366 | - **Webpack:** #1, change build target to `atom`. 367 | 368 | 369 | # 0.2.0 (2015.5.30) 370 | 371 | #### Features 372 | 373 | - **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`. 374 | - **Support asar** 375 | - **Support icon** 376 | 377 | 378 | # 0.1.0 (2015.5.27) 379 | 380 | #### Features 381 | 382 | - **Webpack:** babel, react-hot, ... 383 | - **Flux:** actions, api, components, containers, stores.. 384 | - **Package:** darwin (osx), linux and win32 (windows) platform. 385 | --------------------------------------------------------------------------------