diff --git a/domToImage.ts b/domToImage.ts new file mode 100644 index 0000000..9658067 --- /dev/null +++ b/domToImage.ts @@ -0,0 +1,9 @@ +declare module 'dom-to-image-more' { + const domToImage: { + toBlob(node: HTMLElement): Promise; + toPng(node: HTMLElement): Promise; + toJpeg(node: HTMLElement, options?: any): Promise; + toSvg(node: HTMLElement): Promise; + }; + export default domToImage; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aca927a..5cbb534 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "dom-to-image": "^2.6.0", + "dom-to-image-more": "^3.6.3", "flowbite": "^3.1.2", "flowbite-react": "^0.12.6", "html2canvas": "^1.4.1", @@ -30,6 +32,7 @@ }, "devDependencies": { "@eslint/js": "^9.22.0", + "@types/dom-to-image": "^2.6.7", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", @@ -3320,6 +3323,12 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/dom-to-image": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.7.tgz", + "integrity": "sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -4264,6 +4273,16 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dom-to-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz", + "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==" + }, + "node_modules/dom-to-image-more": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-3.6.3.tgz", + "integrity": "sha512-cKl5UEV8JsXf+LoTifUJsbr60wGZ1K6AF8/8YTtoaBpacAPZaqIm79jvMiVdUB05FOzgAcAZypxWNDI6GtA84w==" + }, "node_modules/electron-to-chromium": { "version": "1.5.187", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", diff --git a/package.json b/package.json index 2063449..87adb8d 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "dom-to-image": "^2.6.0", + "dom-to-image-more": "^3.6.3", "flowbite": "^3.1.2", "flowbite-react": "^0.12.6", "html2canvas": "^1.4.1", @@ -32,6 +34,7 @@ }, "devDependencies": { "@eslint/js": "^9.22.0", + "@types/dom-to-image": "^2.6.7", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", diff --git a/src/components/Charts/ChartContainer.tsx b/src/components/Charts/ChartContainer.tsx new file mode 100644 index 0000000..91d3743 --- /dev/null +++ b/src/components/Charts/ChartContainer.tsx @@ -0,0 +1,32 @@ +import { Spinner } from "flowbite-react"; +import { forwardRef, ReactElement } from "react"; +import { ResponsiveContainer } from "recharts"; + +interface ChartContainerProps { + children: ReactElement; + loading: boolean; + title?: string; +} + +const ChartContainer = forwardRef( + (props, ref) => { + return ( +
+ {props.loading ? ( +
+ +
+ ) : ( + + {props.children} + + )} + + {/* {!props.loading && } */} +
+ ); + } +); + +ChartContainer.displayName = "ChartContatiner"; +export default ChartContainer; diff --git a/src/components/Charts/Namada/NamadaChart.tsx b/src/components/Charts/Namada/NamadaChart.tsx index fd4f0ae..d04e158 100644 --- a/src/components/Charts/Namada/NamadaChart.tsx +++ b/src/components/Charts/Namada/NamadaChart.tsx @@ -73,7 +73,7 @@ function NamadaChart(props: NamadaChartProps) { diff --git a/src/components/Charts/Namada/RewardsChart.tsx b/src/components/Charts/Namada/RewardsChart.tsx index f9fa7fd..9118170 100644 --- a/src/components/Charts/Namada/RewardsChart.tsx +++ b/src/components/Charts/Namada/RewardsChart.tsx @@ -26,6 +26,7 @@ import "chartjs-adapter-date-fns"; import { format, parse } from "date-fns"; import { RefObject, useEffect, useState } from "react"; import { Line } from "react-chartjs-2"; +import ChartContainer from "../ChartContainer"; ChartJS.register( CategoryScale, @@ -266,7 +267,6 @@ const RewardChart = (props: RewardChartProps) => { return (
@@ -305,9 +305,15 @@ const RewardChart = (props: RewardChartProps) => {
-
- -
+ +
+ +
+
+ {/*

Current Staked Ratio

diff --git a/src/components/Charts/Namada/TokenEcosystem.tsx b/src/components/Charts/Namada/TokenEcosystem.tsx index 60dde2b..336f6b4 100644 --- a/src/components/Charts/Namada/TokenEcosystem.tsx +++ b/src/components/Charts/Namada/TokenEcosystem.tsx @@ -12,7 +12,6 @@ import { useResponsiveFontSize } from "../../hooks/useResponsiveFontSize"; import { DATA_URL } from "../../lib/chart/data-url"; import { getNamadaSupply } from "../../lib/chart/helpers"; import { FlattenedTokenData } from "../../lib/chart/types"; -import { Spinner } from "flowbite-react"; import { Area, AreaChart, @@ -30,6 +29,7 @@ import { SelectTrigger, SelectValue, } from "../../UI/shadcn/select"; +import ChartContainer from "../ChartContainer"; type NamadaAsset = { id: string; @@ -78,7 +78,9 @@ export default function TokenEcosystem(props: TokenEcosystemProps) { } } - fetchData(); + setTimeout(() => { + fetchData(); + }, 2000); return () => controller.abort(); }, []); @@ -113,10 +115,7 @@ export default function TokenEcosystem(props: TokenEcosystemProps) { : [props.selectedTokenId]; return ( - + {props.selectedTokenId === "all" @@ -154,90 +153,82 @@ export default function TokenEcosystem(props: TokenEcosystemProps) {
- - {loading ? ( -
- -
- ) : ( - - - {activeTokenIds.map((id, index) => ( - - - - - ))} - - - - - { - // Use billions format for Namada, thousands for others - if (props.selectedTokenId === "Namada") { - return `${(value / 1e9).toFixed(1)}B`; - } - return `${(value / 1e3).toFixed(0)}k`; - }} - /> - - ( -
- {activeTokenIds.map((id, index) => ( -
- -

{id}

-
- ))} -
- )} - /> + + + {activeTokenIds.map((id, index) => ( - + id={`${id}-gradient`} + x1="0" + y1="0" + x2="0" + y2="1" + > + + + ))} - - )} -
+ + + + + { + // Use billions format for Namada, thousands for others + if (props.selectedTokenId === "Namada") { + return `${(value / 1e9).toFixed(1)}B`; + } + return `${(value / 1e3).toFixed(0)}k`; + }} + /> + + ( +
+ {activeTokenIds.map((id, index) => ( +
+ +

{id}

+
+ ))} +
+ )} + /> + {activeTokenIds.map((id, index) => ( + + ))} + +
diff --git a/src/components/hooks/useExportDashboardAsPNG.tsx b/src/components/hooks/useExportDashboardAsPNG.tsx index 8e4a77b..40c641f 100644 --- a/src/components/hooks/useExportDashboardAsPNG.tsx +++ b/src/components/hooks/useExportDashboardAsPNG.tsx @@ -1,5 +1,6 @@ import html2canvas from "html2canvas"; import { useRef } from "react"; +import domToImage from "dom-to-image"; export const PoolsType = { default: "default", @@ -14,16 +15,12 @@ const useExportDashboardAsPNG = () => { const handleSaveToPng = async (label: string) => { try { - const canvas = await html2canvas(divChartRef.current!, { - scale: 3, // renders at 3x resolution - useCORS: true, - }); - + const blob = await domToImage.toBlob(divChartRef.current!); + if (!blob) throw new Error("Export failed: empty blob"); const link = document.createElement("a"); - link.href = canvas.toDataURL("image/png"); + link.href = URL.createObjectURL(blob); link.download = `${label}-chart.png`; link.click(); - } catch (err) { alert("Error exporting chart!"); console.error("Error saving chart: ", err);