From e5206b1f4503a53c283b946134768650f18a2997 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 03:35:59 +0000 Subject: [PATCH] feat: Add download as image feature Adds a new button to the UI that allows users to download the current mind map view as a PNG image. This is implemented using the 'html2canvas' and 'file-saver' libraries. A 'Download' button has been added to the header. When clicked, it captures the ReactFlow component's DOM element, converts it to a canvas, and then saves it as 'mind-map.png'. --- package-lock.json | 57 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ src/App.jsx | 31 +++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index a8ceddf..b72de8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.0", "dependencies": { "axios": "^1.10.0", + "file-saver": "^2.0.5", + "html2canvas": "^1.4.1", "react": "^19.1.0", "react-dom": "^19.1.0", "reactflow": "^11.11.4" @@ -1860,6 +1862,15 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2033,6 +2044,15 @@ "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==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2539,6 +2559,12 @@ "node": ">=16.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2771,6 +2797,19 @@ "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==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3380,6 +3419,15 @@ "node": ">=8" } }, + "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==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -3460,6 +3508,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vite": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.4.tgz", diff --git a/package.json b/package.json index 3e2eede..5bb1b6b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ }, "dependencies": { "axios": "^1.10.0", + "file-saver": "^2.0.5", + "html2canvas": "^1.4.1", "react": "^19.1.0", "react-dom": "^19.1.0", "reactflow": "^11.11.4" diff --git a/src/App.jsx b/src/App.jsx index 01dd0a6..8cbb6cb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,6 +9,8 @@ import ReactFlow, { } from 'reactflow'; import 'reactflow/dist/style.css'; import axios from 'axios'; +import html2canvas from 'html2canvas'; +import { saveAs } from 'file-saver'; // 🧠 Initial State & Helpers const initialNodes = []; @@ -112,6 +114,19 @@ export default function App() { const [prompt, setPrompt] = useState(''); const [loading, setLoading] = useState(false); const stopGenerationRef = useRef(false); + const reactFlowWrapper = useRef(null); + + const onDownload = () => { + if (reactFlowWrapper.current) { + html2canvas(reactFlowWrapper.current).then((canvas) => { + canvas.toBlob((blob) => { + if (blob) { + saveAs(blob, 'mind-map.png'); + } + }); + }); + } + }; const handleSubmit = async () => { if (!prompt.trim()) return; @@ -217,11 +232,25 @@ export default function App() { > ⏹ Stop + {/* 🧠 ReactFlow Canvas */} -
+