From 1aa54662d6d1f2debc57454155837696e53dd07b Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Fri, 1 Mar 2024 01:06:09 +0600 Subject: [PATCH] feat: implement ai roadmap download --- package.json | 2 + pnpm-lock.yaml | 14 +++ .../GenerateRoadmap/GenerateRoadmap.css | 5 ++ .../GenerateRoadmap/GenerateRoadmap.tsx | 85 ++++++++++++++----- 4 files changed, 84 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index e8343741f..d4e87dab9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "astro": "^4.4.0", "astro-compress": "^2.2.10", "clsx": "^2.1.0", + "dom-to-image": "^2.6.0", "dracula-prism": "^2.1.16", "jose": "^5.2.2", "js-cookie": "^3.0.5", @@ -57,6 +58,7 @@ "devDependencies": { "@playwright/test": "^1.41.2", "@tailwindcss/typography": "^0.5.10", + "@types/dom-to-image": "^2.6.7", "@types/js-cookie": "^3.0.6", "@types/prismjs": "^1.26.3", "csv-parser": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 727b581e5..40919589e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: clsx: specifier: ^2.1.0 version: 2.1.0 + dom-to-image: + specifier: ^2.6.0 + version: 2.6.0 dracula-prism: specifier: ^2.1.16 version: 2.1.16 @@ -106,6 +109,9 @@ devDependencies: '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.10(tailwindcss@3.4.1) + '@types/dom-to-image': + specifier: ^2.6.7 + version: 2.6.7 '@types/js-cookie': specifier: ^3.0.6 version: 3.0.6 @@ -1624,6 +1630,10 @@ packages: '@types/ms': 0.7.34 dev: false + /@types/dom-to-image@2.6.7: + resolution: {integrity: sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==} + dev: true + /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: false @@ -2703,6 +2713,10 @@ packages: entities: 4.5.0 dev: false + /dom-to-image@2.6.0: + resolution: {integrity: sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==} + dev: false + /domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} dev: false diff --git a/src/components/GenerateRoadmap/GenerateRoadmap.css b/src/components/GenerateRoadmap/GenerateRoadmap.css index fc5a1d577..d30ca465a 100644 --- a/src/components/GenerateRoadmap/GenerateRoadmap.css +++ b/src/components/GenerateRoadmap/GenerateRoadmap.css @@ -1,3 +1,8 @@ +@font-face { + font-family: 'balsamiq'; + src: url('/fonts/balsamiq.woff2'); +} + svg text tspan { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/src/components/GenerateRoadmap/GenerateRoadmap.tsx b/src/components/GenerateRoadmap/GenerateRoadmap.tsx index 83f622384..72a80f082 100644 --- a/src/components/GenerateRoadmap/GenerateRoadmap.tsx +++ b/src/components/GenerateRoadmap/GenerateRoadmap.tsx @@ -22,8 +22,7 @@ export function GenerateRoadmap() { const [hasSubmitted, setHasSubmitted] = useState(false); const [isLoading, setIsLoading] = useState(false); const [roadmapTopic, setRoadmapTopic] = useState(''); - const [nodes, setNodes] = useState([]); - const [edges, setEdges] = useState([]); + const [generatedRoadmap, setGeneratedRoadmap] = useState(''); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); @@ -79,23 +78,7 @@ export function GenerateRoadmap() { } }, onStreamEnd: async (result) => { - const { nodes, edges } = generateAIRoadmapFromText(result); - setNodes( - nodes.map((node) => ({ - ...node, - - // To reset the width and height of the node - // so that it can be calculated based on the content in the editor - width: undefined, - height: undefined, - style: { - ...node.style, - width: undefined, - height: undefined, - }, - })), - ); - setEdges(edges); + setGeneratedRoadmap(result); }, }); @@ -105,11 +88,25 @@ export function GenerateRoadmap() { const editGeneratedRoadmap = async () => { pageProgressMessage.set('Redirecting to Editor'); + const { nodes, edges } = generateAIRoadmapFromText(generatedRoadmap); + const { response, error } = await httpPost<{ roadmapId: string; }>(`${import.meta.env.PUBLIC_API_URL}/v1-edit-ai-generated-roadmap`, { title: roadmapTopic, - nodes, + nodes: nodes.map((node) => ({ + ...node, + + // To reset the width and height of the node + // so that it can be calculated based on the content in the editor + width: undefined, + height: undefined, + style: { + ...node.style, + width: undefined, + height: undefined, + }, + })), edges, }); @@ -122,6 +119,46 @@ export function GenerateRoadmap() { window.location.href = `${import.meta.env.PUBLIC_EDITOR_APP_URL}/${response.roadmapId}`; }; + const downloadGeneratedRoadmap = async () => { + pageProgressMessage.set('Downloading Roadmap'); + + const node = document.getElementById('roadmap-container'); + if (!node) { + toast.error('Something went wrong'); + return; + } + + // Append a watermark to the bottom right of the image + const watermark = document.createElement('div'); + watermark.className = 'flex justify-end absolute bottom-4 right-4 gap-2'; + watermark.innerHTML = ` + + roadmap.sh + + `; + node.insertAdjacentElement('afterbegin', watermark); + + try { + const domtoimage = (await import('dom-to-image')).default; + const dataUrl = await domtoimage.toJpeg(node, { + bgcolor: 'white', + quality: 1, + }); + node?.removeChild(watermark); + const link = document.createElement('a'); + link.download = `${roadmapTopic}-roadmap.jpg`; + link.href = dataUrl; + link.click(); + + pageProgressMessage.set(''); + } catch (error) { + console.error(error); + toast.error('Something went wrong'); + } + }; + if (!hasSubmitted) { return (
- @@ -198,7 +238,8 @@ export function GenerateRoadmap() {
);