@file-viewer/core 2.0.11 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/config/options.d.ts +1 -1
- package/dist/config/options.js +1 -1
- package/dist/contracts/types.d.ts +76 -1
- package/dist/headless.d.ts +3 -3
- package/dist/headless.js +2 -2
- package/dist/index.d.ts +10 -7
- package/dist/index.js +106 -49
- package/dist/lifecycle/operations.d.ts +1 -0
- package/dist/lifecycle/operations.js +65 -6
- package/dist/platform/assets.d.ts +3 -1
- package/dist/platform/assets.js +43 -6
- package/dist/registry/capabilities.d.ts +2 -2
- package/dist/registry/capabilities.js +2 -1
- package/dist/registry/formats.d.ts +20 -7
- package/dist/registry/formats.js +14 -5
- package/dist/registry/registry.d.ts +8 -1
- package/dist/registry/registry.js +29 -0
- package/dist/renderers/image.js +1 -10
- package/dist/renderers/index.d.ts +320 -2
- package/dist/renderers/index.js +27 -157
- package/dist/viewer/createViewer.js +86 -3
- package/package.json +17 -44
- package/dist/renderers/archive.d.ts +0 -2
- package/dist/renderers/archive.js +0 -547
- package/dist/renderers/archiveCache.d.ts +0 -10
- package/dist/renderers/archiveCache.js +0 -96
- package/dist/renderers/archiveFallback.d.ts +0 -7
- package/dist/renderers/archiveFallback.js +0 -166
- package/dist/renderers/archiveShared.d.ts +0 -23
- package/dist/renderers/archiveShared.js +0 -71
- package/dist/renderers/audio.d.ts +0 -8
- package/dist/renderers/audio.js +0 -219
- package/dist/renderers/cad.d.ts +0 -2
- package/dist/renderers/cad.js +0 -446
- package/dist/renderers/code.d.ts +0 -11
- package/dist/renderers/code.js +0 -233
- package/dist/renderers/data.d.ts +0 -7
- package/dist/renderers/data.js +0 -370
- package/dist/renderers/drawing.d.ts +0 -10
- package/dist/renderers/drawing.js +0 -882
- package/dist/renderers/eda.d.ts +0 -2
- package/dist/renderers/eda.js +0 -434
- package/dist/renderers/edaParser.d.ts +0 -77
- package/dist/renderers/edaParser.js +0 -569
- package/dist/renderers/email.d.ts +0 -2
- package/dist/renderers/email.js +0 -463
- package/dist/renderers/epub.d.ts +0 -2
- package/dist/renderers/epub.js +0 -331
- package/dist/renderers/geo.d.ts +0 -2
- package/dist/renderers/geo.js +0 -284
- package/dist/renderers/markdown.d.ts +0 -2
- package/dist/renderers/markdown.js +0 -83
- package/dist/renderers/model.d.ts +0 -2
- package/dist/renderers/model.js +0 -567
- package/dist/renderers/ofd.d.ts +0 -2
- package/dist/renderers/ofd.js +0 -256
- package/dist/renderers/openDocument.d.ts +0 -2
- package/dist/renderers/openDocument.js +0 -122
- package/dist/renderers/pdf.d.ts +0 -3
- package/dist/renderers/pdf.js +0 -1001
- package/dist/renderers/pdfStyles.d.ts +0 -1
- package/dist/renderers/pdfStyles.js +0 -1
- package/dist/renderers/pptx.d.ts +0 -2
- package/dist/renderers/pptx.js +0 -217
- package/dist/renderers/spreadsheet/state.d.ts +0 -80
- package/dist/renderers/spreadsheet/state.js +0 -96
- package/dist/renderers/spreadsheet/view.d.ts +0 -25
- package/dist/renderers/spreadsheet/view.js +0 -833
- package/dist/renderers/spreadsheet/worker/index.d.ts +0 -2
- package/dist/renderers/spreadsheet/worker/index.js +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +0 -73
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +0 -623
- package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +0 -2
- package/dist/renderers/spreadsheet/worker/sheetjs/color.js +0 -73
- package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/index.js +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +0 -18
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +0 -106
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +0 -11
- package/dist/renderers/spreadsheet/worker/type.d.ts +0 -57
- package/dist/renderers/spreadsheet/worker/type.js +0 -1
- package/dist/renderers/spreadsheet.d.ts +0 -3
- package/dist/renderers/spreadsheet.js +0 -929
- package/dist/renderers/typst.d.ts +0 -8
- package/dist/renderers/typst.js +0 -547
- package/dist/renderers/umd/parser.d.ts +0 -30
- package/dist/renderers/umd/parser.js +0 -408
- package/dist/renderers/umd.d.ts +0 -2
- package/dist/renderers/umd.js +0 -297
- package/dist/renderers/video.d.ts +0 -8
- package/dist/renderers/video.js +0 -108
- package/dist/renderers/wordDoc.d.ts +0 -5
- package/dist/renderers/wordDoc.js +0 -284
- package/dist/renderers/wordDocx.d.ts +0 -5
- package/dist/renderers/wordDocx.js +0 -985
- package/dist/renderers/wordDocx.worker.d.ts +0 -1
- package/dist/renderers/wordDocx.worker.js +0 -96
- package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +0 -183
- package/vendor/ofd/dltech/jbig2/ccitt.js +0 -1070
- package/vendor/ofd/dltech/jbig2/compatibility.js +0 -12
- package/vendor/ofd/dltech/jbig2/core_utils.js +0 -180
- package/vendor/ofd/dltech/jbig2/is_node.js +0 -27
- package/vendor/ofd/dltech/jbig2/jbig2.js +0 -2589
- package/vendor/ofd/dltech/jbig2/jbig2_stream.js +0 -81
- package/vendor/ofd/dltech/jbig2/primitives.js +0 -387
- package/vendor/ofd/dltech/jbig2/stream.js +0 -1348
- package/vendor/ofd/dltech/jbig2/util.js +0 -972
- package/vendor/ofd/dltech/ofd/ofd.d.ts +0 -11
- package/vendor/ofd/dltech/ofd/ofd.js +0 -100
- package/vendor/ofd/dltech/ofd/ofd_parser.js +0 -395
- package/vendor/ofd/dltech/ofd/ofd_render.js +0 -473
- package/vendor/ofd/dltech/ofd/ofd_util.js +0 -350
- package/vendor/ofd/dltech/ofd/pipeline.js +0 -26
|
@@ -1,882 +0,0 @@
|
|
|
1
|
-
import { registerFileViewerZoomProvider, unregisterFileViewerZoomProvider, } from '../features/document/dom/index.js';
|
|
2
|
-
import { createFileViewerZoomChangeEmitter } from '../features/document/zoom.js';
|
|
3
|
-
import { waitForFileViewerNextPaint } from '../output/export.js';
|
|
4
|
-
import { resolveFileViewerDrawioViewerScriptUrl } from '../platform/assets.js';
|
|
5
|
-
import { readFileViewerText } from '../source/index.js';
|
|
6
|
-
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
7
|
-
const EXCALIDRAW_OFFICIAL_TIMEOUT = 6000;
|
|
8
|
-
const DRAWIO_OFFICIAL_TIMEOUT = 6000;
|
|
9
|
-
const diagramsViewerPromises = new WeakMap();
|
|
10
|
-
const drawingStyle = `
|
|
11
|
-
.drawing-viewer{display:flex;height:100%;min-height:360px;flex-direction:column;background:#edf2f7;color:#172033}
|
|
12
|
-
.drawing-toolbar{position:sticky;top:0;z-index:2;display:flex;min-height:46px;align-items:center;justify-content:space-between;gap:16px;padding:8px 14px;border-bottom:1px solid rgba(148,163,184,.35);background:rgba(248,250,252,.92);backdrop-filter:blur(12px)}
|
|
13
|
-
.drawing-title{display:flex;min-width:0;align-items:center;gap:10px}
|
|
14
|
-
.drawing-title span{display:inline-flex;height:24px;align-items:center;justify-content:center;border-radius:6px;padding:0 8px;background:#0f766e;color:#fff;font-size:11px;font-weight:800;letter-spacing:0}
|
|
15
|
-
.drawing-title strong{overflow:hidden;color:#172033;font-size:13px;font-weight:800;text-overflow:ellipsis;white-space:nowrap}
|
|
16
|
-
.drawing-actions{display:flex;flex-shrink:0;align-items:center;gap:6px}
|
|
17
|
-
.drawing-actions button{min-width:32px;height:28px;border:1px solid rgba(100,116,139,.28);border-radius:6px;background:#fff;color:#0f172a;cursor:pointer;font-size:12px;font-weight:800}
|
|
18
|
-
.drawing-actions button:hover{border-color:rgba(15,118,110,.5);color:#0f766e}
|
|
19
|
-
.drawing-actions span{min-width:48px;color:#64748b;font-size:12px;font-weight:800;text-align:center}
|
|
20
|
-
.drawing-stage{position:relative;min-height:0;flex:1;overflow:hidden}
|
|
21
|
-
.drawing-scroll{height:100%;overflow:auto;padding:22px}
|
|
22
|
-
.drawing-canvas{width:100%;min-height:420px;transition:transform .18s ease,zoom .18s ease}
|
|
23
|
-
.drawing-canvas .drawing-svg,.drawing-canvas svg{display:block;max-width:100%;height:auto;margin:0 auto;border-radius:10px;background:#fff;box-shadow:0 18px 42px rgba(15,23,42,.12)}
|
|
24
|
-
.drawing-canvas .drawing-mxgraph{min-height:420px;overflow:hidden;border-radius:10px;background:#fff;box-shadow:0 18px 42px rgba(15,23,42,.12)}
|
|
25
|
-
.drawing-state{position:absolute;inset:0;z-index:1;display:flex;align-items:center;justify-content:center;padding:24px;color:#64748b;font-size:14px;font-weight:700;text-align:center}
|
|
26
|
-
.drawing-state[hidden]{display:none!important}
|
|
27
|
-
.drawing-state.error{color:#b42318}
|
|
28
|
-
@media (max-width:720px){.drawing-toolbar{align-items:flex-start;flex-direction:column}.drawing-actions{width:100%;justify-content:space-between}.drawing-scroll{padding:12px}}
|
|
29
|
-
`;
|
|
30
|
-
const createStyle = (documentRef) => {
|
|
31
|
-
const style = documentRef.createElement('style');
|
|
32
|
-
style.textContent = drawingStyle;
|
|
33
|
-
return style;
|
|
34
|
-
};
|
|
35
|
-
const createElement = (documentRef, tagName, className, text) => {
|
|
36
|
-
const element = documentRef.createElement(tagName);
|
|
37
|
-
if (className) {
|
|
38
|
-
element.className = className;
|
|
39
|
-
}
|
|
40
|
-
if (text !== undefined) {
|
|
41
|
-
element.textContent = text;
|
|
42
|
-
}
|
|
43
|
-
return element;
|
|
44
|
-
};
|
|
45
|
-
const createSvgElement = (documentRef, tagName) => {
|
|
46
|
-
return documentRef.createElementNS(SVG_NS, tagName);
|
|
47
|
-
};
|
|
48
|
-
const normalizeDrawingType = (type) => {
|
|
49
|
-
return (type === null || type === void 0 ? void 0 : type.toLowerCase()) === 'excalidraw' ? 'excalidraw' : 'drawio';
|
|
50
|
-
};
|
|
51
|
-
const formatDrawingLabel = (type) => {
|
|
52
|
-
const normalized = (type || 'drawio').toLowerCase();
|
|
53
|
-
return normalized === 'dio' ? 'DRAWIO' : normalized.toUpperCase();
|
|
54
|
-
};
|
|
55
|
-
const clampZoom = (value) => {
|
|
56
|
-
return Math.min(3, Math.max(0.5, Number(value.toFixed(2))));
|
|
57
|
-
};
|
|
58
|
-
const toNumber = (value, fallback = 0) => {
|
|
59
|
-
const parsed = Number(value);
|
|
60
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
61
|
-
};
|
|
62
|
-
const isTransparent = (color) => {
|
|
63
|
-
return !color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)';
|
|
64
|
-
};
|
|
65
|
-
const resolveDrawingViewerScriptUrl = (options, documentRef) => {
|
|
66
|
-
return resolveFileViewerDrawioViewerScriptUrl(options, (documentRef === null || documentRef === void 0 ? void 0 : documentRef.baseURI) || (documentRef === null || documentRef === void 0 ? void 0 : documentRef.URL));
|
|
67
|
-
};
|
|
68
|
-
const resolveDirectoryUrl = (url) => {
|
|
69
|
-
try {
|
|
70
|
-
return new URL('.', url).href;
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
const slashIndex = url.lastIndexOf('/');
|
|
74
|
-
return slashIndex >= 0 ? url.slice(0, slashIndex + 1) : '';
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
const configureOfflineDiagramsViewerAssets = (documentRef, scriptUrl) => {
|
|
78
|
-
const ownerWindow = documentRef.defaultView || (typeof window !== 'undefined' ? window : undefined);
|
|
79
|
-
if (!ownerWindow) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const viewerWindow = ownerWindow;
|
|
83
|
-
const baseUrl = resolveDirectoryUrl(scriptUrl);
|
|
84
|
-
const setDefault = (key, value) => {
|
|
85
|
-
if (viewerWindow[key] === undefined || viewerWindow[key] === '') {
|
|
86
|
-
viewerWindow[key] = value;
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
// viewer-static.min.js still contains public diagrams.net defaults. Setting
|
|
90
|
-
// these before the script executes keeps every secondary asset on the same
|
|
91
|
-
// self-hosted directory as the viewer script.
|
|
92
|
-
setDefault('PROXY_URL', `${baseUrl}proxy`);
|
|
93
|
-
setDefault('STYLE_PATH', `${baseUrl}styles`);
|
|
94
|
-
setDefault('SHAPES_PATH', `${baseUrl}shapes`);
|
|
95
|
-
setDefault('STENCIL_PATH', `${baseUrl}stencils`);
|
|
96
|
-
setDefault('DRAW_MATH_URL', `${baseUrl}math4/es5`);
|
|
97
|
-
setDefault('GRAPH_IMAGE_PATH', `${baseUrl}img`);
|
|
98
|
-
setDefault('mxImageBasePath', `${baseUrl}mxgraph/images`);
|
|
99
|
-
setDefault('mxBasePath', `${baseUrl}mxgraph/`);
|
|
100
|
-
setDefault('mxLoadStylesheets', false);
|
|
101
|
-
setDefault('DRAWIO_BASE_URL', baseUrl.replace(/\/$/, ''));
|
|
102
|
-
setDefault('DRAWIO_LIGHTBOX_URL', baseUrl.replace(/\/$/, ''));
|
|
103
|
-
setDefault('DRAWIO_SERVER_URL', baseUrl);
|
|
104
|
-
setDefault('DRAWIO_VIEWER_URL', `${baseUrl}viewer-static.min.js`);
|
|
105
|
-
setDefault('DRAWIO_LOG_URL', '');
|
|
106
|
-
setDefault('EXPORT_URL', `${baseUrl}export`);
|
|
107
|
-
setDefault('PLANT_URL', `${baseUrl}plant`);
|
|
108
|
-
setDefault('VSS_CONVERT_URL', `${baseUrl}VsdConverter/api/converter`);
|
|
109
|
-
setDefault('DRAWIO_GITLAB_URL', baseUrl);
|
|
110
|
-
setDefault('DRAWIO_GITHUB_URL', baseUrl);
|
|
111
|
-
setDefault('DRAWIO_GITHUB_API_URL', baseUrl);
|
|
112
|
-
setDefault('RT_WEBSOCKET_URL', `${baseUrl}rt`);
|
|
113
|
-
setDefault('NOTIFICATIONS_URL', `${baseUrl}notifications`);
|
|
114
|
-
};
|
|
115
|
-
const getDiagramsViewerPromiseMap = (documentRef) => {
|
|
116
|
-
let promiseMap = diagramsViewerPromises.get(documentRef);
|
|
117
|
-
if (!promiseMap) {
|
|
118
|
-
promiseMap = new Map();
|
|
119
|
-
diagramsViewerPromises.set(documentRef, promiseMap);
|
|
120
|
-
}
|
|
121
|
-
return promiseMap;
|
|
122
|
-
};
|
|
123
|
-
const deleteDiagramsViewerPromise = (documentRef, scriptUrl) => {
|
|
124
|
-
const promiseMap = diagramsViewerPromises.get(documentRef);
|
|
125
|
-
promiseMap === null || promiseMap === void 0 ? void 0 : promiseMap.delete(scriptUrl);
|
|
126
|
-
if (promiseMap && promiseMap.size === 0) {
|
|
127
|
-
diagramsViewerPromises.delete(documentRef);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
const loadDiagramsViewer = (documentRef, scriptUrl) => {
|
|
131
|
-
const ownerWindow = documentRef.defaultView || (typeof window !== 'undefined' ? window : undefined);
|
|
132
|
-
if (ownerWindow === null || ownerWindow === void 0 ? void 0 : ownerWindow.GraphViewer) {
|
|
133
|
-
return Promise.resolve();
|
|
134
|
-
}
|
|
135
|
-
configureOfflineDiagramsViewerAssets(documentRef, scriptUrl);
|
|
136
|
-
const promiseMap = getDiagramsViewerPromiseMap(documentRef);
|
|
137
|
-
const existingPromise = promiseMap.get(scriptUrl);
|
|
138
|
-
if (existingPromise) {
|
|
139
|
-
return existingPromise;
|
|
140
|
-
}
|
|
141
|
-
const nextPromise = new Promise((resolve, reject) => {
|
|
142
|
-
const existed = Array.from(documentRef.querySelectorAll('script[src]'))
|
|
143
|
-
.find(script => script.src === scriptUrl);
|
|
144
|
-
if (existed) {
|
|
145
|
-
existed.addEventListener('load', () => resolve(), { once: true });
|
|
146
|
-
existed.addEventListener('error', () => reject(new Error('diagrams.net viewer 加载失败')), { once: true });
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const script = documentRef.createElement('script');
|
|
150
|
-
script.src = scriptUrl;
|
|
151
|
-
script.async = true;
|
|
152
|
-
script.onload = () => resolve();
|
|
153
|
-
script.onerror = () => reject(new Error('diagrams.net viewer 加载失败'));
|
|
154
|
-
documentRef.head.appendChild(script);
|
|
155
|
-
});
|
|
156
|
-
promiseMap.set(scriptUrl, nextPromise);
|
|
157
|
-
return nextPromise;
|
|
158
|
-
};
|
|
159
|
-
const runWithTimeout = async (task, timeout, message) => {
|
|
160
|
-
let timer;
|
|
161
|
-
try {
|
|
162
|
-
return await Promise.race([
|
|
163
|
-
task,
|
|
164
|
-
new Promise((_, reject) => {
|
|
165
|
-
timer = setTimeout(() => reject(new Error(message)), timeout);
|
|
166
|
-
}),
|
|
167
|
-
]);
|
|
168
|
-
}
|
|
169
|
-
finally {
|
|
170
|
-
if (timer) {
|
|
171
|
-
clearTimeout(timer);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
const markRendered = (target, mode) => {
|
|
176
|
-
if (target.dataset.drawingRendered) {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
target.dataset.drawingRendered = mode;
|
|
180
|
-
return true;
|
|
181
|
-
};
|
|
182
|
-
const appendRenderedSvg = (target, svg, mode) => {
|
|
183
|
-
if (!markRendered(target, mode)) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
svg.classList.add('drawing-svg');
|
|
187
|
-
target.appendChild(svg);
|
|
188
|
-
};
|
|
189
|
-
const suppressExcalidrawWorkerWarning = () => {
|
|
190
|
-
const originalError = console.error;
|
|
191
|
-
const patchedError = (...args) => {
|
|
192
|
-
const message = args.map(arg => String(arg)).join(' ');
|
|
193
|
-
if (message.includes('Failed to use workers for subsetting') ||
|
|
194
|
-
message.includes('Failed to fetch font family')) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
originalError(...args);
|
|
198
|
-
};
|
|
199
|
-
console.error = patchedError;
|
|
200
|
-
return () => {
|
|
201
|
-
if (console.error === patchedError) {
|
|
202
|
-
console.error = originalError;
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
};
|
|
206
|
-
const getElementPoints = (element) => {
|
|
207
|
-
if (Array.isArray(element.points) && element.points.length) {
|
|
208
|
-
return element.points.map((point) => [
|
|
209
|
-
toNumber(element.x) + toNumber(point[0]),
|
|
210
|
-
toNumber(element.y) + toNumber(point[1]),
|
|
211
|
-
]);
|
|
212
|
-
}
|
|
213
|
-
return [
|
|
214
|
-
[toNumber(element.x), toNumber(element.y)],
|
|
215
|
-
[toNumber(element.x) + toNumber(element.width), toNumber(element.y) + toNumber(element.height)],
|
|
216
|
-
];
|
|
217
|
-
};
|
|
218
|
-
const getElementBounds = (element) => {
|
|
219
|
-
const points = getElementPoints(element);
|
|
220
|
-
const xs = points.map(point => point[0]);
|
|
221
|
-
const ys = points.map(point => point[1]);
|
|
222
|
-
if (!Array.isArray(element.points)) {
|
|
223
|
-
xs.push(toNumber(element.x) + toNumber(element.width));
|
|
224
|
-
ys.push(toNumber(element.y) + toNumber(element.height));
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
minX: Math.min(...xs),
|
|
228
|
-
minY: Math.min(...ys),
|
|
229
|
-
maxX: Math.max(...xs),
|
|
230
|
-
maxY: Math.max(...ys),
|
|
231
|
-
};
|
|
232
|
-
};
|
|
233
|
-
const getSceneBounds = (elements) => {
|
|
234
|
-
const initial = {
|
|
235
|
-
minX: Number.POSITIVE_INFINITY,
|
|
236
|
-
minY: Number.POSITIVE_INFINITY,
|
|
237
|
-
maxX: Number.NEGATIVE_INFINITY,
|
|
238
|
-
maxY: Number.NEGATIVE_INFINITY,
|
|
239
|
-
};
|
|
240
|
-
const bounds = elements.reduce((scene, element) => {
|
|
241
|
-
const elementBounds = getElementBounds(element);
|
|
242
|
-
return {
|
|
243
|
-
minX: Math.min(scene.minX, elementBounds.minX),
|
|
244
|
-
minY: Math.min(scene.minY, elementBounds.minY),
|
|
245
|
-
maxX: Math.max(scene.maxX, elementBounds.maxX),
|
|
246
|
-
maxY: Math.max(scene.maxY, elementBounds.maxY),
|
|
247
|
-
};
|
|
248
|
-
}, initial);
|
|
249
|
-
if (!Number.isFinite(bounds.minX)) {
|
|
250
|
-
return { minX: 0, minY: 0, maxX: 800, maxY: 480 };
|
|
251
|
-
}
|
|
252
|
-
return bounds;
|
|
253
|
-
};
|
|
254
|
-
const getRoughOptions = (element) => {
|
|
255
|
-
const fill = isTransparent(element.backgroundColor) ? undefined : element.backgroundColor;
|
|
256
|
-
return {
|
|
257
|
-
stroke: element.strokeColor || '#1e1e1e',
|
|
258
|
-
strokeWidth: Math.max(1, toNumber(element.strokeWidth, 1)),
|
|
259
|
-
roughness: Math.max(0, toNumber(element.roughness, 1)),
|
|
260
|
-
fill,
|
|
261
|
-
fillStyle: element.fillStyle || 'hachure',
|
|
262
|
-
seed: toNumber(element.seed, 1),
|
|
263
|
-
strokeLineDash: element.strokeStyle === 'dashed'
|
|
264
|
-
? [10, 8]
|
|
265
|
-
: element.strokeStyle === 'dotted'
|
|
266
|
-
? [2, 6]
|
|
267
|
-
: undefined,
|
|
268
|
-
};
|
|
269
|
-
};
|
|
270
|
-
const appendWithOpacity = (group, element, node) => {
|
|
271
|
-
const opacity = toNumber(element.opacity, 100) / 100;
|
|
272
|
-
if (opacity < 1) {
|
|
273
|
-
node.setAttribute('opacity', String(opacity));
|
|
274
|
-
}
|
|
275
|
-
group.appendChild(node);
|
|
276
|
-
};
|
|
277
|
-
const createElementGroup = (documentRef, element) => {
|
|
278
|
-
const group = createSvgElement(documentRef, 'g');
|
|
279
|
-
const angle = toNumber(element.angle);
|
|
280
|
-
if (angle) {
|
|
281
|
-
const cx = toNumber(element.x) + toNumber(element.width) / 2;
|
|
282
|
-
const cy = toNumber(element.y) + toNumber(element.height) / 2;
|
|
283
|
-
group.setAttribute('transform', `rotate(${angle * 180 / Math.PI} ${cx} ${cy})`);
|
|
284
|
-
}
|
|
285
|
-
return group;
|
|
286
|
-
};
|
|
287
|
-
const renderTextFallback = (documentRef, group, element) => {
|
|
288
|
-
const text = String(element.text || '');
|
|
289
|
-
if (!text.trim()) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const textNode = createSvgElement(documentRef, 'text');
|
|
293
|
-
const fontSize = Math.max(8, toNumber(element.fontSize, 20));
|
|
294
|
-
const lineHeight = fontSize * 1.25;
|
|
295
|
-
const lines = text.split(/\r?\n/);
|
|
296
|
-
const familyMap = {
|
|
297
|
-
1: 'Virgil, Segoe Print, Comic Sans MS, sans-serif',
|
|
298
|
-
2: 'Helvetica, Arial, sans-serif',
|
|
299
|
-
3: 'Cascadia Mono, Menlo, Consolas, monospace',
|
|
300
|
-
};
|
|
301
|
-
textNode.setAttribute('x', String(toNumber(element.x)));
|
|
302
|
-
textNode.setAttribute('y', String(toNumber(element.y) + fontSize));
|
|
303
|
-
textNode.setAttribute('fill', element.strokeColor || '#1e1e1e');
|
|
304
|
-
textNode.setAttribute('font-size', String(fontSize));
|
|
305
|
-
textNode.setAttribute('font-family', familyMap[toNumber(element.fontFamily, 1)] || familyMap[1]);
|
|
306
|
-
textNode.setAttribute('font-weight', String(element.fontWeight || 400));
|
|
307
|
-
textNode.setAttribute('text-anchor', element.textAlign === 'center' ? 'middle' : element.textAlign === 'right' ? 'end' : 'start');
|
|
308
|
-
if (element.textAlign === 'center') {
|
|
309
|
-
textNode.setAttribute('x', String(toNumber(element.x) + toNumber(element.width) / 2));
|
|
310
|
-
}
|
|
311
|
-
else if (element.textAlign === 'right') {
|
|
312
|
-
textNode.setAttribute('x', String(toNumber(element.x) + toNumber(element.width)));
|
|
313
|
-
}
|
|
314
|
-
lines.forEach((line, index) => {
|
|
315
|
-
const lineNode = createSvgElement(documentRef, 'tspan');
|
|
316
|
-
lineNode.setAttribute('x', textNode.getAttribute('x') || String(toNumber(element.x)));
|
|
317
|
-
lineNode.setAttribute('dy', index === 0 ? '0' : String(lineHeight));
|
|
318
|
-
lineNode.textContent = line;
|
|
319
|
-
textNode.appendChild(lineNode);
|
|
320
|
-
});
|
|
321
|
-
appendWithOpacity(group, element, textNode);
|
|
322
|
-
};
|
|
323
|
-
const appendArrowHead = (documentRef, group, element, points) => {
|
|
324
|
-
const endArrow = element.endArrowhead || element.startArrowhead;
|
|
325
|
-
if (!endArrow || points.length < 2) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
const end = points[points.length - 1];
|
|
329
|
-
const before = points[points.length - 2];
|
|
330
|
-
const angle = Math.atan2(end[1] - before[1], end[0] - before[0]);
|
|
331
|
-
const size = Math.max(10, toNumber(element.strokeWidth, 1) * 7);
|
|
332
|
-
const left = [
|
|
333
|
-
end[0] - size * Math.cos(angle - Math.PI / 7),
|
|
334
|
-
end[1] - size * Math.sin(angle - Math.PI / 7),
|
|
335
|
-
];
|
|
336
|
-
const right = [
|
|
337
|
-
end[0] - size * Math.cos(angle + Math.PI / 7),
|
|
338
|
-
end[1] - size * Math.sin(angle + Math.PI / 7),
|
|
339
|
-
];
|
|
340
|
-
const arrow = createSvgElement(documentRef, 'polygon');
|
|
341
|
-
arrow.setAttribute('points', `${end.join(',')} ${left.join(',')} ${right.join(',')}`);
|
|
342
|
-
arrow.setAttribute('fill', element.strokeColor || '#1e1e1e');
|
|
343
|
-
arrow.setAttribute('stroke', element.strokeColor || '#1e1e1e');
|
|
344
|
-
appendWithOpacity(group, element, arrow);
|
|
345
|
-
};
|
|
346
|
-
const renderRoughFallback = async (documentRef, payload, elements, target) => {
|
|
347
|
-
var _a;
|
|
348
|
-
const { default: rough } = await import('roughjs');
|
|
349
|
-
const bounds = getSceneBounds(elements);
|
|
350
|
-
const padding = 80;
|
|
351
|
-
const width = Math.max(320, bounds.maxX - bounds.minX + padding * 2);
|
|
352
|
-
const height = Math.max(220, bounds.maxY - bounds.minY + padding * 2);
|
|
353
|
-
const svg = createSvgElement(documentRef, 'svg');
|
|
354
|
-
const root = createSvgElement(documentRef, 'g');
|
|
355
|
-
const rc = rough.svg(svg);
|
|
356
|
-
svg.setAttribute('viewBox', `${bounds.minX - padding} ${bounds.minY - padding} ${width} ${height}`);
|
|
357
|
-
svg.setAttribute('width', String(width));
|
|
358
|
-
svg.setAttribute('height', String(height));
|
|
359
|
-
svg.setAttribute('role', 'img');
|
|
360
|
-
svg.setAttribute('aria-label', 'Excalidraw rough.js preview');
|
|
361
|
-
const background = createSvgElement(documentRef, 'rect');
|
|
362
|
-
background.setAttribute('x', String(bounds.minX - padding));
|
|
363
|
-
background.setAttribute('y', String(bounds.minY - padding));
|
|
364
|
-
background.setAttribute('width', String(width));
|
|
365
|
-
background.setAttribute('height', String(height));
|
|
366
|
-
background.setAttribute('fill', ((_a = payload.appState) === null || _a === void 0 ? void 0 : _a.viewBackgroundColor) || '#ffffff');
|
|
367
|
-
svg.appendChild(background);
|
|
368
|
-
svg.appendChild(root);
|
|
369
|
-
elements.forEach(element => {
|
|
370
|
-
const group = createElementGroup(documentRef, element);
|
|
371
|
-
const options = getRoughOptions(element);
|
|
372
|
-
const x = toNumber(element.x);
|
|
373
|
-
const y = toNumber(element.y);
|
|
374
|
-
const width = toNumber(element.width);
|
|
375
|
-
const height = toNumber(element.height);
|
|
376
|
-
if (element.type === 'text') {
|
|
377
|
-
renderTextFallback(documentRef, group, element);
|
|
378
|
-
}
|
|
379
|
-
else if (element.type === 'rectangle') {
|
|
380
|
-
appendWithOpacity(group, element, rc.rectangle(x, y, width, height, options));
|
|
381
|
-
}
|
|
382
|
-
else if (element.type === 'diamond') {
|
|
383
|
-
appendWithOpacity(group, element, rc.polygon([
|
|
384
|
-
[x + width / 2, y],
|
|
385
|
-
[x + width, y + height / 2],
|
|
386
|
-
[x + width / 2, y + height],
|
|
387
|
-
[x, y + height / 2],
|
|
388
|
-
], options));
|
|
389
|
-
}
|
|
390
|
-
else if (element.type === 'ellipse') {
|
|
391
|
-
appendWithOpacity(group, element, rc.ellipse(x + width / 2, y + height / 2, Math.abs(width), Math.abs(height), options));
|
|
392
|
-
}
|
|
393
|
-
else if (element.type === 'line' || element.type === 'arrow' || element.type === 'freedraw') {
|
|
394
|
-
const points = getElementPoints(element);
|
|
395
|
-
appendWithOpacity(group, element, rc.linearPath(points, options));
|
|
396
|
-
if (element.type === 'arrow') {
|
|
397
|
-
appendArrowHead(documentRef, group, element, points);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (group.childNodes.length) {
|
|
401
|
-
root.appendChild(group);
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
appendRenderedSvg(target, svg, 'rough');
|
|
405
|
-
};
|
|
406
|
-
const renderOfficialExcalidraw = async (payload, elements, target) => {
|
|
407
|
-
// Excalidraw 在部分浏览器会把字体子集 worker 降级记为 console.error;
|
|
408
|
-
// 实际会继续使用主线程导出,这里只屏蔽这条已知噪声。
|
|
409
|
-
const restoreConsole = suppressExcalidrawWorkerWarning();
|
|
410
|
-
const restoreTimer = setTimeout(restoreConsole, EXCALIDRAW_OFFICIAL_TIMEOUT + 1000);
|
|
411
|
-
const { exportToSvg, restore } = await import('@excalidraw/excalidraw');
|
|
412
|
-
try {
|
|
413
|
-
const restored = restore({
|
|
414
|
-
elements: elements,
|
|
415
|
-
appState: payload.appState || {},
|
|
416
|
-
files: payload.files || {},
|
|
417
|
-
}, null, null, {
|
|
418
|
-
repairBindings: true,
|
|
419
|
-
refreshDimensions: true,
|
|
420
|
-
});
|
|
421
|
-
const restoredElements = restored.elements.filter((element) => !element.isDeleted);
|
|
422
|
-
const svg = await exportToSvg({
|
|
423
|
-
elements: restoredElements,
|
|
424
|
-
appState: {
|
|
425
|
-
...restored.appState,
|
|
426
|
-
exportBackground: true,
|
|
427
|
-
viewBackgroundColor: restored.appState.viewBackgroundColor || '#ffffff',
|
|
428
|
-
},
|
|
429
|
-
files: restored.files || {},
|
|
430
|
-
});
|
|
431
|
-
appendRenderedSvg(target, svg, 'official');
|
|
432
|
-
}
|
|
433
|
-
finally {
|
|
434
|
-
clearTimeout(restoreTimer);
|
|
435
|
-
setTimeout(restoreConsole, 3000);
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
const renderExcalidraw = async (documentRef, text, target) => {
|
|
439
|
-
const payload = JSON.parse(text);
|
|
440
|
-
const elements = Array.isArray(payload.elements)
|
|
441
|
-
? payload.elements.filter((element) => !element.isDeleted)
|
|
442
|
-
: [];
|
|
443
|
-
if (!elements.length) {
|
|
444
|
-
throw new Error('Excalidraw 文件中没有可预览图元');
|
|
445
|
-
}
|
|
446
|
-
try {
|
|
447
|
-
await runWithTimeout(renderOfficialExcalidraw(payload, elements, target), EXCALIDRAW_OFFICIAL_TIMEOUT, 'Excalidraw 官方导出超时,自动切换 rough.js 兼容渲染');
|
|
448
|
-
}
|
|
449
|
-
catch (error) {
|
|
450
|
-
console.warn(error);
|
|
451
|
-
await renderRoughFallback(documentRef, payload, elements, target);
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
const getDirectChild = (parent, tagName) => {
|
|
455
|
-
return Array.from(parent.children).find(child => child.localName === tagName) || null;
|
|
456
|
-
};
|
|
457
|
-
const parseDrawioStyle = (style) => {
|
|
458
|
-
const entries = new Map();
|
|
459
|
-
for (const item of (style || '').split(';')) {
|
|
460
|
-
if (!item) {
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
const [key, ...rest] = item.split('=');
|
|
464
|
-
entries.set(key, rest.join('=') || '1');
|
|
465
|
-
}
|
|
466
|
-
return entries;
|
|
467
|
-
};
|
|
468
|
-
const getDrawioStyleValue = (style, key, fallback) => {
|
|
469
|
-
const value = style.get(key);
|
|
470
|
-
return value && value !== 'none' ? value : fallback;
|
|
471
|
-
};
|
|
472
|
-
const parseDrawioGeometry = (cell) => {
|
|
473
|
-
const geometry = getDirectChild(cell, 'mxGeometry');
|
|
474
|
-
if (!geometry) {
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
return {
|
|
478
|
-
x: toNumber(geometry.getAttribute('x')),
|
|
479
|
-
y: toNumber(geometry.getAttribute('y')),
|
|
480
|
-
width: Math.max(1, toNumber(geometry.getAttribute('width'), 80)),
|
|
481
|
-
height: Math.max(1, toNumber(geometry.getAttribute('height'), 40)),
|
|
482
|
-
points: Array.from(geometry.querySelectorAll('mxPoint')).map(point => ({
|
|
483
|
-
x: toNumber(point.getAttribute('x')),
|
|
484
|
-
y: toNumber(point.getAttribute('y')),
|
|
485
|
-
})),
|
|
486
|
-
};
|
|
487
|
-
};
|
|
488
|
-
const normalizeDrawioText = (documentRef, value) => {
|
|
489
|
-
if (!value) {
|
|
490
|
-
return '';
|
|
491
|
-
}
|
|
492
|
-
const helper = documentRef.createElement('textarea');
|
|
493
|
-
helper.innerHTML = value;
|
|
494
|
-
return helper.value
|
|
495
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
496
|
-
.replace(/<[^>]+>/g, '')
|
|
497
|
-
.replace(/\u00a0/g, ' ')
|
|
498
|
-
.replace(/[ \t]+/g, ' ')
|
|
499
|
-
.trim();
|
|
500
|
-
};
|
|
501
|
-
const appendDrawioWrappedText = (documentRef, svg, text, x, y, width, height, fontSize, fill) => {
|
|
502
|
-
if (!text) {
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
const textNode = createSvgElement(documentRef, 'text');
|
|
506
|
-
const maxChars = Math.max(4, Math.floor(width / Math.max(7, fontSize * 0.55)));
|
|
507
|
-
const words = text.includes(' ') ? text.split(/\s+/) : text.match(new RegExp(`.{1,${maxChars}}`, 'g')) || [text];
|
|
508
|
-
const lines = [];
|
|
509
|
-
let current = '';
|
|
510
|
-
for (const word of words) {
|
|
511
|
-
const next = current ? `${current} ${word}` : word;
|
|
512
|
-
if (next.length > maxChars && current) {
|
|
513
|
-
lines.push(current);
|
|
514
|
-
current = word;
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
current = next;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
if (current) {
|
|
521
|
-
lines.push(current);
|
|
522
|
-
}
|
|
523
|
-
const lineHeight = fontSize * 1.24;
|
|
524
|
-
const totalHeight = lineHeight * lines.length;
|
|
525
|
-
textNode.setAttribute('x', String(x + width / 2));
|
|
526
|
-
textNode.setAttribute('y', String(y + height / 2 - totalHeight / 2 + fontSize));
|
|
527
|
-
textNode.setAttribute('fill', fill);
|
|
528
|
-
textNode.setAttribute('font-size', String(fontSize));
|
|
529
|
-
textNode.setAttribute('font-family', 'Inter, Segoe UI, Arial, sans-serif');
|
|
530
|
-
textNode.setAttribute('font-weight', '600');
|
|
531
|
-
textNode.setAttribute('text-anchor', 'middle');
|
|
532
|
-
textNode.setAttribute('pointer-events', 'none');
|
|
533
|
-
lines.slice(0, 5).forEach((line, index) => {
|
|
534
|
-
const lineNode = createSvgElement(documentRef, 'tspan');
|
|
535
|
-
lineNode.setAttribute('x', String(x + width / 2));
|
|
536
|
-
lineNode.setAttribute('dy', index === 0 ? '0' : String(lineHeight));
|
|
537
|
-
lineNode.textContent = line;
|
|
538
|
-
textNode.appendChild(lineNode);
|
|
539
|
-
});
|
|
540
|
-
svg.appendChild(textNode);
|
|
541
|
-
};
|
|
542
|
-
const renderDrawioFallback = (documentRef, text, target) => {
|
|
543
|
-
var _a;
|
|
544
|
-
const parser = new DOMParser();
|
|
545
|
-
const parsed = parser.parseFromString(text, 'text/xml');
|
|
546
|
-
const parseError = parsed.querySelector('parsererror');
|
|
547
|
-
if (parseError) {
|
|
548
|
-
throw new Error(`Draw.io XML 解析失败:${parseError.textContent || 'invalid xml'}`);
|
|
549
|
-
}
|
|
550
|
-
const firstDiagram = parsed.querySelector('diagram');
|
|
551
|
-
const graphModel = (firstDiagram === null || firstDiagram === void 0 ? void 0 : firstDiagram.querySelector('mxGraphModel')) || parsed.querySelector('mxGraphModel');
|
|
552
|
-
if (!graphModel) {
|
|
553
|
-
throw new Error('当前 Draw.io 文件没有可直接渲染的 mxGraphModel。');
|
|
554
|
-
}
|
|
555
|
-
const cells = Array.from(graphModel.querySelectorAll('mxCell'));
|
|
556
|
-
const vertices = cells
|
|
557
|
-
.filter(cell => cell.getAttribute('vertex') === '1' && cell.getAttribute('connectable') !== '0')
|
|
558
|
-
.map(cell => ({
|
|
559
|
-
cell,
|
|
560
|
-
id: cell.getAttribute('id') || '',
|
|
561
|
-
geometry: parseDrawioGeometry(cell),
|
|
562
|
-
style: parseDrawioStyle(cell.getAttribute('style')),
|
|
563
|
-
text: normalizeDrawioText(documentRef, cell.getAttribute('value')),
|
|
564
|
-
}))
|
|
565
|
-
.filter(item => item.id && item.geometry);
|
|
566
|
-
if (!vertices.length) {
|
|
567
|
-
throw new Error('当前 Draw.io 文件没有可预览图元。');
|
|
568
|
-
}
|
|
569
|
-
const vertexById = new Map(vertices.map(vertex => [vertex.id, vertex]));
|
|
570
|
-
const allX = vertices.flatMap(vertex => [vertex.geometry.x, vertex.geometry.x + vertex.geometry.width]);
|
|
571
|
-
const allY = vertices.flatMap(vertex => [vertex.geometry.y, vertex.geometry.y + vertex.geometry.height]);
|
|
572
|
-
const edgePoints = [];
|
|
573
|
-
const edges = cells.filter(cell => cell.getAttribute('edge') === '1');
|
|
574
|
-
for (const edge of edges) {
|
|
575
|
-
const source = vertexById.get(edge.getAttribute('source') || '');
|
|
576
|
-
const targetVertex = vertexById.get(edge.getAttribute('target') || '');
|
|
577
|
-
const geometry = parseDrawioGeometry(edge);
|
|
578
|
-
if (source === null || source === void 0 ? void 0 : source.geometry) {
|
|
579
|
-
edgePoints.push({
|
|
580
|
-
x: source.geometry.x + source.geometry.width / 2,
|
|
581
|
-
y: source.geometry.y + source.geometry.height / 2,
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
geometry === null || geometry === void 0 ? void 0 : geometry.points.forEach(point => edgePoints.push(point));
|
|
585
|
-
if (targetVertex === null || targetVertex === void 0 ? void 0 : targetVertex.geometry) {
|
|
586
|
-
edgePoints.push({
|
|
587
|
-
x: targetVertex.geometry.x + targetVertex.geometry.width / 2,
|
|
588
|
-
y: targetVertex.geometry.y + targetVertex.geometry.height / 2,
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
allX.push(...edgePoints.map(point => point.x));
|
|
593
|
-
allY.push(...edgePoints.map(point => point.y));
|
|
594
|
-
const padding = 96;
|
|
595
|
-
const minX = Math.min(...allX) - padding;
|
|
596
|
-
const minY = Math.min(...allY) - padding;
|
|
597
|
-
const width = Math.max(480, Math.max(...allX) - Math.min(...allX) + padding * 2);
|
|
598
|
-
const height = Math.max(320, Math.max(...allY) - Math.min(...allY) + padding * 2);
|
|
599
|
-
const svg = createSvgElement(documentRef, 'svg');
|
|
600
|
-
svg.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);
|
|
601
|
-
svg.setAttribute('width', String(Math.ceil(width)));
|
|
602
|
-
svg.setAttribute('height', String(Math.ceil(height)));
|
|
603
|
-
svg.setAttribute('role', 'img');
|
|
604
|
-
svg.setAttribute('aria-label', (firstDiagram === null || firstDiagram === void 0 ? void 0 : firstDiagram.getAttribute('name')) || 'Draw.io local SVG preview');
|
|
605
|
-
const defs = createSvgElement(documentRef, 'defs');
|
|
606
|
-
const marker = createSvgElement(documentRef, 'marker');
|
|
607
|
-
marker.setAttribute('id', 'drawio-arrow');
|
|
608
|
-
marker.setAttribute('viewBox', '0 0 10 10');
|
|
609
|
-
marker.setAttribute('refX', '9');
|
|
610
|
-
marker.setAttribute('refY', '5');
|
|
611
|
-
marker.setAttribute('markerWidth', '7');
|
|
612
|
-
marker.setAttribute('markerHeight', '7');
|
|
613
|
-
marker.setAttribute('orient', 'auto-start-reverse');
|
|
614
|
-
const arrow = createSvgElement(documentRef, 'path');
|
|
615
|
-
arrow.setAttribute('d', 'M 0 0 L 10 5 L 0 10 z');
|
|
616
|
-
arrow.setAttribute('fill', '#64748b');
|
|
617
|
-
marker.appendChild(arrow);
|
|
618
|
-
defs.appendChild(marker);
|
|
619
|
-
svg.appendChild(defs);
|
|
620
|
-
const background = createSvgElement(documentRef, 'rect');
|
|
621
|
-
background.setAttribute('x', String(minX));
|
|
622
|
-
background.setAttribute('y', String(minY));
|
|
623
|
-
background.setAttribute('width', String(width));
|
|
624
|
-
background.setAttribute('height', String(height));
|
|
625
|
-
background.setAttribute('fill', '#ffffff');
|
|
626
|
-
svg.appendChild(background);
|
|
627
|
-
for (const edge of edges) {
|
|
628
|
-
const source = vertexById.get(edge.getAttribute('source') || '');
|
|
629
|
-
const targetVertex = vertexById.get(edge.getAttribute('target') || '');
|
|
630
|
-
if (!(source === null || source === void 0 ? void 0 : source.geometry) || !(targetVertex === null || targetVertex === void 0 ? void 0 : targetVertex.geometry)) {
|
|
631
|
-
continue;
|
|
632
|
-
}
|
|
633
|
-
const style = parseDrawioStyle(edge.getAttribute('style'));
|
|
634
|
-
const stroke = getDrawioStyleValue(style, 'strokeColor', '#64748b');
|
|
635
|
-
const points = [
|
|
636
|
-
{
|
|
637
|
-
x: source.geometry.x + source.geometry.width / 2,
|
|
638
|
-
y: source.geometry.y + source.geometry.height / 2,
|
|
639
|
-
},
|
|
640
|
-
...(((_a = parseDrawioGeometry(edge)) === null || _a === void 0 ? void 0 : _a.points) || []),
|
|
641
|
-
{
|
|
642
|
-
x: targetVertex.geometry.x + targetVertex.geometry.width / 2,
|
|
643
|
-
y: targetVertex.geometry.y + targetVertex.geometry.height / 2,
|
|
644
|
-
},
|
|
645
|
-
];
|
|
646
|
-
const polyline = createSvgElement(documentRef, 'polyline');
|
|
647
|
-
polyline.setAttribute('points', points.map(point => `${point.x},${point.y}`).join(' '));
|
|
648
|
-
polyline.setAttribute('fill', 'none');
|
|
649
|
-
polyline.setAttribute('stroke', stroke);
|
|
650
|
-
polyline.setAttribute('stroke-width', '2');
|
|
651
|
-
if (style.get('dashed') === '1') {
|
|
652
|
-
polyline.setAttribute('stroke-dasharray', '8 7');
|
|
653
|
-
}
|
|
654
|
-
if (style.get('endArrow') !== 'none') {
|
|
655
|
-
polyline.setAttribute('marker-end', 'url(#drawio-arrow)');
|
|
656
|
-
}
|
|
657
|
-
svg.appendChild(polyline);
|
|
658
|
-
}
|
|
659
|
-
for (const vertex of vertices) {
|
|
660
|
-
const geometry = vertex.geometry;
|
|
661
|
-
const fill = getDrawioStyleValue(vertex.style, 'fillColor', '#f8fafc');
|
|
662
|
-
const stroke = getDrawioStyleValue(vertex.style, 'strokeColor', '#64748b');
|
|
663
|
-
const fontSize = Math.max(10, toNumber(vertex.style.get('fontSize'), 14));
|
|
664
|
-
const shape = vertex.style.has('ellipse')
|
|
665
|
-
? 'ellipse'
|
|
666
|
-
: vertex.style.get('shape') === 'rhombus'
|
|
667
|
-
? 'diamond'
|
|
668
|
-
: 'rect';
|
|
669
|
-
if (shape === 'ellipse') {
|
|
670
|
-
const ellipse = createSvgElement(documentRef, 'ellipse');
|
|
671
|
-
ellipse.setAttribute('cx', String(geometry.x + geometry.width / 2));
|
|
672
|
-
ellipse.setAttribute('cy', String(geometry.y + geometry.height / 2));
|
|
673
|
-
ellipse.setAttribute('rx', String(geometry.width / 2));
|
|
674
|
-
ellipse.setAttribute('ry', String(geometry.height / 2));
|
|
675
|
-
ellipse.setAttribute('fill', fill);
|
|
676
|
-
ellipse.setAttribute('stroke', stroke);
|
|
677
|
-
ellipse.setAttribute('stroke-width', '2');
|
|
678
|
-
svg.appendChild(ellipse);
|
|
679
|
-
}
|
|
680
|
-
else if (shape === 'diamond') {
|
|
681
|
-
const diamond = createSvgElement(documentRef, 'polygon');
|
|
682
|
-
diamond.setAttribute('points', [
|
|
683
|
-
`${geometry.x + geometry.width / 2},${geometry.y}`,
|
|
684
|
-
`${geometry.x + geometry.width},${geometry.y + geometry.height / 2}`,
|
|
685
|
-
`${geometry.x + geometry.width / 2},${geometry.y + geometry.height}`,
|
|
686
|
-
`${geometry.x},${geometry.y + geometry.height / 2}`,
|
|
687
|
-
].join(' '));
|
|
688
|
-
diamond.setAttribute('fill', fill);
|
|
689
|
-
diamond.setAttribute('stroke', stroke);
|
|
690
|
-
diamond.setAttribute('stroke-width', '2');
|
|
691
|
-
svg.appendChild(diamond);
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
const rect = createSvgElement(documentRef, 'rect');
|
|
695
|
-
rect.setAttribute('x', String(geometry.x));
|
|
696
|
-
rect.setAttribute('y', String(geometry.y));
|
|
697
|
-
rect.setAttribute('width', String(geometry.width));
|
|
698
|
-
rect.setAttribute('height', String(geometry.height));
|
|
699
|
-
rect.setAttribute('rx', vertex.style.get('rounded') === '1' ? '10' : '2');
|
|
700
|
-
rect.setAttribute('fill', fill);
|
|
701
|
-
rect.setAttribute('stroke', stroke);
|
|
702
|
-
rect.setAttribute('stroke-width', '2');
|
|
703
|
-
svg.appendChild(rect);
|
|
704
|
-
}
|
|
705
|
-
appendDrawioWrappedText(documentRef, svg, vertex.text, geometry.x, geometry.y, geometry.width, geometry.height, fontSize, getDrawioStyleValue(vertex.style, 'fontColor', '#172033'));
|
|
706
|
-
}
|
|
707
|
-
appendRenderedSvg(target, svg, 'rough');
|
|
708
|
-
};
|
|
709
|
-
const renderOfficialDrawio = async (documentRef, text, target, scriptUrl) => {
|
|
710
|
-
const ownerWindow = documentRef.defaultView || (typeof window !== 'undefined' ? window : undefined);
|
|
711
|
-
await loadDiagramsViewer(documentRef, scriptUrl);
|
|
712
|
-
await waitForFileViewerNextPaint(ownerWindow);
|
|
713
|
-
const host = createElement(documentRef, 'div', 'mxgraph drawing-mxgraph');
|
|
714
|
-
host.setAttribute('data-mxgraph', JSON.stringify({
|
|
715
|
-
xml: text,
|
|
716
|
-
toolbar: 'zoom layers lightbox',
|
|
717
|
-
nav: true,
|
|
718
|
-
resize: true,
|
|
719
|
-
'auto-fit': true,
|
|
720
|
-
'auto-crop': true,
|
|
721
|
-
'auto-origin': true,
|
|
722
|
-
'allow-zoom-in': true,
|
|
723
|
-
'allow-zoom-out': true,
|
|
724
|
-
border: 16,
|
|
725
|
-
highlight: '#0f766e',
|
|
726
|
-
}));
|
|
727
|
-
target.appendChild(host);
|
|
728
|
-
if (!(ownerWindow === null || ownerWindow === void 0 ? void 0 : ownerWindow.GraphViewer)) {
|
|
729
|
-
throw new Error('diagrams.net viewer 未正确初始化');
|
|
730
|
-
}
|
|
731
|
-
ownerWindow.GraphViewer.createViewerForElement(host);
|
|
732
|
-
markRendered(target, 'official');
|
|
733
|
-
};
|
|
734
|
-
const renderDrawio = async (documentRef, text, target, options) => {
|
|
735
|
-
if ((options === null || options === void 0 ? void 0 : options.preferOfficial) === false) {
|
|
736
|
-
renderDrawioFallback(documentRef, text, target);
|
|
737
|
-
return;
|
|
738
|
-
}
|
|
739
|
-
const scriptUrl = resolveDrawingViewerScriptUrl(options, documentRef);
|
|
740
|
-
try {
|
|
741
|
-
await runWithTimeout(renderOfficialDrawio(documentRef, text, target, scriptUrl), DRAWIO_OFFICIAL_TIMEOUT, 'diagrams.net 官方 Viewer 加载超时,自动切换本地 SVG 预览');
|
|
742
|
-
}
|
|
743
|
-
catch (error) {
|
|
744
|
-
console.warn(error);
|
|
745
|
-
deleteDiagramsViewerPromise(documentRef, scriptUrl);
|
|
746
|
-
delete target.dataset.drawingRendered;
|
|
747
|
-
target.replaceChildren();
|
|
748
|
-
renderDrawioFallback(documentRef, text, target);
|
|
749
|
-
}
|
|
750
|
-
};
|
|
751
|
-
export default async function renderDrawing(buffer, target, type = 'drawio', context) {
|
|
752
|
-
const documentRef = target.ownerDocument || document;
|
|
753
|
-
const kind = normalizeDrawingType(type);
|
|
754
|
-
const zoomEmitter = createFileViewerZoomChangeEmitter();
|
|
755
|
-
let status = 'loading';
|
|
756
|
-
let errorMessage = '';
|
|
757
|
-
let zoom = 1;
|
|
758
|
-
let disposed = false;
|
|
759
|
-
const root = createElement(documentRef, 'div', 'drawing-viewer');
|
|
760
|
-
root.dataset.viewerZoomProvider = 'drawing';
|
|
761
|
-
const toolbar = createElement(documentRef, 'div', 'drawing-toolbar');
|
|
762
|
-
const title = createElement(documentRef, 'div', 'drawing-title');
|
|
763
|
-
title.append(createElement(documentRef, 'span', undefined, formatDrawingLabel(type)), createElement(documentRef, 'strong', undefined, kind === 'excalidraw'
|
|
764
|
-
? 'Excalidraw 官方 SVG 预览'
|
|
765
|
-
: 'Draw.io 离线 SVG 预览'));
|
|
766
|
-
const actions = createElement(documentRef, 'div', 'drawing-actions');
|
|
767
|
-
const zoomOutButton = createElement(documentRef, 'button', undefined, '-');
|
|
768
|
-
const zoomLabel = createElement(documentRef, 'span');
|
|
769
|
-
const zoomInButton = createElement(documentRef, 'button', undefined, '+');
|
|
770
|
-
const resetButton = createElement(documentRef, 'button', undefined, '适合');
|
|
771
|
-
[zoomOutButton, zoomInButton, resetButton].forEach(button => {
|
|
772
|
-
button.type = 'button';
|
|
773
|
-
});
|
|
774
|
-
zoomOutButton.title = '缩小';
|
|
775
|
-
zoomInButton.title = '放大';
|
|
776
|
-
resetButton.title = '适合宽度';
|
|
777
|
-
actions.append(zoomOutButton, zoomLabel, zoomInButton, resetButton);
|
|
778
|
-
toolbar.append(title, actions);
|
|
779
|
-
const stageWrapper = createElement(documentRef, 'div', 'drawing-stage');
|
|
780
|
-
const state = createElement(documentRef, 'div', 'drawing-state');
|
|
781
|
-
const scroll = createElement(documentRef, 'div', 'drawing-scroll');
|
|
782
|
-
const canvas = createElement(documentRef, 'div', 'drawing-canvas');
|
|
783
|
-
scroll.append(canvas);
|
|
784
|
-
stageWrapper.append(state, scroll);
|
|
785
|
-
root.append(toolbar, stageWrapper);
|
|
786
|
-
target.replaceChildren(createStyle(documentRef), root);
|
|
787
|
-
const clearStage = () => {
|
|
788
|
-
delete canvas.dataset.drawingRendered;
|
|
789
|
-
canvas.replaceChildren();
|
|
790
|
-
};
|
|
791
|
-
const getDrawingZoomState = () => ({
|
|
792
|
-
scale: zoom,
|
|
793
|
-
label: `${Math.round(zoom * 100)}%`,
|
|
794
|
-
canZoomIn: zoom < 3,
|
|
795
|
-
canZoomOut: zoom > 0.5,
|
|
796
|
-
canReset: zoom !== 1,
|
|
797
|
-
minScale: 0.5,
|
|
798
|
-
maxScale: 3,
|
|
799
|
-
});
|
|
800
|
-
const applyZoom = () => {
|
|
801
|
-
if (kind === 'excalidraw') {
|
|
802
|
-
canvas.style.transform = `scale(${zoom})`;
|
|
803
|
-
canvas.style.transformOrigin = 'top center';
|
|
804
|
-
canvas.style.zoom = '';
|
|
805
|
-
}
|
|
806
|
-
else {
|
|
807
|
-
canvas.style.transform = '';
|
|
808
|
-
canvas.style.transformOrigin = '';
|
|
809
|
-
canvas.style.zoom = String(zoom);
|
|
810
|
-
}
|
|
811
|
-
zoomLabel.textContent = `${Math.round(zoom * 100)}%`;
|
|
812
|
-
};
|
|
813
|
-
const setZoom = (scale) => {
|
|
814
|
-
zoom = clampZoom(scale);
|
|
815
|
-
applyZoom();
|
|
816
|
-
zoomEmitter.emit();
|
|
817
|
-
return getDrawingZoomState();
|
|
818
|
-
};
|
|
819
|
-
const syncUi = () => {
|
|
820
|
-
state.hidden = status === 'ready';
|
|
821
|
-
state.classList.toggle('error', status === 'error');
|
|
822
|
-
state.textContent = status === 'error'
|
|
823
|
-
? errorMessage
|
|
824
|
-
: '正在加载绘图预览...';
|
|
825
|
-
applyZoom();
|
|
826
|
-
};
|
|
827
|
-
const loadDrawing = async () => {
|
|
828
|
-
var _a;
|
|
829
|
-
status = 'loading';
|
|
830
|
-
errorMessage = '';
|
|
831
|
-
zoom = 1;
|
|
832
|
-
clearStage();
|
|
833
|
-
syncUi();
|
|
834
|
-
try {
|
|
835
|
-
const text = await readFileViewerText(buffer);
|
|
836
|
-
if (disposed) {
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
if (kind === 'excalidraw') {
|
|
840
|
-
await renderExcalidraw(documentRef, text, canvas);
|
|
841
|
-
}
|
|
842
|
-
else {
|
|
843
|
-
await renderDrawio(documentRef, text, canvas, (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.drawing);
|
|
844
|
-
}
|
|
845
|
-
if (disposed) {
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
status = 'ready';
|
|
849
|
-
syncUi();
|
|
850
|
-
}
|
|
851
|
-
catch (error) {
|
|
852
|
-
if (disposed) {
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
console.error(error);
|
|
856
|
-
errorMessage = error instanceof Error ? error.message : String(error);
|
|
857
|
-
status = 'error';
|
|
858
|
-
syncUi();
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
registerFileViewerZoomProvider(root, {
|
|
862
|
-
zoomIn: () => setZoom(zoom + 0.15),
|
|
863
|
-
zoomOut: () => setZoom(zoom - 0.15),
|
|
864
|
-
resetZoom: () => setZoom(1),
|
|
865
|
-
setZoom,
|
|
866
|
-
getState: getDrawingZoomState,
|
|
867
|
-
subscribe: zoomEmitter.subscribe,
|
|
868
|
-
});
|
|
869
|
-
zoomOutButton.addEventListener('click', () => setZoom(zoom - 0.15));
|
|
870
|
-
zoomInButton.addEventListener('click', () => setZoom(zoom + 0.15));
|
|
871
|
-
resetButton.addEventListener('click', () => setZoom(1));
|
|
872
|
-
syncUi();
|
|
873
|
-
void loadDrawing();
|
|
874
|
-
return {
|
|
875
|
-
$el: root,
|
|
876
|
-
unmount() {
|
|
877
|
-
disposed = true;
|
|
878
|
-
unregisterFileViewerZoomProvider(root);
|
|
879
|
-
target.replaceChildren();
|
|
880
|
-
},
|
|
881
|
-
};
|
|
882
|
-
}
|