From 7e2edbf8a19145a766ae8a77f578e273bef9914b Mon Sep 17 00:00:00 2001 From: "dayuan.jiang" Date: Sun, 11 Oct 2026 13:04:19 +0900 Subject: [PATCH] refactor(canvas): getDrawioSrc takes the editor's source, and CanvasStage can be given one --- components/canvas/canvas-stage.tsx | 17 +++++--- lib/drawio/drawio-config.ts | 63 +++++++++++++++++++----------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/components/canvas/canvas-stage.tsx b/components/canvas/canvas-stage.tsx index c7f52fd1..93c616e9 100644 --- a/components/canvas/canvas-stage.tsx +++ b/components/canvas/canvas-stage.tsx @@ -3,7 +3,12 @@ import { DrawioFrame } from "@/components/canvas/drawio-frame" import { useLocale } from "@/components/canvas/locale-context" import { useDiagram } from "@/contexts/diagram-context" import { useDictionary } from "@/hooks/use-dictionary" -import { DRAWIO_CSS, getDrawioSrc } from "@/lib/drawio/drawio-config" +import { + DRAWIO_CSS, + type DrawioSource, + getDrawioSrc, + webDrawioSource, +} from "@/lib/drawio/drawio-config" import { keepCenter, refitIfRecent } from "@/lib/drawio/editor-bridge" import { i18n, type Locale } from "@/lib/i18n/config" import { STORAGE_KEYS } from "@/lib/storage" @@ -28,6 +33,7 @@ export function CanvasStage({ style, hideOverlays = false, overlay, + drawioSource, waitForSavedLocale = false, }: { className?: string @@ -36,6 +42,9 @@ export function CanvasStage({ hideOverlays?: boolean /** Rendered over the canvas once draw.io is ready */ overlay?: (size: CanvasSize) => React.ReactNode + /** Where the editor comes from; none: the web app's own copy (the MCP + * shell passes what its server configured) */ + drawioSource?: DrawioSource /** Web app: the page is about to switch to the language picked last * time (useSavedLocale in the workspace); do not load draw.io in the * URL's language meanwhile */ @@ -74,11 +83,10 @@ export function CanvasStage({ return } } - const isElectron = !!(window as { electronAPI?: unknown }).electronAPI // The theme class is set before the page renders; the settings store // may not have read it yet const dark = document.documentElement.classList.contains("dark") - setSrc(getDrawioSrc({ lang, isElectron, dark })) + setSrc(getDrawioSrc(drawioSource ?? webDrawioSource(lang), dark)) }, [lang]) // A new iframe starts over: draw.io reports load and access again @@ -94,8 +102,7 @@ export function CanvasStage({ if (editorAccess !== false || !src) return const startedDark = new URL(src).searchParams.get("dark") === "1" if (startedDark === isDark) return - const isElectron = !!(window as { electronAPI?: unknown }).electronAPI - setSrc(getDrawioSrc({ lang, isElectron, dark: isDark })) + setSrc(getDrawioSrc(drawioSource ?? webDrawioSource(lang), isDark)) }, [isDark, editorAccess, lang, src]) useEffect(() => { diff --git a/lib/drawio/drawio-config.ts b/lib/drawio/drawio-config.ts index 162a7f4b..431e748f 100644 --- a/lib/drawio/drawio-config.ts +++ b/lib/drawio/drawio-config.ts @@ -6,28 +6,49 @@ import { BLANK_MXFILE } from "@/packages/mcp-server/src/pages.ts" const EXTERNAL_DRAWIO_URL = process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "" /** - * URL of the draw.io editor iframe. By default this is the copy bundled in - * public/drawio, served from our own origin. + * Where the editor comes from and how it starts. The web app fills it from + * its build settings (webDrawioSource); the MCP's canvas shell from the + * config its server writes into the page. */ -export function getDrawioSrc({ - lang, - isElectron, - dark, -}: { - lang: Locale - isElectron: boolean - dark: boolean -}): string { - // index.html: Next.js does not serve directory indexes - const base = - EXTERNAL_DRAWIO_URL || - `${window.location.origin}${getAssetUrl("/drawio/index.html")}` - const url = new URL(base) +export interface DrawioSource { + /** The editor page: the bundled copy's index.html on this origin, or an + * external draw.io (cross-origin, limited features) */ + editorUrl: string + /** draw.io theme (its ui parameter); none: draw.io's default and the + * user's own pick */ + ui?: string + /** Language in draw.io's naming ("zh-tw"); none: draw.io's own pick */ + lang?: string + /** No calls to external services (the desktop app) */ + offline?: boolean +} + +/** + * The web app's editor: the copy in public/drawio, served from our own + * origin (index.html, as Next.js serves no directory indexes), or + * NEXT_PUBLIC_DRAWIO_BASE_URL; the simple theme in the page's language. + */ +export function webDrawioSource(lang: Locale): DrawioSource { + const isElectron = !!(window as { electronAPI?: unknown }).electronAPI + return { + editorUrl: EXTERNAL_DRAWIO_URL || getAssetUrl("/drawio/index.html"), + ui: "simple", + // draw.io names Traditional Chinese "zh-tw" + lang: lang === "zh-Hant" ? "zh-tw" : lang, + offline: isElectron && !EXTERNAL_DRAWIO_URL, + } +} + +/** URL of the draw.io editor iframe */ +export function getDrawioSrc(source: DrawioSource, dark: boolean): string { + const url = new URL(source.editorUrl, window.location.href) const params: Record = { embed: "1", proto: "json", configure: "1", - ui: "simple", + } + if (source.ui) params.ui = source.ui + Object.assign(params, { spin: "0", libraries: "0", noSaveBtn: "1", @@ -36,11 +57,9 @@ export function getDrawioSrc({ // draw.io's own dark mode (switched in place later when the editor // can be driven directly) dark: dark ? "1" : "0", - // draw.io names Traditional Chinese "zh-tw" - lang: lang === "zh-Hant" ? "zh-tw" : lang, - } - // No calls to external services from the desktop app - if (isElectron && !EXTERNAL_DRAWIO_URL) params.offline = "1" + }) + if (source.lang) params.lang = source.lang + if (source.offline) params.offline = "1" for (const [key, value] of Object.entries(params)) { url.searchParams.set(key, value) }