@bpmn-nova/studio 0.3.0-preview → 0.3.2-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 +245 -20
- package/dist/canvas.js +7 -4
- package/dist/context-menu.js +2 -2
- package/dist/controller.js +84 -6
- package/dist/index.d.ts +138 -38
- package/dist/index.js +12 -11
- package/dist/interactions.js +1 -1
- package/dist/modules/bpmn-model/index.d.ts +11 -0
- package/dist/modules/bpmn-model/index.js +703 -0
- package/dist/modules/core/containment.js +590 -0
- package/dist/modules/core/gateway.js +72 -0
- package/dist/modules/core/history.js +32 -0
- package/dist/modules/core/index.d.ts +268 -0
- package/dist/modules/core/index.js +7 -0
- package/dist/modules/core/layout.js +644 -0
- package/dist/modules/core/model.js +287 -0
- package/dist/modules/core/runtime-transition-route.js +360 -0
- package/dist/modules/core/scope.js +99 -0
- package/dist/modules/designer/index.d.ts +79 -0
- package/dist/modules/designer/index.js +607 -0
- package/dist/modules/engine-activiti/index.d.ts +19 -0
- package/dist/modules/engine-activiti/index.js +160 -0
- package/dist/modules/engine-flowable/index.d.ts +19 -0
- package/dist/modules/engine-flowable/index.js +160 -0
- package/dist/modules/export-svg/index.d.ts +112 -0
- package/dist/modules/export-svg/index.js +2 -0
- package/dist/modules/export-svg/preview.js +327 -0
- package/dist/modules/export-svg/render.js +718 -0
- package/dist/modules/icons/index.d.ts +24 -0
- package/dist/modules/icons/index.js +264 -0
- package/dist/modules/palette/index.d.ts +74 -0
- package/dist/modules/palette/index.js +99 -0
- package/dist/modules/palette/panel.js +99 -0
- package/dist/modules/properties/index.d.ts +20 -0
- package/dist/modules/properties/index.js +19 -0
- package/dist/modules/properties-activiti/index.d.ts +3 -0
- package/dist/modules/properties-activiti/index.js +97 -0
- package/dist/modules/properties-bpmn/index.d.ts +3 -0
- package/dist/modules/properties-bpmn/index.js +518 -0
- package/dist/modules/properties-core/index.d.ts +124 -0
- package/dist/modules/properties-core/index.js +312 -0
- package/dist/modules/properties-flowable/index.d.ts +3 -0
- package/dist/modules/properties-flowable/index.js +114 -0
- package/dist/modules/properties-renderer/index.d.ts +25 -0
- package/dist/modules/properties-renderer/index.js +491 -0
- package/dist/modules/renderer-svg/index.d.ts +118 -0
- package/dist/modules/renderer-svg/index.js +1460 -0
- package/dist/modules/runtime/index.d.ts +169 -0
- package/dist/modules/runtime/index.js +535 -0
- package/dist/modules/theme/index.d.ts +95 -0
- package/dist/modules/theme/index.js +368 -0
- package/dist/modules/viewer/index.d.ts +265 -0
- package/dist/modules/viewer/index.js +1011 -0
- package/dist/modules/viewer/runtime-content.js +123 -0
- package/dist/modules/viewer/runtime-details-motion.js +228 -0
- package/dist/modules/viewer/runtime-trace.js +574 -0
- package/dist/modules/viewer/timeline.js +276 -0
- package/dist/selection-layout.js +1 -1
- package/dist/shell.js +210 -26
- package/dist/styles.css +116 -7
- package/llms-full.txt +3082 -0
- package/llms.txt +225 -0
- package/package.json +39 -16
package/dist/shell.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { createDefaultIconRegistry, hydrateIcons } from '
|
|
2
|
-
import { createDefaultPaletteRegistry, PalettePanel } from '
|
|
3
|
-
import { createDefaultPropertiesRegistry, PropertiesPanel } from '
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { createDefaultIconRegistry, hydrateIcons } from './modules/icons/index.js';
|
|
2
|
+
import { createDefaultPaletteRegistry, PalettePanel } from './modules/palette/index.js';
|
|
3
|
+
import { createDefaultPropertiesRegistry, PropertiesPanel } from './modules/properties/index.js';
|
|
4
|
+
import { BpmnViewer } from './modules/viewer/index.js';
|
|
5
|
+
import { ThemeController } from './modules/theme/index.js';
|
|
6
|
+
import { openSvgExportPreview } from './modules/export-svg/index.js';
|
|
7
7
|
import { BpmnCanvas } from './canvas.js';
|
|
8
8
|
import { createDefaultContextMenuRegistry } from './context-menu.js';
|
|
9
9
|
import { createInteractionController, createTemplateRegistry } from './interactions.js';
|
|
@@ -28,6 +28,39 @@ function mountSlot(slot, container, context) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
const STUDIO_MODES = ['design', 'viewer', 'instance'];
|
|
32
|
+
const STUDIO_SHELL_REGIONS = ['header', 'left', 'right', 'footer'];
|
|
33
|
+
const DEFAULT_STUDIO_SHELL_REGIONS = Object.freeze({
|
|
34
|
+
header: 'default',
|
|
35
|
+
left: 'default',
|
|
36
|
+
right: 'default',
|
|
37
|
+
footer: 'default',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function normalizeAllowedModes(allowedModes) {
|
|
41
|
+
if (allowedModes === undefined || allowedModes === null) return [...STUDIO_MODES];
|
|
42
|
+
if (!Array.isArray(allowedModes)) throw new Error('allowedModes must be an array of Studio modes.');
|
|
43
|
+
const normalized = [...new Set(allowedModes.filter((mode) => STUDIO_MODES.includes(mode)))];
|
|
44
|
+
if (!normalized.length || normalized.length !== allowedModes.length) {
|
|
45
|
+
throw new Error('allowedModes must contain one or more unique design, viewer, or instance modes.');
|
|
46
|
+
}
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeRegions(regions, current = DEFAULT_STUDIO_SHELL_REGIONS) {
|
|
51
|
+
if (regions === undefined || regions === null) return { ...current };
|
|
52
|
+
if (!regions || typeof regions !== 'object' || Array.isArray(regions)) {
|
|
53
|
+
throw new Error('regions must be an object containing header, left, right, or footer modes.');
|
|
54
|
+
}
|
|
55
|
+
for (const name of Object.keys(regions)) {
|
|
56
|
+
if (!STUDIO_SHELL_REGIONS.includes(name)) throw new Error(`Unknown Studio Shell region: ${name}.`);
|
|
57
|
+
if (!['default', 'hidden'].includes(regions[name])) {
|
|
58
|
+
throw new Error(`Studio Shell region "${name}" must be "default" or "hidden".`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { ...current, ...regions };
|
|
62
|
+
}
|
|
63
|
+
|
|
31
64
|
export class BpmnStudioShell {
|
|
32
65
|
constructor({
|
|
33
66
|
container,
|
|
@@ -40,8 +73,10 @@ export class BpmnStudioShell {
|
|
|
40
73
|
rendererOptions = {},
|
|
41
74
|
slots = {},
|
|
42
75
|
layout = null,
|
|
43
|
-
|
|
76
|
+
regions = null,
|
|
77
|
+
runtime = null,
|
|
44
78
|
mode = 'design',
|
|
79
|
+
allowedModes = null,
|
|
45
80
|
projection = undefined,
|
|
46
81
|
responsive = false,
|
|
47
82
|
projectionOptions = null,
|
|
@@ -49,11 +84,17 @@ export class BpmnStudioShell {
|
|
|
49
84
|
rightWidth = 360,
|
|
50
85
|
theme = null,
|
|
51
86
|
runtimeAppearance = null,
|
|
87
|
+
svgExport = null,
|
|
52
88
|
onThemeChange = null,
|
|
53
89
|
} = {}) {
|
|
54
90
|
if (!container || !studio) throw new Error('BpmnStudioShell requires container and studio.');
|
|
91
|
+
if (typeof layout === 'function' && regions !== null && regions !== undefined) {
|
|
92
|
+
throw new Error('BpmnStudioShell options "layout" and "regions" cannot be used together.');
|
|
93
|
+
}
|
|
94
|
+
const resolvedAllowedModes = normalizeAllowedModes(allowedModes);
|
|
95
|
+
const resolvedMode = resolvedAllowedModes.includes(mode) ? mode : resolvedAllowedModes[0];
|
|
55
96
|
const instanceProjection = projection || (responsive ? 'auto' : 'approval');
|
|
56
|
-
const activeProjection =
|
|
97
|
+
const activeProjection = resolvedMode === 'instance' ? instanceProjection : 'standard';
|
|
57
98
|
Object.assign(this, {
|
|
58
99
|
container,
|
|
59
100
|
studio,
|
|
@@ -64,22 +105,57 @@ export class BpmnStudioShell {
|
|
|
64
105
|
rendererOptions,
|
|
65
106
|
slots,
|
|
66
107
|
runtime,
|
|
67
|
-
mode,
|
|
108
|
+
mode: resolvedMode,
|
|
109
|
+
allowedModes: resolvedAllowedModes,
|
|
68
110
|
projection: activeProjection,
|
|
69
111
|
_instanceProjection: instanceProjection,
|
|
70
112
|
responsive,
|
|
71
113
|
projectionOptions,
|
|
72
114
|
runtimeAppearance,
|
|
115
|
+
svgExport: svgExport || rendererOptions.svgExport || null,
|
|
116
|
+
_usesCustomLayout: typeof layout === 'function',
|
|
117
|
+
_regions: normalizeRegions(regions),
|
|
73
118
|
});
|
|
74
119
|
this.propertiesRegistry = propertiesRegistry || createDefaultPropertiesRegistry({ studio });
|
|
75
120
|
this.interactions = createInteractionController({ studio, templates: templateRegistry });
|
|
76
121
|
this._cleanups = [];
|
|
77
122
|
this._instances = [];
|
|
123
|
+
this._svgExportPreview = null;
|
|
124
|
+
this.actions = Object.freeze({
|
|
125
|
+
undo: () => this.studio.undo(),
|
|
126
|
+
redo: () => this.studio.redo(),
|
|
127
|
+
beautify: (options) => {
|
|
128
|
+
this.studio.commands.beautify(options);
|
|
129
|
+
requestAnimationFrame(() => this._fitActive());
|
|
130
|
+
},
|
|
131
|
+
rerouteEdges: (options) => {
|
|
132
|
+
this.studio.commands.rerouteEdges(options);
|
|
133
|
+
requestAnimationFrame(() => this._fitActive());
|
|
134
|
+
},
|
|
135
|
+
fitView: () => this.fitView(),
|
|
136
|
+
validate: () => this.studio.validate(),
|
|
137
|
+
importXml: (xml, engine) => this.studio.importXml(xml, engine),
|
|
138
|
+
exportXml: (engine) => this.studio.exportXml(engine),
|
|
139
|
+
exportSvg: (options) => this.exportSvg(options),
|
|
140
|
+
openSvgExportPreview: (options) => this.openSvgExportPreview(options),
|
|
141
|
+
});
|
|
78
142
|
this.container.classList.add('nova-studio-shell');
|
|
79
143
|
this.themeController = new ThemeController({ root: this.container, theme, onChange: onThemeChange });
|
|
80
144
|
this.container.style.setProperty('--nova-left-width', `${leftWidth}px`);
|
|
81
145
|
this.container.style.setProperty('--nova-right-width', `${rightWidth}px`);
|
|
82
|
-
const context = {
|
|
146
|
+
const context = {
|
|
147
|
+
studio,
|
|
148
|
+
shell: this,
|
|
149
|
+
actions: this.actions,
|
|
150
|
+
getState: () => this.studio.getState(),
|
|
151
|
+
subscribe: (listener) => this.studio.subscribe(listener),
|
|
152
|
+
iconRegistry,
|
|
153
|
+
paletteRegistry,
|
|
154
|
+
propertiesRegistry: this.propertiesRegistry,
|
|
155
|
+
templateRegistry,
|
|
156
|
+
contextMenuRegistry,
|
|
157
|
+
interactions: this.interactions,
|
|
158
|
+
};
|
|
83
159
|
const mount = {
|
|
84
160
|
canvas: (host, options = {}) => this._mountCanvas(host, options),
|
|
85
161
|
palette: (host, options = {}) => this._mountPalette(host, options),
|
|
@@ -105,7 +181,7 @@ export class BpmnStudioShell {
|
|
|
105
181
|
{ value: 'design', label: '流程设计', iconId: 'ui.design' },
|
|
106
182
|
{ value: 'viewer', label: '流程展示', iconId: 'ui.preview' },
|
|
107
183
|
{ value: 'instance', label: '审批轨迹', iconId: 'ui.trace' },
|
|
108
|
-
]) {
|
|
184
|
+
].filter((item) => this.allowedModes.includes(item.value))) {
|
|
109
185
|
const button = node('button');
|
|
110
186
|
button.type = 'button';
|
|
111
187
|
button.setAttribute('data-mode', item.value);
|
|
@@ -140,8 +216,8 @@ export class BpmnStudioShell {
|
|
|
140
216
|
};
|
|
141
217
|
|
|
142
218
|
const divider = () => tools.appendChild(node('span', 'nova-studio-tool-divider'));
|
|
143
|
-
const undo = tool('', '撤销 Ctrl/⌘+Z', () => this.
|
|
144
|
-
const redo = tool('', '重做 Ctrl/⌘+Y', () => this.
|
|
219
|
+
const undo = tool('', '撤销 Ctrl/⌘+Z', () => this.actions.undo(), 'is-icon', 'ui.undo');
|
|
220
|
+
const redo = tool('', '重做 Ctrl/⌘+Y', () => this.actions.redo(), 'is-icon', 'ui.redo');
|
|
145
221
|
divider();
|
|
146
222
|
const beautifySplit = node('div', 'nova-studio-beautify-split');
|
|
147
223
|
const beautifyButton = node('button', 'nova-studio-tool nova-studio-beautify is-magic');
|
|
@@ -196,7 +272,7 @@ export class BpmnStudioShell {
|
|
|
196
272
|
});
|
|
197
273
|
beautifySplit.append(beautifyButton, beautifyCaret, beautifyMenu);
|
|
198
274
|
tools.appendChild(beautifySplit);
|
|
199
|
-
const fit = tool('最佳视图', '适应画布内容', () => this.
|
|
275
|
+
const fit = tool('最佳视图', '适应画布内容', () => this.actions.fitView(), '', 'ui.fitView');
|
|
200
276
|
|
|
201
277
|
const importInput = node('input', 'nova-studio-import-input');
|
|
202
278
|
importInput.type = 'file';
|
|
@@ -204,12 +280,39 @@ export class BpmnStudioShell {
|
|
|
204
280
|
importInput.hidden = true;
|
|
205
281
|
const validateButton = tool('校验', '校验流程结构', () => this._showValidationStatus(), 'nova-studio-validate');
|
|
206
282
|
const importButton = tool('导入', '导入 BPMN XML', () => importInput.click(), 'nova-studio-import');
|
|
207
|
-
const
|
|
283
|
+
const exportMenu = node('div', 'nova-studio-export-menu');
|
|
284
|
+
const exportButton = node('button', 'nova-studio-tool nova-studio-export is-primary');
|
|
285
|
+
exportButton.type = 'button';
|
|
286
|
+
exportButton.title = '导出当前内容';
|
|
287
|
+
exportButton.append(node('span', '', '导出'), iconNode('ui.chevron', 'nova-icon nova-icon-xs'));
|
|
288
|
+
const exportPopover = node('div', 'nova-studio-export-popover is-hidden');
|
|
289
|
+
const exportSvgButton = node('button', 'nova-studio-export-option');
|
|
290
|
+
exportSvgButton.type = 'button';
|
|
291
|
+
exportSvgButton.append(iconNode('ui.preview'), node('strong', '', '导出 SVG'), node('small', '', '预览确认后下载'));
|
|
292
|
+
exportSvgButton.addEventListener('click', () => {
|
|
293
|
+
exportPopover.classList.add('is-hidden');
|
|
294
|
+
exportButton.focus({ preventScroll: true });
|
|
295
|
+
this.actions.openSvgExportPreview();
|
|
296
|
+
});
|
|
297
|
+
const exportBpmnButton = node('button', 'nova-studio-export-option');
|
|
298
|
+
exportBpmnButton.type = 'button';
|
|
299
|
+
exportBpmnButton.append(iconNode('ui.design'), node('strong', '', '导出 BPMN'), node('small', '', '下载流程 XML'));
|
|
300
|
+
exportBpmnButton.addEventListener('click', () => {
|
|
301
|
+
exportPopover.classList.add('is-hidden');
|
|
302
|
+
this._exportBpmn();
|
|
303
|
+
});
|
|
304
|
+
exportPopover.append(exportSvgButton, exportBpmnButton);
|
|
305
|
+
exportButton.addEventListener('click', (event) => {
|
|
306
|
+
event.stopPropagation();
|
|
307
|
+
exportPopover.classList.toggle('is-hidden');
|
|
308
|
+
});
|
|
309
|
+
exportMenu.append(exportButton, exportPopover);
|
|
310
|
+
tools.appendChild(exportMenu);
|
|
208
311
|
importInput.addEventListener('change', async () => {
|
|
209
312
|
const file = importInput.files?.[0];
|
|
210
313
|
if (!file) return;
|
|
211
314
|
try {
|
|
212
|
-
this.
|
|
315
|
+
this.actions.importXml(await file.text(), this.studio.model.engine);
|
|
213
316
|
this._setStatus('已导入 BPMN 文件', 'ok');
|
|
214
317
|
requestAnimationFrame(() => this._fitActive());
|
|
215
318
|
} catch (error) {
|
|
@@ -264,9 +367,13 @@ export class BpmnStudioShell {
|
|
|
264
367
|
this.container.append(header, body);
|
|
265
368
|
hydrateIcons(this.container, this.iconRegistry);
|
|
266
369
|
Object.assign(this, {
|
|
370
|
+
_header: header,
|
|
371
|
+
_headerStart: brand,
|
|
267
372
|
_body: body,
|
|
268
373
|
_left: left,
|
|
269
374
|
_right: right,
|
|
375
|
+
_canvasColumn: canvasColumn,
|
|
376
|
+
_footer: statusbar,
|
|
270
377
|
_canvasHost: canvasHost,
|
|
271
378
|
_floatingHead: floatingHead,
|
|
272
379
|
_scopeBack: scopeBack,
|
|
@@ -275,7 +382,10 @@ export class BpmnStudioShell {
|
|
|
275
382
|
_projectionSwitch: projectionSwitch,
|
|
276
383
|
_projectionButtons: projectionButtons,
|
|
277
384
|
_viewportTools: viewportTools,
|
|
278
|
-
_designControls: [undo, redo, beautifySplit, validateButton, importButton
|
|
385
|
+
_designControls: [undo, redo, beautifySplit, validateButton, importButton],
|
|
386
|
+
_exportMenu: exportMenu,
|
|
387
|
+
_exportPopover: exportPopover,
|
|
388
|
+
_exportBpmnButton: exportBpmnButton,
|
|
279
389
|
_defaultContext: context,
|
|
280
390
|
_defaultMount: mount,
|
|
281
391
|
_usesDefaultProperties: !this.slots.right,
|
|
@@ -294,8 +404,15 @@ export class BpmnStudioShell {
|
|
|
294
404
|
else this.palette = mount.palette(left, { canvas: this.canvas });
|
|
295
405
|
if (this.slots.right) this._recordCleanup(mountSlot(this.slots.right, right, { ...context, canvas: this.canvas }));
|
|
296
406
|
else this.properties = mount.properties(right, { canvas: this.canvas });
|
|
297
|
-
if (this.slots.header) {
|
|
407
|
+
if (this.slots.header) {
|
|
408
|
+
header.innerHTML = '';
|
|
409
|
+
this._recordCleanup(mountSlot(this.slots.header, header, { ...context, canvas: this.canvas }));
|
|
410
|
+
} else if (this.slots.headerStart) {
|
|
411
|
+
brand.innerHTML = '';
|
|
412
|
+
this._recordCleanup(mountSlot(this.slots.headerStart, brand, { ...context, canvas: this.canvas }));
|
|
413
|
+
}
|
|
298
414
|
if (this.slots.footer) { statusbar.innerHTML = ''; this._recordCleanup(mountSlot(this.slots.footer, statusbar, { ...context, canvas: this.canvas })); }
|
|
415
|
+
hydrateIcons(this.container, this.iconRegistry);
|
|
299
416
|
this._offState = this.studio.subscribe((event) => {
|
|
300
417
|
if (event.type === 'historyChanged' || event.type === 'modelChanged') {
|
|
301
418
|
undo.disabled = !this.studio.history.canUndo;
|
|
@@ -319,6 +436,7 @@ export class BpmnStudioShell {
|
|
|
319
436
|
this._setStatus(`${initialGraph.nodes.length} 节点 · ${initialGraph.edges.length} 连线`, 'ok');
|
|
320
437
|
const closeBeautify = (event) => {
|
|
321
438
|
if (!beautifySplit.contains(event.target)) beautifyMenu.classList.add('is-hidden');
|
|
439
|
+
if (!exportMenu.contains(event.target)) exportPopover.classList.add('is-hidden');
|
|
322
440
|
};
|
|
323
441
|
document.addEventListener('click', closeBeautify);
|
|
324
442
|
this._cleanups.push(() => document.removeEventListener('click', closeBeautify));
|
|
@@ -364,14 +482,13 @@ export class BpmnStudioShell {
|
|
|
364
482
|
|
|
365
483
|
_runBeautify(action = null) {
|
|
366
484
|
const settings = this.studio.model.settings || {};
|
|
367
|
-
if (action === 'routing') this.
|
|
368
|
-
else if (action === 'smooth') this.
|
|
369
|
-
else this.
|
|
485
|
+
if (action === 'routing') this.actions.rerouteEdges({ edgeStyle: 'rounded' });
|
|
486
|
+
else if (action === 'smooth') this.actions.rerouteEdges({ edgeStyle: 'smooth' });
|
|
487
|
+
else this.actions.beautify({
|
|
370
488
|
direction: action || settings.direction || 'horizontal',
|
|
371
489
|
density: settings.layoutDensity || 'balanced',
|
|
372
490
|
edgeStyle: settings.edgeStyle || 'rounded',
|
|
373
491
|
});
|
|
374
|
-
requestAnimationFrame(() => this._fitActive());
|
|
375
492
|
}
|
|
376
493
|
|
|
377
494
|
_syncDensityButtons() {
|
|
@@ -381,16 +498,18 @@ export class BpmnStudioShell {
|
|
|
381
498
|
}
|
|
382
499
|
|
|
383
500
|
_setMode(mode, { force = false } = {}) {
|
|
384
|
-
if (!
|
|
501
|
+
if (!this.allowedModes.includes(mode)) return;
|
|
385
502
|
if (!force && this.mode === mode) return;
|
|
386
503
|
this.mode = mode;
|
|
387
504
|
this.projection = mode === 'instance' ? this._instanceProjection : 'standard';
|
|
388
505
|
this._modeButtons?.forEach((button, value) => button.classList.toggle('is-active', value === mode));
|
|
389
506
|
const readonly = mode !== 'design';
|
|
390
507
|
this._body?.classList.toggle('is-readonly', readonly);
|
|
508
|
+
this._applyRegions({ fit: false });
|
|
391
509
|
this._floatingHead?.classList.toggle('is-hidden', readonly);
|
|
392
510
|
this._designControls?.forEach((control) => control.classList.toggle('is-hidden-by-mode', readonly));
|
|
393
511
|
this._projectionSwitch?.classList.toggle('is-hidden', mode !== 'instance');
|
|
512
|
+
this._exportBpmnButton?.classList.toggle('is-hidden', mode !== 'design');
|
|
394
513
|
this._syncProjectionButtons();
|
|
395
514
|
this._body?.classList.remove('is-timeline');
|
|
396
515
|
|
|
@@ -418,6 +537,7 @@ export class BpmnStudioShell {
|
|
|
418
537
|
runtimePresenter: this.rendererOptions.runtimePresenter,
|
|
419
538
|
runtimeTraceProjector: this.rendererOptions.runtimeTraceProjector,
|
|
420
539
|
runtimeAssetResolver: this.rendererOptions.runtimeAssetResolver,
|
|
540
|
+
svgExport: this.svgExport,
|
|
421
541
|
runtimeTimelineRenderer: this.slots.runtimeTimeline
|
|
422
542
|
? (timeline) => mountSlot(this.slots.runtimeTimeline, timeline.container, { ...timeline, studio: this.studio, shell: this })
|
|
423
543
|
: this.rendererOptions.runtimeTimelineRenderer,
|
|
@@ -526,6 +646,34 @@ export class BpmnStudioShell {
|
|
|
526
646
|
this._syncProjectionButtons();
|
|
527
647
|
if (this.mode === 'instance') this.viewer?.setProjection?.(projection);
|
|
528
648
|
}
|
|
649
|
+
getRegions() { return Object.freeze({ ...this._regions }); }
|
|
650
|
+
setRegions(regions) {
|
|
651
|
+
if (this._usesCustomLayout) {
|
|
652
|
+
throw new Error('BpmnStudioShell setRegions() is unavailable when a custom layout is active.');
|
|
653
|
+
}
|
|
654
|
+
const next = normalizeRegions(regions, this._regions);
|
|
655
|
+
const changed = STUDIO_SHELL_REGIONS.some((name) => next[name] !== this._regions[name]);
|
|
656
|
+
this._regions = next;
|
|
657
|
+
if (changed) this._applyRegions();
|
|
658
|
+
return this.getRegions();
|
|
659
|
+
}
|
|
660
|
+
_applyRegions({ fit = true } = {}) {
|
|
661
|
+
if (this._usesCustomLayout || !this._body) return;
|
|
662
|
+
const readonly = this.mode !== 'design';
|
|
663
|
+
const headerHidden = this._regions.header === 'hidden';
|
|
664
|
+
const leftHidden = readonly || this._regions.left === 'hidden';
|
|
665
|
+
const rightHidden = this._regions.right === 'hidden';
|
|
666
|
+
const footerHidden = this._regions.footer === 'hidden';
|
|
667
|
+
this.container.classList.toggle('is-header-hidden', headerHidden);
|
|
668
|
+
this._body.classList.toggle('is-left-hidden', leftHidden);
|
|
669
|
+
this._body.classList.toggle('is-right-hidden', rightHidden);
|
|
670
|
+
this._canvasColumn?.classList.toggle('is-footer-hidden', footerHidden);
|
|
671
|
+
if (this._header) this._header.hidden = headerHidden;
|
|
672
|
+
if (this._left) this._left.hidden = leftHidden;
|
|
673
|
+
if (this._right) this._right.hidden = rightHidden;
|
|
674
|
+
if (this._footer) this._footer.hidden = footerHidden;
|
|
675
|
+
if (fit) requestAnimationFrame(() => this._fitActive());
|
|
676
|
+
}
|
|
529
677
|
setTheme(theme) { return this.themeController.setTheme(theme); }
|
|
530
678
|
setThemeMode(mode) { return this.themeController.setMode(mode); }
|
|
531
679
|
getThemeState() { return this.themeController.getState(); }
|
|
@@ -544,6 +692,35 @@ export class BpmnStudioShell {
|
|
|
544
692
|
}
|
|
545
693
|
fitView() { this._fitActive(); }
|
|
546
694
|
zoomBy(factor) { this._zoomActive(factor); }
|
|
695
|
+
exportSvg(options) {
|
|
696
|
+
const active = this.canvas || this.viewer;
|
|
697
|
+
if (!active?.exportSvg) return Promise.reject(new Error('当前视图不支持 SVG 导出。'));
|
|
698
|
+
const label = this.mode === 'design' ? '流程设计' : this.mode === 'viewer' ? '流程展示' : this.viewer?.activeProjection === 'compact' ? '移动时间线' : this.viewer?.activeProjection === 'approval' ? '实际路径' : '完整 BPMN';
|
|
699
|
+
const filename = options?.filename || this.svgExport?.filename || `${this.studio.model.name || this.studio.model.id}-${label}`;
|
|
700
|
+
return active.exportSvg({ label, ...(options || {}), filename });
|
|
701
|
+
}
|
|
702
|
+
openSvgExportPreview(options = {}) {
|
|
703
|
+
if (this._svgExportPreview && !this._svgExportPreview.closed) {
|
|
704
|
+
this._svgExportPreview.focus();
|
|
705
|
+
return this._svgExportPreview;
|
|
706
|
+
}
|
|
707
|
+
const active = this.canvas || this.viewer;
|
|
708
|
+
if (!active?.exportSvg) return null;
|
|
709
|
+
const label = this.mode === 'design' ? '流程设计' : this.mode === 'viewer' ? '流程展示' : this.viewer?.activeProjection === 'compact' ? '移动时间线' : this.viewer?.activeProjection === 'approval' ? '实际路径' : '完整 BPMN';
|
|
710
|
+
const filename = options.filename || this.svgExport?.filename || `${this.studio.model.name || this.studio.model.id}-${label}`;
|
|
711
|
+
this._svgExportPreview = openSvgExportPreview({
|
|
712
|
+
container: this.container,
|
|
713
|
+
title: options.previewTitle || `导出 ${label} SVG`,
|
|
714
|
+
initialTheme: options.theme || 'current',
|
|
715
|
+
createArtifact: (previewOptions) => this.exportSvg({ label, ...options, ...previewOptions, filename }),
|
|
716
|
+
onClose: () => { this._svgExportPreview = null; },
|
|
717
|
+
onDownload: (artifact) => {
|
|
718
|
+
this._setStatus(`SVG 已导出:${artifact.filename}`, 'ok');
|
|
719
|
+
options.onDownload?.(artifact);
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
return this._svgExportPreview;
|
|
723
|
+
}
|
|
547
724
|
|
|
548
725
|
_setStatus(message, tone = 'ok') {
|
|
549
726
|
if (!this._statusText || !this._statusDot) return;
|
|
@@ -552,7 +729,7 @@ export class BpmnStudioShell {
|
|
|
552
729
|
}
|
|
553
730
|
|
|
554
731
|
_showValidationStatus() {
|
|
555
|
-
const issues = this.
|
|
732
|
+
const issues = this.actions.validate();
|
|
556
733
|
if (!issues.length) this._setStatus('校验通过:流程结构正常', 'ok');
|
|
557
734
|
else {
|
|
558
735
|
const errors = issues.filter((issue) => issue.level === 'error').length;
|
|
@@ -562,7 +739,7 @@ export class BpmnStudioShell {
|
|
|
562
739
|
}
|
|
563
740
|
|
|
564
741
|
_exportBpmn() {
|
|
565
|
-
const blob = new Blob([this.
|
|
742
|
+
const blob = new Blob([this.actions.exportXml()], { type: 'application/xml;charset=utf-8' });
|
|
566
743
|
const url = URL.createObjectURL(blob);
|
|
567
744
|
const anchor = node('a');
|
|
568
745
|
anchor.href = url;
|
|
@@ -583,7 +760,12 @@ export class BpmnStudioShell {
|
|
|
583
760
|
selectionToolbar: options.selectionToolbar ?? this.slots.selectionToolbar ?? null,
|
|
584
761
|
contextMenu: options.contextMenu ?? this.slots.contextMenu ?? null,
|
|
585
762
|
contextMenuRegistry: options.contextMenuRegistry ?? this.contextMenuRegistry,
|
|
586
|
-
rendererOptions: {
|
|
763
|
+
rendererOptions: {
|
|
764
|
+
iconRegistry: this.iconRegistry,
|
|
765
|
+
...this.rendererOptions,
|
|
766
|
+
svgExport: this.svgExport,
|
|
767
|
+
...(options.rendererOptions || {}),
|
|
768
|
+
},
|
|
587
769
|
themeController: this.themeController,
|
|
588
770
|
});
|
|
589
771
|
this._instances.push(instance); return instance;
|
|
@@ -597,6 +779,8 @@ export class BpmnStudioShell {
|
|
|
597
779
|
instance.render(); this._instances.push(instance); return instance;
|
|
598
780
|
}
|
|
599
781
|
destroy() {
|
|
782
|
+
this._svgExportPreview?.close?.({ immediate: true });
|
|
783
|
+
this._svgExportPreview = null;
|
|
600
784
|
this._cleanups.splice(0).reverse().forEach((cleanup) => cleanup?.());
|
|
601
785
|
this._instances.splice(0).reverse().forEach((instance) => instance.destroy?.());
|
|
602
786
|
this.container.innerHTML = '';
|
package/dist/styles.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @bpmn-nova/theme */
|
|
1
|
+
/* @bpmn-nova/internal/theme */
|
|
2
2
|
[data-nova-theme] {
|
|
3
3
|
accent-color: var(--nova-color-primary);
|
|
4
4
|
}
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
--nova-tone-manual-task-strong: #dc876f;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
/* @bpmn-nova/icons */
|
|
152
|
+
/* @bpmn-nova/internal/icons */
|
|
153
153
|
.nova-icon { width: 16px; height: 16px; display: inline-grid; place-items: center; flex: none; color: currentColor; line-height: 0; transform-origin: 50% 50%; }
|
|
154
154
|
.nova-icon.nova-icon-xs { width: 12px; height: 12px; }
|
|
155
155
|
.nova-icon.nova-icon-sm { width: 14px; height: 14px; }
|
|
@@ -157,7 +157,97 @@
|
|
|
157
157
|
.nova-icon > .nova-icon-svg { width: 100%; height: 100%; display: block; overflow: visible; fill: none; stroke: currentColor; vector-effect: non-scaling-stroke; }
|
|
158
158
|
.nova-icon[data-icon-missing="true"] { visibility: hidden; }
|
|
159
159
|
|
|
160
|
-
/* @bpmn-nova/
|
|
160
|
+
/* @bpmn-nova/internal/export-svg */
|
|
161
|
+
.nova-svg-export-preview {
|
|
162
|
+
position: absolute;
|
|
163
|
+
inset: 0;
|
|
164
|
+
z-index: 140;
|
|
165
|
+
display: grid;
|
|
166
|
+
place-items: center;
|
|
167
|
+
padding: 24px;
|
|
168
|
+
box-sizing: border-box;
|
|
169
|
+
background: var(--nova-color-backdrop, rgba(20, 26, 39, .38));
|
|
170
|
+
color: var(--nova-color-text, #1f2430);
|
|
171
|
+
font-family: var(--nova-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", sans-serif);
|
|
172
|
+
opacity: 1;
|
|
173
|
+
transition: opacity .18s ease;
|
|
174
|
+
}
|
|
175
|
+
.nova-svg-export-preview.is-entering,
|
|
176
|
+
.nova-svg-export-preview.is-closing { opacity: 0; }
|
|
177
|
+
.nova-svg-export-dialog {
|
|
178
|
+
width: min(1120px, 100%);
|
|
179
|
+
height: min(820px, 100%);
|
|
180
|
+
min-height: 420px;
|
|
181
|
+
display: grid;
|
|
182
|
+
grid-template-rows: auto auto minmax(0, 1fr) auto auto;
|
|
183
|
+
overflow: hidden;
|
|
184
|
+
border: 1px solid var(--nova-color-border, #e1e5ec);
|
|
185
|
+
border-radius: 16px;
|
|
186
|
+
background: var(--nova-color-surface, #fff);
|
|
187
|
+
box-shadow: var(--nova-shadow-lg, 0 18px 52px rgba(22, 29, 48, .2));
|
|
188
|
+
transform: translateY(0) scale(1);
|
|
189
|
+
transition: transform .2s cubic-bezier(.2, .8, .2, 1);
|
|
190
|
+
}
|
|
191
|
+
.nova-svg-export-preview.is-entering .nova-svg-export-dialog,
|
|
192
|
+
.nova-svg-export-preview.is-closing .nova-svg-export-dialog { transform: translateY(10px) scale(.985); }
|
|
193
|
+
.nova-svg-export-header,
|
|
194
|
+
.nova-svg-export-toolbar,
|
|
195
|
+
.nova-svg-export-footer { display: flex; align-items: center; gap: 12px; padding: 14px 18px; }
|
|
196
|
+
.nova-svg-export-header { justify-content: space-between; border-bottom: 1px solid var(--nova-color-divider, #e9ecf2); }
|
|
197
|
+
.nova-svg-export-heading { display: grid; gap: 3px; }
|
|
198
|
+
.nova-svg-export-heading strong { font-size: 17px; }
|
|
199
|
+
.nova-svg-export-heading span { color: var(--nova-color-text-muted); font-size: 12px; }
|
|
200
|
+
.nova-svg-export-close { width: 34px; height: 34px; border: 0; border-radius: 9px; background: transparent; color: var(--nova-color-text-muted); font-size: 24px; line-height: 1; cursor: pointer; }
|
|
201
|
+
.nova-svg-export-close:hover { background: var(--nova-color-surface-muted); color: var(--nova-color-text); }
|
|
202
|
+
.nova-svg-export-toolbar { justify-content: space-between; flex-wrap: wrap; background: var(--nova-color-surface-subtle); border-bottom: 1px solid var(--nova-color-divider); }
|
|
203
|
+
.nova-svg-export-theme,
|
|
204
|
+
.nova-svg-export-viewport-tools { display: inline-flex; align-items: center; gap: 3px; padding: 3px; border-radius: 9px; background: var(--nova-color-surface); border: 1px solid var(--nova-color-border); }
|
|
205
|
+
.nova-svg-export-theme button,
|
|
206
|
+
.nova-svg-export-viewport-tools button { min-height: 28px; padding: 0 10px; border: 0; border-radius: 6px; background: transparent; color: var(--nova-color-text-secondary); cursor: pointer; }
|
|
207
|
+
.nova-svg-export-theme button.is-active { background: var(--nova-color-primary-soft); color: var(--nova-color-primary); font-weight: 650; }
|
|
208
|
+
.nova-svg-export-theme button:focus-visible,
|
|
209
|
+
.nova-svg-export-viewport-tools button:focus-visible,
|
|
210
|
+
.nova-svg-export-actions button:focus-visible,
|
|
211
|
+
.nova-svg-export-close:focus-visible,
|
|
212
|
+
.nova-svg-export-viewport:focus-visible { outline: 2px solid var(--nova-color-focus-ring); outline-offset: 2px; }
|
|
213
|
+
.nova-svg-export-transparent { display: inline-flex; align-items: center; gap: 7px; color: var(--nova-color-text-secondary); font-size: 12px; cursor: pointer; }
|
|
214
|
+
.nova-svg-export-viewport-tools span { min-width: 46px; text-align: center; color: var(--nova-color-text-muted); font-size: 11px; }
|
|
215
|
+
.nova-svg-export-body { position: relative; min-height: 0; overflow: hidden; background: var(--nova-color-canvas); }
|
|
216
|
+
.nova-svg-export-viewport { position: absolute; inset: 0; overflow: hidden; cursor: grab; touch-action: none; background-color: var(--nova-color-canvas); background-image: linear-gradient(45deg, color-mix(in srgb, var(--nova-color-border) 35%, transparent) 25%, transparent 25%), linear-gradient(-45deg, color-mix(in srgb, var(--nova-color-border) 35%, transparent) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, color-mix(in srgb, var(--nova-color-border) 35%, transparent) 75%), linear-gradient(-45deg, transparent 75%, color-mix(in srgb, var(--nova-color-border) 35%, transparent) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0; }
|
|
217
|
+
.nova-svg-export-viewport.is-dragging { cursor: grabbing; }
|
|
218
|
+
.nova-svg-export-stage { position: absolute; left: 0; top: 0; width: 1px; height: 1px; transform-origin: 0 0; will-change: transform; }
|
|
219
|
+
.nova-svg-export-image { display: block; max-width: none; user-select: none; pointer-events: none; box-shadow: 0 6px 24px rgba(0, 0, 0, .14); }
|
|
220
|
+
.nova-svg-export-state { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; gap: 12px; background: color-mix(in srgb, var(--nova-color-surface) 88%, transparent); color: var(--nova-color-text-secondary); }
|
|
221
|
+
.nova-svg-export-state[hidden] { display: none; }
|
|
222
|
+
.nova-svg-export-state.is-error { color: var(--nova-tone-danger-foreground); }
|
|
223
|
+
.nova-svg-export-spinner { width: 24px; height: 24px; border: 2px solid var(--nova-color-border); border-top-color: var(--nova-color-primary); border-radius: 50%; animation: nova-svg-export-spin .8s linear infinite; }
|
|
224
|
+
.nova-svg-export-state.is-error .nova-svg-export-spinner { display: none; }
|
|
225
|
+
@keyframes nova-svg-export-spin { to { transform: rotate(360deg); } }
|
|
226
|
+
.nova-svg-export-warnings { max-height: 110px; overflow: auto; padding: 10px 18px; border-top: 1px solid var(--nova-tone-warning-border); background: var(--nova-tone-warning-background); color: var(--nova-tone-warning-foreground); font-size: 11px; }
|
|
227
|
+
.nova-svg-export-warnings[hidden] { display: none; }
|
|
228
|
+
.nova-svg-export-warnings strong { display: block; margin-bottom: 4px; }
|
|
229
|
+
.nova-svg-export-warnings ul { margin: 0; padding-left: 18px; }
|
|
230
|
+
.nova-svg-export-footer { justify-content: space-between; border-top: 1px solid var(--nova-color-divider); }
|
|
231
|
+
.nova-svg-export-summary { min-width: 0; overflow: hidden; color: var(--nova-color-text-muted); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
|
|
232
|
+
.nova-svg-export-actions { display: flex; gap: 8px; }
|
|
233
|
+
.nova-svg-export-actions button { min-height: 34px; padding: 0 14px; border: 1px solid var(--nova-color-border); border-radius: 9px; background: var(--nova-color-surface); color: var(--nova-color-text-secondary); cursor: pointer; }
|
|
234
|
+
.nova-svg-export-actions button.is-primary { border-color: var(--nova-color-primary); background: var(--nova-color-primary); color: var(--nova-color-text-inverse, #fff); }
|
|
235
|
+
.nova-svg-export-actions button:disabled { opacity: .48; cursor: not-allowed; }
|
|
236
|
+
@media (max-width: 720px) {
|
|
237
|
+
.nova-svg-export-preview { padding: 10px; }
|
|
238
|
+
.nova-svg-export-dialog { width: 100%; height: 100%; min-height: 0; border-radius: 12px; }
|
|
239
|
+
.nova-svg-export-toolbar { align-items: flex-start; }
|
|
240
|
+
.nova-svg-export-viewport-tools { order: 3; width: 100%; justify-content: center; }
|
|
241
|
+
.nova-svg-export-summary { display: none; }
|
|
242
|
+
.nova-svg-export-footer { justify-content: flex-end; }
|
|
243
|
+
}
|
|
244
|
+
@media (prefers-reduced-motion: reduce) {
|
|
245
|
+
.nova-svg-export-preview,
|
|
246
|
+
.nova-svg-export-dialog { transition: none; }
|
|
247
|
+
.nova-svg-export-spinner { animation: none; }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* @bpmn-nova/internal/renderer-svg */
|
|
161
251
|
.mb-diagram-host,
|
|
162
252
|
.mb-runtime-timeline-host {
|
|
163
253
|
--mb-primary: var(--nova-color-primary, #635bff);
|
|
@@ -898,7 +988,7 @@ button.mb-runtime-action-summary:hover, button.mb-runtime-action-summary:focus-v
|
|
|
898
988
|
height: 100%;
|
|
899
989
|
}
|
|
900
990
|
|
|
901
|
-
/* @bpmn-nova/palette */
|
|
991
|
+
/* @bpmn-nova/internal/palette */
|
|
902
992
|
.nova-palette { height: 100%; overflow: auto; padding: 10px; color: var(--nova-color-text, #243047); background: var(--nova-color-surface, #fff); }
|
|
903
993
|
.nova-palette-section { border-bottom: 1px solid var(--nova-color-divider, #edf0f5); padding: 3px 0 8px; }
|
|
904
994
|
.nova-palette-section-toggle { width: 100%; min-height: 34px; border: 0; background: transparent; display: grid; grid-template-columns: 16px 1fr auto; align-items: center; gap: 6px; color: inherit; cursor: pointer; text-align: left; }
|
|
@@ -917,7 +1007,7 @@ button.mb-runtime-action-summary:hover, button.mb-runtime-action-summary:focus-v
|
|
|
917
1007
|
.nova-palette-item-copy strong { font-size: var(--nova-font-size-sm, 12px); line-height: 18px; font-weight: var(--nova-font-weight-semibold, 600); }
|
|
918
1008
|
.nova-palette-item-copy small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--nova-color-text-muted, #919aad); font-size: var(--nova-font-size-xs, 10px); line-height: 14px; }
|
|
919
1009
|
|
|
920
|
-
/* @bpmn-nova/properties-renderer */
|
|
1010
|
+
/* @bpmn-nova/internal/properties-renderer */
|
|
921
1011
|
.nova-properties { min-height: 100%; color: var(--nova-color-text); background: var(--nova-color-surface); color-scheme: inherit; }
|
|
922
1012
|
.properties-head { position: sticky; top: 0; z-index: 5; padding: 15px 16px 12px; border-bottom: 1px solid var(--nova-color-divider, #e7eaf1); background: color-mix(in srgb, var(--nova-color-surface) 97%, transparent); backdrop-filter: blur(10px); }
|
|
923
1013
|
.properties-title-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
@@ -1049,8 +1139,10 @@ textarea.property-control { resize: vertical; }
|
|
|
1049
1139
|
.property-branch-target { min-width: 0; flex: 1; display: grid; grid-template-columns: auto minmax(0,1fr) auto; align-items: center; gap: 5px; }
|
|
1050
1140
|
.property-branch-target small { color: var(--nova-color-text-muted); font-size: var(--nova-font-size-xs, 10px); line-height: 14px; }.property-branch-target strong { overflow: hidden; color: var(--nova-color-text-secondary); font-size: var(--nova-font-size-sm, 12px); line-height: 18px; font-weight: var(--nova-font-weight-semibold, 600); text-overflow: ellipsis; white-space: nowrap; }.property-branch-target > span { color: var(--nova-color-primary); font-size: var(--nova-font-size-sm, 12px); }
|
|
1051
1141
|
|
|
1052
|
-
/* @bpmn-nova/studio */
|
|
1053
|
-
.nova-studio-shell { --nova-left-width: 244px; --nova-right-width: 360px; --nova-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; --nova-font-size-xs: 10px; --nova-font-size-sm: 12px; --nova-font-size-md: 14px; --nova-font-size-lg: 16px; --nova-font-weight-regular: 400; --nova-font-weight-medium: 500; --nova-font-weight-semibold: 600; --nova-font-weight-bold: 700; width: 100%; height: 100%; min-height: 520px; overflow: hidden; display: grid; grid-template-rows: 58px minmax(0, 1fr); color: var(--nova-color-text, #263148); background: var(--nova-color-canvas, #f6f7fb); font-family: var(--nova-font-family); }
|
|
1142
|
+
/* @bpmn-nova/internal/studio */
|
|
1143
|
+
.nova-studio-shell { --nova-left-width: 244px; --nova-right-width: 360px; --nova-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; --nova-font-size-xs: 10px; --nova-font-size-sm: 12px; --nova-font-size-md: 14px; --nova-font-size-lg: 16px; --nova-font-weight-regular: 400; --nova-font-weight-medium: 500; --nova-font-weight-semibold: 600; --nova-font-weight-bold: 700; position: relative; isolation: isolate; width: 100%; height: 100%; min-height: 520px; overflow: hidden; display: grid; grid-template-rows: 58px minmax(0, 1fr); color: var(--nova-color-text, #263148); background: var(--nova-color-canvas, #f6f7fb); font-family: var(--nova-font-family); }
|
|
1144
|
+
.nova-studio-shell.is-header-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1145
|
+
.nova-studio-header[hidden], .nova-studio-left[hidden], .nova-studio-right[hidden], .nova-studio-statusbar[hidden] { display: none !important; }
|
|
1054
1146
|
.nova-studio-header { min-width: 0; border-bottom: 1px solid var(--nova-color-divider, #e4e8f0); background: var(--nova-color-surface, #fff); display: grid; grid-template-columns: minmax(180px, auto) auto minmax(0, 1fr); align-items: center; gap: 18px; padding: 0 12px 0 14px; }
|
|
1055
1147
|
.nova-studio-brand { min-width: 0; display: flex; align-items: center; gap: 10px; }
|
|
1056
1148
|
.nova-studio-brand-mark { flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; color: var(--nova-color-text-inverse, #fff); background: linear-gradient(145deg, var(--nova-color-primary, #5362da), var(--nova-color-primary-hover)); font-size: var(--nova-font-size-md); font-weight: var(--nova-font-weight-bold); box-shadow: var(--nova-shadow-sm); }
|
|
@@ -1076,6 +1168,17 @@ textarea.property-control { resize: vertical; }
|
|
|
1076
1168
|
.nova-studio-tool.is-primary { border-color: var(--nova-color-primary); color: var(--nova-color-text-inverse, #fff); background: var(--nova-color-primary, #5361d6); }
|
|
1077
1169
|
.nova-studio-tool.is-primary:hover { border-color: var(--nova-color-primary-hover); color: var(--nova-color-text-inverse, #fff); background: var(--nova-color-primary-hover); }
|
|
1078
1170
|
.nova-studio-tool:disabled { opacity: .4; cursor: default; }
|
|
1171
|
+
.nova-studio-export-menu { position: relative; flex: none; }
|
|
1172
|
+
.nova-studio-export-menu > .nova-studio-tool { display: inline-flex; align-items: center; gap: 7px; }
|
|
1173
|
+
.nova-studio-export-popover { position: absolute; z-index: 90; top: calc(100% + 8px); right: 0; width: 220px; padding: 6px; border: 1px solid var(--nova-color-border); border-radius: 11px; background: var(--nova-color-surface-raised); box-shadow: var(--nova-shadow-md); }
|
|
1174
|
+
.nova-studio-export-popover.is-hidden { display: none; }
|
|
1175
|
+
.nova-studio-export-option { width: 100%; display: grid; grid-template-columns: 28px minmax(0, 1fr); grid-template-rows: auto auto; column-gap: 9px; padding: 9px; border: 0; border-radius: 8px; background: transparent; color: var(--nova-color-text); text-align: left; cursor: pointer; }
|
|
1176
|
+
.nova-studio-export-option:hover { background: var(--nova-color-surface-muted); }
|
|
1177
|
+
.nova-studio-export-option:focus-visible { outline: 2px solid var(--nova-color-focus-ring); outline-offset: -2px; }
|
|
1178
|
+
.nova-studio-export-option > .nova-icon { grid-row: 1 / span 2; align-self: center; color: var(--nova-color-primary); }
|
|
1179
|
+
.nova-studio-export-option strong { font-size: var(--nova-font-size-sm, 12px); }
|
|
1180
|
+
.nova-studio-export-option small { color: var(--nova-color-text-muted); font-size: var(--nova-font-size-xs, 10px); }
|
|
1181
|
+
.nova-studio-export-option.is-hidden { display: none; }
|
|
1079
1182
|
.nova-studio-import-input { display: none; }
|
|
1080
1183
|
.nova-studio-beautify-split { position: relative; flex: none; display: flex; }
|
|
1081
1184
|
.nova-studio-beautify-split .nova-studio-beautify { border-radius: 7px 0 0 7px; }
|
|
@@ -1093,7 +1196,11 @@ textarea.property-control { resize: vertical; }
|
|
|
1093
1196
|
.nova-studio-beautify-action-icon > .nova-icon-svg { width: 14px; height: 14px; }
|
|
1094
1197
|
.nova-studio-beautify-action strong { font-size: var(--nova-font-size-sm); font-weight: var(--nova-font-weight-semibold); line-height: 18px; }.nova-studio-beautify-action small { color: #778195; color: var(--nova-color-text-muted, #778195); font-size: var(--nova-font-size-xs); line-height: 14px; }
|
|
1095
1198
|
.nova-studio-body { min-width: 0; min-height: 0; display: grid; grid-template-columns: var(--nova-left-width) minmax(320px, 1fr) var(--nova-right-width); }
|
|
1199
|
+
.nova-studio-body.is-left-hidden { grid-template-columns: minmax(320px, 1fr) var(--nova-right-width); }
|
|
1200
|
+
.nova-studio-body.is-right-hidden { grid-template-columns: var(--nova-left-width) minmax(320px, 1fr); }
|
|
1201
|
+
.nova-studio-body.is-left-hidden.is-right-hidden { grid-template-columns: minmax(320px, 1fr); }
|
|
1096
1202
|
.nova-studio-body.is-readonly { grid-template-columns: minmax(320px, 1fr) var(--nova-right-width); }
|
|
1203
|
+
.nova-studio-body.is-readonly.is-right-hidden { grid-template-columns: minmax(320px, 1fr); }
|
|
1097
1204
|
.nova-studio-body.is-readonly .nova-studio-left { display: none; }
|
|
1098
1205
|
.nova-studio-left, .nova-studio-right, .nova-studio-canvas-column, .nova-studio-canvas { min-width: 0; min-height: 0; overflow: hidden; }
|
|
1099
1206
|
.nova-studio-left { border-right: 1px solid var(--nova-color-divider, #e4e8f0); background: var(--nova-color-surface, #fff); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
|
|
@@ -1105,6 +1212,7 @@ textarea.property-control { resize: vertical; }
|
|
|
1105
1212
|
.nova-studio-left::-webkit-scrollbar-thumb:hover, .nova-studio-right::-webkit-scrollbar-thumb:hover { background: #c1c9d5; background: var(--nova-color-text-muted); }
|
|
1106
1213
|
.nova-studio-left::-webkit-scrollbar-button, .nova-studio-right::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
|
|
1107
1214
|
.nova-studio-canvas-column { display: grid; grid-template-rows: minmax(0, 1fr) 35px; background: var(--nova-color-canvas, #f7f8fc); }
|
|
1215
|
+
.nova-studio-canvas-column.is-footer-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1108
1216
|
.nova-studio-canvas-stage { position: relative; min-width: 0; min-height: 0; overflow: hidden; }
|
|
1109
1217
|
.nova-studio-canvas { position: relative; }
|
|
1110
1218
|
.nova-studio-canvas-stage > .nova-studio-canvas { width: 100%; height: 100%; }
|
|
@@ -1321,6 +1429,7 @@ textarea.property-control { resize: vertical; }
|
|
|
1321
1429
|
}
|
|
1322
1430
|
@media (max-width: 720px) {
|
|
1323
1431
|
.nova-studio-shell { grid-template-rows: 54px minmax(0,1fr); }
|
|
1432
|
+
.nova-studio-shell.is-header-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1324
1433
|
.nova-studio-header { grid-template-columns: auto minmax(0,1fr); padding-left: 9px; }
|
|
1325
1434
|
.nova-studio-brand-copy { display: none; }
|
|
1326
1435
|
.nova-studio-body { grid-template-columns: 1fr; }
|