├── .editorconfig ├── .erb ├── configs │ ├── .eslintrc │ ├── webpack.config.base.ts │ ├── webpack.config.eslint.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.renderer.dev.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.paths.ts ├── img │ ├── erb-banner.svg │ ├── erb-logo.png │ └── palette-sponsor-banner.svg ├── mocks │ └── fileMock.js └── scripts │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ └── notarize.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── README.md ├── assets ├── assets.d.ts ├── entitlements.mac.plist ├── icon.icns ├── icon.ico ├── icon.png ├── icon.svg └── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png ├── offline-auth.gif ├── package-lock.json ├── package.json ├── preview.jpg ├── release └── app │ ├── database.db │ ├── package-lock.json │ ├── package.json │ └── yarn.lock ├── src ├── __tests__ │ └── App.test.tsx ├── main │ ├── main.ts │ ├── menu.ts │ ├── preload.ts │ ├── services │ │ └── Database.service.ts │ └── util.ts └── renderer │ ├── components │ ├── FAB │ │ ├── FAB.module.css │ │ └── index.tsx │ ├── Logo │ │ ├── Logo.module.css │ │ └── index.tsx │ ├── Modal │ │ ├── Modal.module.css │ │ └── index.tsx │ ├── Sidebar │ │ ├── Sidebar.module.css │ │ └── index.tsx │ ├── TaskArea │ │ ├── TaskArea.module.css │ │ └── index.tsx │ └── TaskItem │ │ ├── TaskItem.module.css │ │ └── index.tsx │ ├── index.ejs │ ├── index.tsx │ ├── preload.d.ts │ ├── routes.tsx │ ├── styles │ ├── App.global.css │ └── themes │ │ └── default.css │ └── views │ └── Home │ ├── Home.module.css │ └── index.tsx ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin'; 7 | import webpackPaths from './webpack.paths'; 8 | import { dependencies as externals } from '../../release/app/package.json'; 9 | 10 | const configuration: webpack.Configuration = { 11 | externals: [...Object.keys(externals || {})], 12 | 13 | stats: 'errors-only', 14 | 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.[jt]sx?$/, 19 | exclude: /node_modules/, 20 | use: { 21 | loader: 'ts-loader', 22 | options: { 23 | // Remove this line to enable type checking in webpack builds 24 | transpileOnly: true, 25 | compilerOptions: { 26 | module: 'esnext', 27 | }, 28 | }, 29 | }, 30 | }, 31 | ], 32 | }, 33 | 34 | output: { 35 | path: webpackPaths.srcPath, 36 | // https://github.com/webpack/webpack/issues/1114 37 | library: { 38 | type: 'commonjs2', 39 | }, 40 | }, 41 | 42 | /** 43 | * Determine the array of extensions that should be used to resolve modules. 44 | */ 45 | resolve: { 46 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 47 | modules: [webpackPaths.srcPath, 'node_modules'], 48 | // There is no need to add aliases here, the paths in tsconfig get mirrored 49 | plugins: [new TsconfigPathsPlugins()], 50 | }, 51 | 52 | plugins: [ 53 | new webpack.EnvironmentPlugin({ 54 | NODE_ENV: 'production', 55 | }), 56 | ], 57 | }; 58 | 59 | export default configuration; 60 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | library: { 34 | type: 'umd', 35 | }, 36 | }, 37 | 38 | optimization: { 39 | minimizer: [ 40 | new TerserPlugin({ 41 | parallel: true, 42 | }), 43 | ], 44 | }, 45 | 46 | plugins: [ 47 | new BundleAnalyzerPlugin({ 48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 49 | analyzerPort: 8888, 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'production', 63 | DEBUG_PROD: false, 64 | START_MINIMIZED: false, 65 | }), 66 | 67 | new webpack.DefinePlugin({ 68 | 'process.type': '"browser"', 69 | }), 70 | ], 71 | 72 | /** 73 | * Disables webpack processing of __dirname and __filename. 74 | * If you run the bundle in node.js it falls back to these values of node.js. 75 | * https://github.com/webpack/webpack/issues/2010 76 | */ 77 | node: { 78 | __dirname: false, 79 | __filename: false, 80 | }, 81 | }; 82 | 83 | export default merge(baseConfig, configuration); 84 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | library: { 28 | type: 'umd', 29 | }, 30 | }, 31 | 32 | plugins: [ 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | * 46 | * By default, use 'development' as NODE_ENV. This can be overriden with 47 | * 'staging', for example, by changing the ENV variables in the npm scripts 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'development', 51 | }), 52 | 53 | new webpack.LoaderOptionsPlugin({ 54 | debug: true, 55 | }), 56 | ], 57 | 58 | /** 59 | * Disables webpack processing of __dirname and __filename. 60 | * If you run the bundle in node.js it falls back to these values of node.js. 61 | * https://github.com/webpack/webpack/issues/2010 62 | */ 63 | node: { 64 | __dirname: false, 65 | __filename: false, 66 | }, 67 | 68 | watch: true, 69 | }; 70 | 71 | export default merge(baseConfig, configuration); 72 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.ts: -------------------------------------------------------------------------------- 1 | import 'webpack-dev-server'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import webpack from 'webpack'; 5 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 6 | import chalk from 'chalk'; 7 | import { merge } from 'webpack-merge'; 8 | import { execSync, spawn } from 'child_process'; 9 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | 14 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 15 | // at the dev webpack config is not accidentally run in a production environment 16 | if (process.env.NODE_ENV === 'production') { 17 | checkNodeEnv('development'); 18 | } 19 | 20 | const port = process.env.PORT || 1212; 21 | const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json'); 22 | const skipDLLs = 23 | module.parent?.filename.includes('webpack.config.renderer.dev.dll') || 24 | module.parent?.filename.includes('webpack.config.eslint'); 25 | 26 | /** 27 | * Warn if the DLL is not built 28 | */ 29 | if ( 30 | !skipDLLs && 31 | !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest)) 32 | ) { 33 | console.log( 34 | chalk.black.bgYellow.bold( 35 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 36 | ) 37 | ); 38 | execSync('npm run postinstall'); 39 | } 40 | 41 | const configuration: webpack.Configuration = { 42 | devtool: 'inline-source-map', 43 | 44 | mode: 'development', 45 | 46 | target: ['web', 'electron-renderer'], 47 | 48 | entry: [ 49 | `webpack-dev-server/client?http://localhost:${port}/dist`, 50 | 'webpack/hot/only-dev-server', 51 | path.join(webpackPaths.srcRendererPath, 'index.tsx'), 52 | ], 53 | 54 | output: { 55 | path: webpackPaths.distRendererPath, 56 | publicPath: '/', 57 | filename: 'renderer.dev.js', 58 | library: { 59 | type: 'umd', 60 | }, 61 | }, 62 | 63 | module: { 64 | rules: [ 65 | { 66 | test: /\.s?(c|a)ss$/, 67 | use: [ 68 | 'style-loader', 69 | { 70 | loader: 'css-loader', 71 | options: { 72 | modules: true, 73 | sourceMap: true, 74 | importLoaders: 1, 75 | }, 76 | }, 77 | 'sass-loader', 78 | ], 79 | include: /\.module\.s?(c|a)ss$/, 80 | }, 81 | { 82 | test: /\.s?css$/, 83 | use: ['style-loader', 'css-loader', 'sass-loader'], 84 | exclude: /\.module\.s?(c|a)ss$/, 85 | }, 86 | // Fonts 87 | { 88 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 89 | type: 'asset/resource', 90 | }, 91 | // Images 92 | { 93 | test: /\.(png|jpg|jpeg|gif)$/i, 94 | type: 'asset/resource', 95 | }, 96 | // SVG 97 | { 98 | test: /\.svg$/, 99 | use: [ 100 | { 101 | loader: '@svgr/webpack', 102 | options: { 103 | prettier: false, 104 | svgo: false, 105 | svgoConfig: { 106 | plugins: [{ removeViewBox: false }], 107 | }, 108 | titleProp: true, 109 | ref: true, 110 | }, 111 | }, 112 | 'file-loader', 113 | ], 114 | }, 115 | ], 116 | }, 117 | plugins: [ 118 | ...(skipDLLs 119 | ? [] 120 | : [ 121 | new webpack.DllReferencePlugin({ 122 | context: webpackPaths.dllPath, 123 | manifest: require(manifest), 124 | sourceType: 'var', 125 | }), 126 | ]), 127 | 128 | new webpack.NoEmitOnErrorsPlugin(), 129 | 130 | /** 131 | * Create global constants which can be configured at compile time. 132 | * 133 | * Useful for allowing different behaviour between development builds and 134 | * release builds 135 | * 136 | * NODE_ENV should be production so that modules do not perform certain 137 | * development checks 138 | * 139 | * By default, use 'development' as NODE_ENV. This can be overriden with 140 | * 'staging', for example, by changing the ENV variables in the npm scripts 141 | */ 142 | new webpack.EnvironmentPlugin({ 143 | NODE_ENV: 'development', 144 | }), 145 | 146 | new webpack.LoaderOptionsPlugin({ 147 | debug: true, 148 | }), 149 | 150 | new ReactRefreshWebpackPlugin(), 151 | 152 | new HtmlWebpackPlugin({ 153 | filename: path.join('index.html'), 154 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 155 | minify: { 156 | collapseWhitespace: true, 157 | removeAttributeQuotes: true, 158 | removeComments: true, 159 | }, 160 | isBrowser: false, 161 | env: process.env.NODE_ENV, 162 | isDevelopment: process.env.NODE_ENV !== 'production', 163 | nodeModules: webpackPaths.appNodeModulesPath, 164 | }), 165 | ], 166 | 167 | node: { 168 | __dirname: false, 169 | __filename: false, 170 | }, 171 | 172 | devServer: { 173 | port, 174 | compress: true, 175 | hot: true, 176 | headers: { 'Access-Control-Allow-Origin': '*' }, 177 | static: { 178 | publicPath: '/', 179 | }, 180 | historyApiFallback: { 181 | verbose: true, 182 | }, 183 | setupMiddlewares(middlewares) { 184 | console.log('Starting preload.js builder...'); 185 | const preloadProcess = spawn('npm', ['run', 'start:preload'], { 186 | shell: true, 187 | stdio: 'inherit', 188 | }) 189 | .on('close', (code: number) => process.exit(code!)) 190 | .on('error', (spawnError) => console.error(spawnError)); 191 | 192 | console.log('Starting Main Process...'); 193 | let args = ['run', 'start:main']; 194 | if (process.env.MAIN_ARGS) { 195 | args = args.concat( 196 | ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat() 197 | ); 198 | } 199 | spawn('npm', args, { 200 | shell: true, 201 | stdio: 'inherit', 202 | }) 203 | .on('close', (code: number) => { 204 | preloadProcess.kill(); 205 | process.exit(code!); 206 | }) 207 | .on('error', (spawnError) => console.error(spawnError)); 208 | return middlewares; 209 | }, 210 | }, 211 | }; 212 | 213 | export default merge(baseConfig, configuration); 214 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; 11 | import { merge } from 'webpack-merge'; 12 | import TerserPlugin from 'terser-webpack-plugin'; 13 | import baseConfig from './webpack.config.base'; 14 | import webpackPaths from './webpack.paths'; 15 | import checkNodeEnv from '../scripts/check-node-env'; 16 | import deleteSourceMaps from '../scripts/delete-source-maps'; 17 | 18 | checkNodeEnv('production'); 19 | deleteSourceMaps(); 20 | 21 | const configuration: webpack.Configuration = { 22 | devtool: 'source-map', 23 | 24 | mode: 'production', 25 | 26 | target: ['web', 'electron-renderer'], 27 | 28 | entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')], 29 | 30 | output: { 31 | path: webpackPaths.distRendererPath, 32 | publicPath: './', 33 | filename: 'renderer.js', 34 | library: { 35 | type: 'umd', 36 | }, 37 | }, 38 | 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.s?(a|c)ss$/, 43 | use: [ 44 | MiniCssExtractPlugin.loader, 45 | { 46 | loader: 'css-loader', 47 | options: { 48 | modules: true, 49 | sourceMap: true, 50 | importLoaders: 1, 51 | }, 52 | }, 53 | 'sass-loader', 54 | ], 55 | include: /\.module\.s?(c|a)ss$/, 56 | }, 57 | { 58 | test: /\.s?(a|c)ss$/, 59 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 60 | exclude: /\.module\.s?(c|a)ss$/, 61 | }, 62 | // Fonts 63 | { 64 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 65 | type: 'asset/resource', 66 | }, 67 | // Images 68 | { 69 | test: /\.(png|jpg|jpeg|gif)$/i, 70 | type: 'asset/resource', 71 | }, 72 | // SVG 73 | { 74 | test: /\.svg$/, 75 | use: [ 76 | { 77 | loader: '@svgr/webpack', 78 | options: { 79 | prettier: false, 80 | svgo: false, 81 | svgoConfig: { 82 | plugins: [{ removeViewBox: false }], 83 | }, 84 | titleProp: true, 85 | ref: true, 86 | }, 87 | }, 88 | 'file-loader', 89 | ], 90 | }, 91 | ], 92 | }, 93 | 94 | optimization: { 95 | minimize: true, 96 | minimizer: [new TerserPlugin(), new CssMinimizerPlugin()], 97 | }, 98 | 99 | plugins: [ 100 | /** 101 | * Create global constants which can be configured at compile time. 102 | * 103 | * Useful for allowing different behaviour between development builds and 104 | * release builds 105 | * 106 | * NODE_ENV should be production so that modules do not perform certain 107 | * development checks 108 | */ 109 | new webpack.EnvironmentPlugin({ 110 | NODE_ENV: 'production', 111 | DEBUG_PROD: false, 112 | }), 113 | 114 | new MiniCssExtractPlugin({ 115 | filename: 'style.css', 116 | }), 117 | 118 | new BundleAnalyzerPlugin({ 119 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 120 | analyzerPort: 8889, 121 | }), 122 | 123 | new HtmlWebpackPlugin({ 124 | filename: 'index.html', 125 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 126 | minify: { 127 | collapseWhitespace: true, 128 | removeAttributeQuotes: true, 129 | removeComments: true, 130 | }, 131 | isBrowser: false, 132 | isDevelopment: false, 133 | }), 134 | 135 | new webpack.DefinePlugin({ 136 | 'process.type': '"renderer"', 137 | }), 138 | ], 139 | }; 140 | 141 | export default merge(baseConfig, configuration); 142 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.erb/img/erb-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /.erb/img/palette-sponsor-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package' 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import { rimrafSync } from 'rimraf'; 2 | import fs from 'fs'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const foldersToRemove = [ 6 | webpackPaths.distPath, 7 | webpackPaths.buildPath, 8 | webpackPaths.dllPath, 9 | ]; 10 | 11 | foldersToRemove.forEach((folder) => { 12 | if (fs.existsSync(folder)) rimrafSync(folder); 13 | }); 14 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { rimrafSync } from 'rimraf'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | export default function deleteSourceMaps() { 7 | if (fs.existsSync(webpackPaths.distMainPath)) 8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), { 9 | glob: true, 10 | }); 11 | if (fs.existsSync(webpackPaths.distRendererPath)) 12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), { 13 | glob: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (process.env.CI !== 'true') { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 16 | console.warn( 17 | 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' 18 | ); 19 | return; 20 | } 21 | 22 | const appName = context.packager.appInfo.productFilename; 23 | 24 | await notarize({ 25 | appBundleId: build.appId, 26 | appPath: `${appOutDir}/${appName}.app`, 27 | appleId: process.env.APPLE_ID, 28 | appleIdPassword: process.env.APPLE_ID_PASS, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | plugins: ['@typescript-eslint'], 4 | rules: { 5 | // A temporary hack related to IDE not resolving correct package.json 6 | 'import/no-extraneous-dependencies': 'off', 7 | 'react/react-in-jsx-scope': 'off', 8 | 'react/jsx-filename-extension': 'off', 9 | 'import/extensions': 'off', 10 | 'import/no-unresolved': 'off', 11 | 'import/no-import-module-exports': 'off', 12 | 'no-shadow': 'off', 13 | '@typescript-eslint/no-shadow': 'error', 14 | 'no-unused-vars': 'off', 15 | '@typescript-eslint/no-unused-vars': 'error', 16 | }, 17 | parserOptions: { 18 | ecmaVersion: 2020, 19 | sourceType: 'module', 20 | project: './tsconfig.json', 21 | tsconfigRootDir: __dirname, 22 | createDefaultProgram: true, 23 | }, 24 | settings: { 25 | 'import/resolver': { 26 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 27 | node: {}, 28 | webpack: { 29 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 30 | }, 31 | typescript: {}, 32 | }, 33 | 'import/parsers': { 34 | '@typescript-eslint/parser': ['.ts', '.tsx'], 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: tuliocll 4 | ko_fi: tuliocll 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electron + React + SQLite - TODO APP 2 | 3 | This is a project created for the [Desktop Apps with Electron, React and SQLite](https://tuliocalil.com/desktop-apps-with-electron-react-and-sqlite/) post! 4 | This project uses React for the UI part, Better Sqlite to connect to the database and create the TODOs CRUD, CSS Modules for style and IPC to comunicate between Electron process. 5 | 6 |  7 | 8 | You can found the Offline Auth code from the [Offline auth with Electron + SQLite + React post](https://tuliocalil.com/offline-auth-with-electron-sqlite-react/) on the `feat/offline-auth` branch. 9 | 10 |  11 | 12 | ## Libs 13 | 14 | - React 15 | - Electron 16 | - Better-sqlite 17 | - React router dom 18 | 19 | ## Requirements 20 | 21 | - Nodejs 14+ 22 | 23 | Created using the [Electron React Boilerplate](https://electron-react-boilerplate.js.org/) 24 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | 6 | export const ReactComponent: React.FC>; 7 | 8 | const content: string; 9 | export default content; 10 | } 11 | 12 | declare module '*.png' { 13 | const content: string; 14 | export default content; 15 | } 16 | 17 | declare module '*.jpg' { 18 | const content: string; 19 | export default content; 20 | } 21 | 22 | declare module '*.scss' { 23 | const content: Styles; 24 | export default content; 25 | } 26 | 27 | declare module '*.sass' { 28 | const content: Styles; 29 | export default content; 30 | } 31 | 32 | declare module '*.css' { 33 | const content: Styles; 34 | export default content; 35 | } 36 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icon.png -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/24x24.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/assets/icons/96x96.png -------------------------------------------------------------------------------- /offline-auth.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/offline-auth.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "A foundation for scalable desktop apps", 3 | "keywords": [ 4 | "electron", 5 | "boilerplate", 6 | "react", 7 | "typescript", 8 | "ts", 9 | "sass", 10 | "webpack", 11 | "hot", 12 | "reload" 13 | ], 14 | "homepage": "https://github.com/electron-react-boilerplate/electron-react-boilerplate#readme", 15 | "bugs": { 16 | "url": "https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/electron-react-boilerplate/electron-react-boilerplate.git" 21 | }, 22 | "license": "MIT", 23 | "author": { 24 | "name": "Electron React Boilerplate Maintainers", 25 | "email": "electronreactboilerplate@gmail.com", 26 | "url": "https://electron-react-boilerplate.js.org" 27 | }, 28 | "contributors": [ 29 | { 30 | "name": "Amila Welihinda", 31 | "email": "amilajack@gmail.com", 32 | "url": "https://github.com/amilajack" 33 | } 34 | ], 35 | "main": "./src/main/main.ts", 36 | "scripts": { 37 | "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"", 38 | "build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts", 39 | "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts", 40 | "postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts", 41 | "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx", 42 | "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never", 43 | "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app", 44 | "start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer", 45 | "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .", 46 | "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts", 47 | "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", 48 | "test": "jest" 49 | }, 50 | "browserslist": [], 51 | "prettier": { 52 | "singleQuote": true, 53 | "overrides": [ 54 | { 55 | "files": [ 56 | ".prettierrc", 57 | ".eslintrc" 58 | ], 59 | "options": { 60 | "parser": "json" 61 | } 62 | } 63 | ] 64 | }, 65 | "jest": { 66 | "moduleDirectories": [ 67 | "node_modules", 68 | "release/app/node_modules", 69 | "src" 70 | ], 71 | "moduleFileExtensions": [ 72 | "js", 73 | "jsx", 74 | "ts", 75 | "tsx", 76 | "json" 77 | ], 78 | "moduleNameMapper": { 79 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.erb/mocks/fileMock.js", 80 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 81 | }, 82 | "setupFiles": [ 83 | "./.erb/scripts/check-build-exists.ts" 84 | ], 85 | "testEnvironment": "jsdom", 86 | "testEnvironmentOptions": { 87 | "url": "http://localhost/" 88 | }, 89 | "testPathIgnorePatterns": [ 90 | "release/app/dist", 91 | ".erb/dll" 92 | ], 93 | "transform": { 94 | "\\.(ts|tsx|js|jsx)$": "ts-jest" 95 | } 96 | }, 97 | "dependencies": { 98 | "date-fns": "^2.30.0", 99 | "electron-debug": "^3.2.0", 100 | "electron-log": "^4.4.8", 101 | "electron-updater": "^5.3.0", 102 | "react": "^18.2.0", 103 | "react-dom": "^18.2.0", 104 | "react-router-dom": "^6.11.2" 105 | }, 106 | "devDependencies": { 107 | "@electron/notarize": "^1.2.3", 108 | "@electron/rebuild": "^3.2.13", 109 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", 110 | "@svgr/webpack": "^8.0.1", 111 | "@teamsupercell/typings-for-css-modules-loader": "^2.5.2", 112 | "@testing-library/jest-dom": "^5.16.5", 113 | "@testing-library/react": "^14.0.0", 114 | "@types/better-sqlite3": "^7.6.4", 115 | "@types/jest": "^29.5.2", 116 | "@types/node": "20.2.5", 117 | "@types/react": "^18.2.8", 118 | "@types/react-dom": "^18.2.4", 119 | "@types/react-test-renderer": "^18.0.0", 120 | "@types/terser-webpack-plugin": "^5.0.4", 121 | "@types/webpack-bundle-analyzer": "^4.6.0", 122 | "@typescript-eslint/eslint-plugin": "^5.59.8", 123 | "@typescript-eslint/parser": "^5.59.8", 124 | "browserslist-config-erb": "^0.0.3", 125 | "chalk": "^4.1.2", 126 | "concurrently": "^8.1.0", 127 | "core-js": "^3.30.2", 128 | "cross-env": "^7.0.3", 129 | "css-loader": "^6.8.1", 130 | "css-minimizer-webpack-plugin": "^5.0.0", 131 | "detect-port": "^1.5.1", 132 | "electron": "^25.0.1", 133 | "electron-builder": "^24.2.1", 134 | "electron-devtools-installer": "^3.2.0", 135 | "electronmon": "^2.0.2", 136 | "eslint": "^8.42.0", 137 | "eslint-config-airbnb-base": "^15.0.0", 138 | "eslint-config-erb": "^4.0.6", 139 | "eslint-import-resolver-typescript": "^3.5.5", 140 | "eslint-import-resolver-webpack": "^0.13.2", 141 | "eslint-plugin-compat": "^4.1.4", 142 | "eslint-plugin-import": "^2.27.5", 143 | "eslint-plugin-jest": "^27.2.1", 144 | "eslint-plugin-jsx-a11y": "^6.7.1", 145 | "eslint-plugin-promise": "^6.1.1", 146 | "eslint-plugin-react": "^7.32.2", 147 | "eslint-plugin-react-hooks": "^4.6.0", 148 | "file-loader": "^6.2.0", 149 | "html-webpack-plugin": "^5.5.1", 150 | "identity-obj-proxy": "^3.0.0", 151 | "jest": "^29.5.0", 152 | "jest-environment-jsdom": "^29.5.0", 153 | "mini-css-extract-plugin": "^2.7.6", 154 | "prettier": "3.0.0", 155 | "react-refresh": "^0.14.0", 156 | "react-test-renderer": "^18.2.0", 157 | "rimraf": "^5.0.1", 158 | "sass": "^1.62.1", 159 | "sass-loader": "^13.3.1", 160 | "style-loader": "^3.3.3", 161 | "terser-webpack-plugin": "^5.3.9", 162 | "ts-jest": "^29.1.0", 163 | "ts-loader": "^9.4.3", 164 | "ts-node": "^10.9.1", 165 | "tsconfig-paths-webpack-plugin": "^4.0.1", 166 | "typescript": "^5.1.3", 167 | "url-loader": "^4.1.1", 168 | "webpack": "^5.85.0", 169 | "webpack-bundle-analyzer": "^4.9.0", 170 | "webpack-cli": "^5.1.1", 171 | "webpack-dev-server": "^4.15.0", 172 | "webpack-merge": "^5.9.0" 173 | }, 174 | "build": { 175 | "productName": "ElectronReact", 176 | "appId": "org.erb.ElectronReact", 177 | "asar": true, 178 | "asarUnpack": "**\\*.{node,dll}", 179 | "files": [ 180 | "dist", 181 | "node_modules", 182 | "package.json" 183 | ], 184 | "afterSign": ".erb/scripts/notarize.js", 185 | "mac": { 186 | "target": { 187 | "target": "default", 188 | "arch": [ 189 | "arm64", 190 | "x64" 191 | ] 192 | }, 193 | "type": "distribution", 194 | "hardenedRuntime": true, 195 | "entitlements": "assets/entitlements.mac.plist", 196 | "entitlementsInherit": "assets/entitlements.mac.plist", 197 | "gatekeeperAssess": false 198 | }, 199 | "dmg": { 200 | "contents": [ 201 | { 202 | "x": 130, 203 | "y": 220 204 | }, 205 | { 206 | "x": 410, 207 | "y": 220, 208 | "type": "link", 209 | "path": "/Applications" 210 | } 211 | ] 212 | }, 213 | "win": { 214 | "target": [ 215 | "nsis" 216 | ] 217 | }, 218 | "linux": { 219 | "target": [ 220 | "AppImage" 221 | ], 222 | "category": "Development" 223 | }, 224 | "directories": { 225 | "app": "release/app", 226 | "buildResources": "assets", 227 | "output": "release/build" 228 | }, 229 | "extraResources": [ 230 | "./assets/**" 231 | ], 232 | "publish": { 233 | "provider": "github", 234 | "owner": "electron-react-boilerplate", 235 | "repo": "electron-react-boilerplate" 236 | } 237 | }, 238 | "collective": { 239 | "url": "https://opencollective.com/electron-react-boilerplate-594" 240 | }, 241 | "devEngines": { 242 | "node": ">=14.x", 243 | "npm": ">=7.x" 244 | }, 245 | "electronmon": { 246 | "patterns": [ 247 | "!**/**", 248 | "src/main/**" 249 | ], 250 | "logLevel": "quiet" 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/preview.jpg -------------------------------------------------------------------------------- /release/app/database.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuliocll/electron-react-sqlite-todo/215c0dcd1eae090f9285b935f954984c3cb694d6/release/app/database.db -------------------------------------------------------------------------------- /release/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "version": "4.6.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "electron-react-boilerplate", 9 | "version": "4.6.0", 10 | "hasInstallScript": true, 11 | "license": "MIT" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "version": "4.6.0", 4 | "description": "A foundation for scalable desktop apps", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Electron React Boilerplate Maintainers", 8 | "email": "electronreactboilerplate@gmail.com", 9 | "url": "https://github.com/electron-react-boilerplate" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": { 18 | "better-sqlite3": "^8.4.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /release/app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | base64-js@^1.3.1: 6 | version "1.5.1" 7 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 8 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 9 | 10 | better-sqlite3@^8.4.0: 11 | version "8.4.0" 12 | resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-8.4.0.tgz#aa27bbc6bb42bb438fc55c88b146fcfe5978fa76" 13 | integrity sha512-NmsNW1CQvqMszu/CFAJ3pLct6NEFlNfuGM6vw72KHkjOD1UDnL96XNN1BMQc1hiHo8vE2GbOWQYIpZ+YM5wrZw== 14 | dependencies: 15 | bindings "^1.5.0" 16 | prebuild-install "^7.1.0" 17 | 18 | bindings@^1.5.0: 19 | version "1.5.0" 20 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 21 | integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 22 | dependencies: 23 | file-uri-to-path "1.0.0" 24 | 25 | bl@^4.0.3: 26 | version "4.1.0" 27 | resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" 28 | integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== 29 | dependencies: 30 | buffer "^5.5.0" 31 | inherits "^2.0.4" 32 | readable-stream "^3.4.0" 33 | 34 | buffer@^5.5.0: 35 | version "5.7.1" 36 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 37 | integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== 38 | dependencies: 39 | base64-js "^1.3.1" 40 | ieee754 "^1.1.13" 41 | 42 | chownr@^1.1.1: 43 | version "1.1.4" 44 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" 45 | integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== 46 | 47 | decompress-response@^6.0.0: 48 | version "6.0.0" 49 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" 50 | integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== 51 | dependencies: 52 | mimic-response "^3.1.0" 53 | 54 | deep-extend@^0.6.0: 55 | version "0.6.0" 56 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 57 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 58 | 59 | detect-libc@^2.0.0: 60 | version "2.0.1" 61 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" 62 | integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== 63 | 64 | end-of-stream@^1.1.0, end-of-stream@^1.4.1: 65 | version "1.4.4" 66 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 67 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 68 | dependencies: 69 | once "^1.4.0" 70 | 71 | expand-template@^2.0.3: 72 | version "2.0.3" 73 | resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" 74 | integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== 75 | 76 | file-uri-to-path@1.0.0: 77 | version "1.0.0" 78 | resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 79 | integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== 80 | 81 | fs-constants@^1.0.0: 82 | version "1.0.0" 83 | resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" 84 | integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== 85 | 86 | github-from-package@0.0.0: 87 | version "0.0.0" 88 | resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" 89 | integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== 90 | 91 | ieee754@^1.1.13: 92 | version "1.2.1" 93 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 94 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 95 | 96 | inherits@^2.0.3, inherits@^2.0.4: 97 | version "2.0.4" 98 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 99 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 100 | 101 | ini@~1.3.0: 102 | version "1.3.8" 103 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" 104 | integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== 105 | 106 | lru-cache@^6.0.0: 107 | version "6.0.0" 108 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 109 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 110 | dependencies: 111 | yallist "^4.0.0" 112 | 113 | mimic-response@^3.1.0: 114 | version "3.1.0" 115 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" 116 | integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== 117 | 118 | minimist@^1.2.0, minimist@^1.2.3: 119 | version "1.2.8" 120 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" 121 | integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== 122 | 123 | mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: 124 | version "0.5.3" 125 | resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" 126 | integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== 127 | 128 | napi-build-utils@^1.0.1: 129 | version "1.0.2" 130 | resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" 131 | integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== 132 | 133 | node-abi@^3.3.0: 134 | version "3.45.0" 135 | resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.45.0.tgz#f568f163a3bfca5aacfce1fbeee1fa2cc98441f5" 136 | integrity sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ== 137 | dependencies: 138 | semver "^7.3.5" 139 | 140 | once@^1.3.1, once@^1.4.0: 141 | version "1.4.0" 142 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 143 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 144 | dependencies: 145 | wrappy "1" 146 | 147 | prebuild-install@^7.1.0: 148 | version "7.1.1" 149 | resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" 150 | integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== 151 | dependencies: 152 | detect-libc "^2.0.0" 153 | expand-template "^2.0.3" 154 | github-from-package "0.0.0" 155 | minimist "^1.2.3" 156 | mkdirp-classic "^0.5.3" 157 | napi-build-utils "^1.0.1" 158 | node-abi "^3.3.0" 159 | pump "^3.0.0" 160 | rc "^1.2.7" 161 | simple-get "^4.0.0" 162 | tar-fs "^2.0.0" 163 | tunnel-agent "^0.6.0" 164 | 165 | pump@^3.0.0: 166 | version "3.0.0" 167 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 168 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 169 | dependencies: 170 | end-of-stream "^1.1.0" 171 | once "^1.3.1" 172 | 173 | rc@^1.2.7: 174 | version "1.2.8" 175 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 176 | integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== 177 | dependencies: 178 | deep-extend "^0.6.0" 179 | ini "~1.3.0" 180 | minimist "^1.2.0" 181 | strip-json-comments "~2.0.1" 182 | 183 | readable-stream@^3.1.1, readable-stream@^3.4.0: 184 | version "3.6.2" 185 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" 186 | integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== 187 | dependencies: 188 | inherits "^2.0.3" 189 | string_decoder "^1.1.1" 190 | util-deprecate "^1.0.1" 191 | 192 | safe-buffer@^5.0.1, safe-buffer@~5.2.0: 193 | version "5.2.1" 194 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 195 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 196 | 197 | semver@^7.3.5: 198 | version "7.5.3" 199 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" 200 | integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== 201 | dependencies: 202 | lru-cache "^6.0.0" 203 | 204 | simple-concat@^1.0.0: 205 | version "1.0.1" 206 | resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" 207 | integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== 208 | 209 | simple-get@^4.0.0: 210 | version "4.0.1" 211 | resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" 212 | integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== 213 | dependencies: 214 | decompress-response "^6.0.0" 215 | once "^1.3.1" 216 | simple-concat "^1.0.0" 217 | 218 | string_decoder@^1.1.1: 219 | version "1.3.0" 220 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 221 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 222 | dependencies: 223 | safe-buffer "~5.2.0" 224 | 225 | strip-json-comments@~2.0.1: 226 | version "2.0.1" 227 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 228 | integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== 229 | 230 | tar-fs@^2.0.0: 231 | version "2.1.1" 232 | resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" 233 | integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== 234 | dependencies: 235 | chownr "^1.1.1" 236 | mkdirp-classic "^0.5.2" 237 | pump "^3.0.0" 238 | tar-stream "^2.1.4" 239 | 240 | tar-stream@^2.1.4: 241 | version "2.2.0" 242 | resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" 243 | integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== 244 | dependencies: 245 | bl "^4.0.3" 246 | end-of-stream "^1.4.1" 247 | fs-constants "^1.0.0" 248 | inherits "^2.0.3" 249 | readable-stream "^3.1.1" 250 | 251 | tunnel-agent@^0.6.0: 252 | version "0.6.0" 253 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 254 | integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== 255 | dependencies: 256 | safe-buffer "^5.0.1" 257 | 258 | util-deprecate@^1.0.1: 259 | version "1.0.2" 260 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 261 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 262 | 263 | wrappy@1: 264 | version "1.0.2" 265 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 266 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 267 | 268 | yallist@^4.0.0: 269 | version "4.0.0" 270 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 271 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 272 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/main/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, no-console: off, promise/always-return: off */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build:main`, this file is compiled to 9 | * `./src/main.js` using webpack. This gives us some performance wins. 10 | */ 11 | import path from 'path'; 12 | import { app, BrowserWindow, shell, ipcMain } from 'electron'; 13 | import { autoUpdater } from 'electron-updater'; 14 | import log from 'electron-log'; 15 | import MenuBuilder from './menu'; 16 | import { resolveHtmlPath } from './util'; 17 | import { 18 | deleteTODO, 19 | getAllTODO, 20 | getOneTODO, 21 | insertTODO, 22 | updateTODO, 23 | TODO, 24 | } from './services/Database.service'; 25 | 26 | class AppUpdater { 27 | constructor() { 28 | log.transports.file.level = 'info'; 29 | autoUpdater.logger = log; 30 | autoUpdater.checkForUpdatesAndNotify(); 31 | } 32 | } 33 | 34 | let mainWindow: BrowserWindow | null = null; 35 | 36 | ipcMain.on('ipc-example', async (event, arg) => { 37 | const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; 38 | console.log(msgTemplate(arg)); 39 | event.reply('ipc-example', msgTemplate('pong')); 40 | }); 41 | 42 | if (process.env.NODE_ENV === 'production') { 43 | const sourceMapSupport = require('source-map-support'); 44 | sourceMapSupport.install(); 45 | } 46 | 47 | const isDebug = 48 | process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; 49 | 50 | if (isDebug) { 51 | require('electron-debug')(); 52 | } 53 | 54 | const installExtensions = async () => { 55 | const installer = require('electron-devtools-installer'); 56 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 57 | const extensions = ['REACT_DEVELOPER_TOOLS']; 58 | 59 | return installer 60 | .default( 61 | extensions.map((name) => installer[name]), 62 | forceDownload, 63 | ) 64 | .catch(console.log); 65 | }; 66 | 67 | const createWindow = async () => { 68 | if (isDebug) { 69 | await installExtensions(); 70 | } 71 | 72 | const RESOURCES_PATH = app.isPackaged 73 | ? path.join(process.resourcesPath, 'assets') 74 | : path.join(__dirname, '../../assets'); 75 | 76 | const getAssetPath = (...paths: string[]): string => { 77 | return path.join(RESOURCES_PATH, ...paths); 78 | }; 79 | 80 | mainWindow = new BrowserWindow({ 81 | show: false, 82 | width: 1024, 83 | height: 728, 84 | icon: getAssetPath('icon.png'), 85 | webPreferences: { 86 | preload: app.isPackaged 87 | ? path.join(__dirname, 'preload.js') 88 | : path.join(__dirname, '../../.erb/dll/preload.js'), 89 | }, 90 | }); 91 | 92 | mainWindow.loadURL(resolveHtmlPath('index.html')); 93 | 94 | mainWindow.on('ready-to-show', () => { 95 | if (!mainWindow) { 96 | throw new Error('"mainWindow" is not defined'); 97 | } 98 | if (process.env.START_MINIMIZED) { 99 | mainWindow.minimize(); 100 | } else { 101 | mainWindow.show(); 102 | } 103 | }); 104 | 105 | mainWindow.on('closed', () => { 106 | mainWindow = null; 107 | }); 108 | 109 | const menuBuilder = new MenuBuilder(mainWindow); 110 | menuBuilder.buildMenu(); 111 | 112 | // Open urls in the user's browser 113 | mainWindow.webContents.setWindowOpenHandler((edata) => { 114 | shell.openExternal(edata.url); 115 | return { action: 'deny' }; 116 | }); 117 | 118 | // Remove this if your app does not use auto updates 119 | // eslint-disable-next-line 120 | new AppUpdater(); 121 | }; 122 | 123 | /** 124 | * Add event listeners... 125 | */ 126 | 127 | app.on('window-all-closed', () => { 128 | // Respect the OSX convention of having the application in memory even 129 | // after all windows have been closed 130 | if (process.platform !== 'darwin') { 131 | app.quit(); 132 | } 133 | }); 134 | 135 | app 136 | .whenReady() 137 | .then(() => { 138 | ipcMain.handle('todo:insert', async (_, todo: TODO) => { 139 | insertTODO(todo); 140 | }); 141 | ipcMain.handle('todo:update', async (_, todo: TODO) => { 142 | updateTODO(todo); 143 | }); 144 | ipcMain.handle('todo:delete', async (_, id: number) => { 145 | deleteTODO(id); 146 | }); 147 | ipcMain.handle('todo:getOne', async (_, id: number) => { 148 | return getOneTODO(id); 149 | }); 150 | ipcMain.handle('todo:getAll', async () => { 151 | return getAllTODO(); 152 | }); 153 | createWindow(); 154 | app.on('activate', () => { 155 | // On macOS it's common to re-create a window in the app when the 156 | // dock icon is clicked and there are no other windows open. 157 | if (mainWindow === null) createWindow(); 158 | }); 159 | }) 160 | .catch(console.log); 161 | -------------------------------------------------------------------------------- /src/main/menu.ts: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | Menu, 4 | shell, 5 | BrowserWindow, 6 | MenuItemConstructorOptions, 7 | } from 'electron'; 8 | 9 | interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { 10 | selector?: string; 11 | submenu?: DarwinMenuItemConstructorOptions[] | Menu; 12 | } 13 | 14 | export default class MenuBuilder { 15 | mainWindow: BrowserWindow; 16 | 17 | constructor(mainWindow: BrowserWindow) { 18 | this.mainWindow = mainWindow; 19 | } 20 | 21 | buildMenu(): Menu { 22 | if ( 23 | process.env.NODE_ENV === 'development' || 24 | process.env.DEBUG_PROD === 'true' 25 | ) { 26 | this.setupDevelopmentEnvironment(); 27 | } 28 | 29 | const template = 30 | process.platform === 'darwin' 31 | ? this.buildDarwinTemplate() 32 | : this.buildDefaultTemplate(); 33 | 34 | const menu = Menu.buildFromTemplate(template); 35 | Menu.setApplicationMenu(menu); 36 | 37 | return menu; 38 | } 39 | 40 | setupDevelopmentEnvironment(): void { 41 | this.mainWindow.webContents.on('context-menu', (_, props) => { 42 | const { x, y } = props; 43 | 44 | Menu.buildFromTemplate([ 45 | { 46 | label: 'Inspect element', 47 | click: () => { 48 | this.mainWindow.webContents.inspectElement(x, y); 49 | }, 50 | }, 51 | ]).popup({ window: this.mainWindow }); 52 | }); 53 | } 54 | 55 | buildDarwinTemplate(): MenuItemConstructorOptions[] { 56 | const subMenuAbout: DarwinMenuItemConstructorOptions = { 57 | label: 'Electron', 58 | submenu: [ 59 | { 60 | label: 'About ElectronReact', 61 | selector: 'orderFrontStandardAboutPanel:', 62 | }, 63 | { type: 'separator' }, 64 | { label: 'Services', submenu: [] }, 65 | { type: 'separator' }, 66 | { 67 | label: 'Hide ElectronReact', 68 | accelerator: 'Command+H', 69 | selector: 'hide:', 70 | }, 71 | { 72 | label: 'Hide Others', 73 | accelerator: 'Command+Shift+H', 74 | selector: 'hideOtherApplications:', 75 | }, 76 | { label: 'Show All', selector: 'unhideAllApplications:' }, 77 | { type: 'separator' }, 78 | { 79 | label: 'Quit', 80 | accelerator: 'Command+Q', 81 | click: () => { 82 | app.quit(); 83 | }, 84 | }, 85 | ], 86 | }; 87 | const subMenuEdit: DarwinMenuItemConstructorOptions = { 88 | label: 'Edit', 89 | submenu: [ 90 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }, 91 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' }, 92 | { type: 'separator' }, 93 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }, 94 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }, 95 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }, 96 | { 97 | label: 'Select All', 98 | accelerator: 'Command+A', 99 | selector: 'selectAll:', 100 | }, 101 | ], 102 | }; 103 | const subMenuViewDev: MenuItemConstructorOptions = { 104 | label: 'View', 105 | submenu: [ 106 | { 107 | label: 'Reload', 108 | accelerator: 'Command+R', 109 | click: () => { 110 | this.mainWindow.webContents.reload(); 111 | }, 112 | }, 113 | { 114 | label: 'Toggle Full Screen', 115 | accelerator: 'Ctrl+Command+F', 116 | click: () => { 117 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 118 | }, 119 | }, 120 | { 121 | label: 'Toggle Developer Tools', 122 | accelerator: 'Alt+Command+I', 123 | click: () => { 124 | this.mainWindow.webContents.toggleDevTools(); 125 | }, 126 | }, 127 | ], 128 | }; 129 | const subMenuViewProd: MenuItemConstructorOptions = { 130 | label: 'View', 131 | submenu: [ 132 | { 133 | label: 'Toggle Full Screen', 134 | accelerator: 'Ctrl+Command+F', 135 | click: () => { 136 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 137 | }, 138 | }, 139 | ], 140 | }; 141 | const subMenuWindow: DarwinMenuItemConstructorOptions = { 142 | label: 'Window', 143 | submenu: [ 144 | { 145 | label: 'Minimize', 146 | accelerator: 'Command+M', 147 | selector: 'performMiniaturize:', 148 | }, 149 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 150 | { type: 'separator' }, 151 | { label: 'Bring All to Front', selector: 'arrangeInFront:' }, 152 | ], 153 | }; 154 | const subMenuHelp: MenuItemConstructorOptions = { 155 | label: 'Help', 156 | submenu: [ 157 | { 158 | label: 'Learn More', 159 | click() { 160 | shell.openExternal('https://electronjs.org'); 161 | }, 162 | }, 163 | { 164 | label: 'Documentation', 165 | click() { 166 | shell.openExternal( 167 | 'https://github.com/electron/electron/tree/main/docs#readme' 168 | ); 169 | }, 170 | }, 171 | { 172 | label: 'Community Discussions', 173 | click() { 174 | shell.openExternal('https://www.electronjs.org/community'); 175 | }, 176 | }, 177 | { 178 | label: 'Search Issues', 179 | click() { 180 | shell.openExternal('https://github.com/electron/electron/issues'); 181 | }, 182 | }, 183 | ], 184 | }; 185 | 186 | const subMenuView = 187 | process.env.NODE_ENV === 'development' || 188 | process.env.DEBUG_PROD === 'true' 189 | ? subMenuViewDev 190 | : subMenuViewProd; 191 | 192 | return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]; 193 | } 194 | 195 | buildDefaultTemplate() { 196 | const templateDefault = [ 197 | { 198 | label: '&File', 199 | submenu: [ 200 | { 201 | label: '&Open', 202 | accelerator: 'Ctrl+O', 203 | }, 204 | { 205 | label: '&Close', 206 | accelerator: 'Ctrl+W', 207 | click: () => { 208 | this.mainWindow.close(); 209 | }, 210 | }, 211 | ], 212 | }, 213 | { 214 | label: '&View', 215 | submenu: 216 | process.env.NODE_ENV === 'development' || 217 | process.env.DEBUG_PROD === 'true' 218 | ? [ 219 | { 220 | label: '&Reload', 221 | accelerator: 'Ctrl+R', 222 | click: () => { 223 | this.mainWindow.webContents.reload(); 224 | }, 225 | }, 226 | { 227 | label: 'Toggle &Full Screen', 228 | accelerator: 'F11', 229 | click: () => { 230 | this.mainWindow.setFullScreen( 231 | !this.mainWindow.isFullScreen() 232 | ); 233 | }, 234 | }, 235 | { 236 | label: 'Toggle &Developer Tools', 237 | accelerator: 'Alt+Ctrl+I', 238 | click: () => { 239 | this.mainWindow.webContents.toggleDevTools(); 240 | }, 241 | }, 242 | ] 243 | : [ 244 | { 245 | label: 'Toggle &Full Screen', 246 | accelerator: 'F11', 247 | click: () => { 248 | this.mainWindow.setFullScreen( 249 | !this.mainWindow.isFullScreen() 250 | ); 251 | }, 252 | }, 253 | ], 254 | }, 255 | { 256 | label: 'Help', 257 | submenu: [ 258 | { 259 | label: 'Learn More', 260 | click() { 261 | shell.openExternal('https://electronjs.org'); 262 | }, 263 | }, 264 | { 265 | label: 'Documentation', 266 | click() { 267 | shell.openExternal( 268 | 'https://github.com/electron/electron/tree/main/docs#readme' 269 | ); 270 | }, 271 | }, 272 | { 273 | label: 'Community Discussions', 274 | click() { 275 | shell.openExternal('https://www.electronjs.org/community'); 276 | }, 277 | }, 278 | { 279 | label: 'Search Issues', 280 | click() { 281 | shell.openExternal('https://github.com/electron/electron/issues'); 282 | }, 283 | }, 284 | ], 285 | }, 286 | ]; 287 | 288 | return templateDefault; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/main/preload.ts: -------------------------------------------------------------------------------- 1 | // Disable no-unused-vars, broken for spread args 2 | /* eslint no-unused-vars: off */ 3 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; 4 | import { TODO } from './services/Database.service'; 5 | 6 | export type Channels = 'ipc-example'; 7 | 8 | const electronHandler = { 9 | ipcRenderer: { 10 | sendMessage(channel: Channels, ...args: unknown[]) { 11 | ipcRenderer.send(channel, ...args); 12 | }, 13 | on(channel: Channels, func: (...args: unknown[]) => void) { 14 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => 15 | func(...args); 16 | ipcRenderer.on(channel, subscription); 17 | 18 | return () => { 19 | ipcRenderer.removeListener(channel, subscription); 20 | }; 21 | }, 22 | once(channel: Channels, func: (...args: unknown[]) => void) { 23 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 24 | }, 25 | }, 26 | insertTODO: (todo: TODO) => ipcRenderer.invoke('todo:insert', todo), 27 | deleteTODO: (id: number) => ipcRenderer.invoke('todo:delete', id), 28 | getAllTODO: () => ipcRenderer.invoke('todo:getAll'), 29 | getOneTODO: (id: number) => ipcRenderer.invoke('todo:getOne', id), 30 | updateTODO: (todo: TODO) => ipcRenderer.invoke('todo:update', todo), 31 | }; 32 | 33 | contextBridge.exposeInMainWorld('electron', electronHandler); 34 | 35 | export type ElectronHandler = typeof electronHandler; 36 | -------------------------------------------------------------------------------- /src/main/services/Database.service.ts: -------------------------------------------------------------------------------- 1 | import Database from 'better-sqlite3'; 2 | import path from 'path'; 3 | 4 | export type TODO = { 5 | id?: number; 6 | title: string; 7 | date: string; 8 | status: number; 9 | }; 10 | 11 | export function connect() { 12 | return Database( 13 | path.join(__dirname, '../../../', 'release/app', 'database.db'), 14 | { verbose: console.log, fileMustExist: true }, 15 | ); 16 | } 17 | 18 | export function insertTODO(todo: TODO) { 19 | const db = connect(); 20 | 21 | const stm = db.prepare( 22 | 'INSERT INTO todos (title, date, status) VALUES (@title, @date, @status)', 23 | ); 24 | 25 | stm.run(todo); 26 | } 27 | 28 | export function updateTODO(todo: TODO) { 29 | const db = connect(); 30 | const { title, status, id } = todo; 31 | 32 | const stm = db.prepare( 33 | 'UPDATE todos SET title = @title, status = @status WHERE id = @id', 34 | ); 35 | 36 | stm.run({ title, status, id }); 37 | } 38 | 39 | export function deleteTODO(id: number) { 40 | const db = connect(); 41 | 42 | const stm = db.prepare('DELETE FROM todos WHERE id = @id'); 43 | 44 | stm.run({ id }); 45 | } 46 | 47 | export function getAllTODO() { 48 | const db = connect(); 49 | 50 | const stm = db.prepare('SELECT * FROM todos'); 51 | 52 | return stm.all() as TODO[]; 53 | } 54 | 55 | export function getOneTODO(id: number) { 56 | const db = connect(); 57 | 58 | const stm = db.prepare('SELECT * FROM todos where id = @id'); 59 | 60 | return stm.get({ id }) as TODO; 61 | } 62 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/components/FAB/FAB.module.css: -------------------------------------------------------------------------------- 1 | @import '../../styles/themes/default.css'; 2 | 3 | .container { 4 | background-color: var(--primary-color); 5 | position: absolute; 6 | bottom: 10px; 7 | right: 10px; 8 | width: 50px; 9 | height: 50px; 10 | border-radius: 50%; 11 | border: none; 12 | color: #fff; 13 | font-size: 1.3rem; 14 | } 15 | 16 | .container:hover { 17 | cursor: pointer; 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/components/FAB/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './FAB.module.css'; 2 | 3 | export default function FAB({ onClick }) { 4 | return ( 5 | 6 | + 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/components/Logo/Logo.module.css: -------------------------------------------------------------------------------- 1 | @import '../../styles/themes/default.css'; 2 | 3 | .logo { 4 | color: var(--primary-color); 5 | margin: 20px 0px; 6 | font-family: var(--font); 7 | font-weight: 800; 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './Logo.module.css'; 2 | 3 | export default function Logo() { 4 | return TODO Clone; 5 | } 6 | -------------------------------------------------------------------------------- /src/renderer/components/Modal/Modal.module.css: -------------------------------------------------------------------------------- 1 | @import '../../styles/themes/default.css'; 2 | 3 | .modal { 4 | display: block; /* Hidden by default */ 5 | position: fixed; /* Stay in place */ 6 | z-index: 1; /* Sit on top */ 7 | padding-top: 100px; /* Location of the box */ 8 | left: 0; 9 | top: 0; 10 | width: 100%; /* Full width */ 11 | height: 100%; /* Full height */ 12 | overflow: auto; /* Enable scroll if needed */ 13 | background-color: rgb(0, 0, 0); /* Fallback color */ 14 | background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */ 15 | color: var(--text-color); 16 | } 17 | 18 | .modal_content { 19 | background-color: var(--background-color); 20 | margin: auto; 21 | padding: 20px; 22 | border: 1px solid var(--primary-color); 23 | width: 50%; 24 | } 25 | 26 | /* The Close Button */ 27 | .close { 28 | color: var(--text-cancel-color); 29 | float: right; 30 | font-size: 28px; 31 | font-weight: bold; 32 | } 33 | 34 | .close:hover, 35 | .close:focus { 36 | color: #000; 37 | text-decoration: none; 38 | cursor: pointer; 39 | } 40 | 41 | .formGroup { 42 | display: flex; 43 | flex-direction: column; 44 | margin: 10px 0px; 45 | } 46 | 47 | .formGroup > input { 48 | font-size: 1rem; 49 | padding: 5px; 50 | margin: 5px 0px; 51 | border: 1px solid; 52 | border-radius: 4px; 53 | border-color: var(--primary-color); 54 | background-color: var(--secondary-color); 55 | color: var(--text-color); 56 | } 57 | -------------------------------------------------------------------------------- /src/renderer/components/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import styles from './Modal.module.css'; 3 | import { TODO } from '../TaskArea'; 4 | export type Modal = { 5 | initialData: TODO | undefined; 6 | onClose: () => void; 7 | onSave: (item: TODO) => void; 8 | }; 9 | 10 | export default function Modal({ onClose, initialData, onSave }: Modal) { 11 | const [title, setTitle] = useState(initialData?.title || ''); 12 | 13 | function handleOnSave() { 14 | if (title === '') { 15 | alert('Invalid title'); 16 | return; 17 | } 18 | 19 | onSave({ 20 | title, 21 | date: initialData?.date || new Date().toLocaleString(), 22 | status: initialData?.status || 0, 23 | id: initialData?.id, 24 | }); 25 | } 26 | 27 | return ( 28 | 29 | 30 | 31 | × 32 | 33 | New task 34 | 35 | Title 36 | setTitle(el.target.value)} /> 37 | 38 | 39 | Save 40 | 41 | 42 | Cancel 43 | 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/renderer/components/Sidebar/Sidebar.module.css: -------------------------------------------------------------------------------- 1 | @import '../../styles/themes/default.css'; 2 | 3 | .sidenav { 4 | width: 240px; 5 | height: 100vh; 6 | background: var(--background-color); 7 | overflow-x: hidden; 8 | padding-left: 10px; 9 | } 10 | 11 | .sidenav a { 12 | padding: 10px; 13 | text-decoration: none; 14 | font-family: var(--font); 15 | font-size: 1.1rem; 16 | color: var(--link-color); 17 | display: block; 18 | } 19 | 20 | .sidenav a:hover { 21 | background-color: var(--alternate-background-color); 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/components/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import Logo from '../Logo'; 2 | 3 | import styles from './Sidebar.module.css'; 4 | 5 | export default function Sidebar() { 6 | return ( 7 | 8 | 9 | Meu dia 10 | Importante 11 | Planejado 12 | Trabalho 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/components/TaskArea/TaskArea.module.css: -------------------------------------------------------------------------------- 1 | @import '../../styles/themes/default.css'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | width: 100%; 7 | padding: 10px; 8 | background-color: var(--alternate-background-color); 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/components/TaskArea/index.tsx: -------------------------------------------------------------------------------- 1 | import TaskItem from '../TaskItem'; 2 | 3 | import styles from './TaskArea.module.css'; 4 | 5 | export type TODO = { 6 | id?: number; 7 | title: string; 8 | date: string; 9 | status: number; 10 | }; 11 | 12 | export default function TaskArea({ 13 | todos, 14 | onCheck, 15 | onDelete, 16 | onEdit, 17 | }: { 18 | todos: TODO[]; 19 | onCheck: (id: number) => void; 20 | onDelete: (id: number) => void; 21 | onEdit: (id: number) => void; 22 | }) { 23 | return ( 24 | 25 | {todos.map((todo) => ( 26 | 36 | ))} 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/renderer/components/TaskItem/TaskItem.module.css: -------------------------------------------------------------------------------- 1 | @import '../../styles/themes/default.css'; 2 | 3 | .container { 4 | display: flex; 5 | align-items: center; 6 | background-color: var(--secondary-color); 7 | padding: 10px 20px; 8 | margin: 1px 0px; 9 | color: var(--text-color); 10 | font-family: var(--font); 11 | border-radius: 6px; 12 | } 13 | 14 | .container > :nth-child(1) { 15 | margin-right: 15px; 16 | } 17 | 18 | .task-label { 19 | font-size: 0.85rem; 20 | color: var(--text-color); 21 | } 22 | 23 | .task-date { 24 | font-size: 0.85rem; 25 | color: var(--text-cancel-color); 26 | font-weight: bold; 27 | } 28 | 29 | .task-finish .task-label { 30 | text-decoration: line-through; 31 | } 32 | 33 | input[type='checkbox'] { 34 | -webkit-appearance: none; 35 | appearance: none; 36 | background-color: var(--alternate-background-color); 37 | margin: 0; 38 | font: inherit; 39 | color: currentColor; 40 | width: 1.35em; 41 | height: 1.35em; 42 | border: 0.15em solid var(--background-color); 43 | border-radius: 50px; 44 | transform: translateY(-0.075em); 45 | display: grid; 46 | place-content: center; 47 | } 48 | 49 | input[type='checkbox']::before { 50 | content: ''; 51 | width: 0.55em; 52 | height: 0.55em; 53 | clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); 54 | border-radius: 50px; 55 | transform: scale(0); 56 | transform-origin: bottom left; 57 | transition: 120ms transform ease-in-out; 58 | box-shadow: inset 1em 1em var(--background-color); 59 | background-color: var(--background-color); 60 | } 61 | 62 | input[type='checkbox']:checked::before { 63 | transform: scale(1); 64 | } 65 | 66 | input[type='checkbox']:checked { 67 | background-color: var(--primary-color); 68 | } 69 | 70 | input[type='checkbox']:focus { 71 | outline: max(2px, 0.15em) solid currentColor; 72 | outline-offset: max(2px, 0.15em); 73 | } 74 | 75 | input[type='checkbox']:disabled { 76 | color: var(--primary-color); 77 | cursor: not-allowed; 78 | } 79 | -------------------------------------------------------------------------------- /src/renderer/components/TaskItem/index.tsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns'; 2 | import styles from './TaskItem.module.css'; 3 | 4 | export type TaskItem = { 5 | label: string; 6 | date: string; 7 | id: number; 8 | checked: boolean; 9 | onChange: (id: number) => void; 10 | onEdit: (id: number) => void; 11 | onDelete: (id: number) => void; 12 | }; 13 | 14 | export default function TaskItem({ 15 | date, 16 | label, 17 | id, 18 | checked, 19 | onChange, 20 | onDelete, 21 | onEdit, 22 | }: TaskItem) { 23 | function handleCheck() { 24 | onChange(id); 25 | } 26 | 27 | function handleEdit() { 28 | onEdit(id); 29 | } 30 | 31 | function handleDelete() { 32 | onDelete(id); 33 | } 34 | 35 | return ( 36 | 40 | 46 | 47 | {label} 48 | 49 | {format(new Date(date), "E., dd 'de' MMM")} 50 | 51 | 52 | 53 | Editar 54 | 55 | 56 | Deletar 57 | 58 | 59 | 60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Hello Electron React! 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import Routes from './routes'; 3 | import './styles/App.global.css'; 4 | 5 | const container = document.getElementById('root') as HTMLElement; 6 | const root = createRoot(container); 7 | root.render(); 8 | -------------------------------------------------------------------------------- /src/renderer/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronHandler } from 'main/preload'; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-unused-vars 5 | interface Window { 6 | electron: ElectronHandler; 7 | } 8 | } 9 | 10 | export {}; 11 | -------------------------------------------------------------------------------- /src/renderer/routes.tsx: -------------------------------------------------------------------------------- 1 | import { MemoryRouter, Route, Routes } from 'react-router-dom'; 2 | 3 | import Home from './views/Home'; 4 | 5 | export default function appRoutes() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/styles/App.global.css: -------------------------------------------------------------------------------- 1 | @import './themes/default.css'; 2 | 3 | * { 4 | padding: 0; 5 | margin: 0; 6 | font-family: var(--font); 7 | } 8 | 9 | .button { 10 | background-color: var(--primary-color); 11 | border: none; 12 | margin: 5px; 13 | font-size: 0.7rem; 14 | padding: 5px 10px; 15 | border-radius: 4px; 16 | color: var(--text-color); 17 | } 18 | 19 | .button:hover { 20 | opacity: 0.8; 21 | cursor: pointer; 22 | } 23 | 24 | .button + .secondary { 25 | background-color: var(--text-cancel-color); 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/styles/themes/default.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300&display=swap'); 2 | 3 | :root { 4 | --primary-color: #788cde; 5 | --secondary-color: #323232; 6 | --background-color: #282828; 7 | --alternate-background-color: #1e1e1e; 8 | 9 | --text-color: #e1e1e1; 10 | --text-color-light: #777676bb; 11 | --font: Roboto; 12 | 13 | --text-cancel-color: #dd2a2c; 14 | 15 | --link-color: #e1e1e1; 16 | --link-color--hover: #543fd7; 17 | } 18 | -------------------------------------------------------------------------------- /src/renderer/views/Home/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | -------------------------------------------------------------------------------- /src/renderer/views/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import TaskArea, { TODO } from '../../components/TaskArea'; 2 | import Sidebar from '../../components/Sidebar'; 3 | import FAB from '../../components/FAB'; 4 | import Modal from '../../components/Modal'; 5 | 6 | import styles from './Home.module.css'; 7 | import { useEffect, useState } from 'react'; 8 | 9 | export default function Home() { 10 | const [modal, setModal] = useState(false); 11 | const [todos, setTodos] = useState([]); 12 | const [edit, setEdit] = useState(); 13 | 14 | async function onSave(todo: TODO) { 15 | const update = todos.find((el) => el.id === todo.id); 16 | if (update) { 17 | await window.electron.updateTODO(todo); 18 | } else { 19 | await window.electron.insertTODO(todo); 20 | } 21 | 22 | await getAllTODOS(); 23 | toggleModal(); 24 | } 25 | 26 | async function onCheck(id: number) { 27 | const newState = todos.find((todo) => todo.id === id); 28 | 29 | if (!newState) return; 30 | 31 | newState.status = newState.status === 1 ? 0 : 1; 32 | await window.electron.updateTODO(newState); 33 | await getAllTODOS(); 34 | } 35 | 36 | async function onDelete(id: number) { 37 | await window.electron.deleteTODO(id); 38 | await getAllTODOS(); 39 | } 40 | 41 | function onEdit(id: number) { 42 | const editTodo = todos.find((todo) => todo.id === id); 43 | if (editTodo) { 44 | setEdit(editTodo); 45 | } 46 | 47 | toggleModal(); 48 | } 49 | 50 | function toggleModal() { 51 | if (modal) { 52 | setEdit(undefined); 53 | } 54 | 55 | setModal(!modal); 56 | } 57 | 58 | async function getAllTODOS() { 59 | const data = await window.electron.getAllTODO(); 60 | 61 | if (data) { 62 | setTodos(data); 63 | } 64 | } 65 | 66 | useEffect(() => { 67 | getAllTODOS(); 68 | }, []); 69 | 70 | return ( 71 | 72 | 73 | 74 | {modal && ( 75 | 76 | )} 77 | 83 | 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2021", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2021"], 7 | "jsx": "react-jsx", 8 | "strict": true, 9 | "sourceMap": true, 10 | "baseUrl": "./src", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "resolveJsonModule": true, 15 | "allowJs": true, 16 | "outDir": ".erb/dll" 17 | }, 18 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"] 19 | } 20 | --------------------------------------------------------------------------------
{label}
49 | {format(new Date(date), "E., dd 'de' MMM")} 50 |