diff --git a/package-lock.json b/package-lock.json
index 1bf09c01..05f49444 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,6 +43,7 @@
"dompurify": "^3.3.0",
"file-saver": "^2.0.5",
"highlight.js": "^11.11.1",
+ "html-to-image": "^1.11.13",
"html2pdf.js": "^0.12.1",
"js-beautify": "^1.15.4",
"jspdf": "^3.0.3",
@@ -22984,6 +22985,12 @@
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"license": "MIT"
},
+ "node_modules/html-to-image": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
+ "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==",
+ "license": "MIT"
+ },
"node_modules/html-webpack-plugin": {
"version": "5.6.7",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz",
diff --git a/package.json b/package.json
index eda71d92..06228b27 100755
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"dompurify": "^3.3.0",
"file-saver": "^2.0.5",
"highlight.js": "^11.11.1",
+ "html-to-image": "^1.11.13",
"html2pdf.js": "^0.12.1",
"js-beautify": "^1.15.4",
"jspdf": "^3.0.3",
diff --git a/src/pages/DiagramEditor.js b/src/pages/DiagramEditor.js
index 35e95e36..0533de90 100755
--- a/src/pages/DiagramEditor.js
+++ b/src/pages/DiagramEditor.js
@@ -1,126 +1,121 @@
import React, { useState, useRef, useEffect } from "react";
-import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
-import mermaid from "mermaid";
import {
GitGraph,
Plus,
Upload,
Download,
Globe,
- Edit3,
Type,
Columns,
Maximize2,
Minimize2,
- Check,
- Copy,
FileImage,
FileCode2,
- ZoomIn,
- ZoomOut,
- Maximize,
- FileDown,
Eye,
AlertTriangle,
+ FileText,
} from "lucide-react";
import ToolLayout from "../components/ToolLayout";
import CodeMirrorEditor from "../components/CodeMirrorEditor";
-import AdvancedURLFetch from "../components/AdvancedURLFetch";
import SEO from "../components/SEO";
import RelatedTools from "../components/RelatedTools";
-
-// Debounce helper
-const useDebounce = (value, delay) => {
- const [debouncedValue, setDebouncedValue] = useState(value);
- useEffect(() => {
- const handler = setTimeout(() => {
- setDebouncedValue(value);
- }, delay);
- return () => clearTimeout(handler);
- }, [value, delay]);
- return debouncedValue;
-};
+import ReactFlowEditor from "../components/diagram/ReactFlowEditor";
+import FullscreenAdBanner from "../components/FullscreenAdBanner";
+import { toPng } from "html-to-image";
const DIAGRAM_TEMPLATES = {
- flowchart: `flowchart TD
- A[Start] --> B{Is it?};
- B -- Yes --> C[OK];
- C --> D[Rethink];
- D --> B;
- B -- No ----> E[End];`,
- sequence: `sequenceDiagram
- participant Alice
- participant Bob
- Alice->>John: Hello John, how are you?
- loop Healthcheck
- John->>John: Fight against hypochondria
- end
- Note right of John: Rational thoughts
prevail!
- John-->>Alice: Great!
- John->>Bob: How about you?
- Bob-->>John: Jolly good!`,
- class: `classDiagram
- Animal <|-- Duck
- Animal <|-- Fish
- Animal <|-- Zebra
- Animal : +int age
- Animal : +String gender
- Animal: +isMammal()
- Animal: +mate()
- class Duck{
- +String beakColor
- +swim()
- +quack()
- }
- class Fish{
- -int sizeInFeet
- -canEat()
- }
- class Zebra{
- +bool is_wild
- +run()
- }`,
- state: `stateDiagram-v2
- [*] --> Still
- Still --> [*]
-
- Still --> Moving
- Moving --> Still
- Moving --> Crash
- Crash --> [*]`,
- er: `erDiagram
- CUSTOMER ||--o{ ORDER : places
- ORDER ||--|{ LINE-ITEM : contains
- CUSTOMER }|..|{ DELIVERY-ADDRESS : uses`,
- gantt: `gantt
- title A Gantt Diagram
- dateFormat YYYY-MM-DD
- section Section
- A task :a1, 2014-01-01, 30d
- Another task :after a1 , 20d
- section Another
- Task in sec :2014-01-12 , 12d
- another task : 24d`,
- pie: `pie title Pets adopted by volunteers
- "Dogs" : 386
- "Cats" : 85
- "Rats" : 15`,
- git: `gitGraph
- commit
- commit
- branch develop
- checkout develop
- commit
- commit
- checkout main
- merge develop
- commit
- commit`,
+ flowchart: {
+ nodes: [
+ {
+ id: "1",
+ type: "process",
+ position: { x: 250, y: 50 },
+ data: { label: "Start" },
+ },
+ {
+ id: "2",
+ type: "decision",
+ position: { x: 250, y: 150 },
+ data: { label: "Is it OK?" },
+ },
+ {
+ id: "3",
+ type: "process",
+ position: { x: 100, y: 250 },
+ data: { label: "Fix it" },
+ },
+ {
+ id: "4",
+ type: "database",
+ position: { x: 400, y: 250 },
+ data: { label: "Save to DB" },
+ },
+ {
+ id: "5",
+ type: "process",
+ position: { x: 250, y: 350 },
+ data: { label: "End" },
+ },
+ ],
+ edges: [
+ { id: "e1-2", source: "1", target: "2" },
+ {
+ id: "e2-3",
+ source: "2",
+ target: "3",
+ sourceHandle: "left",
+ label: "No",
+ },
+ {
+ id: "e2-4",
+ source: "2",
+ target: "4",
+ sourceHandle: "right",
+ label: "Yes",
+ },
+ { id: "e3-2", source: "3", target: "2", targetHandle: "left" },
+ {
+ id: "e4-5",
+ source: "4",
+ target: "5",
+ sourceHandle: "bottom",
+ targetHandle: "right",
+ },
+ ],
+ },
+ database: {
+ nodes: [
+ {
+ id: "users",
+ type: "database",
+ position: { x: 100, y: 100 },
+ data: { label: "Users Table" },
+ },
+ {
+ id: "orders",
+ type: "database",
+ position: { x: 400, y: 100 },
+ data: { label: "Orders Table" },
+ },
+ {
+ id: "process",
+ type: "process",
+ position: { x: 250, y: 250 },
+ data: { label: "Order Processor" },
+ },
+ ],
+ edges: [
+ { id: "eu-p", source: "users", target: "process", label: "Read" },
+ { id: "ep-o", source: "process", target: "orders", label: "Write" },
+ ],
+ },
};
const DiagramEditor = () => {
- const [code, setCode] = useState(DIAGRAM_TEMPLATES.flowchart);
- const debouncedCode = useDebounce(code, 500); // 500ms debounce
+ const [graphData, setGraphData] = useState(DIAGRAM_TEMPLATES.flowchart);
+ const [code, setCode] = useState(
+ JSON.stringify(DIAGRAM_TEMPLATES.flowchart, null, 2),
+ );
const [activeTab, setActiveTab] = useState("create");
const [viewMode, setViewMode] = useState(() =>
@@ -132,66 +127,34 @@ const DiagramEditor = () => {
const [fetching, setFetching] = useState(false);
const [selectedTemplate, setSelectedTemplate] = useState("flowchart");
- const [svgContent, setSvgContent] = useState("");
- const svgContainerRef = useRef(null);
+ const reactFlowWrapper = useRef(null);
const fileInputRef = useRef(null);
- // Initialize Mermaid
+ // Sync JSON text input to Graph data
useEffect(() => {
- mermaid.initialize({
- startOnLoad: false,
- theme: "default", // We can sync this with dark mode later
- securityLevel: "loose",
- fontFamily:
- '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
- });
- }, []);
-
- // Render diagram when debounced code changes
- useEffect(() => {
- const renderDiagram = async () => {
- try {
- setError(null); // Clear previous errors
- if (!debouncedCode.trim()) {
- setSvgContent("");
- return;
- }
-
- // Validate syntax first
- const isValid = await mermaid.parse(debouncedCode, {
- suppressErrors: true,
- });
-
- if (isValid) {
- const id = `mermaid-${Date.now()}`;
- const { svg } = await mermaid.render(id, debouncedCode);
- setSvgContent(svg);
- } else {
- // If parse returns false but doesn't throw, it's still invalid
- setError("Syntax error in diagram code");
- }
- } catch (err) {
- console.error("Mermaid parsing error:", err);
- // Extract the most readable part of the error
- let errorMessage = "Syntax error";
- if (err.message) {
- errorMessage = err.message.split("\n")[0] || "Syntax error";
- } else if (typeof err === "string") {
- errorMessage = err.split("\n")[0];
- }
- setError(errorMessage);
+ try {
+ const parsed = JSON.parse(code);
+ if (parsed.nodes && parsed.edges) {
+ setGraphData(parsed);
+ setError(null);
}
- };
+ } catch (err) {
+ setError(err.message);
+ }
+ }, [code]);
- renderDiagram();
- }, [debouncedCode]);
+ const handleGraphChange = (newGraphData) => {
+ setGraphData(newGraphData);
+ setCode(JSON.stringify(newGraphData, null, 2));
+ };
// Input Handlers
const handleTemplateChange = (e) => {
const template = e.target.value;
setSelectedTemplate(template);
if (DIAGRAM_TEMPLATES[template]) {
- setCode(DIAGRAM_TEMPLATES[template]);
+ setGraphData(DIAGRAM_TEMPLATES[template]);
+ setCode(JSON.stringify(DIAGRAM_TEMPLATES[template], null, 2));
}
};
@@ -234,7 +197,7 @@ const DiagramEditor = () => {
}
};
- // Export Handlers
+ // Export Handlers (Updated for ReactFlow)
const downloadFile = (content, filename, mimeType) => {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
@@ -247,41 +210,33 @@ const DiagramEditor = () => {
URL.revokeObjectURL(url);
};
- const exportSVG = () => {
- if (!svgContent) return;
- downloadFile(svgContent, "diagram.svg", "image/svg+xml");
+ const exportJSON = () => {
+ downloadFile(code, "diagram.json", "application/json");
};
const exportPNG = () => {
- if (!svgContent) return;
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- const img = new Image();
+ const flowElement = document.querySelector(".react-flow");
+ if (!flowElement) return;
- // Create blob URL from SVG
- const svg = new Blob([svgContent], { type: "image/svg+xml;charset=utf-8" });
- const DOMURL = window.URL || window.webkitURL || window;
- const url = DOMURL.createObjectURL(svg);
-
- img.onload = () => {
- canvas.width = img.width * 2; // High DPI
- canvas.height = img.height * 2;
- ctx.fillStyle = "white"; // Background
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
-
- const pngUrl = canvas.toDataURL("image/png");
- const a = document.createElement("a");
- a.download = "diagram.png";
- a.href = pngUrl;
- a.click();
- DOMURL.revokeObjectURL(url);
- };
- img.src = url;
- };
-
- const exportCode = () => {
- downloadFile(code, "diagram.mmd", "text/plain");
+ toPng(flowElement, {
+ backgroundColor: "#ffffff",
+ width: flowElement.offsetWidth,
+ height: flowElement.offsetHeight,
+ style: {
+ width: "100%",
+ height: "100%",
+ transform: "translate(0, 0)",
+ },
+ })
+ .then((dataUrl) => {
+ const a = document.createElement("a");
+ a.setAttribute("download", "diagram.png");
+ a.setAttribute("href", dataUrl);
+ a.click();
+ })
+ .catch((error) => {
+ setError("Failed to export PNG: " + error.message);
+ });
};
return (
@@ -298,38 +253,59 @@ const DiagramEditor = () => {
description="Create diagrams as code using Mermaid.js with live preview and multi-format export."
icon={GitGraph}
>
- {/* Input Tabs */}
+ {/* Input Section - Always visible */}