diff --git a/package-lock.json b/package-lock.json index d686eed..2f90d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "html2canvas": "^1.4.1", "lucide-react": "^0.379.0", "next": "14.2.3", "react": "^18", @@ -1779,6 +1780,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2013,6 +2022,14 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -3435,6 +3452,18 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5597,6 +5626,14 @@ "node": ">=6" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5858,6 +5895,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/victory-vendor": { "version": "36.9.2", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", diff --git a/package.json b/package.json index 8c31055..c4462bc 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "html2canvas": "^1.4.1", "lucide-react": "^0.379.0", "next": "14.2.3", "react": "^18", diff --git a/src/app/page.tsx b/src/app/page.tsx index cc2588f..2340967 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,18 +4,47 @@ import Menu from "../components/menu"; import Graph from "../components/graph"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { Theme, allThemes } from "@/lib/theme"; +import html2canvas from "html2canvas"; /** * The issue in the original code is that the `allThemes["sunset"]` object does not match the expected `Theme` type. * To fix this, we need to ensure that the `allThemes["sunset"]` object conforms to the `Theme` type. */ -// Start of Selection export default function Home() { const [padding, setPadding] = useState(64); const [theme, setTheme] = useState(() => allThemes["sunset"] as Theme); + + const chartRef = useRef(null); + + const handleExport = async () => { + if (chartRef.current) { + const canvas = await html2canvas(chartRef.current, { + useCORS: true, + allowTaint: true, + backgroundColor: null, + scale: 2, + logging: true, + onclone: (document: Document) => { + Array.from( + document + .querySelector(".cc") + ?.querySelectorAll("*") || [] + ).forEach((e) => { + let existingStyle = e.getAttribute("style") || ""; + e.setAttribute("style", existingStyle + "; font-family: Inter, sans-serif !important"); + }); + }, + }); + const link = document.createElement("a"); + link.href = canvas.toDataURL("image/png"); + link.download = "chart.png"; + link.click(); + } + }; + return (
- -
- Made by Firecrawl + +
+ Made by{" "} + + Firecrawl 🔥 +
diff --git a/src/components/graph.tsx b/src/components/graph.tsx index 0f20bb8..2cf7e13 100644 --- a/src/components/graph.tsx +++ b/src/components/graph.tsx @@ -2,9 +2,10 @@ import { Theme } from "@/lib/theme"; import { AreaChart, Color } from "@tremor/react"; -import { useState } from "react"; +import { useState, useRef } from "react"; +import html2canvas from "html2canvas"; -export default function Graph({ padding, theme }: { padding: number, theme: Theme }) { +export default function Graph({ padding, theme, chartRef }: { padding: number, theme: Theme, chartRef: React.RefObject }) { const mockchartdata = [ { date: "Apr 15", @@ -75,15 +76,18 @@ export default function Graph({ padding, theme }: { padding: number, theme: Them const [chartData, setChartData] = useState(mockchartdata); const maxStars = Math.max(...mockchartdata.map((data) => data.Stars)); const [maxValue, setMaxValue] = useState(maxStars); + + return (
-
+
diff --git a/src/components/menu.tsx b/src/components/menu.tsx index ed943ee..ebbafb7 100644 --- a/src/components/menu.tsx +++ b/src/components/menu.tsx @@ -14,11 +14,13 @@ export default function Menu({ setPadding, theme, setTheme, + handleExport, }: { padding: number; setPadding: (padding: number) => void; theme: Theme; setTheme: (theme: Theme) => void; + handleExport: () => void; }) { return (
@@ -63,7 +65,7 @@ export default function Menu({ -