@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
|
@@ -6,29 +6,34 @@ import {
|
|
|
6
6
|
getScopeGraph,
|
|
7
7
|
NODE_DEFINITIONS,
|
|
8
8
|
} from '../core/index.js';
|
|
9
|
-
import { createRuntimePresentation, normalizeRuntime } from '../runtime/index.js';
|
|
9
|
+
import { compareRuntimeOrder, createRuntimePresentation, normalizeRuntime, parseRuntimeInstant } from '../runtime/index.js';
|
|
10
10
|
|
|
11
11
|
const HUMAN_TASKS = new Set(['userTask', 'manualTask', 'callActivity']);
|
|
12
12
|
const AUTOMATED_TASKS = new Set(['serviceTask', 'scriptTask', 'businessRuleTask', 'sendTask', 'receiveTask']);
|
|
13
13
|
const CONDITIONAL_GATEWAYS = new Set(['exclusiveGateway', 'inclusiveGateway', 'eventBasedGateway', 'complexGateway']);
|
|
14
14
|
const ABNORMAL_TRANSITIONS = new Set(['reject', 'return']);
|
|
15
15
|
|
|
16
|
-
function timestamp(value
|
|
17
|
-
|
|
18
|
-
const parsed = Date.parse(String(value).replace(' ', 'T'));
|
|
19
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
16
|
+
function timestamp(value) {
|
|
17
|
+
return parseRuntimeInstant(value).instant;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
function recordOrder(record
|
|
23
|
-
return timestamp(record.startTime || record.endTime
|
|
20
|
+
function recordOrder(record) {
|
|
21
|
+
return timestamp(record.startTime || record.endTime);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
function transitionOrder(transition
|
|
27
|
-
return timestamp(transition.occurredAt || transition.time
|
|
24
|
+
function transitionOrder(transition) {
|
|
25
|
+
return timestamp(transition.occurredAt || transition.time);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
function edgeVisitOrder(visit
|
|
31
|
-
return timestamp(visit.occurredAt || visit.time
|
|
28
|
+
function edgeVisitOrder(visit) {
|
|
29
|
+
return timestamp(visit.occurredAt || visit.time);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function byTime(left, right) {
|
|
33
|
+
return compareRuntimeOrder(
|
|
34
|
+
{ instant: left.order ?? left.instant ?? null, index: left.index ?? left.originalIndex ?? 0 },
|
|
35
|
+
{ instant: right.order ?? right.instant ?? null, index: right.index ?? right.originalIndex ?? 0 },
|
|
36
|
+
);
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
function visitStatus(records) {
|
|
@@ -186,8 +191,8 @@ function inferVisitedEdges(model, runtime, outgoing) {
|
|
|
186
191
|
]);
|
|
187
192
|
if (ids.size) return ids;
|
|
188
193
|
const ordered = [...runtime.activities]
|
|
189
|
-
.map((record, index) => ({ record, order: recordOrder(record, index
|
|
190
|
-
.sort(
|
|
194
|
+
.map((record, index) => ({ record, order: recordOrder(record), index }))
|
|
195
|
+
.sort(byTime)
|
|
191
196
|
.map(({ record }) => record.elementId)
|
|
192
197
|
.filter((id, index, values) => id && (!index || id !== values[index - 1]));
|
|
193
198
|
for (let index = 0; index < ordered.length - 1; index += 1) {
|
|
@@ -198,9 +203,9 @@ function inferVisitedEdges(model, runtime, outgoing) {
|
|
|
198
203
|
|
|
199
204
|
function latestRuntimeElement(runtime, nodeMap) {
|
|
200
205
|
return runtime.activities
|
|
201
|
-
.map((record, index) => ({ record, order: recordOrder(record, index
|
|
206
|
+
.map((record, index) => ({ record, order: recordOrder(record), index }))
|
|
202
207
|
.filter(({ record }) => nodeMap.has(record.elementId) && !isGateway(nodeMap.get(record.elementId)))
|
|
203
|
-
.sort(
|
|
208
|
+
.sort(byTime)
|
|
204
209
|
.at(-1)?.record?.elementId || null;
|
|
205
210
|
}
|
|
206
211
|
|
|
@@ -290,11 +295,11 @@ function projectRuntime(runtime, graphModel, presentation) {
|
|
|
290
295
|
if (sourcePresentations.every((item) => item.historicallyVisited)) projectedVisited.push(edge.id);
|
|
291
296
|
const visits = sources.map((id) => runtime.edgeVisits
|
|
292
297
|
.filter((visit) => visit.edgeId === id)
|
|
293
|
-
.map((visit, index) => ({ visit, order: edgeVisitOrder(visit, index
|
|
294
|
-
.sort(
|
|
298
|
+
.map((visit, index) => ({ visit, order: edgeVisitOrder(visit), index }))
|
|
299
|
+
.sort(byTime)
|
|
295
300
|
.at(-1)).filter(Boolean);
|
|
296
301
|
if (visits.length === sources.length) {
|
|
297
|
-
const latest = visits.sort(
|
|
302
|
+
const latest = visits.sort(byTime).at(-1);
|
|
298
303
|
projectedEdgeVisits.push({
|
|
299
304
|
...latest.visit,
|
|
300
305
|
id: `TraceVisit_${edge.id}`,
|
|
@@ -324,27 +329,25 @@ function buildVisitItems(model, runtime, presentation, predictedNodeIds, ordered
|
|
|
324
329
|
if (!node || isGateway(node) || !isVisibleTraceNode(node, { actual: true, ...options })) return;
|
|
325
330
|
const visitKey = record.visitId || record.multiInstanceId || record.id || String(index);
|
|
326
331
|
const key = `${record.elementId}:${visitKey}`;
|
|
327
|
-
if (!groups.has(key)) groups.set(key, { key, node, visitId: visitKey, records: [], order: recordOrder(record
|
|
332
|
+
if (!groups.has(key)) groups.set(key, { key, node, visitId: visitKey, records: [], order: recordOrder(record), originalIndex: index });
|
|
328
333
|
const group = groups.get(key);
|
|
329
334
|
group.records.push(record);
|
|
330
|
-
|
|
335
|
+
const nextOrder = recordOrder(record);
|
|
336
|
+
if (nextOrder != null) group.order = group.order == null ? nextOrder : Math.min(group.order, nextOrder);
|
|
331
337
|
});
|
|
332
338
|
const rounds = new Map();
|
|
333
|
-
const items = [...groups.values()].sort(
|
|
339
|
+
const items = [...groups.values()].sort(byTime).map((group) => {
|
|
334
340
|
const round = (rounds.get(group.node.id) || 0) + 1;
|
|
335
341
|
rounds.set(group.node.id, round);
|
|
336
342
|
const status = visitStatus(group.records);
|
|
337
343
|
const participants = uniqueParticipants(group.records);
|
|
338
344
|
const startTime = group.records.map((record) => record.startTime).filter(Boolean).sort()[0];
|
|
339
345
|
const endTime = group.records.map((record) => record.endTime).filter(Boolean).sort().at(-1);
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
const latestAction =
|
|
346
|
+
const visitPresentation = presentation.getNode(group.node.id).visits.find((visit) => visit.id === group.visitId);
|
|
347
|
+
const actions = (visitPresentation?.actions || []).filter((action) => !transitionActionIds.has(action.id));
|
|
348
|
+
const latestAction = visitPresentation?.latestAction || actions.at(-1) || null;
|
|
343
349
|
const imageCount = actions.reduce((sum, action) => sum + action.imageCount, 0);
|
|
344
350
|
const fileCount = actions.reduce((sum, action) => sum + action.fileCount, 0);
|
|
345
|
-
const assetSummary = latestAction
|
|
346
|
-
? [latestAction.imageCount ? `${latestAction.imageCount} 张图片` : '', latestAction.fileCount ? `${latestAction.fileCount} 个附件` : ''].filter(Boolean).join(' · ')
|
|
347
|
-
: '';
|
|
348
351
|
return {
|
|
349
352
|
id: `TraceItem_${group.node.id}_${group.visitId}`,
|
|
350
353
|
kind: isStartOrEnd(group.node) ? 'milestone' : 'activity',
|
|
@@ -362,7 +365,7 @@ function buildVisitItems(model, runtime, presentation, predictedNodeIds, ordered
|
|
|
362
365
|
actions,
|
|
363
366
|
latestAction,
|
|
364
367
|
actionText: latestAction?.plainText || '',
|
|
365
|
-
actionSummary: latestAction ? [latestAction.label, latestAction.plainText
|
|
368
|
+
actionSummary: visitPresentation?.actionSummary || (latestAction ? [latestAction.label, latestAction.plainText].filter(Boolean).join(' · ') : ''),
|
|
366
369
|
imageCount,
|
|
367
370
|
fileCount,
|
|
368
371
|
assetCount: imageCount + fileCount,
|
|
@@ -375,7 +378,7 @@ function buildVisitItems(model, runtime, presentation, predictedNodeIds, ordered
|
|
|
375
378
|
order: group.order,
|
|
376
379
|
};
|
|
377
380
|
});
|
|
378
|
-
let predictedOrder = Math.max(0, ...items.map((item) => item.order)) + 1;
|
|
381
|
+
let predictedOrder = Math.max(0, ...items.map((item) => item.order).filter((value) => Number.isFinite(value))) + 1;
|
|
379
382
|
for (const nodeId of orderedPredictedIds) {
|
|
380
383
|
if (!predictedNodeIds.has(nodeId) || items.some((item) => item.elementId === nodeId)) continue;
|
|
381
384
|
const node = nodeMap.get(nodeId);
|
|
@@ -435,7 +438,7 @@ function buildVisitItems(model, runtime, presentation, predictedNodeIds, ordered
|
|
|
435
438
|
order: transitionOrder(transition, 0),
|
|
436
439
|
});
|
|
437
440
|
}
|
|
438
|
-
return items.sort((
|
|
441
|
+
return items.sort((left, right) => byTime(left, right) || left.id.localeCompare(right.id));
|
|
439
442
|
}
|
|
440
443
|
|
|
441
444
|
function buildTimelineLinks(items, maps, historicalEdgeIds, allowedEdgeIds) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createDefaultIconRegistry, createIconElement } from '../icons/index.js';
|
|
2
|
+
import { formatRuntimeInstant } from '../runtime/index.js';
|
|
2
3
|
import { renderRuntimeApprovalContent } from './runtime-content.js';
|
|
3
4
|
import { applyRuntimeTone } from '../theme/index.js';
|
|
4
5
|
|
|
@@ -31,8 +32,7 @@ function resultText(item) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function formatTime(value) {
|
|
34
|
-
|
|
35
|
-
return String(value).replace('T', ' ');
|
|
35
|
+
return formatRuntimeInstant(value);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function renderParticipants(item) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Snapshot only public data. Never freeze the Controller, Viewer, or their source models.
|
|
2
|
+
function immutableCopy(value, seen = new WeakMap()) {
|
|
3
|
+
if (!value || typeof value !== 'object') return value;
|
|
4
|
+
if (seen.has(value)) return seen.get(value);
|
|
5
|
+
const copy = Array.isArray(value) ? [] : {};
|
|
6
|
+
seen.set(value, copy);
|
|
7
|
+
for (const key of Object.keys(value)) {
|
|
8
|
+
Object.defineProperty(copy, key, {
|
|
9
|
+
value: immutableCopy(value[key], seen),
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return Object.freeze(copy);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const EMPTY_SELECTION = Object.freeze({ selection: null, selectedElement: null, trace: null });
|
|
19
|
+
|
|
20
|
+
/** Resolve current facts rather than retaining a clicked node or an old Runtime snapshot. */
|
|
21
|
+
export function createPanelSelection(studio, viewer, previousTrace = null) {
|
|
22
|
+
if (!viewer) {
|
|
23
|
+
const selection = studio.selection;
|
|
24
|
+
const selectedElement = studio.getSelectedElement();
|
|
25
|
+
if (!selection || !selectedElement) return EMPTY_SELECTION;
|
|
26
|
+
return immutableCopy({ selection, selectedElement, trace: null });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// An explicit clear must stay cleared across refreshes, even when the Viewer still
|
|
30
|
+
// has an older visual selection (for example after a selected visit disappears).
|
|
31
|
+
if (!previousTrace) return EMPTY_SELECTION;
|
|
32
|
+
const current = viewer._resolveTraceClick(previousTrace);
|
|
33
|
+
if (!current) return EMPTY_SELECTION;
|
|
34
|
+
const { viewer: liveViewer, originalEvent: _originalEvent, ...traceData } = current;
|
|
35
|
+
const selection = current.element
|
|
36
|
+
? { kind: current.targetType === 'edge' ? 'edge' : 'node', id: current.element.id }
|
|
37
|
+
: null;
|
|
38
|
+
const snapshot = immutableCopy({ selection, selectedElement: current.element || null, trace: traceData });
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
...snapshot,
|
|
41
|
+
trace: Object.freeze({ ...snapshot.trace, viewer: liveViewer }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Ignore fresh object identity so refreshes and paired timeline callbacks notify only once. */
|
|
46
|
+
export function samePanelSelection(previous, next) {
|
|
47
|
+
const pairs = new WeakMap();
|
|
48
|
+
const same = (left, right) => {
|
|
49
|
+
if (Object.is(left, right)) return true;
|
|
50
|
+
if (!left || !right || typeof left !== 'object' || typeof right !== 'object') return false;
|
|
51
|
+
if (pairs.has(left)) return pairs.get(left) === right;
|
|
52
|
+
pairs.set(left, right);
|
|
53
|
+
const keys = Object.keys(left);
|
|
54
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
55
|
+
return keys.every((key) => Object.hasOwn(right, key) && (key === 'viewer'
|
|
56
|
+
? left[key] === right[key]
|
|
57
|
+
: same(left[key], right[key])));
|
|
58
|
+
};
|
|
59
|
+
return same(previous, next);
|
|
60
|
+
}
|