├── .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 |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 |