|
|
|
@ -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() |
|
|
|
|
} |
|
|
|
|
if (oldState.newInternalTransactions !== state.newInternalTransactions && $internalTransactionsList.length) { |
|
|
|
|
slideDownPrepend($internalTransactionsList, state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join('')) |
|
|
|
|
updateAllAges() |
|
|
|
|
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.pendingTransactionHashes.length !== state.pendingTransactionHashes.length && $pendingTransactionsCount.length) { |
|
|
|
|
$pendingTransactionsCount[0].innerHTML = numeral(state.pendingTransactionHashes.length).format() |
|
|
|
|
if (oldState.transactions !== state.transactions) { |
|
|
|
|
if ($('[data-selector="pending-transactions-list"]').is(':visible')) { |
|
|
|
|
setTimeout(updateTransactions, TRANSACTION_VALIDATED_MOVE_DELAY + 400) |
|
|
|
|
} else { |
|
|
|
|
updateTransactions() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (oldState.newPendingTransactions !== state.newPendingTransactions && $pendingTransactionsList.length) { |
|
|
|
|
slideDownPrepend($pendingTransactionsList, state.newPendingTransactions.slice(oldState.newPendingTransactions.length).reverse().join('')) |
|
|
|
|
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' }) |
|
|
|
|
} |
|
|
|
|
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) |
|
|
|
|
} else { |
|
|
|
|
$transaction.remove() |
|
|
|
|
slideDownPrepend($transactionsList, transactionHtml) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
updateAllAges() |
|
|
|
|
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.newBlock !== state.newBlock) { |
|
|
|
|
slideDownPrepend($validationsList, state.newBlock) |
|
|
|
|
updateAllAges() |
|
|
|
|
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' }) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|