From 84a2c500063e0bb184c68340d49e72bb70115a9b Mon Sep 17 00:00:00 2001 From: Kristof Csillag Date: Tue, 25 Apr 2023 20:02:05 +0200 Subject: [PATCH] Use BigNumber for number formatting --- src/app/components/Account/index.tsx | 4 +- src/app/components/Blocks/BlockLink.tsx | 18 +++--- src/app/components/Blocks/index.tsx | 22 +++---- src/app/hooks/useNumberFormatter.ts | 60 +++++++++++++++++++ src/app/pages/BlockDetailPage/index.tsx | 19 +++--- .../pages/DashboardPage/ActiveAccounts.tsx | 10 +++- src/app/pages/DashboardPage/Nodes.tsx | 4 +- .../pages/DashboardPage/TotalTransactions.tsx | 11 ++-- .../DashboardPage/TransactionsChartCard.tsx | 13 ++-- .../pages/DashboardPage/TransactionsStats.tsx | 4 +- src/app/pages/TransactionDetailPage/index.tsx | 4 +- src/locales/en/translation.json | 13 ++-- 12 files changed, 123 insertions(+), 59 deletions(-) create mode 100644 src/app/hooks/useNumberFormatter.ts diff --git a/src/app/components/Account/index.tsx b/src/app/components/Account/index.tsx index 8f5e685c3..42266ef97 100644 --- a/src/app/components/Account/index.tsx +++ b/src/app/components/Account/index.tsx @@ -17,6 +17,7 @@ import { AccountLink } from './AccountLink' import { RouteUtils } from '../../utils/route-utils' import { accountTransactionsContainerId } from '../../pages/AccountDetailsPage/TransactionsCard' import Link from '@mui/material/Link' +import { useFormatNumber } from '../../hooks/useNumberFormatter' export const StyledAvatarContainer = styled('dt')(({ theme }) => ({ '&&': { @@ -53,6 +54,7 @@ type AccountProps = { export const Account: FC = ({ account, isLoading, roseFiatValue, showLayer }) => { const { t } = useTranslation() + const formatNumber = useFormatNumber() const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('sm')) const balance = account?.balances[0]?.balance ?? '0' @@ -108,7 +110,7 @@ export const Account: FC = ({ account, isLoading, roseFiatValue, s
{t('common.transactions')}
- {t('common.transactionsNumber', { count: account.stats.num_txns })} + {formatNumber(account.stats.num_txns, { countKey: 'common.transactionsNumber' })}
diff --git a/src/app/components/Blocks/BlockLink.tsx b/src/app/components/Blocks/BlockLink.tsx index f8be09e74..8ed4eeb2c 100644 --- a/src/app/components/Blocks/BlockLink.tsx +++ b/src/app/components/Blocks/BlockLink.tsx @@ -6,14 +6,18 @@ import Link from '@mui/material/Link' import { Layer } from '../../../oasis-indexer/api' import { RouteUtils } from '../../utils/route-utils' import { TrimLinkLabel } from '../TrimLinkLabel' +import { useFormatNumber } from '../../hooks/useNumberFormatter' -export const BlockLink: FC<{ layer: Layer; height: number }> = ({ layer, height }) => ( - - - {height.toLocaleString()} - - -) +export const BlockLink: FC<{ layer: Layer; height: number }> = ({ layer, height }) => { + const formatNumber = useFormatNumber() + return ( + + + {formatNumber(height)} + + + ) +} export const BlockHashLink: FC<{ layer: Layer; hash: string; height: number }> = ({ layer, diff --git a/src/app/components/Blocks/index.tsx b/src/app/components/Blocks/index.tsx index f33cf0b2a..c6a160b17 100644 --- a/src/app/components/Blocks/index.tsx +++ b/src/app/components/Blocks/index.tsx @@ -6,6 +6,8 @@ import { Table, TableCellAlign, TableColProps } from '../../components/Table' import { paraTimesConfig } from '../../../config' import { TablePaginationProps } from '../Table/TablePagination' import { BlockHashLink, BlockLink } from './BlockLink' +import { useFormatNumber } from '../../hooks/useNumberFormatter' +import { FC } from 'react' export type TableRuntimeBlock = RuntimeBlock & { markAsNew?: boolean @@ -25,9 +27,10 @@ type BlocksProps = { pagination: false | TablePaginationProps } -export const Blocks = (props: BlocksProps) => { +export const Blocks: FC = (props: BlocksProps) => { const { isLoading, blocks, verbose, pagination, limit } = props const { t } = useTranslation() + const formatNumber = useFormatNumber() const tableColumns: TableColProps[] = [ { content: t('common.fill') }, @@ -66,7 +69,7 @@ export const Blocks = (props: BlocksProps) => { }, { align: TableCellAlign.Right, - content: block.num_transactions, + content: formatNumber(block.num_transactions), key: 'txs', }, ...(verbose @@ -79,15 +82,8 @@ export const Blocks = (props: BlocksProps) => { : []), { align: TableCellAlign.Right, - content: t('common.bytes', { - value: block.size, - formatParams: { - value: { - style: 'unit', - unit: 'byte', - unitDisplay: 'long', - } satisfies Intl.NumberFormatOptions, - }, + content: formatNumber(block.size, { + unit: 'byte', }), key: 'size', }, @@ -95,7 +91,7 @@ export const Blocks = (props: BlocksProps) => { ? [ { align: TableCellAlign.Right, - content: block.gas_used.toLocaleString(), + content: formatNumber(block.gas_used), key: 'gasUsed', }, ] @@ -104,7 +100,7 @@ export const Blocks = (props: BlocksProps) => { ? [ { align: TableCellAlign.Right, - content: blockGasLimit.toLocaleString(), + content: formatNumber(blockGasLimit), key: 'gasLimit', }, ] diff --git a/src/app/hooks/useNumberFormatter.ts b/src/app/hooks/useNumberFormatter.ts new file mode 100644 index 000000000..aaacdf469 --- /dev/null +++ b/src/app/hooks/useNumberFormatter.ts @@ -0,0 +1,60 @@ +import { BigNumber } from 'bignumber.js' +import { useTranslation } from 'react-i18next' + +export type NumberFormattingParameters = Partial & { + // Additional features which are not natively supported by BigNumber + + decimalPlaces?: number + maximumFractionDigits?: number + roundingMode?: BigNumber.RoundingMode + unit?: string + countKey?: string +} + +export const useFormatNumber = () => { + const { t } = useTranslation() + return ( + inputNumber: number | string | BigNumber.Instance | undefined, + format: NumberFormattingParameters = {}, + ): string | undefined => { + if (inputNumber === undefined) return + const { decimalPlaces, maximumFractionDigits, roundingMode, unit, countKey, ...formatting } = format + if (!!unit && !!countKey) { + throw new Error("Please don't try to use unit and countKey together! They are incompatible.") + } + let number = + typeof inputNumber === 'number' + ? BigNumber(inputNumber.toString(2), 2) // This is required to keep all precision + : BigNumber(inputNumber) + if (maximumFractionDigits !== undefined) { + number = BigNumber(number.toFixed(maximumFractionDigits, roundingMode)) + } + const wantedFormat = { ...BigNumber.config().FORMAT, ...formatting } + const numberString = + decimalPlaces === undefined + ? number.toFormat(wantedFormat) + : roundingMode === undefined + ? number.toFormat(decimalPlaces, wantedFormat) + : number.toFormat(decimalPlaces, roundingMode, wantedFormat) + if (unit) { + const formattedUnit = Intl.NumberFormat(undefined, { + style: 'unit', + unit, + unitDisplay: 'long', + }) + .formatToParts(number.toNumber()) + .find(p => p.type === 'unit')!.value + return `${numberString} ${formattedUnit}` + } else if (countKey) { + const num: number = number.toNumber() + if (num === 1) { + return t(countKey as any, { count: 1 }) + } else { + const i18nForm = t('common.number', { value: num }) + return t(countKey as any, { count: num }).replace(i18nForm, numberString) + } + } else { + return numberString + } + } +} diff --git a/src/app/pages/BlockDetailPage/index.tsx b/src/app/pages/BlockDetailPage/index.tsx index eaa1f495f..ee9f0ced6 100644 --- a/src/app/pages/BlockDetailPage/index.tsx +++ b/src/app/pages/BlockDetailPage/index.tsx @@ -18,6 +18,7 @@ import { transactionsContainerId } from './TransactionsCard' import { useLayerParam } from '../../hooks/useLayerParam' import { BlockLink, BlockHashLink } from '../../components/Blocks/BlockLink' import { RouteUtils } from '../../utils/route-utils' +import { useFormatNumber } from '../../hooks/useNumberFormatter' export const BlockDetailPage: FC = () => { const { t } = useTranslation() @@ -58,6 +59,7 @@ export const BlockDetailView: FC<{ standalone?: boolean }> = ({ isLoading, block, showLayer, standalone = false }) => { const { t } = useTranslation() + const formatNumber = useFormatNumber() const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('sm')) const formattedTime = useFormattedTimestampString(block?.timestamp) @@ -100,29 +102,22 @@ export const BlockDetailView: FC<{
{t('common.size')}
- {t('common.bytes', { - value: block.size, - formatParams: { - value: { - style: 'unit', - unit: 'byte', - unitDisplay: 'long', - } satisfies Intl.NumberFormatOptions, - }, + {formatNumber(block.size, { + unit: 'byte', })}
{t('common.transactions')}
- {t('common.transactionsNumber', { count: block.num_transactions })} + {formatNumber(block.num_transactions, { countKey: 'common.transactionsNumber' })}
{t('common.gasUsed')}
{t('block.gasUsed', { - value: block.gas_used, + value: formatNumber(block.gas_used), percentage: block.gas_used / blockGasLimit, formatParams: { percentage: { @@ -134,7 +129,7 @@ export const BlockDetailView: FC<{
{t('common.gasLimit')}
-
{blockGasLimit.toLocaleString()}
+
{formatNumber(blockGasLimit)}
) } diff --git a/src/app/pages/DashboardPage/ActiveAccounts.tsx b/src/app/pages/DashboardPage/ActiveAccounts.tsx index f6258947b..5b7321fc5 100644 --- a/src/app/pages/DashboardPage/ActiveAccounts.tsx +++ b/src/app/pages/DashboardPage/ActiveAccounts.tsx @@ -16,6 +16,7 @@ import { sumBucketsByStartDuration, } from '../../utils/chart-utils' import { useLayerParam } from '../../hooks/useLayerParam' +import { useFormatNumber } from '../../hooks/useNumberFormatter' export const getActiveAccountsWindows = (duration: ChartDuration, windows: Windows[]) => { switch (duration) { @@ -60,6 +61,7 @@ export const ActiveAccounts: FC = ({ chartDuration }) => { const { t } = useTranslation() const { limit, bucket_size_seconds } = durationToQueryParams[chartDuration] const layer = useLayerParam() + const formatNumber = useFormatNumber() const activeAccountsQuery = useGetLayerStatsActiveAccounts( layer, { @@ -80,8 +82,10 @@ export const ActiveAccounts: FC = ({ chartDuration }) => { getActiveAccountsWindows(chartDuration, activeAccountsQuery.data?.data?.windows) const totalNumberLabel = dailyChart && windows?.length - ? windows[0].active_accounts.toLocaleString() - : windows?.reduce((acc, curr) => acc + curr.active_accounts, 0).toLocaleString() + ? formatNumber(windows[0].active_accounts) + : windows + ? formatNumber(windows.reduce((acc, curr) => acc + curr.active_accounts, 0)) + : undefined return ( @@ -93,7 +97,7 @@ export const ActiveAccounts: FC = ({ chartDuration }) => { formatters={{ data: (value: number) => t('activeAccounts.tooltip', { - value, + value: formatNumber(value), }), label: (value: string) => t('common.formattedDateTime', { diff --git a/src/app/pages/DashboardPage/Nodes.tsx b/src/app/pages/DashboardPage/Nodes.tsx index 0fd2da1a5..fe61a6ddc 100644 --- a/src/app/pages/DashboardPage/Nodes.tsx +++ b/src/app/pages/DashboardPage/Nodes.tsx @@ -8,9 +8,11 @@ import { COLORS } from '../../../styles/theme/colors' import { Layer, useGetRuntimeStatus } from '../../../oasis-indexer/api' import { useLayerParam } from '../../hooks/useLayerParam' import { AppErrors } from '../../../types/errors' +import { useFormatNumber } from '../../hooks/useNumberFormatter' export const Nodes: FC = () => { const { t } = useTranslation() + const formatNumber = useFormatNumber() const layer = useLayerParam() if (layer === Layer.consensus) { throw AppErrors.UnsupportedLayer @@ -25,7 +27,7 @@ export const Nodes: FC = () => { <> - {t('nodes.value', { value: activeNodes })} + {formatNumber(activeNodes)} )} diff --git a/src/app/pages/DashboardPage/TotalTransactions.tsx b/src/app/pages/DashboardPage/TotalTransactions.tsx index 7f47cdab2..0976319db 100644 --- a/src/app/pages/DashboardPage/TotalTransactions.tsx +++ b/src/app/pages/DashboardPage/TotalTransactions.tsx @@ -9,9 +9,11 @@ import { DurationPills } from './DurationPills' import { CardHeaderWithResponsiveActions } from './CardHeaderWithResponsiveActions' import { ChartDuration, cumulativeSum } from '../../utils/chart-utils' import { useLayerParam } from '../../hooks/useLayerParam' +import { useFormatNumber } from '../../hooks/useNumberFormatter' export const TotalTransactions: FC = () => { const { t } = useTranslation() + const formatNumber = useFormatNumber() const [chartDuration, setChartDuration] = useState(ChartDuration.MONTH) const statsParams = durationToQueryParams[chartDuration] const layer = useLayerParam() @@ -47,12 +49,9 @@ export const TotalTransactions: FC = () => { formatters={{ data: (value: number) => t('totalTransactions.tooltip', { - value, - formatParams: { - value: { - maximumFractionDigits: 2, - } satisfies Intl.NumberFormatOptions, - }, + value: formatNumber(value, { + maximumFractionDigits: 2, + })!, }), label: (value: string) => t('common.formattedDateTime', { diff --git a/src/app/pages/DashboardPage/TransactionsChartCard.tsx b/src/app/pages/DashboardPage/TransactionsChartCard.tsx index bbb60aa21..abac71a43 100644 --- a/src/app/pages/DashboardPage/TransactionsChartCard.tsx +++ b/src/app/pages/DashboardPage/TransactionsChartCard.tsx @@ -14,6 +14,7 @@ import { SnapshotCard } from './SnapshotCard' import { PercentageGain } from '../../components/PercentageGain' import startOfHour from 'date-fns/startOfHour' import { useLayerParam } from '../../hooks/useLayerParam' +import { useFormatNumber } from '../../hooks/useNumberFormatter' interface TransactionsChartCardProps { chartDuration: ChartDuration @@ -21,6 +22,7 @@ interface TransactionsChartCardProps { const TransactionsChartCardCmp: FC = ({ chartDuration }) => { const { t } = useTranslation() + const formatNumber = useFormatNumber() const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('sm')) const statsParams = durationToQueryParams[chartDuration] @@ -67,7 +69,7 @@ const TransactionsChartCardCmp: FC = ({ chartDuratio /> ) } - label={totalTransactions.toLocaleString()} + label={formatNumber(totalTransactions)} > {lineChartData && ( = ({ chartDuratio formatters={{ data: (value: number) => t('transactionsTpsChart.tooltip', { - value, - formatParams: { - value: { - maximumFractionDigits: 2, - } satisfies Intl.NumberFormatOptions, - }, + value: formatNumber(value, { + maximumFractionDigits: 2, + }), }), label: (value: string) => t('common.formattedDateTime', { diff --git a/src/app/pages/DashboardPage/TransactionsStats.tsx b/src/app/pages/DashboardPage/TransactionsStats.tsx index 34fb796f2..1574b5c60 100644 --- a/src/app/pages/DashboardPage/TransactionsStats.tsx +++ b/src/app/pages/DashboardPage/TransactionsStats.tsx @@ -13,9 +13,11 @@ import { DurationPills } from './DurationPills' import { CardHeaderWithResponsiveActions } from './CardHeaderWithResponsiveActions' import { ChartDuration } from '../../utils/chart-utils' import { useLayerParam } from '../../hooks/useLayerParam' +import { useFormatNumber } from '../../hooks/useNumberFormatter' export const TransactionsStats: FC = () => { const { t } = useTranslation() + const formatNumber = useFormatNumber() const [chartDuration, setChartDuration] = useState(ChartDuration.MONTH) const statsParams = durationToQueryParams[chartDuration] const layer = useLayerParam() @@ -55,7 +57,7 @@ export const TransactionsStats: FC = () => { data={buckets.slice().reverse()} dataKey="tx_volume" formatters={{ - data: (value: number) => t('transactionStats.tooltip', { value: value.toLocaleString() }), + data: (value: number) => t('transactionStats.tooltip', { value: formatNumber(value) }), label: (value: string) => t('common.formattedDateTime', { timestamp: new Date(value), diff --git a/src/app/pages/TransactionDetailPage/index.tsx b/src/app/pages/TransactionDetailPage/index.tsx index 10691a8d3..e3f1c9282 100644 --- a/src/app/pages/TransactionDetailPage/index.tsx +++ b/src/app/pages/TransactionDetailPage/index.tsx @@ -27,6 +27,7 @@ import { useLayerParam } from '../../hooks/useLayerParam' import { BlockLink } from '../../components/Blocks/BlockLink' import { TransactionLink } from '../../components/Transactions/TransactionLink' import { TransactionLogs } from '../../components/Transactions/Logs' +import { useFormatNumber } from '../../hooks/useNumberFormatter' type TransactionSelectionResult = { wantedTransaction?: RuntimeTransaction @@ -130,6 +131,7 @@ export const TransactionDetailView: FC<{ standalone?: boolean }> = ({ isLoading, transaction, showLayer, standalone = false }) => { const { t } = useTranslation() + const formatNumber = useFormatNumber() const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('sm')) const formattedTimestamp = useFormattedTimestampString(transaction?.timestamp) @@ -204,7 +206,7 @@ export const TransactionDetailView: FC<{
{t('common.valueInRose', { value: transaction.fee })}
{t('common.gasLimit')}
-
{transaction.gas_limit.toLocaleString()}
+
{formatNumber(transaction.gas_limit)}
)} diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index ff881a09f..146814ba5 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -15,13 +15,13 @@ }, "activeAccounts": { "title": "Active Accounts", - "tooltip": "{{value, number}} accounts" + "tooltip": "{{value}} accounts" }, "blocks": { "latest": "Latest Blocks" }, "block": { - "gasUsed": "{{value, number}} | {{percentage, number}}" + "gasUsed": "{{value}} | {{percentage, number}}" }, "buildInternal": "Please note this is an internal launch meant to gather your feedback. Please share your feedback with screenshots through this from: ", "buildPreview": "Please note this is an experimental build of Oasis Explorer and that data that is shown might be incorrect.", @@ -35,7 +35,6 @@ "active": "Active", "balance": "Balance", "block": "Block", - "bytes": "{{value, number}}", "change": "Change", "data": "Data", "emerald": "Emerald", @@ -57,6 +56,7 @@ "lessThanAmount": "< {{value}} {{ticker}}", "logs": "Logs", "name": "Name", + "number": "{{value}}", "paratime": "Paratime", "rose": "ROSE", "size": "Size", @@ -80,8 +80,7 @@ "paraTimeOnline": "ParaTime Online" }, "nodes": { - "title": "Active nodes", - "value": "{{value, number}}" + "title": "Active nodes" }, "errors": { "code": "error code", @@ -153,7 +152,7 @@ }, "totalTransactions": { "header": "Total Transactions", - "tooltip": "{{value, number}} total transactions" + "tooltip": "{{value}} total transactions" }, "transactions": { "latest": "Latest Transactions", @@ -187,7 +186,7 @@ "tooltip": "{{value}} tx/day" }, "transactionsTpsChart": { - "tooltip": "{{value, number}} TPS" + "tooltip": "{{value}} TPS" }, "select": { "placeholder": "Select"