nandi/oripublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

DrawioView.tsx · 102 lines · 3.0 KBTypeScript Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1/**
2 * DrawioView renders a .drawio / .dio diagram with the diagrams.net embed
3 * viewer, loaded in an iframe and fed the XML over its postMessage protocol
4 * (`init` → `load`). There is no offline renderer for draw.io's shape
5 * library that ori could bundle, so an isolated sandbox without access to
6 * embed.diagrams.net gets a notice and the XML source instead.
7 *
8 * @example
9 * <DrawioView xml={content} onUnavailable={() => setMode("source")} />
10 */
11
12import { useEffect, useRef, useState } from "react";
13
14/** Read-only embed: lightbox chrome (zoom, pages, layers), no editor UI. */
15export const DRAWIO_EMBED_URL =
16 "https://embed.diagrams.net/?embed=1&proto=json&spin=1&lightbox=1&nav=1&chrome=0";
17
18/** How long to wait for the viewer's `init` before declaring it unreachable. */
19const DEFAULT_TIMEOUT_MS = 8000;
20
21interface DrawioViewProps {
22 xml: string;
23 /** Override for tests; defaults to DRAWIO_EMBED_URL. */
24 embedUrl?: string;
25 /** Override for tests; defaults to 8 s. */
26 timeoutMs?: number;
27}
28
29type ViewerStatus = "loading" | "ready" | "unavailable";
30
31export function DrawioView({
32 xml,
33 embedUrl = DRAWIO_EMBED_URL,
34 timeoutMs = DEFAULT_TIMEOUT_MS,
35}: DrawioViewProps) {
36 const frameRef = useRef<HTMLIFrameElement>(null);
37 const [status, setStatus] = useState<ViewerStatus>("loading");
38
39 useEffect(() => {
40 const frame = frameRef.current;
41 if (!frame) {
42 return;
43 }
44 const timer = setTimeout(() => {
45 setStatus((current) => (current === "ready" ? current : "unavailable"));
46 }, timeoutMs);
47
48 const onMessage = (event: MessageEvent) => {
49 if (event.source !== frame.contentWindow) {
50 return;
51 }
52 const message = parseViewerMessage(event.data);
53 if (message?.event === "init") {
54 frame.contentWindow?.postMessage(
55 JSON.stringify({ action: "load", xml, autosave: 0 }),
56 "*",
57 );
58 }
59 if (message?.event === "init" || message?.event === "load") {
60 clearTimeout(timer);
61 setStatus("ready");
62 }
63 };
64 window.addEventListener("message", onMessage);
65 return () => {
66 clearTimeout(timer);
67 window.removeEventListener("message", onMessage);
68 };
69 }, [xml, timeoutMs]);
70
71 return (
72 <div className={`drawio-view drawio-view-${status}`}>
73 {status === "unavailable" && (
74 <p className="drawio-notice">
75 The diagram viewer (embed.diagrams.net) is not reachable from this
76 browser; use the <strong>Source</strong> view to read the diagram XML.
77 </p>
78 )}
79 {status === "loading" && <p className="pane-empty">loading viewer</p>}
80 <iframe
81 ref={frameRef}
82 className="drawio-frame"
83 title="draw.io diagram"
84 src={embedUrl}
85 sandbox="allow-scripts allow-same-origin allow-popups"
86 hidden={status === "unavailable"}
87 />
88 </div>
89 );
90}
91
92/** parseViewerMessage decodes one embed-protocol frame; anything else is null. */
93function parseViewerMessage(data: unknown): { event?: string } | null {
94 if (typeof data !== "string" || !data.startsWith("{")) {
95 return null;
96 }
97 try {
98 return JSON.parse(data) as { event?: string };
99 } catch {
100 return null;
101 }
102}