Skip to content

Commit

Permalink
token manager: state caching, loading indicator and init (#845)
Browse files Browse the repository at this point in the history
* token-manager: use externals and init for caching

* Use updated API which expects an object for every external contract

* Use cached state in init to prevent request

* Add syncing states (faked for now)

* Reduce sync status in background script

* Update apps/token-manager/app/src/script.js

Co-Authored-By: Brett Sun <[email protected]>

* Remove unneeded import

* Handle custom events before contract events

* Upgrade aragon ui

* Token Manager: fix loading indicator not being shown when syncing

* Token Manager: only use the SyncIndicator for the loading states (#873)

* Token Manager: fix lint

* Token Manager: update @aragon/api dependencies

* Cosmetic changes to code

* Add comment about balances

* Use a boolean for loading indicator

* Upgrade @aragon/ui to 0.40.1
  • Loading branch information
2color authored May 29, 2019
1 parent a1ba052 commit 441b51a
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 92 deletions.
6 changes: 3 additions & 3 deletions apps/token-manager/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"private": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
"@aragon/api": "^1.0.0",
"@aragon/api-react": "^1.0.0-beta.2",
"@aragon/ui": "^0.38.1",
"@aragon/api": "^2.0.0-beta.2",
"@aragon/api-react": "^2.0.0-beta.1",
"@aragon/ui": "^0.40.1",
"bn.js": "^4.11.6",
"prop-types": "^15.7.2",
"react": "^16.8.4",
Expand Down
15 changes: 11 additions & 4 deletions apps/token-manager/app/src/App.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import BN from 'bn.js'
import { Badge, Main, SidePanel } from '@aragon/ui'
import { Badge, Main, SidePanel, SyncIndicator } from '@aragon/ui'
import { useAragonApi } from '@aragon/api-react'
import EmptyState from './screens/EmptyState'
import Holders from './screens/Holders'
Expand All @@ -19,9 +19,11 @@ const initialAssignTokensConfig = {
class App extends React.PureComponent {
static propTypes = {
api: PropTypes.object,
isSyncing: PropTypes.bool,
}
static defaultProps = {
appStateReady: false,
isSyncing: true,
holders: [],
connectedAccount: '',
groupMode: false,
Expand Down Expand Up @@ -86,6 +88,7 @@ class App extends React.PureComponent {
appStateReady,
groupMode,
holders,
isSyncing,
maxAccountTokens,
numData,
tokenAddress,
Expand All @@ -98,13 +101,15 @@ class App extends React.PureComponent {
requestMenu,
} = this.props
const { assignTokensConfig, sidepanelOpened } = this.state

return (
<Main assetsUrl="./aragon-ui">
<div css="min-width: 320px">
<IdentityProvider
onResolve={this.handleResolveLocalIdentity}
onShowLocalIdentityModal={this.handleShowLocalIdentityModal}
>
<SyncIndicator visible={isSyncing} />
<AppLayout
title="Token Manager"
afterTitle={tokenSymbol && <Badge.App>{tokenSymbol}</Badge.App>}
Expand Down Expand Up @@ -132,9 +137,11 @@ class App extends React.PureComponent {
onRemoveTokens={this.handleLaunchRemoveTokens}
/>
) : (
<EmptyState
onActivate={this.handleLaunchAssignTokensNoHolder}
/>
!isSyncing && (
<EmptyState
onActivate={this.handleLaunchAssignTokensNoHolder}
/>
)
)}
</AppLayout>
<SidePanel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Badge, IdentityBadge, font } from '@aragon/ui'
import { IdentityContext } from '../IdentityManager/IdentityManager'
import { useIdentity } from '../IdentityManager/IdentityManager'

const LocalIdentityBadge = ({ entity, ...props }) => {
Expand Down
7 changes: 4 additions & 3 deletions apps/token-manager/app/src/components/SideBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const displayedStakes = (accounts, total) => {
}))
}

class SideBar extends React.Component {
class SideBar extends React.PureComponent {
static defaultProps = {
holders: [],
}
Expand Down Expand Up @@ -131,16 +131,17 @@ const Main = styled.aside`
flex-shrink: 0;
flex-grow: 0;
min-height: 100%;
margin-top: 55px;
margin-top: 16px;
padding: 0 20px;
${breakpoint(
'medium',
`
width: 260px;
margin-left: 30px;
margin-top: unset;
margin-top: 0;
padding: 0;
opacity: 1;
`
)};
`
Expand Down
32 changes: 10 additions & 22 deletions apps/token-manager/app/src/screens/Holders.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import {
TabBar,
Expand All @@ -14,11 +15,13 @@ import SideBar from '../components/SideBar'
const TABS = ['Holders', 'Token Info']

class Holders extends React.Component {
state = { selectedTab: 0 }

static propTypes = {
holders: PropTypes.array,
}
static defaultProps = {
holders: [],
}
state = { selectedTab: 0 }
render() {
const {
groupMode,
Expand Down Expand Up @@ -54,7 +57,7 @@ class Holders extends React.Component {
/>
</TabBarWrapper>
)}
<Screen selected={!tabbedNavigation || selectedTab === 0}>
{(!tabbedNavigation || selectedTab === 0) && (
<ResponsiveTable
header={
<TableRow>
Expand Down Expand Up @@ -92,10 +95,10 @@ class Holders extends React.Component {
/>
))}
</ResponsiveTable>
</Screen>
)}
</Main>
<Screen selected={!tabbedNavigation || selectedTab === 1}>
<ResponsiveSideBar
{(!tabbedNavigation || selectedTab === 1) && (
<SideBar
holders={holders}
tokenAddress={tokenAddress}
tokenDecimalsBase={tokenDecimalsBase}
Expand All @@ -105,7 +108,7 @@ class Holders extends React.Component {
tokenTransfersEnabled={tokenTransfersEnabled}
userAccount={userAccount}
/>
</Screen>
)}
</TwoPanels>
)
}}
Expand All @@ -118,8 +121,6 @@ class Holders extends React.Component {
}
}

const Screen = ({ selected, children }) => selected && children

const TabBarWrapper = styled.div`
margin-top: 16px;
& ul {
Expand All @@ -142,18 +143,6 @@ const ResponsiveTable = styled(Table)`
)};
`

const ResponsiveSideBar = styled(SideBar)`
margin-top: 16px;
${breakpoint(
'medium',
`
opacity: 1;
margin-top: 0;
`
)};
`

const Main = styled.div`
max-width: 100%;
Expand All @@ -166,7 +155,6 @@ const Main = styled.div`
`
const TwoPanels = styled.div`
width: 100%;
${breakpoint(
'medium',
`
Expand Down
128 changes: 69 additions & 59 deletions apps/token-manager/app/src/script.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import Aragon from '@aragon/api'
import { of } from 'rxjs'
import Aragon, { events } from '@aragon/api'
import tokenSettings, { hasLoadedTokenSettings } from './token-settings'
import { addressesEqual } from './web3-utils'
import tokenAbi from './abi/minimeToken.json'

const INITIALIZATION_TRIGGER = Symbol('INITIALIZATION_TRIGGER')

const app = new Aragon()

/*
Expand Down Expand Up @@ -47,67 +44,80 @@ retryEvery(retry => {
})
})

async function initialize(tokenAddr) {
const token = app.external(tokenAddr, tokenAbi)
try {
const tokenSymbol = await token.symbol().toPromise()
app.identify(tokenSymbol)
} catch (err) {
console.error(
`Failed to load token symbol for token at ${tokenAddr} due to:`,
err
)
}

return createStore(token, tokenAddr)
}
async function initialize(tokenAddress) {
const token = app.external(tokenAddress, tokenAbi)

// Hook up the script as an aragon.js store
async function createStore(token, tokenAddr) {
return app.store(
async (state, { address, event, returnValues }) => {
let nextState = {
...state,
// Fetch the app's settings, if we haven't already
...(!hasLoadedTokenSettings(state)
? await loadTokenSettings(token)
: {}),
async function reducer(state, { address, event, returnValues }) {
let nextState = {
...state,
}

if (event === events.SYNC_STATUS_SYNCING) {
nextState.isSyncing = true
} else if (event === events.SYNC_STATUS_SYNCED) {
nextState.isSyncing = false
}

if (addressesEqual(address, tokenAddress)) {
switch (event) {
case 'ClaimedTokens':
if (addressesEqual(returnValues._token, tokenAddress)) {
return claimedTokens(token, nextState, returnValues)
}
return nextState
case 'Transfer':
return transfer(token, nextState, returnValues)
default:
return nextState
}
} else {
// Token Manager event
// TODO: add handlers for the vesting events from token Manager
}

if (event === INITIALIZATION_TRIGGER) {
nextState = {
...nextState,
tokenAddress: tokenAddr,
maxAccountTokens: await app.call('maxAccountTokens').toPromise(),
}
} else if (addressesEqual(address, tokenAddr)) {
switch (event) {
case 'ClaimedTokens':
if (addressesEqual(returnValues._token, tokenAddr)) {
nextState = await claimedTokens(token, nextState, returnValues)
}
break
case 'Transfer':
nextState = await transfer(token, nextState, returnValues)
break
default:
break
}
} else {
// TODO: add handlers for the vesting events from token Manager
}
return nextState
}

return nextState
},
[
// Always initialize the store with our own home-made event
of({ event: INITIALIZATION_TRIGGER }),
// Merge in the token's events into the app's own events for the store
token.events(),
]
)
const storeOptions = {
externals: [{ contract: token }],
init: initState({ token, tokenAddress }),
}

return app.store(reducer, storeOptions)
}

function initState({ token, tokenAddress }) {
return async cachedState => {
try {
const tokenSymbol = await token.symbol().toPromise()
app.identify(tokenSymbol)
} catch (err) {
console.error(
`Failed to load token symbol for token at ${tokenAddress} due to:`,
err
)
}

const tokenSettings = hasLoadedTokenSettings(cachedState)
? {}
: await loadTokenSettings(token)

const maxAccountTokens = await app.call('maxAccountTokens').toPromise()

const inititalState = {
...cachedState,
isSyncing: true,
tokenAddress,
maxAccountTokens,
...tokenSettings,
}

// It's safe to not refresh the balances of all token holders
// because we process any event that could change balances, even with block caching

return inititalState
}
}
/***********************
* *
* Event Handlers *
Expand Down

0 comments on commit 441b51a

Please sign in to comment.