|
| 1 | +/* eslint-disable no-console */ |
| 2 | +"use client"; |
| 3 | + |
| 4 | +import React, { useEffect, useRef } from "react"; |
| 5 | + |
| 6 | +import mermaid from "mermaid"; |
| 7 | +import { useTranslation } from "react-i18next"; |
| 8 | + |
| 9 | +import { ModalName } from "@enums/components"; |
| 10 | +import { DiagramViewerModalProps } from "@interfaces/components"; |
| 11 | + |
| 12 | +import { useModalStore } from "@store"; |
| 13 | + |
| 14 | +import { Button } from "@components/atoms"; |
| 15 | +import { Modal } from "@components/molecules"; |
| 16 | + |
| 17 | +// Mermaid Initialization (from your snippet) |
| 18 | +try { |
| 19 | + mermaid.initialize({ |
| 20 | + startOnLoad: false, |
| 21 | + securityLevel: "loose", |
| 22 | + theme: "base", |
| 23 | + logLevel: "error", |
| 24 | + flowchart: { |
| 25 | + useMaxWidth: true, |
| 26 | + htmlLabels: true, |
| 27 | + }, |
| 28 | + themeVariables: { |
| 29 | + background: "#1b1b1b", |
| 30 | + // Primary colors |
| 31 | + primaryColor: "#7FAE3C", // green-600 from your theme |
| 32 | + primaryTextColor: "white", // gray-1150 |
| 33 | + primaryBorderColor: "#7FAE3C", // green-600 |
| 34 | + |
| 35 | + // Secondary colors |
| 36 | + secondaryColor: "#ededed", // gray-300 |
| 37 | + secondaryTextColor: "#2d2d2d", // gray-1100 |
| 38 | + secondaryBorderColor: "#bec3d1", // gray-600 |
| 39 | + |
| 40 | + // Tertiary colors |
| 41 | + tertiaryColor: "#f3f3f6", // gray-200 |
| 42 | + tertiaryTextColor: "#515151", // gray-1000 |
| 43 | + tertiaryBorderColor: "#cdcdcd", // gray-550 |
| 44 | + |
| 45 | + // Line colors |
| 46 | + lineColor: "#626262", // gray-850 |
| 47 | + |
| 48 | + // Note colors |
| 49 | + noteBkgColor: "#E8FFCA", // green-200 |
| 50 | + noteTextColor: "#2d2d2d", // gray-1100 |
| 51 | + noteBorderColor: "#7FAE3C", // green-600 |
| 52 | + |
| 53 | + // Actor colors |
| 54 | + actorBkg: "#7FAE3C", // green-600 |
| 55 | + actorTextColor: "#ffffff", // white |
| 56 | + actorBorder: "#626262", // gray-850 |
| 57 | + |
| 58 | + // Other elements |
| 59 | + activationBorderColor: "#BCF870", // green-800 |
| 60 | + activationBkgColor: "#E8FFCA", // green-200 |
| 61 | + }, |
| 62 | + }); |
| 63 | +} catch (e) { |
| 64 | + console.warn("Mermaid already initialized or error during initialization:", e); |
| 65 | +} |
| 66 | + |
| 67 | +// Props type for the MermaidDiagram component |
| 68 | +type MermaidDiagramComponentProps = { |
| 69 | + code: string; |
| 70 | +}; |
| 71 | + |
| 72 | +// MermaidDiagram Component (from your snippet) |
| 73 | +const MermaidDiagram = ({ code }: MermaidDiagramComponentProps) => { |
| 74 | + const mermaidRef = useRef<HTMLDivElement>(null); |
| 75 | + |
| 76 | + useEffect(() => { |
| 77 | + if (code) { |
| 78 | + const currentRef = mermaidRef.current; |
| 79 | + if (!currentRef) return; |
| 80 | + |
| 81 | + currentRef.innerHTML = ""; |
| 82 | + |
| 83 | + let cleanCode = code.trim(); |
| 84 | + |
| 85 | + if (cleanCode.startsWith("```sequenceDiagram")) { |
| 86 | + cleanCode = cleanCode.replace(/```sequenceDiagram\s*/, "").replace(/```\s*$/, ""); |
| 87 | + } else if (!cleanCode.startsWith("sequenceDiagram")) { |
| 88 | + cleanCode = "sequenceDiagram\n" + cleanCode; |
| 89 | + } |
| 90 | + |
| 91 | + if (cleanCode.startsWith("sequenceDiagram") && !cleanCode.startsWith("sequenceDiagram\n")) { |
| 92 | + cleanCode = cleanCode.replace("sequenceDiagram", "sequenceDiagram\n"); |
| 93 | + } |
| 94 | + |
| 95 | + cleanCode = cleanCode.replace(/\n\s{4}/g, "\n"); |
| 96 | + |
| 97 | + try { |
| 98 | + const uniqueId = `mermaid-${Math.random().toString(36).substring(2, 11)}`; |
| 99 | + |
| 100 | + mermaid |
| 101 | + .render(uniqueId, cleanCode) |
| 102 | + .then((result) => { |
| 103 | + if (currentRef) { |
| 104 | + while (currentRef.firstChild) { |
| 105 | + currentRef.removeChild(currentRef.firstChild); |
| 106 | + } |
| 107 | + currentRef.innerHTML = result.svg; |
| 108 | + |
| 109 | + const svg = currentRef.querySelector("svg"); |
| 110 | + if (svg) { |
| 111 | + svg.style.backgroundColor = "#161616"; |
| 112 | + svg.style.padding = "0.5rem"; |
| 113 | + } |
| 114 | + } |
| 115 | + return result; |
| 116 | + }) |
| 117 | + .catch((error) => { |
| 118 | + console.error("Error rendering Mermaid diagram:", error); |
| 119 | + if (currentRef) { |
| 120 | + currentRef.innerHTML = `<pre>Error rendering diagram:\n${String(error)}\n\nCode:\n${cleanCode}</pre>`; |
| 121 | + } |
| 122 | + }); |
| 123 | + } catch (error) { |
| 124 | + console.error("Synchronous error during Mermaid diagram rendering:", error); |
| 125 | + if (currentRef) { |
| 126 | + currentRef.innerHTML = `<pre>Error rendering diagram:\n${String(error)}\n\nCode:\n${cleanCode}</pre>`; |
| 127 | + } |
| 128 | + } |
| 129 | + } else if (mermaidRef.current) { |
| 130 | + mermaidRef.current.innerHTML = ""; |
| 131 | + } |
| 132 | + }, [code]); |
| 133 | + |
| 134 | + if (!code) return null; |
| 135 | + |
| 136 | + return ( |
| 137 | + <div className="h-full overflow-auto rounded-xl bg-gray-1250 text-sm shadow-md"> |
| 138 | + <div className="flex h-full items-center justify-center" ref={mermaidRef}> |
| 139 | + {/* Mermaid will render the diagram here */} |
| 140 | + </div> |
| 141 | + </div> |
| 142 | + ); |
| 143 | +}; |
| 144 | + |
| 145 | +// Main DiagramViewerModal component |
| 146 | +export const DiagramViewerModal = () => { |
| 147 | + const { t } = useTranslation("modals", { keyPrefix: "diagramViewer" }); |
| 148 | + const { closeModal, data } = useModalStore(); |
| 149 | + const diagramData = data as DiagramViewerModalProps | undefined; |
| 150 | + |
| 151 | + if (!diagramData || !diagramData.content) { |
| 152 | + return null; |
| 153 | + } |
| 154 | + |
| 155 | + return ( |
| 156 | + <Modal className="h-5/6 w-4/5 max-w-6xl bg-gray-1250" name={ModalName.diagramViewer}> |
| 157 | + <div className="flex h-full flex-col"> |
| 158 | + <div className="flex-1 overflow-hidden"> |
| 159 | + <MermaidDiagram code={diagramData.content} /> |
| 160 | + </div> |
| 161 | + |
| 162 | + <div className="flex items-center justify-end gap-3 border-t border-gray-950 bg-gray-1250 px-4 py-3"> |
| 163 | + <Button |
| 164 | + ariaLabel={t("proceedButton")} |
| 165 | + className="h-12 bg-gray-1100 px-4 py-3 font-semibold" |
| 166 | + onClick={() => closeModal(ModalName.fileViewer)} |
| 167 | + variant="filled" |
| 168 | + > |
| 169 | + {t("proceedButton")} |
| 170 | + </Button> |
| 171 | + </div> |
| 172 | + </div> |
| 173 | + </Modal> |
| 174 | + ); |
| 175 | +}; |
| 176 | + |
| 177 | +// Note: The original export default MermaidDiagram; is removed as this file now exports DiagramViewerModal. |
| 178 | +// The MermaidDiagram component is now a local component within this modal file. |
0 commit comments