diff --git a/app/src/renderer/components/govern/PageProposals.vue b/app/src/renderer/components/govern/PageProposals.vue index 2f622fd400..c8e0c12f7f 100644 --- a/app/src/renderer/components/govern/PageProposals.vue +++ b/app/src/renderer/components/govern/PageProposals.vue @@ -9,7 +9,8 @@ page(title='Proposals') .label Search modal-search(type="proposals") - data-empty(v-if="proposals.length === 0") + data-loading(v-if="proposals.loading") + data-empty(v-else-if="proposals.length === 0") data-empty-search(v-else-if="filteredProposals.length === 0") li-proposal( v-else @@ -22,6 +23,7 @@ page(title='Proposals') import { mapGetters } from 'vuex' import { includes, orderBy } from 'lodash' import Mousetrap from 'mousetrap' +import DataLoading from 'common/NiDataLoading' import DataEmpty from 'common/NiDataEmpty' import DataEmptySearch from 'common/NiDataEmptySearch' import LiProposal from 'govern/LiProposal' @@ -33,6 +35,7 @@ import Part from 'common/NiPart' export default { name: 'page-proposals', components: { + DataLoading, DataEmpty, DataEmptySearch, LiProposal, @@ -45,9 +48,9 @@ export default { computed: { ...mapGetters(['proposals', 'filters']), filteredProposals () { - if (this.proposals && this.filters) { + if (this.proposals.items && this.filters) { let query = this.filters.proposals.search.query - let proposals = orderBy(this.proposals, [this.sort.property], [this.sort.order]) + let proposals = orderBy(this.proposals.items, [this.sort.property], [this.sort.order]) if (this.filters.proposals.search.visible) { return proposals.filter(p => includes(p.title.toLowerCase(), query)) } else { diff --git a/app/src/renderer/components/monitor/PageBlock.vue b/app/src/renderer/components/monitor/PageBlock.vue index 75ae746134..bbe90a091c 100644 --- a/app/src/renderer/components/monitor/PageBlock.vue +++ b/app/src/renderer/components/monitor/PageBlock.vue @@ -41,14 +41,16 @@ page(:title="pageBlockTitle" v-if="block.header") :dd="p.signature.data") part(title='Transactions') - list-item(v-if="block.header.num_txs > 0" v-for="tx in block.data.txs" :key="tx.id" dt="Transaction" :dd="TODO") - data-empty(v-if="block.header.num_txs === 0" title="Empty Block" subtitle="There were no transactions in this block.") + data-loading(v-if="blockchain.blockLoading") + data-empty(v-else-if="block.header.num_txs === 0" title="Empty Block" subtitle="There were no transactions in this block.") + list-item(v-else v-for="tx in block.data.txs" :key="tx.id" dt="Transaction" :dd="TODO") diff --git a/app/src/renderer/components/staking/LiDelegate.vue b/app/src/renderer/components/staking/LiDelegate.vue index 0178d51b1a..2f1e68e3d2 100644 --- a/app/src/renderer/components/staking/LiDelegate.vue +++ b/app/src/renderer/components/staking/LiDelegate.vue @@ -42,13 +42,13 @@ export default { return value }, vpMax () { - if (this.delegates.length > 0) { - let richestDelegate = maxBy(this.delegates, 'voting_power') + if (this.delegates.delegates.length > 0) { + let richestDelegate = maxBy(this.delegates.delegates, 'voting_power') return richestDelegate.voting_power } else { return 0 } }, vpTotal () { - return this.delegates + return this.delegates.delegates .slice() .sort((a, b) => b.voting_power - a.voting_power) .slice(0, 100) diff --git a/app/src/renderer/components/staking/PageDelegate.vue b/app/src/renderer/components/staking/PageDelegate.vue index 6760f4c62b..7070968264 100644 --- a/app/src/renderer/components/staking/PageDelegate.vue +++ b/app/src/renderer/components/staking/PageDelegate.vue @@ -54,8 +54,8 @@ export default { let value = { description: {} } - if (this.delegates && this.$route.params.delegate) { - value = this.delegates.find(v => v.id === this.$route.params.delegate) || value + if (this.delegates.delegates && this.$route.params.delegate) { + value = this.delegates.delegates.find(v => v.id === this.$route.params.delegate) || value } return value }, diff --git a/app/src/renderer/components/staking/PageDelegates.vue b/app/src/renderer/components/staking/PageDelegates.vue index c74145716b..cde373d2ca 100644 --- a/app/src/renderer/components/staking/PageDelegates.vue +++ b/app/src/renderer/components/staking/PageDelegates.vue @@ -11,7 +11,8 @@ page#page-delegates(title='Delegates') modal-search(type="delegates") .delegates-container - data-loading(v-if="delegates.length === 0") + data-loading(v-if="delegates.loading") + data-empty(v-else-if="delegates.delegates.length === 0") data-empty-search(v-else-if="filteredDelegates.length === 0") template(v-else) panel-sort(:sort='sort') @@ -32,6 +33,7 @@ import { includes, orderBy } from 'lodash' import Mousetrap from 'mousetrap' import LiDelegate from 'staking/LiDelegate' import Btn from '@nylira/vue-button' +import DataEmpty from 'common/NiDataEmpty' import DataEmptySearch from 'common/NiDataEmptySearch' import DataLoading from 'common/NiDataLoading' import Field from '@nylira/vue-field' @@ -45,6 +47,7 @@ export default { components: { LiDelegate, Btn, + DataEmpty, DataEmptySearch, DataLoading, Field, @@ -59,7 +62,7 @@ export default { address () { return this.user.address }, filteredDelegates () { let query = this.filters.delegates.search.query - let list = orderBy(this.delegates, [this.sort.property], [this.sort.order]) + let list = orderBy(this.delegates.delegates, [this.sort.property], [this.sort.order]) if (this.filters.delegates.search.visible) { return list.filter(i => includes(JSON.stringify(i).toLowerCase(), query.toLowerCase())) } else { diff --git a/app/src/renderer/components/wallet/PageBalances.vue b/app/src/renderer/components/wallet/PageBalances.vue index e07cfd9772..7928e6db11 100644 --- a/app/src/renderer/components/wallet/PageBalances.vue +++ b/app/src/renderer/components/wallet/PageBalances.vue @@ -14,7 +14,8 @@ page(title='Balances') li-copy(:value="wallet.key.address") part(title="Denomination Balances") - data-empty(v-if="wallet.balances.length === 0") + data-loading(v-if="wallet.balancesLoading") + data-empty(v-else-if="wallet.balances.length === 0") data-empty-search(v-else-if="filteredBalances.length === 0") list-item( v-for="i in filteredBalances" @@ -37,6 +38,7 @@ page(title='Balances') import { mapGetters } from 'vuex' import { includes, orderBy } from 'lodash' import Mousetrap from 'mousetrap' +import DataLoading from 'common/NiDataLoading' import DataEmpty from 'common/NiDataEmpty' import DataEmptySearch from 'common/NiDataEmptySearch' import LiCopy from 'common/NiLiCopy' @@ -48,6 +50,7 @@ import ToolBar from 'common/NiToolBar' export default { name: 'page-balances', components: { + DataLoading, DataEmpty, DataEmptySearch, LiCopy, diff --git a/app/src/renderer/components/wallet/PageTransactions.vue b/app/src/renderer/components/wallet/PageTransactions.vue index 3509502208..609fd897e9 100644 --- a/app/src/renderer/components/wallet/PageTransactions.vue +++ b/app/src/renderer/components/wallet/PageTransactions.vue @@ -7,7 +7,8 @@ page(title='Transactions') modal-search(type="transactions") - data-empty-tx(v-if='transactions.length === 0') + data-loading(v-if="wallet.historyLoading") + data-empty-tx(v-else-if='transactions.length === 0') data-empty-search(v-else-if="filteredTransactions.length === 0") li-transaction( v-else @@ -23,6 +24,7 @@ import shortid from 'shortid' import { mapGetters } from 'vuex' import { includes, orderBy, uniqBy } from 'lodash' import Mousetrap from 'mousetrap' +import DataLoading from 'common/NiDataLoading' import DataEmptySearch from 'common/NiDataEmptySearch' import DataEmptyTx from 'common/NiDataEmptyTx' import LiTransaction from 'wallet/LiTransaction' @@ -34,6 +36,7 @@ export default { name: 'page-transactions', components: { LiTransaction, + DataLoading, DataEmptySearch, DataEmptyTx, ModalSearch, diff --git a/app/src/renderer/vuex/modules/blockchain.js b/app/src/renderer/vuex/modules/blockchain.js index 9cc05b86ec..8f24070187 100644 --- a/app/src/renderer/vuex/modules/blockchain.js +++ b/app/src/renderer/vuex/modules/blockchain.js @@ -9,6 +9,7 @@ export default ({ commit, node }) => { abciInfo: {}, blocks: [], block: {}, + blockLoading: false, url: '' } @@ -35,9 +36,11 @@ export default ({ commit, node }) => { const actions = { async getBlock ({ state, commit }, height) { + state.blockLoading = true const blockUrl = url + '/block?height=' + height let block = (await axios.get(blockUrl)).data.result commit('setBlock', block) + state.blockLoading = false } } diff --git a/app/src/renderer/vuex/modules/delegates.js b/app/src/renderer/vuex/modules/delegates.js index 14611b1f1d..80e6fec5ff 100644 --- a/app/src/renderer/vuex/modules/delegates.js +++ b/app/src/renderer/vuex/modules/delegates.js @@ -1,7 +1,10 @@ import axios from 'axios' export default ({ dispatch, node }) => { - const state = [] + const state = { + delegates: [], + loading: false + } const mutations = { addDelegate (state, delegate) { @@ -9,23 +12,26 @@ export default ({ dispatch, node }) => { Object.assign(delegate, delegate.description) // update if we already have this delegate - for (let existingDelegate of state) { + for (let existingDelegate of state.delegates) { if (existingDelegate.id === delegate.id) { Object.assign(existingDelegate, delegate) return } } - state.push(delegate) + state.delegates.push(delegate) } } const actions = { - async getDelegates ({ dispatch }) { + async getDelegates ({ state, dispatch }) { + state.loading = true let delegatePubkeys = (await node.candidates()).data - return Promise.all(delegatePubkeys.map(pubkey => { + let delegates = await Promise.all(delegatePubkeys.map(pubkey => { return dispatch('getDelegate', pubkey) })) + state.loading = false + return delegates }, async getDelegate ({ commit }, pubkey) { let delegate = (await axios.get(`http://localhost:${node.relayPort}/query/stake/candidate/${pubkey.data}`)).data.data diff --git a/app/src/renderer/vuex/modules/proposals.js b/app/src/renderer/vuex/modules/proposals.js index cd7e1a0b16..84c069cf64 100644 --- a/app/src/renderer/vuex/modules/proposals.js +++ b/app/src/renderer/vuex/modules/proposals.js @@ -1,7 +1,11 @@ import data from '../json/proposals.json' export default ({ commit }) => { - const state = data + const state = { + items: data, + loading: false + } + const mutations = { } return { state, mutations } diff --git a/app/src/renderer/vuex/modules/validators.js b/app/src/renderer/vuex/modules/validators.js index b969080903..e923596e19 100644 --- a/app/src/renderer/vuex/modules/validators.js +++ b/app/src/renderer/vuex/modules/validators.js @@ -1,6 +1,7 @@ -export default ({ commit, node }) => { +export default ({ node }) => { const state = { validators: {}, + loading: false, validatorHash: null } @@ -14,21 +15,21 @@ export default ({ commit, node }) => { } const actions = { - maybeUpdateValidators ({state, commit}, header) { + getValidators ({state, commit}) { + state.loading = true + node.rpc.validators((err, { validators }) => { + if (err) return console.error('error fetching validator set') + commit('setValidators', validators) + state.loading = false + }) + }, + maybeUpdateValidators ({state, commit, dispatch}, header) { let validatorHash = header.validators_hash if (validatorHash === state.validatorHash) return commit('setValidatorHash', validatorHash) - getValidators() + dispatch('getValidators') } } - function getValidators () { - node.rpc.validators((err, { validators }) => { - if (err) return console.error('error fetching validator set') - commit('setValidators', validators) - }) - } - getValidators() - return { state, mutations, actions } } diff --git a/app/src/renderer/vuex/modules/wallet.js b/app/src/renderer/vuex/modules/wallet.js index 6a162b55a8..5dcc16dcd6 100644 --- a/app/src/renderer/vuex/modules/wallet.js +++ b/app/src/renderer/vuex/modules/wallet.js @@ -5,8 +5,10 @@ let root = require('../../../root.js') export default ({ commit, node }) => { let state = { balances: [], + balancesLoading: false, key: { address: '' }, history: [], + historyLoading: false, denoms: [], blockMetas: [] } @@ -49,8 +51,12 @@ export default ({ commit, node }) => { dispatch('queryWalletHistory') }, async queryWalletBalances ({ state, rootState, commit }) { + state.balancesLoading = true let res = await node.queryAccount(state.key.address) - if (!res) return + if (!res) { + state.balancesLoading = false + return + } commit('setWalletBalances', res.data.coins) for (let coin of res.data.coins) { if (coin.denom === rootState.config.bondingDenom) { @@ -58,8 +64,10 @@ export default ({ commit, node }) => { break } } + state.balancesLoading = false }, async queryWalletHistory ({ state, commit, dispatch }) { + state.historyLoading = true let res = await node.coinTxs(state.key.address) if (!res) return commit('setWalletHistory', res) @@ -70,9 +78,10 @@ export default ({ commit, node }) => { blockHeights.push(t.height) } }) - return Promise.all(blockHeights.map(h => + await Promise.all(blockHeights.map(h => dispatch('queryTransactionTime', h) )) + state.historyLoading = false }, async queryTransactionTime ({ commit, dispatch }, blockHeight) { let blockMetaInfo = await dispatch('queryBlockInfo', blockHeight) diff --git a/test/unit/specs/components/staking/LiDelegate.spec.js b/test/unit/specs/components/staking/LiDelegate.spec.js index e895073323..e0a2448994 100644 --- a/test/unit/specs/components/staking/LiDelegate.spec.js +++ b/test/unit/specs/components/staking/LiDelegate.spec.js @@ -45,7 +45,7 @@ describe('LiDelegate', () => { } }) - delegate = store.state.delegates[0] + delegate = store.state.delegates.delegates[0] wrapper.setData({ delegate }) }) @@ -73,12 +73,12 @@ describe('LiDelegate', () => { expect(wrapper.html()).not.toContain('li-delegate-active') wrapper.find('#add-to-cart').trigger('click') expect(wrapper.vm.inCart).toBeTruthy() - expect(store.commit).toHaveBeenCalledWith('addToCart', store.state.delegates[0]) + expect(store.commit).toHaveBeenCalledWith('addToCart', store.state.delegates.delegates[0]) expect(wrapper.html()).toContain('li-delegate-active') }) it('should remove from cart', () => { - store.commit('addToCart', store.state.delegates[0]) + store.commit('addToCart', store.state.delegates.delegates[0]) wrapper.update() expect(wrapper.vm.inCart).toBeTruthy() wrapper.find('#remove-from-cart').trigger('click') diff --git a/test/unit/specs/components/staking/PageDelegates.spec.js b/test/unit/specs/components/staking/PageDelegates.spec.js index 6b62aec089..00f5f7560a 100644 --- a/test/unit/specs/components/staking/PageDelegates.spec.js +++ b/test/unit/specs/components/staking/PageDelegates.spec.js @@ -75,8 +75,8 @@ describe('PageDelegates', () => { }) it('should show the amount of selected delegates', () => { - store.commit('addToCart', store.state.delegates[0]) - store.commit('addToCart', store.state.delegates[1]) + store.commit('addToCart', store.state.delegates.delegates[0]) + store.commit('addToCart', store.state.delegates.delegates[1]) wrapper.update() expect(wrapper.find('.fixed-button-bar strong').text().trim()).toContain('2') }) @@ -84,7 +84,10 @@ describe('PageDelegates', () => { it('should show placeholder if delegates are loading', () => { let {wrapper} = mount(PageDelegates, { getters: { - delegates: () => [] + delegates: () => ({ + delegates: [], + loading: true + }) }, stubs: { 'data-loading': '' diff --git a/test/unit/specs/store/delegates.spec.js b/test/unit/specs/store/delegates.spec.js index c41aceb6ae..77017150d8 100644 --- a/test/unit/specs/store/delegates.spec.js +++ b/test/unit/specs/store/delegates.spec.js @@ -15,21 +15,21 @@ describe('Module: Delegates', () => { it('adds delegate to state', () => { store.commit('addDelegate', { pub_key: { data: 'foo' } }) - expect(store.state.delegates[0]).toEqual({ + expect(store.state.delegates.delegates[0]).toEqual({ id: 'foo', pub_key: { data: 'foo' } }) - expect(store.state.delegates.length).toBe(1) + expect(store.state.delegates.delegates.length).toBe(1) }) it('replaces existing delegate with same id', () => { store.commit('addDelegate', { pub_key: { data: 'foo' }, updated: true }) - expect(store.state.delegates[0]).toEqual({ + expect(store.state.delegates.delegates[0]).toEqual({ id: 'foo', pub_key: { data: 'foo' }, updated: true }) - expect(store.state.delegates.length).toBe(1) + expect(store.state.delegates.delegates.length).toBe(1) }) it('fetches a candidate', async () => { @@ -45,7 +45,7 @@ describe('Module: Delegates', () => { await store.dispatch('getDelegate', { data: 'foo' }) expect(axios.get.mock.calls[0][0]).toBe('http://localhost:9060/query/stake/candidate/foo') - expect(store.state.delegates[0].test).toBe(123) + expect(store.state.delegates.delegates[0].test).toBe(123) }) it('fetches all candidates', async () => { @@ -78,7 +78,7 @@ describe('Module: Delegates', () => { await store.dispatch('getDelegates') expect(axios.get.mock.calls[0][0]).toBe('http://localhost:9060/query/stake/candidate/foo') expect(axios.get.mock.calls[1][0]).toBe('http://localhost:9060/query/stake/candidate/bar') - expect(store.state.delegates[0].test).toBe(123) - expect(store.state.delegates[1].test).toBe(456) + expect(store.state.delegates.delegates[0].test).toBe(123) + expect(store.state.delegates.delegates[1].test).toBe(456) }) }) diff --git a/test/unit/specs/store/wallet.spec.js b/test/unit/specs/store/wallet.spec.js index 870edd6fe4..3e93358543 100644 --- a/test/unit/specs/store/wallet.spec.js +++ b/test/unit/specs/store/wallet.spec.js @@ -16,8 +16,10 @@ describe('Module: Wallet', () => { it('should have an empty state by default', () => { const state = { balances: [], + balancesLoading: false, key: { address: '' }, history: [], + historyLoading: false, denoms: [], blockMetas: [] }