eventmodeler 0.3.2 → 0.3.3
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/dist/lib/flow-utils.d.ts +1 -0
- package/dist/lib/flow-utils.js +63 -47
- package/dist/slices/show-slice/index.js +31 -78
- package/package.json +1 -1
package/dist/lib/flow-utils.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface SliceFlowInfo extends FlowInfo {
|
|
|
26
26
|
name: string;
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
export declare function resolveToCanonical(model: EventModel, id: string): string;
|
|
29
30
|
export declare function getElementInfo(model: EventModel, id: string): ElementInfo | null;
|
|
30
31
|
export declare function getSliceComponentIds(model: EventModel, slice: Slice): Set<string>;
|
|
31
32
|
export declare function findSliceForComponent(model: EventModel, componentId: string): Slice | null;
|
package/dist/lib/flow-utils.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Resolve a linked copy to its canonical original, or return the ID unchanged if not a copy
|
|
2
|
+
export function resolveToCanonical(model, id) {
|
|
3
|
+
const screen = model.screens.get(id);
|
|
4
|
+
if (screen?.originalNodeId)
|
|
5
|
+
return screen.originalNodeId;
|
|
6
|
+
const event = model.events.get(id);
|
|
7
|
+
if (event?.originalNodeId)
|
|
8
|
+
return event.originalNodeId;
|
|
9
|
+
const readModel = model.readModels.get(id);
|
|
10
|
+
if (readModel?.originalNodeId)
|
|
11
|
+
return readModel.originalNodeId;
|
|
12
|
+
return id;
|
|
13
|
+
}
|
|
1
14
|
// Get element info (name and type) by ID
|
|
2
15
|
export function getElementInfo(model, id) {
|
|
3
16
|
const screen = model.screens.get(id);
|
|
@@ -61,16 +74,13 @@ function isElementInSlice(slice, position, width, height) {
|
|
|
61
74
|
centerY >= slice.position.y &&
|
|
62
75
|
centerY <= slice.position.y + slice.size.height);
|
|
63
76
|
}
|
|
64
|
-
// Get all component IDs in a slice (
|
|
77
|
+
// Get all component IDs in a slice (excludes linked copies - they are UI-only)
|
|
65
78
|
export function getSliceComponentIds(model, slice) {
|
|
66
79
|
const ids = new Set();
|
|
67
|
-
|
|
68
|
-
// First pass: collect IDs and canonical IDs of elements in the slice
|
|
80
|
+
// Only include canonical elements (not linked copies) that are spatially in the slice
|
|
69
81
|
for (const screen of model.screens.values()) {
|
|
70
|
-
if (isElementInSlice(slice, screen.position, screen.width, screen.height)) {
|
|
82
|
+
if (!screen.originalNodeId && isElementInSlice(slice, screen.position, screen.width, screen.height)) {
|
|
71
83
|
ids.add(screen.id);
|
|
72
|
-
if (screen.canonicalId)
|
|
73
|
-
canonicalIds.add(screen.canonicalId);
|
|
74
84
|
}
|
|
75
85
|
}
|
|
76
86
|
for (const command of model.commands.values()) {
|
|
@@ -79,17 +89,13 @@ export function getSliceComponentIds(model, slice) {
|
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
for (const event of model.events.values()) {
|
|
82
|
-
if (isElementInSlice(slice, event.position, event.width, event.height)) {
|
|
92
|
+
if (!event.originalNodeId && isElementInSlice(slice, event.position, event.width, event.height)) {
|
|
83
93
|
ids.add(event.id);
|
|
84
|
-
if (event.canonicalId)
|
|
85
|
-
canonicalIds.add(event.canonicalId);
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
for (const readModel of model.readModels.values()) {
|
|
89
|
-
if (isElementInSlice(slice, readModel.position, readModel.width, readModel.height)) {
|
|
97
|
+
if (!readModel.originalNodeId && isElementInSlice(slice, readModel.position, readModel.width, readModel.height)) {
|
|
90
98
|
ids.add(readModel.id);
|
|
91
|
-
if (readModel.canonicalId)
|
|
92
|
-
canonicalIds.add(readModel.canonicalId);
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
for (const processor of model.processors.values()) {
|
|
@@ -97,25 +103,6 @@ export function getSliceComponentIds(model, slice) {
|
|
|
97
103
|
ids.add(processor.id);
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
|
-
// Second pass: add all elements that share a canonical ID with elements in the slice
|
|
101
|
-
// This ensures flows targeting any element in a canonical group are detected
|
|
102
|
-
if (canonicalIds.size > 0) {
|
|
103
|
-
for (const screen of model.screens.values()) {
|
|
104
|
-
if (screen.canonicalId && canonicalIds.has(screen.canonicalId)) {
|
|
105
|
-
ids.add(screen.id);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
for (const event of model.events.values()) {
|
|
109
|
-
if (event.canonicalId && canonicalIds.has(event.canonicalId)) {
|
|
110
|
-
ids.add(event.id);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
for (const readModel of model.readModels.values()) {
|
|
114
|
-
if (readModel.canonicalId && canonicalIds.has(readModel.canonicalId)) {
|
|
115
|
-
ids.add(readModel.id);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
106
|
return ids;
|
|
120
107
|
}
|
|
121
108
|
// Find which slice contains a component
|
|
@@ -163,10 +150,13 @@ export function findSliceForComponent(model, componentId) {
|
|
|
163
150
|
}
|
|
164
151
|
return null;
|
|
165
152
|
}
|
|
166
|
-
// Build FlowInfo from a Flow
|
|
153
|
+
// Build FlowInfo from a Flow (resolves linked copies to their canonical originals)
|
|
167
154
|
function buildFlowInfo(model, flow) {
|
|
168
|
-
|
|
169
|
-
const
|
|
155
|
+
// Resolve linked copies to canonical originals
|
|
156
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
157
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
158
|
+
const source = getElementInfo(model, canonicalSourceId);
|
|
159
|
+
const target = getElementInfo(model, canonicalTargetId);
|
|
170
160
|
if (!source || !target)
|
|
171
161
|
return null;
|
|
172
162
|
return {
|
|
@@ -177,13 +167,16 @@ function buildFlowInfo(model, flow) {
|
|
|
177
167
|
fieldMappings: enrichFieldMappings(model, flow),
|
|
178
168
|
};
|
|
179
169
|
}
|
|
180
|
-
// Build SliceFlowInfo from a Flow (includes slice info for source/target)
|
|
170
|
+
// Build SliceFlowInfo from a Flow (includes slice info for source/target, resolves linked copies)
|
|
181
171
|
function buildSliceFlowInfo(model, flow) {
|
|
182
172
|
const base = buildFlowInfo(model, flow);
|
|
183
173
|
if (!base)
|
|
184
174
|
return null;
|
|
185
|
-
|
|
186
|
-
const
|
|
175
|
+
// Resolve linked copies to canonical originals for slice lookup
|
|
176
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
177
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
178
|
+
const sourceSlice = findSliceForComponent(model, canonicalSourceId);
|
|
179
|
+
const targetSlice = findSliceForComponent(model, canonicalTargetId);
|
|
187
180
|
return {
|
|
188
181
|
...base,
|
|
189
182
|
sourceSlice: sourceSlice ? { id: sourceSlice.id, name: sourceSlice.name } : undefined,
|
|
@@ -195,7 +188,10 @@ export function getInboundFlows(model, slice) {
|
|
|
195
188
|
const componentIds = getSliceComponentIds(model, slice);
|
|
196
189
|
const flows = [];
|
|
197
190
|
for (const flow of model.flows.values()) {
|
|
198
|
-
|
|
191
|
+
// Resolve linked copies to canonical originals for containment check
|
|
192
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
193
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
194
|
+
if (componentIds.has(canonicalTargetId) && !componentIds.has(canonicalSourceId)) {
|
|
199
195
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
200
196
|
if (flowInfo)
|
|
201
197
|
flows.push(flowInfo);
|
|
@@ -208,7 +204,10 @@ export function getOutboundFlows(model, slice) {
|
|
|
208
204
|
const componentIds = getSliceComponentIds(model, slice);
|
|
209
205
|
const flows = [];
|
|
210
206
|
for (const flow of model.flows.values()) {
|
|
211
|
-
|
|
207
|
+
// Resolve linked copies to canonical originals for containment check
|
|
208
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
209
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
210
|
+
if (componentIds.has(canonicalSourceId) && !componentIds.has(canonicalTargetId)) {
|
|
212
211
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
213
212
|
if (flowInfo)
|
|
214
213
|
flows.push(flowInfo);
|
|
@@ -221,7 +220,10 @@ export function getInternalFlows(model, slice) {
|
|
|
221
220
|
const componentIds = getSliceComponentIds(model, slice);
|
|
222
221
|
const flows = [];
|
|
223
222
|
for (const flow of model.flows.values()) {
|
|
224
|
-
|
|
223
|
+
// Resolve linked copies to canonical originals for containment check
|
|
224
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
225
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
226
|
+
if (componentIds.has(canonicalSourceId) && componentIds.has(canonicalTargetId)) {
|
|
225
227
|
const flowInfo = buildFlowInfo(model, flow);
|
|
226
228
|
if (flowInfo)
|
|
227
229
|
flows.push(flowInfo);
|
|
@@ -229,17 +231,22 @@ export function getInternalFlows(model, slice) {
|
|
|
229
231
|
}
|
|
230
232
|
return flows;
|
|
231
233
|
}
|
|
232
|
-
// Get all flows for a specific element (both directions)
|
|
234
|
+
// Get all flows for a specific element (both directions, resolves linked copies)
|
|
233
235
|
export function getFlowsForElement(model, elementId) {
|
|
234
236
|
const incoming = [];
|
|
235
237
|
const outgoing = [];
|
|
238
|
+
// Resolve element ID to canonical if it's a linked copy
|
|
239
|
+
const canonicalElementId = resolveToCanonical(model, elementId);
|
|
236
240
|
for (const flow of model.flows.values()) {
|
|
237
|
-
|
|
241
|
+
// Resolve flow endpoints to canonical originals
|
|
242
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
243
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
244
|
+
if (canonicalTargetId === canonicalElementId) {
|
|
238
245
|
const flowInfo = buildFlowInfo(model, flow);
|
|
239
246
|
if (flowInfo)
|
|
240
247
|
incoming.push(flowInfo);
|
|
241
248
|
}
|
|
242
|
-
if (
|
|
249
|
+
if (canonicalSourceId === canonicalElementId) {
|
|
243
250
|
const flowInfo = buildFlowInfo(model, flow);
|
|
244
251
|
if (flowInfo)
|
|
245
252
|
outgoing.push(flowInfo);
|
|
@@ -257,13 +264,16 @@ export function findSliceToSliceFlows(model, slices) {
|
|
|
257
264
|
// Group flows by slice pair
|
|
258
265
|
const flowsBySlicePair = new Map();
|
|
259
266
|
for (const flow of model.flows.values()) {
|
|
267
|
+
// Resolve linked copies to canonical originals
|
|
268
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
269
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
260
270
|
let sourceSliceId = null;
|
|
261
271
|
let targetSliceId = null;
|
|
262
272
|
// Find which slice contains source and target
|
|
263
273
|
for (const [sliceId, componentIds] of sliceComponentMap.entries()) {
|
|
264
|
-
if (componentIds.has(
|
|
274
|
+
if (componentIds.has(canonicalSourceId))
|
|
265
275
|
sourceSliceId = sliceId;
|
|
266
|
-
if (componentIds.has(
|
|
276
|
+
if (componentIds.has(canonicalTargetId))
|
|
267
277
|
targetSliceId = sliceId;
|
|
268
278
|
}
|
|
269
279
|
// Only include flows between different slices in our set
|
|
@@ -304,7 +314,10 @@ export function findChapterInboundFlows(model, slices) {
|
|
|
304
314
|
}
|
|
305
315
|
const flows = [];
|
|
306
316
|
for (const flow of model.flows.values()) {
|
|
307
|
-
|
|
317
|
+
// Resolve linked copies to canonical originals
|
|
318
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
319
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
320
|
+
if (chapterComponentIds.has(canonicalTargetId) && !chapterComponentIds.has(canonicalSourceId)) {
|
|
308
321
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
309
322
|
if (flowInfo)
|
|
310
323
|
flows.push(flowInfo);
|
|
@@ -322,7 +335,10 @@ export function findChapterOutboundFlows(model, slices) {
|
|
|
322
335
|
}
|
|
323
336
|
const flows = [];
|
|
324
337
|
for (const flow of model.flows.values()) {
|
|
325
|
-
|
|
338
|
+
// Resolve linked copies to canonical originals
|
|
339
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
340
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
341
|
+
if (chapterComponentIds.has(canonicalSourceId) && !chapterComponentIds.has(canonicalTargetId)) {
|
|
326
342
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
327
343
|
if (flowInfo)
|
|
328
344
|
flows.push(flowInfo);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { escapeXml, escapeXmlText, outputJson } from '../../lib/format.js';
|
|
2
2
|
import { findElementOrExit } from '../../lib/element-lookup.js';
|
|
3
3
|
import { findChapterForSlice, getChapterHierarchy } from '../../lib/chapter-utils.js';
|
|
4
|
-
import { getInboundFlows, getOutboundFlows, getFlowsForElement, getSliceComponentIds, } from '../../lib/flow-utils.js';
|
|
4
|
+
import { getInboundFlows, getOutboundFlows, getFlowsForElement, getSliceComponentIds, resolveToCanonical, } from '../../lib/flow-utils.js';
|
|
5
5
|
function formatFieldValues(values) {
|
|
6
6
|
if (!values || Object.keys(values).length === 0)
|
|
7
7
|
return '';
|
|
@@ -46,36 +46,16 @@ function getSliceComponents(model, slice) {
|
|
|
46
46
|
const centerY = pos.y + height / 2;
|
|
47
47
|
return centerX >= bounds.left && centerX <= bounds.right && centerY >= bounds.top && centerY <= bounds.bottom;
|
|
48
48
|
}
|
|
49
|
-
//
|
|
50
|
-
//
|
|
49
|
+
// Only include canonical elements - linked copies are UI-only conveniences
|
|
50
|
+
// and should not appear as slice contents in CLI output
|
|
51
51
|
return {
|
|
52
52
|
commands: [...model.commands.values()].filter(c => isInSlice(c.position, c.width, c.height)),
|
|
53
|
-
events: [...model.events.values()].filter(e => isInSlice(e.position, e.width, e.height)),
|
|
54
|
-
readModels: [...model.readModels.values()].filter(rm => isInSlice(rm.position, rm.width, rm.height)),
|
|
55
|
-
screens: [...model.screens.values()].filter(s => isInSlice(s.position, s.width, s.height)),
|
|
53
|
+
events: [...model.events.values()].filter(e => !e.originalNodeId && isInSlice(e.position, e.width, e.height)),
|
|
54
|
+
readModels: [...model.readModels.values()].filter(rm => !rm.originalNodeId && isInSlice(rm.position, rm.width, rm.height)),
|
|
55
|
+
screens: [...model.screens.values()].filter(s => !s.originalNodeId && isInSlice(s.position, s.width, s.height)),
|
|
56
56
|
processors: [...model.processors.values()].filter(p => isInSlice(p.position, p.width, p.height)),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
// Find which slice contains the original of a linked copy
|
|
60
|
-
function findSliceForNode(model, nodeId) {
|
|
61
|
-
const event = model.events.get(nodeId);
|
|
62
|
-
const readModel = model.readModels.get(nodeId);
|
|
63
|
-
const screen = model.screens.get(nodeId);
|
|
64
|
-
const node = event ?? readModel ?? screen;
|
|
65
|
-
if (!node)
|
|
66
|
-
return null;
|
|
67
|
-
for (const slice of model.slices.values()) {
|
|
68
|
-
const centerX = node.position.x + node.width / 2;
|
|
69
|
-
const centerY = node.position.y + node.height / 2;
|
|
70
|
-
if (centerX >= slice.position.x &&
|
|
71
|
-
centerX <= slice.position.x + slice.size.width &&
|
|
72
|
-
centerY >= slice.position.y &&
|
|
73
|
-
centerY <= slice.position.y + slice.size.height) {
|
|
74
|
-
return slice;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
59
|
// Find which aggregate an event belongs to (center point inside aggregate bounds)
|
|
80
60
|
function findAggregateForEvent(model, event) {
|
|
81
61
|
const centerX = event.position.x + event.width / 2;
|
|
@@ -191,8 +171,17 @@ function formatSliceXml(model, slice) {
|
|
|
191
171
|
const chapter = findChapterForSlice(model, slice);
|
|
192
172
|
// Use shared function that handles canonical groups for linked copies
|
|
193
173
|
const componentIds = getSliceComponentIds(model, slice);
|
|
194
|
-
|
|
195
|
-
const
|
|
174
|
+
// Resolve linked copies to canonical originals when checking flow containment
|
|
175
|
+
const flows = [...model.flows.values()].filter(f => {
|
|
176
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
177
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
178
|
+
return componentIds.has(canonicalSourceId) || componentIds.has(canonicalTargetId);
|
|
179
|
+
});
|
|
180
|
+
const internalFlows = flows.filter(f => {
|
|
181
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
182
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
183
|
+
return componentIds.has(canonicalSourceId) && componentIds.has(canonicalTargetId);
|
|
184
|
+
});
|
|
196
185
|
// Get inbound and outbound flows for the slice
|
|
197
186
|
const inboundFlows = getInboundFlows(model, slice);
|
|
198
187
|
const outboundFlows = getOutboundFlows(model, slice);
|
|
@@ -210,19 +199,10 @@ function formatSliceXml(model, slice) {
|
|
|
210
199
|
}
|
|
211
200
|
xml += ' <components>\n';
|
|
212
201
|
for (const screen of components.screens) {
|
|
213
|
-
// Check if this is a linked copy
|
|
214
|
-
const copyAttr = screen.originalNodeId ? ' linked-copy="true"' : '';
|
|
215
|
-
let originAttr = '';
|
|
216
|
-
if (screen.originalNodeId) {
|
|
217
|
-
const originSlice = findSliceForNode(model, screen.originalNodeId);
|
|
218
|
-
if (originSlice) {
|
|
219
|
-
originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
202
|
// Check which actor this screen belongs to
|
|
223
203
|
const actor = findActorForScreen(model, screen);
|
|
224
204
|
const actorAttr = actor ? ` actor="${escapeXml(actor.name)}"` : '';
|
|
225
|
-
xml += ` <screen id="${screen.id}" name="${escapeXml(screen.name)}"${
|
|
205
|
+
xml += ` <screen id="${screen.id}" name="${escapeXml(screen.name)}"${actorAttr}>\n`;
|
|
226
206
|
// Add flow annotations
|
|
227
207
|
const screenFlows = getFlowsForElement(model, screen.id);
|
|
228
208
|
xml += formatFlowAnnotationsXml(screen.id, screenFlows.incoming, screenFlows.outgoing, componentIds, ' ');
|
|
@@ -258,19 +238,10 @@ function formatSliceXml(model, slice) {
|
|
|
258
238
|
xml += ' </command>\n';
|
|
259
239
|
}
|
|
260
240
|
for (const event of components.events) {
|
|
261
|
-
// Check if this is a linked copy
|
|
262
|
-
const copyAttr = event.originalNodeId ? ' linked-copy="true"' : '';
|
|
263
|
-
let originAttr = '';
|
|
264
|
-
if (event.originalNodeId) {
|
|
265
|
-
const originSlice = findSliceForNode(model, event.originalNodeId);
|
|
266
|
-
if (originSlice) {
|
|
267
|
-
originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
241
|
// Check which aggregate this event belongs to
|
|
271
242
|
const aggregate = findAggregateForEvent(model, event);
|
|
272
243
|
const aggregateAttr = aggregate ? ` aggregate="${escapeXml(aggregate.name)}"` : '';
|
|
273
|
-
xml += ` <event id="${event.id}" name="${escapeXml(event.name)}"${
|
|
244
|
+
xml += ` <event id="${event.id}" name="${escapeXml(event.name)}"${aggregateAttr}>\n`;
|
|
274
245
|
// Add flow annotations
|
|
275
246
|
const eventFlows = getFlowsForElement(model, event.id);
|
|
276
247
|
xml += formatFlowAnnotationsXml(event.id, eventFlows.incoming, eventFlows.outgoing, componentIds, ' ');
|
|
@@ -295,16 +266,7 @@ function formatSliceXml(model, slice) {
|
|
|
295
266
|
xml += ' </event>\n';
|
|
296
267
|
}
|
|
297
268
|
for (const readModel of components.readModels) {
|
|
298
|
-
|
|
299
|
-
const copyAttr = readModel.originalNodeId ? ' linked-copy="true"' : '';
|
|
300
|
-
let originAttr = '';
|
|
301
|
-
if (readModel.originalNodeId) {
|
|
302
|
-
const originSlice = findSliceForNode(model, readModel.originalNodeId);
|
|
303
|
-
if (originSlice) {
|
|
304
|
-
originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
xml += ` <read-model id="${readModel.id}" name="${escapeXml(readModel.name)}"${copyAttr}${originAttr}>\n`;
|
|
269
|
+
xml += ` <read-model id="${readModel.id}" name="${escapeXml(readModel.name)}">\n`;
|
|
308
270
|
// Add flow annotations
|
|
309
271
|
const rmFlows = getFlowsForElement(model, readModel.id);
|
|
310
272
|
xml += formatFlowAnnotationsXml(readModel.id, rmFlows.incoming, rmFlows.outgoing, componentIds, ' ');
|
|
@@ -428,8 +390,17 @@ function formatSliceJson(model, slice) {
|
|
|
428
390
|
const chapter = findChapterForSlice(model, slice);
|
|
429
391
|
// Use shared function that handles canonical groups for linked copies
|
|
430
392
|
const componentIds = getSliceComponentIds(model, slice);
|
|
431
|
-
|
|
432
|
-
const
|
|
393
|
+
// Resolve linked copies to canonical originals when checking flow containment
|
|
394
|
+
const flows = [...model.flows.values()].filter(f => {
|
|
395
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
396
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
397
|
+
return componentIds.has(canonicalSourceId) || componentIds.has(canonicalTargetId);
|
|
398
|
+
});
|
|
399
|
+
const internalFlows = flows.filter(f => {
|
|
400
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
401
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
402
|
+
return componentIds.has(canonicalSourceId) && componentIds.has(canonicalTargetId);
|
|
403
|
+
});
|
|
433
404
|
// Get inbound and outbound flows for the slice
|
|
434
405
|
const inboundFlows = getInboundFlows(model, slice);
|
|
435
406
|
const outboundFlows = getOutboundFlows(model, slice);
|
|
@@ -453,12 +424,6 @@ function formatSliceJson(model, slice) {
|
|
|
453
424
|
name: screen.name,
|
|
454
425
|
fields: screen.fields.map(fieldToJson)
|
|
455
426
|
};
|
|
456
|
-
if (screen.originalNodeId) {
|
|
457
|
-
screenObj.linkedCopy = true;
|
|
458
|
-
const originSlice = findSliceForNode(model, screen.originalNodeId);
|
|
459
|
-
if (originSlice)
|
|
460
|
-
screenObj.originSlice = originSlice.name;
|
|
461
|
-
}
|
|
462
427
|
const actor = findActorForScreen(model, screen);
|
|
463
428
|
if (actor)
|
|
464
429
|
screenObj.actor = actor.name;
|
|
@@ -509,12 +474,6 @@ function formatSliceJson(model, slice) {
|
|
|
509
474
|
name: event.name,
|
|
510
475
|
fields: event.fields.map(fieldToJson)
|
|
511
476
|
};
|
|
512
|
-
if (event.originalNodeId) {
|
|
513
|
-
eventObj.linkedCopy = true;
|
|
514
|
-
const originSlice = findSliceForNode(model, event.originalNodeId);
|
|
515
|
-
if (originSlice)
|
|
516
|
-
eventObj.originSlice = originSlice.name;
|
|
517
|
-
}
|
|
518
477
|
const aggregate = findAggregateForEvent(model, event);
|
|
519
478
|
if (aggregate)
|
|
520
479
|
eventObj.aggregate = aggregate.name;
|
|
@@ -542,12 +501,6 @@ function formatSliceJson(model, slice) {
|
|
|
542
501
|
name: rm.name,
|
|
543
502
|
fields: rm.fields.map(fieldToJson)
|
|
544
503
|
};
|
|
545
|
-
if (rm.originalNodeId) {
|
|
546
|
-
rmObj.linkedCopy = true;
|
|
547
|
-
const originSlice = findSliceForNode(model, rm.originalNodeId);
|
|
548
|
-
if (originSlice)
|
|
549
|
-
rmObj.originSlice = originSlice.name;
|
|
550
|
-
}
|
|
551
504
|
// Add flow annotations
|
|
552
505
|
const rmFlows = getFlowsForElement(model, rm.id);
|
|
553
506
|
if (rmFlows.incoming.length > 0) {
|