Merge pull request #10 from kremalicious/feature/touchbar-electron
Electron Touch Bar
18
README.md
@ -26,6 +26,7 @@
|
|||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Download](#download)
|
- [Download](#download)
|
||||||
- [Development](#development)
|
- [Development](#development)
|
||||||
|
- [Configuration](#configuration)
|
||||||
- [Build packages](#build-packages)
|
- [Build packages](#build-packages)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
@ -39,9 +40,10 @@
|
|||||||
- re-fetches everything automatically every minute
|
- re-fetches everything automatically every minute
|
||||||
- balances are fetched via etherscan.io API
|
- balances are fetched via etherscan.io API
|
||||||
- spot prices are fetched from coingecko.com API
|
- spot prices are fetched from coingecko.com API
|
||||||
- detects system locale for number formatting
|
|
||||||
- detects dark appearance setting and switches to dark theme automatically (macOS only)
|
- detects dark appearance setting and switches to dark theme automatically (macOS only)
|
||||||
- detects system accent color and uses it as primary color (macOS & Windows only)
|
- detects system accent color and uses it as primary color (macOS & Windows only)
|
||||||
|
- Touch Bar support (macOS only)
|
||||||
|
- detects system locale for number formatting
|
||||||
- currently highly optimized for macOS, your mileage on Windows or Linux may vary
|
- currently highly optimized for macOS, your mileage on Windows or Linux may vary
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
@ -57,6 +59,8 @@ Alternatively, you can [build the app on your system](#build-packages).
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
The main app is a React app in `src/renderer/` wrapped within an Electron app defined in `src/main/`.
|
||||||
|
|
||||||
Clone, and run:
|
Clone, and run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -70,6 +74,18 @@ npm install
|
|||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The app has a settings screen where you can add your account addresses.
|
||||||
|
|
||||||
|
When building the app yourself, you can configure more in the `src/config.js` file:
|
||||||
|
|
||||||
|
| Key | Description |
|
||||||
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `conversions` | Array defining the currencies the Ocean balance is converted to. Every currency listed here will appear in the ticker buttons. |
|
||||||
|
| `refreshInterval` | Defines the interval prices and balances are refetched. |
|
||||||
|
| `oceanTokenContract` | Contract address of the Ocean Token. You should not change this. |
|
||||||
|
|
||||||
## Build packages
|
## Build packages
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
26
package.json
@ -3,7 +3,7 @@
|
|||||||
"productName": "Blowfish",
|
"productName": "Blowfish",
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"description": "🐡 Simple Electron-based desktop app to retrieve and display your total Ocean Token balances.",
|
"description": "🐡 Simple Electron-based desktop app to retrieve and display your total Ocean Token balances.",
|
||||||
"main": "./src/main.js",
|
"main": "./src/main/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "eslint ./src/**/*.{js,jsx} && stylelint ./src/**/*.css",
|
"test": "eslint ./src/**/*.{js,jsx} && stylelint ./src/**/*.css",
|
||||||
"start": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js",
|
"start": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js",
|
||||||
@ -21,28 +21,23 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coingecko/cryptoformat": "^0.3.1",
|
"@coingecko/cryptoformat": "^0.3.1",
|
||||||
"@reach/router": "^1.2.1",
|
|
||||||
"ethereum-address": "0.0.4",
|
"ethereum-address": "0.0.4",
|
||||||
"ms": "^2.1.1",
|
"ms": "^2.1.1"
|
||||||
"react": "^16.8.6",
|
|
||||||
"react-blockies": "^1.4.1",
|
|
||||||
"react-dom": "^16.8.6",
|
|
||||||
"react-pose": "^4.0.8",
|
|
||||||
"react-touchbar-electron": "0.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.4.4",
|
"@babel/core": "^7.4.5",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||||
"@babel/plugin-transform-runtime": "^7.4.4",
|
"@babel/plugin-transform-runtime": "^7.4.4",
|
||||||
"@babel/preset-env": "^7.4.4",
|
"@babel/preset-env": "^7.4.5",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@babel/runtime": "^7.4.4",
|
"@babel/runtime": "^7.4.5",
|
||||||
|
"@reach/router": "^1.2.1",
|
||||||
"@svgr/webpack": "^4.2.0",
|
"@svgr/webpack": "^4.2.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"copy-webpack-plugin": "^5.0.3",
|
"copy-webpack-plugin": "^5.0.3",
|
||||||
"css-loader": "^2.1.1",
|
"css-loader": "^2.1.1",
|
||||||
"electron": "^6.0.0-beta.3",
|
"electron": "^5.0.2",
|
||||||
"electron-builder": "^20.40.2",
|
"electron-builder": "^20.40.2",
|
||||||
"electron-devtools-installer": "^2.2.4",
|
"electron-devtools-installer": "^2.2.4",
|
||||||
"electron-store": "^3.2.0",
|
"electron-store": "^3.2.0",
|
||||||
@ -53,10 +48,14 @@
|
|||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"prettier": "^1.17.0",
|
"prettier": "^1.17.0",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
|
"react": "^16.8.6",
|
||||||
|
"react-blockies": "^1.4.1",
|
||||||
|
"react-dom": "^16.8.6",
|
||||||
|
"react-pose": "^4.0.8",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
"stylelint": "^10.0.1",
|
"stylelint": "^10.0.1",
|
||||||
"stylelint-config-standard": "^18.3.0",
|
"stylelint-config-standard": "^18.3.0",
|
||||||
"webpack": "^4.31.0",
|
"webpack": "^4.32.2",
|
||||||
"webpack-cli": "^3.3.2",
|
"webpack-cli": "^3.3.2",
|
||||||
"webpack-dev-server": "^3.3.1"
|
"webpack-dev-server": "^3.3.1"
|
||||||
},
|
},
|
||||||
@ -65,6 +64,7 @@
|
|||||||
"appId": "com.kremalicious.blowfish",
|
"appId": "com.kremalicious.blowfish",
|
||||||
"files": [
|
"files": [
|
||||||
"./build/**/*",
|
"./build/**/*",
|
||||||
|
"./src/main/**/*",
|
||||||
"./src/*.js",
|
"./src/*.js",
|
||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { TouchBar, Button } from 'react-touchbar-electron'
|
|
||||||
import { cryptoFormatter } from '../../utils'
|
|
||||||
import { AppContext } from '../store/createContext'
|
|
||||||
|
|
||||||
const TouchbarItems = ({ prices, currency, toggleCurrencies, accentColor }) => (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
label={cryptoFormatter(1, 'ocean')}
|
|
||||||
onClick={() => toggleCurrencies('ocean')}
|
|
||||||
backgroundColor={currency === 'ocean' ? accentColor : '#141414'}
|
|
||||||
/>
|
|
||||||
{Object.keys(prices).map(key => (
|
|
||||||
<Button
|
|
||||||
key={key}
|
|
||||||
label={cryptoFormatter(prices[key], key)}
|
|
||||||
onClick={() => toggleCurrencies(key)}
|
|
||||||
backgroundColor={
|
|
||||||
currency !== 'ocean' && currency === key ? accentColor : '#141414'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
|
|
||||||
TouchbarItems.propTypes = {
|
|
||||||
prices: PropTypes.object.isRequired,
|
|
||||||
currency: PropTypes.string.isRequired,
|
|
||||||
toggleCurrencies: PropTypes.func.isRequired,
|
|
||||||
accentColor: PropTypes.string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Touchbar extends PureComponent {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<TouchBar>
|
|
||||||
<TouchbarItems
|
|
||||||
prices={this.context.prices}
|
|
||||||
currency={this.context.currency}
|
|
||||||
toggleCurrencies={this.context.toggleCurrencies}
|
|
||||||
accentColor={this.context.accentColor}
|
|
||||||
/>
|
|
||||||
</TouchBar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Touchbar.contextType = AppContext
|
|
@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
prices: ['eur', 'usd', 'btc', 'eth'],
|
conversions: ['eur', 'usd', 'btc', 'eth'],
|
||||||
refreshInterval: '1m',
|
refreshInterval: '1m',
|
||||||
oceanTokenContract: '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41'
|
oceanTokenContract: '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41'
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { app, BrowserWindow, systemPreferences } = require('electron')
|
const { app, BrowserWindow, systemPreferences, ipcMain } = require('electron')
|
||||||
const { touchBarWrapper } = require('react-touchbar-electron')
|
const pkg = require('../../package.json')
|
||||||
const pkg = require('../package.json')
|
|
||||||
const buildMenu = require('./menu')
|
const buildMenu = require('./menu')
|
||||||
const { rgbaToHex } = require('./utils')
|
const { buildTouchbar, updateTouchbar } = require('./touchbar')
|
||||||
|
const { rgbaToHex } = require('../utils')
|
||||||
|
|
||||||
let mainWindow
|
let mainWindow
|
||||||
|
|
||||||
@ -20,6 +20,84 @@ if (
|
|||||||
const width = 620
|
const width = 620
|
||||||
const height = 440
|
const height = 440
|
||||||
|
|
||||||
|
const createWindow = async () => {
|
||||||
|
const isDarkMode = systemPreferences.isDarkMode()
|
||||||
|
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
minWidth: width,
|
||||||
|
minHeight: height,
|
||||||
|
acceptFirstMouse: true,
|
||||||
|
titleBarStyle: 'hiddenInset',
|
||||||
|
fullscreenWindowTitle: true,
|
||||||
|
backgroundColor: isDarkMode ? '#141414' : '#fff',
|
||||||
|
frame: false,
|
||||||
|
show: false,
|
||||||
|
title: pkg.productName,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
scrollBounce: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.loadURL(
|
||||||
|
isDev
|
||||||
|
? 'http://localhost:8080'
|
||||||
|
: `file://${path.join(__dirname, '../../build/index.html')}`
|
||||||
|
)
|
||||||
|
|
||||||
|
createWindowEvents(mainWindow)
|
||||||
|
installDevTools(mainWindow)
|
||||||
|
|
||||||
|
mainWindow.once('ready-to-show', () => {
|
||||||
|
mainWindow.show()
|
||||||
|
mainWindow.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.on('closed', () => {
|
||||||
|
mainWindow = null
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load menubar
|
||||||
|
buildMenu(mainWindow)
|
||||||
|
// Load touchbar
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
const accentColor = getAccentColor()
|
||||||
|
buildTouchbar(mainWindow, accentColor)
|
||||||
|
|
||||||
|
ipcMain.on('prices-updated', (event, pricesNew) => {
|
||||||
|
updateTouchbar(pricesNew, mainWindow, accentColor)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('currency-updated', (event, pricesNew, currentCurrency) => {
|
||||||
|
updateTouchbar(pricesNew, mainWindow, accentColor, currentCurrency)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('ready', () => {
|
||||||
|
createWindow()
|
||||||
|
|
||||||
|
mainWindow.webContents.on('dom-ready', () => {
|
||||||
|
switchTheme()
|
||||||
|
switchAccentColor()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Quit when all windows are closed.
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (mainWindow === null) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const installDevTools = async mainWindow => {
|
const installDevTools = async mainWindow => {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const {
|
const {
|
||||||
@ -66,80 +144,17 @@ const createWindowEvents = mainWindow => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createWindow = async () => {
|
|
||||||
const isDarkMode = systemPreferences.isDarkMode()
|
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
minWidth: width,
|
|
||||||
minHeight: height,
|
|
||||||
acceptFirstMouse: true,
|
|
||||||
titleBarStyle: 'hiddenInset',
|
|
||||||
fullscreenWindowTitle: true,
|
|
||||||
backgroundColor: isDarkMode ? '#141414' : '#fff',
|
|
||||||
frame: false,
|
|
||||||
show: false,
|
|
||||||
title: pkg.productName,
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: true,
|
|
||||||
scrollBounce: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
mainWindow.loadURL(
|
|
||||||
isDev
|
|
||||||
? 'http://localhost:8080'
|
|
||||||
: `file://${path.join(__dirname, '../build/index.html')}`
|
|
||||||
)
|
|
||||||
|
|
||||||
createWindowEvents(mainWindow)
|
|
||||||
installDevTools(mainWindow)
|
|
||||||
|
|
||||||
mainWindow.once('ready-to-show', () => {
|
|
||||||
mainWindow.show()
|
|
||||||
mainWindow.focus()
|
|
||||||
})
|
|
||||||
|
|
||||||
mainWindow.on('closed', () => {
|
|
||||||
mainWindow = null
|
|
||||||
})
|
|
||||||
|
|
||||||
// Load menubar
|
|
||||||
buildMenu(mainWindow)
|
|
||||||
// Load touchbar
|
|
||||||
process.platform === 'darwin' && touchBarWrapper(mainWindow)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('ready', () => {
|
|
||||||
createWindow()
|
|
||||||
|
|
||||||
mainWindow.webContents.on('dom-ready', () => {
|
|
||||||
switchTheme()
|
|
||||||
switchAccentColor()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Quit when all windows are closed.
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
|
||||||
if (mainWindow === null) {
|
|
||||||
createWindow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Accent color setting
|
// Accent color setting
|
||||||
// macOS & Windows
|
// macOS & Windows
|
||||||
//
|
//
|
||||||
const switchAccentColor = () => {
|
const getAccentColor = () => {
|
||||||
const systemAccentColor = systemPreferences.getAccentColor()
|
const systemAccentColor = systemPreferences.getAccentColor()
|
||||||
const accentColor = rgbaToHex(systemAccentColor)
|
return rgbaToHex(systemAccentColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchAccentColor = () => {
|
||||||
|
const accentColor = getAccentColor()
|
||||||
mainWindow.webContents.send('accent-color', accentColor)
|
mainWindow.webContents.send('accent-color', accentColor)
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
const { app, Menu } = require('electron')
|
const { app, Menu } = require('electron')
|
||||||
const { openUrl } = require('./utils')
|
const { openUrl } = require('../utils')
|
||||||
const { homepage } = require('../package.json')
|
const { homepage } = require('../../package.json')
|
||||||
|
|
||||||
const buildMenu = mainWindow => {
|
const buildMenu = mainWindow => {
|
||||||
const template = [
|
const template = [
|
54
src/main/touchbar.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const { TouchBar } = require('electron')
|
||||||
|
const { cryptoFormatter } = require('../utils')
|
||||||
|
const { conversions } = require('../config')
|
||||||
|
|
||||||
|
const { TouchBarButton } = TouchBar
|
||||||
|
|
||||||
|
const createButton = (
|
||||||
|
value,
|
||||||
|
key,
|
||||||
|
mainWindow,
|
||||||
|
accentColor,
|
||||||
|
currentCurrency = 'ocean'
|
||||||
|
) =>
|
||||||
|
new TouchBarButton({
|
||||||
|
label: cryptoFormatter(value, key),
|
||||||
|
click: () => mainWindow.webContents.send('setCurrency', key),
|
||||||
|
backgroundColor: key === currentCurrency ? accentColor : '#141414'
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildTouchbar = (mainWindow, accentColor) => {
|
||||||
|
const touchBar = new TouchBar({
|
||||||
|
items: [
|
||||||
|
createButton(1, 'ocean', mainWindow, accentColor),
|
||||||
|
...conversions.map(key => createButton(0, key, mainWindow, accentColor))
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.setTouchBar(touchBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTouchbar = (
|
||||||
|
pricesNew,
|
||||||
|
mainWindow,
|
||||||
|
accentColor,
|
||||||
|
currentCurrency = 'ocean'
|
||||||
|
) => {
|
||||||
|
const items = pricesNew.map(item => {
|
||||||
|
return createButton(
|
||||||
|
item[1],
|
||||||
|
item[0],
|
||||||
|
mainWindow,
|
||||||
|
accentColor,
|
||||||
|
currentCurrency
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const touchBar = new TouchBar({
|
||||||
|
items: [...items]
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.setTouchBar(touchBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { buildTouchbar, updateTouchbar }
|
@ -8,7 +8,6 @@ import Home from './screens/Home'
|
|||||||
import Preferences from './screens/Preferences'
|
import Preferences from './screens/Preferences'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import { defaultAnimation } from './components/Animations'
|
import { defaultAnimation } from './components/Animations'
|
||||||
import Touchbar from './components/Touchbar'
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Disable zooming
|
// Disable zooming
|
||||||
@ -51,8 +50,6 @@ export default class App extends PureComponent {
|
|||||||
<Preferences path="/preferences" />
|
<Preferences path="/preferences" />
|
||||||
</PosedRouter>
|
</PosedRouter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Touchbar />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -11,8 +11,10 @@ export default class Ticker extends PureComponent {
|
|||||||
static contextType = AppContext
|
static contextType = AppContext
|
||||||
|
|
||||||
items = activeStyle =>
|
items = activeStyle =>
|
||||||
Object.keys(this.context.prices).map((key, i) => (
|
// convert Map to array first, cause for...of or forEach returns undefined,
|
||||||
<Item key={i} className="number-unit">
|
// so it cannot be mapped to a collection of elements
|
||||||
|
[...this.context.prices.entries()].map(([key, value]) => (
|
||||||
|
<Item key={key} className="number-unit">
|
||||||
<button
|
<button
|
||||||
className="label label--price"
|
className="label label--price"
|
||||||
onClick={() => this.context.toggleCurrencies(key)}
|
onClick={() => this.context.toggleCurrencies(key)}
|
||||||
@ -23,7 +25,7 @@ export default class Ticker extends PureComponent {
|
|||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{cryptoFormatter(this.context.prices[key], key)}
|
{cryptoFormatter(value, key)}
|
||||||
</button>
|
</button>
|
||||||
</Item>
|
</Item>
|
||||||
))
|
))
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import { AppContext } from '../store/createContext'
|
import { AppContext } from '../store/createContext'
|
||||||
import Balance from './Balance'
|
import Balance from './Balance'
|
||||||
import { prices } from '../config'
|
import { conversions } from '../../config'
|
||||||
|
|
||||||
const calculateTotalBalance = (accounts, currency) => {
|
const calculateTotalBalance = (accounts, currency) => {
|
||||||
const balanceTotalArray = []
|
const balanceTotalArray = []
|
||||||
@ -23,15 +23,15 @@ export default class Total extends PureComponent {
|
|||||||
static contextType = AppContext
|
static contextType = AppContext
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const conversions = Object.assign(
|
const conversionsBalance = Object.assign(
|
||||||
...prices.map(key => ({
|
...conversions.map(key => ({
|
||||||
[key]: calculateTotalBalance(this.context.accounts, key)
|
[key]: calculateTotalBalance(this.context.accounts, key)
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
const balanceNew = {
|
const balanceNew = {
|
||||||
ocean: calculateTotalBalance(this.context.accounts, 'ocean'),
|
ocean: calculateTotalBalance(this.context.accounts, 'ocean'),
|
||||||
...conversions
|
...conversionsBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
Before Width: | Height: | Size: 871 B After Width: | Height: | Size: 871 B |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@ -1,6 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
import { TouchBarProvider } from 'react-touchbar-electron'
|
|
||||||
import AppProvider from './store/AppProvider'
|
import AppProvider from './store/AppProvider'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
@ -13,9 +12,7 @@ document.body.appendChild(root)
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<AppProvider>
|
<AppProvider>
|
||||||
<TouchBarProvider>
|
<App />
|
||||||
<App />
|
|
||||||
</TouchBarProvider>
|
|
||||||
</AppProvider>,
|
</AppProvider>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
)
|
)
|
@ -4,8 +4,14 @@ import ms from 'ms'
|
|||||||
import { ipcRenderer } from 'electron'
|
import { ipcRenderer } from 'electron'
|
||||||
import Store from 'electron-store'
|
import Store from 'electron-store'
|
||||||
import { AppContext } from './createContext'
|
import { AppContext } from './createContext'
|
||||||
import fetchData from '../util/fetch'
|
import fetchData from '../utils/fetch'
|
||||||
import { refreshInterval, prices, oceanTokenContract } from '../config'
|
import { refreshInterval, conversions, oceanTokenContract } from '../../config'
|
||||||
|
|
||||||
|
// construct initial prices Map to get consistent
|
||||||
|
// order for Ticker and Touchbar
|
||||||
|
let pricesMap = new Map()
|
||||||
|
pricesMap.set('ocean', 1)
|
||||||
|
conversions.map(key => pricesMap.set(key, 0))
|
||||||
|
|
||||||
export default class AppProvider extends PureComponent {
|
export default class AppProvider extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -19,18 +25,26 @@ export default class AppProvider extends PureComponent {
|
|||||||
accounts: [],
|
accounts: [],
|
||||||
currency: 'ocean',
|
currency: 'ocean',
|
||||||
needsConfig: false,
|
needsConfig: false,
|
||||||
prices: Object.assign(...prices.map(key => ({ [key]: 0 }))),
|
prices: pricesMap,
|
||||||
toggleCurrencies: currency => this.setState({ currency }),
|
toggleCurrencies: currency => this.toggleCurrencies(currency),
|
||||||
setBalances: () => this.setBalances(),
|
setBalances: () => this.setBalances(),
|
||||||
accentColor: ''
|
accentColor: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
// listener for accent color
|
||||||
ipcRenderer.on('accent-color', (event, accentColor) => {
|
ipcRenderer.on('accent-color', (event, accentColor) => {
|
||||||
this.setState({ accentColor })
|
this.setState({ accentColor })
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.fetchAndSetPrices()
|
// listener for touchbar
|
||||||
|
ipcRenderer.on('setCurrency', (evt, currency) =>
|
||||||
|
this.state.toggleCurrencies(currency)
|
||||||
|
)
|
||||||
|
|
||||||
|
const newPrizes = await this.fetchAndSetPrices()
|
||||||
|
this.setState({ prices: newPrizes })
|
||||||
|
|
||||||
await this.setBalances()
|
await this.setBalances()
|
||||||
|
|
||||||
setInterval(this.fetchAndSetPrices, ms(refreshInterval))
|
setInterval(this.fetchAndSetPrices, ms(refreshInterval))
|
||||||
@ -66,19 +80,17 @@ export default class AppProvider extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchAndSetPrices = async () => {
|
fetchAndSetPrices = async () => {
|
||||||
const currencies = prices.join(',')
|
const currencies = conversions.join(',')
|
||||||
const json = await fetchData(
|
const json = await fetchData(
|
||||||
`https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}`
|
`https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const newPrizes = Object.assign(
|
let newPrices = new Map(this.state.prices) // make a shallow copy of the Map
|
||||||
...prices.map(key => ({
|
conversions.map(key => newPrices.set(key, json['ocean-protocol'][key])) // modify the copy
|
||||||
ocean: 1,
|
|
||||||
[key]: json['ocean-protocol'][key]
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
this.setState({ prices: newPrizes })
|
ipcRenderer.send('prices-updated', Array.from(newPrices)) // convert Map to array, ipc messages seem to kill it
|
||||||
|
this.setState({ prices: newPrices })
|
||||||
|
return newPrices
|
||||||
}
|
}
|
||||||
|
|
||||||
setBalances = async () => {
|
setBalances = async () => {
|
||||||
@ -89,9 +101,9 @@ export default class AppProvider extends PureComponent {
|
|||||||
for (const account of accountsPref) {
|
for (const account of accountsPref) {
|
||||||
const oceanBalance = await this.getBalance(account)
|
const oceanBalance = await this.getBalance(account)
|
||||||
|
|
||||||
const conversions = Object.assign(
|
const conversionsBalance = Object.assign(
|
||||||
...prices.map(key => ({
|
...conversions.map(key => ({
|
||||||
[key]: oceanBalance * this.state.prices[key] || 0
|
[key]: oceanBalance * this.state.prices.get(key) || 0
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,7 +111,7 @@ export default class AppProvider extends PureComponent {
|
|||||||
address: account,
|
address: account,
|
||||||
balance: {
|
balance: {
|
||||||
ocean: oceanBalance,
|
ocean: oceanBalance,
|
||||||
...conversions
|
...conversionsBalance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +123,12 @@ export default class AppProvider extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleCurrencies(currency) {
|
||||||
|
const pricesNew = Array.from(this.state.prices)
|
||||||
|
ipcRenderer.send('currency-updated', pricesNew, currency)
|
||||||
|
this.setState({ currency })
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider value={this.state}>
|
<AppContext.Provider value={this.state}>
|
@ -15,7 +15,7 @@ const rgbaToHex = color => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const locale =
|
const locale =
|
||||||
typeof navigator !== 'undefined' ? navigator.language : app.getLocale()
|
typeof navigator !== 'undefined' ? navigator.language : () => app.getLocale()
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
|
||||||
const numberFormatter = value =>
|
const numberFormatter = value =>
|
||||||
@ -39,7 +39,7 @@ const cryptoFormatter = (value, currency) => {
|
|||||||
if (currency === 'ocean') {
|
if (currency === 'ocean') {
|
||||||
return formatOcean(value)
|
return formatOcean(value)
|
||||||
} else {
|
} else {
|
||||||
return formatCurrency(value, currency.toUpperCase(), locale.split('-')[0])
|
return formatCurrency(value, currency.toUpperCase(), locale)
|
||||||
.replace(/BTC/, 'Ƀ')
|
.replace(/BTC/, 'Ƀ')
|
||||||
.replace(/ETH/, 'Ξ')
|
.replace(/ETH/, 'Ξ')
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,10 @@ const path = require('path')
|
|||||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
const CopyPlugin = require('copy-webpack-plugin')
|
const CopyPlugin = require('copy-webpack-plugin')
|
||||||
|
|
||||||
// Any directories you will be adding code/files into, need to be added to this array so webpack will pick them up
|
const defaultInclude = [path.resolve(__dirname, 'src', 'renderer')]
|
||||||
const defaultInclude = [path.resolve(__dirname, 'src')]
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: path.resolve(__dirname, 'src') + '/app/index.js',
|
entry: path.resolve(__dirname, 'src', 'renderer', 'index.js'),
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'build'),
|
path: path.resolve(__dirname, 'build'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
@ -33,11 +32,6 @@ module.exports = {
|
|||||||
test: /\.svg$/,
|
test: /\.svg$/,
|
||||||
use: ['@svgr/webpack'],
|
use: ['@svgr/webpack'],
|
||||||
include: defaultInclude
|
include: defaultInclude
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(eot|ttf|woff|woff2)$/,
|
|
||||||
use: ['file-loader?name=font/[name]__[hash:base64:5].[ext]'],
|
|
||||||
include: defaultInclude
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -48,7 +42,7 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin(),
|
new HtmlWebpackPlugin(),
|
||||||
new CopyPlugin([
|
new CopyPlugin([
|
||||||
{ from: './src/app/images/icon.*', to: './', flatten: true }
|
{ from: './src/renderer/images/icon.*', to: './', flatten: true }
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|