mirror of
https://github.com/kremalicious/blowfish.git
synced 2024-12-26 06:37:52 +01:00
initial commit 🐡
This commit is contained in:
commit
a296148f5b
7
.babelrc
Normal file
7
.babelrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"presets": ["@babel/env", "@babel/react"],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
# 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
|
||||
|
||||
[*.css]
|
||||
indent_size = 4
|
27
.eslintrc
Normal file
27
.eslintrc
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"prettier",
|
||||
"prettier/react"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"plugins": ["react"],
|
||||
"rules": {
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "never"]
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"jest": true
|
||||
}
|
||||
}
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
builds
|
||||
dist
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
}
|
11
.stylelintrc
Normal file
11
.stylelintrc
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"./node_modules/prettier-stylelint/config.js"
|
||||
],
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"declaration-empty-line-before": null,
|
||||
"number-leading-zero": "never"
|
||||
}
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
27
README.md
Normal file
27
README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# ocean-balance
|
||||
|
||||
> Simple Electron-based desktop app to retrieve and display your total Ocean balances.
|
||||
|
||||
## Usage
|
||||
|
||||
Clone and run:
|
||||
|
||||
```bash
|
||||
# Clone this repository
|
||||
git clone git@github.com:kremalicious/ocean-balance.git
|
||||
cd ocean-balance
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
# Run the app in dev mode
|
||||
npm start
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
## Build package
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run create-installer-mac
|
||||
```
|
5
constants.js
Normal file
5
constants.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
accounts: ['ETH ADDRESS 1', 'ETH ADDRESS 2'],
|
||||
refreshInterval: '1m',
|
||||
oceanTokenContract: '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41'
|
||||
}
|
53
package.json
Normal file
53
package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "ocean-balance",
|
||||
"productName": "Ocean",
|
||||
"version": "1.0.0",
|
||||
"description": "A minimal Electron application",
|
||||
"main": "./src/main.js",
|
||||
"scripts": {
|
||||
"test": "eslint ./src/**/*.{js,jsx} && stylelint ./app/*.css",
|
||||
"start": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js",
|
||||
"build": "webpack --config webpack.build.config.js && npm run package:mac",
|
||||
"package:mac": "electron-packager . --overwrite --asar=true --platform=darwin --arch=x64 --icon=src/images/app.icns --prune=true --out=./builds && open ./builds",
|
||||
"package:win": "electron-packager . --overwrite --asar=true --platform=win32 --arch=ia32 --icon=assets/icons/win/icon.ico --prune=true --out=./builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Coinbase\"",
|
||||
"package:linux": "electron-packager . --overwrite --asar=true --platform=linux --arch=x64 --icon=assets/icons/png/1024x1024.png --prune=true --out=./builds",
|
||||
"create-installer-mac": "electron-installer-dmg ./builds/Ocean-darwin-x64/Ocean.app Ocean --out=./builds --overwrite --icon=app/app.icns",
|
||||
"create-installer-win": "node installers/windows/createinstaller.js"
|
||||
},
|
||||
"repository": "https://github.com/kremalicious/ocean-balance.git",
|
||||
"author": "Matthias Kretschmann",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||
"@babel/plugin-transform-runtime": "^7.4.4",
|
||||
"@babel/preset-env": "^7.4.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.5",
|
||||
"css-loader": "^2.1.1",
|
||||
"electron": "^5.0.0",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-installer-dmg": "^2.0.0",
|
||||
"electron-packager": "^13.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-prettier": "^4.2.0",
|
||||
"eslint-plugin-react": "^7.13.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"prettier": "^1.17.0",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"style-loader": "^0.23.1",
|
||||
"stylelint": "^10.0.1",
|
||||
"stylelint-config-standard": "^18.3.0",
|
||||
"webpack": "^4.30.0",
|
||||
"webpack-cli": "^3.3.1",
|
||||
"webpack-dev-server": "^3.3.1"
|
||||
}
|
||||
}
|
120
src/App.css
Normal file
120
src/App.css
Normal file
@ -0,0 +1,120 @@
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
background: #141414;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
html.fullscreen {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: relative;
|
||||
font-size: 1rem;
|
||||
line-height: 1.3;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol';
|
||||
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1, 'pnum' 1, 'tnum' 0,
|
||||
'onum' 0, 'lnum' 0, 'dlig' 1;
|
||||
color: #e2e2e2;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
transform: translate3d(0, 0, 0);
|
||||
-webkit-app-region: drag;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.app__content {
|
||||
padding: 5% 7%;
|
||||
cursor: default;
|
||||
height: calc(100vh - 35px);
|
||||
transition: .15s ease-out;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fullscreen .app__content {
|
||||
transform: translate3d(0, -36px, 0);
|
||||
}
|
||||
|
||||
.number-unit-wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.number-unit {
|
||||
text-align: center;
|
||||
flex: 1 1 20%;
|
||||
margin-top: 5%;
|
||||
padding-left: 2%;
|
||||
padding-right: 2%;
|
||||
}
|
||||
|
||||
.number {
|
||||
margin: 0;
|
||||
transition: .15s ease-out;
|
||||
font-weight: 400;
|
||||
-webkit-app-region: no-drag;
|
||||
-webkit-user-select: auto;
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
padding: 0 .3rem;
|
||||
animation: fadein .5s ease-out forwards;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.updated {
|
||||
animation: updated .5s ease-out forwards;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #8b98a9;
|
||||
font-size: .85rem;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@keyframes updated {
|
||||
0% {
|
||||
background: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
100% {
|
||||
background: rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
background: rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
27
src/App.jsx
Normal file
27
src/App.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import './App.css'
|
||||
import React, { PureComponent } from 'react'
|
||||
import { webFrame } from 'electron'
|
||||
import AppProvider from './store/AppProvider'
|
||||
import Titlebar from './components/Titlebar'
|
||||
import Accounts from './components/Accounts'
|
||||
|
||||
//
|
||||
// Disable zooming
|
||||
//
|
||||
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||
webFrame.setLayoutZoomLevelLimits(0, 0)
|
||||
|
||||
class App extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<AppProvider>
|
||||
<Titlebar />
|
||||
<div className="app__content">
|
||||
<Accounts />
|
||||
</div>
|
||||
</AppProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
40
src/components/Account.jsx
Normal file
40
src/components/Account.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { fiatFormatter, numberFormatter } from '../util/moneyFormatter'
|
||||
|
||||
class Account extends PureComponent {
|
||||
static propTypes = {
|
||||
isNativeShown: PropTypes.bool.isRequired,
|
||||
account: PropTypes.shape({
|
||||
address: PropTypes.string.isRequired,
|
||||
balance: PropTypes.shape({
|
||||
ocean: PropTypes.number.isRequired,
|
||||
eur: PropTypes.number.isRequired
|
||||
}).isRequired
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { balance, address } = this.props.account
|
||||
const { ocean, eur } = balance
|
||||
|
||||
return (
|
||||
<div className="number-unit">
|
||||
<h1 className="number">
|
||||
{this.props.isNativeShown ? (
|
||||
<span className="balance-native">{fiatFormatter('EUR', eur)}</span>
|
||||
) : (
|
||||
<span className="balance" title={numberFormatter(ocean)}>
|
||||
{numberFormatter(ocean) || 0} Ọ
|
||||
</span>
|
||||
)}
|
||||
</h1>
|
||||
<span className="label" title={address}>
|
||||
{address.substring(0, 12)}...
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Account
|
29
src/components/Accounts.css
Normal file
29
src/components/Accounts.css
Normal file
@ -0,0 +1,29 @@
|
||||
.main {
|
||||
width: 100%;
|
||||
padding: 5%;
|
||||
background: #303030;
|
||||
border-radius: 5px;
|
||||
border: .1rem solid #41474e;
|
||||
min-height: 186px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.number-unit-wrap--accounts {
|
||||
min-height: 55px;
|
||||
}
|
||||
|
||||
.number-unit--main {
|
||||
padding-bottom: 5%;
|
||||
border-bottom: 1px solid #41474e;
|
||||
}
|
||||
|
||||
.number-unit--main .number {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.number-unit--main .label {
|
||||
font-size: .95rem;
|
||||
}
|
40
src/components/Accounts.jsx
Normal file
40
src/components/Accounts.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import { Consumer } from '../store/createContext'
|
||||
import Total from './Total'
|
||||
import Account from './Account'
|
||||
import './Accounts.css'
|
||||
|
||||
export default class Accounts extends PureComponent {
|
||||
state = {
|
||||
isNativeShown: false
|
||||
}
|
||||
|
||||
toggleBalances = () => {
|
||||
this.setState({ isNativeShown: !this.state.isNativeShown })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<main className="main">
|
||||
<Total />
|
||||
|
||||
<div
|
||||
className="number-unit-wrap number-unit-wrap--accounts"
|
||||
onClick={this.toggleBalances}
|
||||
>
|
||||
<Consumer>
|
||||
{({ accounts }) =>
|
||||
accounts.map((account, i) => (
|
||||
<Account
|
||||
key={i}
|
||||
account={account}
|
||||
isNativeShown={this.state.isNativeShown}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Consumer>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
}
|
48
src/components/Titlebar.css
Normal file
48
src/components/Titlebar.css
Normal file
@ -0,0 +1,48 @@
|
||||
.titlebar {
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
background: linear-gradient(to top, #ccc 0%, #d6d6d6 1px, #ebebeb 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5);
|
||||
transition: opacity .15s ease-out;
|
||||
}
|
||||
|
||||
.dark .titlebar {
|
||||
background: linear-gradient(to top, #141416 0%, #38383c 1px, #3f3f44 100%);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.fullscreen .titlebar {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.blur .titlebar {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.blur.dark .titlebar {
|
||||
background: linear-gradient(to top, #141416 0%, #2d2a32 1px, #2d2a32 100%);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
line-height: 35px;
|
||||
height: 35px;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.dark .header-title {
|
||||
color: #b6b3ba;
|
||||
}
|
||||
|
||||
.blur .header-title {
|
||||
color: #b6b6b6;
|
||||
}
|
||||
|
||||
.blur.dark .header-title {
|
||||
color: #67666e;
|
||||
}
|
10
src/components/Titlebar.jsx
Normal file
10
src/components/Titlebar.jsx
Normal file
@ -0,0 +1,10 @@
|
||||
import './Titlebar.css'
|
||||
import React from 'react'
|
||||
|
||||
const Titlebar = () => (
|
||||
<header className="titlebar">
|
||||
<span className="header-title">Ocean</span>
|
||||
</header>
|
||||
)
|
||||
|
||||
export default Titlebar
|
33
src/components/Total.jsx
Normal file
33
src/components/Total.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
import { Consumer } from '../store/createContext'
|
||||
import { numberFormatter } from '../util/moneyFormatter'
|
||||
|
||||
const calculateTotalBalance = accounts => {
|
||||
const balanceTotalArray = []
|
||||
|
||||
for (const account of accounts) {
|
||||
balanceTotalArray.push(account.balance.ocean)
|
||||
}
|
||||
|
||||
// Convert array to numbers and add numbers together
|
||||
const balanceTotal = balanceTotalArray.reduce(
|
||||
(a, b) => Number(a) + Number(b),
|
||||
0
|
||||
)
|
||||
|
||||
return numberFormatter(balanceTotal)
|
||||
}
|
||||
|
||||
const Total = () => (
|
||||
<div className="number-unit number-unit--main">
|
||||
<Consumer>
|
||||
{({ accounts }) => {
|
||||
const total = calculateTotalBalance(accounts)
|
||||
return <h1 className="number">{total || 0} Ọ</h1>
|
||||
}}
|
||||
</Consumer>
|
||||
<span className="label">Total balance</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Total
|
BIN
src/images/app.icns
Normal file
BIN
src/images/app.icns
Normal file
Binary file not shown.
13
src/index.js
Normal file
13
src/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
document.body.style.backgroundColor = '#141414'
|
||||
|
||||
// Since we are using HtmlWebpackPlugin WITHOUT a template, we should create our own root node in the body element before rendering into it
|
||||
let root = document.createElement('div')
|
||||
root.id = 'root'
|
||||
document.body.appendChild(root)
|
||||
|
||||
// Now we can render our application into it
|
||||
render(<App />, document.getElementById('root'))
|
144
src/main.js
Normal file
144
src/main.js
Normal file
@ -0,0 +1,144 @@
|
||||
const path = require('path')
|
||||
const { app, BrowserWindow, systemPreferences, ipcMain } = require('electron')
|
||||
|
||||
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 = 550
|
||||
const height = 380
|
||||
|
||||
const createWindow = () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: width,
|
||||
height: height,
|
||||
minWidth: width,
|
||||
minHeight: height,
|
||||
// maxWidth: width,
|
||||
// maxHeight: height,
|
||||
acceptFirstMouse: true,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
fullscreenWindowTitle: true,
|
||||
backgroundColor: '#141414',
|
||||
frame: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadURL(
|
||||
isDev
|
||||
? 'http://localhost:8080'
|
||||
: `file://${path.join(__dirname, '../dist/index.html')}`
|
||||
)
|
||||
|
||||
if (isDev) {
|
||||
const {
|
||||
default: installExtension,
|
||||
REACT_DEVELOPER_TOOLS
|
||||
} = require('electron-devtools-installer')
|
||||
|
||||
installExtension(REACT_DEVELOPER_TOOLS)
|
||||
.then(name => {
|
||||
console.log(`Added Extension: ${name}`) // eslint-disable-line no-console
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('An error occurred: ', err) // eslint-disable-line no-console
|
||||
})
|
||||
}
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
mainWindow.focus()
|
||||
})
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null
|
||||
})
|
||||
|
||||
//
|
||||
// Events
|
||||
//
|
||||
mainWindow.on('enter-full-screen', () => {
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'fullscreen\')'
|
||||
)
|
||||
})
|
||||
|
||||
mainWindow.on('leave-full-screen', () => {
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'fullscreen\')'
|
||||
)
|
||||
})
|
||||
|
||||
mainWindow.on('blur', () => {
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'blur\')'
|
||||
)
|
||||
})
|
||||
|
||||
mainWindow.on('focus', () => {
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'blur\')'
|
||||
)
|
||||
})
|
||||
|
||||
// Make window bigger automatically when devtools are opened
|
||||
mainWindow.webContents.on('devtools-opened', () => {
|
||||
mainWindow.setSize(1024, 420, true)
|
||||
})
|
||||
|
||||
mainWindow.webContents.on('devtools-closed', () => {
|
||||
mainWindow.setSize(width, height, true)
|
||||
})
|
||||
|
||||
// Load menubar menu items
|
||||
require('./menu.js')
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
createWindow()
|
||||
|
||||
// Switch to user theme on start, and on reload
|
||||
mainWindow.webContents.on('dom-ready', () => switchTheme())
|
||||
})
|
||||
|
||||
// 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 switchTheme = () => {
|
||||
if (systemPreferences.isDarkMode()) {
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'dark\')'
|
||||
)
|
||||
} else {
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'dark\')'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for theme changes in System Preferences
|
||||
systemPreferences.subscribeNotification(
|
||||
'AppleInterfaceThemeChangedNotification',
|
||||
() => switchTheme()
|
||||
)
|
154
src/menu.js
Normal file
154
src/menu.js
Normal file
@ -0,0 +1,154 @@
|
||||
const { app, Menu } = require('electron')
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
role: 'undo'
|
||||
},
|
||||
{
|
||||
role: 'redo'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
role: 'pasteandmatchstyle'
|
||||
},
|
||||
{
|
||||
role: 'delete'
|
||||
},
|
||||
{
|
||||
role: 'selectall'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
role: 'reload'
|
||||
},
|
||||
{
|
||||
role: 'forcereload'
|
||||
},
|
||||
{
|
||||
role: 'toggledevtools'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'togglefullscreen'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
role: 'close'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click() {
|
||||
require('electron').shell.openExternal('https://electron.atom.io')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
template.unshift({
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{
|
||||
role: 'about'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'services',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'hide'
|
||||
},
|
||||
{
|
||||
role: 'hideothers'
|
||||
},
|
||||
{
|
||||
role: 'unhide'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'quit'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Edit menu
|
||||
template[1].submenu.push(
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Speech',
|
||||
submenu: [
|
||||
{
|
||||
role: 'startspeaking'
|
||||
},
|
||||
{
|
||||
role: 'stopspeaking'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
// Window menu
|
||||
template[3].submenu = [
|
||||
{
|
||||
role: 'close'
|
||||
},
|
||||
{
|
||||
role: 'minimize'
|
||||
},
|
||||
{
|
||||
role: 'zoom'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'front'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
72
src/store/AppProvider.jsx
Normal file
72
src/store/AppProvider.jsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ms from 'ms'
|
||||
import { Provider } from './createContext'
|
||||
import { accounts, refreshInterval, oceanTokenContract } from '../../constants'
|
||||
|
||||
export default class AppProvider extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.any.isRequired
|
||||
}
|
||||
|
||||
state = {
|
||||
accounts: []
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setBalances()
|
||||
setInterval(this.setBalances, ms(refreshInterval))
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearAccounts()
|
||||
}
|
||||
|
||||
clearAccounts() {
|
||||
this.setState({ accounts: [] })
|
||||
}
|
||||
|
||||
fetchBalance = async account => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${oceanTokenContract}&address=${account}`
|
||||
)
|
||||
|
||||
if (response.status !== 200) {
|
||||
return console.log('Non-200 response: ' + response.status) // eslint-disable-line
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
if (!json) return
|
||||
|
||||
const balance = (json.result /= 1000000000000000000) // Convert from wei 10^18
|
||||
return balance
|
||||
} catch (error) {
|
||||
console.log('Error parsing etherscan.io json:' + error) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
setBalances = () => {
|
||||
this.clearAccounts()
|
||||
|
||||
accounts.map(async account => {
|
||||
const oceanBalance = await this.fetchBalance(account)
|
||||
|
||||
const newAccount = {
|
||||
address: account,
|
||||
balance: {
|
||||
ocean: oceanBalance || 0,
|
||||
eur: 0
|
||||
}
|
||||
}
|
||||
|
||||
this.setState(prevState => ({
|
||||
accounts: [...prevState.accounts, newAccount]
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Provider value={this.state}>{this.props.children}</Provider>
|
||||
}
|
||||
}
|
5
src/store/createContext.jsx
Normal file
5
src/store/createContext.jsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { createContext } from 'react'
|
||||
|
||||
const { Provider, Consumer } = createContext()
|
||||
|
||||
export { Provider, Consumer }
|
18
src/util/moneyFormatter.js
Normal file
18
src/util/moneyFormatter.js
Normal file
@ -0,0 +1,18 @@
|
||||
const locale = navigator.language
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
|
||||
const numberFormatter = number =>
|
||||
new Intl.NumberFormat(locale, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2
|
||||
}).format(number)
|
||||
|
||||
const fiatFormatter = (currency, number) =>
|
||||
new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency,
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2
|
||||
}).format(number)
|
||||
|
||||
export { numberFormatter, fiatFormatter }
|
14
webpack.build.config.js
Normal file
14
webpack.build.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
const common = require('./webpack.common.config')
|
||||
|
||||
module.exports = Object.assign({}, common, {
|
||||
mode: 'production',
|
||||
output: {
|
||||
publicPath: './'
|
||||
},
|
||||
stats: {
|
||||
colors: true,
|
||||
children: false,
|
||||
chunks: false,
|
||||
modules: false
|
||||
}
|
||||
})
|
42
webpack.common.config.js
Normal file
42
webpack.common.config.js
Normal file
@ -0,0 +1,42 @@
|
||||
const path = require('path')
|
||||
const HtmlWebpackPlugin = require('html-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')]
|
||||
|
||||
module.exports = {
|
||||
entry: path.resolve(__dirname, 'src') + '/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
include: defaultInclude
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)?$/,
|
||||
use: ['babel-loader'],
|
||||
include: defaultInclude
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif)$/,
|
||||
use: ['file-loader?name=img/[name]__[hash:base64:5].[ext]'],
|
||||
include: defaultInclude
|
||||
},
|
||||
{
|
||||
test: /\.(eot|svg|ttf|woff|woff2)$/,
|
||||
use: ['file-loader?name=font/[name]__[hash:base64:5].[ext]'],
|
||||
include: defaultInclude
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.jsx']
|
||||
},
|
||||
target: 'electron-renderer',
|
||||
plugins: [new HtmlWebpackPlugin()]
|
||||
}
|
24
webpack.dev.config.js
Normal file
24
webpack.dev.config.js
Normal file
@ -0,0 +1,24 @@
|
||||
const path = require('path')
|
||||
const common = require('./webpack.common.config')
|
||||
const { spawn } = require('child_process')
|
||||
|
||||
module.exports = Object.assign({}, common, {
|
||||
mode: 'development',
|
||||
output: {
|
||||
publicPath: '/'
|
||||
},
|
||||
devtool: 'cheap-source-map',
|
||||
devServer: {
|
||||
contentBase: path.resolve(__dirname, 'dist'),
|
||||
stats: 'minimal',
|
||||
before: () => {
|
||||
spawn('electron', ['.'], {
|
||||
shell: true,
|
||||
env: process.env,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
.on('close', () => process.exit(0))
|
||||
.on('error', spawnError => console.error(spawnError)) // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue
Block a user