dioxus_mdx/components/
mermaid.rs1use std::sync::atomic::{AtomicU64, Ordering};
8
9use dioxus::prelude::*;
10
11static MERMAID_ID: AtomicU64 = AtomicU64::new(0);
13
14#[derive(Props, Clone, PartialEq)]
16pub struct MermaidDiagramProps {
17 pub code: String,
19}
20
21#[component]
29pub fn MermaidDiagram(props: MermaidDiagramProps) -> Element {
30 let id = use_signal(|| format!("mermaid-{}", MERMAID_ID.fetch_add(1, Ordering::Relaxed)));
31
32 #[allow(unused_variables)]
33 let code = props.code.clone();
34
35 #[cfg(target_arch = "wasm32")]
36 {
37 let element_id = id().clone();
38 use_effect(move || {
39 let element_id = element_id.clone();
40 let code = code.clone();
41 spawn(async move {
42 let code_json = serde_json::to_string(&code).unwrap_or_default();
43 let js = format!(
44 r#"
45 (async function() {{
46 const elId = {element_id_json};
47 const code = {code_json};
48
49 // Load mermaid.js from CDN once
50 if (!window.mermaid) {{
51 await new Promise((resolve, reject) => {{
52 if (document.querySelector('script[data-mermaid-cdn]')) {{
53 // Another instance is already loading — wait for it
54 const check = setInterval(() => {{
55 if (window.mermaid) {{ clearInterval(check); resolve(); }}
56 }}, 50);
57 return;
58 }}
59 const s = document.createElement('script');
60 s.src = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js';
61 s.setAttribute('data-mermaid-cdn', '1');
62 s.onload = () => {{
63 window.mermaid.initialize({{ startOnLoad: false }});
64 resolve();
65 }};
66 s.onerror = reject;
67 document.head.appendChild(s);
68 }});
69 }}
70
71 // Detect DaisyUI theme → mermaid theme
72 function mermaidTheme() {{
73 const dt = document.documentElement.getAttribute('data-theme') || '';
74 return (dt === 'light') ? 'default' : 'dark';
75 }}
76
77 // Render helper
78 async function render() {{
79 const el = document.getElementById(elId);
80 if (!el) return;
81 // Reset element so mermaid re-parses it
82 el.removeAttribute('data-processed');
83 el.innerHTML = code;
84 try {{
85 await window.mermaid.run({{
86 nodes: [el],
87 suppressErrors: true,
88 }});
89 }} catch (_) {{}}
90 }}
91
92 // Initial render with the right theme
93 window.mermaid.initialize({{ startOnLoad: false, theme: mermaidTheme() }});
94 await render();
95
96 // Re-render on theme change
97 const observer = new MutationObserver(async () => {{
98 window.mermaid.initialize({{ startOnLoad: false, theme: mermaidTheme() }});
99 await render();
100 }});
101 observer.observe(document.documentElement, {{
102 attributes: true,
103 attributeFilter: ['data-theme'],
104 }});
105 }})();
106 "#,
107 element_id_json = serde_json::to_string(&element_id).unwrap_or_default(),
108 code_json = code_json,
109 );
110 let _ = document::eval(&js);
111 });
112 });
113 }
114
115 rsx! {
116 div { class: "my-6 flex justify-center",
117 pre {
118 class: "mermaid",
119 id: "{id()}",
120 "{props.code}"
121 }
122 }
123 }
124}