pict-section-flow 0.0.3 → 0.0.6
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 +7 -5
- package/source/PictFlowCard.js +14 -2
- package/source/panels/FlowCardPropertiesPanel-Form.js +5 -0
- package/source/panels/FlowCardPropertiesPanel-Template.js +1 -1
- package/source/providers/PictProvider-Flow-CSS.js +54 -0
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +10 -1
- package/source/providers/PictProvider-Flow-Geometry.js +119 -28
- package/source/services/PictService-Flow-ConnectionRenderer.js +24 -1
- package/source/views/PictView-Flow-Node.js +44 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pict-section-flow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Pict Section Flow Diagram",
|
|
5
5
|
"main": "source/Pict-Section-Flow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
"author": "steven velozo <steven@velozo.com>",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"
|
|
14
|
+
"fable-serviceproviderbase": "^3.0.19",
|
|
15
15
|
"pict-provider": "^1.0.12",
|
|
16
|
-
"
|
|
16
|
+
"pict-section-form": "^1.0.194",
|
|
17
|
+
"pict-view": "^1.0.67"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
|
-
"pict": "^1.0.
|
|
20
|
-
"
|
|
20
|
+
"pict": "^1.0.357",
|
|
21
|
+
"pict-router": "^1.0.6",
|
|
22
|
+
"quackage": "^1.0.64"
|
|
21
23
|
}
|
|
22
24
|
}
|
package/source/PictFlowCard.js
CHANGED
|
@@ -105,6 +105,7 @@ class PictFlowCard extends libFableServiceProviderBase
|
|
|
105
105
|
this.cardPortLabelsOnHover = (typeof tmpOptions.PortLabelsOnHover === 'boolean') ? tmpOptions.PortLabelsOnHover : false;
|
|
106
106
|
this.cardPortLabelsVertical = (typeof tmpOptions.PortLabelsVertical === 'boolean') ? tmpOptions.PortLabelsVertical : false;
|
|
107
107
|
this.cardPortLabelPadding = (typeof tmpOptions.PortLabelPadding === 'boolean') ? tmpOptions.PortLabelPadding : false;
|
|
108
|
+
this.cardPortLabelsOutside = (typeof tmpOptions.PortLabelsOutside === 'boolean') ? tmpOptions.PortLabelsOutside : false;
|
|
108
109
|
this.cardLabelsInFront = (typeof tmpOptions.LabelsInFront === 'boolean') ? tmpOptions.LabelsInFront : true;
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -132,6 +133,10 @@ class PictFlowCard extends libFableServiceProviderBase
|
|
|
132
133
|
MinimumInputCount: (typeof tmpInput.MinimumInputCount === 'number') ? tmpInput.MinimumInputCount : 0,
|
|
133
134
|
MaximumInputCount: (typeof tmpInput.MaximumInputCount === 'number') ? tmpInput.MaximumInputCount : -1
|
|
134
135
|
};
|
|
136
|
+
if (tmpInput.PortType)
|
|
137
|
+
{
|
|
138
|
+
tmpPort.PortType = tmpInput.PortType;
|
|
139
|
+
}
|
|
135
140
|
tmpPorts.push(tmpPort);
|
|
136
141
|
}
|
|
137
142
|
|
|
@@ -139,13 +144,18 @@ class PictFlowCard extends libFableServiceProviderBase
|
|
|
139
144
|
for (let i = 0; i < this.cardOutputs.length; i++)
|
|
140
145
|
{
|
|
141
146
|
let tmpOutput = this.cardOutputs[i];
|
|
142
|
-
|
|
147
|
+
let tmpOutPort =
|
|
143
148
|
{
|
|
144
149
|
Hash: null,
|
|
145
150
|
Direction: 'output',
|
|
146
151
|
Side: tmpOutput.Side || 'right',
|
|
147
152
|
Label: tmpOutput.Name || `Out ${i + 1}`
|
|
148
|
-
}
|
|
153
|
+
};
|
|
154
|
+
if (tmpOutput.PortType)
|
|
155
|
+
{
|
|
156
|
+
tmpOutPort.PortType = tmpOutput.PortType;
|
|
157
|
+
}
|
|
158
|
+
tmpPorts.push(tmpOutPort);
|
|
149
159
|
}
|
|
150
160
|
|
|
151
161
|
// If no ports were defined, provide sensible defaults
|
|
@@ -184,6 +194,7 @@ class PictFlowCard extends libFableServiceProviderBase
|
|
|
184
194
|
tmpResult.PortLabelsOnHover = this.cardPortLabelsOnHover;
|
|
185
195
|
tmpResult.PortLabelsVertical = this.cardPortLabelsVertical;
|
|
186
196
|
tmpResult.PortLabelPadding = this.cardPortLabelPadding;
|
|
197
|
+
tmpResult.PortLabelsOutside = this.cardPortLabelsOutside;
|
|
187
198
|
tmpResult.LabelsInFront = this.cardLabelsInFront;
|
|
188
199
|
|
|
189
200
|
// Include properties panel config if defined
|
|
@@ -251,5 +262,6 @@ module.exports.default_configuration =
|
|
|
251
262
|
PortLabelsOnHover: false,
|
|
252
263
|
PortLabelsVertical: false,
|
|
253
264
|
PortLabelPadding: false,
|
|
265
|
+
PortLabelsOutside: false,
|
|
254
266
|
LabelsInFront: true
|
|
255
267
|
};
|
|
@@ -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
|
|
@@ -67,7 +67,7 @@ class FlowCardPropertiesPanelTemplate extends libPictFlowCardPropertiesPanel
|
|
|
67
67
|
if (!tmpTemplateHash) return;
|
|
68
68
|
|
|
69
69
|
let tmpRecord = this._NodeData;
|
|
70
|
-
let tmpHTML = this.fable.
|
|
70
|
+
let tmpHTML = this.fable.parseTemplateByHash(tmpTemplateHash, tmpRecord, null, [tmpRecord]);
|
|
71
71
|
this._ContentContainer.innerHTML = tmpHTML;
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -61,6 +61,18 @@ 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: #9b59b6;
|
|
69
|
+
--pf-port-error-fill: #e74c3c;
|
|
70
|
+
|
|
71
|
+
/* Connection Type Colors */
|
|
72
|
+
--pf-connection-event-stroke: #95a5a6;
|
|
73
|
+
--pf-connection-data-stroke: #9b59b6;
|
|
74
|
+
--pf-connection-error-stroke: #e74c3c;
|
|
75
|
+
|
|
64
76
|
/* Panels */
|
|
65
77
|
--pf-panel-bg: #ffffff;
|
|
66
78
|
--pf-panel-border: #d0d4d8;
|
|
@@ -286,6 +298,22 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
286
298
|
r: 7;
|
|
287
299
|
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.20));
|
|
288
300
|
}
|
|
301
|
+
/* Port type color overrides */
|
|
302
|
+
.pict-flow-port.port-type-event-in {
|
|
303
|
+
fill: var(--pf-port-event-in-fill);
|
|
304
|
+
}
|
|
305
|
+
.pict-flow-port.port-type-event-out {
|
|
306
|
+
fill: var(--pf-port-event-out-fill);
|
|
307
|
+
}
|
|
308
|
+
.pict-flow-port.port-type-setting {
|
|
309
|
+
fill: var(--pf-port-setting-fill);
|
|
310
|
+
}
|
|
311
|
+
.pict-flow-port.port-type-value {
|
|
312
|
+
fill: var(--pf-port-value-fill);
|
|
313
|
+
}
|
|
314
|
+
.pict-flow-port.port-type-error {
|
|
315
|
+
fill: var(--pf-port-error-fill);
|
|
316
|
+
}
|
|
289
317
|
.pict-flow-port-label {
|
|
290
318
|
fill: #7f8c8d;
|
|
291
319
|
font-size: 9px;
|
|
@@ -326,6 +354,16 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
326
354
|
stroke: var(--pf-connection-selected-stroke);
|
|
327
355
|
stroke-width: 3;
|
|
328
356
|
}
|
|
357
|
+
/* Connection type color overrides (based on source port type) */
|
|
358
|
+
.pict-flow-connection.conn-type-value {
|
|
359
|
+
stroke: var(--pf-connection-data-stroke);
|
|
360
|
+
}
|
|
361
|
+
.pict-flow-connection.conn-type-setting {
|
|
362
|
+
stroke: var(--pf-connection-data-stroke);
|
|
363
|
+
}
|
|
364
|
+
.pict-flow-connection.conn-type-error {
|
|
365
|
+
stroke: var(--pf-connection-error-stroke);
|
|
366
|
+
}
|
|
329
367
|
.pict-flow-connection-hitarea {
|
|
330
368
|
fill: none;
|
|
331
369
|
stroke: transparent;
|
|
@@ -591,6 +629,22 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
591
629
|
.pict-flow-info-panel-port.output {
|
|
592
630
|
border-left: 3px solid var(--pf-port-output-fill);
|
|
593
631
|
}
|
|
632
|
+
/* Info panel port type color overrides */
|
|
633
|
+
.pict-flow-info-panel-port.port-type-event-in {
|
|
634
|
+
border-left-color: var(--pf-port-event-in-fill);
|
|
635
|
+
}
|
|
636
|
+
.pict-flow-info-panel-port.port-type-event-out {
|
|
637
|
+
border-left-color: var(--pf-port-event-out-fill);
|
|
638
|
+
}
|
|
639
|
+
.pict-flow-info-panel-port.port-type-setting {
|
|
640
|
+
border-left-color: var(--pf-port-setting-fill);
|
|
641
|
+
}
|
|
642
|
+
.pict-flow-info-panel-port.port-type-value {
|
|
643
|
+
border-left-color: var(--pf-port-value-fill);
|
|
644
|
+
}
|
|
645
|
+
.pict-flow-info-panel-port.port-type-error {
|
|
646
|
+
border-left-color: var(--pf-port-error-fill);
|
|
647
|
+
}
|
|
594
648
|
.pict-flow-info-panel-port-constraint {
|
|
595
649
|
color: #8e99a4;
|
|
596
650
|
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
|
}
|
|
@@ -6,6 +6,16 @@ const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
|
6
6
|
* Shared geometry utilities for the flow diagram.
|
|
7
7
|
* Provides direction vectors and edge center calculations used by
|
|
8
8
|
* connections, tethers, and other flow components.
|
|
9
|
+
*
|
|
10
|
+
* Port Side values (12 positions):
|
|
11
|
+
*
|
|
12
|
+
* Top edge: 'top-left' 'top' 'top-right'
|
|
13
|
+
* Left edge: 'left-top' 'left' 'left-bottom'
|
|
14
|
+
* Right edge: 'right-top' 'right' 'right-bottom'
|
|
15
|
+
* Bottom edge: 'bottom-left' 'bottom' 'bottom-right'
|
|
16
|
+
*
|
|
17
|
+
* The old 4-value sides ('left', 'right', 'top', 'bottom') map to
|
|
18
|
+
* the middle position on each edge for backward compatibility.
|
|
9
19
|
*/
|
|
10
20
|
class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
11
21
|
{
|
|
@@ -16,15 +26,55 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
|
16
26
|
this.serviceType = 'PictProviderFlowGeometry';
|
|
17
27
|
}
|
|
18
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Extract the edge name from a Side value.
|
|
31
|
+
*
|
|
32
|
+
* Maps all 12 positions (and the 4 legacy values) back to
|
|
33
|
+
* the edge they sit on: 'left', 'right', 'top', or 'bottom'.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} pSide - Any valid Side value
|
|
36
|
+
* @returns {string} The edge: 'left', 'right', 'top', or 'bottom'
|
|
37
|
+
*/
|
|
38
|
+
getEdgeFromSide(pSide)
|
|
39
|
+
{
|
|
40
|
+
switch (pSide)
|
|
41
|
+
{
|
|
42
|
+
case 'left-top':
|
|
43
|
+
case 'left':
|
|
44
|
+
case 'left-bottom':
|
|
45
|
+
return 'left';
|
|
46
|
+
|
|
47
|
+
case 'right-top':
|
|
48
|
+
case 'right':
|
|
49
|
+
case 'right-bottom':
|
|
50
|
+
return 'right';
|
|
51
|
+
|
|
52
|
+
case 'top-left':
|
|
53
|
+
case 'top':
|
|
54
|
+
case 'top-right':
|
|
55
|
+
return 'top';
|
|
56
|
+
|
|
57
|
+
case 'bottom-left':
|
|
58
|
+
case 'bottom':
|
|
59
|
+
case 'bottom-right':
|
|
60
|
+
return 'bottom';
|
|
61
|
+
|
|
62
|
+
default:
|
|
63
|
+
return 'right';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
19
67
|
/**
|
|
20
68
|
* Get the outward unit direction vector for a given side.
|
|
21
69
|
*
|
|
22
|
-
*
|
|
70
|
+
* All positions on the same edge share the same direction vector.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} pSide - Any valid Side value (12 positions or 4 legacy)
|
|
23
73
|
* @returns {{dx: number, dy: number}}
|
|
24
74
|
*/
|
|
25
75
|
sideDirection(pSide)
|
|
26
76
|
{
|
|
27
|
-
switch (pSide)
|
|
77
|
+
switch (this.getEdgeFromSide(pSide))
|
|
28
78
|
{
|
|
29
79
|
case 'left': return { dx: -1, dy: 0 };
|
|
30
80
|
case 'right': return { dx: 1, dy: 0 };
|
|
@@ -63,12 +113,17 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
|
63
113
|
/**
|
|
64
114
|
* Calculate a port's local position relative to node origin.
|
|
65
115
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
116
|
+
* Supports 12 positions (3 zones per edge). For left/right edges,
|
|
117
|
+
* the body area below the title bar is divided into three vertical zones
|
|
118
|
+
* (start/middle/end). For top/bottom edges, the full width is divided
|
|
119
|
+
* into three horizontal zones.
|
|
68
120
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
121
|
+
* Multiple ports sharing the same Side value distribute evenly within
|
|
122
|
+
* their zone.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} pSide - Side value (any of 12 positions or 4 legacy)
|
|
125
|
+
* @param {number} pIndex - Index of this port within its Side group
|
|
126
|
+
* @param {number} pTotal - Total ports with this Side value
|
|
72
127
|
* @param {number} pWidth - Node width
|
|
73
128
|
* @param {number} pHeight - Node height
|
|
74
129
|
* @param {number} pTitleBarHeight - Height of the node title bar
|
|
@@ -76,30 +131,66 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
|
76
131
|
*/
|
|
77
132
|
getPortLocalPosition(pSide, pIndex, pTotal, pWidth, pHeight, pTitleBarHeight)
|
|
78
133
|
{
|
|
79
|
-
let
|
|
134
|
+
let tmpEdge = this.getEdgeFromSide(pSide);
|
|
135
|
+
let tmpZone = this._getZoneFromSide(pSide);
|
|
136
|
+
|
|
137
|
+
if (tmpEdge === 'left' || tmpEdge === 'right')
|
|
138
|
+
{
|
|
139
|
+
let tmpX = (tmpEdge === 'left') ? 0 : pWidth;
|
|
140
|
+
let tmpBodyHeight = pHeight - pTitleBarHeight;
|
|
141
|
+
let tmpZoneStart = pTitleBarHeight + tmpBodyHeight * tmpZone.start;
|
|
142
|
+
let tmpZoneHeight = tmpBodyHeight * (tmpZone.end - tmpZone.start);
|
|
143
|
+
let tmpSpacing = tmpZoneHeight / (pTotal + 1);
|
|
144
|
+
let tmpY = tmpZoneStart + tmpSpacing * (pIndex + 1);
|
|
145
|
+
return { x: tmpX, y: tmpY };
|
|
146
|
+
}
|
|
80
147
|
|
|
148
|
+
// top or bottom
|
|
149
|
+
let tmpY = (tmpEdge === 'top') ? 0 : pHeight;
|
|
150
|
+
let tmpZoneStart = pWidth * tmpZone.start;
|
|
151
|
+
let tmpZoneWidth = pWidth * (tmpZone.end - tmpZone.start);
|
|
152
|
+
let tmpSpacing = tmpZoneWidth / (pTotal + 1);
|
|
153
|
+
let tmpX = tmpZoneStart + tmpSpacing * (pIndex + 1);
|
|
154
|
+
return { x: tmpX, y: tmpY };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get the zone fraction (start, end) for a Side value.
|
|
159
|
+
*
|
|
160
|
+
* Each edge is divided into three zones of equal size:
|
|
161
|
+
* start: 0.0 — 0.333
|
|
162
|
+
* middle: 0.333 — 0.667
|
|
163
|
+
* end: 0.667 — 1.0
|
|
164
|
+
*
|
|
165
|
+
* @param {string} pSide
|
|
166
|
+
* @returns {{start: number, end: number}}
|
|
167
|
+
*/
|
|
168
|
+
_getZoneFromSide(pSide)
|
|
169
|
+
{
|
|
81
170
|
switch (pSide)
|
|
82
171
|
{
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
case 'right':
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
case 'top':
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
172
|
+
// Left edge: top, middle, bottom
|
|
173
|
+
case 'left-top': return { start: 0.0, end: 0.333 };
|
|
174
|
+
case 'left': return { start: 0.333, end: 0.667 };
|
|
175
|
+
case 'left-bottom': return { start: 0.667, end: 1.0 };
|
|
176
|
+
|
|
177
|
+
// Right edge: top, middle, bottom
|
|
178
|
+
case 'right-top': return { start: 0.0, end: 0.333 };
|
|
179
|
+
case 'right': return { start: 0.333, end: 0.667 };
|
|
180
|
+
case 'right-bottom': return { start: 0.667, end: 1.0 };
|
|
181
|
+
|
|
182
|
+
// Top edge: left, middle, right
|
|
183
|
+
case 'top-left': return { start: 0.0, end: 0.333 };
|
|
184
|
+
case 'top': return { start: 0.333, end: 0.667 };
|
|
185
|
+
case 'top-right': return { start: 0.667, end: 1.0 };
|
|
186
|
+
|
|
187
|
+
// Bottom edge: left, middle, right
|
|
188
|
+
case 'bottom-left': return { start: 0.0, end: 0.333 };
|
|
189
|
+
case 'bottom': return { start: 0.333, end: 0.667 };
|
|
190
|
+
case 'bottom-right': return { start: 0.667, end: 1.0 };
|
|
191
|
+
|
|
192
|
+
// Fallback: full range (legacy behavior)
|
|
193
|
+
default: return { start: 0.0, end: 1.0 };
|
|
103
194
|
}
|
|
104
195
|
}
|
|
105
196
|
}
|
|
@@ -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,9 @@ 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
|
+
|
|
72
90
|
// Hit area (wider invisible path for easier selection)
|
|
73
91
|
let tmpShapeProvider = this._FlowView._ConnectorShapesProvider;
|
|
74
92
|
if (tmpShapeProvider)
|
|
@@ -78,6 +96,11 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
|
|
|
78
96
|
|
|
79
97
|
let tmpPathElement = tmpShapeProvider.createConnectionPathElement(
|
|
80
98
|
tmpPath, pConnection.Hash, pIsSelected, tmpViewIdentifier);
|
|
99
|
+
if (tmpConnTypeClass)
|
|
100
|
+
{
|
|
101
|
+
tmpPathElement.setAttribute('class',
|
|
102
|
+
(tmpPathElement.getAttribute('class') || '') + tmpConnTypeClass);
|
|
103
|
+
}
|
|
81
104
|
if (tmpStrokeDashArray)
|
|
82
105
|
{
|
|
83
106
|
tmpPathElement.setAttribute('stroke-dasharray', tmpStrokeDashArray);
|
|
@@ -94,7 +117,7 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
|
|
|
94
117
|
pConnectionsLayer.appendChild(tmpHitArea);
|
|
95
118
|
|
|
96
119
|
let tmpPathElement = this._FlowView._SVGHelperProvider.createSVGElement('path');
|
|
97
|
-
tmpPathElement.setAttribute('class', `pict-flow-connection ${pIsSelected ? 'selected' : ''}`);
|
|
120
|
+
tmpPathElement.setAttribute('class', `pict-flow-connection${tmpConnTypeClass} ${pIsSelected ? 'selected' : ''}`);
|
|
98
121
|
tmpPathElement.setAttribute('d', tmpPath);
|
|
99
122
|
tmpPathElement.setAttribute('data-connection-hash', pConnection.Hash);
|
|
100
123
|
tmpPathElement.setAttribute('data-element-type', 'connection');
|
|
@@ -292,22 +292,28 @@ class PictViewFlowNode extends libPictView
|
|
|
292
292
|
|
|
293
293
|
let tmpPortLabelsVertical = (pNodeTypeConfig && pNodeTypeConfig.PortLabelsVertical);
|
|
294
294
|
let tmpPortLabelPadding = (pNodeTypeConfig && pNodeTypeConfig.PortLabelPadding);
|
|
295
|
+
let tmpPortLabelsOutside = (pNodeTypeConfig && pNodeTypeConfig.PortLabelsOutside);
|
|
296
|
+
let tmpGeometryProvider = this._FlowView._GeometryProvider;
|
|
295
297
|
|
|
296
|
-
// Group ports by
|
|
297
|
-
let tmpPortsBySide = {
|
|
298
|
+
// Group ports by their Side value (supports all 12 positions)
|
|
299
|
+
let tmpPortsBySide = {};
|
|
298
300
|
for (let i = 0; i < pNodeData.Ports.length; i++)
|
|
299
301
|
{
|
|
300
302
|
let tmpPort = pNodeData.Ports[i];
|
|
301
303
|
let tmpSide = tmpPort.Side || (tmpPort.Direction === 'input' ? 'left' : 'right');
|
|
302
|
-
if (tmpPortsBySide[tmpSide])
|
|
304
|
+
if (!tmpPortsBySide[tmpSide])
|
|
303
305
|
{
|
|
304
|
-
tmpPortsBySide[tmpSide]
|
|
306
|
+
tmpPortsBySide[tmpSide] = [];
|
|
305
307
|
}
|
|
308
|
+
tmpPortsBySide[tmpSide].push(tmpPort);
|
|
306
309
|
}
|
|
307
310
|
|
|
308
311
|
for (let tmpSide in tmpPortsBySide)
|
|
309
312
|
{
|
|
310
313
|
let tmpPorts = tmpPortsBySide[tmpSide];
|
|
314
|
+
// Determine the edge for label positioning
|
|
315
|
+
let tmpEdge = tmpGeometryProvider ? tmpGeometryProvider.getEdgeFromSide(tmpSide) : tmpSide;
|
|
316
|
+
|
|
311
317
|
for (let i = 0; i < tmpPorts.length; i++)
|
|
312
318
|
{
|
|
313
319
|
let tmpPort = tmpPorts[i];
|
|
@@ -323,18 +329,28 @@ class PictViewFlowNode extends libPictView
|
|
|
323
329
|
else
|
|
324
330
|
{
|
|
325
331
|
tmpCircle = this._FlowView._SVGHelperProvider.createSVGElement('circle');
|
|
326
|
-
|
|
332
|
+
let tmpPortClass = `pict-flow-port ${tmpPort.Direction}`;
|
|
333
|
+
if (tmpPort.PortType)
|
|
334
|
+
{
|
|
335
|
+
tmpPortClass += ` port-type-${tmpPort.PortType}`;
|
|
336
|
+
}
|
|
337
|
+
tmpCircle.setAttribute('class', tmpPortClass);
|
|
327
338
|
tmpCircle.setAttribute('cx', String(tmpPosition.x));
|
|
328
339
|
tmpCircle.setAttribute('cy', String(tmpPosition.y));
|
|
329
340
|
tmpCircle.setAttribute('r', '5');
|
|
330
341
|
tmpCircle.setAttribute('data-port-hash', tmpPort.Hash);
|
|
331
342
|
tmpCircle.setAttribute('data-node-hash', pNodeData.Hash);
|
|
332
343
|
tmpCircle.setAttribute('data-port-direction', tmpPort.Direction);
|
|
344
|
+
if (tmpPort.PortType)
|
|
345
|
+
{
|
|
346
|
+
tmpCircle.setAttribute('data-port-type', tmpPort.PortType);
|
|
347
|
+
}
|
|
333
348
|
tmpCircle.setAttribute('data-element-type', 'port');
|
|
334
349
|
}
|
|
335
350
|
pGroup.appendChild(tmpCircle);
|
|
336
351
|
|
|
337
|
-
// Port label
|
|
352
|
+
// Port label — use the edge for alignment (all positions on the
|
|
353
|
+
// same edge share the same label direction)
|
|
338
354
|
if (tmpPort.Label)
|
|
339
355
|
{
|
|
340
356
|
let tmpLabel = this._FlowView._SVGHelperProvider.createSVGElement('text');
|
|
@@ -345,62 +361,64 @@ class PictViewFlowNode extends libPictView
|
|
|
345
361
|
let tmpLabelOffset = 12;
|
|
346
362
|
let tmpPaddingExtra = tmpPortLabelPadding ? 8 : 0;
|
|
347
363
|
|
|
348
|
-
|
|
364
|
+
// When PortLabelsOutside is true, labels render outside the node
|
|
365
|
+
// boundary (away from center) instead of inside (toward center).
|
|
366
|
+
// The direction multiplier flips the offset direction per edge.
|
|
367
|
+
let tmpOutsideFlip = tmpPortLabelsOutside ? -1 : 1;
|
|
368
|
+
|
|
369
|
+
if (tmpPortLabelsVertical)
|
|
349
370
|
{
|
|
350
|
-
|
|
351
|
-
// After rotation, text-anchor controls vertical centering, so 'middle'
|
|
352
|
-
// ensures the label is centered next to its port circle.
|
|
353
|
-
switch (tmpSide)
|
|
371
|
+
switch (tmpEdge)
|
|
354
372
|
{
|
|
355
373
|
case 'left':
|
|
356
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x + tmpLabelOffset + tmpPaddingExtra));
|
|
374
|
+
tmpLabel.setAttribute('x', String(tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
357
375
|
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
358
376
|
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
359
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x + tmpLabelOffset + tmpPaddingExtra}, ${tmpPosition.y})`);
|
|
377
|
+
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip}, ${tmpPosition.y})`);
|
|
360
378
|
break;
|
|
361
379
|
case 'right':
|
|
362
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x - tmpLabelOffset
|
|
380
|
+
tmpLabel.setAttribute('x', String(tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
363
381
|
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
364
382
|
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
365
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x - tmpLabelOffset
|
|
383
|
+
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip}, ${tmpPosition.y})`);
|
|
366
384
|
break;
|
|
367
385
|
case 'top':
|
|
368
386
|
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
369
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y + tmpLabelOffset + tmpPaddingExtra));
|
|
387
|
+
tmpLabel.setAttribute('y', String(tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
370
388
|
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
371
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y + tmpLabelOffset + tmpPaddingExtra})`);
|
|
389
|
+
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip})`);
|
|
372
390
|
break;
|
|
373
391
|
case 'bottom':
|
|
374
392
|
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
375
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y - tmpLabelOffset
|
|
393
|
+
tmpLabel.setAttribute('y', String(tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
376
394
|
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
377
|
-
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y - tmpLabelOffset
|
|
395
|
+
tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip})`);
|
|
378
396
|
break;
|
|
379
397
|
}
|
|
380
398
|
}
|
|
381
399
|
else
|
|
382
400
|
{
|
|
383
401
|
// Horizontal labels (default)
|
|
384
|
-
switch (
|
|
402
|
+
switch (tmpEdge)
|
|
385
403
|
{
|
|
386
404
|
case 'left':
|
|
387
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x + tmpLabelOffset + tmpPaddingExtra));
|
|
405
|
+
tmpLabel.setAttribute('x', String(tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
388
406
|
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
389
|
-
tmpLabel.setAttribute('text-anchor', 'start');
|
|
407
|
+
tmpLabel.setAttribute('text-anchor', tmpPortLabelsOutside ? 'end' : 'start');
|
|
390
408
|
break;
|
|
391
409
|
case 'right':
|
|
392
|
-
tmpLabel.setAttribute('x', String(tmpPosition.x - tmpLabelOffset
|
|
410
|
+
tmpLabel.setAttribute('x', String(tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
393
411
|
tmpLabel.setAttribute('y', String(tmpPosition.y));
|
|
394
|
-
tmpLabel.setAttribute('text-anchor', 'end');
|
|
412
|
+
tmpLabel.setAttribute('text-anchor', tmpPortLabelsOutside ? 'start' : 'end');
|
|
395
413
|
break;
|
|
396
414
|
case 'top':
|
|
397
415
|
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
398
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y + tmpLabelOffset + tmpPaddingExtra));
|
|
416
|
+
tmpLabel.setAttribute('y', String(tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
399
417
|
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
400
418
|
break;
|
|
401
419
|
case 'bottom':
|
|
402
420
|
tmpLabel.setAttribute('x', String(tmpPosition.x));
|
|
403
|
-
tmpLabel.setAttribute('y', String(tmpPosition.y - tmpLabelOffset
|
|
421
|
+
tmpLabel.setAttribute('y', String(tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
|
|
404
422
|
tmpLabel.setAttribute('text-anchor', 'middle');
|
|
405
423
|
break;
|
|
406
424
|
}
|