Use listMorph on address page

pull/1030/head
jimmay5469 6 years ago
parent 1fdd7ed19c
commit 2e7e5d1932
  1. 212
      apps/block_scout_web/assets/js/pages/address.js
  2. 1
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  3. 6
      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 numeral from 'numeral'
import socket from '../socket'
import { batchChannel, initRedux, slideDownPrepend, slideUpRemove } from '../utils'
import { updateAllAges } from '../lib/from_now'
import { batchChannel, initRedux, listMorph } from '../utils'
import { updateAllCalculatedUsdValues } from '../lib/currency.js'
import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown'
const BATCH_THRESHOLD = 10
const TRANSACTION_VALIDATED_MOVE_DELAY = 1000
export const initialState = {
addressHash: null,
balance: null,
batchCountAccumulator: 0,
beyondPageOne: null,
channelDisconnected: false,
addressHash: null,
filter: null,
newBlock: null,
newInternalTransactions: [],
newPendingTransactions: [],
newTransactions: [],
pendingTransactionHashes: [],
balance: null,
transactionCount: null,
validationCount: null
validationCount: null,
pendingTransactions: [],
transactions: [],
internalTransactions: [],
internalTransactionsBatch: [],
validatedBlocks: [],
beyondPageOne: null
}
export function reducer (state = initialState, action) {
@ -32,11 +35,18 @@ export function reducer (state = initialState, action) {
case 'PAGE_LOAD': {
return Object.assign({}, state, {
addressHash: action.addressHash,
beyondPageOne: action.beyondPageOne,
filter: action.filter,
pendingTransactionHashes: action.pendingTransactionHashes,
balance: action.balance,
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': {
@ -44,7 +54,7 @@ export function reducer (state = initialState, action) {
return Object.assign({}, state, {
channelDisconnected: true,
batchCountAccumulator: 0
internalTransactionsBatch: []
})
}
case 'RECEIVED_NEW_BLOCK': {
@ -54,7 +64,10 @@ export function reducer (state = initialState, action) {
if (state.beyondPageOne) return Object.assign({}, state, { validationCount })
return Object.assign({}, state, {
newBlock: action.msg.blockHtml,
validatedBlocks: [
action.msg,
...state.validatedBlocks
],
validationCount
})
}
@ -68,16 +81,19 @@ export function reducer (state = initialState, action) {
(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, {
newInternalTransactions: [
...state.newInternalTransactions,
..._.map(incomingInternalTransactions, 'internalTransactionHtml')
internalTransactions: [
...incomingInternalTransactions.reverse(),
...state.internalTransactions
]
})
} else {
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, {
newPendingTransactions: [
...state.newPendingTransactions,
action.msg.transactionHtml
],
pendingTransactionHashes: [
...state.pendingTransactionHashes,
action.msg.transactionHash
pendingTransactions: [
action.msg,
...state.pendingTransactions
]
})
}
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': {
if (state.channelDisconnected) return state
@ -111,15 +130,12 @@ export function reducer (state = initialState, action) {
return Object.assign({}, state, { transactionCount })
}
const updatedPendingTransactionHashes =
_.without(state.pendingTransactionHashes, action.msg.transactionHash)
return Object.assign({}, state, {
newTransactions: [
...state.newTransactions,
action.msg
pendingTransactions: state.pendingTransactions.map((transaction) => action.msg.transactionHash === transaction.transactionHash ? Object.assign({}, action.msg, { validated: true }) : transaction),
transactions: [
action.msg,
...state.transactions
],
pendingTransactionHashes: updatedPendingTransactionHashes,
transactionCount: transactionCount
})
}
@ -142,13 +158,32 @@ if ($addressDetailsPage.length) {
const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true))
store.dispatch({
type: 'PAGE_LOAD',
addressHash,
beyondPageOne: !!blockNumber,
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(),
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.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) })
))
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}`, {})
blocksChannel.join()
blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
blocksChannel.on('new_block', (msg) => store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) }))
},
render (state, oldState) {
const $balance = $('[data-selector="balance-card"]')
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 (state.channelDisconnected) $('[data-selector="channel-disconnected-message"]').show()
if (oldState.balance !== state.balance) {
$balance.empty().append(state.balance)
$('[data-selector="balance-card"]').empty().append(state.balance)
loadTokenBalanceDropdown()
updateAllCalculatedUsdValues()
}
if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format())
if (oldState.validationCount !== state.validationCount) $validationCount.empty().append(numeral(state.validationCount).format())
if (state.batchCountAccumulator) {
$channelBatching.show()
$channelBatchingCount[0].innerHTML = numeral(state.batchCountAccumulator).format()
} else {
$channelBatching.hide()
if (oldState.transactionCount !== state.transactionCount) $('[data-selector="transaction-count"]').empty().append(numeral(state.transactionCount).format())
if (oldState.validationCount !== state.validationCount) $('[data-selector="validation-count"]').empty().append(numeral(state.validationCount).format())
if (oldState.pendingTransactions !== state.pendingTransactions) {
const container = $('[data-selector="pending-transactions-list"]')[0]
const newElements = _.map(state.pendingTransactions, ({ transactionHtml }) => $(transactionHtml)[0])
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()
}
function updateTransactions () {
const container = $('[data-selector="transactions-list"]')[0]
const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.transactionHash' })
}
if (oldState.newInternalTransactions !== state.newInternalTransactions && $internalTransactionsList.length) {
slideDownPrepend($internalTransactionsList, state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join(''))
updateAllAges()
}
if (oldState.pendingTransactionHashes.length !== state.pendingTransactionHashes.length && $pendingTransactionsCount.length) {
$pendingTransactionsCount[0].innerHTML = numeral(state.pendingTransactionHashes.length).format()
}
if (oldState.newPendingTransactions !== state.newPendingTransactions && $pendingTransactionsList.length) {
slideDownPrepend($pendingTransactionsList, state.newPendingTransactions.slice(oldState.newPendingTransactions.length).reverse().join(''))
updateAllAges()
}
if (oldState.newTransactions !== state.newTransactions && $transactionsList.length) {
const newlyValidatedTransactions = state.newTransactions.slice(oldState.newTransactions.length).reverse()
newlyValidatedTransactions.forEach(({ transactionHash, transactionHtml }) => {
let $transaction = $(`[data-selector="pending-transactions-list"] [data-transaction-hash="${transactionHash}"]`)
$transaction.html($(transactionHtml).html())
if ($transaction.is(':visible')) {
setTimeout(() => {
$transaction.addClass('shrink-out')
setTimeout(() => {
slideUpRemove($transaction)
slideDownPrepend($transactionsList, transactionHtml)
}, 400)
}, 1000)
if (oldState.transactions !== state.transactions) {
if ($('[data-selector="pending-transactions-list"]').is(':visible')) {
setTimeout(updateTransactions, TRANSACTION_VALIDATED_MOVE_DELAY + 400)
} else {
$transaction.remove()
slideDownPrepend($transactionsList, transactionHtml)
updateTransactions()
}
})
updateAllAges()
}
if (oldState.newBlock !== state.newBlock) {
slideDownPrepend($validationsList, state.newBlock)
updateAllAges()
if (oldState.internalTransactions !== state.internalTransactions) {
const container = $('[data-selector="internal-transactions-list"]')[0]
const newElements = _.map(state.internalTransactions, ({ internalTransactionHtml }) => $(internalTransactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.internalTransactionId' })
}
const $channelBatching = $('[data-selector="channel-batching-message"]')
if (state.internalTransactionsBatch.length) {
$channelBatching.show()
$('[data-selector="channel-batching-count"]')[0].innerHTML = numeral(state.internalTransactionsBatch.length).format()
} else {
$channelBatching.hide()
}
if (oldState.validatedBlocks !== state.validatedBlocks) {
const container = $('[data-selector="validations-list"]')[0]
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", %{
to_address_hash: to_string(internal_transaction.to_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
})

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

Loading…
Cancel
Save