Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pagination for cell list on transaction page #222

Merged
merged 8 commits into from
Jan 29, 2024
74 changes: 74 additions & 0 deletions src/components/Pagination/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.pageSizeSelector {
white-space: nowrap;
position: relative;
display: flex;
align-items: center;
align-content: stretch;
height: 100%;
margin: 0 2rem;
font-size: 0.875rem;
line-height: 1em;
color: #333;

.pageSize {
text-align: left;
width: 3.5rem;
margin-left: 0.5rem;
border-radius: 4px;
padding: 8px;
height: 2rem;
background: var(--input-bg-color);
border: none;

&:focus {
& + div[role='menu'] {
display: flex;
}
}
}

div[role='menu'] {
position: absolute;
width: 3.5rem;
top: 100%;
right: 0;
display: none;
flex-direction: column;
align-items: flex-start;
border-radius: 4px;
padding: 2px 0;
background: #fff;
overflow: hidden;
box-shadow: 0 2px 10px 0 #eee;
}

div[role='menuitem'] {
width: 100%;
padding: 0.375rem 0.5rem;
cursor: pointer;
text-align: left;

&:hover {
background: var(--input-bg-color);
}

&[data-is-selected='true'] {
color: var(--primary-color);
}
}

&:hover {
div[role='menu'] {
display: flex;
}
}

// FIXME: the layout will be optimized later with a new design
@media (750px <=width) and (width <= 900px) {
display: none;
}

@media (width <= 600px) {
display: none;
}
}
27 changes: 26 additions & 1 deletion src/components/Pagination/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import RightGrey from './pagination_grey_right.png'
import { useIsMobile } from '../../hooks'
import SimpleButton from '../SimpleButton'
import { HelpTip } from '../HelpTip'
import styles from './index.module.scss'

const Pagination = ({
currentPage,
Expand All @@ -16,13 +17,17 @@ const Pagination = ({
onChange,
className,
annotation,
pageSize,
presetPageSizes,
}: {
currentPage: number
totalPages: number
gotoPage?: number
onChange: (page: number) => void
onChange: (page: number, size?: number) => void
className?: string
annotation?: string
pageSize?: number
presetPageSizes?: number[]
}) => {
const isMobile = useIsMobile()
const { t } = useTranslation()
Expand Down Expand Up @@ -74,6 +79,26 @@ const Pagination = ({
<SimpleButton className="paginationLastButton" onClick={() => changePage(total)}>
{t('pagination.last')}
</SimpleButton>
{presetPageSizes ? (
<div className={styles.pageSizeSelector}>
<span>{`${t('pagination.show_rows')}:`}</span>
<div className={styles.pageSize}>{pageSize}</div>
<div role="menu">
{presetPageSizes.map(size => (
<div
key={size}
role="menuitem"
tabIndex={0}
data-is-selected={size === pageSize}
onClick={() => onChange(current, size)}
onKeyDown={() => onChange(current, size)}
>
{size}
</div>
))}
</div>
</div>
) : null}
</PaginationLeftItem>
<PaginationRightItem>
<span className="paginationPageLabel">{t('pagination.page')}</span>
Expand Down
1 change: 1 addition & 0 deletions src/components/Pagination/styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const PaginationLeftItem = styled.div`
font-size: 12px;
padding: 0 12px;
margin-left: 20px;
white-space: nowrap;

@media (max-width: ${variables.mobileBreakPoint}) {
background: white;
Expand Down
1 change: 1 addition & 0 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ONE_HOUR_MILLISECOND = ONE_HOUR_SECOND * 1000
export const ONE_MINUTE_SECOND = 60
export const EPOCHS_PER_HALVING = 8760
export const THEORETICAL_EPOCH_TIME = 1000 * 60 * 60 * 4 // 4 hours
export const PAGE_SIZE = 10

export function getPrimaryColor() {
return isMainnet() ? '#00CC9B' : '#9A2CEC'
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,8 @@
"end_page": "",
"current_page": "Page",
"of_page": "of",
"only_first_pages_visible": "Showing first {{pages}} pages"
"only_first_pages_visible": "Showing first {{pages}} pages",
"show_rows": "Show Rows"
},
"udt": {
"sudt": "sUDT",
Expand Down
3 changes: 2 additions & 1 deletion src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,8 @@
"end_page": "页",
"current_page": "第",
"of_page": "页,共",
"only_first_pages_visible": "显示最近 {{pages}} 页"
"only_first_pages_visible": "显示最近 {{pages}} 页",
"show_rows": "每页显示"
},
"udt": {
"sudt": "sUDT",
Expand Down
15 changes: 6 additions & 9 deletions src/pages/Transaction/TransactionCellList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ const SCROLL_BOTTOM_OFFSET = 5
const SCROLL_LOADING_TIME = 400

export default ({
total,
inputs,
outputs,
txHash,
showReward,
startIndex,
}: {
total?: number
inputs?: Cell[]
outputs?: Cell[]
txHash?: string
showReward?: boolean
startIndex: number
}) => {
const { t } = useTranslation()
const [offset, setOffset] = useState(PAGE_CELL_COUNT)
Expand Down Expand Up @@ -53,18 +57,11 @@ export default ({
const [isDeprecatedAddressesDisplayed, setIsDeprecatedAddressesDisplayed] = useIsDeprecatedAddressesDisplayed()
const toggleDeprecatedAddressesDisplayed = () => setIsDeprecatedAddressesDisplayed(value => !value)

const cellsCount = () => {
if (inputs) {
return inputs.length
}
return outputs ? outputs.length : 0
}

const cellTitle = () => {
const title = inputs ? t('transaction.input') : t('transaction.output')
return (
<div className={styles.cellListTitle}>
{`${title} (${cellsCount()})`}
{`${title} (${total ?? '-'})`}
<Tooltip placement="top" title={t(`address.view-deprecated-address`)}>
<div className={styles.newAddrToggle} onClick={toggleDeprecatedAddressesDisplayed} role="presentation">
{!isDeprecatedAddressesDisplayed ? <DeprecatedAddrOff /> : <DeprecatedAddrOn />}
Expand Down Expand Up @@ -99,7 +96,7 @@ export default ({
key={cell.id}
cell={cell}
cellType={inputs ? CellType.Input : CellType.Output}
index={index}
index={index + startIndex}
txHash={txHash}
showReward={showReward}
isAddrNew={!isDeprecatedAddressesDisplayed}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Transaction/TransactionCellList/styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const TransactionCellListTitlePanel = styled.div`

export const TransactionCellListPanel = styled.div`
width: 100%;
border-radius: 6px;
border-radius: 6px 6px 0 0;
box-shadow: 2px 2px 6px 0 #dfdfdf;
background-color: #fff;
padding: 12px 20px 12px 40px;
Expand Down
117 changes: 112 additions & 5 deletions src/pages/Transaction/TransactionComp/TransactionComp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { useQuery } from '@tanstack/react-query'
import { useHistory, useLocation } from 'react-router-dom'
import type { Response } from '../../../services/ExplorerService/types'
import TransactionCellList from '../TransactionCellList'
import { Cell } from '../../../models/Cell'
import { Transaction } from '../../../models/Transaction'
import { explorerService } from '../../../services/ExplorerService'
import Loading from '../../../components/Loading'
import Pagination from '../../../components/Pagination'
import { PAGE_SIZE } from '../../../constants/common'
import { useSearchParams } from '../../../hooks'

const handleCellbaseInputs = (inputs: Cell[], outputs: Cell[]) => {
if (inputs[0] && inputs[0].fromCellbase && outputs[0] && outputs[0].baseReward) {
Expand All @@ -17,19 +25,118 @@ const handleCellbaseInputs = (inputs: Cell[], outputs: Cell[]) => {
return inputs
}

export const TransactionComp = ({ transaction }: { transaction: Transaction }) => {
const { transactionHash, displayInputs, displayOutputs, blockNumber, isCellbase } = transaction
const emptyList: Response.Response<Cell[]> = {
data: [],
meta: {
total: 0,
pageSize: PAGE_SIZE,
},
}

const PRESET_PAGE_SIZES = [5, 10, 15]

export const TransactionComp = ({
transaction: { transactionHash: txHash, blockNumber, isCellbase },
}: {
transaction: Transaction
}) => {
const {
page_of_inputs = '1',
page_of_outputs = '1',
page_size = `${PAGE_SIZE}`,
} = useSearchParams('page_of_inputs', 'page_of_outputs', 'page_size')
const { pathname, search } = useLocation()

const history = useHistory()

const inputsPage = page_of_inputs && Number.isNaN(+page_of_inputs) ? 1 : +page_of_inputs
const outputsPage = page_of_outputs && Number.isNaN(+page_of_outputs) ? 1 : +page_of_outputs
const pageSize = Number.isNaN(+page_size) ? PAGE_SIZE : +page_size

const { data: displayInputs, isFetching: isInputsLoading } = useQuery(
['transaction_inputs', txHash, inputsPage, pageSize],
async () => {
try {
const res = await explorerService.api.fetchCellsByTxHash(txHash, 'inputs', { no: inputsPage, size: pageSize })
return res
} catch (e) {
return emptyList
}
},
{
initialData: emptyList,
},
)

const { data: displayOutputs, isFetching: isOutputsLoading } = useQuery(
['transaction_outputs', txHash, outputsPage, pageSize],
async () => {
try {
const res = await explorerService.api.fetchCellsByTxHash(txHash, 'outputs', { no: outputsPage, size: pageSize })
return res
} catch (e) {
return emptyList
}
},
{
initialData: emptyList,
},
)

const inputs = handleCellbaseInputs(displayInputs, displayOutputs)
const inputs = handleCellbaseInputs(displayInputs.data, displayOutputs.data)
const inputsPageCount = displayInputs.meta ? Math.ceil(displayInputs.meta.total / displayInputs.meta.pageSize) : 1
const outputsPageCount = displayOutputs.meta ? Math.ceil(displayOutputs.meta.total / displayOutputs.meta.pageSize) : 1

const handlePageChange = (type: 'inputs' | 'outputs') => (page: number, size?: number) => {
const pageField = `page_of_${type}`
const searchParams = new URLSearchParams(search)
if (size) {
searchParams.set('page_size', `${size}`)
searchParams.delete('page_of_inputs')
searchParams.delete('page_of_outputs')
} else {
searchParams.set(pageField, `${page}`)
}
const url = `${pathname}?${searchParams.toString()}`
history.push(url)
}

/// [0, 11] block doesn't show block reward and only cellbase show block reward
return (
<>
<div className="transactionInputs">
{inputs && <TransactionCellList inputs={inputs} showReward={blockNumber > 0 && isCellbase} />}
<Loading show={isInputsLoading} />
<TransactionCellList
total={displayInputs.meta?.total}
inputs={inputs}
startIndex={(inputsPage - 1) * pageSize}
showReward={blockNumber > 0 && isCellbase}
/>
<div style={{ height: 4 }} />
<Pagination
currentPage={inputsPage}
totalPages={inputsPageCount}
onChange={handlePageChange('inputs')}
pageSize={pageSize}
presetPageSizes={PRESET_PAGE_SIZES}
/>
</div>
<div className="transactionOutputs">
{displayOutputs && <TransactionCellList outputs={displayOutputs} txHash={transactionHash} />}
<Loading show={isOutputsLoading} />
<TransactionCellList
total={displayOutputs.meta?.total}
outputs={displayOutputs.data}
startIndex={(outputsPage - 1) * pageSize}
txHash={txHash}
/>
<div style={{ height: 4 }} />
<Pagination
currentPage={outputsPage}
totalPages={outputsPageCount}
onChange={handlePageChange('outputs')}
pageSize={pageSize}
presetPageSizes={PRESET_PAGE_SIZES}
/>
</div>
</>
)
Expand Down
13 changes: 12 additions & 1 deletion src/services/ExplorerService/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,18 @@ export const apiFetcher = {

fetchTransactionRaw: (hash: string) => requesterV2.get<unknown>(`transactions/${hash}/raw`).then(res => res.data),

fetchTransactionByHash: (hash: string) => v1GetUnwrapped<Transaction>(`transactions/${hash}`),
fetchTransactionByHash: (hash: string, displayCells: boolean = false) =>
v1GetUnwrapped<Transaction>(`transactions/${hash}?display_cells=${displayCells}`),

fetchCellsByTxHash: (hash: string, type: 'inputs' | 'outputs', page: Record<'no' | 'size', number>) =>
requesterV2
.get(`ckb_transactions/${hash}/display_${type}`, {
params: {
page: page.no,
page_size: page.size,
},
})
.then(res => toCamelcase<Response.Response<Cell[]>>(res.data)),

fetchTransactionLiteDetailsByHash: (hash: string) =>
requesterV2
Expand Down
1 change: 1 addition & 0 deletions src/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ body {
--navbar-height: 64px;
--table-separator-color: #f5f5f5;
--decimal-color: #999;
--input-bg-color: #f5f5f5;

margin: 0;
padding: 0;
Expand Down