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: Display live cells of a specific address #1597

Merged
merged 16 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
8 changes: 7 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,13 @@
"leap_out_tip": "This RGB++ transaction is marked as \"L2 → L1\", indicating that the number of cells with RGB++ lock in the input exceeds the number of cells with RGB++ lock in the output.",
"view_in_btc_explorer": "View in Bitcoin Explorer",
"view-as-asset-items": "View Asset Distribution",
"view-as-merged-assets": "View Asset Amounts(CKB excluded)"
"view-as-merged-assets": "View Asset Amounts(CKB excluded)",
"block-height": "Block Height",
"out-point": "Out Point",
"capacity": "Capacity(CKB)",
"type": "Type",
"amount": "Amount",
"detail": "Detail"
},
"rgbpp": {
"transaction": {
Expand Down
8 changes: 7 additions & 1 deletion src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,13 @@
"leap_out_tip": "本笔 RGB++ 交易被标记为\"L2 → L1\",即存在 RGB++ 资产转化为一般 CKB 资产。",
"view_in_btc_explorer": "在 Bitcoin 浏览器查看",
"view-as-asset-items": "查看资产分布",
"view-as-merged-assets": "查看资产合计(不含 CKB)"
"view-as-merged-assets": "查看资产合计(不含 CKB)",
"block-height": "区块高度",
"out-point": "Out Point",
"capacity": "Capacity",
"type": "类型",
"amount": "数量",
"detail": "详情"
},
"rgbpp": {
"transaction": {
Expand Down
168 changes: 107 additions & 61 deletions src/pages/Address/Cells.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { type FC, useState, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { TFunction, useTranslation } from 'react-i18next'
import BigNumber from 'bignumber.js'
import { useInfiniteQuery } from '@tanstack/react-query'
import { Tooltip } from 'antd'
import { explorerService, LiveCell } from '../../services/ExplorerService'
import SUDTTokenIcon from '../../assets/sudt_token.png'
import CKBTokenIcon from './ckb_token_icon.png'
import { ReactComponent as CopyIcon } from './copy.svg'
import { ReactComponent as TypeHashIcon } from './type_script.svg'
import { ReactComponent as DataIcon } from './data.svg'
import { ReactComponent as SporeCluterIcon } from './spore_cluster.svg'
Expand All @@ -18,10 +17,11 @@ import { ReactComponent as ListIcon } from './list.svg'
import { ReactComponent as GridIcon } from './grid.svg'
import { parseUDTAmount } from '../../utils/number'
import { shannonToCkb } from '../../utils/util'
import { useSetToast } from '../../components/Toast'
import { PAGE_SIZE } from '../../constants/common'
import { parseSimpleDateNoSecond } from '../../utils/date'
import styles from './cells.module.scss'
import SmallLoading from '../../components/Loading/SmallLoading'
import { TransactionCellInfo } from '../Transaction/TransactionCell'
import { CellBasicInfo } from '../../utils/transformer'
import { sliceNftName } from '../../utils/string'

enum Sort {
Expand Down Expand Up @@ -53,31 +53,13 @@ const initialPageParams = { size: 10, sort: 'capacity.desc' }

const ATTRIBUTE_LENGTH = 18

const Cell: FC<{ cell: LiveCell }> = ({ cell }) => {
const setToast = useSetToast()
const { t } = useTranslation()

const handleCopy = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.preventDefault()
const { detail } = e.currentTarget.dataset
if (!detail) return
navigator.clipboard.writeText(detail).then(() => {
setToast({ message: t('common.copied') })
})
}

const getCellDetails = (cell: LiveCell, t: TFunction) => {
const ckb = new BigNumber(shannonToCkb(+cell.capacity)).toFormat()
const title = `${cell.txHash.slice(0, 8)}...${cell.txHash.slice(-8)}#${cell.cellIndex}`
const link = `/transaction/${cell.txHash}?${new URLSearchParams({
page_of_outputs: Math.ceil((+cell.cellIndex + 1) / PAGE_SIZE).toString(),
})}`
const assetType: string = cell.extraInfo?.type ?? cell.cellType
let icon: string | React.ReactElement | null = null
let assetName = null
let attribute = null
let detailInfo = null

switch (assetType) {
case 'ckb': {
if (cell.typeHash) {
Expand Down Expand Up @@ -183,40 +165,91 @@ const Cell: FC<{ cell: LiveCell }> = ({ cell }) => {
attribute = '-'
}
}
const outPoint = {
tx_hash: cell.txHash,
index: `0x${cell.cellIndex.toString(16)}`,

const outPointStr = `${cell.txHash.slice(0, 8)}...${cell.txHash.slice(-8)}#${cell.cellIndex}`
const parsedBlockCreateAt = parseSimpleDateNoSecond(cell.blockTimestamp)
const title = `${cell.id}: ${ckb} CKB (${parsedBlockCreateAt})`
const cellInfo = {
...cell,
id: Number(cell.id),
isGenesisOutput: false,
} as CellBasicInfo

return {
ckb,
outPointStr,
icon,
assetName,
attribute,
detailInfo,
title,
cellInfo,
}
}

const Cell: FC<{ cell: LiveCell }> = ({ cell }) => {
const { t } = useTranslation()

const { title, icon, assetName, attribute, detailInfo, cellInfo } = getCellDetails(cell, t)

return (
<li key={cell.txHash + cell.cellIndex} className={styles.card}>
<h5>
<a href={link}>{title}</a>
<TransactionCellInfo cell={cellInfo} isDefaultStyle={false}>
<h5>{title}</h5>

<button type="button" className={styles.copy} data-detail={JSON.stringify(outPoint)} onClick={handleCopy}>
<CopyIcon />
</button>
<span title={`${ckb} CKB`}>{`${ckb} CKB`}</span>
</h5>
<div className={styles.content}>
{typeof icon === 'string' ? <img src={icon} alt={assetName ?? 'sudt'} width="40" height="40" /> : null}
{icon && typeof icon !== 'string' ? icon : null}
<div className={styles.fields}>
<div className={styles.assetName}>{assetName}</div>
<div className={styles.attribute} title={detailInfo ?? attribute}>
<div className={styles.attributeContent}>{attribute}</div>
{detailInfo ? (
<button type="button" className={styles.copy} data-detail={detailInfo} onClick={handleCopy}>
<CopyIcon />
</button>
) : null}
<div className={styles.content}>
{typeof icon === 'string' ? <img src={icon} alt={assetName ?? 'sudt'} width="40" height="40" /> : null}
{icon && typeof icon !== 'string' ? icon : null}
<div className={styles.fields}>
<div className={styles.assetName}>{assetName}</div>
<div className={styles.attribute} title={detailInfo ?? attribute}>
{attribute}
</div>
</div>
</div>
</div>
</TransactionCellInfo>
</li>
)
}

const CellTable: FC<{ cells: LiveCell[] }> = ({ cells }) => {
const { t } = useTranslation()
const headers = getTableHeaders(t)

return (
<div className={styles.tableContainer}>
<table>
<thead>
{headers.map(header => (
<th key={header.key}>{header.title}</th>
))}
</thead>
<tbody>
{cells.map((cell, index) => {
const { ckb, outPointStr, assetName, attribute, cellInfo } = getCellDetails(cell, t)

return (
<tr key={cell.txHash + cell.cellIndex}>
<td>{index + 1}</td>
<td>{cell.blockNumber}</td>
<td>{outPointStr}</td>
<td>{ckb}</td>
<td>{cell.extraInfo.type}</td>
<td>
{attribute} {assetName}
</td>
<td>
<TransactionCellInfo cell={cellInfo}>{t('address.detail')}</TransactionCellInfo>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}

const Cells: FC<{ address: string; count: number }> = ({ address, count }) => {
const { t } = useTranslation()
const [params, setParams] = useState(initialPageParams)
Expand Down Expand Up @@ -279,9 +312,6 @@ const Cells: FC<{ address: string; count: number }> = ({ address, count }) => {
data-sort={params.sort === Sort.TimeAsc ? Sort.TimeDesc : Sort.TimeAsc}
onClick={handleSortChange}
data-is-active={params.sort === Sort.TimeAsc || params.sort === Sort.TimeDesc}
style={{
display: 'none', // FIXME: wait for api fix
}}
>
{params.sort === Sort.TimeAsc ? <TimeUpIcon /> : <TimeDownIcon />}
</button>
Expand All @@ -296,22 +326,21 @@ const Cells: FC<{ address: string; count: number }> = ({ address, count }) => {
<SortIcon data-current-sort={params.sort} className={styles.capacitySortIcon} />
</button>
</Tooltip>
<button
type="button"
onClick={() => setIsDisplayedAsList(i => !i)}
style={{
display: 'none', // TODO PRD should be updated
}}
>
<button type="button" onClick={() => setIsDisplayedAsList(i => !i)}>
{isDisplayedAsList ? <GridIcon /> : <ListIcon />}
</button>
</div>
</div>
<ul>
{cells.map(cell => (
<Cell cell={cell} key={`${cell.txHash}-${cell.cellIndex}`} />
))}
</ul>
{isDisplayedAsList ? (
<CellTable cells={cells} />
) : (
<ul>
{cells.map(cell => (
<Cell cell={cell} key={`${cell.txHash}-${cell.cellIndex}`} />
))}
</ul>
)}

{isFetchingNextPage ? (
<span className={styles.loading}>
<SmallLoading />
Expand All @@ -328,3 +357,20 @@ const Cells: FC<{ address: string; count: number }> = ({ address, count }) => {
)
}
export default Cells

const getTableHeaders = (t: TFunction): TableHeader[] => {
return [
{ title: '#', key: 'index' },
{ title: t('address.block-height'), key: 'block-number' },
{ title: t('address.out-point'), key: 'out-point' },
{ title: t('address.capacity'), key: 'capacity' },
{ title: t('address.type'), key: 'type' },
{ title: t('address.amount'), key: 'amount' },
{ title: '', key: 'action' },
]
}

interface TableHeader {
title: string
key: string
}
51 changes: 25 additions & 26 deletions src/pages/Address/cells.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@

h5 {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--primary-color);
color: #fff;
Expand All @@ -76,36 +75,11 @@
overflow: hidden;
text-overflow: ellipsis;

a {
&:hover {
font-weight: bold;
color: #fff;
text-decoration: underline;
}
}

.copy {
appearance: none;
border: none;
background: none;
width: 14px;
cursor: pointer;
margin-right: 8px;
height: 14px;

svg {
pointer-events: none;
height: 14px;
}
}

span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
cursor: default;
flex: 1;
text-align: right;
}
}

Expand Down Expand Up @@ -160,6 +134,31 @@
}
}

.tableContainer {
table {
width: 100%;
background: #fff;
border: 1px solid #e5e5e5;

th,
td {
border: 1px solid #e5e5e5;
text-align: center;
padding: 0 24px;
font-size: 14px;
color: #333;
}

th {
height: 40px;
}

td {
height: 48px;
}
}
}

.loading {
display: flex;
justify-content: center;
Expand Down
1 change: 1 addition & 0 deletions src/pages/Address/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@
margin-top: 10px;
background-color: #f1f1f1;
padding: 6px 25px;
border-radius: 4px;
width: 100%;
display: flex;
flex-flow: row wrap;
Expand Down
Loading