diff --git a/README.md b/README.md index 7544918..051662d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config.js b/config.js index d3a8d46..e6d0825 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,4 @@ module.exports = { - accounts: ['ETH ADDRESS 1', 'ETH ADDRESS 2'], prices: ['eur', 'usd', 'btc', 'eth'], refreshInterval: '1m', oceanTokenContract: '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41' diff --git a/package.json b/package.json index ddda7bb..54f9b24 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.css b/src/App.css index 07d9ce3..d244c75 100644 --- a/src/App.css +++ b/src/App.css @@ -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; - } -} diff --git a/src/App.jsx b/src/App.jsx index c869b60..d9514c3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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 ( -
- - {({ isLoading, accounts }) => ( - <> -
- {isLoading ? ( - - ) : ( - <> - - -
- {accounts.map((account, i) => ( - - ))} -
- - )} -
- - - - )} -
+
+ + + + + +
) diff --git a/src/index.js b/src/index.js index 95f1224..181b021 100644 --- a/src/index.js +++ b/src/index.js @@ -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(, document.getElementById('root')) diff --git a/src/main.js b/src/main.js index 5769afd..5f3033e 100644 --- a/src/main.js +++ b/src/main.js @@ -102,6 +102,8 @@ const createWindow = async () => { mainWindow.setSize(width, height, true) }) + switchTheme() + // Load menubar menu items require('./menu.js') } diff --git a/src/screens/Home.css b/src/screens/Home.css new file mode 100644 index 0000000..fbdc43c --- /dev/null +++ b/src/screens/Home.css @@ -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; + } +} diff --git a/src/screens/Home.jsx b/src/screens/Home.jsx new file mode 100644 index 0000000..a0a631a --- /dev/null +++ b/src/screens/Home.jsx @@ -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 ( + + {({ isLoading, accounts, needsConfig }) => ( + <> + Settings +
+ {needsConfig ? ( + 'Needs config' + ) : isLoading ? ( + + ) : ( + <> + + +
+ {accounts.map((account, i) => ( + + ))} +
+ + )} +
+ + + + )} +
+ ) + } +} diff --git a/src/screens/Preferences.css b/src/screens/Preferences.css new file mode 100644 index 0000000..46d48f9 --- /dev/null +++ b/src/screens/Preferences.css @@ -0,0 +1,4 @@ +.preferences { + text-align: left; + width: 100%; +} diff --git a/src/screens/Preferences.jsx b/src/screens/Preferences.jsx new file mode 100644 index 0000000..b61dc79 --- /dev/null +++ b/src/screens/Preferences.jsx @@ -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 ( +
+ Hello Preferences Close +
+ {this.state.accounts && + this.state.accounts.map(account => ( +
+ {account} + +
+ ))} +
+
+ + +
+
+ ) + } +} diff --git a/src/store/AppProvider.jsx b/src/store/AppProvider.jsx index bbb7464..6bcf4d3 100644 --- a/src/store/AppProvider.jsx +++ b/src/store/AppProvider.jsx @@ -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(