mirror of
https://github.com/kremalicious/blowfish.git
synced 2024-11-22 01:36:58 +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