diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d5a26dc..0000000 --- a/.babelrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "presets": [ - [ - "@babel/env", - { - "targets": { - "node": "current" - } - } - ], - "@babel/react" - ], - "plugins": ["@babel/plugin-proposal-class-properties"] -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 37fee84..0000000 --- a/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -# EditorConfig is awesome: http://EditorConfig.org -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -insert_final_newline = true -charset = utf-8 -trim_trailing_whitespace = true diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0f8f543 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +ETHERSCAN_API_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0a06796..f371fb8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ package-lock.json build dist coverage +.next +out +.env \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 49955e2..338a8b9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "semi": false, "singleQuote": true, - "trailingComma": "none" + "trailingComma": "none", + "tabWidth": 2 } diff --git a/.travis.yml b/.travis.yml index d4710b1..b57edb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,10 +22,8 @@ before_script: script: - npm test || travis_terminate 1 - - npm run dist - -after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT + - npm run dist branches: except: diff --git a/README.md b/README.md index 5c85f16..8fe6a3b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Alternatively, you can [build the app on your system](#build-packages). ## Development -The main app is a React app in `src/renderer/` wrapped within an Electron app defined in `src/main/`. +The main app is a React app authored with [Next.js](https://nextjs.org) in `src/renderer/` wrapped within an [Electron](https://www.electronjs.org) app defined in `src/main/`. Clone, and run: @@ -125,7 +125,7 @@ For the GitHub releases steps a GitHub personal access token, exported as `GITHU ```text The MIT License (MIT) -Copyright (c) 2019 Matthias Kretschmann +Copyright (c) 2020 Matthias Kretschmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index ade5651..d82b44b 100644 --- a/package.json +++ b/package.json @@ -5,18 +5,20 @@ "description": "🐡 Simple Electron-based desktop app to retrieve and display your total Ocean Token balances.", "main": "./src/main/index.js", "scripts": { - "test": "npm run lint && jest", - "test:watch": "jest --watch", + "start": "electron .", + "test": "npm run lint && npm run jest", + "test:watch": "npm run jest -- --watch", + "jest": "NODE_ENV=test jest -c tests/jest.config.js", "lint": "eslint --ignore-path .gitignore ./src/**/*.{js,jsx} && stylelint --ignore-path .gitignore ./src/**/*.{css,scss}", - "start": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js", - "build": "cross-env NODE_ENV=production webpack --config webpack.common.config.js", - "package": "electron-builder build -ml -p never", - "package:win": "electron-builder build -w -p never", + "copy:icons": "copy 'src/renderer/images/icon*' build/", + "build:react": "cross-env NODE_ENV=production next build src/renderer && next export src/renderer && npm run copy:icons", + "build:electron": "electron-builder build -ml -p never", + "build:electron:win": "electron-builder build -w -p never", "dist": "./scripts/release-prepare.sh", "release": "release-it --non-interactive", "changelog": "auto-changelog -p", - "format": "prettier --write 'src/**/*.{js,jsx}' && npm run format:css", - "format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'" + "format": "prettier --write --ignore-path .gitignore 'src/**/*.{js,jsx,json}' && npm run format:css", + "format:css": "prettier-stylelint --write --ignore-path .gitignore --quiet 'src/**/*.{css,scss}'" }, "repository": "https://github.com/kremalicious/blowfish.git", "homepage": "https://github.com/kremalicious/blowfish", @@ -27,61 +29,56 @@ }, "license": "MIT", "dependencies": { - "@coingecko/cryptoformat": "^0.3.3", + "@coingecko/cryptoformat": "^0.3.4", + "axios": "^0.19.2", + "electron-is-dev": "^1.1.0", + "electron-next": "^3.1.5", + "electron-store": "^5.1.1", "ethereum-address": "^0.0.4", "ethereum-blockies": "github:MyEtherWallet/blockies", - "ms": "^2.1.2" + "ethjs-unit": "^0.1.6", + "ms": "^2.1.2", + "shortid": "^2.2.15" }, "devDependencies": { "@babel/core": "^7.8.4", - "@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/preset-env": "^7.8.4", - "@babel/preset-react": "^7.8.3", "@jest-runner/electron": "^2.0.3", - "@reach/router": "^1.3.1", "@react-mock/state": "^0.1.8", - "@svgr/webpack": "^5.0.0", - "@testing-library/jest-dom": "^5.0.0", - "@testing-library/react": "^9.4.0", + "@svgr/webpack": "^5.2.0", + "@testing-library/jest-dom": "^5.1.1", + "@testing-library/react": "^9.4.1", "auto-changelog": "^1.16.2", "babel-eslint": "^10.0.3", "babel-jest": "^25.1.0", - "babel-loader": "^8.0.6", - "copy-webpack-plugin": "^5.1.1", + "copy": "^0.3.2", "cross-env": "^7.0.0", - "css-loader": "^3.4.2", - "electron": "^8.0.0", + "dotenv": "^8.2.0", + "electron": "^8.0.1", "electron-builder": "^22.3.2", "electron-devtools-installer": "^2.2.4", - "electron-store": "^5.1.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-react": "^7.18.3", - "file-loader": "^5.0.2", - "html-webpack-plugin": "^3.2.0", "identity-obj-proxy": "^3.0.0", "jest": "^25.1.0", - "mini-css-extract-plugin": "^0.9.0", + "next": "^9.2.2", "prettier": "^1.19.1", "prettier-stylelint": "^0.4.2", "react": "^16.12.0", "react-dom": "^16.12.0", "react-pose": "^4.0.10", - "release-it": "^12.4.3", - "style-loader": "^1.1.3", - "stylelint": "^13.1.0", - "stylelint-config-css-modules": "^2.1.0", - "stylelint-config-standard": "^19.0.0", - "webpack": "^4.41.5", - "webpack-cli": "^3.3.10", - "webpack-dev-server": "^3.10.3" + "release-it": "^12.6.1", + "stylelint": "^13.2.0", + "stylelint-config-css-modules": "^2.2.0", + "stylelint-config-standard": "^20.0.0" }, - "browserslist": "electron >= 6.0", + "browserslist": "electron >= 8.0", "build": { "appId": "com.kremalicious.blowfish", "files": [ - "./build/**/*", "./src/main/**/*", + "./src/renderer/out/**/*", "./src/*.js", "package.json" ], @@ -126,29 +123,5 @@ "npm": { "publish": false } - }, - "jest": { - "rootDir": "src/renderer", - "transform": { - "^.+\\.jsx?$": "babel-jest" - }, - "moduleNameMapper": { - ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", - ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/jest/__mocks__/file-mock.js", - "\\.svg": "/jest/__mocks__/svgr-mock.js" - }, - "testURL": "http://localhost", - "setupFilesAfterEnv": [ - "/jest/setup-test-env.js" - ], - "coverageDirectory": "../../coverage/", - "collectCoverage": true, - "collectCoverageFrom": [ - "/**/*.{js,jsx}", - "!/jest/**/*.{js,jsx}", - "!**/node_modules/**" - ], - "runner": "@jest-runner/electron", - "testEnvironment": "@jest-runner/electron/environment" } } diff --git a/scripts/release-prepare.sh b/scripts/release-prepare.sh index 6b4d8e7..39c546c 100755 --- a/scripts/release-prepare.sh +++ b/scripts/release-prepare.sh @@ -1,8 +1,8 @@ #!/bin/bash -rm -rf {dist,build}/ && \ -npm run build && \ -npm run package && \ +rm -rf {dist,build,src/renderer/.next,src/renderer/out}/ && \ +npm run build:react && \ +npm run build:electron && \ if [ -x "$(command -v docker)" ]; then docker run --rm \ @@ -14,6 +14,5 @@ if [ -x "$(command -v docker)" ]; then -v ~/.cache/electron:/root/.cache/electron \ -v ~/.cache/electron-builder:/root/.cache/electron-builder \ electronuserland/builder:wine \ - /bin/bash -c "npm i && npm run build && npm run package:win" + /bin/bash -c "npm i && npm run build:react && npm run build:electron:win" fi - diff --git a/src/main/index.js b/src/main/index.js index 9e62dfe..51a461d 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -6,6 +6,8 @@ const { nativeTheme, ipcMain } = require('electron') +const prepareNext = require('electron-next') +const isDev = require('electron-is-dev') const pkg = require('../../package.json') const buildMenu = require('./menu') const { buildTouchbar, updateTouchbar } = require('./touchbar') @@ -13,16 +15,6 @@ const { rgbaToHex } = require('../utils') let mainWindow -// Keep a reference for dev mode -let isDev = false -if ( - process.defaultApp || - /[\\/]electron-prebuilt[\\/]/.test(process.execPath) || - /[\\/]electron[\\/]/.test(process.execPath) -) { - isDev = true -} - const width = 640 const height = 450 @@ -44,6 +36,7 @@ const createWindow = async () => { autoHideMenuBar: true, webPreferences: { nodeIntegration: true, + preload: path.join(__dirname, 'preload.js'), scrollBounce: true, enableBlinkFeatures: 'OverlayScrollbars' } @@ -51,8 +44,8 @@ const createWindow = async () => { mainWindow.loadURL( isDev - ? 'http://localhost:8080' - : `file://${path.join(__dirname, '../../build/index.html')}` + ? 'http://localhost:8000' + : `file://${path.join(__dirname, '../renderer/out/index.html')}` ) createWindowEvents(mainWindow) @@ -85,18 +78,22 @@ const createWindow = async () => { } } -app.on('ready', () => { - createWindow() +app.on('ready', async () => { + await prepareNext('./src/renderer') + await createWindow() mainWindow.webContents.on('dom-ready', () => { switchTheme() - switchAccentColor() // add platform as class mainWindow.webContents.executeJavaScript( `document.getElementsByTagName('html')[0].classList.add('${process.platform}')` ) }) + + mainWindow.webContents.on('did-finish-load', () => { + switchAccentColor() + }) }) // Quit when all windows are closed. @@ -138,22 +135,22 @@ const installDevTools = async mainWindow => { const createWindowEvents = mainWindow => { mainWindow.on('enter-full-screen', () => mainWindow.webContents.executeJavaScript( - 'document.getElementsByTagName(\'html\')[0].classList.add(\'fullscreen\')' + 'document.getElementsByTagName("html")[0].classList.add("fullscreen")' ) ) mainWindow.on('leave-full-screen', () => mainWindow.webContents.executeJavaScript( - 'document.getElementsByTagName(\'html\')[0].classList.remove(\'fullscreen\')' + 'document.getElementsByTagName("html")[0].classList.remove("fullscreen")' ) ) mainWindow.on('blur', () => mainWindow.webContents.executeJavaScript( - 'document.getElementsByTagName(\'html\')[0].classList.add(\'blur\')' + 'document.getElementsByTagName("html")[0].classList.add("blur")' ) ) mainWindow.on('focus', () => mainWindow.webContents.executeJavaScript( - 'document.getElementsByTagName(\'html\')[0].classList.remove(\'blur\')' + 'document.getElementsByTagName("html")[0].classList.remove("blur")' ) ) } @@ -197,10 +194,10 @@ const switchTheme = () => { isDarkMode ? mainWindow.webContents.executeJavaScript( - 'document.getElementsByTagName(\'html\')[0].classList.add(\'dark\')' + 'document.getElementsByTagName("html")[0].classList.add("dark")' ) : mainWindow.webContents.executeJavaScript( - 'document.getElementsByTagName(\'html\')[0].classList.remove(\'dark\')' + 'document.getElementsByTagName("html")[0].classList.remove("dark")' ) } } diff --git a/src/main/preload.js b/src/main/preload.js new file mode 100644 index 0000000..f9d22f4 --- /dev/null +++ b/src/main/preload.js @@ -0,0 +1,12 @@ +const { webFrame, ipcRenderer } = require('electron') +const Store = require('electron-store') + +const store = new Store() + +// Since we disabled nodeIntegration we can reintroduce +// needed node functionality here +process.once('loaded', () => { + global.ipcRenderer = ipcRenderer + global.webFrame = webFrame + global.store = store +}) diff --git a/src/main/touchbar.js b/src/main/touchbar.js index d321bba..0324cfa 100644 --- a/src/main/touchbar.js +++ b/src/main/touchbar.js @@ -8,7 +8,7 @@ const createButton = ( value, key, mainWindow, - accentColor, + accentColor = '#f6388a', currentCurrency = 'ocean' ) => new TouchBarButton({ diff --git a/src/renderer/App.jsx b/src/renderer/App.jsx deleted file mode 100644 index a1c75af..0000000 --- a/src/renderer/App.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useEffect } from 'react' -import PropTypes from 'prop-types' -import { - Router, - createMemorySource, - createHistory, - LocationProvider, - navigate -} from '@reach/router' -import { webFrame, ipcRenderer } from 'electron' -import posed, { PoseGroup } from 'react-pose' -import Titlebar from './components/Titlebar' -import { defaultAnimation } from './components/Animations' -import Home from './screens/Home' -import Preferences from './screens/Preferences' -import styles from './App.module.css' - -// -// Disable zooming -// -webFrame.setVisualZoomLevelLimits(1, 1) -webFrame.setLayoutZoomLevelLimits(0, 0) - -const Animation = posed.div(defaultAnimation) - -// Fix reach-router in packaged Electron -// https://github.com/reach/router/issues/25#issuecomment-394003652 -let source = createMemorySource('/') -let history = createHistory(source) - -const PosedRouter = ({ children }) => ( - - {({ location }) => ( - - - {children} - - - )} - -) - -PosedRouter.propTypes = { - children: PropTypes.any.isRequired -} - -export default function App() { - useEffect(() => { - ipcRenderer.on('goTo', (evt, route) => { - navigate(route) - }) - }, []) - - return ( - <> - {process.platform === 'darwin' && } -
- - - - -
- - ) -} diff --git a/src/renderer/App.test.jsx b/src/renderer/App.test.jsx deleted file mode 100644 index 7145615..0000000 --- a/src/renderer/App.test.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import { render } from '@testing-library/react' -import AppProvider from './store/AppProvider' -import App from './App' - -describe('App', () => { - it('renders correctly', () => { - const { container } = render( - - - - ) - expect(container.firstChild).toBeInTheDocument() - }) -}) diff --git a/src/renderer/Layout.jsx b/src/renderer/Layout.jsx new file mode 100644 index 0000000..29d2b5d --- /dev/null +++ b/src/renderer/Layout.jsx @@ -0,0 +1,26 @@ +import React from 'react' +import PropTypes from 'prop-types' +import posed, { PoseGroup } from 'react-pose' +import shortid from 'shortid' +import { defaultAnimation } from './components/Animations' +import Titlebar from './components/Titlebar' +import styles from './Layout.module.css' + +const Animation = posed.div(defaultAnimation) + +export default function Layout({ children }) { + return ( + <> + {process.platform === 'darwin' && } +
+ + {children} + +
+ + ) +} + +Layout.propTypes = { + children: PropTypes.any.isRequired +} diff --git a/src/renderer/App.module.css b/src/renderer/Layout.module.css similarity index 100% rename from src/renderer/App.module.css rename to src/renderer/Layout.module.css diff --git a/src/renderer/screens/Home/Accounts.jsx b/src/renderer/components/Home/Accounts.jsx similarity index 93% rename from src/renderer/screens/Home/Accounts.jsx rename to src/renderer/components/Home/Accounts.jsx index d0ebc14..82eac45 100644 --- a/src/renderer/screens/Home/Accounts.jsx +++ b/src/renderer/components/Home/Accounts.jsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react' import { openUrl } from '../../../utils' -import Balance from '../../components/Balance' +import Balance from '../Balance' import { AppContext } from '../../store/createContext' import styles from './Accounts.module.css' diff --git a/src/renderer/components/Home/Accounts.module.css b/src/renderer/components/Home/Accounts.module.css new file mode 100644 index 0000000..2646b25 --- /dev/null +++ b/src/renderer/components/Home/Accounts.module.css @@ -0,0 +1,5 @@ +.accounts { + composes: balancewrap from '../../pages/index.module.css'; + min-height: 55px; + padding-top: 2rem; +} diff --git a/src/renderer/screens/Home/Ticker.jsx b/src/renderer/components/Home/Ticker.jsx similarity index 79% rename from src/renderer/screens/Home/Ticker.jsx rename to src/renderer/components/Home/Ticker.jsx index 1e69168..0e29eb5 100644 --- a/src/renderer/screens/Home/Ticker.jsx +++ b/src/renderer/components/Home/Ticker.jsx @@ -1,16 +1,16 @@ import React, { useContext } from 'react' import PropTypes from 'prop-types' import posed, { PoseGroup } from 'react-pose' -import { AppContext } from '../../store/createContext' +import { AppContext, PriceContext } from '../../store/createContext' import { cryptoFormatter } from '../../../utils' -import stylesIndex from './index.module.css' +import stylesIndex from '../../pages/index.module.css' import styles from './Ticker.module.css' -import { fadeIn } from '../../components/Animations' +import { fadeIn } from '../Animations' const Item = posed.div(fadeIn) const Change = ({ currency }) => { - const { priceChanges } = useContext(AppContext) + const { priceChanges } = useContext(PriceContext) const isNegative = JSON.stringify(priceChanges[currency]).startsWith('-') let classes = isNegative ? styles.negative : styles.positive @@ -27,13 +27,10 @@ Change.propTypes = { } const Items = () => { - const { - prices, - needsConfig, - currency, - toggleCurrencies, - accentColor - } = useContext(AppContext) + const { prices } = useContext(PriceContext) + const { needsConfig, currency, toggleCurrencies, accentColor } = useContext( + AppContext + ) const activeStyle = { backgroundColor: accentColor, @@ -60,7 +57,7 @@ const Items = () => { const Ticker = props => (