mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-13 13:09:52 +08:00
feat(mcp-server): add theme menu, dark mode and editable SVG to the preview
- Pass themes=1 and dark=auto to the draw.io iframe, so the Extras menu offers the Theme submenu and draw.io follows the system dark mode; the header and dialogs follow it too through prefers-color-scheme - Download dialog: new Editable SVG (.drawio.svg) format through draw.io's xmlsvg export, default name diagram-YYYY-MM-DD, Enter saves and Escape closes it and the history dialog - export_diagram accepts format "drawio.svg" and detects the .drawio.svg extension - Use system fonts instead of Google Fonts, so the page works offline with a self-hosted draw.io (DRAWIO_BASE_URL)
This commit is contained in:
@@ -3,7 +3,7 @@ let currentVersion = 0, isReady = false, pendingXml = null, lastXml = null;
|
||||
let pendingSvgExport = null;
|
||||
let pendingSvgBase = 0; // version the pending autosave was based on
|
||||
let pendingAiSvg = false;
|
||||
let pendingMcpExport = null; // 'png' or 'svg' when MCP requested export
|
||||
let pendingMcpExport = null; // 'png', 'svg' or 'xmlsvg' when MCP requested export
|
||||
let mcpExportSeq = 0; // number of the latest MCP export
|
||||
let projectionExportActive = false; // page-targeted export: showing a transient single-page projection
|
||||
let forceReload = false; // reload the server state on the next poll even if the version is unchanged
|
||||
@@ -49,7 +49,7 @@ window.addEventListener('message', (e) => {
|
||||
if (msg.message.mcpExport !== mcpExportSeq) return;
|
||||
const d = msg.data;
|
||||
const isPng = pendingMcpExport === 'png' && d.startsWith('data:image/png');
|
||||
const isSvg = pendingMcpExport === 'svg' && (d.startsWith('data:image/svg') || d.startsWith('<svg'));
|
||||
const isSvg = (pendingMcpExport === 'svg' || pendingMcpExport === 'xmlsvg') && (d.startsWith('data:image/svg') || d.startsWith('<svg'));
|
||||
if (isPng || isSvg) {
|
||||
pendingMcpExport = null;
|
||||
fetch('/api/state', {
|
||||
@@ -208,7 +208,7 @@ async function poll() {
|
||||
// draw.io would use the first page.
|
||||
const exportOpts = pendingMcpExport === 'png'
|
||||
? { action: 'export', format: 'png', scale: 2, currentPage: !extra.pageId, ...extra, mcpExport: seq }
|
||||
: { action: 'export', format: 'svg', mcpExport: seq };
|
||||
: { action: 'export', format: pendingMcpExport, mcpExport: seq };
|
||||
iframe.contentWindow.postMessage(JSON.stringify(exportOpts), '*');
|
||||
};
|
||||
if (s.exportXml) {
|
||||
@@ -246,15 +246,27 @@ const saveCancelBtn = document.getElementById('save-cancel-btn');
|
||||
const saveConfirmBtn = document.getElementById('save-confirm-btn');
|
||||
let pendingDownload = null;
|
||||
|
||||
const extMap = { drawio: '.drawio', png: '.png', svg: '.svg' };
|
||||
const extMap = { drawio: '.drawio', png: '.png', svg: '.svg', xmlsvg: '.drawio.svg' };
|
||||
|
||||
saveBtn.onclick = () => {
|
||||
if (!sessionId || !isReady) return;
|
||||
// Local date as YYYY-MM-DD, like the web app's default name
|
||||
saveFilename.value = 'diagram-' + new Date().toLocaleDateString('sv-SE');
|
||||
saveModal.classList.add('open');
|
||||
saveFilename.focus();
|
||||
saveFilename.select();
|
||||
};
|
||||
|
||||
saveFilename.onkeydown = (e) => {
|
||||
if (e.key === 'Enter' && !e.isComposing && !saveConfirmBtn.disabled) saveConfirmBtn.onclick();
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Escape') return;
|
||||
if (saveModal.classList.contains('open')) saveCancelBtn.onclick();
|
||||
if (historyModal.classList.contains('open')) cancelBtn.onclick();
|
||||
});
|
||||
|
||||
saveFormat.onchange = () => {
|
||||
saveExt.textContent = extMap[saveFormat.value] || '.drawio';
|
||||
};
|
||||
@@ -291,9 +303,10 @@ saveConfirmBtn.onclick = () => {
|
||||
pendingDownload = { format: 'png', filename };
|
||||
iframe.contentWindow.postMessage(JSON.stringify({ action: 'export', format: 'png', scale: 2, currentPage: true, dlExport: true }), '*');
|
||||
setTimeout(() => { saveConfirmBtn.disabled = false; saveConfirmBtn.textContent = 'Save'; pendingDownload = null; }, 5000);
|
||||
} else if (format === 'svg') {
|
||||
pendingDownload = { format: 'svg', filename };
|
||||
iframe.contentWindow.postMessage(JSON.stringify({ action: 'export', format: 'svg', dlExport: true }), '*');
|
||||
} else {
|
||||
// svg, or xmlsvg: an SVG with the diagram embedded, which draw.io can open again
|
||||
pendingDownload = { format, filename };
|
||||
iframe.contentWindow.postMessage(JSON.stringify({ action: 'export', format, dlExport: true }), '*');
|
||||
setTimeout(() => { saveConfirmBtn.disabled = false; saveConfirmBtn.textContent = 'Save'; pendingDownload = null; }, 5000);
|
||||
}
|
||||
};
|
||||
@@ -358,7 +371,7 @@ restoreBtn.onclick = async () => {
|
||||
body: JSON.stringify({ sessionId, id: selectedId })
|
||||
});
|
||||
if (r.ok) { cancelBtn.onclick(); await poll(); }
|
||||
else { alert('Restore failed'); }
|
||||
} catch { alert('Restore failed'); }
|
||||
else { showNotice('Restore failed. Please try again.'); }
|
||||
} catch { showNotice('Restore failed. Please try again.'); }
|
||||
restoreBtn.textContent = 'Restore';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user