pict-section-flow 0.0.5 → 0.0.7
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/package.json +1 -1
- package/source/PictFlowCard.js +11 -2
- package/source/panels/FlowCardPropertiesPanel-Form.js +5 -0
- package/source/providers/PictProvider-Flow-CSS.js +72 -4
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +33 -2
- package/source/providers/PictProvider-Flow-Geometry.js +83 -1
- package/source/services/PictService-Flow-ConnectionRenderer.js +42 -10
- package/source/views/PictView-Flow-Node.js +164 -77
- package/source/views/PictView-Flow.js +42 -1
package/package.json
CHANGED
package/source/PictFlowCard.js
CHANGED
|
@@ -133,6 +133,10 @@ class PictFlowCard extends libFableServiceProviderBase
|
|
|
133
133
|
MinimumInputCount: (typeof tmpInput.MinimumInputCount === 'number') ? tmpInput.MinimumInputCount : 0,
|
|
134
134
|
MaximumInputCount: (typeof tmpInput.MaximumInputCount === 'number') ? tmpInput.MaximumInputCount : -1
|
|
135
135
|
};
|
|
136
|
+
if (tmpInput.PortType)
|
|
137
|
+
{
|
|
138
|
+
tmpPort.PortType = tmpInput.PortType;
|
|
139
|
+
}
|
|
136
140
|
tmpPorts.push(tmpPort);
|
|
137
141
|
}
|
|
138
142
|
|
|
@@ -140,13 +144,18 @@ class PictFlowCard extends libFableServiceProviderBase
|
|
|
140
144
|
for (let i = 0; i < this.cardOutputs.length; i++)
|
|
141
145
|
{
|
|
142
146
|
let tmpOutput = this.cardOutputs[i];
|
|
143
|
-
|
|
147
|
+
let tmpOutPort =
|
|
144
148
|
{
|
|
145
149
|
Hash: null,
|
|
146
150
|
Direction: 'output',
|
|
147
151
|
Side: tmpOutput.Side || 'right',
|
|
148
152
|
Label: tmpOutput.Name || `Out ${i + 1}`
|
|
149
|
-
}
|
|
153
|
+
};
|
|
154
|
+
if (tmpOutput.PortType)
|
|
155
|
+
{
|
|
156
|
+
tmpOutPort.PortType = tmpOutput.PortType;
|
|
157
|
+
}
|
|
158
|
+
tmpPorts.push(tmpOutPort);
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
// If no ports were defined, provide sensible defaults
|
|
@@ -50,6 +50,11 @@ class FlowCardPropertiesPanelForm extends libPictFlowCardPropertiesPanel
|
|
|
50
50
|
let tmpContainerID = `pict-flow-panel-form-${pNodeData.Hash}`;
|
|
51
51
|
pContainer.innerHTML = `<div id="${tmpContainerID}"></div>`;
|
|
52
52
|
|
|
53
|
+
// Bind the node data to AppData.Record so the form descriptors
|
|
54
|
+
// (which use addresses like Record.Data.SearchString) resolve against
|
|
55
|
+
// the actual node object.
|
|
56
|
+
this.pict.AppData.Record = pNodeData;
|
|
57
|
+
|
|
53
58
|
try
|
|
54
59
|
{
|
|
55
60
|
// Look for an existing metacontroller or create one
|
|
@@ -61,6 +61,20 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
61
61
|
--pf-port-stroke: #ffffff;
|
|
62
62
|
--pf-port-stroke-width: 2;
|
|
63
63
|
|
|
64
|
+
/* Port Type Colors */
|
|
65
|
+
--pf-port-event-in-fill: #3498db;
|
|
66
|
+
--pf-port-event-out-fill: #2ecc71;
|
|
67
|
+
--pf-port-setting-fill: #e67e22;
|
|
68
|
+
--pf-port-value-fill: #f1c40f;
|
|
69
|
+
--pf-port-error-fill: #e74c3c;
|
|
70
|
+
|
|
71
|
+
/* Connection Type Colors (match source port) */
|
|
72
|
+
--pf-connection-event-in-stroke: #3498db;
|
|
73
|
+
--pf-connection-event-out-stroke: #2ecc71;
|
|
74
|
+
--pf-connection-setting-stroke: #e67e22;
|
|
75
|
+
--pf-connection-value-stroke: #f1c40f;
|
|
76
|
+
--pf-connection-error-stroke: #e74c3c;
|
|
77
|
+
|
|
64
78
|
/* Panels */
|
|
65
79
|
--pf-panel-bg: #ffffff;
|
|
66
80
|
--pf-panel-border: #d0d4d8;
|
|
@@ -286,18 +300,40 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
286
300
|
r: 7;
|
|
287
301
|
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.20));
|
|
288
302
|
}
|
|
303
|
+
/* Port type color overrides */
|
|
304
|
+
.pict-flow-port.port-type-event-in {
|
|
305
|
+
fill: var(--pf-port-event-in-fill);
|
|
306
|
+
}
|
|
307
|
+
.pict-flow-port.port-type-event-out {
|
|
308
|
+
fill: var(--pf-port-event-out-fill);
|
|
309
|
+
}
|
|
310
|
+
.pict-flow-port.port-type-setting {
|
|
311
|
+
fill: var(--pf-port-setting-fill);
|
|
312
|
+
}
|
|
313
|
+
.pict-flow-port.port-type-value {
|
|
314
|
+
fill: var(--pf-port-value-fill);
|
|
315
|
+
}
|
|
316
|
+
.pict-flow-port.port-type-error {
|
|
317
|
+
fill: var(--pf-port-error-fill);
|
|
318
|
+
}
|
|
289
319
|
.pict-flow-port-label {
|
|
290
|
-
|
|
291
|
-
font-
|
|
320
|
+
font-size: 8px;
|
|
321
|
+
font-weight: 600;
|
|
292
322
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
293
323
|
pointer-events: none;
|
|
294
324
|
}
|
|
325
|
+
/* Port label badge background */
|
|
326
|
+
.pict-flow-port-label-bg {
|
|
327
|
+
pointer-events: none;
|
|
328
|
+
}
|
|
295
329
|
/* Port labels on hover: hidden by default, revealed on node hover */
|
|
296
|
-
.pict-flow-node-port-labels-hover .pict-flow-port-label
|
|
330
|
+
.pict-flow-node-port-labels-hover .pict-flow-port-label,
|
|
331
|
+
.pict-flow-node-port-labels-hover .pict-flow-port-label-bg {
|
|
297
332
|
opacity: 0;
|
|
298
333
|
transition: opacity 0.2s;
|
|
299
334
|
}
|
|
300
|
-
.pict-flow-node-port-labels-hover:hover .pict-flow-port-label
|
|
335
|
+
.pict-flow-node-port-labels-hover:hover .pict-flow-port-label,
|
|
336
|
+
.pict-flow-node-port-labels-hover:hover .pict-flow-port-label-bg {
|
|
301
337
|
opacity: 1;
|
|
302
338
|
}
|
|
303
339
|
`;
|
|
@@ -326,6 +362,22 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
326
362
|
stroke: var(--pf-connection-selected-stroke);
|
|
327
363
|
stroke-width: 3;
|
|
328
364
|
}
|
|
365
|
+
/* Connection type color overrides (based on source port type) */
|
|
366
|
+
.pict-flow-connection.conn-type-event-in {
|
|
367
|
+
stroke: var(--pf-connection-event-in-stroke);
|
|
368
|
+
}
|
|
369
|
+
.pict-flow-connection.conn-type-event-out {
|
|
370
|
+
stroke: var(--pf-connection-event-out-stroke);
|
|
371
|
+
}
|
|
372
|
+
.pict-flow-connection.conn-type-setting {
|
|
373
|
+
stroke: var(--pf-connection-setting-stroke);
|
|
374
|
+
}
|
|
375
|
+
.pict-flow-connection.conn-type-value {
|
|
376
|
+
stroke: var(--pf-connection-value-stroke);
|
|
377
|
+
}
|
|
378
|
+
.pict-flow-connection.conn-type-error {
|
|
379
|
+
stroke: var(--pf-connection-error-stroke);
|
|
380
|
+
}
|
|
329
381
|
.pict-flow-connection-hitarea {
|
|
330
382
|
fill: none;
|
|
331
383
|
stroke: transparent;
|
|
@@ -591,6 +643,22 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
591
643
|
.pict-flow-info-panel-port.output {
|
|
592
644
|
border-left: 3px solid var(--pf-port-output-fill);
|
|
593
645
|
}
|
|
646
|
+
/* Info panel port type color overrides */
|
|
647
|
+
.pict-flow-info-panel-port.port-type-event-in {
|
|
648
|
+
border-left-color: var(--pf-port-event-in-fill);
|
|
649
|
+
}
|
|
650
|
+
.pict-flow-info-panel-port.port-type-event-out {
|
|
651
|
+
border-left-color: var(--pf-port-event-out-fill);
|
|
652
|
+
}
|
|
653
|
+
.pict-flow-info-panel-port.port-type-setting {
|
|
654
|
+
border-left-color: var(--pf-port-setting-fill);
|
|
655
|
+
}
|
|
656
|
+
.pict-flow-info-panel-port.port-type-value {
|
|
657
|
+
border-left-color: var(--pf-port-value-fill);
|
|
658
|
+
}
|
|
659
|
+
.pict-flow-info-panel-port.port-type-error {
|
|
660
|
+
border-left-color: var(--pf-port-error-fill);
|
|
661
|
+
}
|
|
594
662
|
.pict-flow-info-panel-port-constraint {
|
|
595
663
|
color: #8e99a4;
|
|
596
664
|
font-size: 10px;
|
|
@@ -195,7 +195,12 @@ class PictProviderFlowConnectorShapes extends libFableServiceProviderBase
|
|
|
195
195
|
{
|
|
196
196
|
let tmpConfig = this._DefaultShapes['port'];
|
|
197
197
|
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
198
|
-
|
|
198
|
+
let tmpClassName = tmpConfig.ClassName + ' ' + pPortData.Direction;
|
|
199
|
+
if (pPortData.PortType)
|
|
200
|
+
{
|
|
201
|
+
tmpClassName += ' port-type-' + pPortData.PortType;
|
|
202
|
+
}
|
|
203
|
+
tmpElement.setAttribute('class', tmpClassName);
|
|
199
204
|
tmpElement.setAttribute('cx', String(pPosition.x));
|
|
200
205
|
tmpElement.setAttribute('cy', String(pPosition.y));
|
|
201
206
|
// Apply config attributes (r, etc.)
|
|
@@ -206,6 +211,10 @@ class PictProviderFlowConnectorShapes extends libFableServiceProviderBase
|
|
|
206
211
|
tmpElement.setAttribute('data-port-hash', pPortData.Hash);
|
|
207
212
|
tmpElement.setAttribute('data-node-hash', pNodeHash);
|
|
208
213
|
tmpElement.setAttribute('data-port-direction', pPortData.Direction);
|
|
214
|
+
if (pPortData.PortType)
|
|
215
|
+
{
|
|
216
|
+
tmpElement.setAttribute('data-port-type', pPortData.PortType);
|
|
217
|
+
}
|
|
209
218
|
tmpElement.setAttribute('data-element-type', 'port');
|
|
210
219
|
return tmpElement;
|
|
211
220
|
}
|
|
@@ -376,7 +385,7 @@ class PictProviderFlowConnectorShapes extends libFableServiceProviderBase
|
|
|
376
385
|
|
|
377
386
|
let tmpMarkup = '';
|
|
378
387
|
|
|
379
|
-
// Normal connection arrowhead
|
|
388
|
+
// Normal connection arrowhead (default gray)
|
|
380
389
|
tmpMarkup += '<marker id="flow-arrowhead-' + pViewIdentifier + '"'
|
|
381
390
|
+ ' markerWidth="' + tmpConnectionMarker.MarkerWidth + '"'
|
|
382
391
|
+ ' markerHeight="' + tmpConnectionMarker.MarkerHeight + '"'
|
|
@@ -386,6 +395,28 @@ class PictProviderFlowConnectorShapes extends libFableServiceProviderBase
|
|
|
386
395
|
+ '<polygon points="' + tmpConnectionMarker.Points + '" fill="' + tmpConnectionMarker.Fill + '" />'
|
|
387
396
|
+ '</marker>';
|
|
388
397
|
|
|
398
|
+
// Per-port-type connection arrowheads
|
|
399
|
+
let tmpPortTypeColors =
|
|
400
|
+
{
|
|
401
|
+
'event-in': '#3498db',
|
|
402
|
+
'event-out': '#2ecc71',
|
|
403
|
+
'setting': '#e67e22',
|
|
404
|
+
'value': '#f1c40f',
|
|
405
|
+
'error': '#e74c3c'
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
for (let tmpType in tmpPortTypeColors)
|
|
409
|
+
{
|
|
410
|
+
tmpMarkup += '<marker id="flow-arrowhead-' + tmpType + '-' + pViewIdentifier + '"'
|
|
411
|
+
+ ' markerWidth="' + tmpConnectionMarker.MarkerWidth + '"'
|
|
412
|
+
+ ' markerHeight="' + tmpConnectionMarker.MarkerHeight + '"'
|
|
413
|
+
+ ' refX="' + tmpConnectionMarker.RefX + '"'
|
|
414
|
+
+ ' refY="' + tmpConnectionMarker.RefY + '"'
|
|
415
|
+
+ ' orient="auto" markerUnits="strokeWidth">'
|
|
416
|
+
+ '<polygon points="' + tmpConnectionMarker.Points + '" fill="' + tmpPortTypeColors[tmpType] + '" />'
|
|
417
|
+
+ '</marker>';
|
|
418
|
+
}
|
|
419
|
+
|
|
389
420
|
// Selected connection arrowhead
|
|
390
421
|
tmpMarkup += '<marker id="flow-arrowhead-selected-' + pViewIdentifier + '"'
|
|
391
422
|
+ ' markerWidth="' + tmpSelectedMarker.MarkerWidth + '"'
|
|
@@ -134,13 +134,25 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
|
134
134
|
let tmpEdge = this.getEdgeFromSide(pSide);
|
|
135
135
|
let tmpZone = this._getZoneFromSide(pSide);
|
|
136
136
|
|
|
137
|
+
// Minimum spacing between port centers (px)
|
|
138
|
+
let tmpMinSpacing = 16;
|
|
139
|
+
|
|
140
|
+
// Reserve space at the bottom of the body so that port badges
|
|
141
|
+
// never overlap the panel-indicator icon (10×10 rect at bottom-right)
|
|
142
|
+
// and always leave a visible gap above the node bottom edge.
|
|
143
|
+
let tmpBottomPad = 16;
|
|
144
|
+
|
|
137
145
|
if (tmpEdge === 'left' || tmpEdge === 'right')
|
|
138
146
|
{
|
|
139
147
|
let tmpX = (tmpEdge === 'left') ? 0 : pWidth;
|
|
140
|
-
let tmpBodyHeight = pHeight - pTitleBarHeight;
|
|
148
|
+
let tmpBodyHeight = pHeight - pTitleBarHeight - tmpBottomPad;
|
|
141
149
|
let tmpZoneStart = pTitleBarHeight + tmpBodyHeight * tmpZone.start;
|
|
142
150
|
let tmpZoneHeight = tmpBodyHeight * (tmpZone.end - tmpZone.start);
|
|
143
151
|
let tmpSpacing = tmpZoneHeight / (pTotal + 1);
|
|
152
|
+
if (tmpSpacing < tmpMinSpacing)
|
|
153
|
+
{
|
|
154
|
+
tmpSpacing = tmpMinSpacing;
|
|
155
|
+
}
|
|
144
156
|
let tmpY = tmpZoneStart + tmpSpacing * (pIndex + 1);
|
|
145
157
|
return { x: tmpX, y: tmpY };
|
|
146
158
|
}
|
|
@@ -150,6 +162,10 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
|
150
162
|
let tmpZoneStart = pWidth * tmpZone.start;
|
|
151
163
|
let tmpZoneWidth = pWidth * (tmpZone.end - tmpZone.start);
|
|
152
164
|
let tmpSpacing = tmpZoneWidth / (pTotal + 1);
|
|
165
|
+
if (tmpSpacing < tmpMinSpacing)
|
|
166
|
+
{
|
|
167
|
+
tmpSpacing = tmpMinSpacing;
|
|
168
|
+
}
|
|
153
169
|
let tmpX = tmpZoneStart + tmpSpacing * (pIndex + 1);
|
|
154
170
|
return { x: tmpX, y: tmpY };
|
|
155
171
|
}
|
|
@@ -193,6 +209,72 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
|
193
209
|
default: return { start: 0.0, end: 1.0 };
|
|
194
210
|
}
|
|
195
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Compute the minimum node height required so that all ports
|
|
214
|
+
* (with their badges) fit within the node boundary.
|
|
215
|
+
*
|
|
216
|
+
* Uses the same zone system and minimum spacing as getPortLocalPosition.
|
|
217
|
+
* For each left/right zone, calculates where the last port would land
|
|
218
|
+
* and ensures the node is tall enough to contain it plus badge clearance.
|
|
219
|
+
*
|
|
220
|
+
* @param {Array} pPorts - Array of port objects with Side, Direction
|
|
221
|
+
* @param {number} pTitleBarHeight - Height of the title bar
|
|
222
|
+
* @returns {number} Minimum node height in pixels (0 if no ports)
|
|
223
|
+
*/
|
|
224
|
+
computeMinimumNodeHeight(pPorts, pTitleBarHeight)
|
|
225
|
+
{
|
|
226
|
+
if (!pPorts || !Array.isArray(pPorts) || pPorts.length === 0)
|
|
227
|
+
{
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let tmpMinSpacing = 16;
|
|
232
|
+
let tmpBadgeHalfHeight = 6;
|
|
233
|
+
let tmpBottomPad = 16;
|
|
234
|
+
|
|
235
|
+
// Count ports per Side value
|
|
236
|
+
let tmpCountBySide = {};
|
|
237
|
+
for (let i = 0; i < pPorts.length; i++)
|
|
238
|
+
{
|
|
239
|
+
let tmpSide = pPorts[i].Side || (pPorts[i].Direction === 'input' ? 'left' : 'right');
|
|
240
|
+
if (!tmpCountBySide[tmpSide])
|
|
241
|
+
{
|
|
242
|
+
tmpCountBySide[tmpSide] = 0;
|
|
243
|
+
}
|
|
244
|
+
tmpCountBySide[tmpSide]++;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let tmpMinHeight = 0;
|
|
248
|
+
|
|
249
|
+
for (let tmpSide in tmpCountBySide)
|
|
250
|
+
{
|
|
251
|
+
let tmpCount = tmpCountBySide[tmpSide];
|
|
252
|
+
let tmpEdge = this.getEdgeFromSide(tmpSide);
|
|
253
|
+
|
|
254
|
+
// Only left/right edge zones affect required height
|
|
255
|
+
if (tmpEdge !== 'left' && tmpEdge !== 'right')
|
|
256
|
+
{
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let tmpZone = this._getZoneFromSide(tmpSide);
|
|
261
|
+
|
|
262
|
+
// With bottomPad reserving space at the bottom:
|
|
263
|
+
// bodyHeight = H - titleBar - bottomPad
|
|
264
|
+
// lastPortY = titleBar + bodyHeight * zone.start + minSpacing * count
|
|
265
|
+
// Need: lastPortY + badgeHalfHeight <= H - bottomPad
|
|
266
|
+
// Solving for H:
|
|
267
|
+
// H >= titleBar + bottomPad + (minSpacing * count + badgeHalfHeight) / (1 - zone.start)
|
|
268
|
+
let tmpRequired = pTitleBarHeight + tmpBottomPad + (tmpMinSpacing * tmpCount + tmpBadgeHalfHeight) / (1 - tmpZone.start);
|
|
269
|
+
|
|
270
|
+
if (tmpRequired > tmpMinHeight)
|
|
271
|
+
{
|
|
272
|
+
tmpMinHeight = tmpRequired;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return Math.ceil(tmpMinHeight);
|
|
277
|
+
}
|
|
196
278
|
}
|
|
197
279
|
|
|
198
280
|
module.exports = PictProviderFlowGeometry;
|
|
@@ -24,6 +24,21 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
|
|
|
24
24
|
let tmpSourcePos = this._FlowView.getPortPosition(pConnection.SourceNodeHash, pConnection.SourcePortHash);
|
|
25
25
|
let tmpTargetPos = this._FlowView.getPortPosition(pConnection.TargetNodeHash, pConnection.TargetPortHash);
|
|
26
26
|
|
|
27
|
+
// Look up the source port's PortType for connection coloring
|
|
28
|
+
let tmpSourcePortType = null;
|
|
29
|
+
let tmpSourceNode = this._FlowView.getNode(pConnection.SourceNodeHash);
|
|
30
|
+
if (tmpSourceNode && tmpSourceNode.Ports)
|
|
31
|
+
{
|
|
32
|
+
for (let i = 0; i < tmpSourceNode.Ports.length; i++)
|
|
33
|
+
{
|
|
34
|
+
if (tmpSourceNode.Ports[i].Hash === pConnection.SourcePortHash)
|
|
35
|
+
{
|
|
36
|
+
tmpSourcePortType = tmpSourceNode.Ports[i].PortType || null;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
if (!tmpSourcePos || !tmpTargetPos) return;
|
|
28
43
|
|
|
29
44
|
let tmpData = pConnection.Data || {};
|
|
@@ -69,6 +84,24 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
|
|
|
69
84
|
|
|
70
85
|
let tmpViewIdentifier = this._FlowView.options.ViewIdentifier;
|
|
71
86
|
|
|
87
|
+
// Build the port-type CSS class suffix for connection coloring
|
|
88
|
+
let tmpConnTypeClass = tmpSourcePortType ? (' conn-type-' + tmpSourcePortType) : '';
|
|
89
|
+
|
|
90
|
+
// Determine the arrowhead marker based on port type
|
|
91
|
+
let tmpArrowMarkerId;
|
|
92
|
+
if (pIsSelected)
|
|
93
|
+
{
|
|
94
|
+
tmpArrowMarkerId = 'flow-arrowhead-selected-' + tmpViewIdentifier;
|
|
95
|
+
}
|
|
96
|
+
else if (tmpSourcePortType)
|
|
97
|
+
{
|
|
98
|
+
tmpArrowMarkerId = 'flow-arrowhead-' + tmpSourcePortType + '-' + tmpViewIdentifier;
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
{
|
|
102
|
+
tmpArrowMarkerId = 'flow-arrowhead-' + tmpViewIdentifier;
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
// Hit area (wider invisible path for easier selection)
|
|
73
106
|
let tmpShapeProvider = this._FlowView._ConnectorShapesProvider;
|
|
74
107
|
if (tmpShapeProvider)
|
|
@@ -78,6 +111,13 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
|
|
|
78
111
|
|
|
79
112
|
let tmpPathElement = tmpShapeProvider.createConnectionPathElement(
|
|
80
113
|
tmpPath, pConnection.Hash, pIsSelected, tmpViewIdentifier);
|
|
114
|
+
if (tmpConnTypeClass)
|
|
115
|
+
{
|
|
116
|
+
tmpPathElement.setAttribute('class',
|
|
117
|
+
(tmpPathElement.getAttribute('class') || '') + tmpConnTypeClass);
|
|
118
|
+
}
|
|
119
|
+
// Override the default arrowhead with the typed one
|
|
120
|
+
tmpPathElement.setAttribute('marker-end', 'url(#' + tmpArrowMarkerId + ')');
|
|
81
121
|
if (tmpStrokeDashArray)
|
|
82
122
|
{
|
|
83
123
|
tmpPathElement.setAttribute('stroke-dasharray', tmpStrokeDashArray);
|
|
@@ -94,19 +134,11 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
|
|
|
94
134
|
pConnectionsLayer.appendChild(tmpHitArea);
|
|
95
135
|
|
|
96
136
|
let tmpPathElement = this._FlowView._SVGHelperProvider.createSVGElement('path');
|
|
97
|
-
tmpPathElement.setAttribute('class', `pict-flow-connection ${pIsSelected ? 'selected' : ''}`);
|
|
137
|
+
tmpPathElement.setAttribute('class', `pict-flow-connection${tmpConnTypeClass} ${pIsSelected ? 'selected' : ''}`);
|
|
98
138
|
tmpPathElement.setAttribute('d', tmpPath);
|
|
99
139
|
tmpPathElement.setAttribute('data-connection-hash', pConnection.Hash);
|
|
100
140
|
tmpPathElement.setAttribute('data-element-type', 'connection');
|
|
101
|
-
|
|
102
|
-
if (pIsSelected)
|
|
103
|
-
{
|
|
104
|
-
tmpPathElement.setAttribute('marker-end', `url(#flow-arrowhead-selected-${tmpViewIdentifier})`);
|
|
105
|
-
}
|
|
106
|
-
else
|
|
107
|
-
{
|
|
108
|
-
tmpPathElement.setAttribute('marker-end', `url(#flow-arrowhead-${tmpViewIdentifier})`);
|
|
109
|
-
}
|
|
141
|
+
tmpPathElement.setAttribute('marker-end', 'url(#' + tmpArrowMarkerId + ')');
|
|
110
142
|
|
|
111
143
|
if (tmpStrokeDashArray)
|
|
112
144
|
{
|
|
@@ -49,6 +49,23 @@ class PictViewFlowNode extends libPictView
|
|
|
49
49
|
let tmpHeight = pNodeData.Height || 80;
|
|
50
50
|
let tmpTitleBarHeight = this.options.NodeTitleBarHeight;
|
|
51
51
|
|
|
52
|
+
// Ensure node is tall enough for all ports in their zones
|
|
53
|
+
let tmpGeomProvider = this._FlowView._GeometryProvider;
|
|
54
|
+
if (tmpGeomProvider && pNodeData.Ports && pNodeData.Ports.length > 0)
|
|
55
|
+
{
|
|
56
|
+
let tmpMinHeight = tmpGeomProvider.computeMinimumNodeHeight(pNodeData.Ports, tmpTitleBarHeight);
|
|
57
|
+
if (tmpMinHeight > tmpHeight)
|
|
58
|
+
{
|
|
59
|
+
tmpHeight = tmpMinHeight;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Write the adjusted dimensions back to the node data so that
|
|
64
|
+
// connection rendering (which reads pNodeData.Width/Height to
|
|
65
|
+
// compute port positions) uses the same values we render with.
|
|
66
|
+
pNodeData.Width = tmpWidth;
|
|
67
|
+
pNodeData.Height = tmpHeight;
|
|
68
|
+
|
|
52
69
|
// Determine node body mode from theme (bracket vs rect)
|
|
53
70
|
let tmpNodeBodyMode = 'rect';
|
|
54
71
|
if (this._FlowView._ThemeProvider)
|
|
@@ -319,7 +336,140 @@ class PictViewFlowNode extends libPictView
|
|
|
319
336
|
let tmpPort = tmpPorts[i];
|
|
320
337
|
let tmpPosition = this._getPortLocalPosition(tmpSide, i, tmpPorts.length, pWidth, pHeight);
|
|
321
338
|
|
|
322
|
-
// Port
|
|
339
|
+
// Port label badge — flush against the node edge with no
|
|
340
|
+
// border on the edge side; rendered before the port circle
|
|
341
|
+
// so the circle visually sits on top of the badge
|
|
342
|
+
let tmpLabelElement = null;
|
|
343
|
+
if (tmpPort.Label)
|
|
344
|
+
{
|
|
345
|
+
let tmpPortTypeColorMap =
|
|
346
|
+
{
|
|
347
|
+
'event-in': '#3498db',
|
|
348
|
+
'event-out': '#2ecc71',
|
|
349
|
+
'setting': '#e67e22',
|
|
350
|
+
'value': '#f1c40f',
|
|
351
|
+
'error': '#e74c3c'
|
|
352
|
+
};
|
|
353
|
+
let tmpBorderColor = tmpPort.PortType ? (tmpPortTypeColorMap[tmpPort.PortType] || '#95a5a6') : '#95a5a6';
|
|
354
|
+
|
|
355
|
+
let tmpBadgeHeight = 12;
|
|
356
|
+
let tmpBadgePadH = 5;
|
|
357
|
+
let tmpBadgeBorderW = 2;
|
|
358
|
+
let tmpEdgePad = 1;
|
|
359
|
+
let tmpPortRadius = 5;
|
|
360
|
+
|
|
361
|
+
let tmpTextLen = tmpPort.Label.length * 5;
|
|
362
|
+
let tmpBadgeX, tmpBadgeY, tmpBadgeWidth;
|
|
363
|
+
let tmpTextX, tmpTextAnchor;
|
|
364
|
+
let tmpStripeX, tmpStripeY, tmpStripeW, tmpStripeH;
|
|
365
|
+
let tmpBorderPath;
|
|
366
|
+
|
|
367
|
+
if (tmpEdge === 'left')
|
|
368
|
+
{
|
|
369
|
+
tmpBadgeWidth = tmpPortRadius + tmpBadgePadH + tmpTextLen + tmpBadgePadH + tmpBadgeBorderW;
|
|
370
|
+
tmpBadgeX = tmpEdgePad;
|
|
371
|
+
tmpBadgeY = tmpPosition.y - tmpBadgeHeight / 2;
|
|
372
|
+
tmpTextX = tmpBadgeX + tmpPortRadius + tmpBadgePadH;
|
|
373
|
+
tmpTextAnchor = 'start';
|
|
374
|
+
tmpStripeX = tmpBadgeX + tmpBadgeWidth - tmpBadgeBorderW;
|
|
375
|
+
tmpStripeY = tmpBadgeY;
|
|
376
|
+
tmpStripeW = tmpBadgeBorderW;
|
|
377
|
+
tmpStripeH = tmpBadgeHeight;
|
|
378
|
+
tmpBorderPath = 'M ' + tmpBadgeX + ' ' + tmpBadgeY
|
|
379
|
+
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + tmpBadgeY
|
|
380
|
+
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + (tmpBadgeY + tmpBadgeHeight)
|
|
381
|
+
+ ' L ' + tmpBadgeX + ' ' + (tmpBadgeY + tmpBadgeHeight);
|
|
382
|
+
}
|
|
383
|
+
else if (tmpEdge === 'right')
|
|
384
|
+
{
|
|
385
|
+
tmpBadgeWidth = tmpBadgeBorderW + tmpBadgePadH + tmpTextLen + tmpBadgePadH + tmpPortRadius;
|
|
386
|
+
tmpBadgeX = pWidth - tmpBadgeWidth - tmpEdgePad;
|
|
387
|
+
tmpBadgeY = tmpPosition.y - tmpBadgeHeight / 2;
|
|
388
|
+
tmpTextX = tmpBadgeX + tmpBadgeBorderW + tmpBadgePadH;
|
|
389
|
+
tmpTextAnchor = 'start';
|
|
390
|
+
tmpStripeX = tmpBadgeX;
|
|
391
|
+
tmpStripeY = tmpBadgeY;
|
|
392
|
+
tmpStripeW = tmpBadgeBorderW;
|
|
393
|
+
tmpStripeH = tmpBadgeHeight;
|
|
394
|
+
tmpBorderPath = 'M ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + tmpBadgeY
|
|
395
|
+
+ ' L ' + tmpBadgeX + ' ' + tmpBadgeY
|
|
396
|
+
+ ' L ' + tmpBadgeX + ' ' + (tmpBadgeY + tmpBadgeHeight)
|
|
397
|
+
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + (tmpBadgeY + tmpBadgeHeight);
|
|
398
|
+
}
|
|
399
|
+
else if (tmpEdge === 'top')
|
|
400
|
+
{
|
|
401
|
+
tmpBadgeWidth = tmpTextLen + tmpBadgePadH * 2;
|
|
402
|
+
tmpBadgeX = tmpPosition.x - tmpBadgeWidth / 2;
|
|
403
|
+
tmpBadgeY = tmpEdgePad;
|
|
404
|
+
tmpTextX = tmpPosition.x;
|
|
405
|
+
tmpTextAnchor = 'middle';
|
|
406
|
+
tmpStripeX = tmpBadgeX;
|
|
407
|
+
tmpStripeY = tmpBadgeY + tmpBadgeHeight - tmpBadgeBorderW;
|
|
408
|
+
tmpStripeW = tmpBadgeWidth;
|
|
409
|
+
tmpStripeH = tmpBadgeBorderW;
|
|
410
|
+
tmpBorderPath = 'M ' + tmpBadgeX + ' ' + tmpBadgeY
|
|
411
|
+
+ ' L ' + tmpBadgeX + ' ' + (tmpBadgeY + tmpBadgeHeight)
|
|
412
|
+
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + (tmpBadgeY + tmpBadgeHeight)
|
|
413
|
+
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + tmpBadgeY;
|
|
414
|
+
}
|
|
415
|
+
else
|
|
416
|
+
{
|
|
417
|
+
tmpBadgeWidth = tmpTextLen + tmpBadgePadH * 2;
|
|
418
|
+
tmpBadgeX = tmpPosition.x - tmpBadgeWidth / 2;
|
|
419
|
+
tmpBadgeY = pHeight - tmpBadgeHeight - tmpEdgePad;
|
|
420
|
+
tmpTextX = tmpPosition.x;
|
|
421
|
+
tmpTextAnchor = 'middle';
|
|
422
|
+
tmpStripeX = tmpBadgeX;
|
|
423
|
+
tmpStripeY = tmpBadgeY;
|
|
424
|
+
tmpStripeW = tmpBadgeWidth;
|
|
425
|
+
tmpStripeH = tmpBadgeBorderW;
|
|
426
|
+
tmpBorderPath = 'M ' + tmpBadgeX + ' ' + (tmpBadgeY + tmpBadgeHeight)
|
|
427
|
+
+ ' L ' + tmpBadgeX + ' ' + tmpBadgeY
|
|
428
|
+
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + tmpBadgeY
|
|
429
|
+
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + (tmpBadgeY + tmpBadgeHeight);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Background rect (cream, no stroke — border drawn separately)
|
|
433
|
+
let tmpBgRect = this._FlowView._SVGHelperProvider.createSVGElement('rect');
|
|
434
|
+
tmpBgRect.setAttribute('class', 'pict-flow-port-label-bg');
|
|
435
|
+
tmpBgRect.setAttribute('x', String(tmpBadgeX));
|
|
436
|
+
tmpBgRect.setAttribute('y', String(tmpBadgeY));
|
|
437
|
+
tmpBgRect.setAttribute('width', String(tmpBadgeWidth));
|
|
438
|
+
tmpBgRect.setAttribute('height', String(tmpBadgeHeight));
|
|
439
|
+
tmpBgRect.setAttribute('fill', 'rgba(255, 253, 240, 0.5)');
|
|
440
|
+
pGroup.appendChild(tmpBgRect);
|
|
441
|
+
|
|
442
|
+
// 3-sided border path (open on the edge-facing side)
|
|
443
|
+
let tmpBorderPathEl = this._FlowView._SVGHelperProvider.createSVGElement('path');
|
|
444
|
+
tmpBorderPathEl.setAttribute('class', 'pict-flow-port-label-bg');
|
|
445
|
+
tmpBorderPathEl.setAttribute('d', tmpBorderPath);
|
|
446
|
+
tmpBorderPathEl.setAttribute('fill', 'none');
|
|
447
|
+
tmpBorderPathEl.setAttribute('stroke', tmpBorderColor);
|
|
448
|
+
tmpBorderPathEl.setAttribute('stroke-width', '0.75');
|
|
449
|
+
pGroup.appendChild(tmpBorderPathEl);
|
|
450
|
+
|
|
451
|
+
// Colored stripe on the inner side
|
|
452
|
+
let tmpStripe = this._FlowView._SVGHelperProvider.createSVGElement('rect');
|
|
453
|
+
tmpStripe.setAttribute('class', 'pict-flow-port-label-bg');
|
|
454
|
+
tmpStripe.setAttribute('x', String(tmpStripeX));
|
|
455
|
+
tmpStripe.setAttribute('y', String(tmpStripeY));
|
|
456
|
+
tmpStripe.setAttribute('width', String(tmpStripeW));
|
|
457
|
+
tmpStripe.setAttribute('height', String(tmpStripeH));
|
|
458
|
+
tmpStripe.setAttribute('fill', tmpBorderColor);
|
|
459
|
+
pGroup.appendChild(tmpStripe);
|
|
460
|
+
|
|
461
|
+
// Text label — appended after circle for z-order
|
|
462
|
+
tmpLabelElement = this._FlowView._SVGHelperProvider.createSVGElement('text');
|
|
463
|
+
tmpLabelElement.setAttribute('class', 'pict-flow-port-label');
|
|
464
|
+
tmpLabelElement.setAttribute('fill', '#2c3e50');
|
|
465
|
+
tmpLabelElement.textContent = tmpPort.Label;
|
|
466
|
+
tmpLabelElement.setAttribute('x', String(tmpTextX));
|
|
467
|
+
tmpLabelElement.setAttribute('y', String(tmpBadgeY + tmpBadgeHeight / 2));
|
|
468
|
+
tmpLabelElement.setAttribute('text-anchor', tmpTextAnchor);
|
|
469
|
+
tmpLabelElement.setAttribute('dominant-baseline', 'central');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Port circle (rendered on top of badge background)
|
|
323
473
|
let tmpShapeProvider = this._FlowView._ConnectorShapesProvider;
|
|
324
474
|
let tmpCircle;
|
|
325
475
|
if (tmpShapeProvider)
|
|
@@ -329,93 +479,30 @@ class PictViewFlowNode extends libPictView
|
|
|
329
479
|
else
|
|
330
480
|
{
|
|
331
481
|
tmpCircle = this._FlowView._SVGHelperProvider.createSVGElement('circle');
|
|
332
|
-
|
|
482
|
+
let tmpPortClass = `pict-flow-port ${tmpPort.Direction}`;
|
|
483
|
+
if (tmpPort.PortType)
|
|
484
|
+
{
|
|
485
|
+
tmpPortClass += ` port-type-${tmpPort.PortType}`;
|
|
486
|
+
}
|
|
487
|
+
tmpCircle.setAttribute('class', tmpPortClass);
|
|
333
488
|
tmpCircle.setAttribute('cx', String(tmpPosition.x));
|
|
334
489
|
tmpCircle.setAttribute('cy', String(tmpPosition.y));
|
|
335
490
|
tmpCircle.setAttribute('r', '5');
|
|
336
491
|
tmpCircle.setAttribute('data-port-hash', tmpPort.Hash);
|
|
337
492
|
tmpCircle.setAttribute('data-node-hash', pNodeData.Hash);
|
|
338
493
|
tmpCircle.setAttribute('data-port-direction', tmpPort.Direction);
|
|
494
|
+
if (tmpPort.PortType)
|
|
495
|
+
{
|
|
496
|
+
tmpCircle.setAttribute('data-port-type', tmpPort.PortType);
|
|
497
|
+
}
|
|
339
498
|
tmpCircle.setAttribute('data-element-type', 'port');
|
|
340
499
|
}
|
|
341
500
|
pGroup.appendChild(tmpCircle);
|
|
342
501
|
|
|
343
|
-
// Port label
|
|
344
|
-
|
|
345
|
-
if (tmpPort.Label)
|
|
502
|
+
// Port label text (on top of everything)
|
|
503
|
+
if (tmpLabelElement)
|
|
346
504
|
{
|
|
347
|
-
|
|
348
|
-
tmpLabel.setAttribute('class', 'pict-flow-port-label');
|
|
349
|
-
tmpLabel.textContent = tmpPort.Label;
|
|
350
|
-
|
|
351
|
-
// Base offset from port center; PortLabelPadding adds extra space
|
|
352
|
-
let tmpLabelOffset = 12;
|
|
353
|
-
let tmpPaddingExtra = tmpPortLabelPadding ? 8 : 0;
|
|
354
|
-
|
|
355
|
-
// When PortLabelsOutside is true, labels render outside the node
|
|
356
|
-
// boundary (away from center) instead of inside (toward center).
|
|
357
|
-
// The direction multiplier flips the offset direction per edge.
|
|
358
|
-
let tmpOutsideFlip = tmpPortLabelsOutside ? -1 : 1;
|
|
359
|
-
|
|
360
|
-
if (tmpPortLabelsVertical)
|
|
361
|
-
{
|
|
362
|
-
switch (tmpEdge)
|
|
363
|
-
{
|
|
364
|
-
case 'left':
|
|
365
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
366
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
367
|
-
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
368
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip}, ${tmpPosition.y})`);
|
|
369
|
-
break;
|
|
370
|
-
case 'right':
|
|
371
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
372
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
373
|
-
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
374
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip}, ${tmpPosition.y})`);
|
|
375
|
-
break;
|
|
376
|
-
case 'top':
|
|
377
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
378
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
379
|
-
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
380
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip})`);
|
|
381
|
-
break;
|
|
382
|
-
case 'bottom':
|
|
383
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
384
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
385
|
-
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
386
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip})`);
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
else
|
|
391
|
-
{
|
|
392
|
-
// Horizontal labels (default)
|
|
393
|
-
switch (tmpEdge)
|
|
394
|
-
{
|
|
395
|
-
case 'left':
|
|
396
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
397
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
398
|
-
tmpLabel.setAttribute('text-anchor', tmpPortLabelsOutside ? 'end' : 'start');
|
|
399
|
-
break;
|
|
400
|
-
case 'right':
|
|
401
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
402
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
403
|
-
tmpLabel.setAttribute('text-anchor', tmpPortLabelsOutside ? 'start' : 'end');
|
|
404
|
-
break;
|
|
405
|
-
case 'top':
|
|
406
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
407
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
408
|
-
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
409
|
-
break;
|
|
410
|
-
case 'bottom':
|
|
411
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
412
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
413
|
-
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
tmpLabel.setAttribute('dominant-baseline', 'central');
|
|
418
|
-
pGroup.appendChild(tmpLabel);
|
|
505
|
+
pGroup.appendChild(tmpLabelElement);
|
|
419
506
|
}
|
|
420
507
|
}
|
|
421
508
|
}
|
|
@@ -1328,7 +1328,20 @@ class PictViewFlow extends libPictView
|
|
|
1328
1328
|
|
|
1329
1329
|
let tmpTitleBarHeight = (this._NodeView && this._NodeView.options.NodeTitleBarHeight) || 28;
|
|
1330
1330
|
|
|
1331
|
-
|
|
1331
|
+
// Use the adjusted node height that accounts for minimum port
|
|
1332
|
+
// spacing. Connections render before nodes, so the node renderer
|
|
1333
|
+
// may not have written back its adjusted height yet.
|
|
1334
|
+
let tmpHeight = tmpNode.Height || 80;
|
|
1335
|
+
if (this._GeometryProvider && tmpNode.Ports && tmpNode.Ports.length > 0)
|
|
1336
|
+
{
|
|
1337
|
+
let tmpMinHeight = this._GeometryProvider.computeMinimumNodeHeight(tmpNode.Ports, tmpTitleBarHeight);
|
|
1338
|
+
if (tmpMinHeight > tmpHeight)
|
|
1339
|
+
{
|
|
1340
|
+
tmpHeight = tmpMinHeight;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
let tmpLocal = this._GeometryProvider.getPortLocalPosition(tmpPort.Side, tmpPortIndex, tmpPortCount, tmpNode.Width, tmpHeight, tmpTitleBarHeight);
|
|
1332
1345
|
|
|
1333
1346
|
return { x: tmpNode.X + tmpLocal.x, y: tmpNode.Y + tmpLocal.y, side: tmpPort.Side || 'right' };
|
|
1334
1347
|
}
|
|
@@ -1381,6 +1394,34 @@ class PictViewFlow extends libPictView
|
|
|
1381
1394
|
let tmpIsSelected = (this._FlowData.ViewState.SelectedNodeHash === tmpNode.Hash);
|
|
1382
1395
|
let tmpNodeTypeConfig = this._NodeTypeProvider.getNodeType(tmpNode.Type);
|
|
1383
1396
|
|
|
1397
|
+
// Enrich saved port data with metadata from the node type's DefaultPorts.
|
|
1398
|
+
// Saved flow data may not include PortType or may have stale Side values,
|
|
1399
|
+
// so we match each port to its DefaultPort counterpart by Label and Direction,
|
|
1400
|
+
// then copy over PortType and Side from the authoritative node type definition.
|
|
1401
|
+
if (tmpNodeTypeConfig && tmpNodeTypeConfig.DefaultPorts && tmpNode.Ports)
|
|
1402
|
+
{
|
|
1403
|
+
for (let p = 0; p < tmpNode.Ports.length; p++)
|
|
1404
|
+
{
|
|
1405
|
+
let tmpPort = tmpNode.Ports[p];
|
|
1406
|
+
for (let d = 0; d < tmpNodeTypeConfig.DefaultPorts.length; d++)
|
|
1407
|
+
{
|
|
1408
|
+
let tmpDefault = tmpNodeTypeConfig.DefaultPorts[d];
|
|
1409
|
+
if (tmpDefault.Label === tmpPort.Label && tmpDefault.Direction === tmpPort.Direction)
|
|
1410
|
+
{
|
|
1411
|
+
if (tmpDefault.PortType)
|
|
1412
|
+
{
|
|
1413
|
+
tmpPort.PortType = tmpDefault.PortType;
|
|
1414
|
+
}
|
|
1415
|
+
if (tmpDefault.Side)
|
|
1416
|
+
{
|
|
1417
|
+
tmpPort.Side = tmpDefault.Side;
|
|
1418
|
+
}
|
|
1419
|
+
break;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1384
1425
|
this._NodeView.renderNode(tmpNode, this._NodesLayer, tmpIsSelected, tmpNodeTypeConfig);
|
|
1385
1426
|
}
|
|
1386
1427
|
|