diff --git a/.gitignore b/.gitignore index 42037e4..711a327 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules dist .DS_Store flow-coverage +build \ No newline at end of file diff --git a/build-assets/icon.png b/build-assets/icon.png new file mode 100644 index 0000000..060bd00 Binary files /dev/null and b/build-assets/icon.png differ diff --git a/config/electron.js b/config/electron.js new file mode 100644 index 0000000..d3cdad9 --- /dev/null +++ b/config/electron.js @@ -0,0 +1,107 @@ +// @flow +import path from 'path'; + +/* eslint-disable import/no-extraneous-dependencies */ +import { + app, BrowserWindow, powerMonitor, Tray, +} from 'electron'; +import { autoUpdater } from 'electron-updater'; +import Positioner from 'electron-positioner'; +import isDev from 'electron-is-dev'; +/* eslint-enable import/no-extraneous-dependencies */ + +import type { BrowserWindow as BrowserWindowType, Tray as TrayType } from 'electron'; + +import { registerDebugShortcut } from '../utils/debugShortcut'; + +let mainWindow: BrowserWindowType; +let tray: TrayType; +let updateAvailable = false; + +const showStatus = (text) => { + if (text === 'Update downloaded') updateAvailable = true; + + mainWindow.webContents.send('update', { + updateAvailable, + updateInfo: text, + }); +}; + +const createWindow = () => { + autoUpdater.checkForUpdatesAndNotify(); + + autoUpdater.on('checking-for-update', () => showStatus('Checking for update')); + autoUpdater.on('update-available', () => showStatus('Update available')); + autoUpdater.on('update-not-available', () => showStatus('No updates available')); + autoUpdater.on('error', err => showStatus(`Error while updating: ${err}`)); + + autoUpdater.on('download-progress', progress => showStatus( + /* eslint-disable-next-line max-len */ + `Download speed: ${progress.bytesPerSecond} - Downloaded ${progress.percent}% (${progress.transferred}/${ + progress.total + })`, + )); + autoUpdater.on('update-downloaded', () => { + updateAvailable = true; + showStatus('Update downloaded'); + }); + + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + transparent: true, + frame: false, + resizable: true, + webPreferences: { + devTools: true, + webSecurity: false, + }, + }); + + mainWindow.setVisibleOnAllWorkspaces(true); + + // TODO: Update to right icon location + tray = new Tray(path.join(__dirname, '../public/images', 'zcash-icon.png')); + + registerDebugShortcut(app, mainWindow); + + tray.setToolTip('ZCash'); + mainWindow.loadURL(isDev ? 'http://0.0.0.0:8080/' : `file://${path.join(__dirname, '../build/index.html')}`); + + const positioner = new Positioner(mainWindow); + let bounds = tray.getBounds(); + positioner.move('trayCenter', bounds); + + powerMonitor.on('suspend', () => mainWindow.webContents.send('suspend', 'suspended')); + powerMonitor.on('resume', () => mainWindow.webContents.send('resume', 'resumed')); + + mainWindow.once('ready-to-show', () => mainWindow.show()); + mainWindow.on('blur', () => mainWindow.hide()); + mainWindow.on('show', () => tray.setHighlightMode('always')); + mainWindow.on('hide', () => tray.setHighlightMode('never')); + mainWindow.on('closed', () => { + mainWindow = null; + }); + + tray.on('click', () => { + bounds = tray.getBounds(); + positioner.move('trayCenter', bounds); + + if (mainWindow.isVisible()) { + mainWindow.hide(); + } else { + mainWindow.show(); + } + }); + + exports.app = app; + exports.tray = tray; +}; + +app.on('ready', createWindow); +app.on('activate', () => { + if (mainWindow === null) createWindow(); +}); +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit(); +}); diff --git a/config/webpack-dev.config.js b/config/webpack-dev.config.js index a1547de..8d3f7e7 100644 --- a/config/webpack-dev.config.js +++ b/config/webpack-dev.config.js @@ -1,7 +1,7 @@ const path = require('path'); const mainWebpack = require('./webpack-main.config'); -const outputPath = path.resolve(__dirname, '../', 'dist'); +const outputPath = path.resolve(__dirname, '../', 'build'); module.exports = { output: { diff --git a/config/webpack-prod.config.js b/config/webpack-prod.config.js index 75a2623..7e030a0 100644 --- a/config/webpack-prod.config.js +++ b/config/webpack-prod.config.js @@ -1,7 +1,7 @@ const path = require('path'); const mainWebpack = require('./webpack-main.config'); -const outputPath = path.resolve(__dirname, '../', 'dist'); +const outputPath = path.resolve(__dirname, '../', 'build'); module.exports = { output: { diff --git a/index.js b/index.js new file mode 100644 index 0000000..6efb44d --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +require('@babel/register'); +require('./config/electron'); diff --git a/package.json b/package.json index 74c732d..42a20e1 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,19 @@ "main": "index.js", "license": "MIT", "scripts": { - "start": "yarn dev", + "start": "concurrently \"cross-env BROWSER=none yarn dev\" \"wait-on http://0.0.0.0:8080 && yarn electron:dev\"", "dev": "webpack-dev-server --config config/webpack-dev.config.js --mode development --open --hot", - "build": "rm -rf dist && webpack --config config/webpack-prod.config.js --mode production --env.NODE_ENV=production", + "build": "rm -rf build && webpack --config config/webpack-prod.config.js --mode production --env.NODE_ENV=production", "lint:precommit": "eslint ./app/", "flow:precommit": "glow", "flow:coverage": "flow-coverage-report -t html -i 'app/**/*.js' -x 'dist/*.js' --threshold 70", - "flow:report": "yarn flow:coverage && cd ./flow-coverage && open index.html" + "flow:report": "yarn flow:coverage && cd ./flow-coverage && open index.html", + "electron:dev": "electron -r @babel/register .", + "electron:prepare": "yarn icon:build && rm -rf dist && mkdir dist", + "electron:pack": "yarn electron:prepare && electron-builder --dir", + "electron:dist": "yarn electron:prepare && electron-builder", + "preelectron:prepare": "yarn build", + "icon:build": "./node_modules/.bin/electron-icon-maker --input=build-assets/icon.png --output=./build" }, "author": { "name": "André Neves", @@ -29,7 +35,16 @@ "@babel/preset-react": "^7.0.0", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.4", + "concurrently": "^4.1.0", + "cross-env": "^5.2.0", "css-loader": "^1.0.1", + "electron": "^3.0.10", + "electron-builder": "^20.36.2", + "electron-icon-maker": "^0.0.4", + "electron-is-dev": "^1.0.1", + "electron-log": "^2.2.17", + "electron-positioner": "^4.1.0", + "electron-updater": "^4.0.4", "eslint": "^5.8.0", "eslint-config-airbnb": "^17.1.0", "eslint-plugin-flowtype": "^3.2.0", @@ -47,12 +62,14 @@ "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "uglifyjs-webpack-plugin": "^2.0.1", + "wait-on": "^3.2.0", "webpack": "^4.4.1", "webpack-bundle-analyzer": "^3.0.3", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.1" }, "dependencies": { + "@babel/register": "^7.0.0", "autoprefixer": "^9.3.1", "connected-react-router": "^5.0.1", "flow-coverage-report": "^0.6.0", @@ -69,5 +86,30 @@ "pre-commit": [ "lint:precommit", "flow:precommit" - ] + ], + "build": { + "appId": "com.zcash", + "productName": "ZCash", + "asar": true, + "directories": { + "buildResources": "build", + "output": "dist" + }, + "files": [ + "./index.js", + "./build/**/*", + "./node_modules/**/*" + ], + "linux": { + "icon": "./build/icons/png" + }, + "mac": { + "category": "public.app-category.productivity", + "type": "distribution", + "target": [ + "pkg", + "dmg" + ] + } + } } diff --git a/public/images/zcash-icon.png b/public/images/zcash-icon.png new file mode 100644 index 0000000..060bd00 Binary files /dev/null and b/public/images/zcash-icon.png differ diff --git a/public/index.html b/public/index.html index 130f7d1..c79828f 100644 --- a/public/index.html +++ b/public/index.html @@ -1,21 +1,19 @@
- - - - - + + + + +