├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .prettierrc ├── .stylelintrc ├── LICENSE ├── README.md ├── app ├── api │ ├── sys │ │ ├── fileOps.js │ │ └── index.js │ └── www │ │ └── index.js ├── app.html ├── app.icns ├── classes │ ├── AppUpdate.js │ ├── Boot.js │ └── Storage.js ├── components │ ├── LoadingIndicator │ │ ├── index.jsx │ │ └── styles │ │ │ └── index.js │ └── Snackbars │ │ ├── components │ │ └── SnackbarThemeWrapper.jsx │ │ ├── index.jsx │ │ └── styles │ │ ├── SnackbarThemeWrapper.js │ │ └── index.js ├── constants │ ├── env.js │ ├── index.js │ └── meta.js ├── containers │ ├── Alerts │ │ ├── actions.js │ │ ├── index.jsx │ │ └── reducers.js │ ├── App │ │ ├── Root.jsx │ │ ├── actions.js │ │ ├── components │ │ │ └── Titlebar.jsx │ │ ├── index.jsx │ │ ├── reducers.js │ │ ├── selectors.js │ │ └── styles │ │ │ ├── Titlebar.js │ │ │ └── index.js │ ├── ErrorBoundary │ │ ├── components │ │ │ ├── GenerateErrorReport.jsx │ │ │ └── GenerateErrorReportBody.jsx │ │ ├── index.jsx │ │ └── styles │ │ │ ├── GenerateErrorReport.js │ │ │ └── index.js │ ├── HomePage │ │ ├── Loadable.js │ │ ├── actions.js │ │ ├── components │ │ │ ├── BodyAreaPane.jsx │ │ │ ├── ToolbarAreaPane.jsx │ │ │ └── ToolbarBody.jsx │ │ ├── index.jsx │ │ ├── reducers.js │ │ ├── selectors.js │ │ └── styles │ │ │ ├── BodyAreaPane.js │ │ │ ├── ToolbarAreaPane.js │ │ │ └── index.js │ ├── NotFoundPage │ │ ├── Loadable.js │ │ ├── index.jsx │ │ └── styles │ │ │ └── index.scss │ ├── PrivacyPolicyPage │ │ ├── Loadable.js │ │ ├── index.jsx │ │ └── styles │ │ │ └── index.js │ ├── ProgressbarPage │ │ ├── Loadable.js │ │ ├── index.jsx │ │ └── styles │ │ │ └── index.js │ ├── ReportBugsPage │ │ ├── Loadable.js │ │ ├── index.jsx │ │ └── styles │ │ │ └── index.js │ ├── SecondPage │ │ ├── Loadable.js │ │ ├── index.jsx │ │ └── styles │ │ │ └── index.scss │ └── Settings │ │ ├── actions.js │ │ ├── components │ │ └── SettingsDialog.jsx │ │ ├── index.jsx │ │ ├── reducers.js │ │ ├── selectors.js │ │ └── styles │ │ └── index.js ├── index.js ├── main.dev.js ├── menu.js ├── public │ └── images │ │ ├── Toolbar │ │ └── settings.svg │ │ ├── bug.svg │ │ ├── keyboard.jpg │ │ └── no-image.png ├── routing │ └── index.js ├── store │ ├── configureStore │ │ ├── dev.js │ │ ├── index.js │ │ └── prod.js │ └── reducers │ │ ├── index.js │ │ └── withReducer.js ├── styles │ ├── js │ │ ├── index.js │ │ ├── mixins.js │ │ └── variables.js │ └── scss │ │ ├── app.global.scss │ │ ├── base │ │ ├── _base.scss │ │ ├── _extends.scss │ │ ├── _mixins.scss │ │ ├── _variables.scss │ │ └── mixins │ │ │ ├── _align-items.scss │ │ │ ├── _animate-link.scss │ │ │ ├── _animations.scss │ │ │ ├── _backface-visibility.scss │ │ │ ├── _background-cover.scss │ │ │ ├── _border.scss │ │ │ ├── _box-model.scss │ │ │ ├── _box-shadow.scss │ │ │ ├── _breakpoint.scss │ │ │ ├── _clearfix.scss │ │ │ ├── _display.scss │ │ │ ├── _display_flex.scss │ │ │ ├── _flex.scss │ │ │ ├── _hide-text.scss │ │ │ ├── _horz-vert-center.scss │ │ │ ├── _hover-focus.scss │ │ │ ├── _inline-block.scss │ │ │ ├── _inner-shadow.scss │ │ │ ├── _keyframes.scss │ │ │ ├── _linear-gradient-angle.scss │ │ │ ├── _linear-gradient.scss │ │ │ ├── _margin-auto.scss │ │ │ ├── _mediumFont.scss │ │ │ ├── _min-breakpoint.scss │ │ │ ├── _opacity.scss │ │ │ ├── _placeholder.scss │ │ │ ├── _rem.scss │ │ │ ├── _replace-text.scss │ │ │ ├── _retina.scss │ │ │ ├── _rounded-corners.scss │ │ │ ├── _single-transform.scss │ │ │ ├── _text-shadow.scss │ │ │ ├── _transform.scss │ │ │ ├── _transitions.scss │ │ │ ├── _translate.scss │ │ │ └── _triangles.scss │ │ └── themes │ │ ├── fonts.scss │ │ └── reset.scss ├── templates │ ├── generateErrorReport.js │ ├── loadProfileError.js │ ├── menu.js │ └── privacyPolicyPage.js └── utils │ ├── analyticsHelper.js │ ├── binaries.js │ ├── bootHelper.js │ ├── createWindows.js │ ├── date.js │ ├── eventHandling.js │ ├── funcs.js │ ├── getPlatform.js │ ├── gzip.js │ ├── imgsrc.js │ ├── isOnline.js │ ├── isPackaged.js │ ├── log.js │ ├── paths.js │ ├── pkginfo.js │ ├── reducerPrefixer.js │ ├── storageHelper.js │ ├── styleResets.js │ ├── titlebarDoubleClick.js │ └── url.js ├── babel.config.js ├── build ├── icon.icns ├── icon.ico ├── icon.png ├── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png ├── mac │ └── bin │ │ └── .git-keep └── sample.entitlements.mas.plist ├── config ├── dev-app-update.yml ├── env │ ├── env.dev.js │ ├── env.prod.js │ └── index.js └── google-analytics-key.js ├── internals └── scripts │ ├── AfterPack.js │ ├── CheckBuiltsExist.js │ ├── CheckNodeEnv.js │ ├── CheckPortInUse.js │ └── CheckYarn.js ├── package.json └── webpack ├── config.base.js ├── config.eslint.js ├── config.main.prod.babel.js ├── config.renderer.dev.babel.js ├── config.renderer.dev.dll.babel.js └── config.renderer.prod.babel.js /.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 | 30 | # OSX 31 | .DS_Store 32 | 33 | # flow-typed 34 | flow-typed/npm/* 35 | !flow-typed/npm/module_vx.x.x.js 36 | 37 | # App packaged 38 | release 39 | app/main.prod.js 40 | app/main.prod.js.map 41 | app/renderer.prod.js 42 | app/renderer.prod.js.map 43 | app/style.css 44 | app/style.css.map 45 | dist 46 | dll 47 | main.js 48 | main.js.map 49 | 50 | 51 | .idea 52 | npm-debug.log.* 53 | __snapshots__ 54 | 55 | # Package.json 56 | package.json 57 | .travis.yml 58 | 59 | .idea 60 | vendors 61 | build 62 | docs 63 | .vscode 64 | .github 65 | app/dll 66 | .prettierrc 67 | .stylelintrc 68 | .eslintrc.json 69 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | parserOptions: { 4 | sourceType: 'module', 5 | allowImportExportEverywhere: true 6 | }, 7 | env: { 8 | browser: true, 9 | node: true 10 | }, 11 | plugins: ['import', 'promise', 'compat', 'react'], 12 | extends: ['airbnb', 'plugin:prettier/recommended', 'prettier/react'], 13 | settings: { 14 | 'import/resolver': { 15 | webpack: { 16 | config: 'webpack/config.eslint.js' 17 | } 18 | } 19 | }, 20 | rules: { 21 | 'arrow-parens': 'off', 22 | 'compat/compat': 'error', 23 | 'consistent-return': 'off', 24 | 'comma-dangle': 'off', 25 | 'generator-star-spacing': 'off', 26 | 'import/no-unresolved': 'error', 27 | 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 28 | 'jsx-a11y/anchor-is-valid': 'off', 29 | 'jsx-a11y/label-has-for': 'off', 30 | 'jsx-a11y/label-has-associated-control': 'off', 31 | 'jsx-a11y/no-static-element-interactions': 'off', 32 | 'jsx-a11y/click-events-have-key-events': 'off', 33 | 'no-console': [ 34 | 'error', 35 | { 36 | allow: ['info', 'error', 'warn'] 37 | } 38 | ], 39 | 'no-use-before-define': 'off', 40 | 'no-multi-assign': 'off', 41 | 'prettier/prettier': ['error', { singleQuote: true }], 42 | 'promise/param-names': 'error', 43 | 'promise/always-return': 'error', 44 | 'promise/catch-or-return': 'error', 45 | 'promise/no-native': 'off', 46 | 'react/sort-comp': [ 47 | 'error', 48 | { 49 | order: [ 50 | 'type-annotations', 51 | 'static-methods', 52 | 'lifecycle', 53 | 'everything-else', 54 | 'render' 55 | ] 56 | } 57 | ], 58 | 'react/jsx-no-bind': 'off', 59 | 'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx'] }], 60 | 'react/prefer-stateless-function': 'off', 61 | strict: 'off', 62 | 'import/prefer-default-export': 'off', 63 | 'arrow-body-style': 'off', 64 | 'no-underscore-dangle': 'off', 65 | 'class-methods-use-this': 'off', 66 | 'no-shadow': 'off', 67 | 'react/prop-types': 'off', 68 | 'import/no-dynamic-require': 'off', 69 | 'no-unused-vars': [ 70 | 'error', 71 | { 72 | args: 'after-used', 73 | argsIgnorePattern: '^(theme|props|state|ownProps|dispatch|getState)|_', 74 | varsIgnorePattern: '^(variables|mixins|args|log)' 75 | } 76 | ] 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.jpeg binary 5 | *.ico binary 6 | *.icns binary 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: 'bug' 5 | 6 | --- 7 | 8 | **Prerequisites** 9 | 10 | If the following boxes are not ALL checked, your issue is likely to be closed. 11 | 12 | - [ ] Using yarn 13 | - [ ] Using node 10.x 14 | - [ ] Using an up-to-date [`master` branch](https://github.com/ganeshrvel/electron-react-redux-advanced-boilerplate/tree/master) 15 | - [ ] Using latest version of devtools. See [wiki for howto update](https://github.com/electron-react-boilerplate/electron-react-boilerplate/wiki/DevTools) 16 | - [ ] For issue in production release, devtools output of `DEBUG_PROD=true yarn build && yarn start` 17 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400) 18 | 19 | **Describe the bug** 20 | A clear and concise description of what the bug is. 21 | 22 | **Expected behaviour** 23 | If you're describing a bug, tell us what should happen. 24 | If you're suggesting a change/improvement, tell us how it should work. 25 | 26 | **Current Behavior** 27 | If describing a bug, tell us what happens instead of the expected behaviour. 28 | If suggesting a change/improvement, explain the difference between current behaviour. 29 | 30 | **Possible Solution** 31 | 32 | Not obligatory, but suggest a fix/reason for the bug or ideas how to implement the addition or change. 33 | 34 | **Steps to Reproduce (for bugs)** 35 | Provide a link to a live example or an unambiguous set of steps to reproduce this bug. Include code to reproduce, if relevant 36 | 37 | 1. 38 | 39 | 2. 40 | 41 | 3. 42 | 43 | 4. 44 | 45 | **Context** 46 | How has this issue affected you? What are you trying to accomplish? 47 | Did you make any changes to the codes after cloning it? 48 | Providing context helps us come up with a solution that is most useful in the real world. 49 | 50 | **Your Environment** 51 | Include as many relevant details about the environment you experienced the bug in 52 | 53 | - Node version : 54 | - Version or Branch used : 55 | - Operating System and version [e.g. macOS 10.14 mojave]: 56 | - App Version [e.g. Electron React Redux Advanced Boilerplate-v1.0]: 57 | - Link to your project : 58 | 59 | **Attachments** 60 | Include if relevant, 61 | 1. Open your Terminal and run the following code: 62 | ```shell 63 | zip -r -X ~/Desktop/Electron React Redux Advanced Boilerplate-log.zip ~/.io.ganeshrvel/electron-react-redux-advanced-boilerplate/logs/ 64 | ``` 65 | 2. Attach the file *Electron React Redux Advanced Boilerplate-log.zip* found in your Desktop folder here. 66 | 67 | Or 68 | 69 | 1. Open the App, 70 | 2. Click on Help > Report Bugs. 71 | 3. Generate and send us the bugs report. 72 | 73 | **Screenshots** 74 | If applicable, add screenshots to help explain your problem. 75 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to the app or code. 4 | labels: 'enhancement' 5 | --- 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .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.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | yarn.lock 54 | package-lock.json 55 | 56 | 57 | app/certs/*.pem 58 | certs/* 59 | *electron-builder.yml 60 | todo.txt 61 | *yarn-error.log 62 | eslint-common-rules.txt 63 | 64 | build/entitlements.mas.plist 65 | *embedded.provisionprofile 66 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": [".prettierrc", ".babelrc", ".eslintrc", ".stylelintrc"], 5 | "options": { 6 | "parser": "json" 7 | } 8 | } 9 | ], 10 | "singleQuote": true 11 | } 12 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "rules": { 4 | "at-rule-no-unknown": null, 5 | "no-descending-specificity": null 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010-present Ganesh Rathinavel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/api/sys/fileOps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | existsSync as _existsSync, 5 | writeFile as _writeFileAsync, 6 | appendFile as _appendFileAsync, 7 | readFileSync as _readFileSync, 8 | writeFileSync as _writeFileSync 9 | } from 'fs'; 10 | import { EOL } from 'os'; 11 | import mkdirp from 'mkdirp'; 12 | import rimraf from 'rimraf'; 13 | 14 | export const writeFileAsync = (filePath, text) => { 15 | const options = { mode: 0o755 }; 16 | _writeFileAsync(filePath, text, options, err => { 17 | if (err) { 18 | console.error(err, `writeFileAsync`); 19 | return null; 20 | } 21 | }); 22 | }; 23 | 24 | export const writeFileSync = (filePath, text) => { 25 | const options = { mode: 0o755 }; 26 | try { 27 | _writeFileSync(filePath, text, options); 28 | } catch (err) { 29 | console.error(err, `writeFileSync`); 30 | } 31 | }; 32 | 33 | export const appendFileAsync = (filePath, text) => { 34 | const options = { mode: 0o755 }; 35 | _appendFileAsync(filePath, text + EOL, options, err => { 36 | if (err) { 37 | console.error(err, `appendFileAsync`); 38 | return null; 39 | } 40 | }); 41 | }; 42 | 43 | export const readFileSync = filePath => { 44 | const options = { encoding: 'utf8' }; 45 | return _readFileSync(filePath, options); 46 | }; 47 | 48 | export const fileExistsSync = filePath => _existsSync(filePath); 49 | 50 | export const createDirSync = newFolderPath => { 51 | mkdirp.sync(newFolderPath); 52 | }; 53 | 54 | export const deleteFilesSync = filePath => { 55 | rimraf.sync(filePath); 56 | }; 57 | -------------------------------------------------------------------------------- /app/api/sys/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-await-in-loop: off */ 4 | 5 | import { 6 | readdir as fsReaddir, 7 | rename as fsRename, 8 | existsSync, 9 | statSync, 10 | lstatSync 11 | } from 'fs'; 12 | import Promise from 'bluebird'; 13 | import junk from 'junk'; 14 | import rimraf from 'rimraf'; 15 | import mkdirp from 'mkdirp'; 16 | import path from 'path'; 17 | import moment from 'moment'; 18 | import { exec } from 'child_process'; 19 | import findLodash from 'lodash/find'; 20 | import { log } from '../../utils/log'; 21 | import { isArray } from '../../utils/funcs'; 22 | 23 | const readdir = Promise.promisify(fsReaddir); 24 | const execPromise = Promise.promisify(exec); 25 | 26 | export const promisifiedExec = command => { 27 | try { 28 | return new Promise(resolve => { 29 | execPromise(command, (error, stdout, stderr) => { 30 | return resolve({ 31 | data: stdout, 32 | stderr, 33 | error 34 | }); 35 | }); 36 | }); 37 | } catch (e) { 38 | log.error(e); 39 | } 40 | }; 41 | 42 | export const promisifiedExecNoCatch = command => { 43 | return new Promise(resolve => { 44 | execPromise(command, (error, stdout, stderr) => 45 | resolve({ 46 | data: stdout, 47 | stderr, 48 | error 49 | }) 50 | ); 51 | }); 52 | }; 53 | 54 | export const checkFileExists = async filePath => { 55 | try { 56 | if (typeof filePath === 'undefined' || filePath === null) { 57 | return null; 58 | } 59 | 60 | let _isArray = false; 61 | if (isArray(filePath)) { 62 | _isArray = true; 63 | } 64 | 65 | let fullPath = null; 66 | if (_isArray) { 67 | for (let i = 0; i < filePath.length; i += 1) { 68 | const item = filePath[i]; 69 | fullPath = path.resolve(item); 70 | if (await existsSync(fullPath)) { 71 | return true; 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | fullPath = path.resolve(filePath); 78 | return await existsSync(fullPath); 79 | } catch (e) { 80 | log.error(e); 81 | } 82 | }; 83 | 84 | /** 85 | Local device -> 86 | */ 87 | export const asyncReadLocalDir = async ({ filePath, ignoreHidden }) => { 88 | try { 89 | const response = []; 90 | const { error, data } = await readdir(filePath, 'utf8') 91 | .then(res => { 92 | return { 93 | data: res, 94 | error: null 95 | }; 96 | }) 97 | .catch(e => { 98 | return { 99 | data: null, 100 | error: e 101 | }; 102 | }); 103 | 104 | if (error) { 105 | log.error(error, `asyncReadLocalDir`); 106 | return { error: true, data: null }; 107 | } 108 | 109 | let files = data; 110 | 111 | files = data.filter(junk.not); 112 | if (ignoreHidden) { 113 | files = data.filter(item => !/(^|\/)\.[^\/\.]/g.test(item)); // eslint-disable-line no-useless-escape 114 | } 115 | 116 | for (let i = 0; i < files.length; i += 1) { 117 | const file = files[i]; 118 | const fullPath = path.resolve(filePath, file); 119 | 120 | if (!existsSync(fullPath)) { 121 | continue; // eslint-disable-line no-continue 122 | } 123 | const stat = statSync(fullPath); 124 | const isFolder = lstatSync(fullPath).isDirectory(); 125 | const extension = path.extname(fullPath); 126 | const { size, atime: dateTime } = stat; 127 | 128 | if (findLodash(response, { path: fullPath })) { 129 | continue; // eslint-disable-line no-continue 130 | } 131 | 132 | response.push({ 133 | name: file, 134 | path: fullPath, 135 | extension, 136 | size, 137 | isFolder, 138 | dateAdded: moment(dateTime).format('YYYY-MM-DD HH:mm:ss') 139 | }); 140 | } 141 | return { error, data: response }; 142 | } catch (e) { 143 | log.error(e); 144 | } 145 | }; 146 | 147 | export const promisifiedRimraf = item => { 148 | try { 149 | return new Promise(resolve => { 150 | rimraf(item, {}, error => { 151 | resolve({ 152 | data: null, 153 | stderr: error, 154 | error 155 | }); 156 | }); 157 | }); 158 | } catch (e) { 159 | log.error(e); 160 | } 161 | }; 162 | 163 | export const delLocalFiles = async ({ fileList }) => { 164 | try { 165 | if (!fileList || fileList.length < 1) { 166 | return { error: `No files selected.`, stderr: null, data: null }; 167 | } 168 | 169 | for (let i = 0; i < fileList.length; i += 1) { 170 | const item = fileList[i]; 171 | const { error } = await promisifiedRimraf(item); 172 | if (error) { 173 | log.error(`${error}`, `delLocalFiles -> rm error`); 174 | return { error, stderr: null, data: false }; 175 | } 176 | } 177 | 178 | return { error: null, stderr: null, data: true }; 179 | } catch (e) { 180 | log.error(e); 181 | } 182 | }; 183 | 184 | const promisifiedRename = ({ oldFilePath, newFilePath }) => { 185 | try { 186 | return new Promise(resolve => { 187 | fsRename(oldFilePath, newFilePath, error => { 188 | resolve({ 189 | data: null, 190 | stderr: error, 191 | error 192 | }); 193 | }); 194 | }); 195 | } catch (e) { 196 | log.error(e); 197 | } 198 | }; 199 | 200 | export const renameLocalFiles = async ({ oldFilePath, newFilePath }) => { 201 | try { 202 | if ( 203 | typeof oldFilePath === 'undefined' || 204 | oldFilePath === null || 205 | typeof newFilePath === 'undefined' || 206 | newFilePath === null 207 | ) { 208 | return { error: `No files selected.`, stderr: null, data: null }; 209 | } 210 | 211 | const { error } = await promisifiedRename({ oldFilePath, newFilePath }); 212 | if (error) { 213 | log.error(`${error}`, `renameLocalFiles -> mv error`); 214 | return { error, stderr: null, data: false }; 215 | } 216 | 217 | return { error: null, stderr: null, data: true }; 218 | } catch (e) { 219 | log.error(e); 220 | } 221 | }; 222 | 223 | const promisifiedMkdir = ({ newFolderPath }) => { 224 | try { 225 | return new Promise(resolve => { 226 | mkdirp(newFolderPath, error => { 227 | resolve({ data: null, stderr: error, error }); 228 | }); 229 | }); 230 | } catch (e) { 231 | log.error(e); 232 | } 233 | }; 234 | 235 | export const newLocalFolder = async ({ newFolderPath }) => { 236 | try { 237 | if (typeof newFolderPath === 'undefined' || newFolderPath === null) { 238 | return { error: `Invalid path.`, stderr: null, data: null }; 239 | } 240 | 241 | const { error } = await promisifiedMkdir({ newFolderPath }); 242 | if (error) { 243 | log.error(`${error}`, `newLocalFolder -> mkdir error`); 244 | return { error, stderr: null, data: false }; 245 | } 246 | 247 | return { error: null, stderr: null, data: true }; 248 | } catch (e) { 249 | log.error(e); 250 | } 251 | }; 252 | -------------------------------------------------------------------------------- /app/api/www/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export function fetchUrl({ url }) { 4 | return fetch(`${url}`).then(res => { 5 | if (res.status === 200) { 6 | return res.json(); 7 | } 8 | return null; 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Electron React Redux Advanced Boilerplate 10 | 20 | 21 | 22 |
23 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/app/app.icns -------------------------------------------------------------------------------- /app/classes/Boot.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-await-in-loop: off */ 4 | 5 | /** 6 | * Boot 7 | * Note: Don't import log helper file from utils here 8 | */ 9 | 10 | import { readdirSync } from 'fs'; 11 | import { baseName, PATHS } from '../utils/paths'; 12 | import { 13 | fileExistsSync, 14 | writeFileAsync, 15 | createDirSync, 16 | deleteFilesSync 17 | } from '../api/sys/fileOps'; 18 | import { daysDiff, yearMonthNow } from '../utils/date'; 19 | import { LOG_FILE_ROTATION_CLEANUP_THRESHOLD } from '../constants'; 20 | 21 | const { logFile, settingsFile, logDir } = PATHS; 22 | const logFileRotationCleanUpThreshold = LOG_FILE_ROTATION_CLEANUP_THRESHOLD; 23 | 24 | export default class Boot { 25 | constructor() { 26 | this.verifyDirList = [logDir]; 27 | this.verifyFileList = [logFile]; 28 | this.settingsFile = settingsFile; 29 | } 30 | 31 | async init() { 32 | try { 33 | for (let i = 0; i < this.verifyDirList.length; i += 1) { 34 | const item = this.verifyDirList[i]; 35 | 36 | if (!(await this.verifyDir(item))) { 37 | await this.createDir(item); 38 | } 39 | } 40 | 41 | if (!this.verifyFile(this.settingsFile)) { 42 | await this.createFile(this.settingsFile); 43 | } 44 | 45 | for (let i = 0; i < this.verifyFileList.length; i += 1) { 46 | const item = this.verifyFileList[i]; 47 | 48 | if (!this.verifyFile(item)) { 49 | await this.createFile(item); 50 | } 51 | } 52 | 53 | return true; 54 | } catch (e) { 55 | console.error(e); 56 | } 57 | } 58 | 59 | async verify() { 60 | try { 61 | for (let i = 0; i < this.verifyFileList.length; i += 1) { 62 | const item = this.verifyDirList[i]; 63 | 64 | if (!(await this.verifyDir(item))) { 65 | return false; 66 | } 67 | } 68 | for (let i = 0; i < this.verifyFileList.length; i += 1) { 69 | const item = this.verifyFileList[i]; 70 | 71 | if (!this.verifyFile(item)) { 72 | return false; 73 | } 74 | } 75 | 76 | return true; 77 | } catch (e) { 78 | console.error(e); 79 | } 80 | } 81 | 82 | quickVerify() { 83 | try { 84 | for (let i = 0; i < this.verifyFileList.length; i += 1) { 85 | const item = this.verifyFileList[i]; 86 | 87 | if (!this.verifyFile(item)) { 88 | return false; 89 | } 90 | } 91 | } catch (e) { 92 | console.error(e); 93 | } 94 | } 95 | 96 | async verifyDir(filePath) { 97 | try { 98 | return fileExistsSync(filePath); 99 | } catch (e) { 100 | console.error(e); 101 | } 102 | } 103 | 104 | async createDir(newFolderPath) { 105 | try { 106 | createDirSync(newFolderPath); 107 | } catch (e) { 108 | console.error(e); 109 | } 110 | } 111 | 112 | verifyFile(filePath) { 113 | try { 114 | return fileExistsSync(filePath); 115 | } catch (e) { 116 | console.error(e); 117 | } 118 | } 119 | 120 | createFile(filePath) { 121 | try { 122 | writeFileAsync(filePath, ``); 123 | } catch (e) { 124 | console.error(e); 125 | } 126 | } 127 | 128 | cleanRotationFiles() { 129 | try { 130 | const dirFileList = readdirSync(logDir); 131 | const pattern = `^\\${baseName(logFile)}`; 132 | const _regex = new RegExp(pattern, 'gi'); 133 | const filesList = dirFileList.filter(elm => { 134 | return !elm.match(_regex); 135 | }); 136 | 137 | if (filesList === null || filesList.length < 1) { 138 | return null; 139 | } 140 | 141 | filesList.map(async a => { 142 | const dateMatch = a.match(/\d{4}-\d{2}/g); 143 | if ( 144 | dateMatch === null || 145 | dateMatch.length < 1 || 146 | typeof dateMatch[0] === 'undefined' || 147 | dateMatch[0] === null 148 | ) { 149 | return null; 150 | } 151 | 152 | const _diff = daysDiff(yearMonthNow({}), dateMatch[0]); 153 | if (_diff >= logFileRotationCleanUpThreshold) { 154 | deleteFilesSync(`${logDir}/${a}`); 155 | } 156 | }); 157 | } catch (e) { 158 | console.error(e); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/classes/Storage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { log } from '../utils/log'; 4 | import { readFileSync, writeFileSync } from '../api/sys/fileOps'; 5 | 6 | export default class Storage { 7 | constructor(filePath) { 8 | this.filePath = filePath; 9 | } 10 | 11 | getAll() { 12 | try { 13 | const _stream = readFileSync(this.filePath); 14 | if ( 15 | typeof _stream === 'undefined' || 16 | _stream === null || 17 | Object.keys(_stream).length < 1 18 | ) { 19 | return {}; 20 | } 21 | return JSON.parse(_stream); 22 | } catch (e) { 23 | log.error(e, `Storage -> getAll`); 24 | } 25 | } 26 | 27 | getItems(keys) { 28 | try { 29 | if (typeof keys === 'undefined' || keys === null || keys.length < 0) { 30 | return {}; 31 | } 32 | 33 | const allItem = this.getAll(); 34 | // eslint-disable-next-line prefer-const 35 | let _return = {}; 36 | 37 | // eslint-disable-next-line array-callback-return 38 | keys.map(a => { 39 | if (typeof allItem[a] === 'undefined' || allItem[a] === null) { 40 | return null; 41 | } 42 | 43 | _return[a] = allItem[a]; 44 | }); 45 | 46 | return _return; 47 | } catch (e) { 48 | log.error(e, `Storage -> getAll`); 49 | } 50 | } 51 | 52 | setAll({ ...data }) { 53 | try { 54 | writeFileSync(this.filePath, JSON.stringify({ ...data })); 55 | } catch (e) { 56 | log.error(e, `Storage -> setAll`); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withStyles } from '@material-ui/core/styles'; 3 | import CircularProgress from '@material-ui/core/CircularProgress'; 4 | import { styles } from './styles'; 5 | 6 | function LoadingIndicator(props) { 7 | const { classes: styles } = props; 8 | return ( 9 |
10 | 15 |
16 | ); 17 | } 18 | 19 | export default withStyles(styles)(LoadingIndicator); 20 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | root: {}, 7 | progress: { 8 | position: `absolute`, 9 | top: `50%`, 10 | left: `50%` 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /app/components/Snackbars/components/SnackbarThemeWrapper.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import classNames from 'classnames'; 5 | import CheckCircleIcon from '@material-ui/icons/CheckCircle'; 6 | import ErrorIcon from '@material-ui/icons/Error'; 7 | import InfoIcon from '@material-ui/icons/Info'; 8 | import CloseIcon from '@material-ui/icons/Close'; 9 | import IconButton from '@material-ui/core/IconButton'; 10 | import SnackbarContent from '@material-ui/core/SnackbarContent'; 11 | import WarningIcon from '@material-ui/icons/Warning'; 12 | import { withStyles } from '@material-ui/core/styles'; 13 | import { styles } from '../styles/SnackbarThemeWrapper'; 14 | 15 | const variantIcon = { 16 | success: CheckCircleIcon, 17 | warning: WarningIcon, 18 | error: ErrorIcon, 19 | info: InfoIcon 20 | }; 21 | 22 | const SnackbarThemeWrapper = props => { 23 | const { classes: styles, message, onClose, variant, ...other } = props; 24 | const Icon = variantIcon[variant]; 25 | 26 | return ( 27 | 32 | 33 | {message} 34 | 35 | } 36 | action={[ 37 | 44 | 45 | 46 | ]} 47 | {...other} 48 | /> 49 | ); 50 | }; 51 | 52 | export default withStyles(styles)(SnackbarThemeWrapper); 53 | -------------------------------------------------------------------------------- /app/components/Snackbars/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import Snackbar from '@material-ui/core/Snackbar'; 3 | import { withStyles } from '@material-ui/core/styles'; 4 | import SnackbarThemeWrapper from './components/SnackbarThemeWrapper'; 5 | import { styles } from './styles'; 6 | 7 | class Snackbars extends PureComponent { 8 | constructor(props) { 9 | super(props); 10 | this.snackbarOpen = false; 11 | } 12 | 13 | componentWillMount() { 14 | this.fireSnackbar(); 15 | } 16 | 17 | fireSnackbar = () => { 18 | this.snackbarOpen = true; 19 | }; 20 | 21 | handleClose = (event, reason) => { 22 | const { OnSnackBarsCloseAlerts } = this.props; 23 | if (reason === 'clickaway') { 24 | return; 25 | } 26 | 27 | this.snackbarOpen = false; 28 | OnSnackBarsCloseAlerts(); 29 | }; 30 | 31 | render() { 32 | const { classes: styles, message, variant, autoHideDuration } = this.props; 33 | 34 | return ( 35 | 42 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | export default withStyles(styles)(Snackbars); 53 | -------------------------------------------------------------------------------- /app/components/Snackbars/styles/SnackbarThemeWrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import green from '@material-ui/core/colors/green'; 4 | import amber from '@material-ui/core/colors/amber'; 5 | import { variables, mixins } from '../../../styles/js'; 6 | 7 | export const styles = theme => ({ 8 | success: { 9 | backgroundColor: green[600] 10 | }, 11 | error: { 12 | backgroundColor: theme.palette.error.dark 13 | }, 14 | info: { 15 | backgroundColor: theme.palette.primary.dark 16 | }, 17 | warning: { 18 | backgroundColor: amber[700] 19 | }, 20 | icon: { 21 | fontSize: 20 22 | }, 23 | iconVariant: { 24 | opacity: 0.9, 25 | marginRight: theme.spacing.unit 26 | }, 27 | message: { 28 | display: 'flex', 29 | alignItems: 'center' 30 | }, 31 | root: { 32 | minWidth: 288, 33 | maxWidth: 568, 34 | borderRadius: 4, 35 | flexGrow: `unset` 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /app/components/Snackbars/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | margin: { 7 | margin: theme.spacing.unit 8 | }, 9 | root: { 10 | top: 10, 11 | right: 15, 12 | left: `unset` 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /app/constants/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Constants 5 | * Note: Don't import log helper file from utils here 6 | */ 7 | 8 | module.exports.IS_DEV = process.env.NODE_ENV !== 'production'; 9 | 10 | module.exports.IS_PROD = process.env.NODE_ENV === 'production'; 11 | 12 | module.exports.DEBUG_PROD = process.env.DEBUG_PROD === 'true'; 13 | -------------------------------------------------------------------------------- /app/constants/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Constants 5 | * Note: Don't import log helper file from utils here 6 | */ 7 | 8 | export const LOG_FILE_ROTATION_CLEANUP_THRESHOLD = 60; // in days 9 | 10 | export const ENABLE_BACKGROUND_AUTO_UPDATE = false; 11 | 12 | export const AUTO_UPDATE_CHECK_FIREUP_DELAY = 10000; // in ms 13 | 14 | export const DONATE_PAYPAL_URL = `https://paypal.me/ganeshrvel`; 15 | -------------------------------------------------------------------------------- /app/constants/meta.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Constants 5 | * Note: Don't import log helper file from utils here 6 | */ 7 | 8 | import { pkginfo } from '../utils/pkginfo'; 9 | import { undefinedOrNullChained } from '../utils/funcs'; 10 | 11 | const { 12 | productName, 13 | description, 14 | name, 15 | author, 16 | version, 17 | repository, 18 | homepage, 19 | build 20 | } = pkginfo; 21 | 22 | export const APP_NAME = `${productName}`; 23 | 24 | export const APP_VERSION = `${version}`; 25 | 26 | export const AUTHOR_EMAIL = undefinedOrNullChained(author, 'email') 27 | ? author.email 28 | : null; 29 | 30 | export const AUTHOR_NAME = undefinedOrNullChained(author, 'name') 31 | ? author.name 32 | : null; 33 | 34 | export const APP_DESC = `${description}`; 35 | 36 | export const APP_TITLE = `${APP_NAME}`; 37 | 38 | export const APP_IDENTIFIER = `${name}`; 39 | 40 | export const APP_GITHUB_URL = undefinedOrNullChained(repository, 'url') 41 | ? repository.url.replace(/^git\+|\.git/g, '') 42 | : null; 43 | 44 | export const APP_GITHUB_RELEASES_URL = `${APP_GITHUB_URL}/releases`; 45 | 46 | export const APP_WEBSITE = `${homepage}`; 47 | 48 | export const APP_ID = undefinedOrNullChained(build, 'appId') 49 | ? build.appId 50 | : null; 51 | -------------------------------------------------------------------------------- /app/containers/Alerts/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import prefixer from '../../utils/reducerPrefixer'; 4 | 5 | const prefix = '@@Alerts'; 6 | const actionTypesList = ['THROW_ALERT', 'CLEAR_ALERT']; 7 | 8 | export const actionTypes = prefixer(prefix, actionTypesList); 9 | 10 | export function throwAlert(data) { 11 | return { 12 | type: actionTypes.THROW_ALERT, 13 | payload: { 14 | ...data 15 | } 16 | }; 17 | } 18 | export function clearAlert() { 19 | return { 20 | type: actionTypes.CLEAR_ALERT 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /app/containers/Alerts/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { connect } from 'react-redux'; 5 | import { bindActionCreators } from 'redux'; 6 | import { withReducer } from '../../store/reducers/withReducer'; 7 | import reducers from './reducers'; 8 | import { clearAlert } from './actions'; 9 | import Snackbars from '../../components/Snackbars'; 10 | 11 | class Alerts extends Component { 12 | handleClose = () => { 13 | const { clearAlert } = this.props; 14 | clearAlert(); 15 | }; 16 | 17 | render() { 18 | const { Alerts } = this.props; 19 | const { message, variant, autoHideDuration } = Alerts; 20 | return ( 21 | message && ( 22 | this.handleClose()} 24 | message={message} 25 | variant={variant} 26 | autoHideDuration={autoHideDuration} 27 | /> 28 | ) 29 | ); 30 | } 31 | } 32 | 33 | const mapDispatchToProps = (dispatch, ownProps) => 34 | bindActionCreators( 35 | { 36 | clearAlert: () => (_, getState) => { 37 | dispatch(clearAlert()); 38 | } 39 | }, 40 | dispatch 41 | ); 42 | 43 | const mapStateToProps = (state, props) => { 44 | return { 45 | Alerts: state.Alerts 46 | }; 47 | }; 48 | 49 | export default withReducer('Alerts', reducers)( 50 | connect( 51 | mapStateToProps, 52 | mapDispatchToProps 53 | )(Alerts) 54 | ); 55 | -------------------------------------------------------------------------------- /app/containers/Alerts/reducers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { actionTypes } from './actions'; 4 | 5 | export const initialState = { 6 | message: null, 7 | autoHideDuration: 6000, 8 | variant: `error` 9 | }; 10 | 11 | export default function Alerts(state = initialState, action) { 12 | // eslint-disable-next-line prefer-const 13 | let { type, payload } = action; 14 | switch (type) { 15 | case actionTypes.THROW_ALERT: 16 | return { 17 | ...state, 18 | ...payload 19 | }; 20 | case actionTypes.CLEAR_ALERT: 21 | return { 22 | ...state, 23 | ...initialState 24 | }; 25 | default: 26 | return state; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/containers/App/Root.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { Provider } from 'react-redux'; 5 | import { ConnectedRouter } from 'react-router-redux'; 6 | 7 | import App from '.'; 8 | 9 | export default class Root extends Component { 10 | render() { 11 | const { store, history } = this.props; 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/containers/App/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import prefixer from '../../utils/reducerPrefixer'; 4 | 5 | const prefix = '@@App'; 6 | const actionTypesList = ['REQ_LOAD', 'RES_LOAD', 'FAIL_LOAD']; 7 | 8 | export const actionTypes = prefixer(prefix, actionTypesList); 9 | 10 | export function reqLoadApp() { 11 | return { 12 | type: actionTypes.REQ_LOAD 13 | }; 14 | } 15 | export function resLoadApp() { 16 | return { 17 | type: actionTypes.RES_LOAD 18 | }; 19 | } 20 | 21 | export function failLoadApp(e) { 22 | return { 23 | type: actionTypes.FAIL_LOAD, 24 | payload: { 25 | error: e 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /app/containers/App/components/Titlebar.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { PureComponent } from 'react'; 4 | import { withStyles } from '@material-ui/core/styles'; 5 | import { styles } from '../styles/Titlebar'; 6 | import { toggleWindowSizeOnDoubleClick } from '../../../utils/titlebarDoubleClick'; 7 | 8 | class Titlebar extends PureComponent { 9 | render() { 10 | const { classes: styles } = this.props; 11 | return ( 12 |
{ 14 | toggleWindowSizeOnDoubleClick(); 15 | }} 16 | className={styles.root} 17 | /> 18 | ); 19 | } 20 | } 21 | 22 | export default withStyles(styles)(Titlebar); 23 | -------------------------------------------------------------------------------- /app/containers/App/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { log } from '@Log'; 5 | import CssBaseline from '@material-ui/core/CssBaseline'; 6 | import { 7 | MuiThemeProvider, 8 | createMuiTheme, 9 | withStyles 10 | } from '@material-ui/core/styles'; 11 | import { connect } from 'react-redux'; 12 | import { bindActionCreators } from 'redux'; 13 | import { IS_PROD } from '../../constants/env'; 14 | import { theme, styles } from './styles'; 15 | import Alerts from '../Alerts'; 16 | import Titlebar from './components/Titlebar'; 17 | import ErrorBoundary from '../ErrorBoundary'; 18 | import Routes from '../../routing'; 19 | import { bootLoader } from '../../utils/bootHelper'; 20 | import { settingsStorage } from '../../utils/storageHelper'; 21 | import SettingsDialog from '../Settings'; 22 | import { withReducer } from '../../store/reducers/withReducer'; 23 | import reducers from './reducers'; 24 | import { copyJsonFileToSettings, freshInstall } from '../Settings/actions'; 25 | import { analytics } from '../../utils/analyticsHelper'; 26 | import { isConnected } from '../../utils/isOnline'; 27 | 28 | const appTheme = createMuiTheme(theme()); 29 | 30 | class App extends Component { 31 | constructor(props) { 32 | super(props); 33 | 34 | this.state = {}; 35 | this.allowWritingJsonToSettings = false; 36 | } 37 | 38 | async componentWillMount() { 39 | try { 40 | this.setFreshInstall(); 41 | if (this.allowWritingJsonToSettings) { 42 | this.writeJsonToSettings(); 43 | } 44 | 45 | this.runAnalytics(); 46 | } catch (e) { 47 | log.error(e, `App -> componentWillMount`); 48 | } 49 | } 50 | 51 | componentDidMount() { 52 | try { 53 | bootLoader.cleanRotationFiles(); 54 | } catch (e) { 55 | log.error(e, `App -> componentDidMount`); 56 | } 57 | } 58 | 59 | setFreshInstall() { 60 | try { 61 | const { _freshInstall } = this.props; 62 | const isFreshInstallSettings = settingsStorage.getItems(['freshInstall']); 63 | let isFreshInstall = 0; 64 | 65 | switch (isFreshInstallSettings.freshInstall) { 66 | case undefined: 67 | case null: 68 | // app was just installed 69 | isFreshInstall = 1; 70 | break; 71 | case 1: 72 | // second boot after installation 73 | isFreshInstall = 0; 74 | break; 75 | case -1: 76 | // isFreshInstall was reset 77 | isFreshInstall = 1; 78 | break; 79 | case 0: 80 | default: 81 | // more than 2 boot ups have occured 82 | isFreshInstall = 0; 83 | this.allowWritingJsonToSettings = true; 84 | return null; 85 | } 86 | 87 | _freshInstall({ isFreshInstall }); 88 | } catch (e) { 89 | log.error(e, `App -> setFreshInstall`); 90 | } 91 | } 92 | 93 | writeJsonToSettings() { 94 | try { 95 | const { _copyJsonFileToSettings } = this.props; 96 | const settingsFromStorage = settingsStorage.getAll(); 97 | _copyJsonFileToSettings({ ...settingsFromStorage }); 98 | } catch (e) { 99 | log.error(e, `App -> writeJsonToSettings`); 100 | } 101 | } 102 | 103 | runAnalytics() { 104 | const isAnalyticsEnabledSettings = settingsStorage.getItems([ 105 | 'enableAnalytics' 106 | ]); 107 | try { 108 | if (isAnalyticsEnabledSettings.enableAnalytics && IS_PROD) { 109 | isConnected() 110 | .then(connected => { 111 | analytics.send('screenview', { cd: '/Home' }); 112 | analytics.send(`pageview`, { dp: '/Home' }); 113 | 114 | return connected; 115 | }) 116 | .catch(() => {}); 117 | } 118 | } catch (e) { 119 | log.error(e, `App -> runAnalytics`); 120 | } 121 | } 122 | 123 | render() { 124 | const { classes: styles } = this.props; 125 | return ( 126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 | ); 139 | } 140 | } 141 | const mapDispatchToProps = (dispatch, ownProps) => 142 | bindActionCreators( 143 | { 144 | _copyJsonFileToSettings: ({ ...data }) => (_, getState) => { 145 | dispatch(copyJsonFileToSettings({ ...data })); 146 | }, 147 | 148 | _freshInstall: ({ ...data }) => (_, getState) => { 149 | dispatch(freshInstall({ ...data }, getState)); 150 | } 151 | }, 152 | dispatch 153 | ); 154 | 155 | const mapStateToProps = (state, props) => { 156 | return {}; 157 | }; 158 | 159 | export default withReducer('App', reducers)( 160 | connect( 161 | mapStateToProps, 162 | mapDispatchToProps 163 | )(withStyles(styles)(App)) 164 | ); 165 | -------------------------------------------------------------------------------- /app/containers/App/reducers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | import { actionTypes } from './actions'; 5 | 6 | export const initialState = {}; 7 | 8 | export default function App(state = initialState, action) { 9 | // eslint-disable-next-line prefer-const, no-unused-vars 10 | let { type, payload } = action; 11 | switch (type) { 12 | default: 13 | return state; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/containers/App/selectors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | 5 | import { createSelector } from 'reselect'; 6 | import { initialState } from './reducers'; 7 | 8 | const make = (state, props) => (state ? state.App : {}); 9 | 10 | /* eslint-enable no-unused-vars */ 11 | -------------------------------------------------------------------------------- /app/containers/App/styles/Titlebar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = themes => { 6 | return { 7 | root: { 8 | width: `100%`, 9 | height: 14, 10 | ...mixins().appDragEnable 11 | } 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /app/containers/App/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | // eslint-disable-next-line no-unused-vars 6 | export const theme = args => { 7 | return { 8 | palette: { 9 | primary: { 10 | ...variables().styles.primaryColor 11 | }, 12 | secondary: { 13 | ...variables().styles.secondaryColor 14 | } 15 | }, 16 | typography: { 17 | useNextVariants: true, 18 | fontSize: variables().regularFontSize, 19 | fontFamily: [ 20 | 'Roboto', 21 | '-apple-system', 22 | 'BlinkMacSystemFont', 23 | '"Segoe UI"', 24 | '"Helvetica Neue"', 25 | 'Arial', 26 | 'sans-serif', 27 | '"Apple Color Emoji"', 28 | '"Segoe UI Emoji"', 29 | '"Segoe UI Symbol"' 30 | ].join(',') 31 | }, 32 | 33 | overrides: {} 34 | }; 35 | }; 36 | 37 | // eslint-disable-next-line no-unused-vars 38 | export const styles = args => { 39 | // eslint-disable-line no-unused-vars 40 | return { 41 | root: {}, 42 | noProfileError: { 43 | textAlign: `center`, 44 | ...mixins().center, 45 | ...mixins().absoluteCenter 46 | } 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /app/containers/ErrorBoundary/components/GenerateErrorReport.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { shell, remote } from 'electron'; 5 | import path from 'path'; 6 | import { connect } from 'react-redux'; 7 | import { bindActionCreators } from 'redux'; 8 | import { withStyles } from '@material-ui/core/styles'; 9 | import { styles } from '../styles/GenerateErrorReport'; 10 | import { baseName, PATHS } from '../../../utils/paths'; 11 | import { log } from '@Log'; 12 | import { promisifiedRimraf } from '../../../api/sys'; 13 | import { fileExistsSync } from '../../../api/sys/fileOps'; 14 | import { AUTHOR_EMAIL } from '../../../constants/meta'; 15 | import { throwAlert } from '../../Alerts/actions'; 16 | import { 17 | mailToInstructions as _mailToInstructions, 18 | reportGenerateError, 19 | mailTo 20 | } from '../../../templates/generateErrorReport'; 21 | import { compressFile } from '../../../utils/gzip'; 22 | import GenerateErrorReportBody from './GenerateErrorReportBody'; 23 | 24 | const { logFile } = PATHS; 25 | const { getPath } = remote.app; 26 | const desktopPath = getPath('desktop'); 27 | const zippedLogFileBaseName = `${baseName(logFile)}.gz`; 28 | const logFileZippedPath = path.resolve( 29 | path.join(desktopPath, `./${zippedLogFileBaseName}`) 30 | ); 31 | const mailToInstructions = _mailToInstructions(zippedLogFileBaseName); 32 | 33 | class GenerateErrorReport extends Component { 34 | compressLog = () => { 35 | try { 36 | compressFile(logFile, logFileZippedPath); 37 | } catch (e) { 38 | log.error(e, `GenerateErrorReport -> compressLog`); 39 | } 40 | }; 41 | 42 | handleGenerateErrorLogs = async () => { 43 | try { 44 | const { handleThrowError } = this.props; 45 | 46 | const { error } = await promisifiedRimraf(logFileZippedPath); 47 | 48 | if (error) { 49 | handleThrowError({ 50 | message: reportGenerateError 51 | }); 52 | return null; 53 | } 54 | 55 | this.compressLog(); 56 | 57 | if (!fileExistsSync(logFileZippedPath)) { 58 | handleThrowError({ 59 | message: reportGenerateError 60 | }); 61 | return null; 62 | } 63 | 64 | if (window) { 65 | window.location.href = `${mailTo} ${mailToInstructions}`; 66 | } 67 | 68 | shell.showItemInFolder(logFileZippedPath); 69 | } catch (e) { 70 | log.error(e, `GenerateErrorReport -> generateErrorLogs`); 71 | } 72 | }; 73 | 74 | render() { 75 | const { classes: styles } = this.props; 76 | return ( 77 | 85 | ); 86 | } 87 | } 88 | 89 | const mapDispatchToProps = (dispatch, ownProps) => 90 | bindActionCreators( 91 | { 92 | handleThrowError: ({ ...args }) => (_, getState) => { 93 | dispatch(throwAlert({ ...args })); 94 | } 95 | }, 96 | dispatch 97 | ); 98 | 99 | const mapStateToProps = (state, props) => { 100 | return {}; 101 | }; 102 | 103 | export default connect( 104 | mapStateToProps, 105 | mapDispatchToProps 106 | )(withStyles(styles)(GenerateErrorReport)); 107 | -------------------------------------------------------------------------------- /app/containers/ErrorBoundary/components/GenerateErrorReportBody.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { PureComponent } from 'react'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import Button from '@material-ui/core/Button'; 6 | 7 | export default class GenerateErrorReportBody extends PureComponent { 8 | render() { 9 | const { 10 | styles, 11 | zippedLogFileBaseName, 12 | mailTo, 13 | mailToInstructions, 14 | AUTHOR_EMAIL, 15 | onGenerateErrorLogs 16 | } = this.props; 17 | return ( 18 | 19 | 20 |
    21 |
  • Click on the "EMAIL ERROR LOGS" button.
  • 22 |
  • It will open your email client.
  • 23 |
  • 24 | Attach the generated error report 25 | {` ${zippedLogFileBaseName}`} (which is found in 26 | your Desktop folder) along with this email. 27 |
  • 28 |
  • Click send.
  • 29 |
30 |
31 | 39 | 40 | Developer email address: 41 | 45 | {AUTHOR_EMAIL} 46 | 47 | 48 |
49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/containers/ErrorBoundary/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { withStyles } from '@material-ui/core/styles'; 5 | import Typography from '@material-ui/core/Typography'; 6 | import Button from '@material-ui/core/Button'; 7 | import { log } from '@Log'; 8 | import { remote } from 'electron'; 9 | import { EOL } from 'os'; 10 | import { connect } from 'react-redux'; 11 | import { bindActionCreators } from 'redux'; 12 | import { styles } from './styles'; 13 | import { imgsrc } from '../../utils/imgsrc'; 14 | import GenerateErrorReport from './components/GenerateErrorReport'; 15 | 16 | class ErrorBoundary extends Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | errorInfo: null 21 | }; 22 | } 23 | 24 | componentDidCatch(error, errorInfo) { 25 | this.setState({ 26 | errorInfo 27 | }); 28 | const _errorInfo = JSON.stringify(errorInfo); 29 | log.doLog( 30 | `Error boundary log capture:${EOL}${error.toString()}${EOL}${_errorInfo}`, 31 | true, 32 | error 33 | ); 34 | } 35 | 36 | handleReload = () => { 37 | try { 38 | remote.getCurrentWindow().reload(); 39 | } catch (e) { 40 | log.error(e, `ErrorBoundary -> handleReload`); 41 | } 42 | }; 43 | 44 | render() { 45 | const { classes: styles, children } = this.props; 46 | const { errorInfo } = this.state; 47 | if (errorInfo) { 48 | return ( 49 |
50 | Some Error Occured! 55 | 56 | Whoops! 57 | 58 | 59 | I promise it's not you, it's me. 60 | 61 | 62 | Please send us the error log so that I can fix this issue. 63 | 64 | 65 | 72 |
73 | ); 74 | } 75 | 76 | return children; 77 | } 78 | } 79 | 80 | const mapDispatchToProps = (dispatch, ownProps) => 81 | bindActionCreators({}, dispatch); 82 | 83 | const mapStateToProps = (state, props) => { 84 | return {}; 85 | }; 86 | 87 | export default connect( 88 | mapStateToProps, 89 | mapDispatchToProps 90 | )(withStyles(styles)(ErrorBoundary)); 91 | -------------------------------------------------------------------------------- /app/containers/ErrorBoundary/styles/GenerateErrorReport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | subHeading: { 7 | ...mixins().noDrag, 8 | ...mixins().noselect, 9 | marginTop: 15 10 | }, 11 | instructions: { 12 | listStyle: `none`, 13 | color: variables().styles.textLightColor, 14 | lineHeight: '24px', 15 | marginTop: 15, 16 | paddingLeft: 0, 17 | marginBottom: 15 18 | }, 19 | generateLogsBtn: { 20 | marginTop: 0 21 | }, 22 | emailIdWrapper: { 23 | color: variables().styles.textLightColor, 24 | marginTop: 15 25 | }, 26 | emailId: { 27 | fontWeight: `bold` 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /app/containers/ErrorBoundary/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | root: { 7 | textAlign: `center`, 8 | ...mixins().center, 9 | ...mixins().absoluteCenter 10 | }, 11 | bugImg: { 12 | ...mixins().noDrag, 13 | height: `auto`, 14 | width: 150 15 | }, 16 | headings: { 17 | ...mixins().noDrag, 18 | ...mixins().noselect, 19 | marginTop: 15 20 | }, 21 | subHeading: { 22 | ...mixins().noDrag, 23 | ...mixins().noselect, 24 | marginTop: 15 25 | }, 26 | goBackBtn: { 27 | marginTop: 20 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /app/containers/HomePage/Loadable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Loadable from 'react-imported-component'; 4 | import LoadingIndicator from '../../components/LoadingIndicator'; 5 | 6 | export default Loadable(() => import('./index'), { 7 | LoadingComponent: LoadingIndicator 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/HomePage/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import prefixer from '../../utils/reducerPrefixer'; 4 | 5 | const prefix = '@@Home'; 6 | const actionTypesList = []; 7 | 8 | export const actionTypes = prefixer(prefix, actionTypesList); 9 | -------------------------------------------------------------------------------- /app/containers/HomePage/components/BodyAreaPane.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-case-declarations: off */ 4 | 5 | import React, { PureComponent } from 'react'; 6 | import { withRouter, Link } from 'react-router-dom'; 7 | import { withStyles } from '@material-ui/core/styles'; 8 | import Button from '@material-ui/core/Button'; 9 | import { log } from '@Log'; 10 | import { routes } from '../../../routing'; 11 | import { styles } from '../styles/BodyAreaPane'; 12 | 13 | class BodyAreaPane extends PureComponent { 14 | render() { 15 | const { classes: styles, onSendAlertsBtn } = this.props; 16 | 17 | return ( 18 |
19 |

Electron-React-Redux advanced and scalable boilerplate

20 |
21 | 29 | 30 | 41 |
42 |
43 | 53 | 63 |
64 |
65 | Goto Second Page 66 | 67 | {/* 68 | // Import a image from the local path. 69 | // Default images folder: ./app/public/images 70 | 71 | 76 | 77 | imgsrc 78 | * default path: ../public/images/ 79 | * @param filePath (string) 80 | * @param returnNoImageFound (bool) (optional) 81 | */} 82 |
83 |
84 | ); 85 | } 86 | } 87 | 88 | export default withRouter(withStyles(styles)(BodyAreaPane)); 89 | -------------------------------------------------------------------------------- /app/containers/HomePage/components/ToolbarAreaPane.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-case-declarations: off */ 4 | 5 | import React, { PureComponent } from 'react'; 6 | import { withStyles } from '@material-ui/core/styles'; 7 | import { log } from '@Log'; 8 | import { styles } from '../styles/ToolbarAreaPane'; 9 | import ToolbarBody from './ToolbarBody'; 10 | 11 | class ToolbarAreaPane extends PureComponent { 12 | render() { 13 | const { classes: styles, ...parentProps } = this.props; 14 | 15 | return ; 16 | } 17 | } 18 | 19 | export default withStyles(styles)(ToolbarAreaPane); 20 | -------------------------------------------------------------------------------- /app/containers/HomePage/components/ToolbarBody.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { PureComponent } from 'react'; 4 | import AppBar from '@material-ui/core/AppBar'; 5 | import Toolbar from '@material-ui/core/Toolbar'; 6 | import IconButton from '@material-ui/core/IconButton'; 7 | import Tooltip from '@material-ui/core/Tooltip'; 8 | import classNames from 'classnames'; 9 | import { imgsrc } from '../../../utils/imgsrc'; 10 | 11 | export default class ToolbarAreaPane extends PureComponent { 12 | activeToolbarList = ({ ...args }) => { 13 | const { toolbarList } = args; 14 | 15 | const _activeToolbarList = toolbarList; 16 | 17 | Object.keys(_activeToolbarList).map(a => { 18 | const item = _activeToolbarList[a]; 19 | switch (a) { 20 | case 'settings': 21 | _activeToolbarList[a] = { 22 | ...item 23 | }; 24 | break; 25 | default: 26 | break; 27 | } 28 | 29 | return _activeToolbarList; 30 | }); 31 | 32 | return _activeToolbarList; 33 | }; 34 | 35 | render() { 36 | const { 37 | styles, 38 | toolbarList, 39 | handleDoubleClickToolBar, 40 | handleToolbarAction 41 | } = this.props; 42 | 43 | /* Control what items should be enabled on the titlebar */ 44 | const _toolbarList = this.activeToolbarList({ 45 | toolbarList 46 | }); 47 | 48 | return ( 49 |
50 | 51 | { 55 | handleDoubleClickToolBar(event); 56 | }} 57 | > 58 |
59 | {Object.keys(_toolbarList).map(a => { 60 | const item = _toolbarList[a]; 61 | return ( 62 | 63 |
64 | handleToolbarAction(a)} 68 | className={classNames({ 69 | [styles.disabledNavBtns]: !item.enabled, 70 | [styles.invertedNavBtns]: item.invert 71 | })} 72 | > 73 | {item.label} 78 | 79 |
80 |
81 | ); 82 | })} 83 |
84 |
85 |
86 |
87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/containers/HomePage/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { connect } from 'react-redux'; 5 | import { bindActionCreators } from 'redux'; 6 | import { log } from '@Log'; 7 | import { withStyles } from '@material-ui/core/styles'; 8 | import ToolbarAreaPane from './components/ToolbarAreaPane'; 9 | import { withReducer } from '../../store/reducers/withReducer'; 10 | import { throwAlert } from '../Alerts/actions'; 11 | import { styles } from './styles'; 12 | import reducers from './reducers'; 13 | import { toggleSettings } from '../Settings/actions'; 14 | import { makeToolbarList } from './selectors'; 15 | import { toggleWindowSizeOnDoubleClick } from '../../utils/titlebarDoubleClick'; 16 | import BodyAreaPane from './components/BodyAreaPane'; 17 | 18 | class Home extends Component { 19 | _handleDoubleClickToolBar = event => { 20 | if (event.target !== event.currentTarget) { 21 | return null; 22 | } 23 | 24 | toggleWindowSizeOnDoubleClick(); 25 | }; 26 | 27 | _handleToggleSettings = () => { 28 | const { handleToggleSettings } = this.props; 29 | handleToggleSettings(true); 30 | }; 31 | 32 | _handleToolbarAction = itemType => { 33 | switch (itemType) { 34 | case 'settings': 35 | this._handleToggleSettings(true); 36 | break; 37 | 38 | default: 39 | break; 40 | } 41 | }; 42 | 43 | _handleSendAlertsBtn = ({ ...args }) => { 44 | const { handleThrowAlert } = this.props; 45 | 46 | handleThrowAlert({ 47 | message: `This is a test alert.`, 48 | ...args 49 | }); 50 | }; 51 | 52 | render() { 53 | const { classes: styles, toolbarList } = this.props; 54 | 55 | return ( 56 |
57 |
58 | 64 | 65 |
66 |
67 | ); 68 | } 69 | } 70 | 71 | const mapDispatchToProps = (dispatch, ownProps) => 72 | bindActionCreators( 73 | { 74 | handleToggleSettings: data => (_, getState) => { 75 | dispatch(toggleSettings(data)); 76 | }, 77 | 78 | handleThrowAlert: data => (_, getState) => { 79 | dispatch(throwAlert({ ...data })); 80 | } 81 | }, 82 | dispatch 83 | ); 84 | 85 | const mapStateToProps = (state, props) => { 86 | return { 87 | toolbarList: makeToolbarList(state) 88 | }; 89 | }; 90 | 91 | export default withReducer('Home', reducers)( 92 | connect( 93 | mapStateToProps, 94 | mapDispatchToProps 95 | )(withStyles(styles)(Home)) 96 | ); 97 | -------------------------------------------------------------------------------- /app/containers/HomePage/reducers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const initialState = { 4 | toolbarList: { 5 | settings: { 6 | enabled: true, 7 | label: 'Settings', 8 | imgSrc: 'Toolbar/settings.svg', 9 | invert: false 10 | } 11 | } 12 | }; 13 | 14 | export default function Home(state = initialState, action) { 15 | // eslint-disable-next-line prefer-const, no-unused-vars 16 | let { type, payload } = action; 17 | switch (type) { 18 | default: 19 | return state; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/containers/HomePage/selectors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { createSelector } from 'reselect'; 4 | import { initialState } from './reducers'; 5 | 6 | const make = (state, props) => (state ? state.Home : {}); 7 | 8 | export const makeToolbarList = createSelector( 9 | make, 10 | state => (state ? state.toolbarList : initialState.toolbarList) 11 | ); 12 | -------------------------------------------------------------------------------- /app/containers/HomePage/styles/BodyAreaPane.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => { 6 | return { 7 | root: { 8 | padding: 20 9 | }, 10 | btnWrapper: { 11 | ...mixins().center, 12 | width: '100%', 13 | textAlign: 'center' 14 | }, 15 | btn: { 16 | margin: 10 17 | } 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /app/containers/HomePage/styles/ToolbarAreaPane.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => { 6 | return { 7 | root: { 8 | ...mixins().appDragEnable 9 | }, 10 | grow: { 11 | flexGrow: 1 12 | }, 13 | toolbarInnerWrapper: { 14 | display: 'flex' 15 | }, 16 | toolbar: { 17 | width: `auto`, 18 | height: variables().sizes.toolbarHeight 19 | }, 20 | appBar: {}, 21 | navBtns: { 22 | paddingLeft: 5 23 | }, 24 | noAppDrag: { 25 | ...mixins().appDragDisable 26 | }, 27 | navBtnImgs: { 28 | height: 25, 29 | width: `auto`, 30 | ...mixins().noDrag, 31 | ...mixins().noselect 32 | }, 33 | disabledNavBtns: { 34 | backgroundColor: `#f9f9f9` 35 | }, 36 | invertedNavBtns: { 37 | [`&:hover`]: { 38 | filter: `none` 39 | }, 40 | [`&:not(:hover)`]: { 41 | filter: `invert(100)`, 42 | background: variables().styles.primaryColor.main 43 | } 44 | } 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /app/containers/HomePage/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => { 6 | return { 7 | root: {}, 8 | grid: { 9 | width: `100%` 10 | } 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/Loadable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Loadable from 'react-imported-component'; 4 | import LoadingIndicator from '../../components/LoadingIndicator'; 5 | 6 | export default Loadable(() => import('./index'), { 7 | LoadingComponent: LoadingIndicator 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { Link } from 'react-router-dom'; 5 | import { Helmet } from 'react-helmet'; 6 | import { routes } from '../../routing'; 7 | import styles from './styles/index.scss'; 8 | import { APP_TITLE } from '../../constants/meta'; 9 | 10 | export default class NotFound extends Component { 11 | render() { 12 | return ( 13 | 14 | 15 | Resource not found! 16 | 17 |
18 |

Resource not found!

19 | Go back 20 |
21 |
22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/styles/index.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 500px; 3 | margin-left: auto; 4 | margin-right: auto; 5 | text-align: center; 6 | h2 { 7 | font-size: 2rem; 8 | } 9 | a { 10 | font-size: 1.4rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/containers/PrivacyPolicyPage/Loadable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Loadable from 'react-imported-component'; 4 | import LoadingIndicator from '../../components/LoadingIndicator'; 5 | 6 | export default Loadable(() => import('./index'), { 7 | LoadingComponent: LoadingIndicator 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/PrivacyPolicyPage/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | root: { 7 | textAlign: `left`, 8 | padding: '30px 30px 30px 30px', 9 | maxWidth: '800px', 10 | marginRight: 'auto', 11 | marginLeft: 'auto', 12 | overflow: 'auto' 13 | }, 14 | a: { 15 | fontWeight: `bold` 16 | }, 17 | heading: {}, 18 | body: { 19 | lineHeight: `22px` 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /app/containers/ProgressbarPage/Loadable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Loadable from 'react-imported-component'; 4 | import LoadingIndicator from '../../components/LoadingIndicator'; 5 | 6 | export default Loadable(() => import('./index'), { 7 | LoadingComponent: LoadingIndicator 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/ProgressbarPage/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { ipcRenderer } from 'electron'; 4 | import React, { Component } from 'react'; 5 | import { withStyles } from '@material-ui/core/styles'; 6 | import LinearProgress from '@material-ui/core/LinearProgress'; 7 | import Typography from '@material-ui/core/Typography'; 8 | import { styles } from './styles'; 9 | 10 | class ProgressbarPage extends Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.initialState = { 15 | progressTitle: `Progress...`, 16 | progressBodyText: `Progress...`, 17 | value: 0, 18 | variant: `indeterminate` 19 | }; 20 | 21 | this.state = { 22 | ...this.initialState 23 | }; 24 | } 25 | 26 | componentWillMount() { 27 | ipcRenderer.on('progressBarDataCommunication', (event, { ...args }) => { 28 | this.setState({ ...args }); 29 | }); 30 | } 31 | 32 | render() { 33 | const { classes: styles } = this.props; 34 | const { progressTitle, progressBodyText, value, variant } = this.state; 35 | return ( 36 |
37 | 38 | {progressTitle} 39 | 40 | 41 | {progressBodyText} 42 | 43 | 44 |
45 | ); 46 | } 47 | } 48 | 49 | export default withStyles(styles)(ProgressbarPage); 50 | -------------------------------------------------------------------------------- /app/containers/ProgressbarPage/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | root: { 7 | textAlign: `left`, 8 | ...mixins().center, 9 | width: 500, 10 | marginTop: 10 11 | }, 12 | progressBodyText: { 13 | marginBottom: 10 14 | }, 15 | progressTitle: { 16 | fontWeight: 'bold', 17 | marginBottom: 10 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /app/containers/ReportBugsPage/Loadable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Loadable from 'react-imported-component'; 4 | import LoadingIndicator from '../../components/LoadingIndicator'; 5 | 6 | export default Loadable(() => import('./index'), { 7 | LoadingComponent: LoadingIndicator 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/ReportBugsPage/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { withStyles } from '@material-ui/core/styles'; 5 | import { Helmet } from 'react-helmet'; 6 | import { log } from '@Log'; 7 | import { connect } from 'react-redux'; 8 | import { bindActionCreators } from 'redux'; 9 | import GenerateErrorReport from '../ErrorBoundary/components/GenerateErrorReport'; 10 | import { APP_NAME } from '../../constants/meta'; 11 | import { styles } from './styles'; 12 | 13 | class ReportBugsPage extends Component { 14 | render() { 15 | const { classes: styles } = this.props; 16 | return ( 17 |
18 | 19 | Report Bugs 20 | 21 | 22 |
23 | ); 24 | } 25 | } 26 | 27 | const mapDispatchToProps = (dispatch, ownProps) => 28 | bindActionCreators({}, dispatch); 29 | 30 | const mapStateToProps = (state, props) => { 31 | return {}; 32 | }; 33 | 34 | export default connect( 35 | mapStateToProps, 36 | mapDispatchToProps 37 | )(withStyles(styles)(ReportBugsPage)); 38 | -------------------------------------------------------------------------------- /app/containers/ReportBugsPage/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | root: { 7 | textAlign: `center`, 8 | ...mixins().center, 9 | width: 500, 10 | marginTop: 77 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /app/containers/SecondPage/Loadable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Loadable from 'react-imported-component'; 4 | import LoadingIndicator from '../../components/LoadingIndicator'; 5 | 6 | export default Loadable(() => import('./index'), { 7 | LoadingComponent: LoadingIndicator 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/SecondPage/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { Link } from 'react-router-dom'; 5 | import { Helmet } from 'react-helmet'; 6 | import { routes } from '../../routing'; 7 | import styles from './styles/index.scss'; 8 | import { APP_TITLE } from '../../constants/meta'; 9 | 10 | export default class SecondPage extends Component { 11 | render() { 12 | return ( 13 | 14 | 15 | Second Page! 16 | 17 |
18 |

This is the second page!

19 | Go back 20 |
21 |
22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/containers/SecondPage/styles/index.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 500px; 3 | margin-left: auto; 4 | margin-right: auto; 5 | text-align: center; 6 | h2 { 7 | font-size: 2rem; 8 | } 9 | a { 10 | font-size: 1.4rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/containers/Settings/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { log } from '@Log'; 4 | import prefixer from '../../utils/reducerPrefixer'; 5 | import omitLodash from 'lodash/omit'; 6 | import { settingsStorage } from '../../utils/storageHelper'; 7 | 8 | const prefix = '@@Settings'; 9 | const actionTypesList = [ 10 | 'TOGGLE_SETTINGS', 11 | 'FRESH_INSTALL', 12 | 'ENABLE_AUTO_UPDATE_CHECK', 13 | 'ENABLE_ANALYTICS', 14 | 'COPY_JSON_FILE_TO_SETTINGS' 15 | ]; 16 | 17 | const excludeItemsFromSettingsFile = ['toggleSettings']; 18 | 19 | export const actionTypes = prefixer(prefix, actionTypesList); 20 | 21 | export function toggleSettings(data) { 22 | return { 23 | type: actionTypes.TOGGLE_SETTINGS, 24 | payload: data 25 | }; 26 | } 27 | 28 | export function freshInstall({ ...data }, getState) { 29 | const { isFreshInstall } = data; 30 | 31 | return dispatch => { 32 | dispatch({ 33 | type: actionTypes.FRESH_INSTALL, 34 | payload: isFreshInstall 35 | }); 36 | dispatch(copySettingsToJsonFile(getState)); 37 | }; 38 | } 39 | 40 | export function enableAutoUpdateCheck({ ...data }, getState) { 41 | const { toggle } = data; 42 | 43 | return dispatch => { 44 | dispatch({ 45 | type: actionTypes.ENABLE_AUTO_UPDATE_CHECK, 46 | payload: toggle 47 | }); 48 | dispatch(copySettingsToJsonFile(getState)); 49 | }; 50 | } 51 | 52 | export function enableAnalytics({ ...data }, getState) { 53 | const { toggle } = data; 54 | 55 | return dispatch => { 56 | dispatch({ 57 | type: actionTypes.ENABLE_ANALYTICS, 58 | payload: toggle 59 | }); 60 | dispatch(copySettingsToJsonFile(getState)); 61 | }; 62 | } 63 | 64 | export function copySettingsToJsonFile(getState) { 65 | return dispatch => { 66 | const settingsState = getState().Settings ? getState().Settings : {}; 67 | const filteredSettings = omitLodash( 68 | settingsState, 69 | excludeItemsFromSettingsFile 70 | ); 71 | settingsStorage.setAll({ ...filteredSettings }); 72 | }; 73 | } 74 | 75 | export function copyJsonFileToSettings({ ...data }) { 76 | return { 77 | type: actionTypes.COPY_JSON_FILE_TO_SETTINGS, 78 | payload: { 79 | ...data 80 | } 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /app/containers/Settings/components/SettingsDialog.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { PureComponent } from 'react'; 4 | import electronIs from 'electron-is'; 5 | import classNames from 'classnames'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import Button from '@material-ui/core/Button'; 8 | import Dialog from '@material-ui/core/Dialog'; 9 | import DialogActions from '@material-ui/core/DialogActions'; 10 | import DialogContent from '@material-ui/core/DialogContent'; 11 | import Paper from '@material-ui/core/Paper'; 12 | import Switch from '@material-ui/core/Switch'; 13 | import FormControl from '@material-ui/core/FormControl'; 14 | import FormGroup from '@material-ui/core/FormGroup'; 15 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 16 | import { privacyPolicyWindow } from '../../../utils/createWindows'; 17 | 18 | const isMas = electronIs.mas(); 19 | 20 | export default class SettingsDialog extends PureComponent { 21 | render() { 22 | const { 23 | open, 24 | freshInstall, 25 | styles, 26 | enableAutoUpdateCheck, 27 | enableAnalytics, 28 | onAnalyticsChange, 29 | onDialogBoxCloseBtnClick, 30 | onAutoUpdateCheckChange 31 | } = this.props; 32 | 33 | return ( 34 | 41 | onDialogBoxCloseBtnClick({ 42 | confirm: false 43 | }) 44 | } 45 | > 46 | 47 | Settings 48 | 49 | 50 | 51 |
52 | 53 | General Settings 54 | 55 | 56 | {!isMas && ( 57 | 58 | 59 | Enable auto-update check 60 | 61 | 62 | 68 | onAutoUpdateCheckChange({ 69 | toggle: !enableAutoUpdateCheck 70 | }) 71 | } 72 | /> 73 | } 74 | label={enableAutoUpdateCheck ? `Enabled` : `Disabled`} 75 | /> 76 | 77 | )} 78 | 79 | 80 | 81 | Enable anonymous usage statistics gathering 82 | 83 | 84 | 90 | onAnalyticsChange({ 91 | toggle: !enableAnalytics 92 | }) 93 | } 94 | /> 95 | } 96 | label={enableAnalytics ? `Enabled` : `Disabled`} 97 | /> 98 | {freshInstall ? ( 99 | 100 |
101 | 105 | Choose your privacy settings. Use the toggles above to 106 | enable or disable them. 107 | 108 | 109 | ) : null} 110 | 111 | We do not gather any kind of personal information and neither 112 | do we sell your data. We use this information only to improve 113 | the User Experience and squash some bugs.  114 | { 117 | privacyPolicyWindow(true); 118 | }} 119 | > 120 | Learn more... 121 | 122 | 123 | 124 |
125 | 126 | 127 | 128 | 139 | 140 |
141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/containers/Settings/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withStyles } from '@material-ui/core/styles'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import { styles } from './styles'; 6 | import { withReducer } from '../../store/reducers/withReducer'; 7 | import reducers from './reducers'; 8 | import { 9 | makeToggleSettings, 10 | makeEnableAutoUpdateCheck, 11 | makeEnableAnalytics, 12 | makeFreshInstall 13 | } from './selectors'; 14 | import { 15 | enableAnalytics, 16 | enableAutoUpdateCheck, 17 | freshInstall, 18 | toggleSettings 19 | } from './actions'; 20 | import SettingsDialog from './components/SettingsDialog'; 21 | 22 | class Settings extends Component { 23 | _handleDialogBoxCloseBtnClick = ({ confirm = false }) => { 24 | const { freshInstall } = this.props; 25 | this._handleToggleSettings(confirm); 26 | 27 | if (freshInstall !== 0) { 28 | this._handleFreshInstall(); 29 | } 30 | }; 31 | 32 | _handleToggleSettings = confirm => { 33 | const { handleToggleSettings } = this.props; 34 | handleToggleSettings(confirm); 35 | }; 36 | 37 | _handleFreshInstall = () => { 38 | const { handleFreshInstall } = this.props; 39 | 40 | handleFreshInstall({ isFreshInstall: 0 }); 41 | }; 42 | 43 | _handleAutoUpdateCheckChange = ({ ...args }) => { 44 | const { handleEnableAutoUpdateCheck } = this.props; 45 | 46 | handleEnableAutoUpdateCheck({ ...args }); 47 | }; 48 | 49 | _handleAnalyticsChange = ({ ...args }) => { 50 | const { handleEnableAnalytics } = this.props; 51 | 52 | handleEnableAnalytics({ ...args }); 53 | }; 54 | 55 | render() { 56 | const { 57 | freshInstall, 58 | toggleSettings, 59 | classes: styles, 60 | enableAutoUpdateCheck, 61 | enableAnalytics 62 | } = this.props; 63 | const showSettings = toggleSettings || freshInstall !== 0; 64 | 65 | return ( 66 | 77 | ); 78 | } 79 | } 80 | 81 | const mapDispatchToProps = (dispatch, ownProps) => 82 | bindActionCreators( 83 | { 84 | handleToggleSettings: data => (_, getState) => { 85 | dispatch(toggleSettings(data)); 86 | }, 87 | 88 | handleFreshInstall: data => (_, getState) => { 89 | dispatch(freshInstall({ ...data }, getState)); 90 | }, 91 | 92 | handleEnableAutoUpdateCheck: ({ ...data }) => (_, getState) => { 93 | dispatch(enableAutoUpdateCheck({ ...data }, getState)); 94 | }, 95 | 96 | handleEnableAnalytics: ({ ...data }) => (_, getState) => { 97 | dispatch(enableAnalytics({ ...data }, getState)); 98 | } 99 | }, 100 | dispatch 101 | ); 102 | 103 | const mapStateToProps = (state, props) => { 104 | return { 105 | freshInstall: makeFreshInstall(state), 106 | toggleSettings: makeToggleSettings(state), 107 | enableAutoUpdateCheck: makeEnableAutoUpdateCheck(state), 108 | enableAnalytics: makeEnableAnalytics(state) 109 | }; 110 | }; 111 | 112 | export default withReducer('Settings', reducers)( 113 | connect( 114 | mapStateToProps, 115 | mapDispatchToProps 116 | )(withStyles(styles)(Settings)) 117 | ); 118 | -------------------------------------------------------------------------------- /app/containers/Settings/reducers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { actionTypes } from './actions'; 4 | 5 | export const initialState = { 6 | freshInstall: 0, 7 | toggleSettings: false, 8 | enableAutoUpdateCheck: false, 9 | enableAnalytics: false 10 | }; 11 | 12 | export default function Settings(state = initialState, action) { 13 | // eslint-disable-next-line prefer-const, no-unused-vars 14 | let { type, payload } = action; 15 | switch (type) { 16 | case actionTypes.FRESH_INSTALL: 17 | return { ...state, freshInstall: payload }; 18 | 19 | case actionTypes.TOGGLE_SETTINGS: 20 | return { ...state, toggleSettings: payload }; 21 | 22 | case actionTypes.ENABLE_AUTO_UPDATE_CHECK: 23 | return { ...state, enableAutoUpdateCheck: payload }; 24 | 25 | case actionTypes.ENABLE_ANALYTICS: 26 | return { ...state, enableAnalytics: payload }; 27 | 28 | case actionTypes.COPY_JSON_FILE_TO_SETTINGS: 29 | return { ...state, ...payload }; 30 | 31 | default: 32 | return state; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/containers/Settings/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { initialState } from './reducers'; 3 | 4 | const make = (state, props) => (state ? state.Settings : {}); 5 | 6 | export const makeFreshInstall = createSelector( 7 | make, 8 | state => (state ? state.freshInstall : initialState.freshInstall) 9 | ); 10 | 11 | export const makeToggleSettings = createSelector( 12 | make, 13 | state => (state ? state.toggleSettings : initialState.toggleSettings) 14 | ); 15 | 16 | export const makeEnableAutoUpdateCheck = createSelector( 17 | make, 18 | state => 19 | state ? state.enableAutoUpdateCheck : initialState.enableAutoUpdateCheck 20 | ); 21 | 22 | export const makeEnableAnalytics = createSelector( 23 | make, 24 | state => (state ? state.enableAnalytics : initialState.enableAnalytics) 25 | ); 26 | -------------------------------------------------------------------------------- /app/containers/Settings/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables, mixins } from '../../../styles/js'; 4 | 5 | export const styles = theme => ({ 6 | margin: {}, 7 | root: {}, 8 | fieldset: { 9 | width: `100%` 10 | }, 11 | subtitle: {}, 12 | fmSettingsStylesFix: { 13 | marginTop: 10 14 | }, 15 | formGroup: { 16 | paddingTop: 10 17 | }, 18 | subheading: { 19 | marginBottom: 5 20 | }, 21 | title: { 22 | flex: `0 0 auto`, 23 | margin: 0, 24 | padding: `24px 24px 8px` 25 | }, 26 | switch: { 27 | height: 30 28 | }, 29 | block: { 30 | marginBottom: 20 31 | }, 32 | onBoardingPaper: { 33 | position: `relative`, 34 | padding: 10, 35 | marginTop: 4, 36 | backgroundColor: variables().styles.secondaryColor.main 37 | }, 38 | onBoardingPaperArrow: { 39 | fontWeight: `bold`, 40 | content: ' ', 41 | borderBottom: `11px solid ${variables().styles.secondaryColor.main}`, 42 | borderLeft: '8px solid transparent', 43 | borderRight: '8px solid transparent', 44 | position: 'absolute', 45 | top: -10, 46 | left: 2 47 | }, 48 | onBoardingPaperBody: { 49 | color: variables().styles.primaryColor.main 50 | }, 51 | a: { 52 | fontWeight: `bold` 53 | }, 54 | btnPositive: { 55 | ...mixins().btnPositive 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint global-require: off */ 4 | 5 | import React from 'react'; 6 | import { render } from 'react-dom'; 7 | import { AppContainer } from 'react-hot-loader'; 8 | import Root from './containers/App/Root'; 9 | import { configureStore, history } from './store/configureStore'; 10 | import './styles/scss/app.global.scss'; 11 | 12 | const MOUNT_POINT = document.getElementById('root'); 13 | const store = configureStore(); 14 | 15 | render( 16 | 17 | 18 | , 19 | MOUNT_POINT 20 | ); 21 | 22 | if (module.hot) { 23 | module.hot.accept('./containers/App/Root', () => { 24 | const NextRoot = require('./containers/App/Root').default; 25 | render( 26 | 27 | 28 | , 29 | MOUNT_POINT 30 | ); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /app/main.dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint global-require: off */ 4 | 5 | import { app, BrowserWindow, ipcMain } from 'electron'; 6 | import electronIs from 'electron-is'; 7 | import MenuBuilder from './menu'; 8 | import { log } from './utils/log'; 9 | import { DEBUG_PROD, IS_DEV, IS_PROD } from './constants/env'; 10 | import AppUpdate from './classes/AppUpdate'; 11 | import { PATHS } from './utils/paths'; 12 | import { settingsStorage } from './utils/storageHelper'; 13 | import { AUTO_UPDATE_CHECK_FIREUP_DELAY } from './constants'; 14 | import { appEvents } from './utils/eventHandling'; 15 | import { bootLoader } from './utils/bootHelper'; 16 | import { nonBootableDeviceWindow } from './utils/createWindows'; 17 | import { APP_TITLE } from './constants/meta'; 18 | import { isPackaged } from './utils/isPackaged'; 19 | 20 | const isDeviceBootable = bootTheDevice(); 21 | const isMas = electronIs.mas(); 22 | let mainWindow = null; 23 | 24 | if (IS_PROD) { 25 | const sourceMapSupport = require('source-map-support'); 26 | sourceMapSupport.install(); 27 | } 28 | 29 | if (IS_DEV || DEBUG_PROD) { 30 | require('electron-debug')(); 31 | } 32 | 33 | async function bootTheDevice() { 34 | try { 35 | // For an existing installation 36 | if (bootLoader.quickVerify()) { 37 | return true; 38 | } 39 | 40 | // For a fresh installation 41 | await bootLoader.init(); 42 | return await bootLoader.verify(); 43 | } catch (e) { 44 | throw new Error(e); 45 | } 46 | } 47 | 48 | /** 49 | * Checks whether device is ready to boot or not. 50 | * Here profile files are created if not found. 51 | */ 52 | if (!isDeviceBootable) { 53 | app.on('ready', async () => { 54 | try { 55 | nonBootableDeviceWindow(); 56 | } catch (e) { 57 | throw new Error(e); 58 | } 59 | }); 60 | 61 | app.on('window-all-closed', () => { 62 | try { 63 | app.quit(); 64 | } catch (e) { 65 | throw new Error(e); 66 | } 67 | }); 68 | } else { 69 | if (IS_PROD) { 70 | process.on('uncaughtException', error => { 71 | log.error(error, `main.dev -> process -> uncaughtException`); 72 | }); 73 | 74 | appEvents.on('error', error => { 75 | log.error(error, `main.dev -> appEvents -> error`); 76 | }); 77 | 78 | ipcMain.removeAllListeners('ELECTRON_BROWSER_WINDOW_ALERT'); 79 | ipcMain.on('ELECTRON_BROWSER_WINDOW_ALERT', (event, message, title) => { 80 | ipcMain.error( 81 | message, 82 | `main.dev -> ipcMain -> on ELECTRON_BROWSER_WINDOW_ALERT -> ${title}` 83 | ); 84 | // eslint-disable-next-line no-param-reassign 85 | event.returnValue = 0; 86 | }); 87 | } 88 | 89 | const installExtensions = async () => { 90 | try { 91 | const installer = require('electron-devtools-installer'); 92 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 93 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; 94 | 95 | return Promise.all( 96 | extensions.map(name => 97 | installer.default(installer[name], forceDownload) 98 | ) 99 | ).catch(console.error); 100 | } catch (e) { 101 | log.error(e, `main.dev -> installExtensions`); 102 | } 103 | }; 104 | 105 | const createWindow = async () => { 106 | try { 107 | if (IS_DEV || DEBUG_PROD) { 108 | await installExtensions(); 109 | } 110 | 111 | mainWindow = new BrowserWindow({ 112 | title: `${APP_TITLE}`, 113 | center: true, 114 | show: false, 115 | minWidth: 854, 116 | minHeight: 640, 117 | titleBarStyle: 'hidden', 118 | webPreferences: { 119 | nodeIntegration: true 120 | } 121 | }); 122 | 123 | mainWindow.loadURL(`${PATHS.loadUrlPath}`); 124 | 125 | mainWindow.webContents.on('did-finish-load', () => { 126 | if (!mainWindow) { 127 | throw new Error(`"mainWindow" is not defined`); 128 | } 129 | if (process.env.START_MINIMIZED) { 130 | mainWindow.minimize(); 131 | } else { 132 | mainWindow.maximize(); 133 | mainWindow.show(); 134 | mainWindow.focus(); 135 | } 136 | }); 137 | 138 | mainWindow.onerror = error => { 139 | log.error(error, `main.dev -> mainWindow -> onerror`); 140 | }; 141 | 142 | mainWindow.on('closed', () => { 143 | mainWindow = null; 144 | }); 145 | } catch (e) { 146 | log.error(e, `main.dev -> createWindow`); 147 | } 148 | }; 149 | 150 | app.on('window-all-closed', () => { 151 | try { 152 | if (process.platform === 'darwin') { 153 | return; 154 | } 155 | 156 | app.quit(); 157 | } catch (e) { 158 | log.error(e, `main.dev -> window-all-closed`); 159 | } 160 | }); 161 | 162 | app.on('ready', async () => { 163 | try { 164 | await createWindow(); 165 | 166 | let appUpdaterEnable = true; 167 | if (isPackaged && process.platform === 'darwin') { 168 | appUpdaterEnable = !isMas && app.isInApplicationsFolder(); 169 | } 170 | 171 | const autoAppUpdate = new AppUpdate(); 172 | autoAppUpdate.init(); 173 | 174 | const menuBuilder = new MenuBuilder({ 175 | mainWindow, 176 | autoAppUpdate, 177 | appUpdaterEnable 178 | }); 179 | menuBuilder.buildMenu(); 180 | 181 | const autoUpdateCheckSettings = settingsStorage.getItems([ 182 | 'enableAutoUpdateCheck' 183 | ]); 184 | 185 | if (autoUpdateCheckSettings.enableAutoUpdateCheck && appUpdaterEnable) { 186 | setTimeout(() => { 187 | autoAppUpdate.checkForUpdates(); 188 | }, AUTO_UPDATE_CHECK_FIREUP_DELAY); 189 | } 190 | } catch (e) { 191 | log.error(e, `main.dev -> ready`); 192 | } 193 | }); 194 | 195 | app.on('activate', async () => { 196 | try { 197 | if (mainWindow === null) { 198 | await createWindow(); 199 | } 200 | } catch (e) { 201 | log.error(e, `main.dev -> activate`); 202 | } 203 | }); 204 | 205 | app.on('before-quit', () => (app.quitting = true)); // eslint-disable-line no-return-assign 206 | } 207 | -------------------------------------------------------------------------------- /app/public/images/Toolbar/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/public/images/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/public/images/keyboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/app/public/images/keyboard.jpg -------------------------------------------------------------------------------- /app/public/images/no-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/app/public/images/no-image.png -------------------------------------------------------------------------------- /app/routing/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Switch, Route } from 'react-router'; 5 | import { HashRouter } from 'react-router-dom'; 6 | import HomePage from '../containers/HomePage/Loadable'; 7 | import SecondPage from '../containers/SecondPage/Loadable'; 8 | import ReportBugsPage from '../containers/ReportBugsPage/Loadable'; 9 | import ProgressbarPage from '../containers/ProgressbarPage'; 10 | import PrivacyPolicyPage from '../containers/PrivacyPolicyPage/Loadable'; 11 | import NotFoundPage from '../containers/NotFoundPage/Loadable'; 12 | 13 | export const routes = { 14 | Home: { 15 | path: '/', 16 | exact: true, 17 | component: HomePage 18 | }, 19 | SecondPage: { 20 | path: '/secondPage', 21 | exact: true, 22 | component: SecondPage 23 | }, 24 | ReportBugsPage: { 25 | path: '/reportBugsPage', 26 | exact: true, 27 | component: ReportBugsPage 28 | }, 29 | ProgressbarPage: { 30 | path: '/progressbarPage', 31 | exact: true, 32 | component: ProgressbarPage 33 | }, 34 | PrivacyPolicyPage: { 35 | path: '/privacyPolicyPage', 36 | exact: true, 37 | component: PrivacyPolicyPage 38 | }, 39 | NotFound: { 40 | component: NotFoundPage 41 | } 42 | }; 43 | 44 | export default () => ( 45 | 46 | 47 | {Object.keys(routes).map(a => ( 48 | 53 | ))} 54 | 55 | 56 | ); 57 | -------------------------------------------------------------------------------- /app/store/configureStore/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint global-require: off */ 4 | 5 | import { createStore, applyMiddleware, compose } from 'redux'; 6 | import thunk from 'redux-thunk'; 7 | import { createHashHistory } from 'history'; 8 | import { routerMiddleware } from 'react-router-redux'; 9 | import { createLogger } from 'redux-logger'; 10 | import rootReducer from '../reducers'; 11 | 12 | const history = createHashHistory(); 13 | 14 | const configureStore = initialState => { 15 | // Redux Configuration 16 | const middleware = []; 17 | const enhancers = []; 18 | 19 | // Thunk Middleware 20 | middleware.push(thunk); 21 | 22 | // Logging Middleware 23 | const logger = createLogger({ 24 | level: 'info', 25 | collapsed: true 26 | }); 27 | 28 | // Skip redux logs in console during the tests 29 | if (process.env.NODE_ENV !== 'test') { 30 | middleware.push(logger); 31 | } 32 | 33 | // Router Middleware 34 | const router = routerMiddleware(history); 35 | middleware.push(router); 36 | 37 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose 38 | const composeEnhancers = 39 | window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 40 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 41 | : compose; 42 | 43 | // Apply Middleware & Compose Enhancers 44 | enhancers.push(applyMiddleware(...middleware)); 45 | const enhancer = composeEnhancers(...enhancers); 46 | 47 | // Create Store 48 | const store = createStore(rootReducer(), initialState, enhancer); 49 | 50 | store.asyncReducers = {}; 51 | store.injectReducer = (key, reducer) => { 52 | store.asyncReducers[key] = reducer; 53 | store.replaceReducer(rootReducer(store.asyncReducers)); 54 | return store; 55 | }; 56 | 57 | if (module.hot) { 58 | module.hot.accept('../reducers', () => 59 | store.replaceReducer(require('../reducers').default) 60 | ); 61 | } 62 | 63 | return store; 64 | }; 65 | 66 | export default { configureStore, history }; 67 | -------------------------------------------------------------------------------- /app/store/configureStore/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import configureStoreDev from './dev'; 4 | import configureStoreProd from './prod'; 5 | import { IS_PROD } from '../../constants/env'; 6 | 7 | const selectedConfigureStore = IS_PROD ? configureStoreProd : configureStoreDev; 8 | 9 | export const { configureStore } = selectedConfigureStore; 10 | 11 | export const { history } = selectedConfigureStore; 12 | -------------------------------------------------------------------------------- /app/store/configureStore/prod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { createStore, applyMiddleware } from 'redux'; 4 | import thunk from 'redux-thunk'; 5 | import { createHashHistory } from 'history'; 6 | import { routerMiddleware } from 'react-router-redux'; 7 | import rootReducer from '../reducers'; 8 | 9 | const history = createHashHistory(); 10 | const router = routerMiddleware(history); 11 | const enhancer = applyMiddleware(thunk, router); 12 | 13 | const configureStore = initialState => { 14 | const store = createStore(rootReducer(), initialState, enhancer); 15 | 16 | store.asyncReducers = {}; 17 | store.injectReducer = (key, reducer) => { 18 | store.asyncReducers[key] = reducer; 19 | store.replaceReducer(rootReducer(store.asyncReducers)); 20 | return store; 21 | }; 22 | return store; 23 | }; 24 | 25 | export default { configureStore, history }; 26 | -------------------------------------------------------------------------------- /app/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { combineReducers } from 'redux'; 4 | import { routerReducer as router } from 'react-router-redux'; 5 | import Alerts from '../../containers/Alerts/reducers'; 6 | import Settings from '../../containers/Settings/reducers'; 7 | 8 | const rootReducer = asyncReducers => 9 | combineReducers({ 10 | Alerts, 11 | Settings, 12 | router, 13 | ...asyncReducers 14 | }); 15 | 16 | export default rootReducer; 17 | -------------------------------------------------------------------------------- /app/store/reducers/withReducer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { object } from 'prop-types'; 5 | 6 | /* eslint-disable */ 7 | const withReducer = (key, reducer) => WrappedComponent => { 8 | const Extended = (props, context) => { 9 | context.store.injectReducer(key, reducer); 10 | return ; 11 | }; 12 | 13 | Extended.contextTypes = { 14 | store: object 15 | }; 16 | 17 | return Extended; 18 | }; 19 | /* eslint-enable */ 20 | export { withReducer }; 21 | -------------------------------------------------------------------------------- /app/styles/js/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export { default as variables } from './variables'; 4 | export { default as mixins } from './mixins'; 5 | -------------------------------------------------------------------------------- /app/styles/js/mixins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { variables } from './index'; 4 | 5 | // eslint-disable-next-line no-unused-vars 6 | export default args => { 7 | return { 8 | noselect: { 9 | [`-webkitTouchCallout`]: `none`, 10 | [`-webkitUserSelect`]: `none`, 11 | [`-khtmlUserSelect`]: `none`, 12 | [`-mozUserSelect`]: `none`, 13 | [`-msUserSelect`]: `none`, 14 | [`userSelect`]: `none` 15 | }, 16 | noDrag: { 17 | WebkitUserDrag: 'none', 18 | KhtmlUserDrag: 'none', 19 | MozUserDrag: 'none', 20 | OUserDrag: 'none', 21 | userDrag: 'none' 22 | }, 23 | absoluteCenter: { 24 | position: 'absolute', 25 | left: '50%', 26 | top: '50%', 27 | WebkitTransform: 'translate(-50%, -50%)', 28 | transform: 'translate(-50%, -50%)' 29 | }, 30 | center: { 31 | marginLeft: `auto`, 32 | marginRight: `auto` 33 | }, 34 | get appDragEnable() { 35 | return { 36 | [`-webkitAppRegion`]: `drag`, 37 | ...this.noselect 38 | }; 39 | }, 40 | appDragDisable: { 41 | [`-webkitAppRegion`]: `no-drag` 42 | }, 43 | a: { 44 | cursor: `pointer`, 45 | color: variables().styles.secondaryColor.main 46 | }, 47 | btnPositive: { 48 | backgroundColor: variables().styles.secondaryColor.main, 49 | borderColor: variables().styles.secondaryColor.main, 50 | '&:hover': { 51 | backgroundColor: '#0069d9', 52 | borderColor: '#0062cc' 53 | }, 54 | '&:active': { 55 | boxShadow: 'none', 56 | backgroundColor: '#0062cc', 57 | borderColor: '#005cbf' 58 | }, 59 | '&:focus': { 60 | boxShadow: '0 0 0 0.2rem rgba(0,123,255,0.5)' 61 | } 62 | }, 63 | btnNegative: { 64 | backgroundColor: `rgba(0, 122, 245, 0.08)`, 65 | borderColor: `rgba(0, 122, 245, 0.08)`, 66 | '&:hover': { 67 | backgroundColor: `rgba(0, 122, 245, 0.15)`, 68 | borderColor: '#0062cc' 69 | }, 70 | '&:active': { 71 | boxShadow: 'none', 72 | backgroundColor: `rgba(0, 122, 245, 0.23)`, 73 | borderColor: '#005cbf' 74 | }, 75 | '&:focus': { 76 | boxShadow: '0 0 0 0.2rem rgba(0,123,255,0.5)' 77 | } 78 | } 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /app/styles/js/variables.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | export default args => { 5 | return { 6 | sizes: { 7 | toolbarHeight: 64 8 | }, 9 | styles: { 10 | primaryColor: { 11 | main: '#ffffff' 12 | }, 13 | secondaryColor: { 14 | main: '#007af5' 15 | }, 16 | regularFontSize: 14, 17 | textLightColor: `rgba(0, 0, 0, 0.64)` 18 | } 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /app/styles/scss/app.global.scss: -------------------------------------------------------------------------------- 1 | @import './themes/fonts'; 2 | @import './base/base'; 3 | @import './themes/reset'; 4 | -------------------------------------------------------------------------------- /app/styles/scss/base/_base.scss: -------------------------------------------------------------------------------- 1 | //Base 2 | @import 'variables'; 3 | @import 'mixins'; 4 | @import 'extends'; 5 | -------------------------------------------------------------------------------- /app/styles/scss/base/_extends.scss: -------------------------------------------------------------------------------- 1 | %marginAuto { 2 | @include margin-auto(); 3 | } 4 | 5 | %clearfix { 6 | @include clearfix(); 7 | } 8 | 9 | %inlineBlock { 10 | @include inline-block(); 11 | } 12 | 13 | %hideText { 14 | @include hide-text(); 15 | } 16 | -------------------------------------------------------------------------------- /app/styles/scss/base/_mixins.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | 6 | // ---------------------------------------------------------------------- 7 | 8 | // Alignment 9 | 10 | // ---------------------------------------------------------------------- 11 | @import 'mixins/margin-auto'; 12 | @import 'mixins/horz-vert-center'; 13 | @import 'mixins/display'; 14 | 15 | // ---------------------------------------------------------------------- 16 | 17 | // Animation 18 | 19 | // ---------------------------------------------------------------------- 20 | @import 'mixins/animate-link'; 21 | @import 'mixins/animations'; 22 | @import 'mixins/backface-visibility'; 23 | @import 'mixins/keyframes'; 24 | @import 'mixins/single-transform'; 25 | @import 'mixins/transform'; 26 | @import 'mixins/transitions'; 27 | @import 'mixins/translate'; 28 | 29 | // ---------------------------------------------------------------------- 30 | 31 | // Functional 32 | 33 | // ---------------------------------------------------------------------- 34 | @import 'mixins/hide-text'; 35 | @import 'mixins/hover-focus'; 36 | @import 'mixins/replace-text'; 37 | 38 | // ---------------------------------------------------------------------- 39 | // ---------------------------------------------------------------------- 40 | 41 | // Gradients 42 | 43 | // ---------------------------------------------------------------------- 44 | @import 'mixins/linear-gradient'; 45 | @import 'mixins/linear-gradient-angle'; 46 | 47 | // ---------------------------------------------------------------------- 48 | 49 | // Layout 50 | 51 | // ---------------------------------------------------------------------- 52 | @import 'mixins/background-cover'; 53 | @import 'mixins/box-model'; 54 | @import 'mixins/clearfix'; 55 | @import 'mixins/inline-block'; 56 | 57 | // ---------------------------------------------------------------------- 58 | 59 | // Media Queries 60 | 61 | // ---------------------------------------------------------------------- 62 | @import 'mixins/breakpoint'; 63 | @import 'mixins/min-breakpoint'; 64 | @import 'mixins/retina'; 65 | 66 | // ---------------------------------------------------------------------- 67 | 68 | // Styles 69 | 70 | // ---------------------------------------------------------------------- 71 | @import 'mixins/border'; 72 | @import 'mixins/box-shadow'; 73 | @import 'mixins/inner-shadow'; 74 | @import 'mixins/opacity'; 75 | @import 'mixins/placeholder'; 76 | @import 'mixins/rounded-corners'; 77 | @import 'mixins/text-shadow'; 78 | @import 'mixins/triangles'; 79 | 80 | // ---------------------------------------------------------------------- 81 | 82 | // Values 83 | 84 | // ---------------------------------------------------------------------- 85 | @import 'mixins/rem'; 86 | 87 | // ---------------------------------------------------------------------- 88 | 89 | // FlexBox 90 | 91 | // ---------------------------------------------------------------------- 92 | @import 'mixins/flex'; 93 | @import 'mixins/align-items'; 94 | -------------------------------------------------------------------------------- /app/styles/scss/base/_variables.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------- 2 | // Font Sizes 3 | // ---------------------------------------------------------------------- 4 | $regularSize: 14; 5 | 6 | // ---------------------------------------------------------------------- 7 | // Font Families 8 | // ---------------------------------------------------------------------- 9 | $roboto: 'Roboto', sans-serif; 10 | 11 | // ---------------------------------------------------------------------- 12 | // styles 13 | // ---------------------------------------------------------------------- 14 | $appBgColor: #fff !default; 15 | 16 | $appSecondaryColor: #007af5 !default; 17 | 18 | $nativeSystemColor: #ececec !default; 19 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_align-items.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------- 2 | 3 | // Flex Align Items 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | @mixin align-items($value) { 8 | -webkit-box-align: $value; 9 | -moz-box-align: $value; 10 | -ms-flex-align: $value; 11 | -webkit-align-items: $value; 12 | align-items: $value; 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_animate-link.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Animated link that has a fade-in underline 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include animate-link($screenGreen, $gothamMedium, 14); 14 | 15 | @mixin animate-link($color, $font, $fontSize) { 16 | font-family: $font; 17 | 18 | @include single-transition(border, 0.2s, ease-in-out, 0); 19 | 20 | text-decoration: none; 21 | color: $color; 22 | border-bottom: 1px solid transparent; 23 | 24 | @include rem('font-size', $fontSize); 25 | 26 | &:focus, 27 | &:hover { 28 | border-color: $color; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_animations.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Animations 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include animation('slide-down 5s 3'); 14 | 15 | @mixin animation($str) { 16 | -webkit-animation: #{$str}; 17 | -moz-animation: #{$str}; 18 | -ms-animation: #{$str}; 19 | -o-animation: #{$str}; 20 | animation: #{$str}; 21 | } 22 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_backface-visibility.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Backface-visibility 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include backface-visibility("hidden"); 14 | 15 | @mixin backface-visibility($value) { 16 | -webkit-backface-visibility: $value; 17 | -moz-backface-visibility: $value; 18 | backface-visibility: $value; 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_background-cover.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Background cover 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include background-cover(); 14 | 15 | @mixin background-cover() { 16 | -webkit-background-size: cover; 17 | -moz-background-size: cover; 18 | -o-background-size: cover; 19 | background-size: cover; 20 | } 21 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_border.scss: -------------------------------------------------------------------------------- 1 | @mixin border-radius($value) { 2 | -moz-border-radius: $value; 3 | -webkit-border-radius: $value; 4 | border-radius: $value; 5 | } 6 | 7 | @mixin border($value) { 8 | border: $value; 9 | } 10 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_box-model.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Box Model 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | @mixin box-sizing($box-model) { 14 | -webkit-box-sizing: $box-model; 15 | -moz-box-sizing: $box-model; 16 | box-sizing: $box-model; 17 | } 18 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_box-shadow.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Box Shadow 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include box-shadow(1px, 2px, 2px, 2px, #000); 14 | 15 | @mixin box-shadow( 16 | $hoff: false, 17 | $voff: false, 18 | $blur: false, 19 | $spread: false, 20 | $color: false 21 | ) { 22 | -webkit-box-shadow: $hoff $voff $blur $spread $color; 23 | -moz-box-shadow: $hoff $voff $blur $spread $color; 24 | box-shadow: $hoff $voff $blur $spread $color; 25 | } 26 | 27 | @mixin box-shadow-1($value) { 28 | -webkit-box-shadow: $value; 29 | -moz-box-shadow: $value; 30 | box-shadow: $value; 31 | } 32 | 33 | @mixin box-shadow-2($value1, $value2, $value3) { 34 | -webkit-box-shadow: $value1, $value2, $value3; 35 | -moz-box-shadow: $value1, $value2, $value3; 36 | box-shadow: $value1, $value2, $value3; 37 | } 38 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_breakpoint.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Media Query Breakpoints 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example @include breakpoint(940) { width:80%; } 14 | 15 | @mixin breakpoint($size) { 16 | @media only screen and (max-width: $size + px) { 17 | @content; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Clearfix after element 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include clearfix(); 14 | 15 | @mixin clearfix() { 16 | & { 17 | *zoom: 1; 18 | } 19 | &::before, 20 | &::after { 21 | content: ''; 22 | display: table; 23 | } 24 | &::after { 25 | clear: both; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_display.scss: -------------------------------------------------------------------------------- 1 | @mixin display($value) { 2 | display: $value; 3 | } 4 | 5 | @mixin display-none($important) { 6 | @if $important { 7 | $important: !important; 8 | } 9 | 10 | display: none $important; 11 | } 12 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_display_flex.scss: -------------------------------------------------------------------------------- 1 | @mixin display_flex() { 2 | display: -webkit-box; 3 | display: -moz-box; 4 | display: -ms-flexbox; 5 | display: -webkit-flex; 6 | display: flex; 7 | } 8 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_hide-text.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Hide Text 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include hide-text(); 14 | 15 | @mixin hide-text() { 16 | position: relative; 17 | text-indent: -99999px; 18 | display: inline-block; 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_horz-vert-center.scss: -------------------------------------------------------------------------------- 1 | // example: @include horz-vert-center(); 2 | 3 | @mixin horz-vert-center($alignItem: true) { 4 | display: flex; 5 | justify-content: center; 6 | flex-direction: column; 7 | @if $alignItem { 8 | align-items: center; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_hover-focus.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ----------------------------------------------------------------------- 8 | 9 | // Hover and Focus 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example @include hoverFocus('text-decoration', 'none'); 14 | 15 | @mixin hoverFocus($property, $value) { 16 | &:hover, 17 | &:focus { 18 | #{$property}: $value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_inline-block.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Display inline block cross browser 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include inline-block(); 14 | 15 | /* stylelint-disable declaration-block-no-duplicate-properties */ 16 | 17 | @mixin inline-block() { 18 | display: -moz-inline-stack; 19 | display: inline-block; 20 | vertical-align: top; 21 | zoom: 1; 22 | *display: inline; 23 | } 24 | 25 | /* stylelint-enable declaration-block-no-duplicate-properties */ 26 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_inner-shadow.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Inner Shadow 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include inner-shadow(1px, 2px, 2px, #000); 14 | 15 | @mixin inner-shadow($hoff: false, $voff: false, $blur: false, $color: false) { 16 | -webkit-box-shadow: inset $hoff $voff $blur $color; 17 | -moz-box-shadow: inset $hoff $voff $blur $color; 18 | box-shadow: inset $hoff $voff $blur $color; 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_keyframes.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Keyframes 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include keyframes(slide-down) {0%{ opacity:1; } 90%{ opacity:0; }} 14 | 15 | @mixin keyframes($animation-name) { 16 | @-webkit-keyframes #{$animation-name} { 17 | @content; 18 | } 19 | @-moz-keyframes #{$animation-name} { 20 | @content; 21 | } 22 | @-ms-keyframes #{$animation-name} { 23 | @content; 24 | } 25 | @-o-keyframes #{$animation-name} { 26 | @content; 27 | } 28 | @keyframes #{$animation-name} { 29 | @content; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_linear-gradient-angle.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Linear Gradient angle 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include linear-gradient(-10, #cccccc, #333333); 14 | 15 | @mixin linear-gradient($angle, $colorStart, $colorStop) { 16 | background: #{$colorStart}; /* Old browsers */ 17 | background: -moz-linear-gradient( 18 | $angle, 19 | #{$colorStart} 0%, 20 | #{$colorStop} 100% 21 | ); /* FF3.6+ */ 22 | 23 | background: -webkit-gradient( 24 | linear, 25 | left bottom, 26 | right top, 27 | color-stop(0%, #{$colorStart}), 28 | color-stop(100%, #{$colorStop}) 29 | ); /* Chrome,Safari4+ */ 30 | 31 | background: -webkit-linear-gradient( 32 | 45deg, 33 | #{$colorStart} 0%, 34 | #{$colorStop} 100% 35 | ); /* Chrome10+,Safari5.1+ */ 36 | 37 | background: -o-linear-gradient( 38 | 45deg, 39 | #{$colorStart} 0%, 40 | #{$colorStop} 100% 41 | ); /* Opera 11.10+ */ 42 | 43 | background: -ms-linear-gradient( 44 | 45deg, 45 | #{$colorStart} 0%, 46 | #{$colorStop} 100% 47 | ); /* IE10+ */ 48 | 49 | background: linear-gradient( 50 | 45deg, 51 | #{$colorStart} 0%, 52 | #{$colorStop} 100% 53 | ); /* W3C */ 54 | 55 | filter: progid:dximagetransform.microsoft.gradient( startColorstr='#{$colorStart}', endColorstr='#{$colorStop}',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ 56 | } 57 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_linear-gradient.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ----------------------------------------------------------------------- 8 | 9 | // Linear Gradients 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include linearGradient(#cccccc, #333333); 14 | 15 | @mixin linearGradient($top, $bottom) { 16 | background: #{$top}; /* Old browsers */ 17 | background: -moz-linear-gradient( 18 | top, 19 | #{$top} 0%, 20 | #{$bottom} 100% 21 | ); /* FF3.6+ */ 22 | 23 | background: -webkit-gradient( 24 | linear, 25 | left top, 26 | left bottom, 27 | color-stop(0%, #{$top}), 28 | color-stop(100%, #{$bottom}) 29 | ); /* Chrome,Safari4+ */ 30 | 31 | background: -webkit-linear-gradient( 32 | top, 33 | #{$top} 0%, 34 | #{$bottom} 100% 35 | ); /* Chrome10+,Safari5.1+ */ 36 | 37 | background: -o-linear-gradient( 38 | top, 39 | #{$top} 0%, 40 | #{$bottom} 100% 41 | ); /* Opera 11.10+ */ 42 | 43 | background: -ms-linear-gradient(top, #{$top} 0%, #{$bottom} 100%); /* IE10+ */ 44 | background: linear-gradient(to bottom, #{$top} 0%, #{$bottom} 100%); /* W3C */ 45 | filter: progid:dximagetransform.microsoft.gradient( startColorstr='#{$top}', endColorstr='#{$bottom}', GradientType=0 ); /* IE6-9 */ 46 | } 47 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_margin-auto.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Margin auto 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include margin-auto(); 14 | 15 | @mixin margin-auto() { 16 | margin-left: auto; 17 | margin-right: auto; 18 | } 19 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_mediumFont.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------- 2 | 3 | // Medium Font 4 | 5 | // ---------------------------------------------------------------------- 6 | 7 | // example @include medium-font(); 8 | 9 | @mixin medium-font() { 10 | font-weight: bold; 11 | letter-spacing: 0.4px; 12 | } 13 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_min-breakpoint.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Media Query Breakpoints 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example @include min-breakpoint(940) { width:80%; } 14 | 15 | @mixin min-breakpoint($size) { 16 | @media only screen and (min-width: $size + px) { 17 | @content; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_opacity.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ----------------------------------------------------------------------- 8 | 9 | // Opacity 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | @mixin opacity($opacity) { 14 | opacity: $opacity; 15 | $opacity-ie: $opacity * 100; 16 | 17 | filter: alpha(opacity=$opacity-ie); //IE8 18 | } 19 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_placeholder.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Change placeholder text color 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include placeholder-color(#333); 14 | 15 | @mixin placeholder-color($color) { 16 | &.placeholder { 17 | color: $color; 18 | } 19 | 20 | &:-moz-placeholder { 21 | color: $color; 22 | } 23 | 24 | &::-webkit-input-placeholder { 25 | color: $color; 26 | } 27 | 28 | &:-ms-input-placeholder { 29 | color: $color; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_rem.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // REM Units with PX fallback 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include rem("margin", 10, 5, 10, 5); 14 | // example: @include rem("font-size", 14); 15 | 16 | @mixin rem($property, $values...) { 17 | $n: length($values); 18 | $i: 1; 19 | 20 | $pxlist: (); 21 | $remlist: (); 22 | 23 | @while $i <= $n { 24 | $itemVal: (nth($values, $i)); 25 | @if $itemVal != 'auto' { 26 | $pxlist: append($pxlist, $itemVal + px); 27 | //$remlist: append($remlist, ($itemVal / 10) + rem); // Use this if you've set HTML font size value to 62.5% 28 | $remlist: append($remlist, ($itemVal / 16) + rem); 29 | } @else { 30 | $pxlist: append($pxlist, auto); 31 | $remlist: append($remlist, auto); 32 | } 33 | 34 | $i: $i + 1; 35 | } 36 | 37 | #{$property}: $pxlist; 38 | #{$property}: $remlist; 39 | } 40 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_replace-text.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Replace text 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include replace-text(); 14 | 15 | /* stylelint-disable font-family-no-missing-generic-family-keyword */ 16 | 17 | @mixin replace-text() { 18 | border: 0; 19 | color: transparent; 20 | font: 0/0 a; 21 | text-shadow: none; 22 | } 23 | 24 | /* stylelint-enable font-family-no-missing-generic-family-keyword */ 25 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_retina.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ----------------------------------------------------------------------- 8 | 9 | // Retina Images 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include retina("logo2x.png", 100, 50); 14 | 15 | /* stylelint-disable media-feature-name-no-unknown */ 16 | 17 | @mixin retina($image, $width, $height) { 18 | @media (min--moz-device-pixel-ratio: 1.3), 19 | (-o-min-device-pixel-ratio: 2.6/2), 20 | (-webkit-min-device-pixel-ratio: 1.3), 21 | (min-device-pixel-ratio: 1.3), 22 | (min-resolution: 1.3dppx) { 23 | background-image: url('#{$image}'); 24 | background-size: $width + px $height + px; 25 | //background-size: $width / 10 + rem $height / 10 + rem; // Use this if you've set HTML font size value to 62.5% 26 | background-size: $width / 16 + rem $height / 16 + rem; 27 | } 28 | } 29 | 30 | /* stylelint-enable media-feature-name-no-unknown */ 31 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_rounded-corners.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Rounded Corners 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include roundedCorners(10); 14 | 15 | @mixin roundedCorners($size) { 16 | -webkit-border-radius: $size + px; 17 | -moz-border-radius: $size + px; 18 | border-radius: $size + px; 19 | } 20 | 21 | // Rounded Corners Top Only 22 | @mixin roundedTop($size) { 23 | -webkit-border-radius: $size + px $size + px 0 0; 24 | -moz-border-radius: $size + px $size + px 0 0; 25 | border-radius: $size + px $size + px 0 0; 26 | } 27 | 28 | // Rounded Corner Top Left Only 29 | @mixin roundedTopLeft($size) { 30 | -webkit-border-radius: $size + px 0 0 0; 31 | -moz-border-radius: $size + px 0 0 0; 32 | border-radius: $size + px 0 0 0; 33 | } 34 | 35 | // Rounded Corner Top Right Only 36 | @mixin roundedTopRight($size) { 37 | -webkit-border-radius: 0 $size + px 0 0; 38 | -moz-border-radius: 0 $size + px 0 0; 39 | border-radius: 0 $size + px 0 0; 40 | } 41 | 42 | // Rounded Corners Bottom Only 43 | @mixin roundedBottom($size) { 44 | -webkit-border-radius: 0 0 $size + px $size + px; 45 | -moz-border-radius: 0 0 $size + px $size + px; 46 | border-radius: 0 0 $size + px $size + px; 47 | } 48 | 49 | // Rounded Corner Bottom Left Only 50 | @mixin roundedBottomLeft($size) { 51 | -webkit-border-radius: 0 0 0 $size + px; 52 | -moz-border-radius: 0 0 0 $size + px; 53 | border-radius: 0 0 0 $size + px; 54 | } 55 | 56 | // Rounded Corner Bottom Right Only 57 | @mixin roundedBottomRight($size) { 58 | -webkit-border-radius: 0 0 $size + px 0; 59 | -moz-border-radius: 0 0 $size + px 0; 60 | border-radius: 0 0 $size + px 0; 61 | } 62 | 63 | // Rounded Corners Left Only 64 | @mixin roundedLeft($size) { 65 | -webkit-border-radius: 0 0 $size + px $size + px; 66 | -moz-border-radius: 0 0 $size + px $size + px; 67 | border-radius: $size + px 0 0 $size + px; 68 | } 69 | 70 | // Rounded Corners Right Only 71 | @mixin roundedRight($size) { 72 | -webkit-border-radius: 0 $size + px $size + px 0; 73 | -moz-border-radius: 0 $size + px $size + px 0; 74 | border-radius: 0 $size + px $size + px 0; 75 | } 76 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_single-transform.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Single Transform 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include flat-button($greyBlue, white, 5px 15px); 14 | 15 | @mixin single-transform($deg) { 16 | -ms-transform: rotate($deg); 17 | -webkit-transform: rotate($deg); 18 | transform: rotate($deg); 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_text-shadow.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Text Shadow 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include single-text-shadow(1px, 2px, 2px, #000); 14 | 15 | @mixin single-text-shadow( 16 | $hoff: false, 17 | $voff: false, 18 | $blur: false, 19 | $color: false 20 | ) { 21 | text-shadow: $hoff $voff $blur $color; 22 | } 23 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_transform.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Transform 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include transform("origin", 0, 0); 14 | 15 | @mixin transform($type, $values...) { 16 | $n: length($values); 17 | $i: 1; 18 | 19 | $originVal: (); 20 | 21 | @while $i <= $n { 22 | $itemVal: (nth($values, $i)); 23 | @if $type == 'rotate' or $type == 'rotateY' or $type == 'rotateX' { 24 | $originVal: append($originVal, $itemVal + deg); 25 | } @else { 26 | $originVal: append($originVal, $itemVal + px); 27 | } 28 | 29 | $i: $i + 1; 30 | } 31 | 32 | -webkit-transform: #{$type }($originVal); 33 | -moz-transform: #{$type }($originVal); 34 | transform: #{$type }($originVal); 35 | } 36 | 37 | @mixin transform-1($values) { 38 | -webkit-transform: $values; 39 | -ms-transform: $values; 40 | transform: $values; 41 | } 42 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_transitions.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Transitions 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include single-transition(background, 1s, ease-in-out, 0); 14 | 15 | @mixin single-transition($property, $duration, $timing-function, $delay) { 16 | -webkit-transition: $property $duration $timing-function $delay; 17 | -moz-transition: $property $duration $timing-function $delay; 18 | -o-transition: $property $duration $timing-function $delay; 19 | transition: $property $duration $timing-function $delay; 20 | } 21 | 22 | // example: @include double-transition(background, 1s, ease-in-out, 0, opacity, .1s, ease-in-out, 0); 23 | 24 | @mixin double-transition( 25 | $property1, 26 | $duration1, 27 | $timing-function1, 28 | $delay1, 29 | $property2, 30 | $duration2, 31 | $timing-function2, 32 | $delay2 33 | ) { 34 | -webkit-transition: $property1 $duration1 $timing-function1 $delay1, 35 | $property2 $duration2 $timing-function2 $delay2; 36 | -moz-transition: $property1 $duration1 $timing-function1 $delay1, 37 | $property2 $duration2 $timing-function2 $delay2; 38 | -o-transition: $property1 $duration1 $timing-function1 $delay1, 39 | $property2 $duration2 $timing-function2 $delay2; 40 | transition: $property1 $duration1 $timing-function1 $delay1, 41 | $property2 $duration2 $timing-function2 $delay2; 42 | } 43 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_translate.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Translate 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include translate(0); 14 | 15 | @mixin translate($value) { 16 | -webkit-transform: translateZ($value); 17 | -moz-transform: translateZ($value); 18 | -ms-transform: translateZ($value); 19 | -o-transform: translateZ($value); 20 | transform: translateZ($value); 21 | } 22 | -------------------------------------------------------------------------------- /app/styles/scss/base/mixins/_triangles.scss: -------------------------------------------------------------------------------- 1 | //------ SASS Useful Mixins --------------------------------------------- 2 | 3 | // by Ryan Burgess 4 | // https://github.com/ryanburgess/SASS-Useful-Mixins 5 | // MIT © Ryan Burgess 6 | 7 | // ---------------------------------------------------------------------- 8 | 9 | // Arrows / Triangles 10 | 11 | // ---------------------------------------------------------------------- 12 | 13 | // example: @include arrow("left", #cccccc, 10); 14 | 15 | /* stylelint-disable declaration-block-no-duplicate-properties */ 16 | 17 | @mixin arrow($direction, $color, $size) { 18 | $pxSize: $size + px; 19 | $remSize: ($size / 10) + rem; 20 | 21 | width: 0; 22 | height: 0; 23 | 24 | @if $direction == 'left' { 25 | border-top: $pxSize solid transparent; 26 | border-right: $pxSize solid $color; 27 | border-bottom: $pxSize solid transparent; 28 | border-top: $remSize solid transparent; 29 | border-right: $remSize solid $color; 30 | border-bottom: $remSize solid transparent; 31 | } @else if $direction == 'right' { 32 | border-top: $pxSize solid transparent; 33 | border-bottom: $pxSize solid transparent; 34 | border-left: $pxSize solid $color; 35 | border-top: $remSize solid transparent; 36 | border-bottom: $remSize solid transparent; 37 | border-left: $remSize solid $color; 38 | } @else if $direction == 'up' { 39 | border-left: $pxSize solid transparent; 40 | border-right: $pxSize solid transparent; 41 | border-bottom: $pxSize solid $color; 42 | border-left: $remSize solid transparent; 43 | border-right: $remSize solid transparent; 44 | border-bottom: $remSize solid $color; 45 | } @else if $direction == 'down' { 46 | border-left: $pxSize solid transparent; 47 | border-right: $pxSize solid transparent; 48 | border-top: $pxSize solid $color; 49 | border-left: $remSize solid transparent; 50 | border-right: $remSize solid transparent; 51 | border-top: $remSize solid $color; 52 | } 53 | } 54 | 55 | /* stylelint-enable declaration-block-no-duplicate-properties */ 56 | -------------------------------------------------------------------------------- /app/styles/scss/themes/fonts.scss: -------------------------------------------------------------------------------- 1 | @import '~roboto-fontface/css/roboto/roboto-fontface.css'; 2 | -------------------------------------------------------------------------------- /app/styles/scss/themes/reset.scss: -------------------------------------------------------------------------------- 1 | body { 2 | width: 100%; 3 | background: $appBgColor !important; 4 | @include rem('font-size', $regularSize); 5 | 6 | font-family: $roboto; 7 | overflow-x: hidden; 8 | overflow-y: hidden; 9 | } 10 | 11 | a { 12 | cursor: pointer; 13 | color: $appSecondaryColor; 14 | } 15 | 16 | *::-webkit-scrollbar-track { 17 | -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0); 18 | border-radius: 0; 19 | background-color: #fff; 20 | } 21 | 22 | *::-webkit-scrollbar { 23 | width: 8px; 24 | height: 8px; 25 | opacity: 1; 26 | transition: background-color 0.3s; 27 | } 28 | 29 | *::-webkit-scrollbar-thumb:hover { 30 | background-color: #135ba1; 31 | } 32 | 33 | *::-webkit-scrollbar-thumb { 34 | border-radius: 0; 35 | -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0); 36 | background-color: #1976d2; 37 | } 38 | -------------------------------------------------------------------------------- /app/templates/generateErrorReport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { APP_NAME, APP_VERSION, AUTHOR_EMAIL } from '../constants/meta'; 4 | 5 | const subject = `Error Logs for ${APP_NAME} | Version: ${APP_VERSION}`; 6 | const body = subject; 7 | 8 | export const mailTo = `mailto:${AUTHOR_EMAIL}?Subject=${subject}&Body=${body}.`; 9 | 10 | export const mailToInstructions = zippedLogFileBaseName => 11 | `%0D%0A %0D%0A Attach the generated error report file "${zippedLogFileBaseName}" (which is found in your Desktop folder) along with this email.`; 12 | 13 | export const reportGenerateError = `Error report generation failed. Try again!`; 14 | -------------------------------------------------------------------------------- /app/templates/loadProfileError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { APP_NAME, APP_VERSION, AUTHOR_EMAIL } from '../constants/meta'; 4 | 5 | export const loadProfileErrorHtml = ` 6 | 7 | 8 |

Unable to load profile files. Please restart the app.

9 |

Write to the developer if the problem persists.

10 | ${AUTHOR_EMAIL} 11 | 12 | 13 | `; 14 | -------------------------------------------------------------------------------- /app/templates/menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { APP_GITHUB_URL, APP_NAME, APP_DESC } from '../constants/meta'; 4 | 5 | const inviteViaEmailSubject = APP_NAME; 6 | 7 | const inviteViaEmailBody = `Looking for an advanced Electron Boilerplate? %0D%0A Download "${APP_NAME}" - ${APP_DESC} from ${APP_GITHUB_URL} for free. %0D%0A It's Safe, Transparent, Open-Source and FREE for a lifetime!`; 8 | 9 | export const inviteViaEmail = `mailto:?Subject=${inviteViaEmailSubject}&Body=${inviteViaEmailBody}`; 10 | -------------------------------------------------------------------------------- /app/templates/privacyPolicyPage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const PRIVACY_POLICY_PAGE_TITLE = `Privacy Policy`; 4 | -------------------------------------------------------------------------------- /app/utils/analyticsHelper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Analytics from 'electron-ga'; 4 | import { TRACKING_ID } from '../../config/google-analytics-key'; 5 | import { APP_NAME, APP_VERSION } from '../constants/meta'; 6 | 7 | export const analytics = new Analytics(TRACKING_ID, { 8 | appName: APP_NAME, 9 | appVersion: APP_VERSION 10 | }); 11 | -------------------------------------------------------------------------------- /app/utils/binaries.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import { remote } from 'electron'; 5 | import { getPlatform } from './getPlatform'; 6 | import { IS_PROD } from '../constants/env'; 7 | import { PATHS } from './paths'; 8 | import { isPackaged } from './isPackaged'; 9 | 10 | const { root } = PATHS; 11 | const { getAppPath } = remote.app; 12 | 13 | const binariesPath = 14 | IS_PROD && isPackaged 15 | ? path.join(path.dirname(getAppPath()), '..', './Resources', './bin') 16 | : path.join(root, './build', getPlatform(), './bin'); 17 | 18 | export const someBinaryPath = path.resolve( 19 | path.join(binariesPath, './native-binary') 20 | ); 21 | -------------------------------------------------------------------------------- /app/utils/bootHelper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Boot from '../classes/Boot'; 4 | 5 | export const bootLoader = new Boot(); 6 | -------------------------------------------------------------------------------- /app/utils/createWindows.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { BrowserWindow, remote } from 'electron'; 4 | import { PATHS } from './paths'; 5 | import { log } from './log'; 6 | import { loadProfileErrorHtml } from '../templates/loadProfileError'; 7 | import { APP_TITLE } from '../constants/meta'; 8 | import { undefinedOrNull } from './funcs'; 9 | import { PRIVACY_POLICY_PAGE_TITLE } from '../templates/privacyPolicyPage'; 10 | 11 | let _nonBootableDeviceWindow = null; 12 | let _reportBugsWindow = null; 13 | let _privacyPolicyWindow = null; 14 | 15 | /** 16 | * Non Bootable Device Window 17 | */ 18 | 19 | export const getMainWindow = () => { 20 | const _mainWindow = BrowserWindow.getAllWindows(); 21 | if (typeof _mainWindow === 'undefined' || _mainWindow === null) { 22 | return null; 23 | } 24 | 25 | return BrowserWindow.getAllWindows()[_mainWindow.length - 1]; 26 | }; 27 | 28 | const nonBootableDeviceCreateWindow = () => { 29 | return new BrowserWindow({ 30 | title: `${APP_TITLE}`, 31 | center: true, 32 | show: false, 33 | maximizable: false, 34 | minimizable: false, 35 | width: 480, 36 | height: 320, 37 | resizable: false, 38 | webPreferences: { 39 | nodeIntegration: true 40 | } 41 | }); 42 | }; 43 | 44 | export const nonBootableDeviceWindow = () => { 45 | _nonBootableDeviceWindow = nonBootableDeviceCreateWindow(); 46 | _nonBootableDeviceWindow.loadURL( 47 | `data:text/html;charset=utf-8, ${encodeURI(loadProfileErrorHtml)}` 48 | ); 49 | 50 | _nonBootableDeviceWindow.webContents.on('did-finish-load', () => { 51 | if (!_nonBootableDeviceWindow) { 52 | throw new Error(`"nonBootableDeviceWindow" is not defined`); 53 | } 54 | if (process.env.START_MINIMIZED) { 55 | _nonBootableDeviceWindow.minimize(); 56 | } else { 57 | _nonBootableDeviceWindow.show(); 58 | _nonBootableDeviceWindow.focus(); 59 | } 60 | }); 61 | 62 | _nonBootableDeviceWindow.on('closed', () => { 63 | _nonBootableDeviceWindow = null; 64 | }); 65 | 66 | return _nonBootableDeviceWindow; 67 | }; 68 | 69 | /** 70 | * Report Bugs Window 71 | */ 72 | 73 | const reportBugsCreateWindow = () => { 74 | return new BrowserWindow({ 75 | height: 480, 76 | width: 600, 77 | show: false, 78 | resizable: false, 79 | title: `${APP_TITLE}`, 80 | minimizable: false, 81 | fullscreenable: false, 82 | webPreferences: { 83 | nodeIntegration: true 84 | } 85 | }); 86 | }; 87 | 88 | export const reportBugsWindow = () => { 89 | try { 90 | if (_reportBugsWindow) { 91 | _reportBugsWindow.focus(); 92 | _reportBugsWindow.show(); 93 | return _reportBugsWindow; 94 | } 95 | 96 | _reportBugsWindow = reportBugsCreateWindow(); 97 | 98 | _reportBugsWindow.loadURL(`${PATHS.loadUrlPath}#reportBugsPage`); 99 | _reportBugsWindow.webContents.on('did-finish-load', () => { 100 | _reportBugsWindow.show(); 101 | _reportBugsWindow.focus(); 102 | }); 103 | 104 | _reportBugsWindow.onerror = error => { 105 | log.error(error, `createWindows -> reportBugsWindow -> onerror`); 106 | }; 107 | 108 | _reportBugsWindow.on('closed', () => { 109 | _reportBugsWindow = null; 110 | }); 111 | 112 | return _reportBugsWindow; 113 | } catch (e) { 114 | log.error(e, `createWindows -> reportBugsWindow`); 115 | } 116 | }; 117 | 118 | /** 119 | * Privacy Policy Window 120 | */ 121 | 122 | const privacyPolicyCreateWindow = isRenderedPage => { 123 | const config = { 124 | width: 800, 125 | height: 600, 126 | minWidth: 600, 127 | minHeight: 400, 128 | show: false, 129 | resizable: true, 130 | title: `${APP_TITLE}`, 131 | minimizable: true, 132 | fullscreenable: true, 133 | webPreferences: { 134 | nodeIntegration: true 135 | } 136 | }; 137 | 138 | // incoming call from a rendered page 139 | if (isRenderedPage) { 140 | const allWindows = remote.BrowserWindow.getAllWindows(); 141 | 142 | return loadExistingWindow(allWindows, PRIVACY_POLICY_PAGE_TITLE) 143 | ? null 144 | : new remote.BrowserWindow(config); 145 | } 146 | 147 | // incoming call from the main process 148 | const allWindows = BrowserWindow.getAllWindows(); 149 | 150 | return loadExistingWindow(allWindows, PRIVACY_POLICY_PAGE_TITLE) 151 | ? null 152 | : new BrowserWindow(config); 153 | }; 154 | 155 | export const privacyPolicyWindow = (isRenderedPage = false) => { 156 | try { 157 | if (_privacyPolicyWindow) { 158 | _privacyPolicyWindow.focus(); 159 | _privacyPolicyWindow.show(); 160 | return _privacyPolicyWindow; 161 | } 162 | 163 | // show the existing _privacyPolicyWindow 164 | const _privacyPolicyWindowTemp = privacyPolicyCreateWindow(isRenderedPage); 165 | if (!_privacyPolicyWindowTemp) { 166 | return _privacyPolicyWindow; 167 | } 168 | 169 | _privacyPolicyWindow = _privacyPolicyWindowTemp; 170 | _privacyPolicyWindow.loadURL(`${PATHS.loadUrlPath}#privacyPolicyPage`); 171 | _privacyPolicyWindow.webContents.on('did-finish-load', () => { 172 | _privacyPolicyWindow.show(); 173 | _privacyPolicyWindow.focus(); 174 | }); 175 | 176 | _privacyPolicyWindow.onerror = error => { 177 | log.error(error, `createWindows -> privacyPolicyWindow -> onerror`); 178 | }; 179 | 180 | _privacyPolicyWindow.on('closed', () => { 181 | _privacyPolicyWindow = null; 182 | }); 183 | 184 | return _privacyPolicyWindow; 185 | } catch (e) { 186 | log.error(e, `createWindows -> privacyPolicyWindow`); 187 | } 188 | }; 189 | 190 | const loadExistingWindow = (allWindows, title) => { 191 | if (!undefinedOrNull(allWindows)) { 192 | for (let i = 0; i < allWindows.length; i += 1) { 193 | const item = allWindows[i]; 194 | if (item.getTitle().indexOf(title) !== -1) { 195 | item.focus(); 196 | item.show(); 197 | 198 | return item; 199 | } 200 | } 201 | } 202 | 203 | return null; 204 | }; 205 | -------------------------------------------------------------------------------- /app/utils/date.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import moment from 'moment'; 4 | 5 | export const dateNow = ({ monthInletters = false }) => { 6 | let monthFormat = `MM`; 7 | if (monthInletters) { 8 | monthFormat = `MMM`; 9 | } 10 | 11 | return moment().format(`YYYY-${monthFormat}-DD`); 12 | }; 13 | 14 | export const yearMonthNow = ({ monthInletters = false }) => { 15 | let monthFormat = `MM`; 16 | if (monthInletters) { 17 | monthFormat = `MMM`; 18 | } 19 | 20 | return moment().format(`YYYY-${monthFormat}`); 21 | }; 22 | 23 | export const dateTimeNow = ({ monthInletters = false }) => { 24 | let monthFormat = `MM`; 25 | if (monthInletters) { 26 | monthFormat = `MMM`; 27 | } 28 | 29 | return moment().format(`YYYY-${monthFormat}-DD HH:mm:ss`); 30 | }; 31 | 32 | export const dateTimeUnixTimestampNow = ({ monthInletters = false }) => { 33 | let monthFormat = `MM`; 34 | if (monthInletters) { 35 | monthFormat = `MMM`; 36 | } 37 | 38 | return moment().format(`YYYY-${monthFormat}-DD HH:mm:ss (x)`); 39 | }; 40 | 41 | export const unixTimestampNow = () => { 42 | return moment().format(`x`); 43 | }; 44 | 45 | export const msToTime = milliseconds => { 46 | const hours = milliseconds / (1000 * 60 * 60); 47 | const absoluteHours = Math.floor(hours); 48 | const h = absoluteHours > 9 ? absoluteHours : `0${absoluteHours}`; 49 | 50 | const minutes = (hours - absoluteHours) * 60; 51 | const absoluteMinutes = Math.floor(minutes); 52 | const m = absoluteMinutes > 9 ? absoluteMinutes : `0${absoluteMinutes}`; 53 | 54 | const seconds = (minutes - absoluteMinutes) * 60; 55 | const absoluteSeconds = Math.floor(seconds); 56 | const s = absoluteSeconds > 9 ? absoluteSeconds : `0${absoluteSeconds}`; 57 | 58 | return `${h}:${m}:${s}`; 59 | }; 60 | 61 | export const daysDiff = (startDate, endDate) => { 62 | const start = moment(startDate, 'YYYY-MM'); 63 | const end = moment(endDate, 'YYYY-MM'); 64 | 65 | return moment.duration(start.diff(end)).asDays(); 66 | }; 67 | -------------------------------------------------------------------------------- /app/utils/eventHandling.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import EventEmitter from 'events'; 4 | import util from 'util'; 5 | 6 | export default function EmitAppEvents() { 7 | EventEmitter.call(this); 8 | } 9 | 10 | util.inherits(EmitAppEvents, EventEmitter); 11 | 12 | export const appEvents = new EmitAppEvents(); 13 | -------------------------------------------------------------------------------- /app/utils/funcs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const isArraysEqual = (a, b) => { 4 | if (a === b) { 5 | return true; 6 | } 7 | if (a == null || b == null) { 8 | return false; 9 | } 10 | if (a.length !== b.length) { 11 | return false; 12 | } 13 | 14 | for (let i = 0; i < a.length; i += 1) { 15 | if (a[i] !== b[i]) { 16 | return false; 17 | } 18 | } 19 | 20 | return true; 21 | }; 22 | 23 | export const isInt = n => { 24 | if (typeof n !== 'number') { 25 | return false; 26 | } 27 | return Number(n) === n && n % 1 === 0; 28 | }; 29 | 30 | export const isFloat = n => { 31 | if (typeof n !== 'number') { 32 | return false; 33 | } 34 | return Number(n) === n && n % 1 !== 0; 35 | }; 36 | 37 | export const isNumber = n => { 38 | return typeof n === 'number'; 39 | }; 40 | 41 | export const isArray = n => { 42 | return Array.isArray(n); 43 | }; 44 | 45 | export const niceBytes = (a, b) => { 46 | if (a === 0) { 47 | return '0 Bytes'; 48 | } 49 | const c = 1024; 50 | const d = b || 2; 51 | const e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 52 | const f = Math.floor(Math.log(a) / Math.log(c)); 53 | return `${parseFloat((a / Math.pow(c, f)).toFixed(d))} ${e[f]}`; // eslint-disable-line no-restricted-properties 54 | }; 55 | 56 | export const replaceBulk = (str, findArray, replaceArray) => { 57 | let i; 58 | let regex = []; 59 | const map = {}; 60 | for (i = 0; i < findArray.length; i += 1) { 61 | regex.push(findArray[i].replace(/([-[\]{}()*+?.\\^$|#,])/g, '\\$1')); 62 | map[findArray[i]] = replaceArray[i]; 63 | } 64 | regex = regex.join('|'); 65 | return str.replace(new RegExp(regex, 'g'), matched => { 66 | return map[matched]; 67 | }); 68 | }; 69 | 70 | export const splitIntoLines = str => { 71 | return str.split(/(\r?\n)/g); 72 | }; 73 | 74 | export const quickHash = str => { 75 | let hash = 0; 76 | let i; 77 | let chr; 78 | 79 | if (str.length === 0) { 80 | return hash; 81 | } 82 | for (i = 0; i < str.length; i += 1) { 83 | chr = str.charCodeAt(i); 84 | hash = (hash << 5) - hash + chr; // eslint-disable-line no-bitwise 85 | hash |= 0; // eslint-disable-line no-bitwise 86 | } 87 | return hash; 88 | }; 89 | 90 | export const percentage = (current, total) => { 91 | return parseInt((current / total) * 100, 10); 92 | }; 93 | 94 | export const truncate = (str, length) => { 95 | const dots = str.length > length ? '...' : ''; 96 | return str.substring(0, length) + dots; 97 | }; 98 | 99 | export const stripRootSlash = str => { 100 | return str.replace(/^\//g, ''); 101 | }; 102 | 103 | export const springTruncate = (str, minChars = 10, ellipsis = '...') => { 104 | const _str = str; 105 | const strLength = str.length; 106 | if (strLength > minChars) { 107 | const ellipsisLength = ellipsis.length; 108 | 109 | if (ellipsisLength > minChars) { 110 | return { 111 | text: _str, 112 | truncatedText: str.substr(strLength - minChars), 113 | isTruncated: true 114 | }; 115 | } 116 | 117 | const count = -0.5 * (minChars - strLength - ellipsisLength); 118 | const center = strLength / 2; 119 | 120 | return { 121 | text: _str, 122 | truncatedText: `${str.substr(0, center - count)}${ellipsis}${str.substr( 123 | strLength - center + count 124 | )}`, 125 | isTruncated: true 126 | }; 127 | } 128 | 129 | return { 130 | text: _str, 131 | truncatedText: str, 132 | isTruncated: false 133 | }; 134 | }; 135 | 136 | export const undefinedOrNull = _var => { 137 | return typeof _var === 'undefined' || _var === null; 138 | }; 139 | 140 | /** 141 | * Chained object validator 142 | * Validates the chained object values 143 | * @returns {{encode: string, decode: string}} 144 | * @param mainObj: object or array 145 | * @param key: string 146 | */ 147 | export const undefinedOrNullChained = (mainObj, key = null) => { 148 | const _return = undefined; 149 | 150 | if (typeof mainObj === 'undefined' || !mainObj) { 151 | return _return; 152 | } 153 | 154 | if ( 155 | !key || 156 | (Object.prototype.toString.call(key) !== '[object String]' && 157 | key.trim() === '') 158 | ) { 159 | return _return; 160 | } 161 | 162 | const keyArray = key.split('.'); 163 | 164 | if (!keyArray || keyArray.length < 1) { 165 | return _return; 166 | } 167 | let temp = mainObj; 168 | 169 | keyArray.map(a => { 170 | if (typeof temp !== 'undefined') { 171 | const _matches = a.match(/[^[\]]+(?=])/g); 172 | 173 | if (_matches && _matches.length > 0) { 174 | const aSplits = a.split('[')[0]; 175 | let lTemp = temp[aSplits]; 176 | 177 | _matches.map(e => { 178 | if (typeof lTemp !== 'undefined' && typeof lTemp[e] !== 'undefined') { 179 | lTemp = lTemp[e]; 180 | return lTemp; 181 | } 182 | lTemp = undefined; 183 | 184 | return lTemp; 185 | }); 186 | temp = lTemp; 187 | 188 | return temp; 189 | } 190 | if (typeof temp[a] !== 'undefined') { 191 | temp = temp[a]; 192 | return temp; 193 | } 194 | } 195 | 196 | temp = _return; 197 | 198 | return temp; 199 | }); 200 | 201 | return typeof temp !== 'undefined' ? temp : _return; 202 | }; 203 | 204 | export const diffObj = (obj1, obj2) => { 205 | let isSame = true; 206 | // eslint-disable-next-line no-restricted-syntax 207 | for (const p in obj1) { 208 | if (typeof obj1[p] === 'object') { 209 | const objectValue1 = obj1[p]; 210 | const objectValue2 = obj2[p]; 211 | // eslint-disable-next-line no-restricted-syntax, guard-for-in 212 | for (const value in objectValue1) { 213 | isSame = diffObj(objectValue1[value], objectValue2[value]); 214 | if (isSame === false) { 215 | return false; 216 | } 217 | } 218 | } else if (obj1 !== obj2) { 219 | isSame = false; 220 | } 221 | } 222 | return isSame; 223 | }; 224 | -------------------------------------------------------------------------------- /app/utils/getPlatform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { platform } from 'os'; 4 | 5 | export const getPlatform = () => { 6 | switch (platform()) { 7 | case 'aix': 8 | case 'freebsd': 9 | case 'linux': 10 | case 'openbsd': 11 | case 'android': 12 | return 'linux'; 13 | case 'darwin': 14 | case 'sunos': 15 | return 'mac'; 16 | case 'win32': 17 | return 'win'; 18 | default: 19 | return null; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /app/utils/gzip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import zlib from 'zlib'; 4 | import { createReadStream, createWriteStream } from 'fs'; 5 | import { log } from './log'; 6 | 7 | export const compressFile = (_input, _output) => { 8 | try { 9 | const gzip = zlib.createGzip(); 10 | const input = createReadStream(_input); 11 | const output = createWriteStream(_output); 12 | 13 | input.pipe(gzip).pipe(output); 14 | 15 | return true; 16 | } catch (e) { 17 | log.error(e, `gzip -> compressFile`); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /app/utils/imgsrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint global-require: off, prefer-template: off */ 4 | 5 | /** 6 | * handle image import into the program. 7 | * default path: ../public/images/ 8 | * @param filePath 9 | * @param returnNoImageFound (optional) 10 | * @returns {*} 11 | */ 12 | export const imgsrc = (filePath, returnNoImageFound = true) => { 13 | try { 14 | return require('../public/images/' + filePath); 15 | } catch (e) { 16 | if (!returnNoImageFound) { 17 | return null; 18 | } 19 | return require('../public/images/no-image.png'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /app/utils/isOnline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Promise from 'bluebird'; 4 | import dns from 'dns'; 5 | import { log } from './log'; 6 | 7 | export const isConnected = () => { 8 | try { 9 | return new Promise(resolve => { 10 | dns.lookup('github.com', err => { 11 | if (err && err.code === 'ENOTFOUND') { 12 | resolve(false); 13 | return null; 14 | } 15 | resolve(true); 16 | }); 17 | }); 18 | } catch (e) { 19 | log.error(e, `isOnline -> isConnected`); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /app/utils/isPackaged.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const isPackaged = 4 | process.mainModule.filename.indexOf('app.asar') !== -1; 5 | -------------------------------------------------------------------------------- /app/utils/log.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import os, { EOL } from 'os'; 4 | import { IS_DEV } from '../constants/env'; 5 | import { APP_NAME, APP_VERSION } from '../constants/meta'; 6 | import { PATHS } from './paths'; 7 | import { appendFileAsync } from '../api/sys/fileOps'; 8 | import { dateTimeUnixTimestampNow } from './date'; 9 | 10 | const { logFile } = PATHS; 11 | 12 | export const log = { 13 | info(e, title = `Log`, logError = false, allowInProd = false) { 14 | this.doLog(`Info title: ${title}${EOL}Info body: ${e}${EOL}`, logError); 15 | 16 | if (allowInProd) { 17 | console.info(`${title} => `, e); 18 | return; 19 | } 20 | if (IS_DEV) { 21 | console.info(`${title} => `, e); 22 | } 23 | }, 24 | 25 | error(e, title = `Log`, logError = true, allowInProd = false) { 26 | let _consoleError = e; 27 | if (isConsoleError(e)) { 28 | _consoleError = `Error Stack:${EOL}${JSON.stringify(e.stack)}${EOL}`; 29 | } 30 | 31 | this.doLog( 32 | `Error title: ${title}${EOL}Error body: ${EOL}${_consoleError.toString()}${EOL}`, 33 | logError 34 | ); 35 | 36 | if (allowInProd) { 37 | console.error(`${title} => `, e); 38 | return; 39 | } 40 | if (IS_DEV) { 41 | console.error(`${title} => `, e); 42 | } 43 | }, 44 | 45 | doLog(e, logError = true, consoleError = null) { 46 | if (logError === false) { 47 | return null; 48 | } 49 | 50 | const sectionSeperator = `=============================================================`; 51 | let _consoleError = e; 52 | if (isConsoleError(e)) { 53 | _consoleError = `Error Stack:${EOL}${JSON.stringify(e.stack)}${EOL}`; 54 | } 55 | 56 | if (isConsoleError(consoleError)) { 57 | _consoleError += `Error Stack:${EOL}${JSON.stringify( 58 | consoleError.stack 59 | )}${EOL}`; 60 | } 61 | 62 | appendFileAsync( 63 | logFile, 64 | `${sectionSeperator}${EOL}${EOL}App Name: ${APP_NAME}${EOL}App Version: ${APP_VERSION}${EOL}Date Time: ${dateTimeUnixTimestampNow( 65 | { 66 | monthInletters: true 67 | } 68 | )}${EOL}OS type: ${os.type()} / OS Platform: ${os.platform()} / OS Release: ${os.release()}${EOL}${_consoleError.toString()}${EOL}${_consoleError}${EOL}${sectionSeperator}${EOL}` 69 | ); 70 | } 71 | }; 72 | 73 | const isConsoleError = e => { 74 | return e && e.stack; 75 | }; 76 | -------------------------------------------------------------------------------- /app/utils/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Paths 5 | * Note: Don't import log helper file from utils here 6 | */ 7 | 8 | import { join, parse, resolve } from 'path'; 9 | import { homedir as homedirOs } from 'os'; 10 | import url from 'url'; 11 | import { rootPath as root } from 'electron-root-path'; 12 | import { isPackaged } from './isPackaged'; 13 | import { IS_DEV } from '../constants/env'; 14 | import { yearMonthNow } from './date'; 15 | import { APP_IDENTIFIER, APP_NAME } from '../constants/meta'; 16 | 17 | const appPath = join(root, `./app`); 18 | const configDir = join(root, `./config`); 19 | const homeDir = homedirOs(); 20 | const profileDir = join(homeDir, `./.io.ganeshrvel`, `${APP_IDENTIFIER}`); 21 | const rotateFile = yearMonthNow({}); 22 | const logFileName = IS_DEV 23 | ? `error-${rotateFile}.dev.log` 24 | : `error-${rotateFile}.log`; 25 | const logDir = join(profileDir, `./logs`); 26 | const logFile = join(logDir, `./${APP_NAME}-${logFileName}`); 27 | const settingsFile = join(profileDir, `./settings.json`); 28 | const appUpdateFile = join(configDir, `./dev-app-update.yml`); 29 | 30 | export const PATHS = { 31 | root: resolve(root), 32 | app: resolve(appPath), 33 | dist: resolve(join(appPath, `./dist`)), 34 | nodeModules: resolve(join(root, `./node_modules`)), 35 | homeDir: resolve(homeDir), 36 | profileDir: resolve(profileDir), 37 | configDir: resolve(configDir), 38 | logDir: resolve(logDir), 39 | logFile: resolve(logFile), 40 | settingsFile: resolve(settingsFile), 41 | appUpdateFile: resolve(appUpdateFile), 42 | loadUrlPath: url.format({ 43 | protocol: 'file', 44 | slashes: true, 45 | pathname: !isPackaged 46 | ? join(appPath, './app.html') 47 | : join(__dirname, './app.html') 48 | }) 49 | }; 50 | 51 | export const pathUp = filePath => { 52 | return filePath.replace(/\/$/, '').replace(/\/[^/]+$/, '') || '/'; 53 | }; 54 | 55 | export const sanitizePath = filePath => { 56 | return filePath.replace(/\/\/+/g, '/'); 57 | }; 58 | 59 | export const baseName = filePath => { 60 | if (typeof filePath === 'undefined' || filePath === null) { 61 | return null; 62 | } 63 | const parsedPath = pathInfo(filePath); 64 | 65 | return parsedPath !== null ? parsedPath.base : null; 66 | }; 67 | 68 | export const getExtension = (fileName, isFolder) => { 69 | if (isFolder) { 70 | return null; 71 | } 72 | const parsedPath = pathInfo(fileName); 73 | 74 | return parsedPath !== null ? parsedPath.ext : null; 75 | }; 76 | 77 | export const pathInfo = filePath => { 78 | return parse(filePath); 79 | }; 80 | -------------------------------------------------------------------------------- /app/utils/pkginfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { join } from 'path'; 4 | import { readFileSync } from 'fs'; 5 | import { rootPath } from 'electron-root-path'; 6 | 7 | let _pkginfo = {}; 8 | 9 | // eslint-disable-next-line no-undef 10 | if (typeof PKG_INFO !== 'undefined' && PKG_INFO !== null) { 11 | // eslint-disable-next-line no-undef 12 | _pkginfo = PKG_INFO; 13 | } else { 14 | _pkginfo = JSON.parse( 15 | readFileSync(join(rootPath, 'package.json'), { encoding: 'utf8' }) 16 | ); 17 | } 18 | 19 | export const pkginfo = _pkginfo; 20 | -------------------------------------------------------------------------------- /app/utils/reducerPrefixer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default (prefix, typesList) => { 4 | return typesList.reduce((result, value) => { 5 | // eslint-disable-next-line no-param-reassign 6 | result[value] = `${prefix}/${value}`; 7 | return result; 8 | }, {}); 9 | }; 10 | -------------------------------------------------------------------------------- /app/utils/storageHelper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { PATHS } from './paths'; 4 | import Storage from '../classes/Storage'; 5 | 6 | const { settingsFile } = PATHS; 7 | 8 | export const settingsStorage = new Storage(settingsFile); 9 | -------------------------------------------------------------------------------- /app/utils/styleResets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const resetOverFlowY = () => { 4 | if (typeof document !== 'undefined' && document) { 5 | document.body.style.overflowY = 'auto'; 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /app/utils/titlebarDoubleClick.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { remote } from 'electron'; 4 | 5 | export const toggleWindowSizeOnDoubleClick = () => { 6 | const window = remote.getCurrentWindow(); 7 | if (!window.isMaximized()) { 8 | window.maximize(); 9 | return null; 10 | } 11 | window.unmaximize(); 12 | }; 13 | -------------------------------------------------------------------------------- /app/utils/url.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { shell } from 'electron'; 4 | 5 | export const openExternalUrl = (url, events = null) => { 6 | if (events) { 7 | events.preventDefault(); 8 | } 9 | shell.openExternal(url); 10 | }; 11 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint global-require: off */ 4 | 5 | const developmentEnvironments = ['development', 'test']; 6 | const developmentPlugins = [require('react-hot-loader/babel')]; 7 | const productionPlugins = [ 8 | require('babel-plugin-dev-expression'), 9 | require('@babel/plugin-transform-react-constant-elements'), 10 | require('@babel/plugin-transform-react-inline-elements'), 11 | require('babel-plugin-transform-react-remove-prop-types') 12 | ]; 13 | 14 | module.exports = api => { 15 | const development = api.env(developmentEnvironments); 16 | 17 | return { 18 | presets: [ 19 | [ 20 | require('@babel/preset-env'), 21 | { 22 | targets: { 23 | electron: require('electron/package.json').version 24 | }, 25 | useBuiltIns: 'usage' 26 | } 27 | ], 28 | [require('@babel/preset-react'), { development }] 29 | ], 30 | plugins: [ 31 | // Stage 0 32 | require('@babel/plugin-proposal-function-bind'), 33 | 34 | // Stage 1 35 | require('@babel/plugin-proposal-export-default-from'), 36 | require('@babel/plugin-proposal-logical-assignment-operators'), 37 | [require('@babel/plugin-proposal-optional-chaining'), { loose: false }], 38 | [ 39 | require('@babel/plugin-proposal-pipeline-operator'), 40 | { proposal: 'minimal' } 41 | ], 42 | [ 43 | require('@babel/plugin-proposal-nullish-coalescing-operator'), 44 | { loose: false } 45 | ], 46 | require('@babel/plugin-proposal-do-expressions'), 47 | 48 | // Stage 2 49 | [require('@babel/plugin-proposal-decorators'), { legacy: true }], 50 | require('@babel/plugin-proposal-function-sent'), 51 | require('@babel/plugin-proposal-export-namespace-from'), 52 | require('@babel/plugin-proposal-numeric-separator'), 53 | require('@babel/plugin-proposal-throw-expressions'), 54 | 55 | // Stage 3 56 | require('@babel/plugin-syntax-dynamic-import'), 57 | require('@babel/plugin-syntax-import-meta'), 58 | [require('@babel/plugin-proposal-class-properties'), { loose: true }], 59 | require('@babel/plugin-proposal-json-strings'), 60 | ...(development ? developmentPlugins : productionPlugins) 61 | ] 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icon.ico -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icon.png -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/24x24.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/48x48.png -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/64x64.png -------------------------------------------------------------------------------- /build/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/icons/96x96.png -------------------------------------------------------------------------------- /build/mac/bin/.git-keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshrvel/electron-react-redux-advanced-boilerplate/0f0ea31e91067169fcc3efc51e1ece03ca1b4bd8/build/mac/bin/.git-keep -------------------------------------------------------------------------------- /build/sample.entitlements.mas.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | . 9 | com.apple.security.network.client 10 | 11 | com.apple.security.device.usb 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | com.apple.security.files.user-selected.read-only 16 | 17 | com.apple.security.files.downloads.read-write 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /config/dev-app-update.yml: -------------------------------------------------------------------------------- 1 | # change this to match your repo details 2 | owner: ganeshrvel 3 | repo: electron-react-redux-advanced-boilerplate 4 | provider: github 5 | -------------------------------------------------------------------------------- /config/env/env.dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.PORT = 3642; 4 | -------------------------------------------------------------------------------- /config/env/env.prod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.PORT = 3642; 4 | -------------------------------------------------------------------------------- /config/env/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */ 4 | const IS_PROD = require('../../app/constants/env').IS_PROD; 5 | 6 | const PORT_PROD = require('./env.prod').PORT; 7 | 8 | const PORT_DEV = require('./env.dev').PORT; 9 | 10 | module.exports.PORT = process.env.PORT || IS_PROD ? PORT_PROD : PORT_DEV; 11 | -------------------------------------------------------------------------------- /config/google-analytics-key.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // change this to match your Google Analytics UA 4 | export const TRACKING_ID = `UA-XXXXXXX-X`; 5 | -------------------------------------------------------------------------------- /internals/scripts/AfterPack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const glob = require('glob'); 5 | const fs = require('fs-extra'); 6 | 7 | exports.default = context => { 8 | // clean languages unnecessary folder from packed app 9 | const lprojRegEx = /(en)\.lproj/g; 10 | const APP_NAME = context.packager.appInfo.productFilename; 11 | const APP_OUT_DIR = context.appOutDir; 12 | const PLATFORM = context.packager.platform.name; 13 | 14 | const cwd = path.join(`${APP_OUT_DIR}`, `${APP_NAME}.app/Contents/Resources`); 15 | const lproj = glob.sync('*.lproj', { cwd }); 16 | const _promises = []; 17 | 18 | switch (PLATFORM) { 19 | case 'mac': 20 | lproj.forEach(dir => { 21 | if (!lprojRegEx.test(dir)) { 22 | _promises.push(fs.remove(path.join(cwd, dir))); 23 | } 24 | }); 25 | 26 | break; 27 | default: 28 | break; 29 | } 30 | 31 | return Promise.all(_promises); 32 | }; 33 | -------------------------------------------------------------------------------- /internals/scripts/CheckBuiltsExist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Check if the renderer and main bundles are built 4 | import path from 'path'; 5 | import chalk from 'chalk'; 6 | import fs from 'fs'; 7 | 8 | function CheckBuildsExist() { 9 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js'); 10 | const rendererPath = path.join( 11 | __dirname, 12 | '..', 13 | '..', 14 | 'app', 15 | 'dist', 16 | 'renderer.prod.js' 17 | ); 18 | 19 | if (!fs.existsSync(mainPath)) { 20 | throw new Error( 21 | chalk.whiteBright.bgRed.bold( 22 | 'The main process is not built yet. Build it by running "yarn build-main"' 23 | ) 24 | ); 25 | } 26 | 27 | if (!fs.existsSync(rendererPath)) { 28 | throw new Error( 29 | chalk.whiteBright.bgRed.bold( 30 | 'The renderer process is not built yet. Build it by running "yarn build-renderer"' 31 | ) 32 | ); 33 | } 34 | } 35 | 36 | CheckBuildsExist(); 37 | -------------------------------------------------------------------------------- /internals/scripts/CheckNodeEnv.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import chalk from 'chalk'; 4 | 5 | export default function CheckNodeEnv(expectedEnv) { 6 | if (!expectedEnv) { 7 | throw new Error('"expectedEnv" not set'); 8 | } 9 | 10 | if (process.env.NODE_ENV !== expectedEnv) { 11 | console.info( 12 | chalk.whiteBright.bgRed.bold( 13 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 14 | ) 15 | ); 16 | process.exit(2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internals/scripts/CheckPortInUse.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import chalk from 'chalk'; 4 | import detectPort from 'detect-port'; 5 | import { PORT } from '../../config/env'; 6 | 7 | (function CheckPortInUse() { 8 | const _port = PORT.toString(); 9 | 10 | detectPort(_port, (err, availablePort) => { 11 | if (_port !== String(availablePort)) { 12 | throw new Error( 13 | chalk.whiteBright.bgRed.bold( 14 | // eslint-disable-next-line prefer-template 15 | 'Port "' + 16 | _port + 17 | '" on "localhost" is already in use. Please use another port.' 18 | ) 19 | ); 20 | } else { 21 | process.exit(0); 22 | } 23 | }); 24 | })(); 25 | -------------------------------------------------------------------------------- /internals/scripts/CheckYarn.js: -------------------------------------------------------------------------------- 1 | if (!/yarn\.js$/.test(process.env.npm_execpath || '')) { 2 | console.warn( 3 | "\u001b[33mYou don't seem to be using yarn. This could produce unexpected results.\u001b[39m" 4 | ); 5 | } 6 | -------------------------------------------------------------------------------- /webpack/config.base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint global-require: off */ 4 | 5 | /** 6 | * Base webpack config used across other specific configs 7 | */ 8 | 9 | import path from 'path'; 10 | import webpack from 'webpack'; 11 | import { PATHS } from '../app/utils/paths'; 12 | 13 | const pkg = require('../package.json'); 14 | 15 | export default { 16 | externals: [...Object.keys(pkg.dependencies || {})], 17 | 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js|jsx)?$/, 22 | exclude: /node_modules/, 23 | use: { 24 | loader: 'babel-loader', 25 | options: { 26 | cacheDirectory: true 27 | } 28 | } 29 | } 30 | ] 31 | }, 32 | 33 | output: { 34 | path: PATHS.app, 35 | // https://github.com/webpack/webpack/issues/1114 36 | libraryTarget: 'commonjs2' 37 | }, 38 | 39 | /** 40 | * Determine the array of extensions that should be used to resolve modules. 41 | */ 42 | resolve: { 43 | extensions: ['.js', '.jsx', '.json'], 44 | alias: { 45 | '@Log': path.resolve(__dirname, '../app/utils/log.js'), 46 | '@Alerts': path.resolve(__dirname, '../app/containers/Alerts/actions.js') 47 | } 48 | }, 49 | 50 | plugins: [ 51 | new webpack.EnvironmentPlugin({ 52 | NODE_ENV: 'production' 53 | }), 54 | 55 | new webpack.DefinePlugin({ 56 | PKG_INFO: { 57 | productName: JSON.stringify(pkg.productName), 58 | description: JSON.stringify(pkg.description), 59 | name: JSON.stringify(pkg.name), 60 | author: JSON.stringify(pkg.author), 61 | version: JSON.stringify(pkg.version), 62 | repository: JSON.stringify(pkg.repository), 63 | homepage: JSON.stringify(pkg.homepage), 64 | build: JSON.stringify(pkg.build) 65 | } 66 | }), 67 | 68 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 69 | 70 | new webpack.NamedModulesPlugin() 71 | ] 72 | }; 73 | -------------------------------------------------------------------------------- /webpack/config.eslint.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | require('@babel/register'); 3 | 4 | module.exports = require('./config.renderer.dev.babel').default; 5 | -------------------------------------------------------------------------------- /webpack/config.main.prod.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Webpack config for production electron main process 5 | */ 6 | 7 | import webpack from 'webpack'; 8 | import merge from 'webpack-merge'; 9 | import TerserPlugin from 'terser-webpack-plugin'; 10 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 11 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 12 | import baseConfig from './config.base'; 13 | import { PATHS } from '../app/utils/paths'; 14 | 15 | export default merge.smart(baseConfig, { 16 | devtool: 'source-map', 17 | mode: 'production', 18 | target: 'electron-main', 19 | entry: { 20 | client: ['./app/main.dev'] 21 | }, 22 | 23 | output: { 24 | path: PATHS.root, 25 | filename: './app/main.prod.js' 26 | }, 27 | 28 | optimization: { 29 | minimizer: [ 30 | new TerserPlugin({ 31 | parallel: true, 32 | sourceMap: true, 33 | cache: true 34 | }) 35 | ] 36 | }, 37 | 38 | plugins: [ 39 | new CleanWebpackPlugin([`${PATHS.dist}/*`], { 40 | root: PATHS.root 41 | }), 42 | 43 | new BundleAnalyzerPlugin({ 44 | analyzerMode: 45 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 46 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 47 | }), 48 | 49 | /** 50 | * Create global constants which can be configured at compile time. 51 | * 52 | * Useful for allowing different behaviour between development builds and 53 | * release builds 54 | * 55 | * NODE_ENV should be production so that modules do not perform certain 56 | * development checks 57 | */ 58 | new webpack.EnvironmentPlugin({ 59 | NODE_ENV: 'production', 60 | DEBUG_PROD: false, 61 | START_MINIMIZED: false 62 | }) 63 | ], 64 | 65 | /** 66 | * Disables webpack processing of __dirname and __filename. 67 | * If you run the bundle in node.js it falls back to these values of node.js. 68 | * https://github.com/webpack/webpack/issues/2010 69 | */ 70 | node: { 71 | __dirname: false, 72 | __filename: false, 73 | fs: 'empty' 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /webpack/config.renderer.dev.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 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 baseConfig from './config.base'; 17 | import { PATHS } from '../app/utils/paths'; 18 | import { PORT } from '../config/env'; 19 | 20 | const publicPath = `http://localhost:${PORT}/dist`; 21 | const dll = path.resolve(PATHS.root, 'dll'); 22 | const manifest = path.resolve(dll, 'renderer.json'); 23 | const requiredByDLLConfig = module.parent.filename.includes( 24 | 'config.renderer.dev.dll.babel' 25 | ); 26 | 27 | /** 28 | * Warn if the DLL is not built 29 | */ 30 | if (!requiredByDLLConfig && !(fs.existsSync(dll) && fs.existsSync(manifest))) { 31 | console.info( 32 | chalk.black.bgYellow.bold( 33 | 'The DLL files are missing. Sit back while we build them for you with "yarn build-dll"' 34 | ) 35 | ); 36 | execSync('yarn build-dll'); 37 | } 38 | 39 | export default merge.smart(baseConfig, { 40 | devtool: 'inline-source-map', 41 | mode: 'development', 42 | target: 'electron-renderer', 43 | entry: [ 44 | 'react-hot-loader/patch', 45 | `webpack-dev-server/client?http://localhost:${PORT}/`, 46 | 'webpack/hot/only-dev-server', 47 | path.join(PATHS.app, 'index.js') 48 | ], 49 | 50 | output: { 51 | publicPath: `http://localhost:${PORT}/dist/`, 52 | filename: 'renderer.dev.js' 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 | } 65 | } 66 | }, 67 | // Extract all .global.css to style.css as is 68 | { 69 | test: /\.global\.css$/, 70 | use: [ 71 | { 72 | loader: 'style-loader' 73 | }, 74 | { 75 | loader: 'css-loader', 76 | options: { 77 | sourceMap: true 78 | } 79 | } 80 | ] 81 | }, 82 | // Pipe other styles through css modules and append to style.css 83 | { 84 | test: /^((?!\.global).)*\.css$/, 85 | use: [ 86 | { 87 | loader: 'style-loader' 88 | }, 89 | { 90 | loader: 'css-loader', 91 | options: { 92 | modules: true, 93 | sourceMap: true, 94 | importLoaders: 1, 95 | localIdentName: '[name]__[local]__[hash:base64:5]' 96 | } 97 | } 98 | ] 99 | }, 100 | // SASS support - compile all .global.scss files and pipe it to style.css 101 | { 102 | test: /\.global\.(scss|sass)$/, 103 | use: [ 104 | { 105 | loader: 'style-loader' 106 | }, 107 | { 108 | loader: 'css-loader', 109 | options: { 110 | sourceMap: true 111 | } 112 | }, 113 | { 114 | loader: 'sass-loader' 115 | } 116 | ] 117 | }, 118 | // SASS support - compile all other .scss files and pipe it to style.css 119 | { 120 | test: /^((?!\.global).)*\.(scss|sass)$/, 121 | use: [ 122 | { 123 | loader: 'style-loader' 124 | }, 125 | { 126 | loader: 'css-loader', 127 | options: { 128 | modules: true, 129 | sourceMap: true, 130 | importLoaders: 1, 131 | localIdentName: '[name]__[local]__[hash:base64:5]' 132 | } 133 | }, 134 | { 135 | loader: 'sass-loader' 136 | } 137 | ] 138 | }, 139 | // WOFF Font 140 | { 141 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 142 | use: { 143 | loader: 'url-loader', 144 | options: { 145 | limit: 10000, 146 | mimetype: 'application/font-woff' 147 | } 148 | } 149 | }, 150 | // WOFF2 Font 151 | { 152 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 153 | use: { 154 | loader: 'url-loader', 155 | options: { 156 | limit: 10000, 157 | mimetype: 'application/font-woff' 158 | } 159 | } 160 | }, 161 | // TTF Font 162 | { 163 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 164 | use: { 165 | loader: 'url-loader', 166 | options: { 167 | limit: 10000, 168 | mimetype: 'application/octet-stream' 169 | } 170 | } 171 | }, 172 | // EOT Font 173 | { 174 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 175 | use: 'file-loader' 176 | }, 177 | // SVG Font 178 | { 179 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 180 | use: { 181 | loader: 'url-loader', 182 | options: { 183 | limit: 10000, 184 | mimetype: 'image/svg+xml' 185 | } 186 | } 187 | }, 188 | // Common Image Formats 189 | { 190 | test: /\.(?:ico|jpe?g|png|gif|webp)$/i, 191 | use: [ 192 | { 193 | loader: 'url-loader', 194 | options: { 195 | limit: 10000, 196 | name: 'images/[path][name].[hash].[ext]' 197 | } 198 | } 199 | ] 200 | } 201 | ] 202 | }, 203 | 204 | plugins: [ 205 | requiredByDLLConfig 206 | ? null 207 | : new webpack.DllReferencePlugin({ 208 | context: PATHS.root, 209 | manifest: require(manifest), // eslint-disable-line 210 | sourceType: 'var' 211 | }), 212 | 213 | new webpack.HotModuleReplacementPlugin({ 214 | multiStep: false 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.EnvironmentPlugin({ 232 | NODE_ENV: 'development' 233 | }), 234 | 235 | new webpack.LoaderOptionsPlugin({ 236 | debug: true 237 | }) 238 | ], 239 | 240 | node: { 241 | __dirname: false, 242 | __filename: false, 243 | fs: 'empty' 244 | }, 245 | 246 | devServer: { 247 | port: PORT, 248 | publicPath, 249 | compress: true, 250 | noInfo: true, 251 | stats: 'errors-only', 252 | inline: true, 253 | lazy: false, 254 | hot: true, 255 | headers: { 'Access-Control-Allow-Origin': '*' }, 256 | contentBase: path.join(PATHS.root, 'dist'), 257 | watchOptions: { 258 | aggregateTimeout: 300, 259 | ignored: /node_modules/, 260 | poll: 100 261 | }, 262 | historyApiFallback: { 263 | verbose: true, 264 | disableDotRule: false 265 | }, 266 | before() { 267 | if (process.env.START_HOT) { 268 | console.info('Starting Main Process...'); 269 | spawn('npm', ['run', 'start-main-dev'], { 270 | shell: true, 271 | env: process.env, 272 | stdio: 'inherit' 273 | }) 274 | .on('close', code => process.exit(code)) 275 | .on('error', spawnError => console.error(spawnError)); 276 | } 277 | } 278 | } 279 | }); 280 | -------------------------------------------------------------------------------- /webpack/config.renderer.dev.dll.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint global-require: off, */ 4 | 5 | /** 6 | * Builds the DLL for development electron renderer process 7 | */ 8 | 9 | import webpack from 'webpack'; 10 | import path from 'path'; 11 | import merge from 'webpack-merge'; 12 | import baseConfig from './config.base'; 13 | import { PATHS } from '../app/utils/paths'; 14 | 15 | const pkg = require('../package.json'); 16 | 17 | const dll = path.join(PATHS.root, 'dll'); 18 | 19 | export default merge.smart(baseConfig, { 20 | context: PATHS.root, 21 | devtool: 'eval', 22 | mode: 'development', 23 | target: 'electron-renderer', 24 | externals: ['fsevents', 'crypto-browserify'], 25 | 26 | /** 27 | * Use `module` from `config.renderer.dev.babel.js` 28 | */ 29 | module: require('./config.renderer.dev.babel').default.module, 30 | 31 | entry: { 32 | renderer: Object.keys(pkg.dependencies || {}) 33 | }, 34 | 35 | output: { 36 | library: 'renderer', 37 | path: dll, 38 | filename: '[name].dev.dll.js', 39 | libraryTarget: 'var' 40 | }, 41 | 42 | plugins: [ 43 | new webpack.DllPlugin({ 44 | path: path.join(dll, '[name].json'), 45 | name: '[name]' 46 | }), 47 | 48 | /** 49 | * Create global constants which can be configured at compile time. 50 | * 51 | * Useful for allowing different behaviour between development builds and 52 | * release builds 53 | * 54 | * NODE_ENV should be production so that modules do not perform certain 55 | * development checks 56 | */ 57 | new webpack.EnvironmentPlugin({ 58 | NODE_ENV: 'development' 59 | }), 60 | 61 | new webpack.LoaderOptionsPlugin({ 62 | debug: true, 63 | options: { 64 | context: PATHS.app, 65 | output: { 66 | path: dll 67 | } 68 | } 69 | }) 70 | ] 71 | }); 72 | -------------------------------------------------------------------------------- /webpack/config.renderer.prod.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Build config for electron renderer process 5 | */ 6 | 7 | import path from 'path'; 8 | import webpack from 'webpack'; 9 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 10 | import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'; 11 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 12 | import merge from 'webpack-merge'; 13 | import TerserPlugin from 'terser-webpack-plugin'; 14 | import baseConfig from './config.base'; 15 | import { PATHS } from '../app/utils/paths'; 16 | 17 | export default merge.smart(baseConfig, { 18 | devtool: 'source-map', 19 | mode: 'production', 20 | target: 'electron-renderer', 21 | entry: { 22 | client: ['./app/index.js'] 23 | }, 24 | 25 | output: { 26 | path: path.join(PATHS.app, 'dist'), 27 | publicPath: './dist/', 28 | filename: 'renderer.prod.js' 29 | }, 30 | 31 | module: { 32 | rules: [ 33 | // Extract all .global.css to style.css as is 34 | { 35 | test: /\.global\.css$/, 36 | use: [ 37 | { 38 | loader: MiniCssExtractPlugin.loader, 39 | options: { 40 | publicPath: './' 41 | } 42 | }, 43 | { 44 | loader: 'css-loader', 45 | options: { 46 | publicPath: './' 47 | // sourceMap: true 48 | } 49 | } 50 | ] 51 | }, 52 | // Pipe other styles through css modules and append to style.css 53 | { 54 | test: /^((?!\.global).)*\.css$/, 55 | use: [ 56 | { 57 | loader: MiniCssExtractPlugin.loader, 58 | options: { 59 | publicPath: './' 60 | } 61 | }, 62 | { 63 | loader: 'css-loader', 64 | options: { 65 | publicPath: './', 66 | modules: true, 67 | localIdentName: '[name]__[local]__[hash:base64:5]' 68 | // sourceMap: true 69 | } 70 | } 71 | ] 72 | }, 73 | // Add SASS support - compile all .global.scss files and pipe it to style.css 74 | { 75 | test: /\.global\.(scss|sass)$/, 76 | use: [ 77 | { 78 | loader: MiniCssExtractPlugin.loader, 79 | options: { 80 | publicPath: './' 81 | } 82 | }, 83 | { 84 | loader: 'css-loader', 85 | options: { 86 | publicPath: './', 87 | // sourceMap: true, 88 | importLoaders: 1 89 | } 90 | }, 91 | { 92 | loader: 'sass-loader', 93 | options: { 94 | publicPath: './' 95 | // sourceMap: true 96 | } 97 | } 98 | ] 99 | }, 100 | // Add SASS support - compile all other .scss files and pipe it to style.css 101 | { 102 | test: /^((?!\.global).)*\.(scss|sass)$/, 103 | use: [ 104 | { 105 | loader: MiniCssExtractPlugin.loader, 106 | options: { 107 | publicPath: './' 108 | } 109 | }, 110 | { 111 | loader: 'css-loader', 112 | options: { 113 | publicPath: './', 114 | modules: true, 115 | importLoaders: 1, 116 | localIdentName: '[name]__[local]__[hash:base64:5]' 117 | // sourceMap: true 118 | } 119 | }, 120 | { 121 | loader: 'sass-loader', 122 | options: { 123 | publicPath: './' 124 | // sourceMap: true 125 | } 126 | } 127 | ] 128 | }, 129 | // WOFF Font 130 | { 131 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 132 | use: { 133 | loader: 'url-loader', 134 | options: { 135 | publicPath: './', 136 | limit: 10000, // kb 137 | mimetype: 'application/font-woff', 138 | name: 'fonts/[name].[hash].[ext]' 139 | } 140 | } 141 | }, 142 | // WOFF2 Font 143 | { 144 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 145 | use: { 146 | loader: 'url-loader', 147 | options: { 148 | publicPath: './', 149 | limit: 10000, 150 | mimetype: 'application/font-woff', 151 | name: 'fonts/[name].[hash].[ext]' 152 | } 153 | } 154 | }, 155 | // TTF Font 156 | { 157 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 158 | use: { 159 | loader: 'url-loader', 160 | options: { 161 | publicPath: './', 162 | limit: 10000, 163 | mimetype: 'application/octet-stream', 164 | name: 'fonts/[name].[hash].[ext]' 165 | } 166 | } 167 | }, 168 | // EOT Font 169 | { 170 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 171 | use: { 172 | loader: 'file-loader', 173 | options: { 174 | publicPath: './', 175 | name: 'fonts/[name].[hash].[ext]' 176 | } 177 | } 178 | }, 179 | // SVG Font 180 | { 181 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 182 | use: { 183 | loader: 'url-loader', 184 | options: { 185 | publicPath: './', 186 | limit: 10000, 187 | mimetype: 'image/svg+xml', 188 | name: 'images/[path][name].[hash].[ext]' 189 | } 190 | } 191 | }, 192 | // Common Image Formats 193 | { 194 | test: /\.(?:ico|jpe?g|png|gif|webp)$/i, 195 | use: [ 196 | { 197 | loader: 'url-loader', 198 | options: { 199 | limit: 10000, 200 | name: 'images/[path][name].[hash].[ext]' 201 | } 202 | } 203 | ] 204 | } 205 | ] 206 | }, 207 | 208 | optimization: { 209 | minimizer: [ 210 | new TerserPlugin({ 211 | parallel: true, 212 | sourceMap: true, 213 | cache: true 214 | }), 215 | new OptimizeCSSAssetsPlugin({ 216 | cssProcessorOptions: { 217 | map: { 218 | inline: false, 219 | annotation: true 220 | } 221 | } 222 | }) 223 | ] 224 | }, 225 | 226 | plugins: [ 227 | /** 228 | * Create global constants which can be configured at compile time. 229 | * 230 | * Useful for allowing different behaviour between development builds and 231 | * release builds 232 | * 233 | * NODE_ENV should be production so that modules do not perform certain 234 | * development checks 235 | */ 236 | new webpack.EnvironmentPlugin({ 237 | NODE_ENV: 'production' 238 | }), 239 | 240 | new MiniCssExtractPlugin({ 241 | filename: 'style.css' 242 | }), 243 | 244 | new BundleAnalyzerPlugin({ 245 | analyzerMode: 246 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 247 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 248 | }) 249 | ], 250 | 251 | /** 252 | * Disables webpack processing of __dirname and __filename. 253 | * If you run the bundle in node.js it falls back to these values of node.js. 254 | * https://github.com/webpack/webpack/issues/2010 255 | */ 256 | node: { 257 | __dirname: false, 258 | __filename: false 259 | } 260 | }); 261 | --------------------------------------------------------------------------------