Visualizer work
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

159 lines
5.5 KiB

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Visualizer — Interactive Mermaid Flowchart</title>
<!-- Tailwind for quick layout (fine for dev) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- html2canvas for export (no integrity attr) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js" crossorigin="anonymous"></script>
<style>
:root {
--bg: #f8fafb;
--card: #ffffff;
--muted: #64748b;
--accent: #2563eb;
--accent2: #0ea5a4;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}
body { background: var(--bg); color: #0f172a; -webkit-font-smoothing: antialiased; }
.page { max-width: 1200px; margin: 36px auto; padding: 20px; }
.card { background: var(--card); border-radius: 12px; box-shadow: 0 6px 18px rgba(2,6,23,0.06); padding: 18px; }
</style>
</head>
<body>
<main class="page">
<header class="flex items-start justify-between gap-4 mb-6">
<div>
<h1 class="text-2xl font-semibold">Visualizer — Interactive Flowchart (Mermaid)</h1>
<p class="text-sm text-gray-600 mt-1">Paste Mermaid source → instantly render a flowchart beside it.</p>
</div>
<div class="controls flex items-center gap-3 export-area">
<button id="exportPngBtn">Export PNG</button>
<button id="copyBtn" class="small-btn">Copy Source</button>
<button id="rerenderBtn" class="small-btn">Re-render</button>
<select id="themeSelect" class="small-btn">
<option value="neutral" selected>Neutral</option>
<option value="default">Default</option>
<option value="forest">Forest</option>
<option value="dark">Dark</option>
<option value="solarized">Solarized</option>
</select>
</div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Diagram -->
<section class="card">
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-medium">Flowchart</h2>
<div class="legend">
<span class="font-semibold">Legend:</span> Stage A = metadata • Stage B = buffering
</div>
</div>
<div id="mermaidContainer" class="bg-white rounded-lg p-4 min-h-[200px]">
<div id="mermaidRender" class="mermaid"></div>
</div>
</section>
<!-- Source -->
<aside class="card">
<h3 class="font-semibold mb-2">Mermaid Source (editable)</h3>
<pre id="mermaidSource" class="mermaid-source" contenteditable="true" spellcheck="false"></pre>
<p class="note mt-2">Press <kbd>Ctrl</kbd>+<kbd>Enter</kbd> or click “Re-render”.</p>
</aside>
</div>
</main>
<!-- Mermaid v11 as ES module -->
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
// ✅ initial diagram (starts with 'flowchart TB')
const initialText = `flowchart TB
A[Start] --> B[Load resources]
B --> C{Ready?}
C -->|Yes| D[Initialize app]
C -->|No| E([Wait])
D --> F([Done])`;
const src = document.getElementById("mermaidSource");
const renderArea = document.getElementById("mermaidRender");
// set source text content
src.textContent = initialText;
async function initMermaid(theme = "neutral") {
mermaid.initialize({
startOnLoad: false,
theme,
securityLevel: "loose",
flowchart: { curve: "linear" }
});
await mermaid.run(); // ensures theme & config loaded
}
async function renderMermaid(text) {
const code = (text || "").trim();
if (!code.startsWith("flowchart") && !code.startsWith("graph")) {
renderArea.innerHTML = `<pre style="color:#ef4444;">❌ No valid diagram type detected. Make sure your text begins with 'flowchart TB' or 'graph LR'.</pre>`;
return;
}
try {
const { svg } = await mermaid.render("mmdDiagram", code);
renderArea.innerHTML = svg;
} catch (err) {
renderArea.innerHTML = `<pre style="color:#ef4444;">Error: ${err.message}</pre>`;
console.error(err);
}
}
// ✅ initialize Mermaid first
await initMermaid();
// ✅ render initial diagram
await renderMermaid(initialText);
// Re-render button
document.getElementById("rerenderBtn").addEventListener("click", () => {
const text = src.innerText.trim();
renderMermaid(text);
});
// Theme selector
document.getElementById("themeSelect").addEventListener("change", async e => {
await initMermaid(e.target.value);
await renderMermaid(src.innerText.trim());
});
// Copy source
document.getElementById("copyBtn").addEventListener("click", async () => {
await navigator.clipboard.writeText(src.innerText.trim());
alert("Copied Mermaid source!");
});
// Export PNG
document.getElementById("exportPngBtn").addEventListener("click", async () => {
const node = document.getElementById("mermaidContainer");
const canvas = await html2canvas(node, { scale: 2, backgroundColor: null });
const a = document.createElement("a");
a.href = canvas.toDataURL("image/png");
a.download = "flowchart.png";
a.click();
});
// Ctrl+Enter shortcut
document.addEventListener("keydown", e => {
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
e.preventDefault();
renderMermaid(src.innerText.trim());
}
});
</script>
</body>
</html>