diff --git a/apps/block_scout_web/assets/__tests__/pages/address.js b/apps/block_scout_web/assets/__tests__/pages/address.js index 02f319caa7..7691660a40 100644 --- a/apps/block_scout_web/assets/__tests__/pages/address.js +++ b/apps/block_scout_web/assets/__tests__/pages/address.js @@ -6,26 +6,30 @@ describe('PAGE_LOAD', () => { const action = { type: 'PAGE_LOAD', addressHash: '1234', - beyondPageOne: false + beyondPageOne: false, + pendingTransactionHashes: ['1'] } const output = reducer(state, action) expect(output.addressHash).toBe('1234') expect(output.beyondPageOne).toBe(false) expect(output.filter).toBe(undefined) + expect(output.pendingTransactionHashes).toEqual(['1']) }) test('page 2 without filter', () => { const state = initialState const action = { type: 'PAGE_LOAD', + addressHash: '1234', beyondPageOne: true, - addressHash: '1234' + pendingTransactionHashes: ['1'] } const output = reducer(state, action) expect(output.addressHash).toBe('1234') - expect(output.filter).toBe(undefined) expect(output.beyondPageOne).toBe(true) + expect(output.filter).toBe(undefined) + expect(output.pendingTransactionHashes).toEqual(['1']) }) test('page 1 with "to" filter', () => { const state = initialState @@ -38,22 +42,22 @@ describe('PAGE_LOAD', () => { const output = reducer(state, action) expect(output.addressHash).toBe('1234') - expect(output.filter).toBe('to') expect(output.beyondPageOne).toBe(false) + expect(output.filter).toBe('to') }) test('page 2 with "to" filter', () => { const state = initialState const action = { type: 'PAGE_LOAD', - beyondPageOne: true, addressHash: '1234', + beyondPageOne: true, filter: 'to' } const output = reducer(state, action) expect(output.addressHash).toBe('1234') - expect(output.filter).toBe('to') expect(output.beyondPageOne).toBe(true) + expect(output.filter).toBe('to') }) }) @@ -65,7 +69,6 @@ test('CHANNEL_DISCONNECTED', () => { const output = reducer(state, action) expect(output.channelDisconnected).toBe(true) - expect(output.batchCountAccumulator).toBe(0) }) test('RECEIVED_UPDATED_BALANCE', () => { @@ -81,68 +84,19 @@ test('RECEIVED_UPDATED_BALANCE', () => { expect(output.balance).toBe('hello world') }) -describe('RECEIVED_NEW_PENDING_TRANSACTION_BATCH', () => { +describe('RECEIVED_NEW_PENDING_TRANSACTION', () => { test('single transaction', () => { const state = initialState const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_PENDING_TRANSACTION', + msg: { transactionHash: '0x00', transactionHtml: 'test' - }] + } } const output = reducer(state, action) expect(output.newPendingTransactions).toEqual(['test']) - expect(output.batchCountAccumulator).toEqual(0) - expect(output.transactionCount).toEqual(null) - }) - test('large batch of transactions', () => { - const state = initialState - const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', - msgs: [{ - transactionHash: '0x01', - transactionHtml: 'test 1' - },{ - transactionHash: '0x02', - transactionHtml: 'test 2' - },{ - transactionHash: '0x03', - transactionHtml: 'test 3' - },{ - transactionHash: '0x04', - transactionHtml: 'test 4' - },{ - transactionHash: '0x05', - transactionHtml: 'test 5' - },{ - transactionHash: '0x06', - transactionHtml: 'test 6' - },{ - transactionHash: '0x07', - transactionHtml: 'test 7' - },{ - transactionHash: '0x08', - transactionHtml: 'test 8' - },{ - transactionHash: '0x09', - transactionHtml: 'test 9' - },{ - transactionHash: '0x10', - transactionHtml: 'test 10' - },{ - transactionHash: '0x11', - transactionHtml: 'test 11' - }] - } - const output = reducer(state, action) - - expect(output.newPendingTransactions).toEqual([]) - expect(output.newPendingTransactionHashesBatch).toEqual([ - "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07", "0x08", "0x09", "0x10", "0x11" - ]) - expect(output.batchCountAccumulator).toEqual(0) expect(output.transactionCount).toEqual(null) }) test('single transaction after single transaction', () => { @@ -150,273 +104,97 @@ describe('RECEIVED_NEW_PENDING_TRANSACTION_BATCH', () => { newPendingTransactions: ['test 1'] }) const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_PENDING_TRANSACTION', + msg: { transactionHash: '0x02', transactionHtml: 'test 2' - }] + } } const output = reducer(state, action) expect(output.newPendingTransactions).toEqual(['test 1', 'test 2']) - expect(output.newPendingTransactionHashesBatch.length).toEqual(0) - }) - test('single transaction after large batch of transactions', () => { - const state = Object.assign({}, initialState, { - newPendingTransactionHashesBatch: [ - "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07", "0x08", "0x09", "0x10", "0x11" - ] - }) - const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', - msgs: [{ - transactionHash: '0x12', - transactionHtml: 'test 12' - }] - } - const output = reducer(state, action) - - expect(output.newPendingTransactions).toEqual([]) - expect(output.newPendingTransactionHashesBatch.length).toEqual(12) - expect(output.newPendingTransactionHashesBatch).toContain('0x12') - }) - test('large batch of transactions after large batch of transactions', () => { - const state = Object.assign({}, initialState, { - newPendingTransactionHashesBatch: [ - "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07", "0x08", "0x09", "0x10", "0x11" - ] - }) - const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', - msgs: [{ - transactionHash: '0x12', - transactionHtml: 'test 12' - },{ - transactionHash: '0x13', - transactionHtml: 'test 13' - },{ - transactionHash: '0x14', - transactionHtml: 'test 14' - },{ - transactionHash: '0x15', - transactionHtml: 'test 15' - },{ - transactionHash: '0x16', - transactionHtml: 'test 16' - },{ - transactionHash: '0x17', - transactionHtml: 'test 17' - },{ - transactionHash: '0x18', - transactionHtml: 'test 18' - },{ - transactionHash: '0x19', - transactionHtml: 'test 19' - },{ - transactionHash: '0x20', - transactionHtml: 'test 20' - },{ - transactionHash: '0x21', - transactionHtml: 'test 21' - },{ - transactionHash: '0x22', - transactionHtml: 'test 22' - }] - } - const output = reducer(state, action) - - expect(output.newPendingTransactions).toEqual([]) - expect(output.newPendingTransactionHashesBatch.length).toEqual(22) + expect(output.pendingTransactionHashes.length).toEqual(1) }) test('after disconnection', () => { const state = Object.assign({}, initialState, { channelDisconnected: true }) const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_PENDING_TRANSACTION', + msg: { transactionHash: '0x00', transactionHtml: 'test' - }] + } } const output = reducer(state, action) expect(output.newPendingTransactions).toEqual([]) - expect(output.newPendingTransactionHashesBatch).toEqual([]) + expect(output.pendingTransactionHashes).toEqual([]) }) test('on page 2', () => { const state = Object.assign({}, initialState, { beyondPageOne: true }) const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_PENDING_TRANSACTION', + msg: { transactionHash: '0x00', transactionHtml: 'test' - }] + } } const output = reducer(state, action) expect(output.newPendingTransactions).toEqual([]) - expect(output.newPendingTransactionHashesBatch).toEqual([]) - expect(output.batchCountAccumulator).toEqual(0) + expect(output.pendingTransactionHashes).toEqual([]) }) }) -describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { +describe('RECEIVED_NEW_TRANSACTION', () => { test('single transaction', () => { const state = Object.assign({}, initialState, { addressHash: '0x111' }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test', - fromAddressHash: '0x111' - }] - } - const output = reducer(state, action) - - expect(output.newTransactions).toEqual(['test']) - expect(output.batchCountAccumulator).toEqual(0) - expect(output.transactionCount).toEqual(1) - }) - test('large batch of transactions', () => { - const state = initialState - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test 1' - },{ - transactionHtml: 'test 2' - },{ - transactionHtml: 'test 3' - },{ - transactionHtml: 'test 4' - },{ - transactionHtml: 'test 5' - },{ - transactionHtml: 'test 6' - },{ - transactionHtml: 'test 7' - },{ - transactionHtml: 'test 8' - },{ - transactionHtml: 'test 9' - },{ - transactionHtml: 'test 10' - },{ - transactionHtml: 'test 11' - }] + type: 'RECEIVED_NEW_TRANSACTION', + msg: { + transactionHtml: 'test' + } } const output = reducer(state, action) - expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(11) + expect(output.newTransactions).toEqual([{ transactionHtml: 'test' }]) + expect(output.transactionCount).toEqual(null) }) test('single transaction after single transaction', () => { const state = Object.assign({}, initialState, { - newTransactions: ['test 1'] + newTransactions: [{ transactionHtml: 'test 1' }] }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { transactionHtml: 'test 2' - }] - } - const output = reducer(state, action) - - expect(output.newTransactions).toEqual(['test 1', 'test 2']) - expect(output.batchCountAccumulator).toEqual(0) - }) - test('single transaction after large batch of transactions', () => { - const state = Object.assign({}, initialState, { - newTransactions: [], - batchCountAccumulator: 11 - }) - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test 12' - }] + } } const output = reducer(state, action) - expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(12) - }) - test('large batch of transactions after large batch of transactions', () => { - const state = Object.assign({}, initialState, { - newTransactions: [], - batchCountAccumulator: 11 - }) - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test 12' - },{ - transactionHtml: 'test 13' - },{ - transactionHtml: 'test 14' - },{ - transactionHtml: 'test 15' - },{ - transactionHtml: 'test 16' - },{ - transactionHtml: 'test 17' - },{ - transactionHtml: 'test 18' - },{ - transactionHtml: 'test 19' - },{ - transactionHtml: 'test 20' - },{ - transactionHtml: 'test 21' - },{ - transactionHtml: 'test 22' - }] - } - const output = reducer(state, action) - - expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(22) - }) - test('increments the transactions count only when the address sent transactions', () => { - const state = Object.assign({}, initialState, { - newTransactions: [], - addressHash: '0x111', - transactionCount: 1 - }) - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test 12', - fromAddressHash: '0x111', - toAddressHash: '0x222' - },{ - transactionHtml: 'test 13', - fromAddressHash: '0x222', - toAddressHash: '0x111' - }] - } - const output = reducer(state, action) - - expect(output.transactionCount).toEqual(2) + expect(output.newTransactions).toEqual([ + { transactionHtml: 'test 1' }, + { transactionHtml: 'test 2' } + ]) }) test('after disconnection', () => { const state = Object.assign({}, initialState, { channelDisconnected: true }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { transactionHtml: 'test' - }] + } } const output = reducer(state, action) expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(0) }) test('on page 2', () => { const state = Object.assign({}, initialState, { @@ -425,17 +203,15 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { addressHash: '0x111' }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test', - fromAddressHash: '0x111' - }] + type: 'RECEIVED_NEW_TRANSACTION', + msg: { + transactionHtml: 'test' + } } const output = reducer(state, action) expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(0) - expect(output.transactionCount).toEqual(2) + expect(output.transactionCount).toEqual(1) }) test('transaction from current address with "from" filter', () => { const state = Object.assign({}, initialState, { @@ -443,15 +219,17 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { filter: 'from' }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { fromAddressHash: '1234', transactionHtml: 'test' - }] + } } const output = reducer(state, action) - expect(output.newTransactions).toEqual(['test']) + expect(output.newTransactions).toEqual([ + { fromAddressHash: '1234', transactionHtml: 'test' } + ]) }) test('transaction from current address with "to" filter', () => { const state = Object.assign({}, initialState, { @@ -459,11 +237,11 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { filter: 'to' }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { fromAddressHash: '1234', transactionHtml: 'test' - }] + } } const output = reducer(state, action) @@ -475,15 +253,17 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { filter: 'to' }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { toAddressHash: '1234', transactionHtml: 'test' - }] + } } const output = reducer(state, action) - expect(output.newTransactions).toEqual(['test']) + expect(output.newTransactions).toEqual([ + { toAddressHash: '1234', transactionHtml: 'test' } + ]) }) test('transaction to current address with "from" filter', () => { const state = Object.assign({}, initialState, { @@ -491,11 +271,11 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { filter: 'from' }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { toAddressHash: '1234', transactionHtml: 'test' - }] + } } const output = reducer(state, action) @@ -504,58 +284,17 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { test('single transaction collated from pending', () => { const state = initialState const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { transactionHash: '0x00', transactionHtml: 'test' - }] + } } const output = reducer(state, action) - expect(output.newTransactions).toEqual(['test']) - expect(output.batchCountAccumulator).toEqual(0) - }) - test('large batch of transactions', () => { - const state = initialState - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHash: '0x01', - transactionHtml: 'test 1' - },{ - transactionHash: '0x02', - transactionHtml: 'test 2' - },{ - transactionHash: '0x03', - transactionHtml: 'test 3' - },{ - transactionHash: '0x04', - transactionHtml: 'test 4' - },{ - transactionHash: '0x05', - transactionHtml: 'test 5' - },{ - transactionHash: '0x06', - transactionHtml: 'test 6' - },{ - transactionHash: '0x07', - transactionHtml: 'test 7' - },{ - transactionHash: '0x08', - transactionHtml: 'test 8' - },{ - transactionHash: '0x09', - transactionHtml: 'test 9' - },{ - transactionHash: '0x10', - transactionHtml: 'test 10' - },{ - transactionHash: '0x11', - transactionHtml: 'test 11' - }] - } - const output = reducer(state, action) - - expect(output.newTransactions).toEqual([]) + expect(output.newTransactions).toEqual([ + { transactionHash: '0x00', transactionHtml: 'test' } + ]) + expect(output.transactionCount).toEqual(null) }) }) diff --git a/apps/block_scout_web/assets/__tests__/pages/chain.js b/apps/block_scout_web/assets/__tests__/pages/chain.js index 4f0cde7106..9ff632f2d4 100644 --- a/apps/block_scout_web/assets/__tests__/pages/chain.js +++ b/apps/block_scout_web/assets/__tests__/pages/chain.js @@ -195,120 +195,32 @@ test('RECEIVED_NEW_EXCHANGE_RATE', () => { expect(output.usdMarketCap).toEqual(1230000) }) -describe('RECEIVED_NEW_TRANSACTION_BATCH', () => { +describe('RECEIVED_NEW_TRANSACTION', () => { test('single transaction', () => { const state = initialState const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { transactionHtml: 'test' - }] + } } const output = reducer(state, action) expect(output.newTransactions).toEqual(['test']) - expect(output.batchCountAccumulator).toEqual(0) expect(output.transactionCount).toEqual(1) }) - test('large batch of transactions', () => { - const state = initialState - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test 1' - },{ - transactionHtml: 'test 2' - },{ - transactionHtml: 'test 3' - },{ - transactionHtml: 'test 4' - },{ - transactionHtml: 'test 5' - },{ - transactionHtml: 'test 6' - },{ - transactionHtml: 'test 7' - },{ - transactionHtml: 'test 8' - },{ - transactionHtml: 'test 9' - },{ - transactionHtml: 'test 10' - },{ - transactionHtml: 'test 11' - }] - } - const output = reducer(state, action) - - expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(11) - expect(output.transactionCount).toEqual(11) - }) test('single transaction after single transaction', () => { const state = Object.assign({}, initialState, { newTransactions: ['test 1'] }) const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ + type: 'RECEIVED_NEW_TRANSACTION', + msg: { transactionHtml: 'test 2' - }] + } } const output = reducer(state, action) expect(output.newTransactions).toEqual(['test 1', 'test 2']) - expect(output.batchCountAccumulator).toEqual(0) - }) - test('single transaction after large batch of transactions', () => { - const state = Object.assign({}, initialState, { - newTransactions: [], - batchCountAccumulator: 11 - }) - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test 12' - }] - } - const output = reducer(state, action) - - expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(12) - }) - test('large batch of transactions after large batch of transactions', () => { - const state = Object.assign({}, initialState, { - newTransactions: [], - batchCountAccumulator: 11 - }) - const action = { - type: 'RECEIVED_NEW_TRANSACTION_BATCH', - msgs: [{ - transactionHtml: 'test 12' - },{ - transactionHtml: 'test 13' - },{ - transactionHtml: 'test 14' - },{ - transactionHtml: 'test 15' - },{ - transactionHtml: 'test 16' - },{ - transactionHtml: 'test 17' - },{ - transactionHtml: 'test 18' - },{ - transactionHtml: 'test 19' - },{ - transactionHtml: 'test 20' - },{ - transactionHtml: 'test 21' - },{ - transactionHtml: 'test 22' - }] - } - const output = reducer(state, action) - - expect(output.newTransactions).toEqual([]) - expect(output.batchCountAccumulator).toEqual(22) }) }) diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index 394195da2f..935a325d04 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -26,16 +26,17 @@ import './lib/from_now' import './lib/indexing' import './lib/loading_element' import './lib/market_history_chart' +import './lib/pending_transactions_toggle' +import './lib/pretty_json' import './lib/reload_button' -import './lib/tooltip' import './lib/smart_contract/read_only_functions' import './lib/smart_contract/wei_ether_converter' -import './lib/pretty_json' -import './lib/try_api' +import './lib/stop_propagation' import './lib/token_balance_dropdown' import './lib/token_balance_dropdown_search' import './lib/token_transfers_toggle' -import './lib/stop_propagation' +import './lib/tooltip' +import './lib/try_api' import './pages/address' import './pages/block' diff --git a/apps/block_scout_web/assets/js/lib/pending_transactions_toggle.js b/apps/block_scout_web/assets/js/lib/pending_transactions_toggle.js new file mode 100644 index 0000000000..26c28465a1 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/pending_transactions_toggle.js @@ -0,0 +1,20 @@ +import $ from 'jquery' + +const pendingTransactionToggle = (element) => { + const $element = $(element) + const $pendingTransactionsClose = $element.find("[data-selector='pending-transactions-close']") + const $pendingTransactionsOpen = $element.find("[data-selector='pending-transactions-open']") + + $element.on('show.bs.collapse', () => { + $pendingTransactionsOpen.addClass('d-none') + $pendingTransactionsClose.removeClass('d-none') + }) + + $element.on('hide.bs.collapse', () => { + $pendingTransactionsClose.addClass('d-none') + $pendingTransactionsOpen.removeClass('d-none') + }) +} + +// Initialize the script scoped for each instance. +$("[data-selector='pending-transactions-toggle']").each((_index, element) => pendingTransactionToggle(element)) diff --git a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js index ff763df87c..3fac9f8ae8 100644 --- a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js +++ b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js @@ -6,8 +6,6 @@ const tokenBalanceDropdown = (element) => { const $errorMessage = $element.find('[data-error-message]') const apiPath = element.dataset.api_path - $loading.show() - $.get(apiPath) .done(response => $element.html(response)) .fail(() => { diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index d8e7beb849..c3ed5a9ba7 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -4,25 +4,13 @@ import URI from 'urijs' import humps from 'humps' import numeral from 'numeral' import socket from '../socket' -import { batchChannel, initRedux, prependWithClingBottom } from '../utils' +import { batchChannel, initRedux, slideDownPrepend, slideUpRemove } from '../utils' import { updateAllAges } from '../lib/from_now' import { updateAllCalculatedUsdValues } from '../lib/currency.js' import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown' const BATCH_THRESHOLD = 10 -const incrementTransactionsCount = (transactions, addressHash, currentValue) => { - const reducer = (accumulator, {fromAddressHash}) => { - if (fromAddressHash === addressHash) { - accumulator++ - } - - return accumulator - } - - return transactions.reduce(reducer, currentValue) -} - export const initialState = { addressHash: null, balance: null, @@ -34,8 +22,7 @@ export const initialState = { newInternalTransactions: [], newPendingTransactions: [], newTransactions: [], - newTransactionHashes: [], - newPendingTransactionHashesBatch: [], + pendingTransactionHashes: [], transactionCount: null, validationCount: null } @@ -47,6 +34,7 @@ export function reducer (state = initialState, action) { addressHash: action.addressHash, beyondPageOne: action.beyondPageOne, filter: action.filter, + pendingTransactionHashes: action.pendingTransactionHashes, transactionCount: numeral(action.transactionCount).value(), validationCount: action.validationCount ? numeral(action.validationCount).value() : null }) @@ -93,66 +81,47 @@ export function reducer (state = initialState, action) { }) } } - case 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH': { + case 'RECEIVED_NEW_PENDING_TRANSACTION': { if (state.channelDisconnected || state.beyondPageOne) return state - const incomingPendingTransactions = action.msgs - .filter(({toAddressHash, fromAddressHash}) => ( - !state.filter || - (state.filter === 'to' && toAddressHash === state.addressHash) || - (state.filter === 'from' && fromAddressHash === state.addressHash) - )) - if (!state.newPendingTransactionHashesBatch.length && incomingPendingTransactions.length < BATCH_THRESHOLD) { - return Object.assign({}, state, { - newPendingTransactions: [ - ...state.newPendingTransactions, - ..._.map(incomingPendingTransactions, 'transactionHtml') - ] - }) - } else { - return Object.assign({}, state, { - newPendingTransactionHashesBatch: [ - ...state.newPendingTransactionHashesBatch, - ..._.map(incomingPendingTransactions, 'transactionHash') - ] - }) + if ((state.filter === 'to' && action.msg.toAddressHash !== state.addressHash) || + (state.filter === 'from' && action.msg.fromAddressHash !== state.addressHash)) { + return state } + + return Object.assign({}, state, { + newPendingTransactions: [ + ...state.newPendingTransactions, + action.msg.transactionHtml + ], + pendingTransactionHashes: [ + ...state.pendingTransactionHashes, + action.msg.transactionHash + ] + }) } - case 'RECEIVED_NEW_TRANSACTION_BATCH': { + case 'RECEIVED_NEW_TRANSACTION': { if (state.channelDisconnected) return state - const transactionCount = incrementTransactionsCount(action.msgs, state.addressHash, state.transactionCount) + const transactionCount = (action.msg.fromAddressHash === state.addressHash) ? state.transactionCount + 1 : state.transactionCount - if (state.beyondPageOne) return Object.assign({}, state, { transactionCount }) - - const incomingTransactions = action.msgs - .filter(({toAddressHash, fromAddressHash}) => ( - !state.filter || - (state.filter === 'to' && toAddressHash === state.addressHash) || - (state.filter === 'from' && fromAddressHash === state.addressHash) - )) + if (state.beyondPageOne || + (state.filter === 'to' && action.msg.toAddressHash !== state.addressHash) || + (state.filter === 'from' && action.msg.fromAddressHash !== state.addressHash)) { + return Object.assign({}, state, { transactionCount }) + } - const updatedPendingTransactionHashesBatch = - _.difference(state.newPendingTransactionHashesBatch, _.map(incomingTransactions, 'transactionHash')) + const updatedPendingTransactionHashes = + _.without(state.pendingTransactionHashes, action.msg.transactionHash) - if (!state.batchCountAccumulator && incomingTransactions.length < BATCH_THRESHOLD) { - return Object.assign({}, state, { - newTransactions: [ - ...state.newTransactions, - ..._.map(incomingTransactions, 'transactionHtml') - ], - newTransactionHashes: _.map(incomingTransactions, 'transactionHash'), - newPendingTransactionHashesBatch: updatedPendingTransactionHashesBatch, - transactionCount: transactionCount - }) - } else { - return Object.assign({}, state, { - batchCountAccumulator: state.batchCountAccumulator + incomingTransactions.length, - newTransactionHashes: _.map(incomingTransactions, 'transactionHash'), - newPendingTransactionHashesBatch: updatedPendingTransactionHashesBatch, - transactionCount: transactionCount - }) - } + return Object.assign({}, state, { + newTransactions: [ + ...state.newTransactions, + action.msg + ], + pendingTransactionHashes: updatedPendingTransactionHashes, + transactionCount: transactionCount + }) } case 'RECEIVED_UPDATED_BALANCE': { return Object.assign({}, state, { @@ -176,6 +145,8 @@ if ($addressDetailsPage.length) { addressHash, beyondPageOne: !!blockNumber, filter, + pendingTransactionHashes: $('[data-selector="pending-transactions-list"]').children() + .map((index, el) => el.dataset.transactionHash).toArray(), transactionCount: $('[data-selector="transaction-count"]').text(), validationCount: $('[data-selector="validation-count"]') ? $('[data-selector="validation-count"]').text() : null }) @@ -187,29 +158,22 @@ if ($addressDetailsPage.length) { addressChannel.on('internal_transaction', batchChannel((msgs) => store.dispatch({ type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) }) )) - addressChannel.on('pending_transaction', batchChannel((msgs) => - store.dispatch({ type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) }) - )) - addressChannel.on('transaction', batchChannel((msgs) => - store.dispatch({ type: 'RECEIVED_NEW_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) })) 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) }) - }) + 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 $channelPendingBatching = $('[data-selector="channel-pending-batching-message"]') - const $channelPendingBatchingCount = $('[data-selector="channel-pending-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"]') @@ -232,34 +196,39 @@ if ($addressDetailsPage.length) { } else { $channelBatching.hide() } - if (oldState.newPendingTransactionHashesBatch !== state.newPendingTransactionHashesBatch && state.newPendingTransactionHashesBatch.length > 0) { - $channelPendingBatching.show() - $channelPendingBatchingCount[0].innerHTML = numeral(state.newPendingTransactionHashesBatch.length).format() - } else { - $channelPendingBatching.hide() - } - if (oldState.newTransactionHashes !== state.newTransactionHashes && state.newTransactionHashes.length > 0) { - let $transaction - _.each(state.newTransactionHashes, (hash) => { - $transaction = $(`[data-selector="pending-transactions-list"] [data-transaction-hash="${hash}"]`) - $transaction.addClass('shrink-out') - setTimeout(() => $transaction.slideUp({ complete: () => $transaction.remove() }), 400) - }) - } if (oldState.newInternalTransactions !== state.newInternalTransactions && $internalTransactionsList.length) { - prependWithClingBottom($internalTransactionsList, state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join('')) + 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) { - prependWithClingBottom($pendingTransactionsList, state.newPendingTransactions.slice(oldState.newPendingTransactions.length).reverse().join('')) + slideDownPrepend($pendingTransactionsList, state.newPendingTransactions.slice(oldState.newPendingTransactions.length).reverse().join('')) updateAllAges() } if (oldState.newTransactions !== state.newTransactions && $transactionsList.length) { - prependWithClingBottom($transactionsList, state.newTransactions.slice(oldState.newTransactions.length).reverse().join('')) + 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() } if (oldState.newBlock !== state.newBlock) { - prependWithClingBottom($validationsList, state.newBlock) + slideDownPrepend($validationsList, state.newBlock) updateAllAges() } } diff --git a/apps/block_scout_web/assets/js/pages/block.js b/apps/block_scout_web/assets/js/pages/block.js index 4e137984a6..550aafda28 100644 --- a/apps/block_scout_web/assets/js/pages/block.js +++ b/apps/block_scout_web/assets/js/pages/block.js @@ -4,7 +4,7 @@ import URI from 'urijs' import humps from 'humps' import socket from '../socket' import { updateAllAges } from '../lib/from_now' -import { buildFullBlockList, initRedux, beforeWithClingBottom, skippedBlockListBuilder } from '../utils' +import { buildFullBlockList, initRedux, slideDownBefore, skippedBlockListBuilder } from '../utils' export const initialState = { blockNumbers: [], @@ -97,10 +97,10 @@ if ($blockListPage.length) { } else { if (skippedBlockNumbers.length) { _.forEachRight(skippedBlockNumbers, (skippedBlockNumber) => { - beforeWithClingBottom($(`[data-block-number="${skippedBlockNumber - 1}"]`), placeHolderBlock(skippedBlockNumber)) + slideDownBefore($(`[data-block-number="${skippedBlockNumber - 1}"]`), placeHolderBlock(skippedBlockNumber)) }) } - beforeWithClingBottom($(`[data-block-number="${state.blockNumbers[0] - 1}"]`), state.newBlock) + slideDownBefore($(`[data-block-number="${state.blockNumbers[0] - 1}"]`), state.newBlock) } updateAllAges() } diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js index c9755da39e..4586fff98f 100644 --- a/apps/block_scout_web/assets/js/pages/chain.js +++ b/apps/block_scout_web/assets/js/pages/chain.js @@ -5,16 +5,13 @@ import numeral from 'numeral' import socket from '../socket' import { updateAllAges } from '../lib/from_now' import { exchangeRateChannel, formatUsdValue } from '../lib/currency' -import { batchChannel, buildFullBlockList, initRedux, skippedBlockListBuilder, slideDownPrepend } from '../utils' +import { buildFullBlockList, initRedux, skippedBlockListBuilder, slideDownPrepend } from '../utils' import { createMarketHistoryChart } from '../lib/market_history_chart' -const BATCH_THRESHOLD = 10 - export const initialState = { addressCount: null, availableSupply: null, averageBlockTime: null, - batchCountAccumulator: 0, blockNumbers: [], marketHistoryData: null, newBlock: null, @@ -85,21 +82,14 @@ export function reducer (state = initialState, action) { usdMarketCap: action.msg.exchangeRate.marketCapUsd }) } - case 'RECEIVED_NEW_TRANSACTION_BATCH': { - if (!state.batchCountAccumulator && action.msgs.length < BATCH_THRESHOLD) { - return Object.assign({}, state, { - newTransactions: [ - ...state.newTransactions, - ...action.msgs.map(({transactionHtml}) => transactionHtml) - ], - transactionCount: state.transactionCount + action.msgs.length - }) - } else { - return Object.assign({}, state, { - batchCountAccumulator: state.batchCountAccumulator + action.msgs.length, - transactionCount: state.transactionCount + action.msgs.length - }) - } + case 'RECEIVED_NEW_TRANSACTION': { + return Object.assign({}, state, { + newTransactions: [ + ...state.newTransactions, + action.msg.transactionHtml + ], + transactionCount: state.transactionCount + 1 + }) } default: return state @@ -128,9 +118,7 @@ if ($chainDetailsPage.length) { const transactionsChannel = socket.channel(`transactions:new_transaction`) transactionsChannel.join() - transactionsChannel.on('transaction', batchChannel((msgs) => - store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) })) - ) + transactionsChannel.on('transaction', (msg) => store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION', msg: humps.camelizeKeys(msg) })) chart = createMarketHistoryChart($('[data-chart="marketHistoryChart"]')[0]) }, @@ -138,8 +126,6 @@ if ($chainDetailsPage.length) { const $addressCount = $('[data-selector="address-count"]') const $averageBlockTime = $('[data-selector="average-block-time"]') const $blockList = $('[data-selector="chain-block-list"]') - const $channelBatching = $('[data-selector="channel-batching-message"]') - const $channelBatchingCount = $('[data-selector="channel-batching-count"]') const $marketCap = $('[data-selector="market-cap"]') const $transactionsList = $('[data-selector="transactions-list"]') const $transactionCount = $('[data-selector="transaction-count"]') @@ -176,12 +162,6 @@ if ($chainDetailsPage.length) { updateAllAges() } if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format()) - if (state.batchCountAccumulator) { - $channelBatching.show() - $channelBatchingCount[0].innerHTML = numeral(state.batchCountAccumulator).format() - } else { - $channelBatching.hide() - } if (newTransactions.length) { const newTransactionsToInsert = state.newTransactions.slice(oldState.newTransactions.length) $transactionsList diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index 0c91a7cc5e..beda3482c4 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -5,7 +5,7 @@ import humps from 'humps' import numeral from 'numeral' import socket from '../socket' import { updateAllAges } from '../lib/from_now' -import { batchChannel, initRedux, prependWithClingBottom } from '../utils' +import { batchChannel, initRedux, slideDownPrepend, slideUpRemove } from '../utils' const BATCH_THRESHOLD = 10 @@ -171,15 +171,13 @@ if ($transactionPendingListPage.length) { if (oldState.newTransactionHashes !== state.newTransactionHashes && state.newTransactionHashes.length > 0) { const $transaction = $(`[data-transaction-hash="${state.newTransactionHashes[0]}"]`) $transaction.addClass('shrink-out') - setTimeout(() => $transaction.slideUp({ - complete: () => { - if ($pendingTransactionsList.children().length < 2 && state.pendingTransactionCount > 0) { - window.location.href = URI(window.location).removeQuery('inserted_at').removeQuery('hash').toString() - } else { - $transaction.remove() - } + setTimeout(() => { + if ($transaction.length === 1 && $transaction.siblings().length === 0 && state.pendingTransactionCount > 0) { + window.location.href = URI(window.location).removeQuery('inserted_at').removeQuery('hash').toString() + } else { + slideUpRemove($transaction) } - }), 400) + }, 400) } if (state.newPendingTransactionHashesBatch.length) { $channelBatching.show() @@ -189,13 +187,7 @@ if ($transactionPendingListPage.length) { } if (oldState.newPendingTransactions !== state.newPendingTransactions) { const newTransactionsToInsert = state.newPendingTransactions.slice(oldState.newPendingTransactions.length) - $pendingTransactionsList - .children() - .slice($pendingTransactionsList.children().length - newTransactionsToInsert.length, - $pendingTransactionsList.children().length - ) - .remove() - prependWithClingBottom($pendingTransactionsList, newTransactionsToInsert.reverse().join('')) + slideDownPrepend($pendingTransactionsList, newTransactionsToInsert.reverse().join('')) updateAllAges() } @@ -236,11 +228,7 @@ if ($transactionListPage.length) { } if (oldState.newTransactions !== state.newTransactions) { const newTransactionsToInsert = state.newTransactions.slice(oldState.newTransactions.length) - $transactionsList - .children() - .slice($transactionsList.children().length - newTransactionsToInsert.length, $transactionsList.children().length) - .remove() - prependWithClingBottom($transactionsList, newTransactionsToInsert.reverse().join('')) + slideDownPrepend($transactionsList, newTransactionsToInsert.reverse().join('')) updateAllAges() } diff --git a/apps/block_scout_web/assets/js/utils.js b/apps/block_scout_web/assets/js/utils.js index 6544debd3c..ed69186d1c 100644 --- a/apps/block_scout_web/assets/js/utils.js +++ b/apps/block_scout_web/assets/js/utils.js @@ -45,61 +45,63 @@ export function skippedBlockListBuilder (skippedBlockNumbers, newestBlock, oldes return skippedBlockNumbers } -export function slideDownPrepend ($el, content, callback) { - const $content = $(content) - $el.prepend($content.hide()) - $content.slideDown({ complete: callback }) -} -export function slideDownBefore ($el, content, callback) { - const $content = $(content) - $el.before($content.hide()) - $content.slideDown({ complete: callback }) +export function slideDownPrepend ($container, content) { + smarterSlideDown($(content), { + insert ($el) { + $container.prepend($el) + } + }) } -export function prependWithClingBottom ($el, content) { - return slideDownPrepend($el, content, clingBottom($el, content)) +export function slideDownBefore ($container, content) { + smarterSlideDown($(content), { + insert ($el) { + $container.before($el) + } + }) } -export function beforeWithClingBottom ($el, content) { - return slideDownBefore($el, content, clingBottom($el, content)) +export function slideUpRemove ($el) { + smarterSlideUp($el, { + complete () { + $el.remove() + } + }) } -function clingBottom ($el, content) { - function userAtTop () { - return window.scrollY < $('[data-selector="navbar"]').outerHeight() - } - if (userAtTop()) return true +function smarterSlideDown ($el, { insert = _.noop } = {}) { + if (!$el.length) return + const originalScrollHeight = document.body.scrollHeight + const scrollPosition = window.scrollY - let isAnimating - function setIsAnimating () { - isAnimating = true - } - $el.on('animationstart', setIsAnimating) + insert($el) + + const isAboveViewport = $el.offset().top < scrollPosition - let startingScrollPosition = window.scrollY - let endingScrollPosition = window.scrollY - function userIsScrolling () { - return window.scrollY < startingScrollPosition || endingScrollPosition < window.scrollY + if (isAboveViewport) { + const heightDiffAfterInsert = document.body.scrollHeight - originalScrollHeight + const scrollPositionToMaintainViewport = scrollPosition + heightDiffAfterInsert + + $(window).scrollTop(scrollPositionToMaintainViewport) + } else { + $el.hide() + $el.slideDown({ easing: 'linear' }) } +} - const clingDistanceFromBottom = document.body.scrollHeight - window.scrollY - let clingBottomLoop = window.requestAnimationFrame(function clingBottom () { - if (userIsScrolling()) return stopClinging() +function smarterSlideUp ($el, { complete = _.noop } = {}) { + if (!$el.length) return + const originalScrollHeight = document.body.scrollHeight + const scrollPosition = window.scrollY + const isAboveViewport = $el.offset().top + $el.outerHeight(true) < scrollPosition - startingScrollPosition = window.scrollY - endingScrollPosition = document.body.scrollHeight - clingDistanceFromBottom - $(window).scrollTop(endingScrollPosition) - clingBottomLoop = window.requestAnimationFrame(clingBottom) - }) + if (isAboveViewport) { + $el.hide() - function stopClinging () { - window.cancelAnimationFrame(clingBottomLoop) - $el.off('animationstart', setIsAnimating) - $el.off('animationend animationcancel', stopClinging) - } + const heightDiffAfterHide = document.body.scrollHeight - originalScrollHeight + const scrollPositionToMaintainViewport = scrollPosition + heightDiffAfterHide - return { - function () { - $el.on('animationend animationcancel', stopClinging) - setTimeout(() => !isAnimating && stopClinging(), 100) - } + $(window).scrollTop(scrollPositionToMaintainViewport) + complete() + } else { + $el.slideUp({ complete, easing: 'linear' }) } } diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex index a2c85c5e20..e9ac3efa55 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex @@ -10,14 +10,14 @@