@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.
Files changed (63) hide show
  1. package/README.md +245 -20
  2. package/dist/canvas.js +7 -4
  3. package/dist/context-menu.js +2 -2
  4. package/dist/controller.js +84 -6
  5. package/dist/index.d.ts +138 -38
  6. package/dist/index.js +12 -11
  7. package/dist/interactions.js +1 -1
  8. package/dist/modules/bpmn-model/index.d.ts +11 -0
  9. package/dist/modules/bpmn-model/index.js +703 -0
  10. package/dist/modules/core/containment.js +590 -0
  11. package/dist/modules/core/gateway.js +72 -0
  12. package/dist/modules/core/history.js +32 -0
  13. package/dist/modules/core/index.d.ts +268 -0
  14. package/dist/modules/core/index.js +7 -0
  15. package/dist/modules/core/layout.js +644 -0
  16. package/dist/modules/core/model.js +287 -0
  17. package/dist/modules/core/runtime-transition-route.js +360 -0
  18. package/dist/modules/core/scope.js +99 -0
  19. package/dist/modules/designer/index.d.ts +79 -0
  20. package/dist/modules/designer/index.js +607 -0
  21. package/dist/modules/engine-activiti/index.d.ts +19 -0
  22. package/dist/modules/engine-activiti/index.js +160 -0
  23. package/dist/modules/engine-flowable/index.d.ts +19 -0
  24. package/dist/modules/engine-flowable/index.js +160 -0
  25. package/dist/modules/export-svg/index.d.ts +112 -0
  26. package/dist/modules/export-svg/index.js +2 -0
  27. package/dist/modules/export-svg/preview.js +327 -0
  28. package/dist/modules/export-svg/render.js +718 -0
  29. package/dist/modules/icons/index.d.ts +24 -0
  30. package/dist/modules/icons/index.js +264 -0
  31. package/dist/modules/palette/index.d.ts +74 -0
  32. package/dist/modules/palette/index.js +99 -0
  33. package/dist/modules/palette/panel.js +99 -0
  34. package/dist/modules/properties/index.d.ts +20 -0
  35. package/dist/modules/properties/index.js +19 -0
  36. package/dist/modules/properties-activiti/index.d.ts +3 -0
  37. package/dist/modules/properties-activiti/index.js +97 -0
  38. package/dist/modules/properties-bpmn/index.d.ts +3 -0
  39. package/dist/modules/properties-bpmn/index.js +518 -0
  40. package/dist/modules/properties-core/index.d.ts +124 -0
  41. package/dist/modules/properties-core/index.js +312 -0
  42. package/dist/modules/properties-flowable/index.d.ts +3 -0
  43. package/dist/modules/properties-flowable/index.js +114 -0
  44. package/dist/modules/properties-renderer/index.d.ts +25 -0
  45. package/dist/modules/properties-renderer/index.js +491 -0
  46. package/dist/modules/renderer-svg/index.d.ts +118 -0
  47. package/dist/modules/renderer-svg/index.js +1460 -0
  48. package/dist/modules/runtime/index.d.ts +169 -0
  49. package/dist/modules/runtime/index.js +535 -0
  50. package/dist/modules/theme/index.d.ts +95 -0
  51. package/dist/modules/theme/index.js +368 -0
  52. package/dist/modules/viewer/index.d.ts +265 -0
  53. package/dist/modules/viewer/index.js +1011 -0
  54. package/dist/modules/viewer/runtime-content.js +123 -0
  55. package/dist/modules/viewer/runtime-details-motion.js +228 -0
  56. package/dist/modules/viewer/runtime-trace.js +574 -0
  57. package/dist/modules/viewer/timeline.js +276 -0
  58. package/dist/selection-layout.js +1 -1
  59. package/dist/shell.js +210 -26
  60. package/dist/styles.css +116 -7
  61. package/llms-full.txt +3082 -0
  62. package/llms.txt +225 -0
  63. package/package.json +39 -16
@@ -0,0 +1,276 @@
1
+ import { createDefaultIconRegistry, createIconElement } from '../icons/index.js';
2
+ import { renderRuntimeApprovalContent } from './runtime-content.js';
3
+ import { applyRuntimeTone } from '../theme/index.js';
4
+
5
+ function element(tag, className, text) {
6
+ const node = document.createElement(tag);
7
+ if (className) node.className = className;
8
+ if (text !== undefined) node.textContent = text;
9
+ return node;
10
+ }
11
+
12
+ function statusIconId(status) {
13
+ if (status === 'completed') return 'ui.statusCompleted';
14
+ if (status === 'active') return 'ui.statusActive';
15
+ if (status === 'failed') return 'ui.statusFailed';
16
+ if (status === 'rejected') return 'ui.statusRejected';
17
+ if (['skipped', 'cancelled'].includes(status)) return 'ui.statusSkipped';
18
+ return 'ui.statusIdle';
19
+ }
20
+
21
+ function icon(registry, id, className = 'nova-icon nova-icon-md') {
22
+ const host = element('span', className);
23
+ const svg = createIconElement(registry.resolve(id, null), { className: 'nova-icon-svg' });
24
+ if (svg) host.appendChild(svg);
25
+ return host;
26
+ }
27
+
28
+ function resultText(item) {
29
+ const outcome = item.outcome;
30
+ return { approved: '已同意', rejected: '已驳回', returned: '已退回', submitted: '已提交', pending: '待处理' }[outcome] || item.statusLabel || '';
31
+ }
32
+
33
+ function formatTime(value) {
34
+ if (!value) return '';
35
+ return String(value).replace('T', ' ');
36
+ }
37
+
38
+ function renderParticipants(item) {
39
+ if ((item.participants || []).length < 2) return null;
40
+ const details = element('details', 'mb-runtime-timeline-participants');
41
+ const summary = element('summary', '', `查看处理人 · ${item.participants.length}人`);
42
+ const list = element('div', 'mb-runtime-timeline-participant-list');
43
+ item.participants.forEach((participant) => list.appendChild(element('span', '', participant.name)));
44
+ details.append(summary, list);
45
+ return details;
46
+ }
47
+
48
+ function renderTimelineAction({ container, action, resolveAsset, onAssetPreview, appearance }) {
49
+ const actionAppearance = appearance?.resolveAction(action) || { label: action.label, tone: 'neutral' };
50
+ const article = element('article', `mb-runtime-timeline-action type-${action.type}`);
51
+ applyRuntimeTone(article, actionAppearance.tone);
52
+ const header = element('header', 'mb-runtime-timeline-action-head');
53
+ header.append(
54
+ element('strong', '', actionAppearance.label),
55
+ element('span', '', [action.actor?.name, formatTime(action.occurredAt)].filter(Boolean).join(' · ')),
56
+ );
57
+ article.appendChild(header);
58
+ if (action.targets?.length) article.appendChild(element('div', 'mb-runtime-action-targets', `目标:${action.targets.map((target) => target.name).join('、')}`));
59
+ renderRuntimeApprovalContent({
60
+ container: article,
61
+ action,
62
+ resolveAsset,
63
+ compact: true,
64
+ onPreview: onAssetPreview,
65
+ });
66
+ container.appendChild(article);
67
+ }
68
+
69
+ function renderTimelineActions({ item, resolveAsset, onAssetPreview, appearance }) {
70
+ const actions = item.actions || [];
71
+ if (!actions.length) return null;
72
+ const host = element('div', 'mb-runtime-timeline-actions');
73
+ const newest = actions.at(-1);
74
+ renderTimelineAction({ container: host, action: newest, resolveAsset, onAssetPreview, appearance });
75
+ if (actions.length > 1) {
76
+ const details = element('details', 'mb-runtime-timeline-action-history');
77
+ const summary = element('summary', '', `查看更早操作 · ${actions.length - 1} 条`);
78
+ const body = element('div', 'mb-runtime-timeline-action-history-body');
79
+ [...actions].slice(0, -1).reverse().forEach((action) => renderTimelineAction({ container: body, action, resolveAsset, onAssetPreview, appearance }));
80
+ details.append(summary, body);
81
+ host.appendChild(details);
82
+ }
83
+ return host;
84
+ }
85
+
86
+ function renderActivityItem({ item, registry, conditionLabel, onItemClick, onDetailsRequest, resolveAsset, onAssetPreview, appearance, compact = false }) {
87
+ const article = element('article', `mb-runtime-timeline-item status-${item.status}${item.predicted ? ' is-predicted' : ''}${compact ? ' is-group-child' : ''}`);
88
+ applyRuntimeTone(article, appearance?.resolveStatus(item.status));
89
+ article.dataset.traceItemId = item.id;
90
+ article.dataset.elementId = item.elementId;
91
+ const rail = element('div', 'mb-runtime-timeline-rail');
92
+ rail.appendChild(icon(registry, statusIconId(item.status), 'nova-icon nova-icon-lg'));
93
+ const card = element('div', 'mb-runtime-timeline-card');
94
+ const trigger = element('button', 'mb-runtime-timeline-card-main');
95
+ trigger.type = 'button';
96
+ trigger.setAttribute('aria-label', `${item.name},${item.statusLabel}${item.summary ? `,${item.summary}` : ''}${item.comment ? `,${item.comment}` : ''}`);
97
+ const heading = element('div', 'mb-runtime-timeline-heading');
98
+ const title = element('strong', '', item.name);
99
+ if (item.round > 1) title.appendChild(element('small', '', `第 ${item.round} 次`));
100
+ heading.append(title, element('em', '', resultText(item)));
101
+ const meta = element('div', 'mb-runtime-timeline-meta');
102
+ meta.append(element('span', '', item.summary || (item.automated ? item.statusLabel : '')), element('time', '', formatTime(item.time)));
103
+ trigger.append(heading, meta);
104
+ trigger.addEventListener('click', (event) => {
105
+ onItemClick?.(item, event);
106
+ onDetailsRequest?.(item, event.currentTarget, event);
107
+ });
108
+ card.appendChild(trigger);
109
+ const actions = renderTimelineActions({ item, resolveAsset, onAssetPreview, appearance });
110
+ if (actions) card.appendChild(actions);
111
+ const content = element('div', 'mb-runtime-timeline-content');
112
+ if (conditionLabel) content.appendChild(element('span', 'mb-runtime-timeline-condition', conditionLabel));
113
+ content.appendChild(card);
114
+ const participants = renderParticipants(item);
115
+ if (participants) content.appendChild(participants);
116
+ article.append(rail, content);
117
+ return article;
118
+ }
119
+
120
+ function renderTransitionItem({ item, registry, onItemClick, onTransitionRequest, resolveAsset, onAssetPreview, appearance }) {
121
+ const article = element('article', `mb-runtime-timeline-item mb-runtime-timeline-transition type-${item.type}`);
122
+ applyRuntimeTone(article, appearance?.resolveTransition(item).tone);
123
+ article.dataset.traceItemId = item.id;
124
+ const rail = element('div', 'mb-runtime-timeline-rail');
125
+ rail.appendChild(icon(registry, 'ui.statusRejected', 'nova-icon nova-icon-lg'));
126
+ const card = element('div', 'mb-runtime-timeline-transition-card');
127
+ const button = element('button', 'mb-runtime-timeline-transition-main');
128
+ button.type = 'button';
129
+ button.append(
130
+ element('strong', '', item.name),
131
+ element('span', '', [item.summary, formatTime(item.time)].filter(Boolean).join(' · ')),
132
+ );
133
+ button.addEventListener('click', (event) => {
134
+ onItemClick?.(item, event);
135
+ onTransitionRequest?.(item, event.currentTarget, event);
136
+ });
137
+ card.appendChild(button);
138
+ const action = item.actions?.[0];
139
+ if (action) {
140
+ renderRuntimeApprovalContent({ container: card, action, resolveAsset, compact: true, onPreview: onAssetPreview });
141
+ } else if (item.comment) card.appendChild(element('p', 'mb-runtime-action-paragraph', item.comment));
142
+ article.append(rail, card);
143
+ return article;
144
+ }
145
+
146
+ function renderParallelGroup({ group, children, registry, linkByTarget, onItemClick, onDetailsRequest, resolveAsset, onAssetPreview, appearance }) {
147
+ const details = element('details', `mb-runtime-timeline-group status-${group.status}`);
148
+ applyRuntimeTone(details, appearance?.resolveStatus(group.status));
149
+ details.dataset.traceGroupId = group.id;
150
+ const summary = element('summary', 'mb-runtime-timeline-group-summary');
151
+ summary.append(
152
+ icon(registry, statusIconId(group.status), 'nova-icon nova-icon-lg'),
153
+ element('strong', '', group.label),
154
+ element('span', '', `${group.completed}/${group.total} 已完成`),
155
+ );
156
+ const body = element('div', 'mb-runtime-timeline-group-body');
157
+ children.sort((a, b) => a.order - b.order).forEach((item) => body.appendChild(renderActivityItem({
158
+ item,
159
+ registry,
160
+ conditionLabel: linkByTarget.get(item.id)?.label || '',
161
+ onItemClick,
162
+ onDetailsRequest,
163
+ resolveAsset,
164
+ onAssetPreview,
165
+ appearance,
166
+ compact: true,
167
+ })));
168
+ details.append(summary, body);
169
+ return details;
170
+ }
171
+
172
+ export function renderDefaultRuntimeTimeline({
173
+ container,
174
+ model,
175
+ projection,
176
+ title,
177
+ description,
178
+ iconRegistry,
179
+ onItemClick,
180
+ onDetailsRequest,
181
+ onTransitionRequest,
182
+ resolveAsset,
183
+ onAssetPreview,
184
+ appearance,
185
+ }) {
186
+ const registry = iconRegistry || createDefaultIconRegistry();
187
+ const root = element('section', 'mb-runtime-timeline');
188
+ root.setAttribute('aria-label', `${title || model.name || '流程'}审批轨迹`);
189
+ const list = element('div', 'mb-runtime-timeline-list');
190
+ const linkByTarget = new Map(projection.links.map((link) => [link.targetItemId, link]));
191
+ const groupedIds = new Set(projection.groups.flatMap((group) => group.itemIds));
192
+ const sequence = [
193
+ ...projection.items.filter((item) => !groupedIds.has(item.id)),
194
+ ...projection.groups.map((group) => ({ ...group, kind: 'parallel-group' })),
195
+ ].sort((a, b) => (a.order - b.order) || a.id.localeCompare(b.id));
196
+
197
+ sequence.forEach((entry) => {
198
+ if (entry.kind === 'parallel-group') {
199
+ list.appendChild(renderParallelGroup({
200
+ group: entry,
201
+ children: entry.itemIds.map((id) => projection.items.find((item) => item.id === id)).filter(Boolean),
202
+ registry,
203
+ linkByTarget,
204
+ onItemClick,
205
+ onDetailsRequest,
206
+ resolveAsset,
207
+ onAssetPreview,
208
+ appearance,
209
+ }));
210
+ return;
211
+ }
212
+ if (entry.kind === 'transition') {
213
+ list.appendChild(renderTransitionItem({ item: entry, registry, onItemClick, onTransitionRequest, resolveAsset, onAssetPreview, appearance }));
214
+ return;
215
+ }
216
+ list.appendChild(renderActivityItem({
217
+ item: entry,
218
+ registry,
219
+ conditionLabel: linkByTarget.get(entry.id)?.label || '',
220
+ onItemClick,
221
+ onDetailsRequest,
222
+ resolveAsset,
223
+ onAssetPreview,
224
+ appearance,
225
+ }));
226
+ });
227
+ if (!sequence.length) list.appendChild(element('div', 'mb-runtime-timeline-empty', '暂无运行轨迹'));
228
+ if (title !== null || description !== null) {
229
+ const header = element('header', 'mb-runtime-timeline-header');
230
+ if (title !== null) header.appendChild(element('strong', '', title));
231
+ if (description !== null) header.appendChild(element('span', '', description));
232
+ root.appendChild(header);
233
+ }
234
+ root.appendChild(list);
235
+ container.appendChild(root);
236
+ return () => root.remove();
237
+ }
238
+
239
+ export class RuntimeTimelineHost {
240
+ constructor(container, options = {}) {
241
+ this.container = container;
242
+ this.options = options;
243
+ this.iconRegistry = options.iconRegistry || createDefaultIconRegistry();
244
+ this.selection = null;
245
+ this._cleanup = null;
246
+ this.container.innerHTML = '';
247
+ this.container.classList.remove('mb-diagram-host');
248
+ this.container.classList.add('mb-runtime-timeline-host');
249
+ this.render();
250
+ }
251
+
252
+ render() {
253
+ const scrollTop = this.container.scrollTop;
254
+ this._cleanup?.();
255
+ this.container.replaceChildren();
256
+ const renderer = this.options.renderer || renderDefaultRuntimeTimeline;
257
+ this._cleanup = renderer({ container: this.container, iconRegistry: this.iconRegistry, ...this.options }) || null;
258
+ this.container.scrollTop = scrollTop;
259
+ }
260
+
261
+ setSelection(selection) {
262
+ this.selection = selection;
263
+ this.container.querySelectorAll('[data-element-id]').forEach((node) => node.classList.toggle('is-selected', selection?.kind === 'node' && node.dataset.elementId === selection.id));
264
+ }
265
+
266
+ fitView() { this.container.scrollTop = 0; }
267
+ zoomBy() {}
268
+ actualSize() {}
269
+
270
+ destroy() {
271
+ this._cleanup?.();
272
+ this._cleanup = null;
273
+ this.container.innerHTML = '';
274
+ this.container.classList.remove('mb-runtime-timeline-host');
275
+ }
276
+ }
@@ -3,7 +3,7 @@ import {
3
3
  autoLayout,
4
4
  elementScopeId,
5
5
  getNode,
6
- } from '@bpmn-nova/core';
6
+ } from './modules/core/index.js';
7
7
 
8
8
  const LAYOUT_KINDS = new Set(['event', 'task', 'container', 'gateway']);
9
9
  const GROUPABLE_KINDS = new Set(['event', 'task', 'container', 'gateway', 'data', 'dataStore', 'annotation']);