Skip to content

Commit

Permalink
Explorer TX pagination (MystenLabs#2377)
Browse files Browse the repository at this point in the history
* pagination of tx by sequence number

* max cal

* update

* update mock total txcount

* prettier fix

* fixed edge case where last pag number does not show

* mobile optimazation

* fix

* lint fix
  • Loading branch information
Jibz1 authored Jun 6, 2022
1 parent cc50934 commit 6b831e2
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 75 deletions.
22 changes: 22 additions & 0 deletions explorer/client/src/components/pagination/Pagination.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.pagination {
@apply flex flex-row items-center justify-center list-none;
}

.pagination ul {
@apply flex flex-row items-center justify-center list-none pl-0;
}

.pagination ul li {
@apply mr-2;
}

.pagination ul li button {
@apply bg-gray-200 hover:bg-gray-300 focus:bg-gray-300 text-gray-800 hover:text-gray-800 focus:text-gray-800 font-medium py-2 px-4 rounded-none border-0 cursor-pointer
md:pr-3 md:pl-3 pr-1.5 pl-1.5;

transition: all 0.4s ease;
}

.activepag {
@apply bg-sui text-white !important;
}
125 changes: 125 additions & 0 deletions explorer/client/src/components/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { useState, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';

import styles from './Pagination.module.css';
function generatePaginationArr(
startAt: number,
itemsPerPage: number,
totalItems: number
) {
// number of list items to show before truncating
const range: number = 4;
const max = Math.ceil((totalItems - 1) / itemsPerPage);
const maxRange = (Math.floor(startAt / range) + 1) * range;
// set the min range to be the max range minus the range if it is less than the max - range
const minRange = startAt <= max - range ? maxRange - range : max - range;
return {
max,
maxRange,
// generate array of numbers to show in the pagination where the total number of pages is the total tx value / items per page
// show only the range eg if startAt is 5 and range is 5 then show 5, 6, 7, 8, 9, 10
listItems: Array.from({ length: max }, (_, i) => i + 1).filter(
(x: number) => x >= minRange && x <= maxRange
),
range,
};
}

function Pagination({
totalTxCount,
txNum,
}: {
totalTxCount: number;
txNum: number;
}) {
const [searchParams, setSearchParams] = useSearchParams();
const [pageIndex, setPage] = useState(
parseInt(searchParams.get('p') || '1', 10) || 1
);
const [pagiData, setPagiData] = useState(
generatePaginationArr(pageIndex, txNum, totalTxCount)
);

const changePage = useCallback(
(pageNum: number) => () => {
setPage(pageNum);
setSearchParams({ p: pageNum.toString() });
setPagiData(generatePaginationArr(pageNum, txNum, totalTxCount));
},
[setSearchParams, txNum, totalTxCount]
);

return (
<>
<nav className={styles.pagination}>
<ul>
{pageIndex > 1 && (
<li className="page-item">
<button
className={
pageIndex === 1 ? styles.activepag : ''
}
onClick={changePage(pageIndex - 1)}
>
&larr;
</button>
</li>
)}
{pageIndex > pagiData.range - 1 && (
<li className="page-item">
<button
className="page-link"
onClick={changePage(1)}
>
1
</button>
{' ... '}
</li>
)}
{pagiData.listItems.map((itm: any, index: number) => (
<li className="page-item" key={index}>
<button
className={
pageIndex === itm ? styles.activepag : ''
}
onClick={changePage(itm)}
>
{itm}
</button>
</li>
))}

{pageIndex < pagiData.max - 1 && (
<li className="page-item">
{' ... '}
<button
className={
pageIndex === pagiData.max
? styles.activepag
: ''
}
onClick={changePage(pagiData.max)}
>
{pagiData.max}
</button>
</li>
)}
{pageIndex < pagiData.max && (
<li className="page-item">
<button
className="page-link"
onClick={changePage(pageIndex + 1)}
>
</button>
</li>
)}
</ul>
</nav>
</>
);
}

export default Pagination;
81 changes: 69 additions & 12 deletions explorer/client/src/components/transaction-card/RecentTxCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@mysten/sui.js';
import cl from 'classnames';
import { useEffect, useState, useContext } from 'react';
import { Link } from 'react-router-dom';
import { Link, useSearchParams } from 'react-router-dom';

import Longtext from '../../components/longtext/Longtext';
import { NetworkContext } from '../../context';
Expand All @@ -23,6 +23,7 @@ import {
import { IS_STATIC_ENV } from '../../utils/envUtil';
import { getAllMockTransaction } from '../../utils/static/searchUtil';
import ErrorResult from '../error-result/ErrorResult';
import Pagination from '../pagination/Pagination';

import type {
CertifiedTransaction,
Expand All @@ -49,14 +50,49 @@ type TxnData = {
From: string;
};

function generateStartEndRange(
txCount: number,
txNum: number,
pageNum?: number
): { startGatewayTxSeqNumber: number; endGatewayTxSeqNumber: number } {
// Pagination pageNum from query params - default to 0; No negative values
const txPaged = pageNum && pageNum > 0 ? pageNum - 1 : 0;
const endGatewayTxSeqNumber: number = txCount - txNum * txPaged;
const tempStartGatewayTxSeqNumber: number = endGatewayTxSeqNumber - txNum;
// If startGatewayTxSeqNumber is less than 0, then set it 1 the first transaction sequence number
const startGatewayTxSeqNumber: number =
tempStartGatewayTxSeqNumber > 0 ? tempStartGatewayTxSeqNumber : 1;
return {
startGatewayTxSeqNumber,
endGatewayTxSeqNumber,
};
}

async function getRecentTransactions(
network: Network | string,
txNum: number
totalTx: number,
txNum: number,
pageNum?: number
): Promise<TxnData[]> {
try {
// Get the latest transactions

// Instead of getRecentTransactions, use getTransactionCount
// then use getTransactionDigestsInRange using the totalTx as the start totalTx sequence number - txNum as the end sequence number
// Get the total number of transactions, then use as the start and end values for the getTransactionDigestsInRange
const { endGatewayTxSeqNumber, startGatewayTxSeqNumber } =
generateStartEndRange(totalTx, txNum, pageNum);

// TODO: Add error page
// If paged tx value is less than 0, out of range
if (endGatewayTxSeqNumber < 0) {
throw new Error('Invalid transaction number');
}
const transactions = await rpc(network)
.getRecentTransactions(txNum)
.getTransactionDigestsInRange(
startGatewayTxSeqNumber,
endGatewayTxSeqNumber
)
.then((res: GetTxnDigestsResponse) => res);

const digests = transactions.map((tx) => tx[1]);
Expand Down Expand Up @@ -208,27 +244,38 @@ function LatestTxView({
);
}

function LatestTxCardStatic() {
function LatestTxCardStatic({ count }: { count: number }) {
const latestTx = getAllMockTransaction().map((tx) => ({
...tx,
status: tx.status as ExecutionStatusType,
kind: tx.kind as TransactionKindName,
}));
const [searchParams] = useSearchParams();
const pagedNum: number = parseInt(searchParams.get('p') || '1', 10);

const results = {
loadState: 'loaded',
latestTx: latestTx,
};
return <LatestTxView results={results} />;
return (
<>
<LatestTxView results={results} />
<Pagination totalTxCount={count} txNum={pagedNum} />
</>
);
}

function LatestTxCardAPI() {
function LatestTxCardAPI({ count }: { count: number }) {
const [isLoaded, setIsLoaded] = useState(false);
const [results, setResults] = useState(initState);
const [network] = useContext(NetworkContext);
const [searchParams] = useSearchParams();
const [txNumPerPage] = useState(15);
useEffect(() => {
let isMounted = true;
getRecentTransactions(network, 15)
.then((resp: any) => {
const pagedNum: number = parseInt(searchParams.get('p') || '1', 10);
getRecentTransactions(network, count, txNumPerPage, pagedNum)
.then(async (resp: any) => {
if (isMounted) {
setIsLoaded(true);
}
Expand All @@ -248,7 +295,8 @@ function LatestTxCardAPI() {
return () => {
isMounted = false;
};
}, [network]);
}, [count, network, searchParams, txNumPerPage]);

if (results.loadState === 'pending') {
return (
<div className={theme.textresults}>
Expand All @@ -270,10 +318,19 @@ function LatestTxCardAPI() {
return <ErrorResult id="" errorMsg="No Transactions Found" />;
}

return <LatestTxView results={results} />;
return (
<>
<LatestTxView results={results} />
<Pagination totalTxCount={count} txNum={txNumPerPage} />
</>
);
}

const LatestTxCard = () =>
IS_STATIC_ENV ? <LatestTxCardStatic /> : <LatestTxCardAPI />;
const LatestTxCard = ({ count }: { count: number }) =>
IS_STATIC_ENV ? (
<LatestTxCardStatic count={count} />
) : (
<LatestTxCardAPI count={count} />
);

export default LatestTxCard;
62 changes: 2 additions & 60 deletions explorer/client/src/components/transaction-count/TxCountCard.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useEffect, useState, useContext } from 'react';

import { NetworkContext } from '../../context';
import {
DefaultRpcClient as rpc,
type Network,
} from '../../utils/api/DefaultRpcClient';
import { IS_STATIC_ENV } from '../../utils/envUtil';
import ErrorResult from '../error-result/ErrorResult';

import styles from './TxCountCard.module.css';

const initState = { count: 0, loadState: 'pending' };

async function getTransactionCount(network: Network | string): Promise<number> {
return rpc(network).getTotalTransactionNumber();
}

function TxCountCard({ count }: { count: number | string }) {
return (
<div className={styles.txcount} id="txcount">
Expand All @@ -32,51 +18,7 @@ function TxCountCardStatic() {
return <TxCountCard count={3030} />;
}

function TxCountCardAPI() {
const [isLoaded, setIsLoaded] = useState(false);
const [results, setResults] = useState(initState);
const [network] = useContext(NetworkContext);
useEffect(() => {
let isMounted = true;
getTransactionCount(network)
.then((resp: number) => {
if (isMounted) {
setIsLoaded(true);
}
setResults({
loadState: 'loaded',
count: resp,
});
})
.catch((err) => {
setResults({
...initState,
loadState: 'fail',
});
setIsLoaded(false);
});

return () => {
isMounted = false;
};
}, [network]);
if (results.loadState === 'pending') {
return <TxCountCard count="" />;
}

if (!isLoaded && results.loadState === 'fail') {
return (
<ErrorResult
id=""
errorMsg="Error getting total transaction count"
/>
);
}

return <TxCountCard count={results.count} />;
}

const LatestTxCard = () =>
IS_STATIC_ENV ? <TxCountCardStatic /> : <TxCountCardAPI />;
const LatestTxCard = ({ count }: { count: number }) =>
IS_STATIC_ENV ? <TxCountCardStatic /> : <TxCountCard count={count} />;

export default LatestTxCard;
Loading

0 comments on commit 6b831e2

Please sign in to comment.