setup user preferences

This commit is contained in:
Matthias Kretschmann 2019-05-08 01:02:02 +02:00
parent 3cb5d1b5ff
commit bfe2a76ad6
Signed by: m
GPG Key ID: 606EEEF3C479A91F
12 changed files with 301 additions and 159 deletions

View File

@ -29,9 +29,6 @@ Clone, add adresses, and run:
git clone git@github.com:kremalicious/ocean-balance.git
cd ocean-balance
# Add one or more Ethereum addresses to config file
vi config.js
# Install dependencies
npm install
# Run the app in dev mode

View File

@ -1,5 +1,4 @@
module.exports = {
accounts: ['ETH ADDRESS 1', 'ETH ADDRESS 2'],
prices: ['eur', 'usd', 'btc', 'eth'],
refreshInterval: '1m',
oceanTokenContract: '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41'

View File

@ -20,6 +20,7 @@
"dependencies": {
"@coingecko/cryptoformat": "^0.3.1",
"@oceanprotocol/typographies": "^0.1.0",
"@reach/router": "^1.2.1",
"ms": "^2.1.1",
"react": "^16.8.6",
"react-dom": "^16.8.6"
@ -38,6 +39,7 @@
"electron-devtools-installer": "^2.2.4",
"electron-installer-dmg": "^2.0.0",
"electron-packager": "^13.1.1",
"electron-store": "^3.2.0",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.2.0",
"eslint-plugin-react": "^7.13.0",

View File

@ -1,9 +1,16 @@
@import '../node_modules/@oceanprotocol/typographies/css/ocean-typo.css';
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #fcfcfc !important;
}
@ -13,12 +20,6 @@ html.dark,
background: #141414 !important;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-size: 13px;
}
@ -64,11 +65,15 @@ button {
font-weight: 600;
}
.app__content {
.app {
padding: 5% 7%;
cursor: default;
height: calc(100vh - 35px);
transition: .15s ease-out;
width: 100%;
}
.app > div {
display: flex;
align-items: center;
justify-content: center;
@ -76,112 +81,6 @@ button {
width: 100%;
}
.fullscreen .app__content {
.fullscreen .app {
transform: translate3d(0, -36px, 0);
}
.main {
width: 100%;
padding: 5%;
background: #fff;
border-radius: 5px;
border: .1rem solid #e2e2e2;
min-height: 222px;
display: flex;
align-items: center;
flex-wrap: wrap;
position: relative;
animation: fadein .5s .5s ease-out;
}
.dark .main {
background: #222;
border-color: #303030;
}
.number-unit-wrap {
display: flex;
width: 100%;
flex-wrap: wrap;
justify-content: space-around;
position: relative;
}
.number-unit {
text-align: center;
flex: 1 1 20%;
margin-top: 5%;
padding-left: 2%;
padding-right: 2%;
}
.label {
color: #8b98a9;
font-size: .85rem;
display: block;
white-space: nowrap;
margin-top: .3rem;
transition: color .2s ease-out;
}
.number-unit:hover .label {
color: #f6388a;
}
.number {
margin: 0;
transition: .15s ease-out;
font-weight: 400;
-webkit-app-region: no-drag;
-webkit-user-select: text;
font-size: 1rem;
display: inline-block;
padding: 0 .3rem;
animation: fadeIn .5s ease-out;
border-radius: 4px;
}
.updated {
animation: updated .5s ease-out;
}
.number-unit-wrap--accounts {
min-height: 55px;
}
.number-unit--main {
padding-bottom: 5%;
border-bottom: 1px solid #e2e2e2;
}
.number-unit--main:hover .label {
color: #8b98a9;
}
.dark .number-unit--main {
border-bottom-color: #303030;
}
.number-unit--main .number {
font-size: 2.5rem;
}
@keyframes updated {
0% {
background: rgba(255, 255, 255, .2);
}
100% {
background: rgba(255, 255, 255, 0);
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@ -1,12 +1,15 @@
import React, { PureComponent } from 'react'
import {
Router,
createMemorySource,
createHistory,
LocationProvider
} from '@reach/router'
import { webFrame } from 'electron'
import AppProvider from './store/AppProvider'
import { Consumer } from './store/createContext'
import Titlebar from './components/Titlebar'
import Total from './components/Total'
import Account from './components/Account'
import Ticker from './components/Ticker'
import Spinner from './components/Spinner'
import Home from './screens/Home'
import Preferences from './screens/Preferences'
import './App.css'
//
@ -15,35 +18,22 @@ import './App.css'
webFrame.setVisualZoomLevelLimits(1, 1)
webFrame.setLayoutZoomLevelLimits(0, 0)
// https://github.com/reach/router/issues/25
const source = createMemorySource('/')
const history = createHistory(source)
export default class App extends PureComponent {
render() {
return (
<AppProvider>
<Titlebar />
<div className="app__content">
<Consumer>
{({ isLoading, accounts }) => (
<>
<main className="main">
{isLoading ? (
<Spinner />
) : (
<>
<Total />
<div className="number-unit-wrap number-unit-wrap--accounts">
{accounts.map((account, i) => (
<Account key={i} account={account} />
))}
</div>
</>
)}
</main>
<Ticker style={isLoading ? { opacity: 0 } : null} />
</>
)}
</Consumer>
<div className="app">
<LocationProvider history={history}>
<Router>
<Home path="/" default />
<Preferences path="preferences" />
</Router>
</LocationProvider>
</div>
</AppProvider>
)

View File

@ -1,5 +1,6 @@
import React from 'react'
import { render } from 'react-dom'
import App from './App'
document.body.style.backgroundColor = '#141414'
@ -9,5 +10,4 @@ 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'))

View File

@ -102,6 +102,8 @@ const createWindow = async () => {
mainWindow.setSize(width, height, true)
})
switchTheme()
// Load menubar menu items
require('./menu.js')
}

105
src/screens/Home.css Normal file
View File

@ -0,0 +1,105 @@
.main {
width: 100%;
padding: 5%;
background: #fff;
border-radius: 5px;
border: .1rem solid #e2e2e2;
min-height: 222px;
display: flex;
align-items: center;
flex-wrap: wrap;
position: relative;
animation: fadein .5s .5s ease-out;
}
.dark .main {
background: #222;
border-color: #303030;
}
.number-unit-wrap {
display: flex;
width: 100%;
flex-wrap: wrap;
justify-content: space-around;
position: relative;
}
.number-unit {
text-align: center;
flex: 1 1 20%;
margin-top: 5%;
padding-left: 2%;
padding-right: 2%;
}
.label {
color: #8b98a9;
font-size: .85rem;
display: block;
white-space: nowrap;
margin-top: .3rem;
transition: color .2s ease-out;
}
.number-unit:hover .label {
color: #f6388a;
}
.number {
margin: 0;
transition: .15s ease-out;
font-weight: 400;
-webkit-app-region: no-drag;
-webkit-user-select: text;
font-size: 1rem;
display: inline-block;
padding: 0 .3rem;
animation: fadeIn .5s ease-out;
border-radius: 4px;
}
.updated {
animation: updated .5s ease-out;
}
.number-unit-wrap--accounts {
min-height: 55px;
}
.number-unit--main {
padding-bottom: 5%;
border-bottom: 1px solid #e2e2e2;
}
.number-unit--main:hover .label {
color: #8b98a9;
}
.dark .number-unit--main {
border-bottom-color: #303030;
}
.number-unit--main .number {
font-size: 2.5rem;
}
@keyframes updated {
0% {
background: rgba(255, 255, 255, .2);
}
100% {
background: rgba(255, 255, 255, 0);
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

41
src/screens/Home.jsx Normal file
View File

@ -0,0 +1,41 @@
import React, { PureComponent } from 'react'
import { Link } from '@reach/router'
import { Consumer } from '../store/createContext'
import Total from '../components/Total'
import Account from '../components/Account'
import Ticker from '../components/Ticker'
import Spinner from '../components/Spinner'
import './Home.css'
export default class Home extends PureComponent {
render() {
return (
<Consumer>
{({ isLoading, accounts, needsConfig }) => (
<>
<Link to="preferences">Settings</Link>
<main className="main">
{needsConfig ? (
'Needs config'
) : isLoading ? (
<Spinner />
) : (
<>
<Total />
<div className="number-unit-wrap number-unit-wrap--accounts">
{accounts.map((account, i) => (
<Account key={i} account={account} />
))}
</div>
</>
)}
</main>
<Ticker style={isLoading ? { opacity: 0 } : null} />
</>
)}
</Consumer>
)
}
}

View File

@ -0,0 +1,4 @@
.preferences {
text-align: left;
width: 100%;
}

View File

@ -0,0 +1,76 @@
import React, { PureComponent } from 'react'
import { Link } from '@reach/router'
import Store from 'electron-store'
import './Preferences.css'
export default class Preferences extends PureComponent {
store = new Store()
state = { accounts: [], input: '' }
componentDidMount() {
if (this.store.has('accounts')) {
this.setState({ accounts: this.store.get('accounts') })
}
}
handleInputChange = e => {
this.setState({ input: e.target.value })
}
handleSave = e => {
e.preventDefault()
if (
this.state.input !== '' &&
!this.state.accounts.includes(this.state.input) // duplication check
) {
const joined = [...this.state.accounts, this.state.input]
this.store.set('accounts', joined)
this.setState({ accounts: joined })
}
}
handleDelete = (e, account) => {
e.preventDefault()
let array = this.state.accounts
array = array.filter(item => account !== item)
const index = array.indexOf(account)
if (index > -1) {
array.splice(index, 1)
}
this.store.set('accounts', array)
this.setState({ accounts: array })
}
render() {
return (
<div className="preferences">
Hello Preferences <Link to="/">Close</Link>
<div>
{this.state.accounts &&
this.state.accounts.map(account => (
<div key={account}>
{account}
<button onClick={e => this.handleDelete(e, account)}>
&times;
</button>
</div>
))}
</div>
<form>
<input
type="text"
placeholder="0xxxxxxxx"
value={this.state.input}
onChange={this.handleInputChange}
/>
<button onClick={e => this.handleSave(e)}>Add</button>
</form>
</div>
)
}
}

View File

@ -1,41 +1,66 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import ms from 'ms'
import Store from 'electron-store'
import { Provider } from './createContext'
import {
accounts,
refreshInterval,
oceanTokenContract,
prices
} from '../../config'
import { refreshInterval, prices, oceanTokenContract } from '../../config'
export default class AppProvider extends PureComponent {
static propTypes = {
children: PropTypes.any.isRequired
}
store = new Store()
state = {
isLoading: true,
accounts: [],
currency: 'ocean',
needsConfig: false,
prices: Object.assign(...prices.map(key => ({ [key]: 0 }))),
toggleCurrencies: currency => this.setState({ currency })
}
async componentDidMount() {
const { accountsPref } = await this.getAccounts()
await this.fetchAndSetPrices()
await this.setBalances()
await this.setBalances(accountsPref)
await setInterval(this.fetchAndSetPrices, ms(refreshInterval))
await setInterval(this.setBalances, ms(refreshInterval))
this.setState({ isLoading: false })
// document.addEventListener('DOMContentLoaded', () => {
// this.store.onDidChange('accounts', async (newValue, oldValue) => {
// const { accounts } = await this.getAccounts()
// await this.setBalances(accounts)
// console.log('hello from setting window', newValue, oldValue)
// })
// })
}
componentWillUnmount() {
this.clearAccounts()
}
getAccounts() {
let accountsPref
if (this.store.has('accounts')) {
accountsPref = this.store.get('accounts')
!accountsPref.length
? this.setState({ needsConfig: true })
: this.setState({ needsConfig: false })
} else {
accountsPref = []
}
return { accountsPref }
}
clearAccounts() {
this.setState({ accounts: [] })
}
@ -57,7 +82,7 @@ export default class AppProvider extends PureComponent {
}
}
fetchBalance = async account => {
async fetchBalance(account) {
const json = await this.fetch(
`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${oceanTokenContract}&address=${account}&tag=latest`
)
@ -66,7 +91,7 @@ export default class AppProvider extends PureComponent {
return balance
}
fetchAndSetPrices = async () => {
async fetchAndSetPrices() {
const currencies = prices.join(',')
const json = await this.fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}`
@ -82,12 +107,14 @@ export default class AppProvider extends PureComponent {
})
}
setBalances = async () => {
setBalances(accounts) {
// TODO: make this less lazy and update numbers in place
// when they are changed instead of resetting all to 0 here
this.clearAccounts()
accounts.map(async account => {
const accountsArray = accounts ? accounts : this.state.accounts
accountsArray.map(async account => {
const oceanBalance = await this.fetchBalance(account)
const conversions = Object.assign(