diff --git a/_typos.toml b/_typos.toml index 9ed3de437..c2e8bcb72 100644 --- a/_typos.toml +++ b/_typos.toml @@ -6,3 +6,5 @@ rd='rd' th='th' # typo in api, should be fixed later toekn='toekn' +# echart config +metalness = 'metalness' diff --git a/src/components/ui/NumberTicker/index.tsx b/src/components/ui/NumberTicker/index.tsx new file mode 100644 index 000000000..212ea557b --- /dev/null +++ b/src/components/ui/NumberTicker/index.tsx @@ -0,0 +1,52 @@ +import { useEffect, useRef, useState } from 'react' + +interface NumberTickerProps { + value: number | null + duration?: number // duration in milliseconds + className?: string +} + +export function NumberTicker({ + value, + duration = 1000, // default animation duration: 1 second + className = '', +}: NumberTickerProps) { + const [displayValue, setDisplayValue] = useState(value) + const prevValueRef = useRef(value ?? 0) // Store the previous value + const animationFrame = useRef() + + useEffect(() => { + if (typeof value !== 'number') { + return + } + const startValue = prevValueRef.current // Previous value + const targetValue = value // New value + const startTime = performance.now() + + const animate = (currentTime: number) => { + const elapsed = currentTime - startTime + const progress = Math.min(elapsed / duration, 1) // Progress between 0 and 1 + + // Calculate interpolated value + const interpolatedValue = Math.floor(startValue + (targetValue - startValue) * progress) + setDisplayValue(interpolatedValue) + + if (progress < 1) { + animationFrame.current = requestAnimationFrame(animate) + } + } + + // Start animation + if (animationFrame.current) cancelAnimationFrame(animationFrame.current) + animationFrame.current = requestAnimationFrame(animate) + + // Update the previous value reference + prevValueRef.current = value + + return () => { + if (animationFrame.current) cancelAnimationFrame(animationFrame.current) + } + }, [value, duration]) + + return {displayValue?.toLocaleString('en') ?? '--,---,---'} +} diff --git a/src/pages/Home/Banner/index.module.scss b/src/pages/Home/Banner/index.module.scss index a58663a8a..09001d253 100644 --- a/src/pages/Home/Banner/index.module.scss +++ b/src/pages/Home/Banner/index.module.scss @@ -11,6 +11,10 @@ $backgroundColor: #232323; background-repeat: no-repeat; background-position: center center; background-size: auto 100%; + color: white; + display: flex; + align-items: center; + justify-content: center; @media (width <= $mobileBreakPoint) { background-image: url('./ckb_explorer_banner_phone.svg'); @@ -121,3 +125,21 @@ $backgroundColor: #232323; padding: 16px 24px; } } + +.knowledgeBase { + text-align: center; + font-weight: bold; + font-optical-sizing: auto; + mix-blend-mode: difference; + line-height: 1.2; + font-size: 50px; + + .ticker { + display: flex; + gap: 8px; + } + + @media screen and (width <= 500px) { + font-size: 30px; + } +} diff --git a/src/pages/Home/Banner/index.tsx b/src/pages/Home/Banner/index.tsx index 49017fcaf..bab0e05cc 100644 --- a/src/pages/Home/Banner/index.tsx +++ b/src/pages/Home/Banner/index.tsx @@ -1,3 +1,41 @@ +import { useQuery } from '@tanstack/react-query' +import config from '../../../config' import styles from './index.module.scss' +import { getKnowledgeSize } from './utils' +import { NumberTicker } from '../../../components/ui/NumberTicker' -export default () =>
+const { BACKUP_NODES: backupNodes } = config + +export default () => { + const { data: size } = useQuery( + ['backup_nodes'], + async () => { + try { + if (backupNodes.length === 0) return null + + const [size1, size2] = await Promise.race(backupNodes.map(getKnowledgeSize)) + return size1 ?? size2 + } catch { + return null + } + }, + { refetchInterval: 12 * 1000 }, + ) + return ( +
+ + Knowledge Size +
+
+ + CKBytes +
+
+
+ ) +} diff --git a/src/pages/Home/Banner/utils.ts b/src/pages/Home/Banner/utils.ts new file mode 100644 index 000000000..c2df57ef5 --- /dev/null +++ b/src/pages/Home/Banner/utils.ts @@ -0,0 +1,30 @@ +import BigNumber from 'bignumber.js' + +/** + * 6/10 of 7th output in the genesis should be excluded because they are expected to be burnt. + * ref: https://talk.nervos.org/t/how-to-get-the-average-occupied-bytes-per-live-cell-in-ckb/7138/2?u=keith + * */ +const EXCLUDE = BigNumber('504000000000000000') + +export const getKnowledgeSize = async (nodeUrl: string) => { + const header = await fetch(nodeUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'get_tip_header', + params: [], + }), + }) + .then(res => res.json()) + .then(res => res.result) + const { dao } = header + + const [, , , u] = dao + .slice(2) + .match(/\w{16}/g) + .map((i: string) => i.match(/\w{2}/g)?.reverse().join('') ?? '') + const total = BigNumber(`0x${u}`).minus(EXCLUDE).toFormat() + return total +}