Skip to content
This repository has been archived by the owner on Dec 1, 2021. It is now read-only.

Fixed transactions page scrolling issue #511

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [UNRELEASED]
* [#524] Added `noreferrer` to Banner links
* [#447] Fixed txs scrolling issue that was causing page to reload and jump to the top when user scrolled to the bottom of the page.

## v0.41.x-14
* [#488] Updated missing proposer address in proposals.
Expand Down
4 changes: 4 additions & 0 deletions client/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,10 @@ body {
padding-bottom: 0.4rem;
border-bottom: 1px solid $gray-300;
}
&.tx-info-row{
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
}

.tx-info.invalid {
Expand Down
77 changes: 58 additions & 19 deletions imports/ui/transactions/List.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,90 @@ import React, { Component } from 'react';
import { Row, Col, Spinner } from 'reactstrap';
import { TransactionRow } from './TransactionRow.jsx';
import i18n from 'meteor/universe:i18n';
import { VariableSizeList } from 'react-window';


const T = i18n.createComponent();
export default class Transactions extends Component{
constructor(props){
export default class Transactions extends Component {
constructor(props) {
super(props);
this.childRef = React.createRef();

this.state = {
txs: "",
homepage: window?.location?.pathname === '/' ? true : false

homepage: window?.location?.pathname === '/' ? true : false,
}
}

componentDidUpdate(prevProps){
if (this.props != prevProps){
if (this.props.transactions.length > 0){
componentDidUpdate(prevProps, prevState) {
if (this.props != prevProps) {
if (this.props.transactions.length > 0) {
this.setState({
txs: this.props.transactions.map((tx, i) => {
return <TransactionRow
key={i}
index={i}
tx={tx}
return <TransactionRow
key={i}
index={i}
tx={tx}
ref={this.childRef}
/>
})
})
}),
height: this.childRef.current?.myRef?.current?.clientHeight
})
}
}
}

componentDidMount() {
this.childRef?.current?.myRef?.current?.clientHeight
}


getItemSize = index => {
if (this.state.txs[index]?.props?.tx?.tx?.body?.messages.length > 1) {
if (this.state.txs[index]?.props?.tx?.tx?.body?.messages[0]['@type'] === '/cosmos.bank.v1beta1.MsgSend') {
return 100 * this.state.txs[index]?.props?.tx?.tx?.body?.messages.length
}
return 80 * this.state.txs[index]?.props?.tx?.tx?.body?.messages.length
}
else {
return 130
}
}

render(){
if (this.props.loading){
TxRow = ({ index, style }) => (
<div className="tx-info" id="tx-infos"
style={{
...style
}}
>{this.state.txs[index]}</div>
);

render() {
if (this.props.loading) {
return <Spinner type="grow" color="primary" />
}
else if (!this.props.transactionsExist){
else if (!this.props.transactionsExist) {
return <div><T>transactions.notFound</T></div>
}
else{
else {
return <div className="transactions-list">
<Row className="header text-nowrap d-none d-lg-flex">
<Col xs={9} lg={this.state.homepage ? 5 : 7}><i className="material-icons">message</i> <span className="d-none d-md-inline-block"><T>transactions.activities</T></span></Col>
<Col xs={3} lg={!this.state.homepage ? { size: 1, order: "last" } : { size: 2, order: "last" }}><span className={this.state.homepage ? "ml-5" : null}><i className="fas fa-hashtag"></i> <span className="d-none d-md-inline-block"><T>transactions.txHash</T></span></span></Col>
<Col xs={4} md={2} lg={1}><i className="fas fa-database"></i> <span className="d-none d-md-inline-block"><T>common.height</T></span></Col>
<Col xs={2} md={1} className="text-nowrap"><span className={this.state.homepage ? "ml-4" : null}><i className="material-icons">check_circle</i> <span className="d-none d-lg-inline-block"><T>transactions.valid</T></span></span></Col>
{!this.state.homepage ? <Col xs={12} lg={2}><i className="material-icons">monetization_on</i> <span className="d-none d-md-inline-block"><T>transactions.fee</T></span></Col> : null }
{!this.state.homepage ? <Col xs={12} lg={2}><i className="material-icons">monetization_on</i> <span className="d-none d-md-inline-block"><T>transactions.fee</T></span></Col> : null}
</Row>
{this.state.txs}
{this.state.txs ?
<VariableSizeList
height={700}
itemCount={this.props.transactionsCount}
itemSize={this.getItemSize}
width="100%"
>
{this.TxRow}
</VariableSizeList>
: ''}
</div>
}
}
Expand Down
13 changes: 7 additions & 6 deletions imports/ui/transactions/ListContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@ export default ValidatorDetailsContainer = withTracker((props) => {
let transactionsHandle, transactions, transactionsExist;
let loading = true;

if (Meteor.isClient){
if (Meteor.isClient) {
transactionsHandle = Meteor.subscribe('transactions.list', props.limit);
loading = !transactionsHandle.ready();
loading = !transactionsHandle.ready() && props.limit == Meteor.settings.public.initialPageSize;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MonikaCat yes this would fix the display issue. However, if the txs are being loaded continuously, the DOM will be rendering too many components if the users keeps scrolling. Can you do a test and see if the screen will be laggy after some scrolls?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! The screen becomes a little bit laggy after scrolling down multiple times.

I have wrapped the list in Container so we can track scrolling only of the list component. The overall performance of the page has improved and the transactions are loading faster. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. Why wrapped inside a <Container> would improve performance? I think the <List> component is still querying and rendering components controlled by limit?


if (!loading) {
transactions = Transactions.find({}, {sort:{height:-1}}).fetch();
transactions = Transactions.find({}, { sort: { height: -1 } }).fetch();
transactionsExist = !loading && !!transactions;
}
}

if (Meteor.isServer){
transactions = Transactions.find({}, {sort:{height:-1}}).fetch();
if (Meteor.isServer) {
transactions = Transactions.find({}, { sort: { height: -1 } }).fetch();
transactionsExist = !!transactions;
}

return {
loading,
transactionsExist,
transactions: transactionsExist ? transactions : {},
transactionsCount: transactionsExist ? transactions.length : 0
};
})(List);
45 changes: 22 additions & 23 deletions imports/ui/transactions/Transaction.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,40 @@ import Coin from '/both/utils/coins.js';
import TimeStamp from '../components/TimeStamp.jsx';

const T = i18n.createComponent();
export default class Transaction extends Component{
constructor(props){
export default class Transaction extends Component {
constructor(props) {
super(props);
let showdown = require('showdown');
let showdown = require('showdown');
showdown.setFlavor('github');
let denom = this.props.denom;
}

render(){
if (this.props.loading){
render() {


if (this.props.loading) {
return <Container id="transaction">
<Spinner type="grow" color="primary" />
</Container>
}
else{
if (this.props.transactionExist){
else {
if (this.props.transactionExist) {
let tx = this.props.transaction;
return <Container id="transaction">
<Helmet>
<title>Transaction {tx.txhash} on {Meteor.settings.public.chainName} | Big Dipper</title>
<meta name="description" content={"Details of transaction "+tx.txhash} />
<meta name="description" content={"Details of transaction " + tx.txhash} />
</Helmet>
<h4><T>transactions.transaction</T> {(!tx.code)?<TxIcon valid />:<TxIcon />}</h4>
{(tx.code)?<Row><Col xs={{size:12, order:"last"}} className="error">
<h4><T>transactions.transaction</T> {(!tx.code) ? <TxIcon valid /> : <TxIcon />}</h4>
{(tx.code) ? <Row><Col xs={{ size: 12, order: "last" }} className="error">
<Alert color="danger">
<CosmosErrors
code={tx.code}
codespace={tx.codespace}
log={tx.raw_log}
/>
</Alert>
</Col></Row>:''}
</Col></Row> : ''}
<Card>
<div className="card-header"><T>common.information</T></div>
<CardBody>
Expand All @@ -54,30 +54,29 @@ export default class Transaction extends Component{
<Col md={8} className="value text-nowrap overflow-auto address">{tx.txhash}</Col>
<Col md={4} className="label"><T>common.height</T></Col>
<Col md={8} className="value">
<Link to={"/blocks/"+tx.height}>{numbro(tx.height).format("0,0")}</Link>
{tx.block()?<span> <TimeStamp time={tx.block().time}/></span>:null}
<Link to={"/blocks/" + tx.height}>{numbro(tx.height).format("0,0")}</Link>
{tx.block() ? <span> <TimeStamp time={tx.block().time} /></span> : null}
</Col>
<Col md={4} className="label"><T>transactions.fee</T></Col>
<Col md={8} className="value">{(tx.tx.auth_info.fee.amount.length > 0)?tx.tx.auth_info.fee.amount.map((fee,i) => {
<Col md={8} className="value">{(tx.tx.auth_info.fee.amount.length > 0) ? tx.tx.auth_info.fee.amount.map((fee, i) => {
return <span className="text-nowrap" key={i}> {(new Coin(parseFloat(fee.amount), fee.denom)).toString(6)} </span>
}):<span><T>transactions.noFee</T></span>}</Col>
}) : <span><T>transactions.noFee</T></span>}</Col>
<Col md={4} className="label"><T>transactions.gasUsedWanted</T></Col>
<Col md={8} className="value">{numbro(tx.tx_response.gas_used).format("0,0")} / {numbro(tx.tx_response.gas_wanted).format("0,0")}</Col>
<Col md={4} className="label"><T>transactions.memo</T></Col>
<Col md={8} className="value"><Markdown markup={ tx.tx.body.memo } /></Col>

<Col md={8} className="value"><Markdown markup={tx.tx.body.memo} /></Col>
</Row>
</CardBody>
</Card>
<Card>
<div className="card-header"><T>transactions.activities</T></div>
</Card>
{(tx.tx.body.messages && tx.tx.body.messages.length >0)?tx.tx.body.messages.map((msg,i) => {
return <Card body key={i}><Activities msg={msg} invalid={(!!tx.tx_response.code)} events={(tx.tx_response.logs&&tx.tx_response.logs[i])?tx.tx_response.logs[i].events:null} denom={this.denom}/></Card>
}):''}
{(tx.tx.body.messages && tx.tx.body.messages.length > 0) ? tx.tx.body.messages.map((msg, i) => {
return <Card body key={i}><Activities collapse={true} msg={msg} invalid={(!!tx.tx_response.code)} events={(tx.tx_response.logs && tx.tx_response.logs[i]) ? tx.tx_response.logs[i].events : null} denom={this.denom} /></Card>
}) : ''}
</Container>
}
else{
else {
return <Container id="transaction"><div><T>transactions.noTxFound</T></div></Container>
}
}
Expand Down
80 changes: 45 additions & 35 deletions imports/ui/transactions/TransactionRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,51 @@ import numbro from 'numbro';
import Coin from '/both/utils/coins.js'
import SentryBoundary from '../components/SentryBoundary.jsx';
import { Markdown } from 'react-showdown';
import {
useRef,
useEffect,
createRef,
} from 'react';

let showdown = require('showdown');
let showdown = require('showdown');
showdown.setFlavor('github');

export const TransactionRow = (props) => {
let tx = props.tx;
let homepage = window?.location?.pathname === '/' ? true : false;

return <SentryBoundary><Row className={(tx.code)?"tx-info w-40 invalid":"tx-info w-40"}>
<Col xs={12} lg={homepage ? 5 : 7} className="activity" >{(tx?.tx?.body?.messages && tx?.tx?.body?.messages.length >0)?tx?.tx?.body?.messages.map((msg,i) => {
return <Card body key={i}><Activities msg={msg} invalid={(!!tx.tx_response.code)} events={(tx.tx_response.logs&&tx.tx_response.logs[i])?tx.tx_response.logs[i].events:null} /></Card>
}):''}</Col>
{!homepage ? <Col xs={(!props.blockList)?{size:6,order:"last"}:{size:12,order:"last"}} md={(!props.blockList)?{size:3, order: "last"}:{size:7, order: "last"}} lg={(!props.blockList)?{size:1,order:"last"}:{size:2,order:"last"}} className="text-truncate"><i className="fas fa-hashtag d-lg-none"></i> <Link to={"/transactions/"+tx.txhash}>{tx.txhash}</Link></Col> :
<Col xs={(!props.blockList)?{size:6,order:"last"}:{size:12,order:"last"}} md={(!props.blockList)?{size:3, order: "last"}:{size:7, order: "last"}} lg={{size:2,order:"last"}} className="text-truncate ml-n4"><i className="fas fa-hashtag d-lg-none"></i> <Link to={"/transactions/"+tx.txhash}>{tx.txhash}</Link></Col>}
<Col xs={6} md={9} lg={{size:2,order:"last"}} className="text-nowrap"><i className="material-icons">schedule</i> <span>{tx.block()?<TimeAgo time={tx.block().time} />:''}</span>{(tx?.tx?.body?.memo && tx?.tx?.body?.memo != "")?<span>
<i className="material-icons ml-2 memo-button" id={"memo-"+tx.txhash}>message</i>
<UncontrolledPopover trigger="legacy" placement="top-start" target={"memo-"+tx.txhash}>
<PopoverBody><Markdown markup={tx.tx.body.memo} /></PopoverBody>
</UncontrolledPopover>
</span>:""}</Col>
{(!props.blockList) ? <Col xs={4} md={2} lg={!homepage ? 1 : 2}><i className="fas fa-database d-lg-none"></i> <Link to={"/blocks/"+tx.height}>{numbro(tx.height).format("0,0")}</Link></Col>:''}
<Col xs={(!props.blockList)?2:4} md={1}>{(!tx.code)?<TxIcon valid />:<TxIcon />}</Col>
{!homepage ? <Col xs={(!props.blockList)?6:8} md={(!props.blockList)?9:4} lg={2} className="fee"><i className="material-icons d-lg-none">monetization_on</i> {(tx?.tx?.auth_info?.fee?.amount.length > 0)?tx?.tx?.auth_info?.fee?.amount.map((fee,i) => {
return <span className="text-nowrap" key={i}>{(new Coin(parseFloat(fee.amount), (fee)?fee.denom:null)).toString(6)}</span>
}) : <span>No fee</span>}</Col> : <Col xs={(!props.blockList) ? 6 : 8} md={(!props.blockList) ? 9 : 4} lg={2} className="fee d-sm-none"><i className="material-icons d-lg-none">monetization_on</i> {(tx?.tx?.auth_info?.fee?.amount.length > 0) ? tx?.tx?.auth_info?.fee?.amount.map((fee, i) => {
return <span className="text-nowrap" key={i}>{(new Coin(parseFloat(fee.amount), (fee) ? fee.denom : null)).toString(6)}</span>
}) : <span>No fee</span>}</Col> }
{(tx.code)?<Col xs={{size:12, order:"last"}} className="error">
<Alert color="danger">
<CosmosErrors
code={tx.code}
codespace={tx.codespace}
log={tx.raw_log}
/>
</Alert>
</Col>:''}
</Row></SentryBoundary>
}
export class TransactionRow extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
let tx = this.props.tx;
let homepage = window?.location?.pathname === '/' ? true : false;
return <SentryBoundary><div ref={this.myRef}><Row className={(tx.code) ? "tx-info-row w-40 invalid" : "tx-info-row w-40"}>
<Col xs={12} lg={homepage ? 5 : 7} className="activity" >{(tx?.tx?.body?.messages && tx?.tx?.body?.messages.length > 0) ? tx?.tx?.body?.messages.map((msg, i) => {
return <Card body key={i}><Activities msg={msg} id={this.props.index} invalid={(!!tx.tx_response.code)} events={(tx.tx_response.logs && tx.tx_response.logs[i]) ? tx.tx_response.logs[i].events : null} collapse={false} /></Card>
}) : ''}</Col>
{!homepage ? <Col xs={(!this.props.blockList) ? { size: 6, order: "last" } : { size: 12, order: "last" }} md={(!this.props.blockList) ? { size: 3, order: "last" } : { size: 7, order: "last" }} lg={(!this.props.blockList) ? { size: 1, order: "last" } : { size: 2, order: "last" }} className="text-truncate"><i className="fas fa-hashtag d-lg-none"></i> <Link to={"/transactions/" + tx.txhash}>{tx.txhash}</Link></Col> :
<Col xs={(!this.props.blockList) ? { size: 6, order: "last" } : { size: 12, order: "last" }} md={(!this.props.blockList) ? { size: 3, order: "last" } : { size: 7, order: "last" }} lg={{ size: 2, order: "last" }} className="text-truncate ml-n4"><i className="fas fa-hashtag d-lg-none"></i> <Link to={"/transactions/" + tx.txhash}>{tx.txhash}</Link></Col>}
<Col xs={6} md={9} lg={{ size: 2, order: "last" }} className="text-nowrap"><i className="material-icons">schedule</i> <span>{tx.block() ? <TimeAgo time={tx.block().time} /> : ''}</span>{(tx?.tx?.body?.memo && tx?.tx?.body?.memo != "") ? <span>
<i className="material-icons ml-2 memo-button" id={"memo-" + tx.txhash}>message</i>
<UncontrolledPopover trigger="legacy" placement="top-start" target={"memo-" + tx.txhash}>
<PopoverBody><Markdown markup={tx.tx.body.memo} /></PopoverBody>
</UncontrolledPopover>
</span> : ""}</Col>
{(!this.props.blockList) ? <Col xs={4} md={2} lg={!homepage ? 1 : 2}><i className="fas fa-database d-lg-none"></i> <Link to={"/blocks/" + tx.height}>{numbro(tx.height).format("0,0")}</Link></Col> : ''}
<Col xs={(!this.props.blockList) ? 2 : 4} md={1}>{(!tx.code) ? <TxIcon valid /> : <TxIcon />}</Col>
{!homepage ? <Col xs={(!this.props.blockList) ? 6 : 8} md={(!this.props.blockList) ? 9 : 4} lg={2} className="fee"><i className="material-icons d-lg-none">monetization_on</i> {(tx?.tx?.auth_info?.fee?.amount.length > 0) ? tx?.tx?.auth_info?.fee?.amount.map((fee, i) => {
return <span className="text-nowrap" key={i}>{(new Coin(parseFloat(fee.amount), (fee) ? fee.denom : null)).toString(6)}</span>
}) : <span>No fee</span>}</Col> : <Col xs={(!this.props.blockList) ? 6 : 8} md={(!this.props.blockList) ? 9 : 4} lg={2} className="fee d-sm-none"><i className="material-icons d-lg-none">monetization_on</i> {(tx?.tx?.auth_info?.fee?.amount.length > 0) ? tx?.tx?.auth_info?.fee?.amount.map((fee, i) => {
return <span className="text-nowrap" key={i}>{(new Coin(parseFloat(fee.amount), (fee) ? fee.denom : null)).toString(6)}</span>
}) : <span>No fee</span>}</Col>}
{(tx.code) ? <Col xs={{ size: 12, order: "last" }} className="error">
<Alert color="danger">
<CosmosErrors
code={tx.code}
codespace={tx.codespace}
log={tx.raw_log}
/>
</Alert>
</Col> : ''}
</Row></div></SentryBoundary>
}
}
Loading