@bpmn-nova/studio 0.3.4-preview → 0.3.6-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 +16 -298
- 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 +84 -64
- 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 +64 -25
- package/dist/modules/runtime/diagnostics.js +22 -0
- package/dist/modules/runtime/index.d.ts +38 -4
- package/dist/modules/runtime/index.js +272 -80
- package/dist/modules/runtime/time.js +41 -0
- package/dist/modules/runtime/visits.js +167 -0
- package/dist/modules/viewer/index.d.ts +1 -1
- package/dist/modules/viewer/index.js +220 -50
- package/dist/modules/viewer/runtime-trace.js +33 -30
- package/dist/modules/viewer/timeline.js +2 -2
- 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 +3924 -826
- package/llms.txt +64 -15
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { NODE_DEFINITIONS, PALETTE_GROUPS, PARALLEL_GATEWAY_PRESETS, edgeWaypoin
|
|
|
2
2
|
import { createRuntimePresentation } from '../runtime/index.js';
|
|
3
3
|
import { createDefaultIconRegistry, createIconElement, resolveNodeVisual } from '../icons/index.js';
|
|
4
4
|
import { resolveNodeSubtitle, supportsNodeSubtitle } from '../node-presentation/index.js';
|
|
5
|
+
import { resolveNodeGeometry, resolvePresentationWaypoints } from '../node-geometry/index.js';
|
|
5
6
|
import { ThemeController, applyRuntimeTone } from '../theme/index.js';
|
|
6
7
|
import { exportDiagramSvg, openSvgExportPreview } from '../export-svg/index.js';
|
|
7
8
|
|
|
@@ -276,6 +277,26 @@ function nodeAccessibleLabel(title, subtitle) {
|
|
|
276
277
|
return subtitle === null || subtitle === '' ? title : `${title}\n${subtitle}`;
|
|
277
278
|
}
|
|
278
279
|
|
|
280
|
+
function applyNodeGeometry(shape, geometry) {
|
|
281
|
+
shape.classList.add('mb-node-scaled-shape');
|
|
282
|
+
shape.style.width = `${geometry.outline.width}px`;
|
|
283
|
+
shape.style.height = `${geometry.outline.height}px`;
|
|
284
|
+
shape.style.setProperty('--mb-node-shape-scale', String(geometry.scale));
|
|
285
|
+
shape.style.setProperty('--mb-node-shape-rotation', `${geometry.outline.rotation}rad`);
|
|
286
|
+
// CSS may quantize a 1.7px border to 1.5px; keep the document fold inside the outer outline.
|
|
287
|
+
if (geometry.kind === 'data') shape.style.clipPath = `inset(0 round ${geometry.outline.radiusX}px)`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function geometryLabel(node, geometry) {
|
|
291
|
+
const label = el('div', 'mb-node-floating-label', node.name);
|
|
292
|
+
if (geometry) {
|
|
293
|
+
label.style.left = `${geometry.label.x - node.x}px`;
|
|
294
|
+
label.style.top = `${geometry.label.y - node.y}px`;
|
|
295
|
+
label.style.lineHeight = `${geometry.label.lineHeight}px`;
|
|
296
|
+
}
|
|
297
|
+
return label;
|
|
298
|
+
}
|
|
299
|
+
|
|
279
300
|
function runtimeStatusIconId(presentation) {
|
|
280
301
|
if (presentation?.status === 'rejected') return 'ui.statusRejected';
|
|
281
302
|
if (presentation?.status === 'completed') return 'ui.statusCompleted';
|
|
@@ -364,17 +385,21 @@ export class DiagramRenderer {
|
|
|
364
385
|
['arrow-failed', 'var(--mb-danger)'],
|
|
365
386
|
];
|
|
366
387
|
for (const [id] of markers) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
388
|
+
for (const docked of [false, true]) {
|
|
389
|
+
const marker = svgEl('marker', { id: `${id}${docked ? '-docked' : ''}`, viewBox: '0 0 10 10', refX: docked ? '10' : '8.5', refY: '5', markerWidth: '7', markerHeight: '7', orient: 'auto-start-reverse', markerUnits: 'strokeWidth' });
|
|
390
|
+
marker.appendChild(svgEl('path', { d: 'M 0 0 L 10 5 L 0 10 z', fill: 'context-stroke' }));
|
|
391
|
+
defs.appendChild(marker);
|
|
392
|
+
}
|
|
370
393
|
}
|
|
371
394
|
this.edgeSvg.appendChild(defs);
|
|
372
395
|
|
|
373
396
|
const runtimeDefs = svgEl('defs');
|
|
374
397
|
for (const id of ['runtime-arrow-forward', 'runtime-arrow-reject', 'runtime-arrow-return', 'runtime-arrow-skip']) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
398
|
+
for (const docked of [false, true]) {
|
|
399
|
+
const marker = svgEl('marker', { id: `${id}${docked ? '-docked' : ''}`, viewBox: '0 0 10 10', refX: docked ? '10' : '8.5', refY: '5', markerWidth: '7', markerHeight: '7', orient: 'auto-start-reverse', markerUnits: 'strokeWidth' });
|
|
400
|
+
marker.appendChild(svgEl('path', { d: 'M 0 0 L 10 5 L 0 10 z', fill: 'context-stroke' }));
|
|
401
|
+
runtimeDefs.appendChild(marker);
|
|
402
|
+
}
|
|
378
403
|
}
|
|
379
404
|
this.runtimeTransitionSvg.appendChild(runtimeDefs);
|
|
380
405
|
|
|
@@ -712,6 +737,7 @@ export class DiagramRenderer {
|
|
|
712
737
|
const boundsW = maxX - minX || 1;
|
|
713
738
|
const boundsH = maxY - minY || 1;
|
|
714
739
|
const rect = this.viewport.getBoundingClientRect();
|
|
740
|
+
if (rect.width < 1 || rect.height < 1) return;
|
|
715
741
|
const maxZoom = options.maxZoom ?? 1.15;
|
|
716
742
|
const minZoom = options.minZoom ?? 0.25;
|
|
717
743
|
const rawZoom = Math.min((rect.width - padding * 2) / boundsW, (rect.height - padding * 2) / boundsH, maxZoom);
|
|
@@ -767,6 +793,7 @@ export class DiagramRenderer {
|
|
|
767
793
|
? presenter({ model: this.model, runtime: this.runtime, appearance: this.options.runtimeAppearance })
|
|
768
794
|
: createRuntimePresentation({ model: this.model, runtime: null, appearance: this.options.runtimeAppearance });
|
|
769
795
|
const subtitles = this._resolveDefinitionSubtitles();
|
|
796
|
+
this._nodeGeometries = new Map(this.model.nodes.map((node) => [node.id, resolveNodeGeometry(node, NODE_DEFINITIONS[node.type])]));
|
|
770
797
|
this._syncSceneBounds();
|
|
771
798
|
this._renderEdges();
|
|
772
799
|
this._renderRuntimeTransitions();
|
|
@@ -832,8 +859,9 @@ export class DiagramRenderer {
|
|
|
832
859
|
labelSize: { width: metrics.width, height: metrics.height },
|
|
833
860
|
});
|
|
834
861
|
if (!route) return;
|
|
835
|
-
const
|
|
836
|
-
|
|
862
|
+
const display = resolvePresentationWaypoints(route.points, this._nodeGeometries.get(transition.sourceElementId), this._nodeGeometries.get(transition.targetElementId));
|
|
863
|
+
const points = display.points;
|
|
864
|
+
occupiedRuntimeRoutes.push(route.points);
|
|
837
865
|
labelObstacles.push({
|
|
838
866
|
x: route.labelPoint.x - metrics.width / 2,
|
|
839
867
|
y: route.labelPoint.y - metrics.height / 2,
|
|
@@ -860,7 +888,7 @@ export class DiagramRenderer {
|
|
|
860
888
|
d: pathData,
|
|
861
889
|
class: 'mb-runtime-transition-path',
|
|
862
890
|
fill: 'none',
|
|
863
|
-
'marker-end': `url(#runtime-arrow-${transition.type})`,
|
|
891
|
+
'marker-end': `url(#runtime-arrow-${transition.type}${display.targetDocked ? '-docked' : ''})`,
|
|
864
892
|
}));
|
|
865
893
|
const labelX = route.labelPoint.x;
|
|
866
894
|
const labelY = route.labelPoint.y;
|
|
@@ -900,9 +928,10 @@ export class DiagramRenderer {
|
|
|
900
928
|
for (const edge of this.model.edges) {
|
|
901
929
|
const points = edgeWaypoints(this.model, edge);
|
|
902
930
|
if (points.length < 2) continue;
|
|
931
|
+
const display = resolvePresentationWaypoints(points, this._nodeGeometries.get(edge.source), this._nodeGeometries.get(edge.target));
|
|
903
932
|
const corner = edge.cornerRadius ?? this.model.settings?.cornerRadius ?? 14;
|
|
904
933
|
const routeStyle = edge.routeStyle || this.model.settings?.edgeStyle || 'rounded';
|
|
905
|
-
const pathData = routePathData(points, routeStyle, corner);
|
|
934
|
+
const pathData = routePathData(display.points, routeStyle, corner);
|
|
906
935
|
|
|
907
936
|
const edgeStatus = this.runtimePresentation.getEdge(edge.id).status;
|
|
908
937
|
const selected = this.selection?.kind === 'edge' && this.selection.id === edge.id;
|
|
@@ -916,7 +945,7 @@ export class DiagramRenderer {
|
|
|
916
945
|
class: 'mb-edge-path',
|
|
917
946
|
fill: 'none',
|
|
918
947
|
};
|
|
919
|
-
if ((edge.type || 'sequenceFlow') === 'sequenceFlow') pathAttrs['marker-end'] = `url(#arrow${edgeStatus === 'idle' ? '' : `-${edgeStatus}`})`;
|
|
948
|
+
if ((edge.type || 'sequenceFlow') === 'sequenceFlow') pathAttrs['marker-end'] = `url(#arrow${edgeStatus === 'idle' ? '' : `-${edgeStatus}`}${display.targetDocked ? '-docked' : ''})`;
|
|
920
949
|
const path = svgEl('path', pathAttrs);
|
|
921
950
|
const hit = svgEl('path', { d: pathData, class: 'mb-edge-hit', fill: 'none' });
|
|
922
951
|
let labelPosition = edgeLabelPoint(points);
|
|
@@ -1109,7 +1138,13 @@ export class DiagramRenderer {
|
|
|
1109
1138
|
}
|
|
1110
1139
|
|
|
1111
1140
|
if (def.kind === 'gateway') {
|
|
1141
|
+
const geometry = this._nodeGeometries.get(node.id);
|
|
1142
|
+
if (!geometry) {
|
|
1143
|
+
nodeEl.appendChild(geometryLabel(node, geometry));
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1112
1146
|
const shape = el('div', 'mb-gateway-shape');
|
|
1147
|
+
applyNodeGeometry(shape, geometry);
|
|
1113
1148
|
const symbol = el('span', 'mb-gateway-symbol');
|
|
1114
1149
|
if (!this._renderCustomNodeContent(symbol, node, def, runtimeState, visual)) {
|
|
1115
1150
|
const iconNode = visual.iconId
|
|
@@ -1131,23 +1166,19 @@ export class DiagramRenderer {
|
|
|
1131
1166
|
nodeEl.appendChild(badge);
|
|
1132
1167
|
}
|
|
1133
1168
|
}
|
|
1134
|
-
nodeEl.appendChild(
|
|
1169
|
+
nodeEl.appendChild(geometryLabel(node, geometry));
|
|
1135
1170
|
return;
|
|
1136
1171
|
}
|
|
1137
1172
|
|
|
1138
|
-
if (def.kind === 'data') {
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
const store = el('div', 'mb-data-store-shape');
|
|
1148
|
-
store.appendChild(el('span', '', '≡'));
|
|
1149
|
-
nodeEl.appendChild(store);
|
|
1150
|
-
nodeEl.appendChild(el('div', 'mb-node-floating-label', node.name));
|
|
1173
|
+
if (def.kind === 'data' || def.kind === 'dataStore') {
|
|
1174
|
+
const geometry = this._nodeGeometries.get(node.id);
|
|
1175
|
+
if (geometry) {
|
|
1176
|
+
const shape = el('div', def.kind === 'data' ? 'mb-data-object-shape' : 'mb-data-store-shape');
|
|
1177
|
+
applyNodeGeometry(shape, geometry);
|
|
1178
|
+
shape.appendChild(el('span', def.kind === 'data' ? 'mb-data-object-lines' : '', '≡'));
|
|
1179
|
+
nodeEl.appendChild(shape);
|
|
1180
|
+
}
|
|
1181
|
+
nodeEl.appendChild(geometryLabel(node, geometry));
|
|
1151
1182
|
return;
|
|
1152
1183
|
}
|
|
1153
1184
|
|
|
@@ -1308,6 +1339,14 @@ export class DiagramRenderer {
|
|
|
1308
1339
|
|
|
1309
1340
|
const appendPort = (side, { target = false, onClick } = {}) => {
|
|
1310
1341
|
const port = el('button', `mb-port mb-port-${side}${target ? ' mb-port-target' : ''}`);
|
|
1342
|
+
const anchor = this._nodeGeometries.get(node.id)?.ports[side];
|
|
1343
|
+
if (anchor) {
|
|
1344
|
+
port.classList.add('mb-port-geometry');
|
|
1345
|
+
port.style.left = `${anchor.x - node.x}px`;
|
|
1346
|
+
port.style.top = `${anchor.y - node.y}px`;
|
|
1347
|
+
port.style.right = 'auto';
|
|
1348
|
+
port.style.bottom = 'auto';
|
|
1349
|
+
}
|
|
1311
1350
|
port.type = 'button';
|
|
1312
1351
|
port.title = target ? '连接到此节点' : (side === 'right' ? '创建连接' : '输入连接点');
|
|
1313
1352
|
port.tabIndex = -1;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const DIAGNOSTIC_FIELDS = ['code', 'level', 'message', 'fieldPath', 'elementId', 'activityId', 'visitId', 'actionId', 'transitionId', 'edgeId'];
|
|
2
|
+
|
|
3
|
+
export function createDiagnostic(input = {}) {
|
|
4
|
+
const diagnostic = {
|
|
5
|
+
code: String(input.code || 'runtime-issue'),
|
|
6
|
+
level: input.level === 'error' || input.level === 'info' ? input.level : 'warning',
|
|
7
|
+
message: String(input.message || ''),
|
|
8
|
+
};
|
|
9
|
+
for (const field of DIAGNOSTIC_FIELDS.slice(3)) {
|
|
10
|
+
if (input[field]) diagnostic[field] = String(input[field]);
|
|
11
|
+
}
|
|
12
|
+
return Object.freeze(diagnostic);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function pushDiagnostic(list, input) {
|
|
16
|
+
if (!list) return null;
|
|
17
|
+
const diagnostic = createDiagnostic(input);
|
|
18
|
+
if (!list.some((item) => item.code === diagnostic.code && item.fieldPath === diagnostic.fieldPath && item.activityId === diagnostic.activityId && item.visitId === diagnostic.visitId && item.actionId === diagnostic.actionId && item.transitionId === diagnostic.transitionId)) {
|
|
19
|
+
list.push(diagnostic);
|
|
20
|
+
}
|
|
21
|
+
return diagnostic;
|
|
22
|
+
}
|
|
@@ -51,9 +51,27 @@ export interface RuntimeApprovalActionPresentation extends Omit<RuntimeApprovalA
|
|
|
51
51
|
assetCount: number
|
|
52
52
|
order: number
|
|
53
53
|
}
|
|
54
|
+
export interface RuntimeDiagnostic {
|
|
55
|
+
code: string
|
|
56
|
+
level: 'error' | 'warning' | 'info'
|
|
57
|
+
message: string
|
|
58
|
+
fieldPath?: string
|
|
59
|
+
elementId?: string
|
|
60
|
+
activityId?: string
|
|
61
|
+
visitId?: string
|
|
62
|
+
actionId?: string
|
|
63
|
+
transitionId?: string
|
|
64
|
+
edgeId?: string
|
|
65
|
+
}
|
|
66
|
+
export interface RuntimeInstant {
|
|
67
|
+
instant: number | null
|
|
68
|
+
raw: string
|
|
69
|
+
kind: 'offset' | 'z' | 'naive' | 'invalid' | 'missing'
|
|
70
|
+
}
|
|
54
71
|
export interface RuntimeApprovalActionSummary {
|
|
55
72
|
actions: RuntimeApprovalActionPresentation[]
|
|
56
73
|
latestAction: RuntimeApprovalActionPresentation | null
|
|
74
|
+
latestNonEmptyComment: RuntimeApprovalActionPresentation | null
|
|
57
75
|
actionText: string
|
|
58
76
|
actionSummary: string
|
|
59
77
|
imageCount: number
|
|
@@ -70,6 +88,7 @@ export interface ActivityInstance {
|
|
|
70
88
|
assigneeId?: string
|
|
71
89
|
participant?: RuntimeParticipant
|
|
72
90
|
visitId?: string
|
|
91
|
+
supersedesVisitId?: string
|
|
73
92
|
multiInstanceId?: string
|
|
74
93
|
approvalMode?: 'single' | 'all' | 'any'
|
|
75
94
|
multiInstanceMode?: 'parallel' | 'sequential'
|
|
@@ -92,6 +111,7 @@ export interface RuntimeTransition {
|
|
|
92
111
|
state?: 'active' | 'resolved'
|
|
93
112
|
resolvedAt?: string
|
|
94
113
|
resolvedByActivityId?: string
|
|
114
|
+
invalidatedElementIds?: string[]
|
|
95
115
|
invalidatedActivityIds?: string[]
|
|
96
116
|
invalidatedEdgeIds?: string[]
|
|
97
117
|
}
|
|
@@ -120,10 +140,18 @@ export interface RuntimeTransitionPresentation extends RuntimeTransition {
|
|
|
120
140
|
label: string
|
|
121
141
|
latest: boolean
|
|
122
142
|
issues: string[]
|
|
143
|
+
invalidatedElementIds: string[]
|
|
123
144
|
invalidatedActivityIds: string[]
|
|
124
145
|
invalidatedEdgeIds: string[]
|
|
125
146
|
action: RuntimeApprovalActionPresentation | null
|
|
126
147
|
}
|
|
148
|
+
export interface ActivityVisitState {
|
|
149
|
+
status: ActivityStatus
|
|
150
|
+
records: ActivityInstance[]
|
|
151
|
+
latestRecords: ActivityInstance[]
|
|
152
|
+
visits: Array<{ id: string; round: number; records: ActivityInstance[]; effective?: boolean; superseded?: boolean }>
|
|
153
|
+
effectiveVisits: Array<{ id: string; round: number; records: ActivityInstance[]; effective?: boolean; superseded?: boolean }>
|
|
154
|
+
}
|
|
127
155
|
export interface NodeRuntimePresentation extends RuntimeApprovalActionSummary {
|
|
128
156
|
elementId: string
|
|
129
157
|
status: ActivityStatus | 'rejected'
|
|
@@ -141,13 +169,15 @@ export interface NodeRuntimePresentation extends RuntimeApprovalActionSummary {
|
|
|
141
169
|
round: number
|
|
142
170
|
records: ActivityInstance[]
|
|
143
171
|
latestRecords: ActivityInstance[]
|
|
144
|
-
visits: Array<{ id: string; round: number; records: ActivityInstance[] } & RuntimeApprovalActionSummary>
|
|
172
|
+
visits: Array<{ id: string; round: number; records: ActivityInstance[]; effective: boolean; superseded: boolean } & RuntimeApprovalActionSummary>
|
|
173
|
+
effectiveVisits: Array<{ id: string; round: number; records: ActivityInstance[]; effective: boolean; superseded: boolean } & RuntimeApprovalActionSummary>
|
|
145
174
|
transitions: RuntimeTransitionPresentation[]
|
|
146
175
|
isReentry: boolean
|
|
147
176
|
hasDetails: boolean
|
|
148
177
|
}
|
|
149
178
|
export interface RuntimePresentation {
|
|
150
179
|
runtime: ProcessInstanceSnapshot
|
|
180
|
+
diagnostics: readonly RuntimeDiagnostic[]
|
|
151
181
|
getNode(elementId: string): NodeRuntimePresentation
|
|
152
182
|
getEdge(edgeId: string): { edgeId: string; status: ActivityStatus; visited: boolean; historicallyVisited: boolean; superseded: boolean }
|
|
153
183
|
getAction(actionId: string): RuntimeApprovalActionPresentation | null
|
|
@@ -161,9 +191,13 @@ export interface RuntimeAppearanceLike {
|
|
|
161
191
|
resolveTransition(transition?: { type?: string }): { tone: string }
|
|
162
192
|
}
|
|
163
193
|
|
|
164
|
-
export function
|
|
165
|
-
export function
|
|
166
|
-
export function
|
|
194
|
+
export function parseRuntimeInstant(value?: string | null): RuntimeInstant
|
|
195
|
+
export function formatRuntimeInstant(value?: string | RuntimeInstant | null): string
|
|
196
|
+
export function compareRuntimeOrder(left: { instant?: number | null; index?: number } | string | null | undefined, right: { instant?: number | null; index?: number } | string | null | undefined): number
|
|
197
|
+
export function normalizeRuntime(snapshot?: Partial<ProcessInstanceSnapshot> | null, options?: { diagnostics?: RuntimeDiagnostic[]; model?: ProcessModel | null }): ProcessInstanceSnapshot
|
|
198
|
+
export function activityState(runtime: ProcessInstanceSnapshot | null | undefined, elementId: string, options?: { diagnostics?: RuntimeDiagnostic[] }): ActivityVisitState
|
|
199
|
+
export function inspectRuntime(snapshot?: Partial<ProcessInstanceSnapshot> | null, options?: { model?: ProcessModel | null; appearance?: RuntimeAppearanceLike | null }): { runtime: ProcessInstanceSnapshot; diagnostics: readonly RuntimeDiagnostic[] }
|
|
200
|
+
export function createRuntimePresentation(context: { model: ProcessModel; runtime?: ProcessInstanceSnapshot | null; appearance?: RuntimeAppearanceLike | null; diagnostics?: RuntimeDiagnostic[] }): RuntimePresentation
|
|
167
201
|
export function demoRuntime(): ProcessInstanceSnapshot
|
|
168
202
|
|
|
169
203
|
export type RuntimeElement = BpmnNode | BpmnEdge
|