@bpmn-nova/studio 0.3.1-preview → 0.3.3-preview
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.md +109 -4
- package/dist/canvas.js +5 -2
- package/dist/context-menu.js +1 -1
- package/dist/controller.js +82 -4
- package/dist/index.d.ts +100 -5
- package/dist/modules/export-svg/index.d.ts +2 -0
- package/dist/modules/export-svg/render.js +32 -8
- package/dist/modules/node-presentation/index.d.ts +25 -0
- package/dist/modules/node-presentation/index.js +51 -0
- package/dist/modules/palette/index.d.ts +14 -2
- package/dist/modules/palette/index.js +3 -3
- package/dist/modules/properties-bpmn/index.js +3 -1
- package/dist/modules/renderer-svg/index.d.ts +4 -0
- package/dist/modules/renderer-svg/index.js +80 -19
- package/dist/modules/viewer/index.d.ts +3 -0
- package/dist/modules/viewer/index.js +21 -4
- package/dist/shell.js +494 -171
- package/dist/styles.css +12 -3
- package/llms-full.txt +3322 -0
- package/llms.txt +236 -0
- package/package.json +4 -2
package/dist/shell.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createDefaultIconRegistry, hydrateIcons } from './modules/icons/index.js';
|
|
2
2
|
import { createDefaultPaletteRegistry, PalettePanel } from './modules/palette/index.js';
|
|
3
3
|
import { createDefaultPropertiesRegistry, PropertiesPanel } from './modules/properties/index.js';
|
|
4
|
-
import { demoRuntime } from './modules/runtime/index.js';
|
|
5
4
|
import { BpmnViewer } from './modules/viewer/index.js';
|
|
6
5
|
import { ThemeController } from './modules/theme/index.js';
|
|
7
6
|
import { openSvgExportPreview } from './modules/export-svg/index.js';
|
|
@@ -29,6 +28,43 @@ function mountSlot(slot, container, context) {
|
|
|
29
28
|
return null;
|
|
30
29
|
}
|
|
31
30
|
|
|
31
|
+
const STUDIO_MODES = ['design', 'viewer', 'instance'];
|
|
32
|
+
const STUDIO_MODE_OPTIONS = Object.freeze([
|
|
33
|
+
Object.freeze({ value: 'design', label: '流程设计', iconId: 'ui.design' }),
|
|
34
|
+
Object.freeze({ value: 'viewer', label: '流程展示', iconId: 'ui.preview' }),
|
|
35
|
+
Object.freeze({ value: 'instance', label: '审批轨迹', iconId: 'ui.trace' }),
|
|
36
|
+
]);
|
|
37
|
+
const STUDIO_SHELL_REGIONS = ['header', 'left', 'right', 'footer'];
|
|
38
|
+
const DEFAULT_STUDIO_SHELL_REGIONS = Object.freeze({
|
|
39
|
+
header: 'default',
|
|
40
|
+
left: 'default',
|
|
41
|
+
right: 'default',
|
|
42
|
+
footer: 'default',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function normalizeAllowedModes(allowedModes, { allowDefault = true } = {}) {
|
|
46
|
+
if (allowDefault && (allowedModes === undefined || allowedModes === null)) return [...STUDIO_MODES];
|
|
47
|
+
if (!Array.isArray(allowedModes)) throw new Error('allowedModes must be an array of Studio modes.');
|
|
48
|
+
if (!allowedModes.length || allowedModes.some((mode) => !STUDIO_MODES.includes(mode)) || new Set(allowedModes).size !== allowedModes.length) {
|
|
49
|
+
throw new Error('allowedModes must contain one or more unique design, viewer, or instance modes.');
|
|
50
|
+
}
|
|
51
|
+
return [...allowedModes];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeRegions(regions, current = DEFAULT_STUDIO_SHELL_REGIONS) {
|
|
55
|
+
if (regions === undefined || regions === null) return { ...current };
|
|
56
|
+
if (!regions || typeof regions !== 'object' || Array.isArray(regions)) {
|
|
57
|
+
throw new Error('regions must be an object containing header, left, right, or footer modes.');
|
|
58
|
+
}
|
|
59
|
+
for (const name of Object.keys(regions)) {
|
|
60
|
+
if (!STUDIO_SHELL_REGIONS.includes(name)) throw new Error(`Unknown Studio Shell region: ${name}.`);
|
|
61
|
+
if (!['default', 'hidden'].includes(regions[name])) {
|
|
62
|
+
throw new Error(`Studio Shell region "${name}" must be "default" or "hidden".`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { ...current, ...regions };
|
|
66
|
+
}
|
|
67
|
+
|
|
32
68
|
export class BpmnStudioShell {
|
|
33
69
|
constructor({
|
|
34
70
|
container,
|
|
@@ -39,10 +75,13 @@ export class BpmnStudioShell {
|
|
|
39
75
|
templateRegistry = createTemplateRegistry(),
|
|
40
76
|
contextMenuRegistry = createDefaultContextMenuRegistry(),
|
|
41
77
|
rendererOptions = {},
|
|
78
|
+
nodeSubtitleResolver = rendererOptions.nodeSubtitleResolver,
|
|
42
79
|
slots = {},
|
|
43
80
|
layout = null,
|
|
44
|
-
|
|
81
|
+
regions = null,
|
|
82
|
+
runtime = null,
|
|
45
83
|
mode = 'design',
|
|
84
|
+
allowedModes = null,
|
|
46
85
|
projection = undefined,
|
|
47
86
|
responsive = false,
|
|
48
87
|
projectionOptions = null,
|
|
@@ -54,8 +93,13 @@ export class BpmnStudioShell {
|
|
|
54
93
|
onThemeChange = null,
|
|
55
94
|
} = {}) {
|
|
56
95
|
if (!container || !studio) throw new Error('BpmnStudioShell requires container and studio.');
|
|
96
|
+
if (typeof layout === 'function' && regions !== null && regions !== undefined) {
|
|
97
|
+
throw new Error('BpmnStudioShell options "layout" and "regions" cannot be used together.');
|
|
98
|
+
}
|
|
99
|
+
const resolvedAllowedModes = normalizeAllowedModes(allowedModes);
|
|
100
|
+
const resolvedMode = resolvedAllowedModes.includes(mode) ? mode : resolvedAllowedModes[0];
|
|
57
101
|
const instanceProjection = projection || (responsive ? 'auto' : 'approval');
|
|
58
|
-
const activeProjection =
|
|
102
|
+
const activeProjection = resolvedMode === 'instance' ? instanceProjection : 'standard';
|
|
59
103
|
Object.assign(this, {
|
|
60
104
|
container,
|
|
61
105
|
studio,
|
|
@@ -63,27 +107,68 @@ export class BpmnStudioShell {
|
|
|
63
107
|
paletteRegistry,
|
|
64
108
|
templateRegistry,
|
|
65
109
|
contextMenuRegistry,
|
|
66
|
-
rendererOptions,
|
|
110
|
+
rendererOptions: { ...rendererOptions, nodeSubtitleResolver },
|
|
67
111
|
slots,
|
|
68
112
|
runtime,
|
|
69
|
-
mode,
|
|
113
|
+
mode: resolvedMode,
|
|
114
|
+
allowedModes: resolvedAllowedModes,
|
|
70
115
|
projection: activeProjection,
|
|
71
116
|
_instanceProjection: instanceProjection,
|
|
72
117
|
responsive,
|
|
73
118
|
projectionOptions,
|
|
74
119
|
runtimeAppearance,
|
|
75
120
|
svgExport: svgExport || rendererOptions.svgExport || null,
|
|
121
|
+
_usesCustomLayout: typeof layout === 'function',
|
|
122
|
+
_regions: normalizeRegions(regions),
|
|
76
123
|
});
|
|
77
124
|
this.propertiesRegistry = propertiesRegistry || createDefaultPropertiesRegistry({ studio });
|
|
78
125
|
this.interactions = createInteractionController({ studio, templates: templateRegistry });
|
|
79
126
|
this._cleanups = [];
|
|
80
127
|
this._instances = [];
|
|
128
|
+
this._modeListeners = new Set();
|
|
129
|
+
this._validationListeners = new Set();
|
|
130
|
+
this._modeViewports = new Map();
|
|
131
|
+
this._destroyed = false;
|
|
81
132
|
this._svgExportPreview = null;
|
|
133
|
+
this.actions = Object.freeze({
|
|
134
|
+
undo: () => this.studio.undo(),
|
|
135
|
+
redo: () => this.studio.redo(),
|
|
136
|
+
beautify: (options) => {
|
|
137
|
+
this.studio.commands.beautify(options);
|
|
138
|
+
requestAnimationFrame(() => this._fitActive());
|
|
139
|
+
},
|
|
140
|
+
rerouteEdges: (options) => {
|
|
141
|
+
this.studio.commands.rerouteEdges(options);
|
|
142
|
+
requestAnimationFrame(() => this._fitActive());
|
|
143
|
+
},
|
|
144
|
+
fitView: () => this.fitView(),
|
|
145
|
+
validate: () => this._runValidation('toolbar'),
|
|
146
|
+
importXml: (xml, engine) => this.studio.importXml(xml, engine),
|
|
147
|
+
exportXml: (engine) => this.studio.exportXml(engine),
|
|
148
|
+
exportSvg: (options) => this.exportSvg(options),
|
|
149
|
+
openSvgExportPreview: (options) => this.openSvgExportPreview(options),
|
|
150
|
+
});
|
|
82
151
|
this.container.classList.add('nova-studio-shell');
|
|
83
152
|
this.themeController = new ThemeController({ root: this.container, theme, onChange: onThemeChange });
|
|
84
153
|
this.container.style.setProperty('--nova-left-width', `${leftWidth}px`);
|
|
85
154
|
this.container.style.setProperty('--nova-right-width', `${rightWidth}px`);
|
|
86
|
-
const context = {
|
|
155
|
+
const context = {
|
|
156
|
+
studio,
|
|
157
|
+
shell: this,
|
|
158
|
+
actions: this.actions,
|
|
159
|
+
getState: () => this.studio.getState(),
|
|
160
|
+
subscribe: (listener) => this.studio.subscribe(listener),
|
|
161
|
+
getMode: () => this.getMode(),
|
|
162
|
+
getAllowedModes: () => this.getAllowedModes(),
|
|
163
|
+
subscribeMode: (listener) => this.subscribeMode(listener),
|
|
164
|
+
subscribeValidation: (listener) => this.subscribeValidation(listener),
|
|
165
|
+
iconRegistry,
|
|
166
|
+
paletteRegistry,
|
|
167
|
+
propertiesRegistry: this.propertiesRegistry,
|
|
168
|
+
templateRegistry,
|
|
169
|
+
contextMenuRegistry,
|
|
170
|
+
interactions: this.interactions,
|
|
171
|
+
};
|
|
87
172
|
const mount = {
|
|
88
173
|
canvas: (host, options = {}) => this._mountCanvas(host, options),
|
|
89
174
|
palette: (host, options = {}) => this._mountPalette(host, options),
|
|
@@ -104,20 +189,8 @@ export class BpmnStudioShell {
|
|
|
104
189
|
brandCopy.append(node('strong', '', 'BPMN Nova'), node('span', '', this.studio.model.name));
|
|
105
190
|
brand.append(brandMark, brandCopy);
|
|
106
191
|
const modeSwitch = node('nav', 'nova-studio-mode-switch');
|
|
192
|
+
modeSwitch.setAttribute('aria-label', 'Studio 模式');
|
|
107
193
|
const modeButtons = new Map();
|
|
108
|
-
for (const item of [
|
|
109
|
-
{ value: 'design', label: '流程设计', iconId: 'ui.design' },
|
|
110
|
-
{ value: 'viewer', label: '流程展示', iconId: 'ui.preview' },
|
|
111
|
-
{ value: 'instance', label: '审批轨迹', iconId: 'ui.trace' },
|
|
112
|
-
]) {
|
|
113
|
-
const button = node('button');
|
|
114
|
-
button.type = 'button';
|
|
115
|
-
button.setAttribute('data-mode', item.value);
|
|
116
|
-
button.append(iconNode(item.iconId, 'nova-icon nova-icon-xs'), node('span', '', item.label));
|
|
117
|
-
button.addEventListener('click', () => this._setMode(item.value));
|
|
118
|
-
modeButtons.set(item.value, button);
|
|
119
|
-
modeSwitch.appendChild(button);
|
|
120
|
-
}
|
|
121
194
|
const projectionSwitch = node('nav', 'nova-studio-projection-switch is-hidden');
|
|
122
195
|
projectionSwitch.setAttribute('aria-label', '审批轨迹视图');
|
|
123
196
|
const projectionButtons = new Map();
|
|
@@ -136,16 +209,16 @@ export class BpmnStudioShell {
|
|
|
136
209
|
const centerControls = node('div', 'nova-studio-center-controls');
|
|
137
210
|
centerControls.append(modeSwitch, projectionSwitch);
|
|
138
211
|
const tools = node('div', 'nova-studio-tools');
|
|
139
|
-
const tool = (label, title, action, className = '', iconId = null) => {
|
|
212
|
+
const tool = (label, title, action, className = '', iconId = null, target = tools) => {
|
|
140
213
|
const button = node('button', `nova-studio-tool ${className}`.trim()); button.type = 'button'; button.title = title;
|
|
141
214
|
if (iconId) button.appendChild(iconNode(iconId));
|
|
142
215
|
if (label) button.appendChild(node('span', '', label));
|
|
143
|
-
button.addEventListener('click', action);
|
|
216
|
+
button.addEventListener('click', action); target.appendChild(button); return button;
|
|
144
217
|
};
|
|
145
218
|
|
|
146
219
|
const divider = () => tools.appendChild(node('span', 'nova-studio-tool-divider'));
|
|
147
|
-
const undo = tool('', '撤销 Ctrl/⌘+Z', () => this.
|
|
148
|
-
const redo = tool('', '重做 Ctrl/⌘+Y', () => this.
|
|
220
|
+
const undo = tool('', '撤销 Ctrl/⌘+Z', () => this.actions.undo(), 'is-icon', 'ui.undo');
|
|
221
|
+
const redo = tool('', '重做 Ctrl/⌘+Y', () => this.actions.redo(), 'is-icon', 'ui.redo');
|
|
149
222
|
divider();
|
|
150
223
|
const beautifySplit = node('div', 'nova-studio-beautify-split');
|
|
151
224
|
const beautifyButton = node('button', 'nova-studio-tool nova-studio-beautify is-magic');
|
|
@@ -200,56 +273,62 @@ export class BpmnStudioShell {
|
|
|
200
273
|
});
|
|
201
274
|
beautifySplit.append(beautifyButton, beautifyCaret, beautifyMenu);
|
|
202
275
|
tools.appendChild(beautifySplit);
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
exportPopover
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
276
|
+
const headerActions = node('div', 'nova-studio-header-actions');
|
|
277
|
+
let validateButton = null;
|
|
278
|
+
let importButton = null;
|
|
279
|
+
let exportMenu = null;
|
|
280
|
+
let exportPopover = null;
|
|
281
|
+
let exportBpmnButton = null;
|
|
282
|
+
if (!this.slots.headerActions) {
|
|
283
|
+
const importInput = node('input', 'nova-studio-import-input');
|
|
284
|
+
importInput.type = 'file';
|
|
285
|
+
importInput.accept = '.bpmn,.xml,.bpmn20.xml,text/xml,application/xml';
|
|
286
|
+
importInput.hidden = true;
|
|
287
|
+
validateButton = tool('校验', '校验流程结构', () => this.actions.validate(), 'nova-studio-validate', null, headerActions);
|
|
288
|
+
importButton = tool('导入', '导入 BPMN XML', () => importInput.click(), 'nova-studio-import', null, headerActions);
|
|
289
|
+
exportMenu = node('div', 'nova-studio-export-menu');
|
|
290
|
+
const exportButton = node('button', 'nova-studio-tool nova-studio-export is-primary');
|
|
291
|
+
exportButton.type = 'button';
|
|
292
|
+
exportButton.title = '导出当前内容';
|
|
293
|
+
exportButton.append(node('span', '', '导出'), iconNode('ui.chevron', 'nova-icon nova-icon-xs'));
|
|
294
|
+
exportPopover = node('div', 'nova-studio-export-popover is-hidden');
|
|
295
|
+
const exportSvgButton = node('button', 'nova-studio-export-option');
|
|
296
|
+
exportSvgButton.type = 'button';
|
|
297
|
+
exportSvgButton.append(iconNode('ui.preview'), node('strong', '', '导出 SVG'), node('small', '', '预览确认后下载'));
|
|
298
|
+
exportSvgButton.addEventListener('click', () => {
|
|
299
|
+
exportPopover.classList.add('is-hidden');
|
|
300
|
+
exportButton.focus({ preventScroll: true });
|
|
301
|
+
this.actions.openSvgExportPreview();
|
|
302
|
+
});
|
|
303
|
+
exportBpmnButton = node('button', 'nova-studio-export-option');
|
|
304
|
+
exportBpmnButton.type = 'button';
|
|
305
|
+
exportBpmnButton.append(iconNode('ui.design'), node('strong', '', '导出 BPMN'), node('small', '', '下载流程 XML'));
|
|
306
|
+
exportBpmnButton.addEventListener('click', () => {
|
|
307
|
+
exportPopover.classList.add('is-hidden');
|
|
308
|
+
this._exportBpmn();
|
|
309
|
+
});
|
|
310
|
+
exportPopover.append(exportSvgButton, exportBpmnButton);
|
|
311
|
+
exportButton.addEventListener('click', (event) => {
|
|
312
|
+
event.stopPropagation();
|
|
313
|
+
exportPopover.classList.toggle('is-hidden');
|
|
314
|
+
});
|
|
315
|
+
exportMenu.append(exportButton, exportPopover);
|
|
316
|
+
headerActions.append(exportMenu, importInput);
|
|
317
|
+
importInput.addEventListener('change', async () => {
|
|
318
|
+
const file = importInput.files?.[0];
|
|
319
|
+
if (!file) return;
|
|
320
|
+
try {
|
|
321
|
+
this.actions.importXml(await file.text(), this.studio.model.engine);
|
|
322
|
+
this._setStatus('已导入 BPMN 文件', 'ok');
|
|
323
|
+
requestAnimationFrame(() => this._fitActive());
|
|
324
|
+
} catch (error) {
|
|
325
|
+
this._setStatus(`导入失败:${error.message}`, 'error');
|
|
326
|
+
} finally {
|
|
327
|
+
importInput.value = '';
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
tools.appendChild(headerActions);
|
|
253
332
|
header.append(brand, centerControls, tools);
|
|
254
333
|
|
|
255
334
|
const body = node('div', 'nova-studio-body');
|
|
@@ -295,18 +374,24 @@ export class BpmnStudioShell {
|
|
|
295
374
|
this.container.append(header, body);
|
|
296
375
|
hydrateIcons(this.container, this.iconRegistry);
|
|
297
376
|
Object.assign(this, {
|
|
377
|
+
_header: header,
|
|
378
|
+
_headerStart: brand,
|
|
379
|
+
_headerActions: headerActions,
|
|
298
380
|
_body: body,
|
|
299
381
|
_left: left,
|
|
300
382
|
_right: right,
|
|
383
|
+
_canvasColumn: canvasColumn,
|
|
384
|
+
_footer: statusbar,
|
|
301
385
|
_canvasHost: canvasHost,
|
|
302
386
|
_floatingHead: floatingHead,
|
|
303
387
|
_scopeBack: scopeBack,
|
|
304
388
|
_scopeNav: scopeNav,
|
|
305
389
|
_modeButtons: modeButtons,
|
|
390
|
+
_modeSwitch: modeSwitch,
|
|
306
391
|
_projectionSwitch: projectionSwitch,
|
|
307
392
|
_projectionButtons: projectionButtons,
|
|
308
393
|
_viewportTools: viewportTools,
|
|
309
|
-
_designControls: [undo, redo, beautifySplit, validateButton, importButton],
|
|
394
|
+
_designControls: [undo, redo, beautifySplit, validateButton, importButton].filter(Boolean),
|
|
310
395
|
_exportMenu: exportMenu,
|
|
311
396
|
_exportPopover: exportPopover,
|
|
312
397
|
_exportBpmnButton: exportBpmnButton,
|
|
@@ -314,6 +399,7 @@ export class BpmnStudioShell {
|
|
|
314
399
|
_defaultMount: mount,
|
|
315
400
|
_usesDefaultProperties: !this.slots.right,
|
|
316
401
|
});
|
|
402
|
+
this._rebuildModeButtons();
|
|
317
403
|
this.canvas = mount.canvas(canvasHost, {
|
|
318
404
|
rendererOptions: {
|
|
319
405
|
...this.rendererOptions,
|
|
@@ -328,8 +414,21 @@ export class BpmnStudioShell {
|
|
|
328
414
|
else this.palette = mount.palette(left, { canvas: this.canvas });
|
|
329
415
|
if (this.slots.right) this._recordCleanup(mountSlot(this.slots.right, right, { ...context, canvas: this.canvas }));
|
|
330
416
|
else this.properties = mount.properties(right, { canvas: this.canvas });
|
|
331
|
-
|
|
417
|
+
const mountHeaderActions = () => {
|
|
418
|
+
if (this.slots.headerActions) {
|
|
419
|
+
this._recordCleanup(mountSlot(this.slots.headerActions, headerActions, { ...context, canvas: this.canvas }));
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
if (this.slots.header) {
|
|
423
|
+
header.innerHTML = '';
|
|
424
|
+
this._recordCleanup(mountSlot(this.slots.header, header, { ...context, canvas: this.canvas }));
|
|
425
|
+
} else if (this.slots.headerStart) {
|
|
426
|
+
brand.innerHTML = '';
|
|
427
|
+
this._recordCleanup(mountSlot(this.slots.headerStart, brand, { ...context, canvas: this.canvas }));
|
|
428
|
+
mountHeaderActions();
|
|
429
|
+
} else mountHeaderActions();
|
|
332
430
|
if (this.slots.footer) { statusbar.innerHTML = ''; this._recordCleanup(mountSlot(this.slots.footer, statusbar, { ...context, canvas: this.canvas })); }
|
|
431
|
+
hydrateIcons(this.container, this.iconRegistry);
|
|
333
432
|
this._offState = this.studio.subscribe((event) => {
|
|
334
433
|
if (event.type === 'historyChanged' || event.type === 'modelChanged') {
|
|
335
434
|
undo.disabled = !this.studio.history.canUndo;
|
|
@@ -353,13 +452,12 @@ export class BpmnStudioShell {
|
|
|
353
452
|
this._setStatus(`${initialGraph.nodes.length} 节点 · ${initialGraph.edges.length} 连线`, 'ok');
|
|
354
453
|
const closeBeautify = (event) => {
|
|
355
454
|
if (!beautifySplit.contains(event.target)) beautifyMenu.classList.add('is-hidden');
|
|
356
|
-
if (!exportMenu.contains(event.target)) exportPopover.classList.add('is-hidden');
|
|
455
|
+
if (exportMenu && !exportMenu.contains(event.target)) exportPopover.classList.add('is-hidden');
|
|
357
456
|
};
|
|
358
457
|
document.addEventListener('click', closeBeautify);
|
|
359
458
|
this._cleanups.push(() => document.removeEventListener('click', closeBeautify));
|
|
360
459
|
this._syncDensityButtons();
|
|
361
|
-
this._setMode(this.mode, { force: true });
|
|
362
|
-
requestAnimationFrame(() => fit.click());
|
|
460
|
+
this._setMode(this.mode, { force: true, emit: false });
|
|
363
461
|
}
|
|
364
462
|
|
|
365
463
|
_renderScopePath() {
|
|
@@ -399,14 +497,13 @@ export class BpmnStudioShell {
|
|
|
399
497
|
|
|
400
498
|
_runBeautify(action = null) {
|
|
401
499
|
const settings = this.studio.model.settings || {};
|
|
402
|
-
if (action === 'routing') this.
|
|
403
|
-
else if (action === 'smooth') this.
|
|
404
|
-
else this.
|
|
500
|
+
if (action === 'routing') this.actions.rerouteEdges({ edgeStyle: 'rounded' });
|
|
501
|
+
else if (action === 'smooth') this.actions.rerouteEdges({ edgeStyle: 'smooth' });
|
|
502
|
+
else this.actions.beautify({
|
|
405
503
|
direction: action || settings.direction || 'horizontal',
|
|
406
504
|
density: settings.layoutDensity || 'balanced',
|
|
407
505
|
edgeStyle: settings.edgeStyle || 'rounded',
|
|
408
506
|
});
|
|
409
|
-
requestAnimationFrame(() => this._fitActive());
|
|
410
507
|
}
|
|
411
508
|
|
|
412
509
|
_syncDensityButtons() {
|
|
@@ -415,93 +512,225 @@ export class BpmnStudioShell {
|
|
|
415
512
|
this.container.querySelectorAll('[data-density]').forEach((button) => button.classList.toggle('is-active', button.dataset.density === value));
|
|
416
513
|
}
|
|
417
514
|
|
|
418
|
-
|
|
419
|
-
if (!
|
|
420
|
-
|
|
421
|
-
this.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
515
|
+
_rebuildModeButtons() {
|
|
516
|
+
if (!this._modeSwitch) return;
|
|
517
|
+
this._modeSwitch.replaceChildren();
|
|
518
|
+
this._modeButtons = new Map();
|
|
519
|
+
for (const item of STUDIO_MODE_OPTIONS.filter(({ value }) => this.allowedModes.includes(value))) {
|
|
520
|
+
const button = node('button');
|
|
521
|
+
const active = item.value === this.mode;
|
|
522
|
+
button.type = 'button';
|
|
523
|
+
button.setAttribute('data-mode', item.value);
|
|
524
|
+
button.setAttribute('aria-label', `切换到${item.label}`);
|
|
525
|
+
button.setAttribute('aria-pressed', String(active));
|
|
526
|
+
button.classList.toggle('is-active', active);
|
|
527
|
+
button.append(iconNode(item.iconId, 'nova-icon nova-icon-xs'), node('span', '', item.label));
|
|
528
|
+
button.addEventListener('click', () => this.setMode(item.value, 'toolbar'));
|
|
529
|
+
this._modeButtons.set(item.value, button);
|
|
530
|
+
this._modeSwitch.appendChild(button);
|
|
531
|
+
}
|
|
532
|
+
hydrateIcons(this._modeSwitch, this.iconRegistry);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
_syncModeChrome() {
|
|
536
|
+
this._modeButtons?.forEach((button, value) => {
|
|
537
|
+
const active = value === this.mode;
|
|
538
|
+
button.classList.toggle('is-active', active);
|
|
539
|
+
button.setAttribute('aria-pressed', String(active));
|
|
540
|
+
});
|
|
541
|
+
const readonly = this.mode !== 'design';
|
|
425
542
|
this._body?.classList.toggle('is-readonly', readonly);
|
|
543
|
+
this._applyRegions({ fit: false });
|
|
426
544
|
this._floatingHead?.classList.toggle('is-hidden', readonly);
|
|
427
545
|
this._designControls?.forEach((control) => control.classList.toggle('is-hidden-by-mode', readonly));
|
|
428
|
-
this._projectionSwitch?.classList.toggle('is-hidden', mode !== 'instance');
|
|
429
|
-
this._exportBpmnButton?.classList.toggle('is-hidden', mode !== 'design');
|
|
546
|
+
this._projectionSwitch?.classList.toggle('is-hidden', this.mode !== 'instance');
|
|
547
|
+
this._exportBpmnButton?.classList.toggle('is-hidden', this.mode !== 'design');
|
|
430
548
|
this._syncProjectionButtons();
|
|
431
|
-
this.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
this.
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
this.
|
|
489
|
-
this.
|
|
490
|
-
|
|
491
|
-
this.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
549
|
+
this._syncProjectionChrome();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
_syncProjectionChrome() {
|
|
553
|
+
const timeline = this.mode === 'instance' && this.viewer?.activeProjection === 'compact';
|
|
554
|
+
this._body?.classList.toggle('is-timeline', timeline);
|
|
555
|
+
this._viewportTools?.classList.toggle('is-hidden', timeline);
|
|
556
|
+
if (this.mode === 'design') {
|
|
557
|
+
const graph = this.studio.getActiveGraph();
|
|
558
|
+
this._setStatus(`${graph.nodes.length} 节点 · ${graph.edges.length} 连线`, 'ok');
|
|
559
|
+
} else if (this.mode === 'viewer') this._setStatus('流程展示 · 只读预览', 'ok');
|
|
560
|
+
else {
|
|
561
|
+
const active = this.viewer?.activeProjection || this.projection;
|
|
562
|
+
const status = active === 'compact' ? '审批轨迹 · 移动时间线' : active === 'standard' ? '审批轨迹 · 完整 BPMN' : '审批轨迹 · 实际路径';
|
|
563
|
+
this._setStatus(status, 'ok');
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
_captureModeViewport(mode = this.mode) {
|
|
568
|
+
if (this.viewer?.activeProjection === 'compact') return;
|
|
569
|
+
const renderer = this.canvas?.renderer || this.viewer?.renderer;
|
|
570
|
+
const viewport = renderer?.getViewportState?.();
|
|
571
|
+
if (viewport) this._modeViewports.set(mode, viewport);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
_restoreModeViewport(mode) {
|
|
575
|
+
const viewport = this._modeViewports.get(mode);
|
|
576
|
+
const renderer = this.canvas?.renderer || this.viewer?.renderer;
|
|
577
|
+
if (viewport && renderer?.setViewportState) {
|
|
578
|
+
renderer.setViewportState(viewport);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
requestAnimationFrame(() => {
|
|
582
|
+
if (this._destroyed || this.mode !== mode) return;
|
|
583
|
+
this._fitActive();
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
_createViewer(container, mode) {
|
|
588
|
+
let viewer = null;
|
|
589
|
+
viewer = new BpmnViewer({
|
|
590
|
+
container,
|
|
591
|
+
themeController: this.themeController,
|
|
592
|
+
runtimeAppearance: this.runtimeAppearance,
|
|
593
|
+
model: this.studio.model,
|
|
594
|
+
mode,
|
|
595
|
+
iconRegistry: this.iconRegistry,
|
|
596
|
+
nodeRenderers: this.rendererOptions.nodeRenderers,
|
|
597
|
+
nodeRenderer: this.rendererOptions.nodeRenderer,
|
|
598
|
+
nodeSubtitleResolver: this.rendererOptions.nodeSubtitleResolver,
|
|
599
|
+
runtime: mode === 'instance' ? this.runtime : null,
|
|
600
|
+
projection: mode === 'instance' ? this._instanceProjection : 'standard',
|
|
601
|
+
responsive: this.responsive,
|
|
602
|
+
runtimeTraceOptions: this.rendererOptions.runtimeTraceOptions,
|
|
603
|
+
timeline: this.rendererOptions.timeline,
|
|
604
|
+
runtimeDetails: this.rendererOptions.runtimeDetails,
|
|
605
|
+
runtimePresenter: this.rendererOptions.runtimePresenter,
|
|
606
|
+
runtimeTraceProjector: this.rendererOptions.runtimeTraceProjector,
|
|
607
|
+
runtimeAssetResolver: this.rendererOptions.runtimeAssetResolver,
|
|
608
|
+
svgExport: this.svgExport,
|
|
609
|
+
runtimeTimelineRenderer: this.slots.runtimeTimeline
|
|
610
|
+
? (timeline) => mountSlot(this.slots.runtimeTimeline, timeline.container, { ...timeline, studio: this.studio, shell: this })
|
|
611
|
+
: this.rendererOptions.runtimeTimelineRenderer,
|
|
612
|
+
onRuntimeTraceItemClick: this.rendererOptions.onRuntimeTraceItemClick,
|
|
613
|
+
runtimeDetailsRenderer: this.slots.runtimeDetails
|
|
614
|
+
? (details) => mountSlot(this.slots.runtimeDetails, details.container, { ...details, studio: this.studio, shell: this })
|
|
615
|
+
: this.rendererOptions.runtimeDetailsRenderer,
|
|
616
|
+
onRuntimeDetailsOpen: this.rendererOptions.onRuntimeDetailsOpen,
|
|
617
|
+
runtimeTransitionDetailsRenderer: this.slots.runtimeTransitionDetails
|
|
618
|
+
? (details) => mountSlot(this.slots.runtimeTransitionDetails, details.container, { ...details, studio: this.studio, shell: this })
|
|
619
|
+
: this.rendererOptions.runtimeTransitionDetailsRenderer,
|
|
620
|
+
onRuntimeTransitionDetailsOpen: this.rendererOptions.onRuntimeTransitionDetailsOpen,
|
|
621
|
+
onTraceClick: (payload) => {
|
|
622
|
+
const kind = payload.targetType === 'visit' ? 'node' : payload.targetType === 'transition' ? 'node' : payload.targetType;
|
|
623
|
+
this._renderReadonlyDetails({ kind, element: payload.element, presentation: payload.presentation, transition: payload.transition });
|
|
624
|
+
this.rendererOptions.onTraceClick?.(payload);
|
|
625
|
+
},
|
|
626
|
+
onProjectionChange: () => {
|
|
627
|
+
if (this.viewer === viewer) this._syncProjectionChrome();
|
|
628
|
+
},
|
|
629
|
+
onViewportChange: (viewport) => {
|
|
630
|
+
if (this.viewer === viewer) this._handleViewport(viewport);
|
|
631
|
+
},
|
|
632
|
+
onElementClick: (selection) => {
|
|
633
|
+
if (!selection) this._renderReadonlyDetails(null);
|
|
634
|
+
this.rendererOptions.onElementClick?.(selection);
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
return viewer;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
_emitModeChange(previousMode, source) {
|
|
641
|
+
const event = Object.freeze({
|
|
642
|
+
mode: this.mode,
|
|
643
|
+
previousMode,
|
|
644
|
+
source,
|
|
645
|
+
allowedModes: this.getAllowedModes(),
|
|
646
|
+
});
|
|
647
|
+
for (const listener of [...this._modeListeners]) {
|
|
648
|
+
try {
|
|
649
|
+
listener(event);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
console.error('BpmnStudioShell mode listener failed.', error);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
_setMode(mode, { force = false, emit = true, source = 'api', allowedModes = this.allowedModes } = {}) {
|
|
657
|
+
if (this._destroyed || !allowedModes.includes(mode)) return false;
|
|
658
|
+
const previousMode = this.mode;
|
|
659
|
+
if (!force && previousMode === mode) return false;
|
|
660
|
+
|
|
661
|
+
const reusable = (mode === 'design' && this.canvas) || (mode !== 'design' && this.viewer);
|
|
662
|
+
if (force && previousMode === mode && reusable) {
|
|
663
|
+
this.allowedModes = allowedModes;
|
|
664
|
+
this.projection = mode === 'instance' ? this._instanceProjection : 'standard';
|
|
665
|
+
this._rebuildModeButtons();
|
|
666
|
+
this._syncModeChrome();
|
|
667
|
+
this._restoreModeViewport(mode);
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!this._canvasHost) {
|
|
672
|
+
this.allowedModes = allowedModes;
|
|
673
|
+
this.mode = mode;
|
|
674
|
+
this.projection = mode === 'instance' ? this._instanceProjection : 'standard';
|
|
675
|
+
this._rebuildModeButtons();
|
|
676
|
+
this._syncModeChrome();
|
|
677
|
+
if (emit && previousMode !== mode) this._emitModeChange(previousMode, source);
|
|
678
|
+
return true;
|
|
503
679
|
}
|
|
504
|
-
|
|
680
|
+
|
|
681
|
+
if (!force || previousMode !== mode) this._captureModeViewport(previousMode);
|
|
682
|
+
const previousHost = this._canvasHost;
|
|
683
|
+
const nextHost = node('main', 'nova-studio-canvas');
|
|
684
|
+
nextHost.hidden = true;
|
|
685
|
+
previousHost.parentNode?.insertBefore(nextHost, previousHost.nextSibling);
|
|
686
|
+
let nextCanvas = null;
|
|
687
|
+
let nextViewer = null;
|
|
688
|
+
try {
|
|
689
|
+
if (mode === 'design') {
|
|
690
|
+
nextCanvas = this._createCanvas(nextHost, {
|
|
691
|
+
rendererOptions: {
|
|
692
|
+
...this.rendererOptions,
|
|
693
|
+
onViewportChange: (viewport) => {
|
|
694
|
+
if (this.canvas === nextCanvas) this._handleViewport(viewport);
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
} else nextViewer = this._createViewer(nextHost, mode);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
nextCanvas?.destroy?.();
|
|
701
|
+
nextViewer?.destroy?.();
|
|
702
|
+
nextHost.remove();
|
|
703
|
+
this._syncModeChrome();
|
|
704
|
+
console.error(`BpmnStudioShell failed to mount mode "${mode}".`, error);
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const previousCanvas = this.canvas;
|
|
709
|
+
const previousViewer = this.viewer;
|
|
710
|
+
nextHost.hidden = false;
|
|
711
|
+
previousHost.replaceWith(nextHost);
|
|
712
|
+
this._canvasHost = nextHost;
|
|
713
|
+
this.canvas = nextCanvas;
|
|
714
|
+
this.viewer = nextViewer;
|
|
715
|
+
this.allowedModes = allowedModes;
|
|
716
|
+
this.mode = mode;
|
|
717
|
+
this.projection = mode === 'instance' ? this._instanceProjection : 'standard';
|
|
718
|
+
if (nextCanvas) this._instances.push(nextCanvas);
|
|
719
|
+
this._releaseInstance(previousCanvas);
|
|
720
|
+
previousViewer?.destroy?.();
|
|
721
|
+
|
|
722
|
+
if (this.palette) this.palette.canvas = this.canvas;
|
|
723
|
+
if (this._usesDefaultProperties) {
|
|
724
|
+
this._releaseInstance(this.properties);
|
|
725
|
+
this.properties = null;
|
|
726
|
+
if (this.canvas) this.properties = this._mountProperties(this._right, { canvas: this.canvas });
|
|
727
|
+
else this._renderReadonlyDetails(null);
|
|
728
|
+
}
|
|
729
|
+
this._rebuildModeButtons();
|
|
730
|
+
this._syncModeChrome();
|
|
731
|
+
this._restoreModeViewport(mode);
|
|
732
|
+
if (emit && previousMode !== mode) this._emitModeChange(previousMode, source);
|
|
733
|
+
return true;
|
|
505
734
|
}
|
|
506
735
|
|
|
507
736
|
_handleViewport(viewport) {
|
|
@@ -552,7 +781,34 @@ export class BpmnStudioShell {
|
|
|
552
781
|
if (this.canvas) this.canvas.fitView(72);
|
|
553
782
|
else this.viewer?.fitView?.();
|
|
554
783
|
}
|
|
555
|
-
|
|
784
|
+
getMode() { return this.mode; }
|
|
785
|
+
getAllowedModes() { return Object.freeze([...this.allowedModes]); }
|
|
786
|
+
subscribeMode(listener) {
|
|
787
|
+
if (typeof listener !== 'function') throw new TypeError('subscribeMode() requires a listener function.');
|
|
788
|
+
if (this._destroyed) return () => {};
|
|
789
|
+
this._modeListeners.add(listener);
|
|
790
|
+
return () => this._modeListeners.delete(listener);
|
|
791
|
+
}
|
|
792
|
+
subscribeValidation(listener) {
|
|
793
|
+
if (typeof listener !== 'function') throw new TypeError('subscribeValidation() requires a listener function.');
|
|
794
|
+
if (this._destroyed) return () => {};
|
|
795
|
+
this._validationListeners.add(listener);
|
|
796
|
+
return () => this._validationListeners.delete(listener);
|
|
797
|
+
}
|
|
798
|
+
setMode(mode, source = 'api') { return this._setMode(mode, { source }); }
|
|
799
|
+
setAllowedModes(modes) {
|
|
800
|
+
const next = normalizeAllowedModes(modes, { allowDefault: false });
|
|
801
|
+
if (this._destroyed) return this.getAllowedModes();
|
|
802
|
+
if (next.length === this.allowedModes.length && next.every((mode, index) => mode === this.allowedModes[index])) {
|
|
803
|
+
return this.getAllowedModes();
|
|
804
|
+
}
|
|
805
|
+
if (next.includes(this.mode)) {
|
|
806
|
+
this.allowedModes = next;
|
|
807
|
+
this._rebuildModeButtons();
|
|
808
|
+
this._syncModeChrome();
|
|
809
|
+
} else this._setMode(next[0], { source: 'allowed-modes', allowedModes: next });
|
|
810
|
+
return this.getAllowedModes();
|
|
811
|
+
}
|
|
556
812
|
setRuntime(runtime) {
|
|
557
813
|
this.runtime = runtime;
|
|
558
814
|
if (this.mode === 'instance') this.viewer?.setRuntime(runtime);
|
|
@@ -563,6 +819,34 @@ export class BpmnStudioShell {
|
|
|
563
819
|
this._syncProjectionButtons();
|
|
564
820
|
if (this.mode === 'instance') this.viewer?.setProjection?.(projection);
|
|
565
821
|
}
|
|
822
|
+
getRegions() { return Object.freeze({ ...this._regions }); }
|
|
823
|
+
setRegions(regions) {
|
|
824
|
+
if (this._usesCustomLayout) {
|
|
825
|
+
throw new Error('BpmnStudioShell setRegions() is unavailable when a custom layout is active.');
|
|
826
|
+
}
|
|
827
|
+
const next = normalizeRegions(regions, this._regions);
|
|
828
|
+
const changed = STUDIO_SHELL_REGIONS.some((name) => next[name] !== this._regions[name]);
|
|
829
|
+
this._regions = next;
|
|
830
|
+
if (changed) this._applyRegions();
|
|
831
|
+
return this.getRegions();
|
|
832
|
+
}
|
|
833
|
+
_applyRegions({ fit = true } = {}) {
|
|
834
|
+
if (this._usesCustomLayout || !this._body) return;
|
|
835
|
+
const readonly = this.mode !== 'design';
|
|
836
|
+
const headerHidden = this._regions.header === 'hidden';
|
|
837
|
+
const leftHidden = readonly || this._regions.left === 'hidden';
|
|
838
|
+
const rightHidden = this._regions.right === 'hidden';
|
|
839
|
+
const footerHidden = this._regions.footer === 'hidden';
|
|
840
|
+
this.container.classList.toggle('is-header-hidden', headerHidden);
|
|
841
|
+
this._body.classList.toggle('is-left-hidden', leftHidden);
|
|
842
|
+
this._body.classList.toggle('is-right-hidden', rightHidden);
|
|
843
|
+
this._canvasColumn?.classList.toggle('is-footer-hidden', footerHidden);
|
|
844
|
+
if (this._header) this._header.hidden = headerHidden;
|
|
845
|
+
if (this._left) this._left.hidden = leftHidden;
|
|
846
|
+
if (this._right) this._right.hidden = rightHidden;
|
|
847
|
+
if (this._footer) this._footer.hidden = footerHidden;
|
|
848
|
+
if (fit) requestAnimationFrame(() => this._fitActive());
|
|
849
|
+
}
|
|
566
850
|
setTheme(theme) { return this.themeController.setTheme(theme); }
|
|
567
851
|
setThemeMode(mode) { return this.themeController.setMode(mode); }
|
|
568
852
|
getThemeState() { return this.themeController.getState(); }
|
|
@@ -571,6 +855,8 @@ export class BpmnStudioShell {
|
|
|
571
855
|
this.runtimeAppearance = runtimeAppearance || null;
|
|
572
856
|
this.viewer?.setRuntimeAppearance?.(this.runtimeAppearance);
|
|
573
857
|
}
|
|
858
|
+
refreshPresentation() { (this.canvas || this.viewer)?.refreshPresentation?.(); }
|
|
859
|
+
validate() { return this._runValidation('api'); }
|
|
574
860
|
|
|
575
861
|
_syncProjectionButtons() {
|
|
576
862
|
this._projectionButtons?.forEach((button, value) => {
|
|
@@ -617,18 +903,38 @@ export class BpmnStudioShell {
|
|
|
617
903
|
this._statusDot.className = `is-${tone}`;
|
|
618
904
|
}
|
|
619
905
|
|
|
620
|
-
|
|
906
|
+
_runValidation(source) {
|
|
621
907
|
const issues = this.studio.validate();
|
|
908
|
+
const errorCount = issues.filter((issue) => issue.level === 'error').length;
|
|
909
|
+
const warningCount = issues.filter((issue) => issue.level === 'warning').length;
|
|
622
910
|
if (!issues.length) this._setStatus('校验通过:流程结构正常', 'ok');
|
|
623
911
|
else {
|
|
624
|
-
const
|
|
625
|
-
|
|
912
|
+
const summary = [
|
|
913
|
+
errorCount ? `${errorCount} 个错误` : null,
|
|
914
|
+
warningCount ? `${warningCount} 个警告` : null,
|
|
915
|
+
].filter(Boolean).join(' · ');
|
|
916
|
+
this._setStatus(`校验完成:${summary}`, errorCount ? 'error' : 'warning');
|
|
917
|
+
}
|
|
918
|
+
const eventIssues = Object.freeze(issues.map((issue) => Object.freeze({ ...issue })));
|
|
919
|
+
const event = Object.freeze({
|
|
920
|
+
source,
|
|
921
|
+
valid: errorCount === 0,
|
|
922
|
+
errorCount,
|
|
923
|
+
warningCount,
|
|
924
|
+
issues: eventIssues,
|
|
925
|
+
});
|
|
926
|
+
for (const listener of [...this._validationListeners]) {
|
|
927
|
+
try {
|
|
928
|
+
listener(event);
|
|
929
|
+
} catch (error) {
|
|
930
|
+
console.error('BpmnStudioShell validation listener failed.', error);
|
|
931
|
+
}
|
|
626
932
|
}
|
|
627
933
|
return issues;
|
|
628
934
|
}
|
|
629
935
|
|
|
630
936
|
_exportBpmn() {
|
|
631
|
-
const blob = new Blob([this.
|
|
937
|
+
const blob = new Blob([this.actions.exportXml()], { type: 'application/xml;charset=utf-8' });
|
|
632
938
|
const url = URL.createObjectURL(blob);
|
|
633
939
|
const anchor = node('a');
|
|
634
940
|
anchor.href = url;
|
|
@@ -641,8 +947,8 @@ export class BpmnStudioShell {
|
|
|
641
947
|
}
|
|
642
948
|
|
|
643
949
|
_recordCleanup(cleanup) { if (typeof cleanup === 'function') this._cleanups.push(cleanup); }
|
|
644
|
-
|
|
645
|
-
|
|
950
|
+
_createCanvas(container, options = {}) {
|
|
951
|
+
return new BpmnCanvas({
|
|
646
952
|
container,
|
|
647
953
|
studio: this.studio,
|
|
648
954
|
interactions: this.interactions,
|
|
@@ -657,6 +963,9 @@ export class BpmnStudioShell {
|
|
|
657
963
|
},
|
|
658
964
|
themeController: this.themeController,
|
|
659
965
|
});
|
|
966
|
+
}
|
|
967
|
+
_mountCanvas(container, options = {}) {
|
|
968
|
+
const instance = this._createCanvas(container, options);
|
|
660
969
|
this._instances.push(instance); return instance;
|
|
661
970
|
}
|
|
662
971
|
_mountPalette(container, options = {}) {
|
|
@@ -667,11 +976,25 @@ export class BpmnStudioShell {
|
|
|
667
976
|
const instance = new PropertiesPanel({ container, studio: this.studio, registry: this.propertiesRegistry, iconRegistry: this.iconRegistry, themeController: this.themeController, ...options });
|
|
668
977
|
instance.render(); this._instances.push(instance); return instance;
|
|
669
978
|
}
|
|
979
|
+
_releaseInstance(instance) {
|
|
980
|
+
if (!instance) return;
|
|
981
|
+
const index = this._instances.indexOf(instance);
|
|
982
|
+
if (index >= 0) this._instances.splice(index, 1);
|
|
983
|
+
instance.destroy?.();
|
|
984
|
+
}
|
|
670
985
|
destroy() {
|
|
986
|
+
if (this._destroyed) return;
|
|
987
|
+
this._destroyed = true;
|
|
988
|
+
this._modeListeners.clear();
|
|
989
|
+
this._validationListeners.clear();
|
|
671
990
|
this._svgExportPreview?.close?.({ immediate: true });
|
|
672
991
|
this._svgExportPreview = null;
|
|
673
992
|
this._cleanups.splice(0).reverse().forEach((cleanup) => cleanup?.());
|
|
993
|
+
this.viewer?.destroy?.();
|
|
994
|
+
this.viewer = null;
|
|
674
995
|
this._instances.splice(0).reverse().forEach((instance) => instance.destroy?.());
|
|
996
|
+
this.canvas = null;
|
|
997
|
+
this.properties = null;
|
|
675
998
|
this.container.innerHTML = '';
|
|
676
999
|
this.container.classList.remove('nova-studio-shell');
|
|
677
1000
|
this.themeController.destroy();
|