import React, { useState, useRef, useEffect } from "react"; import { GitGraph, Plus, Upload, Download, Globe, Type, Columns, Maximize2, Minimize2, FileImage, FileCode2, Eye, AlertTriangle, FileText, Copy, } from "lucide-react"; import ToolLayout from "../components/ToolLayout"; import CodeMirrorEditor from "../components/CodeMirrorEditor"; import SEO from "../components/SEO"; import RelatedTools from "../components/RelatedTools"; import ReactFlowEditor from "../components/diagram/ReactFlowEditor"; import FullscreenAdBanner from "../components/FullscreenAdBanner"; import { toPng } from "html-to-image"; import { generateMermaidFromGraph } from "../utils/mermaidGenerator"; const DIAGRAM_TEMPLATES = { 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: "e2-5", source: "2", target: "5", sourceHandle: "bottom", targetHandle: "top", }, ], }, 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 [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(() => window.innerWidth < 1024 ? "editor" : "split", ); const [isFullscreen, setIsFullscreen] = useState(false); const [error, setError] = useState(null); const [fetchUrl, setFetchUrl] = useState(""); const [fetching, setFetching] = useState(false); const [selectedTemplate, setSelectedTemplate] = useState("flowchart"); const reactFlowWrapper = useRef(null); const fileInputRef = useRef(null); // Sync JSON text input to Graph data useEffect(() => { try { const parsed = JSON.parse(code); if (parsed.nodes && parsed.edges) { setGraphData(parsed); setError(null); } } catch (err) { setError(err.message); } }, [code]); 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]) { setGraphData(DIAGRAM_TEMPLATES[template]); setCode(JSON.stringify(DIAGRAM_TEMPLATES[template], null, 2)); } }; const handleFileUpload = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { setCode(e.target.result); setActiveTab("create"); }; reader.onerror = () => setError("Failed to read file"); reader.readAsText(file); e.target.value = ""; // Reset input }; const handleFetchFromURL = async () => { if (!fetchUrl.trim()) return; setFetching(true); try { let url = fetchUrl.trim(); if ( url.includes("github.com") && !url.includes("raw.githubusercontent.com") ) { url = url .replace("github.com", "raw.githubusercontent.com") .replace("/blob/", "/"); } const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const text = await response.text(); setCode(text); setActiveTab("create"); } catch (err) { setError(`Fetch failed: ${err.message}`); } finally { setFetching(false); } }; // Export Handlers (Updated for ReactFlow) const downloadFile = (content, filename, mimeType) => { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; const exportJSON = () => { downloadFile(code, "diagram.json", "application/json"); }; const exportMermaid = () => { const mermaidCode = generateMermaidFromGraph( graphData.nodes, graphData.edges, ); downloadFile(mermaidCode, "diagram.mmd", "text/plain"); }; const copyMermaid = () => { const mermaidCode = generateMermaidFromGraph( graphData.nodes, graphData.edges, ); navigator.clipboard .writeText(mermaidCode) .then(() => { // Could add toast notification here console.log("Mermaid code copied!"); }) .catch((err) => console.error("Failed to copy", err)); }; const exportPNG = () => { const flowElement = document.querySelector(".react-flow"); if (!flowElement) return; 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 ( <> {/* Input Section - Always visible */}

Get Started

{/* Tab Navigation */}
{activeTab === "create" && (
)} {activeTab === "url" && (
setFetchUrl(e.target.value)} onKeyPress={(e) => e.key === "Enter" && !fetching && handleFetchFromURL() } placeholder="https://raw.githubusercontent.com/.../diagram.mmd" className="tool-input flex-1" />
)} {activeTab === "paste" && (