pict-section-flow 0.0.2 → 0.0.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/.claude/launch.json +11 -0
- package/docs/README.md +51 -0
- package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +105 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +36 -0
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +42 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +98 -0
- package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +44 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +1 -1
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +9 -1
- package/package.json +2 -2
- package/source/Pict-Section-Flow.js +8 -1
- package/source/PictFlowCard.js +49 -1
- package/source/providers/PictProvider-Flow-CSS.js +1440 -0
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +413 -0
- package/source/providers/PictProvider-Flow-Geometry.js +43 -0
- package/source/providers/PictProvider-Flow-Icons.js +335 -0
- package/source/providers/PictProvider-Flow-Layouts.js +214 -2
- package/source/providers/PictProvider-Flow-NodeTypes.js +30 -7
- package/source/providers/PictProvider-Flow-Noise.js +241 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +19 -0
- package/source/providers/PictProvider-Flow-Theme.js +755 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +95 -32
- package/source/services/PictService-Flow-PanelManager.js +188 -0
- package/source/services/PictService-Flow-SelectionManager.js +109 -0
- package/source/services/PictService-Flow-Tether.js +52 -25
- package/source/services/PictService-Flow-ViewportManager.js +176 -0
- package/source/views/PictView-Flow-FloatingToolbar.js +352 -0
- package/source/views/PictView-Flow-Node.js +654 -169
- package/source/views/PictView-Flow-PropertiesPanel.js +176 -1
- package/source/views/PictView-Flow-Toolbar.js +846 -379
- package/source/views/PictView-Flow.js +279 -671
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
|
+
|
|
3
|
+
class PictProviderFlowConnectorShapes extends libFableServiceProviderBase
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
|
|
9
|
+
this.serviceType = 'PictProviderFlowConnectorShapes';
|
|
10
|
+
|
|
11
|
+
this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
|
|
12
|
+
|
|
13
|
+
// Default shape configurations — each keyed by a shape identifier
|
|
14
|
+
// _OriginalShapes stores a deep copy for reset when switching themes.
|
|
15
|
+
this._DefaultShapes =
|
|
16
|
+
{
|
|
17
|
+
'port':
|
|
18
|
+
{
|
|
19
|
+
ElementType: 'circle',
|
|
20
|
+
Attributes: { r: '5' },
|
|
21
|
+
ClassName: 'pict-flow-port'
|
|
22
|
+
},
|
|
23
|
+
'panel-indicator':
|
|
24
|
+
{
|
|
25
|
+
ElementType: 'rect',
|
|
26
|
+
Attributes: { rx: '2', ry: '2' },
|
|
27
|
+
ClassName: 'pict-flow-node-panel-indicator'
|
|
28
|
+
},
|
|
29
|
+
'connection-path':
|
|
30
|
+
{
|
|
31
|
+
ElementType: 'path',
|
|
32
|
+
Attributes: {},
|
|
33
|
+
ClassName: 'pict-flow-connection'
|
|
34
|
+
},
|
|
35
|
+
'connection-hitarea':
|
|
36
|
+
{
|
|
37
|
+
ElementType: 'path',
|
|
38
|
+
Attributes: {},
|
|
39
|
+
ClassName: 'pict-flow-connection-hitarea'
|
|
40
|
+
},
|
|
41
|
+
'connection-handle':
|
|
42
|
+
{
|
|
43
|
+
ElementType: 'circle',
|
|
44
|
+
Attributes: { r: '6' },
|
|
45
|
+
ClassName: 'pict-flow-connection-handle'
|
|
46
|
+
},
|
|
47
|
+
'connection-handle-midpoint':
|
|
48
|
+
{
|
|
49
|
+
ElementType: 'circle',
|
|
50
|
+
Attributes: { r: '6' },
|
|
51
|
+
ClassName: 'pict-flow-connection-handle-midpoint'
|
|
52
|
+
},
|
|
53
|
+
'drag-connection':
|
|
54
|
+
{
|
|
55
|
+
ElementType: 'path',
|
|
56
|
+
Attributes: {},
|
|
57
|
+
ClassName: 'pict-flow-drag-connection'
|
|
58
|
+
},
|
|
59
|
+
'tether-path':
|
|
60
|
+
{
|
|
61
|
+
ElementType: 'path',
|
|
62
|
+
Attributes: {},
|
|
63
|
+
ClassName: 'pict-flow-tether-line'
|
|
64
|
+
},
|
|
65
|
+
'tether-hitarea':
|
|
66
|
+
{
|
|
67
|
+
ElementType: 'path',
|
|
68
|
+
Attributes: {},
|
|
69
|
+
ClassName: 'pict-flow-tether-hitarea'
|
|
70
|
+
},
|
|
71
|
+
'tether-handle':
|
|
72
|
+
{
|
|
73
|
+
ElementType: 'circle',
|
|
74
|
+
Attributes: { r: '6' },
|
|
75
|
+
ClassName: 'pict-flow-tether-handle'
|
|
76
|
+
},
|
|
77
|
+
'tether-handle-midpoint':
|
|
78
|
+
{
|
|
79
|
+
ElementType: 'circle',
|
|
80
|
+
Attributes: { r: '6' },
|
|
81
|
+
ClassName: 'pict-flow-tether-handle-midpoint'
|
|
82
|
+
},
|
|
83
|
+
'arrowhead-connection':
|
|
84
|
+
{
|
|
85
|
+
MarkerWidth: '5',
|
|
86
|
+
MarkerHeight: '7',
|
|
87
|
+
RefX: '7.5',
|
|
88
|
+
RefY: '3.5',
|
|
89
|
+
Points: '0 0, 5 3.5, 0 7',
|
|
90
|
+
Fill: '#95a5a6'
|
|
91
|
+
},
|
|
92
|
+
'arrowhead-connection-selected':
|
|
93
|
+
{
|
|
94
|
+
MarkerWidth: '5',
|
|
95
|
+
MarkerHeight: '7',
|
|
96
|
+
RefX: '7.5',
|
|
97
|
+
RefY: '3.5',
|
|
98
|
+
Points: '0 0, 5 3.5, 0 7',
|
|
99
|
+
Fill: '#3498db'
|
|
100
|
+
},
|
|
101
|
+
'arrowhead-tether':
|
|
102
|
+
{
|
|
103
|
+
MarkerWidth: '4',
|
|
104
|
+
MarkerHeight: '6',
|
|
105
|
+
RefX: '6',
|
|
106
|
+
RefY: '3',
|
|
107
|
+
Points: '0 0, 4 3, 0 6',
|
|
108
|
+
Fill: '#95a5a6'
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Store a deep copy for resetting when switching themes
|
|
113
|
+
this._OriginalShapes = JSON.parse(JSON.stringify(this._DefaultShapes));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Theme Override Methods ─────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Apply theme-specific shape overrides.
|
|
120
|
+
* Merges the overrides into the active shape configs.
|
|
121
|
+
* @param {Object} pOverrides - Map of shape key to partial config
|
|
122
|
+
*/
|
|
123
|
+
applyThemeOverrides(pOverrides)
|
|
124
|
+
{
|
|
125
|
+
if (!pOverrides || typeof pOverrides !== 'object')
|
|
126
|
+
{
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
for (let tmpKey in pOverrides)
|
|
130
|
+
{
|
|
131
|
+
if (this._DefaultShapes.hasOwnProperty(tmpKey))
|
|
132
|
+
{
|
|
133
|
+
Object.assign(this._DefaultShapes[tmpKey], pOverrides[tmpKey]);
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
{
|
|
137
|
+
this._DefaultShapes[tmpKey] = pOverrides[tmpKey];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reset all shape configs to their original defaults.
|
|
144
|
+
* Called before applying new theme overrides to prevent accumulation.
|
|
145
|
+
*/
|
|
146
|
+
resetToDefaults()
|
|
147
|
+
{
|
|
148
|
+
this._DefaultShapes = JSON.parse(JSON.stringify(this._OriginalShapes));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get a shape configuration by key.
|
|
153
|
+
* @param {string} pShapeKey
|
|
154
|
+
* @returns {Object|null}
|
|
155
|
+
*/
|
|
156
|
+
getShapeConfig(pShapeKey)
|
|
157
|
+
{
|
|
158
|
+
if (this._DefaultShapes.hasOwnProperty(pShapeKey))
|
|
159
|
+
{
|
|
160
|
+
return this._DefaultShapes[pShapeKey];
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Override or add a shape configuration.
|
|
167
|
+
* Consumers can call this to customize connector shapes without subclassing.
|
|
168
|
+
* @param {string} pShapeKey
|
|
169
|
+
* @param {Object} pConfig
|
|
170
|
+
*/
|
|
171
|
+
setShapeConfig(pShapeKey, pConfig)
|
|
172
|
+
{
|
|
173
|
+
this._DefaultShapes[pShapeKey] = pConfig;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get all registered shape keys.
|
|
178
|
+
* @returns {string[]}
|
|
179
|
+
*/
|
|
180
|
+
getShapeKeys()
|
|
181
|
+
{
|
|
182
|
+
return Object.keys(this._DefaultShapes);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---- Factory Methods ----
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create a port SVG element.
|
|
189
|
+
* @param {Object} pPortData - Port data with Hash, Direction
|
|
190
|
+
* @param {{x: number, y: number}} pPosition - Local position within the node group
|
|
191
|
+
* @param {string} pNodeHash - The owning node hash
|
|
192
|
+
* @returns {SVGCircleElement}
|
|
193
|
+
*/
|
|
194
|
+
createPortElement(pPortData, pPosition, pNodeHash)
|
|
195
|
+
{
|
|
196
|
+
let tmpConfig = this._DefaultShapes['port'];
|
|
197
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
198
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName + ' ' + pPortData.Direction);
|
|
199
|
+
tmpElement.setAttribute('cx', String(pPosition.x));
|
|
200
|
+
tmpElement.setAttribute('cy', String(pPosition.y));
|
|
201
|
+
// Apply config attributes (r, etc.)
|
|
202
|
+
for (let tmpKey in tmpConfig.Attributes)
|
|
203
|
+
{
|
|
204
|
+
tmpElement.setAttribute(tmpKey, tmpConfig.Attributes[tmpKey]);
|
|
205
|
+
}
|
|
206
|
+
tmpElement.setAttribute('data-port-hash', pPortData.Hash);
|
|
207
|
+
tmpElement.setAttribute('data-node-hash', pNodeHash);
|
|
208
|
+
tmpElement.setAttribute('data-port-direction', pPortData.Direction);
|
|
209
|
+
tmpElement.setAttribute('data-element-type', 'port');
|
|
210
|
+
return tmpElement;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create a panel indicator SVG element.
|
|
215
|
+
* @param {string} pNodeHash
|
|
216
|
+
* @param {number} pX
|
|
217
|
+
* @param {number} pY
|
|
218
|
+
* @param {number} pWidth
|
|
219
|
+
* @param {number} pHeight
|
|
220
|
+
* @returns {SVGRectElement}
|
|
221
|
+
*/
|
|
222
|
+
createPanelIndicatorElement(pNodeHash, pX, pY, pWidth, pHeight)
|
|
223
|
+
{
|
|
224
|
+
let tmpConfig = this._DefaultShapes['panel-indicator'];
|
|
225
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
226
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName);
|
|
227
|
+
tmpElement.setAttribute('x', String(pX));
|
|
228
|
+
tmpElement.setAttribute('y', String(pY));
|
|
229
|
+
tmpElement.setAttribute('width', String(pWidth));
|
|
230
|
+
tmpElement.setAttribute('height', String(pHeight));
|
|
231
|
+
// Apply config attributes (rx, ry, etc.)
|
|
232
|
+
for (let tmpKey in tmpConfig.Attributes)
|
|
233
|
+
{
|
|
234
|
+
tmpElement.setAttribute(tmpKey, tmpConfig.Attributes[tmpKey]);
|
|
235
|
+
}
|
|
236
|
+
tmpElement.setAttribute('data-node-hash', pNodeHash);
|
|
237
|
+
tmpElement.setAttribute('data-element-type', 'panel-indicator');
|
|
238
|
+
return tmpElement;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Create a visible connection path SVG element.
|
|
243
|
+
* @param {string} pPath - The SVG path d-string
|
|
244
|
+
* @param {string} pConnectionHash
|
|
245
|
+
* @param {boolean} pIsSelected
|
|
246
|
+
* @param {string} pViewIdentifier
|
|
247
|
+
* @returns {SVGPathElement}
|
|
248
|
+
*/
|
|
249
|
+
createConnectionPathElement(pPath, pConnectionHash, pIsSelected, pViewIdentifier)
|
|
250
|
+
{
|
|
251
|
+
let tmpConfig = this._DefaultShapes['connection-path'];
|
|
252
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
253
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName + (pIsSelected ? ' selected' : ''));
|
|
254
|
+
tmpElement.setAttribute('d', pPath);
|
|
255
|
+
tmpElement.setAttribute('data-connection-hash', pConnectionHash);
|
|
256
|
+
tmpElement.setAttribute('data-element-type', 'connection');
|
|
257
|
+
|
|
258
|
+
// Arrow marker
|
|
259
|
+
let tmpMarkerConfig = pIsSelected
|
|
260
|
+
? this._DefaultShapes['arrowhead-connection-selected']
|
|
261
|
+
: this._DefaultShapes['arrowhead-connection'];
|
|
262
|
+
// The marker id follows the naming convention used in generateMarkerDefs
|
|
263
|
+
let tmpMarkerId = pIsSelected
|
|
264
|
+
? ('flow-arrowhead-selected-' + pViewIdentifier)
|
|
265
|
+
: ('flow-arrowhead-' + pViewIdentifier);
|
|
266
|
+
tmpElement.setAttribute('marker-end', 'url(#' + tmpMarkerId + ')');
|
|
267
|
+
|
|
268
|
+
return tmpElement;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Create a connection hit area SVG element (wider invisible path for click targeting).
|
|
273
|
+
* @param {string} pPath - The SVG path d-string
|
|
274
|
+
* @param {string} pConnectionHash
|
|
275
|
+
* @returns {SVGPathElement}
|
|
276
|
+
*/
|
|
277
|
+
createConnectionHitAreaElement(pPath, pConnectionHash)
|
|
278
|
+
{
|
|
279
|
+
let tmpConfig = this._DefaultShapes['connection-hitarea'];
|
|
280
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
281
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName);
|
|
282
|
+
tmpElement.setAttribute('d', pPath);
|
|
283
|
+
tmpElement.setAttribute('data-connection-hash', pConnectionHash);
|
|
284
|
+
tmpElement.setAttribute('data-element-type', 'connection-hitarea');
|
|
285
|
+
return tmpElement;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Create a drag handle circle element.
|
|
290
|
+
* Works for both connection handles and tether handles.
|
|
291
|
+
* @param {string} pOwnerHash - Connection hash or panel hash
|
|
292
|
+
* @param {string} pHandleType - e.g. 'ortho-corner1', 'bezier-midpoint'
|
|
293
|
+
* @param {number} pX
|
|
294
|
+
* @param {number} pY
|
|
295
|
+
* @param {string} pShapeKey - 'connection-handle', 'connection-handle-midpoint', 'tether-handle', 'tether-handle-midpoint'
|
|
296
|
+
* @returns {SVGCircleElement}
|
|
297
|
+
*/
|
|
298
|
+
createHandleElement(pOwnerHash, pHandleType, pX, pY, pShapeKey)
|
|
299
|
+
{
|
|
300
|
+
let tmpConfig = this._DefaultShapes[pShapeKey] || this._DefaultShapes['connection-handle'];
|
|
301
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
302
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName);
|
|
303
|
+
tmpElement.setAttribute('cx', String(pX));
|
|
304
|
+
tmpElement.setAttribute('cy', String(pY));
|
|
305
|
+
// Apply config attributes (r, etc.)
|
|
306
|
+
for (let tmpKey in tmpConfig.Attributes)
|
|
307
|
+
{
|
|
308
|
+
tmpElement.setAttribute(tmpKey, tmpConfig.Attributes[tmpKey]);
|
|
309
|
+
}
|
|
310
|
+
tmpElement.setAttribute('data-handle-type', pHandleType);
|
|
311
|
+
return tmpElement;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Create a temporary drag connection path element.
|
|
316
|
+
* @param {string} pPath - The SVG path d-string
|
|
317
|
+
* @returns {SVGPathElement}
|
|
318
|
+
*/
|
|
319
|
+
createDragConnectionElement(pPath)
|
|
320
|
+
{
|
|
321
|
+
let tmpConfig = this._DefaultShapes['drag-connection'];
|
|
322
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
323
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName);
|
|
324
|
+
tmpElement.setAttribute('d', pPath);
|
|
325
|
+
return tmpElement;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Create a visible tether path SVG element.
|
|
330
|
+
* @param {string} pPath - The SVG path d-string
|
|
331
|
+
* @param {string} pPanelHash
|
|
332
|
+
* @param {boolean} pIsSelected
|
|
333
|
+
* @param {string} pViewIdentifier
|
|
334
|
+
* @returns {SVGPathElement}
|
|
335
|
+
*/
|
|
336
|
+
createTetherPathElement(pPath, pPanelHash, pIsSelected, pViewIdentifier)
|
|
337
|
+
{
|
|
338
|
+
let tmpConfig = this._DefaultShapes['tether-path'];
|
|
339
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
340
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName + (pIsSelected ? ' selected' : ''));
|
|
341
|
+
tmpElement.setAttribute('d', pPath);
|
|
342
|
+
tmpElement.setAttribute('marker-end', 'url(#flow-tether-arrowhead-' + pViewIdentifier + ')');
|
|
343
|
+
tmpElement.setAttribute('data-element-type', 'tether');
|
|
344
|
+
tmpElement.setAttribute('data-panel-hash', pPanelHash);
|
|
345
|
+
return tmpElement;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Create a tether hit area SVG element.
|
|
350
|
+
* @param {string} pPath - The SVG path d-string
|
|
351
|
+
* @param {string} pPanelHash
|
|
352
|
+
* @returns {SVGPathElement}
|
|
353
|
+
*/
|
|
354
|
+
createTetherHitAreaElement(pPath, pPanelHash)
|
|
355
|
+
{
|
|
356
|
+
let tmpConfig = this._DefaultShapes['tether-hitarea'];
|
|
357
|
+
let tmpElement = this._FlowView._SVGHelperProvider.createSVGElement(tmpConfig.ElementType);
|
|
358
|
+
tmpElement.setAttribute('class', tmpConfig.ClassName);
|
|
359
|
+
tmpElement.setAttribute('d', pPath);
|
|
360
|
+
tmpElement.setAttribute('data-element-type', 'tether-hitarea');
|
|
361
|
+
tmpElement.setAttribute('data-panel-hash', pPanelHash);
|
|
362
|
+
return tmpElement;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Generate SVG marker definition markup for all arrowhead types.
|
|
367
|
+
* Returns raw SVG markup to be injected into the <defs> section.
|
|
368
|
+
* @param {string} pViewIdentifier
|
|
369
|
+
* @returns {string}
|
|
370
|
+
*/
|
|
371
|
+
generateMarkerDefs(pViewIdentifier)
|
|
372
|
+
{
|
|
373
|
+
let tmpConnectionMarker = this._DefaultShapes['arrowhead-connection'];
|
|
374
|
+
let tmpSelectedMarker = this._DefaultShapes['arrowhead-connection-selected'];
|
|
375
|
+
let tmpTetherMarker = this._DefaultShapes['arrowhead-tether'];
|
|
376
|
+
|
|
377
|
+
let tmpMarkup = '';
|
|
378
|
+
|
|
379
|
+
// Normal connection arrowhead
|
|
380
|
+
tmpMarkup += '<marker id="flow-arrowhead-' + pViewIdentifier + '"'
|
|
381
|
+
+ ' markerWidth="' + tmpConnectionMarker.MarkerWidth + '"'
|
|
382
|
+
+ ' markerHeight="' + tmpConnectionMarker.MarkerHeight + '"'
|
|
383
|
+
+ ' refX="' + tmpConnectionMarker.RefX + '"'
|
|
384
|
+
+ ' refY="' + tmpConnectionMarker.RefY + '"'
|
|
385
|
+
+ ' orient="auto" markerUnits="strokeWidth">'
|
|
386
|
+
+ '<polygon points="' + tmpConnectionMarker.Points + '" fill="' + tmpConnectionMarker.Fill + '" />'
|
|
387
|
+
+ '</marker>';
|
|
388
|
+
|
|
389
|
+
// Selected connection arrowhead
|
|
390
|
+
tmpMarkup += '<marker id="flow-arrowhead-selected-' + pViewIdentifier + '"'
|
|
391
|
+
+ ' markerWidth="' + tmpSelectedMarker.MarkerWidth + '"'
|
|
392
|
+
+ ' markerHeight="' + tmpSelectedMarker.MarkerHeight + '"'
|
|
393
|
+
+ ' refX="' + tmpSelectedMarker.RefX + '"'
|
|
394
|
+
+ ' refY="' + tmpSelectedMarker.RefY + '"'
|
|
395
|
+
+ ' orient="auto" markerUnits="strokeWidth">'
|
|
396
|
+
+ '<polygon points="' + tmpSelectedMarker.Points + '" fill="' + tmpSelectedMarker.Fill + '" />'
|
|
397
|
+
+ '</marker>';
|
|
398
|
+
|
|
399
|
+
// Tether arrowhead
|
|
400
|
+
tmpMarkup += '<marker id="flow-tether-arrowhead-' + pViewIdentifier + '"'
|
|
401
|
+
+ ' markerWidth="' + tmpTetherMarker.MarkerWidth + '"'
|
|
402
|
+
+ ' markerHeight="' + tmpTetherMarker.MarkerHeight + '"'
|
|
403
|
+
+ ' refX="' + tmpTetherMarker.RefX + '"'
|
|
404
|
+
+ ' refY="' + tmpTetherMarker.RefY + '"'
|
|
405
|
+
+ ' orient="auto" markerUnits="strokeWidth">'
|
|
406
|
+
+ '<polygon points="' + tmpTetherMarker.Points + '" fill="' + tmpTetherMarker.Fill + '" />'
|
|
407
|
+
+ '</marker>';
|
|
408
|
+
|
|
409
|
+
return tmpMarkup;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = PictProviderFlowConnectorShapes;
|
|
@@ -59,6 +59,49 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
|
|
|
59
59
|
return { x: pRectData.X + pRectData.Width, y: pRectData.Y + pRectData.Height / 2 };
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Calculate a port's local position relative to node origin.
|
|
65
|
+
*
|
|
66
|
+
* For left and right side ports, positioning is offset below the title bar
|
|
67
|
+
* so that ports never overlap the header area.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} pSide - 'left', 'right', 'top', 'bottom'
|
|
70
|
+
* @param {number} pIndex - Index of this port on its side
|
|
71
|
+
* @param {number} pTotal - Total ports on this side
|
|
72
|
+
* @param {number} pWidth - Node width
|
|
73
|
+
* @param {number} pHeight - Node height
|
|
74
|
+
* @param {number} pTitleBarHeight - Height of the node title bar
|
|
75
|
+
* @returns {{x: number, y: number}}
|
|
76
|
+
*/
|
|
77
|
+
getPortLocalPosition(pSide, pIndex, pTotal, pWidth, pHeight, pTitleBarHeight)
|
|
78
|
+
{
|
|
79
|
+
let tmpSpacing;
|
|
80
|
+
|
|
81
|
+
switch (pSide)
|
|
82
|
+
{
|
|
83
|
+
case 'left':
|
|
84
|
+
{
|
|
85
|
+
let tmpBodyHeight = pHeight - pTitleBarHeight;
|
|
86
|
+
tmpSpacing = tmpBodyHeight / (pTotal + 1);
|
|
87
|
+
return { x: 0, y: pTitleBarHeight + tmpSpacing * (pIndex + 1) };
|
|
88
|
+
}
|
|
89
|
+
case 'right':
|
|
90
|
+
{
|
|
91
|
+
let tmpBodyHeight = pHeight - pTitleBarHeight;
|
|
92
|
+
tmpSpacing = tmpBodyHeight / (pTotal + 1);
|
|
93
|
+
return { x: pWidth, y: pTitleBarHeight + tmpSpacing * (pIndex + 1) };
|
|
94
|
+
}
|
|
95
|
+
case 'top':
|
|
96
|
+
tmpSpacing = pWidth / (pTotal + 1);
|
|
97
|
+
return { x: tmpSpacing * (pIndex + 1), y: 0 };
|
|
98
|
+
case 'bottom':
|
|
99
|
+
tmpSpacing = pWidth / (pTotal + 1);
|
|
100
|
+
return { x: tmpSpacing * (pIndex + 1), y: pHeight };
|
|
101
|
+
default:
|
|
102
|
+
return { x: pWidth, y: pHeight / 2 };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
62
105
|
}
|
|
63
106
|
|
|
64
107
|
module.exports = PictProviderFlowGeometry;
|