Use listMorph on address page

pull/1030/head
jimmay5469 6 years ago
parent 1fdd7ed19c
commit 2e7e5d1932
  1. 210
      apps/block_scout_web/assets/js/pages/address.js
  2. 1
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  3. 12
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex

@ -4,27 +4,30 @@ import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
import { batchChannel, initRedux, slideDownPrepend, slideUpRemove } from '../utils' import { batchChannel, initRedux, listMorph } from '../utils'
import { updateAllAges } from '../lib/from_now'
import { updateAllCalculatedUsdValues } from '../lib/currency.js' import { updateAllCalculatedUsdValues } from '../lib/currency.js'
import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown' import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown'
const BATCH_THRESHOLD = 10 const BATCH_THRESHOLD = 10
const TRANSACTION_VALIDATED_MOVE_DELAY = 1000
export const initialState = { export const initialState = {
addressHash: null,
balance: null,
batchCountAccumulator: 0,
beyondPageOne: null,
channelDisconnected: false, channelDisconnected: false,
addressHash: null,
filter: null, filter: null,
newBlock: null,
newInternalTransactions: [], balance: null,
newPendingTransactions: [],
newTransactions: [],
pendingTransactionHashes: [],
transactionCount: null, transactionCount: null,
validationCount: null validationCount: null,
pendingTransactions: [],
transactions: [],
internalTransactions: [],
internalTransactionsBatch: [],
validatedBlocks: [],
beyondPageOne: null
} }
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
@ -32,11 +35,18 @@ export function reducer (state = initialState, action) {
case 'PAGE_LOAD': { case 'PAGE_LOAD': {
return Object.assign({}, state, { return Object.assign({}, state, {
addressHash: action.addressHash, addressHash: action.addressHash,
beyondPageOne: action.beyondPageOne,
filter: action.filter, filter: action.filter,
pendingTransactionHashes: action.pendingTransactionHashes,
balance: action.balance,
transactionCount: numeral(action.transactionCount).value(), transactionCount: numeral(action.transactionCount).value(),
validationCount: action.validationCount ? numeral(action.validationCount).value() : null validationCount: action.validationCount ? numeral(action.validationCount).value() : null,
pendingTransactions: action.pendingTransactions,
transactions: action.transactions,
internalTransactions: action.internalTransactions,
validatedBlocks: action.validatedBlocks,
beyondPageOne: action.beyondPageOne
}) })
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
@ -44,7 +54,7 @@ export function reducer (state = initialState, action) {
return Object.assign({}, state, { return Object.assign({}, state, {
channelDisconnected: true, channelDisconnected: true,
batchCountAccumulator: 0 internalTransactionsBatch: []
}) })
} }
case 'RECEIVED_NEW_BLOCK': { case 'RECEIVED_NEW_BLOCK': {
@ -54,7 +64,10 @@ export function reducer (state = initialState, action) {
if (state.beyondPageOne) return Object.assign({}, state, { validationCount }) if (state.beyondPageOne) return Object.assign({}, state, { validationCount })
return Object.assign({}, state, { return Object.assign({}, state, {
newBlock: action.msg.blockHtml, validatedBlocks: [
action.msg,
...state.validatedBlocks
],
validationCount validationCount
}) })
} }
@ -68,16 +81,19 @@ export function reducer (state = initialState, action) {
(state.filter === 'from' && fromAddressHash === state.addressHash) (state.filter === 'from' && fromAddressHash === state.addressHash)
)) ))
if (!state.batchCountAccumulator && incomingInternalTransactions.length < BATCH_THRESHOLD) { if (!state.internalTransactionsBatch.length && incomingInternalTransactions.length < BATCH_THRESHOLD) {
return Object.assign({}, state, { return Object.assign({}, state, {
newInternalTransactions: [ internalTransactions: [
...state.newInternalTransactions, ...incomingInternalTransactions.reverse(),
..._.map(incomingInternalTransactions, 'internalTransactionHtml') ...state.internalTransactions
] ]
}) })
} else { } else {
return Object.assign({}, state, { return Object.assign({}, state, {
batchCountAccumulator: state.batchCountAccumulator + incomingInternalTransactions.length internalTransactionsBatch: [
...incomingInternalTransactions.reverse(),
...state.internalTransactionsBatch
]
}) })
} }
} }
@ -90,16 +106,19 @@ export function reducer (state = initialState, action) {
} }
return Object.assign({}, state, { return Object.assign({}, state, {
newPendingTransactions: [ pendingTransactions: [
...state.newPendingTransactions, action.msg,
action.msg.transactionHtml ...state.pendingTransactions
],
pendingTransactionHashes: [
...state.pendingTransactionHashes,
action.msg.transactionHash
] ]
}) })
} }
case 'REMOVE_PENDING_TRANSACTION': {
if (state.channelDisconnected) return state
return Object.assign({}, state, {
pendingTransactions: state.pendingTransactions.filter((transaction) => action.msg.transactionHash !== transaction.transactionHash)
})
}
case 'RECEIVED_NEW_TRANSACTION': { case 'RECEIVED_NEW_TRANSACTION': {
if (state.channelDisconnected) return state if (state.channelDisconnected) return state
@ -111,15 +130,12 @@ export function reducer (state = initialState, action) {
return Object.assign({}, state, { transactionCount }) return Object.assign({}, state, { transactionCount })
} }
const updatedPendingTransactionHashes =
_.without(state.pendingTransactionHashes, action.msg.transactionHash)
return Object.assign({}, state, { return Object.assign({}, state, {
newTransactions: [ pendingTransactions: state.pendingTransactions.map((transaction) => action.msg.transactionHash === transaction.transactionHash ? Object.assign({}, action.msg, { validated: true }) : transaction),
...state.newTransactions, transactions: [
action.msg action.msg,
...state.transactions
], ],
pendingTransactionHashes: updatedPendingTransactionHashes,
transactionCount: transactionCount transactionCount: transactionCount
}) })
} }
@ -142,13 +158,32 @@ if ($addressDetailsPage.length) {
const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true)) const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true))
store.dispatch({ store.dispatch({
type: 'PAGE_LOAD', type: 'PAGE_LOAD',
addressHash, addressHash,
beyondPageOne: !!blockNumber,
filter, filter,
pendingTransactionHashes: $('[data-selector="pending-transactions-list"]').children()
.map((index, el) => el.dataset.transactionHash).toArray(), balance: $('[data-selector="balance-card"]').html(),
transactionCount: $('[data-selector="transaction-count"]').text(), transactionCount: $('[data-selector="transaction-count"]').text(),
validationCount: $('[data-selector="validation-count"]') ? $('[data-selector="validation-count"]').text() : null validationCount: $('[data-selector="validation-count"]') ? $('[data-selector="validation-count"]').text() : null,
pendingTransactions: $('[data-selector="pending-transactions-list"]').children().map((index, el) => ({
transactionHash: el.dataset.transactionHash,
transactionHtml: el.outerHTML
})).toArray(),
transactions: $('[data-selector="transactions-list"]').children().map((index, el) => ({
transactionHash: el.dataset.transactionHash,
transactionHtml: el.outerHTML
})).toArray(),
internalTransactions: $('[data-selector="internal-transactions-list"]').children().map((index, el) => ({
internalTransactionId: el.dataset.internalTransactionId,
internalTransactionHtml: el.outerHTML
})).toArray(),
validatedBlocks: $('[data-selector="validations-list"]').children().map((index, el) => ({
blockNumber: parseInt(el.dataset.blockNumber),
blockHtml: el.outerHTML
})).toArray(),
beyondPageOne: !!blockNumber
}) })
addressChannel.join() addressChannel.join()
addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
@ -159,77 +194,60 @@ if ($addressDetailsPage.length) {
store.dispatch({ type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) }) store.dispatch({ type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) })
)) ))
addressChannel.on('pending_transaction', (msg) => store.dispatch({ type: 'RECEIVED_NEW_PENDING_TRANSACTION', msg: humps.camelizeKeys(msg) })) addressChannel.on('pending_transaction', (msg) => store.dispatch({ type: 'RECEIVED_NEW_PENDING_TRANSACTION', msg: humps.camelizeKeys(msg) }))
addressChannel.on('transaction', (msg) => store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION', msg: humps.camelizeKeys(msg) })) addressChannel.on('transaction', (msg) => {
store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION', msg: humps.camelizeKeys(msg) })
setTimeout(() => store.dispatch({ type: 'REMOVE_PENDING_TRANSACTION', msg: humps.camelizeKeys(msg) }), TRANSACTION_VALIDATED_MOVE_DELAY)
})
const blocksChannel = socket.channel(`blocks:${addressHash}`, {}) const blocksChannel = socket.channel(`blocks:${addressHash}`, {})
blocksChannel.join() blocksChannel.join()
blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
blocksChannel.on('new_block', (msg) => store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) })) blocksChannel.on('new_block', (msg) => store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) }))
}, },
render (state, oldState) { render (state, oldState) {
const $balance = $('[data-selector="balance-card"]') if (state.channelDisconnected) $('[data-selector="channel-disconnected-message"]').show()
const $channelBatching = $('[data-selector="channel-batching-message"]')
const $channelBatchingCount = $('[data-selector="channel-batching-count"]')
const $channelDisconnected = $('[data-selector="channel-disconnected-message"]')
const $emptyInternalTransactionsList = $('[data-selector="empty-internal-transactions-list"]')
const $emptyTransactionsList = $('[data-selector="empty-transactions-list"]')
const $internalTransactionsList = $('[data-selector="internal-transactions-list"]')
const $pendingTransactionsCount = $('[data-selector="pending-transactions-count"]')
const $pendingTransactionsList = $('[data-selector="pending-transactions-list"]')
const $transactionCount = $('[data-selector="transaction-count"]')
const $transactionsList = $('[data-selector="transactions-list"]')
const $validationCount = $('[data-selector="validation-count"]')
const $validationsList = $('[data-selector="validations-list"]')
if ($emptyInternalTransactionsList.length && state.newInternalTransactions.length) window.location.reload()
if ($emptyTransactionsList.length && state.newTransactions.length) window.location.reload()
if (state.channelDisconnected) $channelDisconnected.show()
if (oldState.balance !== state.balance) { if (oldState.balance !== state.balance) {
$balance.empty().append(state.balance) $('[data-selector="balance-card"]').empty().append(state.balance)
loadTokenBalanceDropdown() loadTokenBalanceDropdown()
updateAllCalculatedUsdValues() updateAllCalculatedUsdValues()
} }
if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format()) if (oldState.transactionCount !== state.transactionCount) $('[data-selector="transaction-count"]').empty().append(numeral(state.transactionCount).format())
if (oldState.validationCount !== state.validationCount) $validationCount.empty().append(numeral(state.validationCount).format()) if (oldState.validationCount !== state.validationCount) $('[data-selector="validation-count"]').empty().append(numeral(state.validationCount).format())
if (state.batchCountAccumulator) {
$channelBatching.show() if (oldState.pendingTransactions !== state.pendingTransactions) {
$channelBatchingCount[0].innerHTML = numeral(state.batchCountAccumulator).format() const container = $('[data-selector="pending-transactions-list"]')[0]
} else { const newElements = _.map(state.pendingTransactions, ({ transactionHtml }) => $(transactionHtml)[0])
$channelBatching.hide() listMorph(container, newElements, { key: 'dataset.transactionHash' })
if($('[data-selector="pending-transactions-count"]').length) $('[data-selector="pending-transactions-count"]')[0].innerHTML = numeral(state.pendingTransactions.filter(({ validated }) => !validated).length).format()
} }
if (oldState.newInternalTransactions !== state.newInternalTransactions && $internalTransactionsList.length) { function updateTransactions () {
slideDownPrepend($internalTransactionsList, state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join('')) const container = $('[data-selector="transactions-list"]')[0]
updateAllAges() const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.transactionHash' })
} }
if (oldState.pendingTransactionHashes.length !== state.pendingTransactionHashes.length && $pendingTransactionsCount.length) { if (oldState.transactions !== state.transactions) {
$pendingTransactionsCount[0].innerHTML = numeral(state.pendingTransactionHashes.length).format() if ($('[data-selector="pending-transactions-list"]').is(':visible')) {
setTimeout(updateTransactions, TRANSACTION_VALIDATED_MOVE_DELAY + 400)
} else {
updateTransactions()
}
} }
if (oldState.newPendingTransactions !== state.newPendingTransactions && $pendingTransactionsList.length) { if (oldState.internalTransactions !== state.internalTransactions) {
slideDownPrepend($pendingTransactionsList, state.newPendingTransactions.slice(oldState.newPendingTransactions.length).reverse().join('')) const container = $('[data-selector="internal-transactions-list"]')[0]
updateAllAges() const newElements = _.map(state.internalTransactions, ({ internalTransactionHtml }) => $(internalTransactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.internalTransactionId' })
} }
if (oldState.newTransactions !== state.newTransactions && $transactionsList.length) { const $channelBatching = $('[data-selector="channel-batching-message"]')
const newlyValidatedTransactions = state.newTransactions.slice(oldState.newTransactions.length).reverse() if (state.internalTransactionsBatch.length) {
newlyValidatedTransactions.forEach(({ transactionHash, transactionHtml }) => { $channelBatching.show()
let $transaction = $(`[data-selector="pending-transactions-list"] [data-transaction-hash="${transactionHash}"]`) $('[data-selector="channel-batching-count"]')[0].innerHTML = numeral(state.internalTransactionsBatch.length).format()
$transaction.html($(transactionHtml).html()) } else {
if ($transaction.is(':visible')) { $channelBatching.hide()
setTimeout(() => {
$transaction.addClass('shrink-out')
setTimeout(() => {
slideUpRemove($transaction)
slideDownPrepend($transactionsList, transactionHtml)
}, 400)
}, 1000)
} else {
$transaction.remove()
slideDownPrepend($transactionsList, transactionHtml)
}
})
updateAllAges()
} }
if (oldState.newBlock !== state.newBlock) { if (oldState.validatedBlocks !== state.validatedBlocks) {
slideDownPrepend($validationsList, state.newBlock) const container = $('[data-selector="validations-list"]')[0]
updateAllAges() const newElements = _.map(state.validatedBlocks, ({ blockHtml }) => $(blockHtml)[0])
listMorph(container, newElements, { key: 'dataset.blockNumber' })
} }
} }
}) })

@ -55,6 +55,7 @@ defmodule BlockScoutWeb.AddressChannel do
push(socket, "internal_transaction", %{ push(socket, "internal_transaction", %{
to_address_hash: to_string(internal_transaction.to_address_hash), to_address_hash: to_string(internal_transaction.to_address_hash),
from_address_hash: to_string(internal_transaction.from_address_hash), from_address_hash: to_string(internal_transaction.from_address_hash),
internal_transaction_id: to_string(internal_transaction.id),
internal_transaction_html: rendered_internal_transaction internal_transaction_html: rendered_internal_transaction
}) })

@ -52,17 +52,19 @@
</div> </div>
<h2 class="card-title"><%= gettext "Transactions" %></h2> <h2 class="card-title"><%= gettext "Transactions" %></h2>
<div data-selector="pending-transactions-toggle"> <div data-selector="pending-transactions-toggle">
<%= link to: "#pending-transactions", class: "d-inline-block mb-3", "data-toggle": "collapse" do %> <%= link to: "#pending-transactions-container", class: "d-inline-block mb-3", "data-toggle": "collapse" do %>
<span data-selector="pending-transactions-open"> <span data-selector="pending-transactions-open">
<%= gettext("Show") %></span> <%= gettext("Show") %></span>
<span data-selector="pending-transactions-close" class="d-none"><%= gettext("Hide") %></span> <span data-selector="pending-transactions-close" class="d-none"><%= gettext("Hide") %></span>
<span data-selector="pending-transactions-count"><%= length(@pending_transactions) %></span> <span data-selector="pending-transactions-count"><%= length(@pending_transactions) %></span>
<%= gettext("Pending Transactions") %> <%= gettext("Pending Transactions") %>
<% end %> <% end %>
<div class="mb-3 collapse" id="pending-transactions" data-selector="pending-transactions-list"> <div class="mb-3 collapse" id="pending-transactions-container">
<%= for pending_transaction <- @pending_transactions do %> <div data-selector="pending-transactions-list">
<%= render(BlockScoutWeb.TransactionView, "_tile.html", current_address: @address, transaction: pending_transaction) %> <%= for pending_transaction <- @pending_transactions do %>
<% end %> <%= render(BlockScoutWeb.TransactionView, "_tile.html", current_address: @address, transaction: pending_transaction) %>
<% end %>
</div>
<hr /> <hr />
</div> </div>
</div> </div>

Loading…
Cancel
Save