From 616255db0dbc8302eb491d60e3191b5d36e1ffb8 Mon Sep 17 00:00:00 2001 From: Manish Gupta Date: Tue, 18 Feb 2025 01:05:06 -0800 Subject: [PATCH] [apps] wip --- .../applications/[owner_project]/page.tsx | 388 ++++++++++++++++-- .../applications/_components/Components.tsx | 109 ++++- .../applications/_components/Search.tsx | 2 +- app/(layout)/applications/page.tsx | 172 ++------ components/layout/GridTable.tsx | 4 +- 5 files changed, 484 insertions(+), 191 deletions(-) diff --git a/app/(layout)/applications/[owner_project]/page.tsx b/app/(layout)/applications/[owner_project]/page.tsx index 2a8debc1..eb89d740 100644 --- a/app/(layout)/applications/[owner_project]/page.tsx +++ b/app/(layout)/applications/[owner_project]/page.tsx @@ -8,8 +8,8 @@ import { useMaster } from "@/contexts/MasterContext"; import { StackedDataBar } from "../_components/GTPChart"; import { ChartScaleProvider } from "../_contexts/ChartScaleContext"; import ChartScaleControls from "../_components/ChartScaleControls"; -import { ApplicationDisplayName, ApplicationIcon, Category, Chains, Links, MetricTooltip } from "../_components/Components"; -import { memo, useEffect, useMemo, useRef, useState } from "react"; +import { ApplicationDisplayName, ApplicationIcon, Category, Chains, formatNumber, Links, MetricTooltip } from "../_components/Components"; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useLocalStorage } from "usehooks-ts"; import HorizontalScrollContainer from "@/components/HorizontalScrollContainer"; import { GridTableAddressCell, GridTableHeader, GridTableHeaderCell, GridTableRow } from "@/components/layout/GridTable"; @@ -20,6 +20,11 @@ import VerticalVirtuosoScrollContainer from "@/components/VerticalVirtuosoScroll import Link from "next/link"; import { SortProvider, useSort } from "../_contexts/SortContext"; import { useUIContext } from "@/contexts/UIContext"; +import { GTPIconName } from "@/icons/gtp-icon-names"; +import { Virtuoso } from "react-virtuoso"; +import { useRouter } from "next/navigation"; +import { AggregatedDataRow, useApplicationsData } from "../_contexts/ApplicationsDataContext"; +import useDragScroll from "@/hooks/useDragScroll"; type Props = { params: { owner_project: string }; @@ -70,7 +75,7 @@ export default function Page({ params: { owner_project } }: Props) { -
+ {/*
*/}
Similar Applications
@@ -80,8 +85,10 @@ export default function Page({ params: { owner_project } }: Props) {
-
+ {/*
*/}
+ + ); } @@ -251,32 +258,6 @@ const MetricChainBreakdownBar = ({ metric }: { metric: string }) => { return [...acc, prev + v]; }, [] as number[]); - function formatNumber(number: number, decimals?: number): string { - if (number === 0) { - return "0"; - } else if (Math.abs(number) >= 1e9) { - if (Math.abs(number) >= 1e12) { - return (number / 1e12).toFixed(2) + "T"; - } else if (Math.abs(number) >= 1e9) { - return (number / 1e9).toFixed(2) + "B"; - } - } else if (Math.abs(number) >= 1e6) { - return (number / 1e6).toFixed(2) + "M"; - } else if (Math.abs(number) >= 1e3) { - const rounded = (number / 1e3).toFixed(2); - return `${rounded}${Math.abs(number) >= 10000 ? "k" : "k"}`; - } else if (Math.abs(number) >= 100) { - return number.toFixed(decimals ? decimals : 2); - } else if (Math.abs(number) >= 10) { - return number.toFixed(decimals ? decimals : 2); - } else { - return number.toFixed(decimals ? decimals : 2); - } - - // Default return if none of the conditions are met - return ""; - } - console.log("cumulativePercentages", cumulativePercentages); if (!metricData) { @@ -291,7 +272,7 @@ const MetricChainBreakdownBar = ({ metric }: { metric: string }) => {
-
{prefix}{formatNumber(values.reduce((acc, v) => acc + v, 0))}
+
{prefix}{formatNumber(values.reduce((acc, v) => acc + v, 0), {defaultDecimals: 2, thresholdDecimals: {base:5}})}
{ownerProjectToProjectData[owner_project] && ownerProjectToProjectData[owner_project].display_name || ""}
@@ -379,6 +360,7 @@ const ContractsTable = () => { const { metricsDef, selectedMetrics, setSelectedMetrics, selectedMetricKeys, } = useMetrics(); const [showUsd, setShowUsd] = useLocalStorage("showUsd", true); const {sort, setSort} = useSort(); + const [showMore, setShowMore] = useState(false); const metricKey = useMemo(() => { let key = selectedMetrics[0]; @@ -390,7 +372,7 @@ const ContractsTable = () => { // Memoize gridColumns to prevent recalculations const gridColumns = useMemo(() => - `26px 280px 95px minmax(95px,800px) ${selectedMetricKeys.map(() => `237px`).join(" ")} 20px`, + `26px 280px 95px minmax(135px,800px) ${selectedMetricKeys.map(() => `237px`).join(" ")} 20px`, [selectedMetricKeys] ); @@ -445,7 +427,7 @@ const ContractsTable = () => { })}
-
+ {/*
{
)} /> +
*/} +
+ ( +
+ +
+ )} + useWindowScroll + increaseViewportBy={{top:0, bottom: 400}} + overscan={50} + /> +
+
+
setShowMore(!showMore)}> + {showMore ? "Show less" : "Show more"} +
- ) } @@ -499,13 +498,17 @@ const ContractsTableRow = memo(({ contract }: { contract: ContractDict}) => { const { owner_project } = useApplicationDetailsData(); const { ownerProjectToProjectData } = useProjectsMetadata(); const { metricsDef, selectedMetrics, selectedMetricKeys, } = useMetrics(); + const { AllChainsByKeys, data: masterData } = useMaster(); // Memoize gridColumns to prevent recalculations const gridColumns = useMemo(() => - `26px 280px 95px minmax(95px,800px) ${selectedMetricKeys.map(() => `237px`).join(" ")} 20px`, + `26px 280px 95px minmax(135px,800px) ${selectedMetricKeys.map(() => `237px`).join(" ")} 20px`, [selectedMetricKeys] ); + if(!masterData) + return null; + return ( {
- + {/* */} +
+ +
{/* */} {contract.name || ()} - + {/* */}
{/*
@@ -532,8 +538,8 @@ const ContractsTableRow = memo(({ contract }: { contract: ContractDict}) => {
-
- {contract.sub_category_key} +
+ {masterData.blockspace_categories.sub_categories[contract.sub_category_key] ? masterData.blockspace_categories.sub_categories[contract.sub_category_key] : contract.sub_category_key}
{selectedMetrics.map((key, index) => (
{ ) }); -ContractsTableRow.displayName = 'ApplicationTableRow'; \ No newline at end of file +ContractsTableRow.displayName = 'ApplicationTableRow'; + +const SimilarApplications = ({ owner_project }: { owner_project: string }) => { + const {ownerProjectToProjectData} = useProjectsMetadata(); + const { applicationDataAggregated, isLoading } = useApplicationsData(); + const { selectedMetrics } = useMetrics(); + const { metricsDef } = useMetrics(); + const { sort } = useSort(); + + const [medianMetricKey, setMedianMetricKey] = useState(selectedMetrics[0]); + + useEffect(() => { + if(Object.keys(metricsDef).includes(sort.metric)){ + let key = sort.metric; + if (sort.metric === "gas_fees") + key = "gas_fees_eth"; + + setMedianMetricKey(key); + } + + }, [metricsDef, sort.metric]); + + const { topGainers, topLosers } = useMemo(() => { + + if(!ownerProjectToProjectData || !ownerProjectToProjectData[owner_project] || !applicationDataAggregated || !applicationDataAggregated.length) + return { topGainers: [], topLosers: [] }; + // let medianMetricKey = Object.keys(metricsDef).includes(sort.metric) ? sort.metric : "gas_fees"; + + const medianMetricValues = applicationDataAggregated.map((application) => application[medianMetricKey]) + .sort((a, b) => a - b); + + const medianValue = medianMetricValues[Math.floor(medianMetricValues.length / 2)]; + + // console.log("medianMetricKey", medianMetricKey); + // console.log("medianValue", medianValue); + // console.log("applicationDataAggregated", applicationDataAggregated); + + // filter out applications with < median value of selected metric and with previous value of 0 + const filteredApplications = applicationDataAggregated + .filter((application) => application[medianMetricKey] > medianValue && application["prev_" + medianMetricKey] > 0 && ownerProjectToProjectData[application.owner_project].main_category === ownerProjectToProjectData[owner_project].main_category) + // console.log("filteredApplications", filteredApplications); + + // top 3 applications with highest change_pct + return { + topGainers: [...filteredApplications] + .sort((a, b) => b[medianMetricKey + "_change_pct"] - a[medianMetricKey + "_change_pct"]) + .slice(0, 3), + topLosers: [...filteredApplications] + .sort((a, b) => a[medianMetricKey + "_change_pct"] - b[medianMetricKey + "_change_pct"]) + .slice(0, 3), + } + }, [applicationDataAggregated, medianMetricKey, ownerProjectToProjectData, owner_project]); + + return ( + <> +
+ + {topGainers.map((application, index) => ( + + ))} + {topLosers.map((application, index) => ( + + ))} + {isLoading && new Array(6).fill(0).map((_, index) => ( + + ))} + +
+
+ ), ...topLosers.map((application) => )]} /> +
+ + ) +} + + +const CardSwiper = ({ cards }: { cards: React.ReactNode[] }) => { + // const containerRef = useRef(null); + const [activeIndex, setActiveIndex] = useState(0); + const [isFirst, setIsFirst] = useState(true); + const [isLast, setIsLast] = useState(false); + const [leftIndex, setLeftIndex] = useState(0); + const [rightIndex, setRightIndex] = useState(2); + + const { containerRef, showLeftGradient, showRightGradient } = + useDragScroll("horizontal", 0.96, { snap: true, snapThreshold: 0.2 }); + + const onScroll = () => { + if (containerRef.current) { + const containerRect = containerRef.current.getBoundingClientRect(); + const containerCenter = containerRect.left + containerRect.width / 2; + const children = Array.from(containerRef.current.children); + let closestIndex = 0; + let closestDistance = Infinity; + children.forEach((child, index) => { + const rect = child.getBoundingClientRect(); + const childCenter = rect.left + rect.width / 2; + const distance = Math.abs(childCenter - containerCenter); + if (distance < closestDistance) { + closestDistance = distance; + closestIndex = index; + } + }); + setActiveIndex(closestIndex); + setLeftIndex(Math.max(0, closestIndex - 1)); + setRightIndex(Math.min(children.length - 1, closestIndex + 1)); + setIsFirst(closestIndex === 0); + setIsLast(closestIndex === children.length - 1); + } + }; + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + container.addEventListener("scroll", onScroll); + // Run once on mount to set the active index correctly. + onScroll(); + return () => container.removeEventListener("scroll", onScroll); + }, []); + + return ( +
+ {cards.map((card, index) => { + return ( +
+ {card} +
+ ) + })} +
+ ); +}; + + + +const ApplicationCard = memo(({ application, className, width }: { application?: AggregatedDataRow, className?: string, width?: number }) => { + const { AllChainsByKeys } = useMaster(); + const { ownerProjectToProjectData } = useProjectsMetadata(); + const { selectedChains, setSelectedChains, } = useApplicationsData(); + const { selectedMetrics, metricsDef } = useMetrics(); + const [showUsd, setShowUsd] = useLocalStorage("showUsd", true); + const router = useRouter(); + + const numContractsString = useCallback((application: AggregatedDataRow) => { + return application.num_contracts.toLocaleString("en-GB"); + }, []); + + + const metricKey = useMemo(() => { + let key = selectedMetrics[0]; + if (selectedMetrics[0] === "gas_fees") + key = showUsd ? "gas_fees_usd" : "gas_fees_eth"; + + return key; + }, [selectedMetrics, showUsd]); + + const rank = useMemo(() => { + if (!application) return null; + + return application[`rank_${metricKey}`]; + + }, [application, metricKey]); + + const value = useMemo(() => { + if (!application) return null; + + return application[metricKey]; + }, [application, metricKey]); + + const prefix = useMemo(() => { + const def = metricsDef[selectedMetrics[0]].units; + + if (Object.keys(def).includes("usd")) { + return showUsd ? def.usd.prefix : def.eth.prefix; + } else { + return Object.values(def)[0].prefix; + } + }, [metricsDef, selectedMetrics, showUsd]); + + if (!application) { + return ( +
+
+ ) + } + + return ( +
{ + // window.location.href = `/applications/${application.owner_project}`; + router.push(`/applications/${application.owner_project}`); + }} + > +
+
+
+
{numContractsString(application)}
+
contracts
+
+
+
Rank
+
{rank}
+
+
+ +
+
+ {/* {JSON.stringify( application)} */} + {application.origin_keys.map((chain, index) => ( +
{ + if (selectedChains.includes(chain)) { + setSelectedChains(selectedChains.filter((c) => c !== chain)); + } else { + setSelectedChains([...selectedChains, chain]); + } + }} + > + {AllChainsByKeys[chain] && ( + + )} +
+ ))} +
+
+
+
+ {prefix} + {value?.toLocaleString("en-GB")} +
+
+ {application[`${metricKey}_change_pct`] !== Infinity ? ( +
+ {/* {application[`${metricKey}_change_pct`] < 0 ? '-' : '+'}{formatNumber(Math.abs(application[`${metricKey}_change_pct`]), {defaultDecimals: 1, thresholdDecimals: {base:0}})}% */} + {application[`${metricKey}_change_pct`] < 0 ? '-' : '+'}{Math.abs(application[`${metricKey}_change_pct`]).toLocaleString("en-GB",{maximumFractionDigits:0})}% +
+ ) :
 
} +
+
+
+
+
+
+ + {ownerProjectToProjectData[application.owner_project] ? ( +
+ ) : ( +
+ )} + + + +
+
+
{ownerProjectToProjectData[application.owner_project] && ownerProjectToProjectData[application.owner_project].main_category}
+ {/*
+ {getLinks(application).map((item, index) => ( +
+ {item.link && + + } +
+ ))} +
*/} + +
+
+ ) +}); + +ApplicationCard.displayName = 'ApplicationCard'; \ No newline at end of file diff --git a/app/(layout)/applications/_components/Components.tsx b/app/(layout)/applications/_components/Components.tsx index 7dbb549d..cc041159 100644 --- a/app/(layout)/applications/_components/Components.tsx +++ b/app/(layout)/applications/_components/Components.tsx @@ -114,9 +114,9 @@ export const PageTitleAndDescriptionAndControls = () => {
*/} -
+
- + Applications {/* {scrollY} {`${Math.min(0, -88 + scrollY)}px`} */} @@ -136,7 +136,7 @@ export const PageTitleAndDescriptionAndControls = () => {
- +
@@ -146,7 +146,12 @@ export const PageTitleAndDescriptionAndControls = () => { {/* Relay is a cross-chain payment system that enables instant, low-cost bridging and transaction execution by connecting users with relayers who act on their behalf for a small fee. It aims to minimize gas costs and execution latency, making it suitable for applications like payments, bridging, NFT minting, and gas abstraction. I can add one more sentence to that and its still legible. And one more maybe so that we reach 450 characters. Let’s see. */}
+
+
+
+ +
{/* */} @@ -215,7 +220,7 @@ export const MultipleSelectTopRowChild = ({ handleNext, handlePrev, selected, se <>
{ +export const ProjectDetailsLinks = memo(({ owner_project, mobile }: { owner_project: string, mobile?: boolean }) => { "use client"; const { ownerProjectToProjectData } = useProjectsMetadata(); const linkPrefixes = ["https://x.com/", "https://github.com/", "", ""]; const icons = ["ri:twitter-x-fill", "ri:github-fill", "feather:monitor", "ri:discord-fill"]; const keys = ["twitter", "main_github", "website", "discord"]; + if(mobile) { + return ( +
+ {ownerProjectToProjectData[owner_project] && keys.filter( + (key) => ownerProjectToProjectData[owner_project][key] + ).map((key, index) => ( + + { + // key === "website" ? + // ( + // <> + // {ownerProjectToProjectData[owner_project] && ( + // + // )} + //
{ownerProjectToProjectData[owner_project].display_name}
+ //
+ // + //
+ // + // ) + // : ( + + // ) + } + + ))} +
+ ); + } + return (
{ownerProjectToProjectData[owner_project] && keys.filter( @@ -389,6 +432,8 @@ export const Links = memo(({ owner_project, showUrl}: { owner_project: string, s const keys = ["website", "twitter", "main_github"]; const [currentHoverKey, setCurrentHover] = useState("website"); + if(!ownerProjectToProjectData[owner_project]) return null; + if(showUrl) { return (
@@ -440,12 +485,13 @@ export const Chains = ({ origin_keys }: { origin_keys: string[] }) => { const { selectedChains, setSelectedChains } = useApplicationsData(); return ( -
+
{origin_keys.map((chain, index) => (
{ + className={`group-hover/chains:opacity-50 hover:!opacity-100 cursor-pointer p-[2.5px] ${selectedChains.includes(chain) || selectedChains.length === 0 ? '' : '!text-[#5A6462]'}`} style={{ color: AllChainsByKeys[chain] ? AllChainsByKeys[chain].colors["dark"][0] : '' }} + onClick={(e) => { + e.stopPropagation(); if (selectedChains.includes(chain)) { setSelectedChains(selectedChains.filter((c) => c !== chain)); } else { @@ -507,4 +553,51 @@ export const Category = ({ category }: { category: string }) => { )}
); +} + +interface ThresholdConfig { + value: number; + suffix: string; + decimals?: number; +} + +interface FormatNumberOptions { + defaultDecimals?: number; + thresholdDecimals?: { + [key: string]: number; // 'T', 'B', 'M', 'k', or 'base' for numbers < 1000 + }; +} + +export function formatNumber( + number: number, + options: FormatNumberOptions = {} +): string { + // Handle special cases + if (!Number.isFinite(number)) return 'N/A'; + if (number === 0) return '0'; + + const defaultDecimals = options.defaultDecimals ?? 2; + + // Define formatting thresholds + const thresholds: ThresholdConfig[] = [ + { value: 1e12, suffix: 'T' }, + { value: 1e9, suffix: 'B' }, + { value: 1e6, suffix: 'M' }, + { value: 1e3, suffix: 'k' } + ]; + + const absNumber = Math.abs(number); + + // Find the appropriate threshold + const threshold = thresholds.find(t => absNumber >= t.value); + + if (threshold) { + const scaledNumber = number / threshold.value; + const decimals = options.thresholdDecimals?.[threshold.suffix] ?? defaultDecimals; + return scaledNumber.toFixed(decimals) + threshold.suffix; + } + + // For numbers less than 1000 + const baseDecimals = options.thresholdDecimals?.['base'] ?? defaultDecimals; + return number.toFixed(baseDecimals); } \ No newline at end of file diff --git a/app/(layout)/applications/_components/Search.tsx b/app/(layout)/applications/_components/Search.tsx index 865f3372..d1d50628 100644 --- a/app/(layout)/applications/_components/Search.tsx +++ b/app/(layout)/applications/_components/Search.tsx @@ -333,7 +333,7 @@ export default function Search() { }} />
setIsOpen(true)} >
diff --git a/app/(layout)/applications/page.tsx b/app/(layout)/applications/page.tsx index c8b5cdbc..8bd3f07c 100644 --- a/app/(layout)/applications/page.tsx +++ b/app/(layout)/applications/page.tsx @@ -18,7 +18,7 @@ import { useLocalStorage } from "usehooks-ts"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/layout/Tooltip"; import VerticalVirtuosoScrollContainer from "@/components/VerticalVirtuosoScrollContainer"; import { Virtuoso } from "react-virtuoso"; -import { ApplicationDisplayName, ApplicationIcon, Category, Chains, Links, MetricTooltip } from "./_components/Components"; +import { ApplicationDisplayName, ApplicationIcon, Category, Chains, formatNumber, Links, MetricTooltip } from "./_components/Components"; import { useProjectsMetadata } from "./_contexts/ProjectsMetadataContext"; import { useSort } from "./_contexts/SortContext"; import { ApplicationsURLs } from "@/lib/urls"; @@ -27,6 +27,7 @@ import useDragScroll from "@/hooks/useDragScroll"; import { useRouter } from "next/navigation"; import Image from "next/image"; import { MetricInfo } from "@/types/api/MasterResponse"; +import { useTimespan } from "./_contexts/TimespanContext"; // Preload data for the overview page @@ -39,6 +40,7 @@ export default function Page() { const { selectedMetrics } = useMetrics(); const { metricsDef } = useMetrics(); const { sort } = useSort(); + const { selectedTimespan } = useTimespan(); const [medianMetricKey, setMedianMetricKey] = useState(selectedMetrics[0]); @@ -61,14 +63,14 @@ export default function Page() { const medianValue = medianMetricValues[Math.floor(medianMetricValues.length / 2)]; - console.log("medianMetricKey", medianMetricKey); - console.log("medianValue", medianValue); - console.log("applicationDataAggregated", applicationDataAggregated); + // console.log("medianMetricKey", medianMetricKey); + // console.log("medianValue", medianValue); + // console.log("applicationDataAggregated", applicationDataAggregated); // filter out applications with < median value of selected metric and with previous value of 0 const filteredApplications = applicationDataAggregated .filter((application) => application[medianMetricKey] > medianValue && application["prev_" + medianMetricKey] > 0); - console.log("filteredApplications", filteredApplications); + // console.log("filteredApplications", filteredApplications); // top 3 applications with highest change_pct return { @@ -85,7 +87,7 @@ export default function Page() { <>
{/* */} - +
Top Gainers and Losers by {metricsDef[selectedMetrics[0]].name}
@@ -93,7 +95,7 @@ export default function Page() {
- + {topGainers.map((application, index) => ( ))} @@ -106,7 +108,7 @@ export default function Page() {
{/* */} -
+
), ...topLosers.map((application) => )]} />
{/* */} @@ -215,125 +217,6 @@ const CardSwiper = ({ cards }: { cards: React.ReactNode[] }) => { ); }; -const ApplicationCardSwiper = () => { - const { applicationDataAggregated } = useApplicationsData(); - const [cardWidth, setCardWidth] = useState(340); - const containerRef = useRef(null); - const [scrollLeft, setScrollLeft] = useState(0); - const [containerWidth, setContainerWidth] = useState(0); - - // Update container width on resize - useEffect(() => { - const handleResize = () => { - if (containerRef.current) { - setContainerWidth(containerRef.current.offsetWidth); - } - }; - - handleResize(); - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, []); - - // Calculate visible applications - const visibleApplications = useMemo(() => { - return [...applicationDataAggregated.slice(0, 3), ...applicationDataAggregated.slice(-3)]; - }, [applicationDataAggregated]); - - // Dragging state - const [dragging, setDragging] = useState(false); - const [dragStartX, setDragStartX] = useState(0); - - // Handle mouse down event - const handleMouseDown = (e: React.MouseEvent) => { - setDragging(true); - setDragStartX(e.screenX); - }; - - // Handle mouse up event - const handleMouseUp = () => { - setDragging(false); - }; - - // Handle mouse move event - const handleMouseMove = (e: MouseEvent) => { - if (dragging && containerRef.current) { - const dx = e.screenX - dragStartX; - const newScrollLeft = scrollLeft - dx; - const maxScroll = containerRef.current.scrollWidth - containerRef.current.clientWidth; - - // Ensure scrollLeft stays within bounds - const boundedScrollLeft = Math.max(0, Math.min(maxScroll, newScrollLeft)); - - // Update scroll position - containerRef.current.scrollLeft = boundedScrollLeft; - setScrollLeft(boundedScrollLeft); - setDragStartX(e.screenX); - } - }; - - useEffect(() => { - // Update container width on resize - const updateContainerWidth = () => { - setCardWidth(window.innerWidth - 40); - }; - - updateContainerWidth(); - window.addEventListener('resize', updateContainerWidth); - - // Add global event listeners - document.addEventListener('mouseup', handleMouseUp); - document.addEventListener('mousemove', handleMouseMove); - - // Cleanup - return () => { - window.removeEventListener('resize', updateContainerWidth); - document.removeEventListener('mouseup', handleMouseUp); - document.removeEventListener('mousemove', handleMouseMove); - }; - }, [dragging, dragStartX, scrollLeft]); // Add dependencies - - useEffect(() => { - setCardWidth(window.innerWidth - 40); - }, []); - - - return ( -
setScrollLeft(e.currentTarget.scrollLeft)} - onMouseDown={handleMouseDown} - onScroll={(e) => setScrollLeft(e.currentTarget.scrollLeft)} - - - > - {visibleApplications.map((application, index) => { - // Calculate scaling and translation to give a carousel effect - const distance = (index * cardWidth) - scrollLeft; - const scaleX = 0.85 + 0.15 * (1 - Math.abs(distance) / containerWidth); - const scaleY = 0.85 + 0.15 * (1 - Math.abs(distance) / containerWidth); - - return ( -
- -
- ); - })} -
- ); -}; const ApplicationCard = memo(({ application, className, width }: { application?: AggregatedDataRow, className?: string, width?: number }) => { @@ -397,23 +280,18 @@ const ApplicationCard = memo(({ application, className, width }: { application?: }} >
-
-
+
+
{numContractsString(application)}
contracts
-
+
Rank
{rank}
- {application[`${metricKey}_change_pct`] !== Infinity ? ( -
- {application[`${metricKey}_change_pct`] < 0 ? '-' : '+'}{Math.abs(application[`${metricKey}_change_pct`]).toFixed(0)}% -
- ) :
 
}
-
-
+ +
{/* {JSON.stringify( application)} */} {application.origin_keys.map((chain, index) => ( @@ -443,10 +321,20 @@ const ApplicationCard = memo(({ application, className, width }: { application?:
))}
-
-
- {prefix} - {value?.toLocaleString("en-GB")} +
+
+
+ {prefix} + {value?.toLocaleString("en-GB")} +
+
+ {application[`${metricKey}_change_pct`] !== Infinity ? ( +
+ {/* {application[`${metricKey}_change_pct`] < 0 ? '-' : '+'}{formatNumber(Math.abs(application[`${metricKey}_change_pct`]), {defaultDecimals: 1, thresholdDecimals: {base:0}})}% */} + {application[`${metricKey}_change_pct`] < 0 ? '-' : '+'}{Math.abs(application[`${metricKey}_change_pct`]).toLocaleString("en-GB",{maximumFractionDigits:0})}% +
+ ) :
 
} +
@@ -673,6 +561,8 @@ const ApplicationsTable = () => {
)} useWindowScroll + increaseViewportBy={{top:0, bottom: 400}} + overscan={50} />
{/* */} diff --git a/components/layout/GridTable.tsx b/components/layout/GridTable.tsx index ccebf5bd..f36a1400 100644 --- a/components/layout/GridTable.tsx +++ b/components/layout/GridTable.tsx @@ -379,6 +379,7 @@ export const GridTableAddressCell = ({ // size 10px font is 6px wide // size 20px font is 9px wide + const fontWidth = useMemo(() => 0.3 * fontSize + 3, [fontSize]); @@ -392,7 +393,7 @@ export const GridTableAddressCell = ({ return ( -
{ e.stopPropagation(); handleCopyAddress(address); @@ -411,6 +412,7 @@ export const GridTableAddressCell = ({ // }} style={{ fontSize: `${fontSize}px`, + fontFeatureSettings: "'tnum'", }} >