diff --git a/src/components/CsvExport/index.tsx b/src/components/CsvExport/index.tsx index 01625ee9f..676e70719 100644 --- a/src/components/CsvExport/index.tsx +++ b/src/components/CsvExport/index.tsx @@ -4,12 +4,20 @@ import styles from './styles.module.scss' import { ReactComponent as ExportIcon } from './export_icon.svg' import { SupportedExportTransactionType } from '../../services/ExplorerService' -export function CsvExport({ type, id }: { type: SupportedExportTransactionType; id?: string }) { +export function CsvExport({ + type, + id, + isViewOriginal, +}: { + type: SupportedExportTransactionType + id?: string + isViewOriginal?: boolean +}) { const [t] = useTranslation() return (
{t(`export_transactions.csv_export`)}
diff --git a/src/hooks/route.ts b/src/hooks/route.ts index 72de13af1..958ca9a4d 100644 --- a/src/hooks/route.ts +++ b/src/hooks/route.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo, useCallback } from 'react' import { useHistory, useLocation } from 'react-router-dom' import { ListPageParams, PageParams } from '../constants/common' -import { omit } from '../utils/object' +import { omit, omitNil } from '../utils/object' function getSearchParams(search: string, names?: T[]): Partial> { const urlSearchParams = new URLSearchParams(search) @@ -76,7 +76,7 @@ export function useSortParam( } export function useUpdateSearchParams(): ( - updater: (current: Partial>) => Partial>, + updater: (current: Partial>) => Partial>, replace?: boolean, ) => void { const history = useHistory() @@ -85,7 +85,7 @@ export function useUpdateSearchParams(): ( return useCallback( (updater, replace) => { const oldParams: Partial> = getSearchParams(search) - const newParams = updater(oldParams) + const newParams = omitNil(updater(oldParams)) const newUrlSearchParams = new URLSearchParams(newParams as Record) newUrlSearchParams.sort() const newQueryString = newUrlSearchParams.toString() diff --git a/src/locales/en.json b/src/locales/en.json index 6e4eb60c6..c070bc3b2 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -593,7 +593,8 @@ "mint_limit": "Mint Limit", "mint_status_minting": "Minting", "mint_status_closed": "Closed", - "mint_status_rebase_start": "Rebase Start" + "mint_status_rebase_start": "Rebase Start", + "view_original": "View Original" }, "nft": { "nft_collection": "NFT Collection", diff --git a/src/locales/zh.json b/src/locales/zh.json index b9f6e5f08..f2d4a2f6a 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -594,7 +594,8 @@ "mint_limit": "铸造限制", "mint_status_minting": "铸造中", "mint_status_closed": "已关闭", - "mint_status_rebase_start": "重铸开始" + "mint_status_rebase_start": "重铸开始", + "view_original": "查看原铭文" }, "nft": { "nft_collection": "NFT 藏品", diff --git a/src/models/UDT/index.ts b/src/models/UDT/index.ts index 489fba58c..ba47afadd 100644 --- a/src/models/UDT/index.ts +++ b/src/models/UDT/index.ts @@ -31,6 +31,7 @@ export interface OmigaInscriptionCollection extends UDT { mintLimit: string expectedSupply: string inscriptionInfoId: string + infoTypeHash: string } export function isOmigaInscriptionCollection(udt: UDT): udt is OmigaInscriptionCollection { diff --git a/src/pages/ExportTransactions/index.tsx b/src/pages/ExportTransactions/index.tsx index cc07a4f5b..3d12d77a2 100644 --- a/src/pages/ExportTransactions/index.tsx +++ b/src/pages/ExportTransactions/index.tsx @@ -30,9 +30,12 @@ const ExportTransactions = () => { 'end-date': endDateStr, 'from-height': fromHeightStr, 'to-height': toHeightStr, - } = useSearchParams('type', 'id', 'tab', 'start-date', 'end-date', 'from-height', 'to-height') + view, + } = useSearchParams('type', 'id', 'tab', 'start-date', 'end-date', 'from-height', 'to-height', 'view') + const isViewOriginal = view === 'original' + function isTransactionCsvExportType(s?: string): s is SupportedExportTransactionType { - return !!s && ['address_transactions', 'blocks', 'udts', 'nft'].includes(s) + return !!s && ['address_transactions', 'blocks', 'udts', 'nft', 'omiga_inscriptions'].includes(s) } const type = isTransactionCsvExportType(typeStr) ? typeStr : 'blocks' @@ -122,6 +125,7 @@ const ExportTransactions = () => { id, date: tab === 'date' ? { start: startDate, end: endDate } : undefined, block: tab === 'height' ? { from: fromHeight!, to: toHeight! } : undefined, + isViewOriginal, }) .then((resp: string | null) => { setIsDownloading(false) diff --git a/src/pages/Tokens/index.tsx b/src/pages/Tokens/index.tsx index a9f54aa51..e64d1a9ae 100644 --- a/src/pages/Tokens/index.tsx +++ b/src/pages/Tokens/index.tsx @@ -95,7 +95,9 @@ const TokenItem = ({ token, isLast }: { token: UDT | OmigaInscriptionCollection; {symbol} diff --git a/src/pages/UDT/UDTComp.tsx b/src/pages/UDT/UDTComp.tsx index 78ee536c0..f09a08577 100644 --- a/src/pages/UDT/UDTComp.tsx +++ b/src/pages/UDT/UDTComp.tsx @@ -13,7 +13,7 @@ import AddressText from '../../components/AddressText' import PaginationWithRear from '../../components/PaginationWithRear' import { CsvExport } from '../../components/CsvExport' import { Transaction } from '../../models/Transaction' -import { OmigaInscriptionCollection, UDT, isOmigaInscriptionCollection } from '../../models/UDT' +import { OmigaInscriptionCollection, UDT, isOmigaInscriptionCollection, MintStatus } from '../../models/UDT' import { Card, CardCellInfo, CardCellsLayout, HashCardHeader } from '../../components/Card' import { useIsMobile } from '../../hooks' import SUDTTokenIcon from '../../assets/sudt_token.png' @@ -25,6 +25,7 @@ import ArrowUpBlueIcon from '../../assets/arrow_up_blue.png' import ArrowDownBlueIcon from '../../assets/arrow_down_blue.png' import Script from '../../components/Script' import Capacity from '../../components/Capacity' +import { ReactComponent as ViewOriginalIcon } from './view_original.svg' const typeScriptIcon = (show: boolean) => { if (show) { @@ -166,6 +167,20 @@ export const UDTOverviewCard = ({ typeHash, udt }: { typeHash: string; udt: UDT className={styles.cardHeader} title={!isMobile && cardTitle} hash={typeHash} + customActions={[ + isOmigaInscriptionCollection(udt) && udt.mintStatus === MintStatus.RebaseStart ? ( + + + + + + ) : null, + ]} rightContent={!isMobile && modifyTokenInfo} /> @@ -189,6 +204,7 @@ export const UDTComp = ({ filterNoResult, id, isInscription, + isViewOriginal, }: { currentPage: number pageSize: number @@ -198,6 +214,7 @@ export const UDTComp = ({ filterNoResult?: boolean id: string isInscription?: boolean + isViewOriginal?: boolean }) => { const { t } = useTranslation() const totalPages = Math.ceil(total / pageSize) @@ -230,8 +247,9 @@ export const UDTComp = ({ currentPage={currentPage} totalPages={totalPages} onChange={onPageChange} - // TODO: The backend has not yet implemented export support for Inscription (xUDT), so it is disabled for now. - rear={!isInscription && } + rear={ + + } /> diff --git a/src/pages/UDT/index.tsx b/src/pages/UDT/index.tsx index 7cf1efc5b..ee90c3902 100644 --- a/src/pages/UDT/index.tsx +++ b/src/pages/UDT/index.tsx @@ -1,59 +1,53 @@ -import { Link, useHistory, useLocation, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { useQuery } from '@tanstack/react-query' -import { Popover } from 'antd' import { FC } from 'react' import Content from '../../components/Content' import { UDTContentPanel, UDTTransactionTitlePanel } from './styled' import UDTComp, { UDTOverviewCard } from './UDTComp' -import { useIsMobile, usePaginationParamsInPage } from '../../hooks' +import { usePaginationParamsInPage, useSearchParams, useUpdateSearchParams } from '../../hooks' import Filter from '../../components/Search/Filter' import { localeNumberString } from '../../utils/number' import { explorerService } from '../../services/ExplorerService' import { deprecatedAddrToNewAddr } from '../../utils/util' import { QueryResult } from '../../components/QueryResult' import { defaultUDTInfo } from './state' -import { ReactComponent as FilterIcon } from '../../assets/filter_icon.svg' -import { ReactComponent as SelectedCheckIcon } from '../../assets/selected_check_icon.svg' import styles from './styles.module.scss' import { Cell } from '../../models/Cell' - -enum TransactionType { - Mint = 'mint', - Transfer = 'normal', - Burn = 'destruction', -} +import { assert } from '../../utils/error' export const UDT: FC<{ isInscription?: boolean }> = ({ isInscription }) => { const { t } = useTranslation() - const isMobile = useIsMobile() - const { push } = useHistory() - const { search } = useLocation() + // The typeHash here could be either udtTypeHash or omigaInscriptionInfoTypeHash. const { hash: typeHash } = useParams<{ hash: string }>() const { currentPage, pageSize: _pageSize, setPage } = usePaginationParamsInPage() - const query = new URLSearchParams(search) - const filter = query.get('filter') - const type = query.get('type') + const { filter, view } = useSearchParams('filter', 'view') + const isViewOriginal = view === 'original' - const queryUDT = useQuery(['udt', isInscription], () => - isInscription ? explorerService.api.fetchOmigaInscription(typeHash) : explorerService.api.fetchSimpleUDT(typeHash), + const updateSearchParams = useUpdateSearchParams<'filter' | 'page'>() + + const queryUDT = useQuery(['udt', isInscription, isViewOriginal], () => + isInscription + ? explorerService.api.fetchOmigaInscription(typeHash, isViewOriginal) + : explorerService.api.fetchSimpleUDT(typeHash), ) const udt = queryUDT.data ?? defaultUDTInfo + const udtTypeHash = isInscription ? queryUDT.data?.typeHash : typeHash const querySimpleUDTTransactions = useQuery( - ['simple-udt-transactions', typeHash, currentPage, _pageSize, filter, type], + ['simple-udt-transactions', udtTypeHash, currentPage, _pageSize, filter], async () => { + assert(udtTypeHash) const { data: transactions, total, pageSize: resPageSize, } = await explorerService.api.fetchUDTTransactions({ - typeHash, + typeHash: udtTypeHash, page: currentPage, size: pageSize, filter, - type, }) const ensureCellAddrIsNewFormat = (cell: Cell) => ({ @@ -71,29 +65,14 @@ export const UDT: FC<{ isInscription?: boolean }> = ({ isInscription }) => { pageSize: resPageSize, } }, + { + enabled: udtTypeHash != null, + }, ) const total = querySimpleUDTTransactions.data?.total ?? 0 const filterNoResult = !!filter && querySimpleUDTTransactions.isError const pageSize: number = querySimpleUDTTransactions.data?.pageSize ?? _pageSize - const filterList: { value: TransactionType; title: string }[] = [ - { - value: TransactionType.Mint, - title: t('udt.view-mint-txns'), - }, - { - value: TransactionType.Transfer, - title: t('udt.view-transfer-txns'), - }, - { - value: TransactionType.Burn, - title: t('udt.view-burn-txns'), - }, - ] - - const isFilteredByType = filterList.some(f => f.value === type) - const udtLinkPrefix = !isInscription ? '/sudt' : '/inscription' - return ( @@ -109,36 +88,9 @@ export const UDT: FC<{ isInscription?: boolean }> = ({ isInscription }) => { defaultValue={filter ?? ''} showReset={!!filter} placeholder={t('udt.search_placeholder')} - onFilter={filter => { - push(`${udtLinkPrefix}/${typeHash}?${new URLSearchParams({ filter })}`) - }} - onReset={() => { - push(`${udtLinkPrefix}/${typeHash}`) - }} + onFilter={filter => updateSearchParams(params => ({ ...params, filter }))} + onReset={() => updateSearchParams(params => ({ ...params, filter: null }))} /> -
- - {filterList.map(f => ( - - {f.title} - - - ))} -
- } - > - - - @@ -154,6 +106,7 @@ export const UDT: FC<{ isInscription?: boolean }> = ({ isInscription }) => { filterNoResult={filterNoResult} id={typeHash} isInscription={isInscription} + isViewOriginal={isViewOriginal} /> )} diff --git a/src/pages/UDT/state.ts b/src/pages/UDT/state.ts index 31bbb2fe5..a6eb417bd 100644 --- a/src/pages/UDT/state.ts +++ b/src/pages/UDT/state.ts @@ -27,4 +27,5 @@ export const defaultOmigaInscriptionInfo: OmigaInscriptionCollection = { mintLimit: '0', expectedSupply: '0', inscriptionInfoId: '', + infoTypeHash: '', } diff --git a/src/pages/UDT/styles.module.scss b/src/pages/UDT/styles.module.scss index 3c9a02ff8..e1d0da3c6 100644 --- a/src/pages/UDT/styles.module.scss +++ b/src/pages/UDT/styles.module.scss @@ -68,6 +68,17 @@ } } +.viewOriginal { + display: flex; + align-items: center; + width: 100%; + height: 100%; + + &:hover { + color: var(--primary-color); + } +} + .addressWidthModify { max-width: 80%; @@ -92,63 +103,3 @@ width: 100%; } } - -.typeFilter { - // hide the filter icon until the api is ready - display: none; - cursor: pointer; - padding-left: 0.8rem; - - svg { - color: #999; - } - - &[data-is-active='true'] { - svg { - color: var(--primary-color); - } - } -} - -.filterItems { - display: flex; - flex-direction: column; - width: 200px; - - a { - display: flex; - justify-content: space-between; - color: var(--primary-color); - padding: 10px 0 10px 10px; - margin-right: 8px; - border-radius: 8px; - - &:hover { - background: var(--primary-hover-bg-color); - color: var(--primary-color); - cursor: pointer; - } - - &[data-is-active='false'] { - color: #000; - - svg { - display: none; - } - } - } - - svg path { - fill: var(--primary-color); - } -} - -.antPopover { - :global { - /* stylelint-disable-next-line selector-class-pattern */ - .ant-popover-inner { - border-radius: 8px; - box-shadow: 0 2px 10px 0 #eee; - } - } -} diff --git a/src/pages/UDT/view_original.svg b/src/pages/UDT/view_original.svg new file mode 100644 index 000000000..492e54de1 --- /dev/null +++ b/src/pages/UDT/view_original.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 4479c6372..ae27a7bfc 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -561,13 +561,11 @@ export const apiFetcher = { page, size, filter, - type, }: { typeHash: string page: number size: number filter?: string | null - type?: string | null }) => v1GetUnwrappedPagedList(`/udt_transactions/${typeHash}`, { params: { @@ -575,7 +573,6 @@ export const apiFetcher = { page_size: size, address_hash: filter?.startsWith('0x') ? undefined : filter, tx_hash: filter?.startsWith('0x') ? filter : undefined, - transfer_action: type, }, }), @@ -588,8 +585,10 @@ export const apiFetcher = { }, }), - fetchOmigaInscription: (typeHash: string) => - v1GetUnwrapped(`/omiga_inscriptions/${typeHash}`), + fetchOmigaInscription: (typeHash: string, isViewOriginal: boolean) => + v1GetUnwrapped( + `/omiga_inscriptions/${typeHash}${isViewOriginal ? '?status=closed' : ''}`, + ), fetchOmigaInscriptions: (page: number, size: number, sort?: string) => v1GetUnwrappedPagedList(`/omiga_inscriptions`, { @@ -605,11 +604,13 @@ export const apiFetcher = { id, date, block, + isViewOriginal, }: { type: SupportedExportTransactionType id?: string date?: Record<'start' | 'end', Dayjs | undefined> block?: Record<'from' | 'to', number> + isViewOriginal: boolean }) => { const rangeParams = { start_date: date?.start?.valueOf(), @@ -623,7 +624,9 @@ export const apiFetcher = { .then(res => toCamelcase(res.data)) } return requesterV1 - .get(`/${type}/download_csv`, { params: { ...rangeParams, id } }) + .get(`/${type}/download_csv${isViewOriginal ? '?status=closed' : ''}`, { + params: { ...rangeParams, id }, + }) .then(res => toCamelcase(res.data)) }, diff --git a/src/services/ExplorerService/types.ts b/src/services/ExplorerService/types.ts index e2b520fea..c85e610b5 100644 --- a/src/services/ExplorerService/types.ts +++ b/src/services/ExplorerService/types.ts @@ -208,4 +208,4 @@ interface FetchStatusValue { // Unused currently export type FetchStatus = keyof FetchStatusValue -export type SupportedExportTransactionType = 'address_transactions' | 'blocks' | 'udts' | 'nft' +export type SupportedExportTransactionType = 'address_transactions' | 'blocks' | 'udts' | 'nft' | 'omiga_inscriptions' diff --git a/src/utils/object.ts b/src/utils/object.ts index c0a0928af..d47f209aa 100644 --- a/src/utils/object.ts +++ b/src/utils/object.ts @@ -13,3 +13,9 @@ export function omit, U extends keyof T>(obj: T, keys }) return newObj } + +export function omitNil>( + obj: T, +): { [K in keyof T]: null extends T[K] ? T[K] | undefined : T[K] } { + return Object.fromEntries(Object.entries(obj).filter(([, value]) => value != null)) as any +}