mirror of
https://github.com/jgraph/drawio-mcp.git
synced 2026-09-14 15:42:06 +08:00
Merge branch 'libavoid-two-file-cdn'
The draw.io release shipping the pure-JS two-file js/libavoid-js layout is live on viewer.diagrams.net, so the CDN now matches this code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ At startup (Node.js) or build time (Workers), the HTML is assembled. The draw.io
|
||||
|
||||
- **`app-with-deps.js`** (~319 KB, from `node_modules/@modelcontextprotocol/ext-apps`) — MCP Apps SDK browser bundle. The bundle is ESM (ends with `export { ... as App }`), so `processAppBundle()` strips the export statement and creates a local `var App = <minifiedName>` alias. This makes it safe to inline in a plain `<script>` tag inside the sandboxed iframe.
|
||||
- **`pako_deflate.min.js`** (~28 KB, from `node_modules/pako`) — for compressing XML into the `#create=` URL format.
|
||||
- **libavoid** (the obstacle-avoiding orthogonal edge router behind `routing: "libavoid"`) is **not vendored/inlined** — the HTML loads glue + base64 wasm payload + loader + shared routing core from the `viewer.diagrams.net` CDN (`js/libavoid-js/`), like drawio-elk and drawio-mermaid. The wasm rides as base64 inside `libavoid-wasm.js` and is decoded by the loader (still no fetch, so the sandbox CSP is satisfied by plain script-src). The routing math is `AvoidRouting.computeRoutes` from `libavoid-routing.js` — the canonical `drawio-dev js/libavoid-js/` artifact, byte-identical to what the draw.io editor bundles and the mcp-tool-server vendors. `buildHtml` still accepts `options.libavoidJs`/`libavoidWasmB64` to inline a local build (dev). See `vendor/libavoid/README.md`.
|
||||
- **libavoid** (the obstacle-avoiding orthogonal edge router behind `routing: "libavoid"`) is **not vendored/inlined** — the HTML loads the pure-JS router bundle (`libavoid.min.js`, a self-contained classic script that publishes `globalThis.Avoid` and parks `window.__libavoidReady` synchronously — no WASM, no fetch, no `wasm-unsafe-eval` needed) + shared routing core from the `viewer.diagrams.net` CDN (`js/libavoid-js/`), like drawio-elk and drawio-mermaid. Requires the draw.io release that ships the pure-JS two-file layout there. The routing math is `AvoidRouting.computeRoutes` from `libavoid-routing.js` — the canonical `drawio-dev js/libavoid-js/` artifact, byte-identical to what the draw.io editor bundles and the mcp-tool-server vendors. `buildHtml` accepts `options.libavoidJs` to inline a local build instead (dev). See `vendor/libavoid/README.md`.
|
||||
|
||||
Loaded from the CDN (not inlined):
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@ const pakoDeflateJs = fs.readFileSync(
|
||||
// out of generated-html.js (~1.5 MB), lets browsers cache them cross-session,
|
||||
// and keeps their versions in sync with the viewer per draw.io release.
|
||||
|
||||
// libavoid (WASM edge router) is NOT inlined — the HTML loads glue + base64
|
||||
// wasm payload + loader + shared routing core from the viewer.diagrams.net
|
||||
// CDN, like drawio-elk and drawio-mermaid (see buildHtml's libavoidBlock).
|
||||
// Keeps ~700 KB out of generated-html.js.
|
||||
// libavoid (edge router) is NOT inlined — the HTML loads the pure-JS router
|
||||
// bundle + shared routing core from the viewer.diagrams.net CDN, like
|
||||
// drawio-elk and drawio-mermaid (see buildHtml's libavoidBlock).
|
||||
// Keeps ~850 KB out of generated-html.js.
|
||||
|
||||
// Read the shared XML reference (single source of truth for all prompts)
|
||||
const xmlReference = fs.readFileSync(
|
||||
|
||||
@@ -81,11 +81,11 @@ if (process.env.MERMAID_PATH)
|
||||
console.log("Inlining local drawio-mermaid from", process.env.MERMAID_PATH);
|
||||
}
|
||||
|
||||
// libavoid (WASM obstacle-avoiding edge router, powers the routing: "libavoid"
|
||||
// pass) is NOT inlined — the HTML loads glue + base64 wasm payload + loader +
|
||||
// shared routing core from the viewer.diagrams.net CDN, like drawio-elk and
|
||||
// libavoid (obstacle-avoiding edge router, powers the routing: "libavoid"
|
||||
// pass) is NOT inlined — the HTML loads the pure-JS router bundle + shared
|
||||
// routing core from the viewer.diagrams.net CDN, like drawio-elk and
|
||||
// drawio-mermaid (see buildHtml's libavoidBlock). Cached cross-session,
|
||||
// version-synced with each draw.io release, and drops ~700 KB from the HTML.
|
||||
// version-synced with each draw.io release, and drops ~850 KB from the HTML.
|
||||
// The URLs are ETag-versioned at startup (and daily on the HTTP transport)
|
||||
// so a release busts the CDN's 30-day browser cache immediately — see
|
||||
// libavoid-versions.js; on a failed startup check the plain URLs are used.
|
||||
|
||||
@@ -5,22 +5,23 @@
|
||||
// browser cache can keep serving a stale router for up to a month. The HTML
|
||||
// is delivered to the host as an MCP resource and rendered in a sandboxed
|
||||
// iframe, so the scripts cannot be served same-origin from this server -
|
||||
// but the URLs can be VERSIONED: the Node server HEADs the four files at
|
||||
// but the URLs can be VERSIONED: the Node server HEADs the two files at
|
||||
// startup (and daily on the HTTP transport - see index.js), derives a
|
||||
// version token from each ETag, and bakes it into the script URLs as
|
||||
// ?v=<token>. Clients keep long-lived caching per URL, and a release
|
||||
// changes the ETag -> changes the URL -> every client re-fetches exactly
|
||||
// once. Best effort: a failed or 404 HEAD (the path only exists once a
|
||||
// draw.io release ships js/libavoid-js/) leaves the plain URL, which is the
|
||||
// previous behavior with its 30-day worst-case staleness.
|
||||
// once. Best effort: a failed or 404 HEAD leaves the plain URL, which is
|
||||
// the previous behavior with its 30-day worst-case staleness.
|
||||
|
||||
const CDN_BASE = "https://viewer.diagrams.net/js/libavoid-js/";
|
||||
|
||||
// Fixed load order: glue -> wasm payload -> loader -> routing core.
|
||||
// Fixed load order: pure-JS router bundle -> routing core. Requires the
|
||||
// draw.io release that ships the pure-JS two-file js/libavoid-js/ layout
|
||||
// (the bundle publishes globalThis.Avoid and parks window.__libavoidReady
|
||||
// itself); the earlier WASM layout's libavoid-wasm.js/libavoid-loader.js
|
||||
// no longer exist and are not referenced.
|
||||
export const LIBAVOID_FILES = [
|
||||
"libavoid.min.js",
|
||||
"libavoid-wasm.js",
|
||||
"libavoid-loader.js",
|
||||
"libavoid-routing.js"
|
||||
];
|
||||
|
||||
@@ -45,7 +46,7 @@ async function versionToken(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* The four libavoid script URLs in load order, ETag-versioned where the CDN
|
||||
* The two libavoid script URLs in load order, ETag-versioned where the CDN
|
||||
* answered. Safe to call repeatedly (each call re-checks). `prev` (the
|
||||
* previous result) keeps a file's last-known URL when its check fails: a
|
||||
* transient HEAD failure is not a version change and must not downgrade a
|
||||
|
||||
@@ -11,12 +11,12 @@ import { buildTagMap, searchShapes } from "../../shared/shape-search.js";
|
||||
/**
|
||||
* Build the self-contained HTML string that renders diagrams.
|
||||
* The MCP Apps App class and pako deflate are inlined. The draw.io viewer,
|
||||
* drawio-elk, drawio-mermaid, and libavoid (glue + wasm payload + loader +
|
||||
* routing core from js/libavoid-js/) load from the viewer.diagrams.net CDN
|
||||
* drawio-elk, drawio-mermaid, and libavoid (pure-JS router bundle + routing
|
||||
* core from js/libavoid-js/) load from the viewer.diagrams.net CDN
|
||||
* by default — cached cross-session and kept in version-sync with each
|
||||
* draw.io release. Pass viewerJs/elkJs/mermaidJs (or libavoidJs +
|
||||
* libavoidWasmB64) to inline a local build instead (for dev — see
|
||||
* VIEWER_PATH/ELK_PATH/MERMAID_PATH in index.js).
|
||||
* draw.io release. Pass viewerJs/elkJs/mermaidJs/libavoidJs to inline a
|
||||
* local build instead (for dev — see VIEWER_PATH/ELK_PATH/MERMAID_PATH in
|
||||
* index.js).
|
||||
*
|
||||
* @param {string} appWithDepsJs - The processed MCP Apps SDK bundle (exports stripped, App alias added).
|
||||
* @param {string} pakoDeflateJs - The pako deflate browser bundle.
|
||||
@@ -24,9 +24,8 @@ import { buildTagMap, searchShapes } from "../../shared/shape-search.js";
|
||||
* @param {object} [options] - Optional configuration.
|
||||
* @param {string} [options.viewerJs] - If provided, inlines this JS instead of loading viewer-static.min.js from CDN.
|
||||
* @param {string} [options.elkJs] - If provided, inlines this drawio-elk bundle instead of loading it from CDN. Defines `var ELK` (engine) plus `ElkLayout`/`ElkAdapter`/`ElkApplier` (the mxGraph bridge + postLayout facade), consumed by drawio-mermaid and the postLayout pass. Loaded before mermaid.
|
||||
* @param {string} [options.libavoidJs] - If provided (together with libavoidWasmB64), inlines this processed libavoid-js bundle (exports stripped, `globalThis.AvoidLib` aliased, loader patched to read `globalThis.__LIBAVOID_WASM_BINARY` — see processLibavoidBundle) with libavoid-routing.js (defines `globalThis.AvoidRouting`) appended, instead of loading the libavoid block from the CDN. Powers the `routing: "libavoid"` edge-routing pass.
|
||||
* @param {string} [options.libavoidWasmB64] - The libavoid.wasm binary, base64-encoded, for the inline case. Decoded to a Uint8Array and handed to the Emscripten module as `wasmBinary` so the router instantiates with no fetch.
|
||||
* @param {string[]} [options.libavoidUrls] - The four libavoid script URLs in load order (glue, wasm payload, loader, routing core), typically ETag-versioned (libavoid-versions.js) so a draw.io release busts the browser cache immediately. Defaults to the plain CDN URLs; ignored when libavoidJs/libavoidWasmB64 inline a local build.
|
||||
* @param {string} [options.libavoidJs] - If provided, inlines this libavoid-js bundle (the drawio-dev js/libavoid-js/libavoid.min.js pure-JS build, used as-is — a self-contained classic script that publishes `globalThis.Avoid` and parks `window.__libavoidReady` synchronously) with libavoid-routing.js (defines `globalThis.AvoidRouting`) appended, instead of loading the libavoid block from the CDN. Powers the `routing: "libavoid"` edge-routing pass.
|
||||
* @param {string[]} [options.libavoidUrls] - The two libavoid script URLs in load order (pure-JS router bundle, routing core), typically ETag-versioned (libavoid-versions.js) so a draw.io release busts the browser cache immediately. Defaults to the plain CDN URLs; ignored when libavoidJs inlines a local build.
|
||||
* @param {string} [options.buildId] - Build identifier (git SHA + timestamp). Exposed as window.__DRAWIO_BUILD in the iframe.
|
||||
* @returns {string} Self-contained HTML string.
|
||||
*/
|
||||
@@ -35,69 +34,39 @@ export function buildHtml(appWithDepsJs, pakoDeflateJs, mermaidJs, options)
|
||||
var viewerJs = (options && options.viewerJs) || null;
|
||||
var elkJs = (options && options.elkJs) || null;
|
||||
var libavoidJs = (options && options.libavoidJs) || null;
|
||||
var libavoidWasmB64 = (options && options.libavoidWasmB64) || null;
|
||||
var buildId = (options && options.buildId) || "unknown";
|
||||
|
||||
// libavoid-js (WASM orthogonal edge router). The glue defines
|
||||
// globalThis.AvoidLib; the loader below decodes the base64 wasm to a
|
||||
// Uint8Array, hands it to the Emscripten module as wasmBinary (no fetch —
|
||||
// see processLibavoidBundle), and kicks off async instantiation. The
|
||||
// resulting promise is parked on window.__libavoidReady (resolving to the
|
||||
// Avoid namespace) so the routing pass can await it on demand; load runs
|
||||
// eagerly at page init so the wasm is usually warm by the time a
|
||||
// routing:"libavoid" diagram finishes rendering. Failures degrade
|
||||
// gracefully — applyRouting just skips and the diagram renders unrouted.
|
||||
var libavoidLoader =
|
||||
'function __libavoidB64ToBytes(b64)\n' +
|
||||
'{\n' +
|
||||
' var bin = atob(b64);\n' +
|
||||
' var bytes = new Uint8Array(bin.length);\n' +
|
||||
' for (var i = 0; i < bin.length; i++) { bytes[i] = bin.charCodeAt(i); }\n' +
|
||||
' return bytes;\n' +
|
||||
'}\n' +
|
||||
'(function()\n' +
|
||||
'{\n' +
|
||||
' if (typeof AvoidLib === "undefined") { console.error("[libavoid] AvoidLib global missing"); return; }\n' +
|
||||
' try { globalThis.__LIBAVOID_WASM_BINARY = __libavoidB64ToBytes(window.__LIBAVOID_WASM_B64); }\n' +
|
||||
' catch (e) { console.error("[libavoid] base64 decode failed:", e); return; }\n' +
|
||||
' window.__libavoidReady = AvoidLib.load().then(function()\n' +
|
||||
' {\n' +
|
||||
' window.Avoid = AvoidLib.getInstance();\n' +
|
||||
' return window.Avoid;\n' +
|
||||
' }).catch(function(e)\n' +
|
||||
' {\n' +
|
||||
' console.error("[libavoid] wasm load failed; routing disabled:", (e && e.message), e);\n' +
|
||||
' return null;\n' +
|
||||
' });\n' +
|
||||
'})();\n';
|
||||
|
||||
// libavoid-js (obstacle-avoiding orthogonal edge router). libavoid.min.js
|
||||
// is the pure-JS Emscripten build from drawio-dev js/libavoid-js/ — a
|
||||
// self-contained classic script (no WASM, no fetch) that publishes
|
||||
// globalThis.Avoid and parks window.__libavoidReady (an already-resolved
|
||||
// promise: the Avoid namespace, or null when init failed) synchronously on
|
||||
// execution. The routing pass awaits __libavoidReady on demand; failures
|
||||
// degrade gracefully — applyRouting just skips and the diagram renders
|
||||
// unrouted.
|
||||
//
|
||||
// ETag-versioned URLs from the Node server (options.libavoidUrls, see
|
||||
// libavoid-versions.js) bust the CDN's 30-day browser cache exactly when a
|
||||
// draw.io release changes a file. The plain-URL default below applies only
|
||||
// when the option is omitted — the Worker build (build-html.js) and tests;
|
||||
// the Node server always passes the option (a failed version check
|
||||
// surfaces as plain URLs from libavoid-versions.js, not from this array).
|
||||
// Fixed order: glue -> wasm payload -> loader -> routing core.
|
||||
// Fixed order: pure-JS router bundle -> routing core.
|
||||
var libavoidSrcs = (options && options.libavoidUrls) || [
|
||||
'https://viewer.diagrams.net/js/libavoid-js/libavoid.min.js',
|
||||
'https://viewer.diagrams.net/js/libavoid-js/libavoid-wasm.js',
|
||||
'https://viewer.diagrams.net/js/libavoid-js/libavoid-loader.js',
|
||||
'https://viewer.diagrams.net/js/libavoid-js/libavoid-routing.js'
|
||||
];
|
||||
|
||||
var libavoidBlock = (libavoidJs && libavoidWasmB64)
|
||||
? '<!-- libavoid-js (inlined WASM edge router). Defines globalThis.AvoidLib; the loader feeds the base64 wasm in as wasmBinary (no fetch). Powers the routing:"libavoid" pass. -->\n' +
|
||||
' <script>' + libavoidJs + '</script>\n' +
|
||||
' <script>\n' +
|
||||
' window.__LIBAVOID_WASM_B64 = "' + libavoidWasmB64 + '";\n' +
|
||||
' ' + libavoidLoader +
|
||||
' </script>'
|
||||
: '<!-- libavoid-js (WASM edge router) + shared routing core from the viewer.diagrams.net CDN,\n' +
|
||||
' like drawio-elk/drawio-mermaid: cached cross-session, version-synced with each draw.io\n' +
|
||||
' release, and byte-identical to what the draw.io editor bundles. The wasm rides as\n' +
|
||||
' base64 inside libavoid-wasm.js; libavoid-loader.js decodes it and parks\n' +
|
||||
' window.__libavoidReady — still no fetch, so the sandbox CSP is satisfied by plain\n' +
|
||||
' script-src. Fixed order: glue -> wasm payload -> loader -> routing core. -->\n' +
|
||||
var libavoidBlock = libavoidJs
|
||||
? '<!-- libavoid-js (inlined local build: pure-JS router bundle + routing core). Publishes\n' +
|
||||
' globalThis.Avoid and parks window.__libavoidReady synchronously. Powers the\n' +
|
||||
' routing:"libavoid" pass. -->\n' +
|
||||
' <script>' + libavoidJs + '</script>'
|
||||
: '<!-- libavoid-js (pure-JS edge router bundle) + shared routing core from the viewer.diagrams.net\n' +
|
||||
' CDN, like drawio-elk/drawio-mermaid: cached cross-session, version-synced with each\n' +
|
||||
' draw.io release, and byte-identical to what the draw.io editor bundles. The bundle\n' +
|
||||
' publishes globalThis.Avoid and parks window.__libavoidReady synchronously — no WASM,\n' +
|
||||
' no fetch, so the sandbox CSP is satisfied by plain script-src. -->\n' +
|
||||
libavoidSrcs.map(function(u)
|
||||
{
|
||||
return ' <script src="' + u + '"></script>';
|
||||
@@ -1900,7 +1869,7 @@ function routeWithLibavoid(graph, Avoid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Async wrapper around routeWithLibavoid: await wasm readiness, then route.
|
||||
* Async wrapper around routeWithLibavoid: await router readiness, then route.
|
||||
* Degrades gracefully — calls onDone(false) (diagram stays unrouted) when
|
||||
* libavoid isn't present, failed to load, or routing throws.
|
||||
*
|
||||
@@ -4451,7 +4420,7 @@ function finalizeStreamingView(xml, opts)
|
||||
// Routing without ELK: the author's (LLM's) vertex positions are kept;
|
||||
// libavoid only computes obstacle-avoiding paths for the edges. The
|
||||
// normal fit above already ran (no postLayout), and routing doesn't move
|
||||
// vertices, so no extra camera move is needed. applyRouting awaits wasm
|
||||
// vertices, so no extra camera move is needed. applyRouting awaits router
|
||||
// readiness internally, so no rAF gate here.
|
||||
runRoutingPass(streamGraph);
|
||||
}
|
||||
@@ -6401,39 +6370,6 @@ export function processElkBundle(raw)
|
||||
"drawio-elk.min.js");
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the libavoid-js browser bundle (obstacle-avoiding orthogonal edge
|
||||
* routing — the `routing: "libavoid"` pass). Ships as ESM ending in
|
||||
* `export{X as AvoidLib}` and uses `import.meta.url` (illegal in a classic
|
||||
* <script>). Three transforms before the standard strip-and-alias:
|
||||
*
|
||||
* 1. Neutralize `import.meta.url` → "". It is only used by the Emscripten
|
||||
* module to locate the .wasm via fetch — which we bypass entirely.
|
||||
* 2. Patch the loader so the Emscripten factory receives `wasmBinary`
|
||||
* (a Uint8Array set on globalThis). The sandboxed iframe has no
|
||||
* allow-same-origin and the host CSP's connect-src forbids data: URIs,
|
||||
* so we hand the wasm bytes in directly instead of fetching them.
|
||||
* 3. Strip the ESM export and alias `globalThis.AvoidLib`.
|
||||
*/
|
||||
export function processLibavoidBundle(raw)
|
||||
{
|
||||
raw = raw.replace(/import\.meta\.url/g, '""');
|
||||
|
||||
// `this.avoidLib=await le({locateFile:ce})` →
|
||||
// `... await le({locateFile:ce,wasmBinary:(globalThis.__LIBAVOID_WASM_BINARY||void 0)})`
|
||||
var patched = raw.replace(
|
||||
/(await\s+\w+\(\{\s*locateFile\s*:\s*\w+)\s*\}\)/,
|
||||
"$1,wasmBinary:(globalThis.__LIBAVOID_WASM_BINARY||void 0)})"
|
||||
);
|
||||
|
||||
if (patched === raw)
|
||||
{
|
||||
throw new Error("Could not patch libavoid loader to accept wasmBinary (loader signature changed?)");
|
||||
}
|
||||
|
||||
return stripEsmExportsAndAlias(patched, { AvoidLib: "AvoidLib" }, "libavoid.min.js");
|
||||
}
|
||||
|
||||
// ── Diagram validation ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
+22
-22
@@ -1,32 +1,34 @@
|
||||
# libavoid (app server) — loaded from the CDN
|
||||
|
||||
Obstacle-avoiding orthogonal **edge routing** (the `routing: "libavoid"`
|
||||
pass). Since the draw.io release that ships `js/libavoid-js/` on
|
||||
`viewer.diagrams.net`, the viewer HTML loads libavoid from the CDN like
|
||||
drawio-elk and drawio-mermaid — nothing is vendored here anymore:
|
||||
pass). Since the draw.io release that ships the pure-JS two-file
|
||||
`js/libavoid-js/` layout on `viewer.diagrams.net`, the viewer HTML loads
|
||||
libavoid from the CDN like drawio-elk and drawio-mermaid — nothing is
|
||||
vendored here anymore:
|
||||
|
||||
```html
|
||||
<script src="https://viewer.diagrams.net/js/libavoid-js/libavoid.min.js"></script>
|
||||
<script src="https://viewer.diagrams.net/js/libavoid-js/libavoid-wasm.js"></script>
|
||||
<script src="https://viewer.diagrams.net/js/libavoid-js/libavoid-loader.js"></script>
|
||||
<script src="https://viewer.diagrams.net/js/libavoid-js/libavoid-routing.js"></script>
|
||||
```
|
||||
|
||||
(glue → base64 wasm payload → loader → shared routing core; see
|
||||
`buildHtml`'s `libavoidBlock` in `src/shared.js`). The wasm rides as base64
|
||||
inside `libavoid-wasm.js` and is decoded by the loader — still **no
|
||||
`fetch`**, so the sandboxed iframe (no `allow-same-origin`, no `data:` in
|
||||
`connect-src`) is satisfied by plain `script-src`. The loader parks
|
||||
`window.__libavoidReady` (resolves to the `Avoid` namespace, or `null` on
|
||||
failure); the routing core defines `globalThis.AvoidRouting` — the same
|
||||
canonical artifact the draw.io editor bundles into `extensions.min.js` and
|
||||
the mcp-tool-server vendors (`mcp-tool-server/vendor/libavoid/`, which stays
|
||||
vendored: it runs server-side in Node and ships in the npm package).
|
||||
(pure-JS router bundle → shared routing core; see `buildHtml`'s
|
||||
`libavoidBlock` in `src/shared.js`, ETag-versioned URLs from
|
||||
`src/libavoid-versions.js`). `libavoid.min.js` is the pure-JS Emscripten
|
||||
build — a self-contained classic script, **no WASM and no `fetch`**, so the
|
||||
sandboxed iframe (no `allow-same-origin`, no `data:` in `connect-src`) is
|
||||
satisfied by plain `script-src` — no `'wasm-unsafe-eval'` needed. On
|
||||
execution it publishes `globalThis.Avoid` and synchronously parks
|
||||
`window.__libavoidReady` (an already-resolved promise: the `Avoid`
|
||||
namespace, or `null` on an init failure); the routing core defines
|
||||
`globalThis.AvoidRouting` — the same canonical `drawio-dev js/libavoid-js/`
|
||||
artifact the draw.io editor bundles and the mcp-tool-server vendors
|
||||
(`mcp-tool-server/vendor/libavoid/`, which stays vendored: it runs
|
||||
server-side in Node and ships in the npm package).
|
||||
|
||||
`buildHtml` still supports inlining a local build instead (pass
|
||||
`options.libavoidJs` — a `processLibavoidBundle`-processed glue with
|
||||
`libavoid-routing.js` appended — plus `options.libavoidWasmB64`), e.g. for
|
||||
testing unreleased libavoid changes.
|
||||
`options.libavoidJs` — the drawio-dev `libavoid.min.js` artifact as-is, with
|
||||
`libavoid-routing.js` appended), e.g. for testing unreleased libavoid
|
||||
changes.
|
||||
|
||||
Remaining files:
|
||||
|
||||
@@ -37,7 +39,5 @@ Remaining files:
|
||||
- `LICENSE` — libavoid-js is LGPL-2.1-or-later (kept for reference; the
|
||||
binaries are served by the CDN, not shipped from this repo).
|
||||
|
||||
> ⚠️ WebAssembly must instantiate inside the Claude.ai MCP-app iframe,
|
||||
> which requires the host CSP to allow wasm compilation
|
||||
> (`'wasm-unsafe-eval'`) and `viewer.diagrams.net` in `script-src` (the
|
||||
> same allowance drawio-elk/drawio-mermaid already rely on).
|
||||
> ⚠️ The host CSP must allow `viewer.diagrams.net` in `script-src` — the
|
||||
> same allowance drawio-elk/drawio-mermaid already rely on.
|
||||
|
||||
+7
-5
@@ -3,9 +3,10 @@
|
||||
Obstacle-avoiding orthogonal **edge routing** for the `routing: "libavoid"`
|
||||
option on `open_drawio_xml`. Vendored from [`libavoid-js`](https://github.com/Aksem/libavoid-js).
|
||||
|
||||
Unlike the app server (which inlines the *browser* build + base64 wasm into a
|
||||
sandboxed iframe), the tool server runs in plain Node, so we vendor the **node
|
||||
build** and let it read the wasm from disk — no patching, no base64.
|
||||
Unlike the app server (which loads the pure-JS *browser* bundle from the
|
||||
viewer.diagrams.net CDN into a sandboxed iframe), the tool server runs in
|
||||
plain Node, so we vendor the **node build** and let it read the wasm from
|
||||
disk — no patching, no base64.
|
||||
|
||||
Artifacts:
|
||||
|
||||
@@ -15,8 +16,9 @@ Artifacts:
|
||||
- `libavoid.wasm` — the Emscripten binary (~492 KB). Loaded by path:
|
||||
`await AvoidLib.load(join(__dirname, "vendor/libavoid/libavoid.wasm"))`.
|
||||
- `libavoid-routing.js` — the shared routing core (`globalThis.AvoidRouting`:
|
||||
`computeRoutes` incl. fixed-connection-point pins and jettySize stub
|
||||
checkpoints, plus the pure geometry helpers). **Verbatim copy** — the
|
||||
`computeRoutes` incl. fixed-connection-point pins, jetty checkpoints, and
|
||||
dangling-end free-point routing, plus the pure geometry helpers).
|
||||
**Verbatim copy** — the
|
||||
canonical source is `drawio-dev src/main/webapp/js/libavoid-js/
|
||||
libavoid-routing.js` (the same artifact the draw.io editor bundles and the
|
||||
app server loads from the CDN); copy it over when it changes there. At
|
||||
|
||||
+50
-15
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* libavoid-routing.js — the draw.io routing core for the vendored libavoid-js
|
||||
* WASM router. Hand-authored (NOT regenerated by ant vendor-libavoid, like
|
||||
* libavoid-loader.js); it never changes with the upstream version.
|
||||
* libavoid-routing.js — the draw.io routing core over the libavoid solver.
|
||||
* Hand-authored; engine-agnostic (takes the `Avoid` namespace as a parameter,
|
||||
* so it works whether the host loads libavoid as WASM or pure JS) and never
|
||||
* changes with the libavoid build.
|
||||
*
|
||||
* CANONICAL SOURCE: this file (drawio-dev js/libavoid-js/). drawio-mcp vendors
|
||||
* verbatim copies (mcp-app-server/vendor/libavoid/ and
|
||||
@@ -216,6 +217,12 @@
|
||||
*/
|
||||
AvoidRouting.cappedJetty = function(jetty, constraint, from, to)
|
||||
{
|
||||
// A dangling (free-point) end has no bounds to measure the gap against.
|
||||
if (from == null || to == null)
|
||||
{
|
||||
return jetty;
|
||||
}
|
||||
|
||||
var DIR = AvoidRouting.DIR;
|
||||
var d = (constraint != null) ? constraint.dir : 0;
|
||||
var dgap = (d == DIR.down) ? to.y - (from.y + from.h) :
|
||||
@@ -323,11 +330,17 @@
|
||||
* @param {Array<{id:string,x:number,y:number,w:number,h:number}>} vertices
|
||||
* Obstacles, in ABSOLUTE coordinates. Shapes enclosing a terminal
|
||||
* of a routed edge are dropped (filterEnclosing).
|
||||
* @param {Array<{id,source,target,sourceConstraint?,targetConstraint?,
|
||||
* @param {Array<{id,source,target,sourcePoint?,targetPoint?,
|
||||
* sourceConstraint?,targetConstraint?,
|
||||
* sourcePoints?,targetPoints?,sourceSides?,targetSides?,
|
||||
* sourceJetty?,targetJetty?}>} edges
|
||||
* Edges referencing vertex ids (endpoints not both known vertices
|
||||
* are skipped). *Constraint = {x,y,dir} (constraintForPoint): a
|
||||
* Edges referencing vertex ids. An end may instead be a DANGLING free
|
||||
* point — sourcePoint/targetPoint {x,y} in absolute coords, used when
|
||||
* that terminal has no vertex (an unconnected endpoint): it routes to
|
||||
* a plain Point ConnEnd (no shape, no direction), matching the
|
||||
* drag-preview, and takes no constraint/pin/mask/jetty. An edge is
|
||||
* skipped only when an end is neither a vertex nor a free point.
|
||||
* *Constraint = {x,y,dir} (constraintForPoint): a
|
||||
* fixed connection point routed via a directed ShapeConnectionPin;
|
||||
* absent => the endpoint floats at the shape centre. *Points = an
|
||||
* array of {x,y,dir} candidate anchors (draw.io's snapToPoint —
|
||||
@@ -423,8 +436,20 @@
|
||||
// constraint (pinned exit/entry) wins over both, and a snap-point set
|
||||
// wins over a mask (the snapToPoint branch of
|
||||
// updateFloatingTerminalPoint runs before any mask enforcement).
|
||||
function makeEnd(vid, b, constraint, sides, points)
|
||||
function makeEnd(vid, b, constraint, sides, points, freePoint)
|
||||
{
|
||||
// Dangling end: no vertex bounds, just a free point (the edge's
|
||||
// unconnected endpoint). Route to a plain Point ConnEnd — no shape,
|
||||
// no direction — exactly as the warm-session drag preview does, so a
|
||||
// committed edge with an unconnected end matches its live preview.
|
||||
if (b == null)
|
||||
{
|
||||
var dp = new Avoid.Point(freePoint.x, freePoint.y);
|
||||
var dce = new Avoid.ConnEnd(dp);
|
||||
dp.delete();
|
||||
return dce;
|
||||
}
|
||||
|
||||
if (constraint != null && constraint.dir != null && shapeRefs[vid] != null)
|
||||
{
|
||||
pinClass++;
|
||||
@@ -481,10 +506,16 @@
|
||||
return ce;
|
||||
}
|
||||
|
||||
// Endpoint anchor: the fixed connection point, else the shape centre —
|
||||
// the same point the ConnEnd routes from.
|
||||
function anchor(b, constraint)
|
||||
// Endpoint anchor: the free point for a dangling end, else the fixed
|
||||
// connection point, else the shape centre — the point the ConnEnd routes
|
||||
// from.
|
||||
function anchor(b, constraint, freePoint)
|
||||
{
|
||||
if (b == null)
|
||||
{
|
||||
return freePoint;
|
||||
}
|
||||
|
||||
return (constraint != null) ?
|
||||
{x: b.x + constraint.x * b.w, y: b.y + constraint.y * b.h} :
|
||||
{x: b.x + b.w / 2, y: b.y + b.h / 2};
|
||||
@@ -518,15 +549,19 @@
|
||||
var sb = bounds[e.source];
|
||||
var tb = bounds[e.target];
|
||||
|
||||
if (sb == null || tb == null)
|
||||
// An end is either a known vertex (sb/tb) or a dangling free point
|
||||
// (e.sourcePoint / e.targetPoint, absolute coords) for an unconnected
|
||||
// endpoint. Skip only when an end is neither.
|
||||
if ((sb == null && e.sourcePoint == null) ||
|
||||
(tb == null && e.targetPoint == null))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ConnRef copies the ConnEnds, so free them after construction
|
||||
// (they are not owned by the router).
|
||||
var se = makeEnd(e.source, sb, e.sourceConstraint, e.sourceSides, e.sourcePoints);
|
||||
var de = makeEnd(e.target, tb, e.targetConstraint, e.targetSides, e.targetPoints);
|
||||
var se = makeEnd(e.source, sb, e.sourceConstraint, e.sourceSides, e.sourcePoints, e.sourcePoint);
|
||||
var de = makeEnd(e.target, tb, e.targetConstraint, e.targetSides, e.targetPoints, e.targetPoint);
|
||||
var conn = new Avoid.ConnRef(router, se, de);
|
||||
se.delete();
|
||||
de.delete();
|
||||
@@ -543,8 +578,8 @@
|
||||
// after the first solve (see below).
|
||||
var sourceJetty = AvoidRouting.cappedJetty(e.sourceJetty, e.sourceConstraint, sb, tb);
|
||||
var targetJetty = AvoidRouting.cappedJetty(e.targetJetty, e.targetConstraint, tb, sb);
|
||||
var sa = anchor(sb, e.sourceConstraint);
|
||||
var ta = anchor(tb, e.targetConstraint);
|
||||
var sa = anchor(sb, e.sourceConstraint, e.sourcePoint);
|
||||
var ta = anchor(tb, e.targetConstraint, e.targetPoint);
|
||||
|
||||
// A pinned anchor buried inside ANOTHER obstacle (a shape dragged
|
||||
// over the terminal) puts the whole route into libavoid's escape /
|
||||
|
||||
Reference in New Issue
Block a user