@bpmn-nova/studio 0.3.4-preview → 0.3.5-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 +75 -6
- package/dist/config.js +285 -0
- package/dist/controller.js +19 -4
- package/dist/index.d.ts +101 -2
- package/dist/modules/export-svg/render.js +82 -62
- package/dist/modules/node-geometry/index.d.ts +19 -0
- package/dist/modules/node-geometry/index.js +147 -0
- package/dist/modules/renderer-svg/index.js +63 -25
- package/dist/modules/viewer/index.d.ts +1 -1
- package/dist/modules/viewer/index.js +86 -35
- package/dist/panel-selection.js +60 -0
- package/dist/shell.js +315 -87
- package/dist/sidebars.js +195 -0
- package/dist/styles.css +72 -12
- package/llms-full.txt +1545 -92
- package/llms.txt +62 -15
- package/package.json +1 -1
package/dist/shell.js
CHANGED
|
@@ -5,6 +5,15 @@ import { BpmnViewer } from './modules/viewer/index.js';
|
|
|
5
5
|
import { ThemeController } from './modules/theme/index.js';
|
|
6
6
|
import { openSvgExportPreview } from './modules/export-svg/index.js';
|
|
7
7
|
import { BpmnCanvas } from './canvas.js';
|
|
8
|
+
import { StudioSidebars } from './sidebars.js';
|
|
9
|
+
import { createPanelSelection, samePanelSelection } from './panel-selection.js';
|
|
10
|
+
import {
|
|
11
|
+
applyStudioControlMetrics,
|
|
12
|
+
createStudioUiContext,
|
|
13
|
+
normalizeStudioConfig,
|
|
14
|
+
normalizeStudioRegions,
|
|
15
|
+
resolveStudioControlMetrics,
|
|
16
|
+
} from './config.js';
|
|
8
17
|
import { createDefaultContextMenuRegistry } from './context-menu.js';
|
|
9
18
|
import { createInteractionController, createTemplateRegistry } from './interactions.js';
|
|
10
19
|
|
|
@@ -24,10 +33,25 @@ function iconNode(id, className = 'nova-icon nova-icon-sm') {
|
|
|
24
33
|
function mountSlot(slot, container, context) {
|
|
25
34
|
if (!slot) return null;
|
|
26
35
|
if (slot instanceof HTMLElement) { container.appendChild(slot); return null; }
|
|
27
|
-
if (typeof slot === 'function') return slot({ container,
|
|
36
|
+
if (typeof slot === 'function') return slot(Object.defineProperties({ container }, Object.getOwnPropertyDescriptors(context)));
|
|
28
37
|
return null;
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
function sameConfigValue(left, right) {
|
|
41
|
+
if (Object.is(left, right)) return true;
|
|
42
|
+
if (!left || !right || typeof left !== 'object' || typeof right !== 'object') return false;
|
|
43
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
44
|
+
return Array.isArray(left)
|
|
45
|
+
&& Array.isArray(right)
|
|
46
|
+
&& left.length === right.length
|
|
47
|
+
&& left.every((value, index) => sameConfigValue(value, right[index]));
|
|
48
|
+
}
|
|
49
|
+
const leftKeys = Object.keys(left);
|
|
50
|
+
const rightKeys = Object.keys(right);
|
|
51
|
+
return leftKeys.length === rightKeys.length
|
|
52
|
+
&& leftKeys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && sameConfigValue(left[key], right[key]));
|
|
53
|
+
}
|
|
54
|
+
|
|
31
55
|
const STUDIO_MODES = ['design', 'viewer', 'instance'];
|
|
32
56
|
const STUDIO_MODE_OPTIONS = Object.freeze([
|
|
33
57
|
Object.freeze({ value: 'design', label: '流程设计', iconId: 'ui.design' }),
|
|
@@ -35,12 +59,6 @@ const STUDIO_MODE_OPTIONS = Object.freeze([
|
|
|
35
59
|
Object.freeze({ value: 'instance', label: '审批轨迹', iconId: 'ui.trace' }),
|
|
36
60
|
]);
|
|
37
61
|
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
62
|
|
|
45
63
|
function normalizeAllowedModes(allowedModes, { allowDefault = true } = {}) {
|
|
46
64
|
if (allowDefault && (allowedModes === undefined || allowedModes === null)) return [...STUDIO_MODES];
|
|
@@ -51,54 +69,62 @@ function normalizeAllowedModes(allowedModes, { allowDefault = true } = {}) {
|
|
|
51
69
|
return [...allowedModes];
|
|
52
70
|
}
|
|
53
71
|
|
|
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
|
-
|
|
68
72
|
export class BpmnStudioShell {
|
|
69
|
-
constructor({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
constructor(options = {}) {
|
|
74
|
+
const {
|
|
75
|
+
container,
|
|
76
|
+
studio,
|
|
77
|
+
config,
|
|
78
|
+
iconRegistry = createDefaultIconRegistry(),
|
|
79
|
+
paletteRegistry = createDefaultPaletteRegistry(),
|
|
80
|
+
propertiesRegistry = null,
|
|
81
|
+
templateRegistry = createTemplateRegistry(),
|
|
82
|
+
contextMenuRegistry = createDefaultContextMenuRegistry(),
|
|
83
|
+
rendererOptions = {},
|
|
84
|
+
nodeSubtitleResolver = rendererOptions.nodeSubtitleResolver,
|
|
85
|
+
slots = {},
|
|
86
|
+
layout = null,
|
|
87
|
+
regions = null,
|
|
88
|
+
runtime = null,
|
|
89
|
+
mode = 'design',
|
|
90
|
+
allowedModes = null,
|
|
91
|
+
projection = undefined,
|
|
92
|
+
responsive = false,
|
|
93
|
+
projectionOptions = null,
|
|
94
|
+
leftWidth = 244,
|
|
95
|
+
rightWidth = 360,
|
|
96
|
+
timeline,
|
|
97
|
+
runtimeDetails,
|
|
98
|
+
runtimeTraceOptions,
|
|
99
|
+
theme = null,
|
|
100
|
+
runtimeAppearance = null,
|
|
101
|
+
svgExport = null,
|
|
102
|
+
onThemeChange = null,
|
|
103
|
+
} = options;
|
|
95
104
|
if (!container || !studio) throw new Error('BpmnStudioShell requires container and studio.');
|
|
96
|
-
|
|
105
|
+
const configuredRegions = regions !== null && regions !== undefined
|
|
106
|
+
|| config?.ui?.regions !== null && config?.ui?.regions !== undefined;
|
|
107
|
+
if (typeof layout === 'function' && configuredRegions) {
|
|
97
108
|
throw new Error('BpmnStudioShell options "layout" and "regions" cannot be used together.');
|
|
98
109
|
}
|
|
110
|
+
const fallbackConfig = {
|
|
111
|
+
viewer: {
|
|
112
|
+
timeline: rendererOptions.timeline,
|
|
113
|
+
runtimeDetails: rendererOptions.runtimeDetails,
|
|
114
|
+
runtimeTraceOptions: rendererOptions.runtimeTraceOptions,
|
|
115
|
+
},
|
|
116
|
+
export: rendererOptions.svgExport,
|
|
117
|
+
};
|
|
118
|
+
const legacyConfig = {};
|
|
119
|
+
for (const name of ['regions', 'leftWidth', 'rightWidth', 'responsive', 'projectionOptions', 'timeline', 'runtimeDetails', 'runtimeTraceOptions', 'svgExport']) {
|
|
120
|
+
if (Object.prototype.hasOwnProperty.call(options, name)) legacyConfig[name] = options[name];
|
|
121
|
+
}
|
|
122
|
+
const normalizedConfig = normalizeStudioConfig(config, { fallback: fallbackConfig, legacy: legacyConfig });
|
|
123
|
+
const resolvedUi = normalizedConfig.ui;
|
|
124
|
+
const resolvedViewer = normalizedConfig.viewer;
|
|
99
125
|
const resolvedAllowedModes = normalizeAllowedModes(allowedModes);
|
|
100
126
|
const resolvedMode = resolvedAllowedModes.includes(mode) ? mode : resolvedAllowedModes[0];
|
|
101
|
-
const instanceProjection = projection || (responsive ? 'auto' : 'approval');
|
|
127
|
+
const instanceProjection = projection || (resolvedViewer.responsive ? 'auto' : 'approval');
|
|
102
128
|
const activeProjection = resolvedMode === 'instance' ? instanceProjection : 'standard';
|
|
103
129
|
Object.assign(this, {
|
|
104
130
|
container,
|
|
@@ -107,26 +133,40 @@ export class BpmnStudioShell {
|
|
|
107
133
|
paletteRegistry,
|
|
108
134
|
templateRegistry,
|
|
109
135
|
contextMenuRegistry,
|
|
110
|
-
rendererOptions: {
|
|
136
|
+
rendererOptions: {
|
|
137
|
+
...rendererOptions,
|
|
138
|
+
nodeSubtitleResolver,
|
|
139
|
+
timeline: resolvedViewer.timeline,
|
|
140
|
+
runtimeDetails: resolvedViewer.runtimeDetails,
|
|
141
|
+
runtimeTraceOptions: resolvedViewer.runtimeTraceOptions,
|
|
142
|
+
},
|
|
111
143
|
slots,
|
|
112
144
|
runtime,
|
|
113
145
|
mode: resolvedMode,
|
|
114
146
|
allowedModes: resolvedAllowedModes,
|
|
115
147
|
projection: activeProjection,
|
|
116
148
|
_instanceProjection: instanceProjection,
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
_projectionExplicit: projection !== undefined,
|
|
150
|
+
responsive: resolvedViewer.responsive,
|
|
151
|
+
projectionOptions: resolvedViewer.projectionOptions || null,
|
|
119
152
|
runtimeAppearance,
|
|
120
|
-
svgExport:
|
|
153
|
+
svgExport: normalizedConfig.export || null,
|
|
121
154
|
_usesCustomLayout: typeof layout === 'function',
|
|
122
|
-
_regions:
|
|
155
|
+
_regions: normalizeStudioRegions(resolvedUi.regions),
|
|
156
|
+
_config: normalizedConfig,
|
|
157
|
+
_configFallback: fallbackConfig,
|
|
158
|
+
_legacyConfig: legacyConfig,
|
|
159
|
+
_controlMetrics: resolveStudioControlMetrics(resolvedUi.controlSize),
|
|
123
160
|
});
|
|
161
|
+
this.ui = createStudioUiContext(() => this._controlMetrics);
|
|
124
162
|
this.propertiesRegistry = propertiesRegistry || createDefaultPropertiesRegistry({ studio });
|
|
125
163
|
this.interactions = createInteractionController({ studio, templates: templateRegistry });
|
|
126
164
|
this._cleanups = [];
|
|
127
165
|
this._instances = [];
|
|
128
166
|
this._modeListeners = new Set();
|
|
129
167
|
this._validationListeners = new Set();
|
|
168
|
+
this._panelSelectionListeners = new Set();
|
|
169
|
+
this._panelSelection = Object.freeze({ selection: null, selectedElement: null, trace: null });
|
|
130
170
|
this._modeViewports = new Map();
|
|
131
171
|
this._destroyed = false;
|
|
132
172
|
this._svgExportPreview = null;
|
|
@@ -150,18 +190,25 @@ export class BpmnStudioShell {
|
|
|
150
190
|
});
|
|
151
191
|
this.container.classList.add('nova-studio-shell');
|
|
152
192
|
this.themeController = new ThemeController({ root: this.container, theme, onChange: onThemeChange });
|
|
153
|
-
this.container
|
|
154
|
-
this.container.style.setProperty('--nova-
|
|
193
|
+
applyStudioControlMetrics(this.container, this._controlMetrics);
|
|
194
|
+
this.container.style.setProperty('--nova-left-width', `${resolvedUi.sidebarWidth.left}px`);
|
|
195
|
+
this.container.style.setProperty('--nova-right-width', `${resolvedUi.sidebarWidth.right}px`);
|
|
155
196
|
const context = {
|
|
156
197
|
studio,
|
|
157
198
|
shell: this,
|
|
158
199
|
actions: this.actions,
|
|
200
|
+
ui: this.ui,
|
|
159
201
|
getState: () => this.studio.getState(),
|
|
160
202
|
subscribe: (listener) => this.studio.subscribe(listener),
|
|
161
203
|
getMode: () => this.getMode(),
|
|
162
204
|
getAllowedModes: () => this.getAllowedModes(),
|
|
163
205
|
subscribeMode: (listener) => this.subscribeMode(listener),
|
|
164
206
|
subscribeValidation: (listener) => this.subscribeValidation(listener),
|
|
207
|
+
getSidebarState: () => this.getSidebarState(),
|
|
208
|
+
setSidebarCollapsed: (side, collapsed) => this.setSidebarCollapsed(side, collapsed),
|
|
209
|
+
subscribeSidebarChange: (listener) => this.subscribeSidebarChange(listener),
|
|
210
|
+
getPanelSelection: () => this.getPanelSelection(),
|
|
211
|
+
subscribePanelSelection: (listener) => this.subscribePanelSelection(listener),
|
|
165
212
|
iconRegistry,
|
|
166
213
|
paletteRegistry,
|
|
167
214
|
propertiesRegistry: this.propertiesRegistry,
|
|
@@ -178,6 +225,12 @@ export class BpmnStudioShell {
|
|
|
178
225
|
const cleanup = layout({ container, ...context, mount });
|
|
179
226
|
if (typeof cleanup === 'function') this._cleanups.push(cleanup);
|
|
180
227
|
} else this._buildDefault(context, mount);
|
|
228
|
+
if (!this._usesCustomLayout) {
|
|
229
|
+
this._updatePanelSelection();
|
|
230
|
+
this._recordCleanup(this.studio.subscribe((event) => {
|
|
231
|
+
if (['selectionChanged', 'modelChanged', 'scopeChanged'].includes(event.type)) this._updatePanelSelection();
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
181
234
|
}
|
|
182
235
|
|
|
183
236
|
_buildDefault(context, mount) {
|
|
@@ -194,18 +247,6 @@ export class BpmnStudioShell {
|
|
|
194
247
|
const projectionSwitch = node('nav', 'nova-studio-projection-switch is-hidden');
|
|
195
248
|
projectionSwitch.setAttribute('aria-label', '审批轨迹视图');
|
|
196
249
|
const projectionButtons = new Map();
|
|
197
|
-
const projectionOptions = this.projectionOptions || [
|
|
198
|
-
{ value: this.responsive ? 'auto' : 'approval', label: '实际路径' },
|
|
199
|
-
{ value: 'standard', label: '完整 BPMN' },
|
|
200
|
-
];
|
|
201
|
-
for (const item of projectionOptions) {
|
|
202
|
-
const button = node('button', '', item.label);
|
|
203
|
-
button.type = 'button';
|
|
204
|
-
button.dataset.projection = item.value;
|
|
205
|
-
button.addEventListener('click', () => this.setProjection(item.value));
|
|
206
|
-
projectionButtons.set(item.value, button);
|
|
207
|
-
projectionSwitch.appendChild(button);
|
|
208
|
-
}
|
|
209
250
|
const centerControls = node('div', 'nova-studio-center-controls');
|
|
210
251
|
centerControls.append(modeSwitch, projectionSwitch);
|
|
211
252
|
const tools = node('div', 'nova-studio-tools');
|
|
@@ -370,6 +411,10 @@ export class BpmnStudioShell {
|
|
|
370
411
|
statusbar.append(statusLeft, viewportTools);
|
|
371
412
|
canvasColumn.append(canvasStage, statusbar);
|
|
372
413
|
const right = node('aside', 'nova-studio-right');
|
|
414
|
+
const leftContent = node('div', 'nova-studio-sidebar-content nova-studio-left-content');
|
|
415
|
+
const rightContent = node('div', 'nova-studio-sidebar-content nova-studio-right-content');
|
|
416
|
+
left.appendChild(leftContent);
|
|
417
|
+
right.appendChild(rightContent);
|
|
373
418
|
body.append(left, canvasColumn, right);
|
|
374
419
|
this.container.append(header, body);
|
|
375
420
|
hydrateIcons(this.container, this.iconRegistry);
|
|
@@ -380,6 +425,8 @@ export class BpmnStudioShell {
|
|
|
380
425
|
_body: body,
|
|
381
426
|
_left: left,
|
|
382
427
|
_right: right,
|
|
428
|
+
_leftContent: leftContent,
|
|
429
|
+
_rightContent: rightContent,
|
|
383
430
|
_canvasColumn: canvasColumn,
|
|
384
431
|
_footer: statusbar,
|
|
385
432
|
_canvasHost: canvasHost,
|
|
@@ -399,7 +446,9 @@ export class BpmnStudioShell {
|
|
|
399
446
|
_defaultMount: mount,
|
|
400
447
|
_usesDefaultProperties: !this.slots.right,
|
|
401
448
|
});
|
|
449
|
+
this._sidebars = new StudioSidebars(this, { body, left, right, leftContent, rightContent });
|
|
402
450
|
this._rebuildModeButtons();
|
|
451
|
+
this._rebuildProjectionButtons();
|
|
403
452
|
this.canvas = mount.canvas(canvasHost, {
|
|
404
453
|
rendererOptions: {
|
|
405
454
|
...this.rendererOptions,
|
|
@@ -410,10 +459,9 @@ export class BpmnStudioShell {
|
|
|
410
459
|
},
|
|
411
460
|
});
|
|
412
461
|
this._renderScopePath();
|
|
413
|
-
if (this.slots.left) this._recordCleanup(mountSlot(this.slots.left,
|
|
414
|
-
else this.palette = mount.palette(
|
|
415
|
-
|
|
416
|
-
else this.properties = mount.properties(right, { canvas: this.canvas });
|
|
462
|
+
if (this.slots.left) this._recordCleanup(mountSlot(this.slots.left, leftContent, { ...context, canvas: this.canvas }));
|
|
463
|
+
else this.palette = mount.palette(leftContent, { canvas: this.canvas });
|
|
464
|
+
this._setRightSlot(this.slots.right);
|
|
417
465
|
const mountHeaderActions = () => {
|
|
418
466
|
if (this.slots.headerActions) {
|
|
419
467
|
this._recordCleanup(mountSlot(this.slots.headerActions, headerActions, { ...context, canvas: this.canvas }));
|
|
@@ -532,6 +580,26 @@ export class BpmnStudioShell {
|
|
|
532
580
|
hydrateIcons(this._modeSwitch, this.iconRegistry);
|
|
533
581
|
}
|
|
534
582
|
|
|
583
|
+
_rebuildProjectionButtons() {
|
|
584
|
+
if (!this._projectionSwitch) return;
|
|
585
|
+
this._projectionSwitch.replaceChildren();
|
|
586
|
+
this._projectionButtons = new Map();
|
|
587
|
+
const options = this.projectionOptions || [
|
|
588
|
+
{ value: this.responsive ? 'auto' : 'approval', label: '实际路径' },
|
|
589
|
+
{ value: 'standard', label: '完整 BPMN' },
|
|
590
|
+
];
|
|
591
|
+
for (const item of options) {
|
|
592
|
+
const button = node('button', '', item.label);
|
|
593
|
+
button.type = 'button';
|
|
594
|
+
button.dataset.projection = item.value;
|
|
595
|
+
button.addEventListener('click', () => this.setProjection(item.value));
|
|
596
|
+
this._projectionButtons.set(item.value, button);
|
|
597
|
+
this._projectionSwitch.appendChild(button);
|
|
598
|
+
}
|
|
599
|
+
hydrateIcons(this._projectionSwitch, this.iconRegistry);
|
|
600
|
+
this._syncProjectionButtons();
|
|
601
|
+
}
|
|
602
|
+
|
|
535
603
|
_syncModeChrome() {
|
|
536
604
|
this._modeButtons?.forEach((button, value) => {
|
|
537
605
|
const active = value === this.mode;
|
|
@@ -619,10 +687,11 @@ export class BpmnStudioShell {
|
|
|
619
687
|
: this.rendererOptions.runtimeTransitionDetailsRenderer,
|
|
620
688
|
onRuntimeTransitionDetailsOpen: this.rendererOptions.onRuntimeTransitionDetailsOpen,
|
|
621
689
|
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
690
|
this.rendererOptions.onTraceClick?.(payload);
|
|
625
691
|
},
|
|
692
|
+
_onPanelSelection: (trace) => {
|
|
693
|
+
if (viewer && this.viewer === viewer) this._updatePanelSelection(trace);
|
|
694
|
+
},
|
|
626
695
|
onProjectionChange: () => {
|
|
627
696
|
if (this.viewer === viewer) this._syncProjectionChrome();
|
|
628
697
|
},
|
|
@@ -630,7 +699,6 @@ export class BpmnStudioShell {
|
|
|
630
699
|
if (this.viewer === viewer) this._handleViewport(viewport);
|
|
631
700
|
},
|
|
632
701
|
onElementClick: (selection) => {
|
|
633
|
-
if (!selection) this._renderReadonlyDetails(null);
|
|
634
702
|
this.rendererOptions.onElementClick?.(selection);
|
|
635
703
|
},
|
|
636
704
|
});
|
|
@@ -717,17 +785,19 @@ export class BpmnStudioShell {
|
|
|
717
785
|
this.projection = mode === 'instance' ? this._instanceProjection : 'standard';
|
|
718
786
|
if (nextCanvas) this._instances.push(nextCanvas);
|
|
719
787
|
this._releaseInstance(previousCanvas);
|
|
788
|
+
if (this._sidebars?.motionViewer === previousViewer) this._sidebars.finishMotion({ resume: false });
|
|
720
789
|
previousViewer?.destroy?.();
|
|
721
790
|
|
|
722
791
|
if (this.palette) this.palette.canvas = this.canvas;
|
|
723
792
|
if (this._usesDefaultProperties) {
|
|
724
793
|
this._releaseInstance(this.properties);
|
|
725
794
|
this.properties = null;
|
|
726
|
-
if (this.canvas) this.properties = this._mountProperties(this.
|
|
795
|
+
if (this.canvas) this.properties = this._mountProperties(this._rightContent, { canvas: this.canvas });
|
|
727
796
|
else this._renderReadonlyDetails(null);
|
|
728
797
|
}
|
|
729
798
|
this._rebuildModeButtons();
|
|
730
799
|
this._syncModeChrome();
|
|
800
|
+
this._updatePanelSelection(null);
|
|
731
801
|
this._restoreModeViewport(mode);
|
|
732
802
|
if (emit && previousMode !== mode) this._emitModeChange(previousMode, source);
|
|
733
803
|
return true;
|
|
@@ -738,11 +808,56 @@ export class BpmnStudioShell {
|
|
|
738
808
|
this.rendererOptions.onViewportChange?.(viewport);
|
|
739
809
|
}
|
|
740
810
|
|
|
811
|
+
// Adapter-only seam: replace right content without remounting the workbench.
|
|
812
|
+
_setRightSlot(slot) {
|
|
813
|
+
if (this._destroyed || !this._rightContent) return;
|
|
814
|
+
const next = slot || null;
|
|
815
|
+
if (this._rightSlot === next) return;
|
|
816
|
+
this._rightSlotCleanup?.();
|
|
817
|
+
this._rightSlotCleanup = null;
|
|
818
|
+
this._releaseInstance(this.properties);
|
|
819
|
+
this.properties = null;
|
|
820
|
+
this._rightContent.replaceChildren();
|
|
821
|
+
this._rightSlot = next;
|
|
822
|
+
this._usesDefaultProperties = !next;
|
|
823
|
+
if (next) {
|
|
824
|
+
const shell = this;
|
|
825
|
+
const context = { ...this._defaultContext, get canvas() { return shell.canvas; } };
|
|
826
|
+
this._rightSlotCleanup = mountSlot(next, this._rightContent, context);
|
|
827
|
+
} else if (this.canvas) this.properties = this._mountProperties(this._rightContent, { canvas: this.canvas });
|
|
828
|
+
else this._renderReadonlyDetails(this._readonlyPanelDetails());
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
getPanelSelection() { return this._panelSelection; }
|
|
832
|
+
subscribePanelSelection(listener) {
|
|
833
|
+
if (typeof listener !== 'function') throw new TypeError('subscribePanelSelection() requires a listener function.');
|
|
834
|
+
if (this._destroyed) return () => {};
|
|
835
|
+
this._panelSelectionListeners.add(listener);
|
|
836
|
+
return () => this._panelSelectionListeners.delete(listener);
|
|
837
|
+
}
|
|
838
|
+
_readonlyPanelDetails() {
|
|
839
|
+
const { selection, selectedElement, trace } = this._panelSelection;
|
|
840
|
+
return selectedElement ? { kind: selection?.kind, element: selectedElement, presentation: trace?.presentation, transition: trace?.transition } : null;
|
|
841
|
+
}
|
|
842
|
+
_updatePanelSelection(trace = this._panelSelection.trace) {
|
|
843
|
+
if (this._destroyed) return;
|
|
844
|
+
const next = createPanelSelection(this.studio, this.mode === 'design' ? null : this.viewer, trace);
|
|
845
|
+
if (samePanelSelection(next, this._panelSelection)) {
|
|
846
|
+
if (this.mode !== 'design' && !next.selectedElement) this._renderReadonlyDetails(null);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
this._panelSelection = next;
|
|
850
|
+
if (this.mode !== 'design') this._renderReadonlyDetails(this._readonlyPanelDetails());
|
|
851
|
+
for (const listener of [...this._panelSelectionListeners]) {
|
|
852
|
+
try { listener(next); } catch (error) { console.error('BpmnStudioShell panel selection listener failed.', error); }
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
741
856
|
_renderReadonlyDetails(selection) {
|
|
742
|
-
if (!this._usesDefaultProperties || !this.
|
|
857
|
+
if (!this._usesDefaultProperties || !this._rightContent) return;
|
|
743
858
|
const model = this.studio.model;
|
|
744
859
|
const element = selection?.element;
|
|
745
|
-
this.
|
|
860
|
+
this._rightContent.innerHTML = '';
|
|
746
861
|
const panel = node('div', 'nova-studio-readonly-panel');
|
|
747
862
|
const header = node('header', 'nova-studio-readonly-header');
|
|
748
863
|
header.append(node('strong', '', element?.name || (selection?.kind === 'edge' ? '流程连线' : model.name)), node('span', '', element?.id || model.id));
|
|
@@ -769,7 +884,7 @@ export class BpmnStudioShell {
|
|
|
769
884
|
body.appendChild(row);
|
|
770
885
|
}
|
|
771
886
|
panel.appendChild(body);
|
|
772
|
-
this.
|
|
887
|
+
this._rightContent.appendChild(panel);
|
|
773
888
|
}
|
|
774
889
|
|
|
775
890
|
_zoomActive(factor) { (this.canvas || this.viewer)?.zoomBy?.(factor); }
|
|
@@ -781,6 +896,91 @@ export class BpmnStudioShell {
|
|
|
781
896
|
if (this.canvas) this.canvas.fitView(72);
|
|
782
897
|
else this.viewer?.fitView?.();
|
|
783
898
|
}
|
|
899
|
+
getConfig() {
|
|
900
|
+
const state = this.studio.getState();
|
|
901
|
+
const modeling = Object.freeze({
|
|
902
|
+
propertiesProfile: state.propertiesProfile,
|
|
903
|
+
...(state.allowedNodeTypes ? { allowedNodeTypes: Object.freeze([...state.allowedNodeTypes]) } : {}),
|
|
904
|
+
...(state.allowedEdgeTypes ? { allowedEdgeTypes: Object.freeze([...state.allowedEdgeTypes]) } : {}),
|
|
905
|
+
});
|
|
906
|
+
return Object.freeze({
|
|
907
|
+
modeling,
|
|
908
|
+
ui: this._config.ui,
|
|
909
|
+
viewer: this._config.viewer,
|
|
910
|
+
...(this._config.export ? { export: this._config.export } : {}),
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
setConfig(config) {
|
|
914
|
+
if (this._destroyed) return this.getConfig();
|
|
915
|
+
const next = normalizeStudioConfig(config, {
|
|
916
|
+
fallback: this._configFallback,
|
|
917
|
+
legacy: this._legacyConfig,
|
|
918
|
+
});
|
|
919
|
+
const hasRegions = config?.ui
|
|
920
|
+
&& Object.prototype.hasOwnProperty.call(config.ui, 'regions')
|
|
921
|
+
&& config.ui.regions !== undefined
|
|
922
|
+
&& config.ui.regions !== null;
|
|
923
|
+
if (this._usesCustomLayout && hasRegions) {
|
|
924
|
+
throw new Error('BpmnStudioShell config.ui.regions is unavailable when a custom layout is active.');
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const previous = this._config;
|
|
928
|
+
const previousSidebars = this._sidebars?.getState();
|
|
929
|
+
const nextMetrics = resolveStudioControlMetrics(next.ui.controlSize);
|
|
930
|
+
const regionsChanged = !sameConfigValue(previous.ui.regions, next.ui.regions);
|
|
931
|
+
const projectionOptionsChanged = !sameConfigValue(previous.viewer.projectionOptions, next.viewer.projectionOptions);
|
|
932
|
+
const responsiveChanged = previous.viewer.responsive !== next.viewer.responsive;
|
|
933
|
+
const displayChanged = ['timeline', 'runtimeDetails', 'runtimeTraceOptions']
|
|
934
|
+
.some((name) => !sameConfigValue(previous.viewer[name], next.viewer[name]));
|
|
935
|
+
const exportChanged = !sameConfigValue(previous.export, next.export);
|
|
936
|
+
const nextDefaultProjection = next.viewer.responsive ? 'auto' : 'approval';
|
|
937
|
+
const projectionChanged = responsiveChanged
|
|
938
|
+
&& !this._projectionExplicit
|
|
939
|
+
&& this._instanceProjection !== nextDefaultProjection;
|
|
940
|
+
const viewport = this.viewer?.activeProjection === 'compact'
|
|
941
|
+
? null
|
|
942
|
+
: (this.canvas?.renderer || this.viewer?.renderer)?.getViewportState?.();
|
|
943
|
+
|
|
944
|
+
this._config = next;
|
|
945
|
+
this._controlMetrics = nextMetrics;
|
|
946
|
+
applyStudioControlMetrics(this.container, nextMetrics);
|
|
947
|
+
this.container.style.setProperty('--nova-left-width', `${next.ui.sidebarWidth.left}px`);
|
|
948
|
+
this.container.style.setProperty('--nova-right-width', `${next.ui.sidebarWidth.right}px`);
|
|
949
|
+
this._regions = normalizeStudioRegions(next.ui.regions);
|
|
950
|
+
this.responsive = next.viewer.responsive;
|
|
951
|
+
this.projectionOptions = next.viewer.projectionOptions || null;
|
|
952
|
+
this.rendererOptions.timeline = next.viewer.timeline;
|
|
953
|
+
this.rendererOptions.runtimeDetails = next.viewer.runtimeDetails;
|
|
954
|
+
this.rendererOptions.runtimeTraceOptions = next.viewer.runtimeTraceOptions;
|
|
955
|
+
this.svgExport = next.export || null;
|
|
956
|
+
this.rendererOptions.svgExport = this.svgExport;
|
|
957
|
+
if (projectionChanged) {
|
|
958
|
+
this._instanceProjection = nextDefaultProjection;
|
|
959
|
+
if (this.mode === 'instance') this.projection = nextDefaultProjection;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (regionsChanged) this._applyRegions({ fit: false, sidebars: false });
|
|
963
|
+
if (previousSidebars) this._sidebars.updateConfig(previousSidebars);
|
|
964
|
+
if (projectionOptionsChanged || responsiveChanged) this._rebuildProjectionButtons();
|
|
965
|
+
if (this.canvas?.renderer && exportChanged) this.canvas.renderer.svgExportOptions = this.svgExport || {};
|
|
966
|
+
if (this.viewer) {
|
|
967
|
+
this.viewer.responsive = this.responsive;
|
|
968
|
+
this.viewer.options.responsive = this.responsive;
|
|
969
|
+
if (exportChanged) this.viewer.svgExportOptions = this.svgExport || {};
|
|
970
|
+
if (projectionChanged && this.mode === 'instance') this.viewer.projection = this._instanceProjection;
|
|
971
|
+
if (displayChanged) {
|
|
972
|
+
this.viewer.setDisplayOptions({
|
|
973
|
+
timeline: next.viewer.timeline,
|
|
974
|
+
runtimeDetails: next.viewer.runtimeDetails,
|
|
975
|
+
runtimeTraceOptions: next.viewer.runtimeTraceOptions,
|
|
976
|
+
replace: true,
|
|
977
|
+
});
|
|
978
|
+
} else if (projectionChanged && this.mode === 'instance') this.viewer.refresh();
|
|
979
|
+
}
|
|
980
|
+
if (viewport) (this.canvas?.renderer || this.viewer?.renderer)?.setViewportState?.(viewport);
|
|
981
|
+
this._syncProjectionButtons();
|
|
982
|
+
return this.getConfig();
|
|
983
|
+
}
|
|
784
984
|
getMode() { return this.mode; }
|
|
785
985
|
getAllowedModes() { return Object.freeze([...this.allowedModes]); }
|
|
786
986
|
subscribeMode(listener) {
|
|
@@ -814,37 +1014,61 @@ export class BpmnStudioShell {
|
|
|
814
1014
|
if (this.mode === 'instance') this.viewer?.setRuntime(runtime);
|
|
815
1015
|
}
|
|
816
1016
|
setProjection(projection) {
|
|
1017
|
+
this._projectionExplicit = true;
|
|
817
1018
|
this._instanceProjection = projection;
|
|
818
1019
|
if (this.mode === 'instance') this.projection = projection;
|
|
819
1020
|
this._syncProjectionButtons();
|
|
820
1021
|
if (this.mode === 'instance') this.viewer?.setProjection?.(projection);
|
|
821
1022
|
}
|
|
822
|
-
getRegions() { return Object.freeze({ ...this._regions }); }
|
|
1023
|
+
getRegions() { return Object.freeze({ ...this._regions, left: this._isSidebarHidden('left') ? 'hidden' : 'default', right: this._isSidebarHidden('right') ? 'hidden' : 'default' }); }
|
|
823
1024
|
setRegions(regions) {
|
|
824
1025
|
if (this._usesCustomLayout) {
|
|
825
1026
|
throw new Error('BpmnStudioShell setRegions() is unavailable when a custom layout is active.');
|
|
826
1027
|
}
|
|
827
|
-
const next =
|
|
1028
|
+
const next = normalizeStudioRegions(regions, this._regions);
|
|
828
1029
|
const changed = STUDIO_SHELL_REGIONS.some((name) => next[name] !== this._regions[name]);
|
|
829
1030
|
this._regions = next;
|
|
830
|
-
if (
|
|
1031
|
+
if (this._config) {
|
|
1032
|
+
this._config = Object.freeze({
|
|
1033
|
+
...this._config,
|
|
1034
|
+
ui: Object.freeze({ ...this._config.ui, regions: Object.freeze(normalizeStudioRegions(regions, this._config.ui.regions)) }),
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
if (changed || regions?.right === 'default') this._applyRegions();
|
|
831
1038
|
return this.getRegions();
|
|
832
1039
|
}
|
|
833
|
-
|
|
1040
|
+
_isSidebarHidden(side) {
|
|
1041
|
+
if (this._regions[side] === 'hidden') return true;
|
|
1042
|
+
if (side === 'left') return this.mode !== 'design';
|
|
1043
|
+
return this.mode === 'instance' && this._config?.ui.regions.right !== 'default';
|
|
1044
|
+
}
|
|
1045
|
+
getSidebarState() {
|
|
1046
|
+
if (this._sidebars) return this._sidebars.getState();
|
|
1047
|
+
return Object.freeze(Object.fromEntries(['left', 'right'].map((side) => [side, Object.freeze({ collapsed: false, hidden: true, collapsible: false })])));
|
|
1048
|
+
}
|
|
1049
|
+
setSidebarCollapsed(side, collapsed) { return this._sidebars?.setCollapsed(side, collapsed) || false; }
|
|
1050
|
+
subscribeSidebarChange(listener) {
|
|
1051
|
+
if (typeof listener !== 'function') throw new TypeError('subscribeSidebarChange() requires a listener function.');
|
|
1052
|
+
return this._sidebars?.subscribe(listener) || (() => {});
|
|
1053
|
+
}
|
|
1054
|
+
_applyRegions({ fit = true, sidebars = true } = {}) {
|
|
834
1055
|
if (this._usesCustomLayout || !this._body) return;
|
|
835
1056
|
const readonly = this.mode !== 'design';
|
|
836
1057
|
const headerHidden = this._regions.header === 'hidden';
|
|
837
|
-
const leftHidden =
|
|
838
|
-
const rightHidden = this.
|
|
1058
|
+
const leftHidden = this._isSidebarHidden('left');
|
|
1059
|
+
const rightHidden = this._isSidebarHidden('right');
|
|
839
1060
|
const footerHidden = this._regions.footer === 'hidden';
|
|
840
1061
|
this.container.classList.toggle('is-header-hidden', headerHidden);
|
|
841
1062
|
this._body.classList.toggle('is-left-hidden', leftHidden);
|
|
842
1063
|
this._body.classList.toggle('is-right-hidden', rightHidden);
|
|
843
1064
|
this._canvasColumn?.classList.toggle('is-footer-hidden', footerHidden);
|
|
844
1065
|
if (this._header) this._header.hidden = headerHidden;
|
|
845
|
-
if (this.
|
|
846
|
-
|
|
1066
|
+
if (!this._sidebars) {
|
|
1067
|
+
if (this._left) this._left.hidden = leftHidden;
|
|
1068
|
+
if (this._right) this._right.hidden = rightHidden;
|
|
1069
|
+
}
|
|
847
1070
|
if (this._footer) this._footer.hidden = footerHidden;
|
|
1071
|
+
if (sidebars) this._sidebars?.apply({ animate: false });
|
|
848
1072
|
if (fit) requestAnimationFrame(() => this._fitActive());
|
|
849
1073
|
}
|
|
850
1074
|
setTheme(theme) { return this.themeController.setTheme(theme); }
|
|
@@ -987,6 +1211,10 @@ export class BpmnStudioShell {
|
|
|
987
1211
|
this._destroyed = true;
|
|
988
1212
|
this._modeListeners.clear();
|
|
989
1213
|
this._validationListeners.clear();
|
|
1214
|
+
this._panelSelectionListeners.clear();
|
|
1215
|
+
this._sidebars?.destroy();
|
|
1216
|
+
this._rightSlotCleanup?.();
|
|
1217
|
+
this._rightSlotCleanup = null;
|
|
990
1218
|
this._svgExportPreview?.close?.({ immediate: true });
|
|
991
1219
|
this._svgExportPreview = null;
|
|
992
1220
|
this._cleanups.splice(0).reverse().forEach((cleanup) => cleanup?.());
|