pict-section-flow 0.0.13 → 0.0.16
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/providers/PictProvider-Flow-CSS.js +0 -10
- package/source/providers/PictProvider-Flow-NodeTypes.js +9 -2
- package/source/services/PictService-Flow-ConnectionHandleManager.js +3 -0
- package/source/services/PictService-Flow-InteractionManager.js +67 -1
- package/source/services/PictService-Flow-Tether.js +164 -10
- package/source/views/PictView-Flow-FloatingToolbar.js +25 -4
- package/source/views/PictView-Flow-Toolbar.js +110 -20
- package/source/views/PictView-Flow.js +55 -2
package/package.json
CHANGED
|
@@ -934,13 +934,6 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
934
934
|
.pict-flow-toolbar-btn:active {
|
|
935
935
|
background-color: var(--pf-button-active-bg);
|
|
936
936
|
}
|
|
937
|
-
.pict-flow-toolbar-btn.danger {
|
|
938
|
-
color: var(--pf-button-danger-text);
|
|
939
|
-
border-color: var(--pf-button-danger-text);
|
|
940
|
-
}
|
|
941
|
-
.pict-flow-toolbar-btn.danger:hover {
|
|
942
|
-
background-color: var(--pf-button-danger-hover-bg);
|
|
943
|
-
}
|
|
944
937
|
.pict-flow-toolbar-btn-icon {
|
|
945
938
|
display: inline-flex;
|
|
946
939
|
align-items: center;
|
|
@@ -1397,9 +1390,6 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
|
|
|
1397
1390
|
.pict-flow-floating-btn:hover {
|
|
1398
1391
|
background-color: var(--pf-button-hover-bg);
|
|
1399
1392
|
}
|
|
1400
|
-
.pict-flow-floating-btn.danger:hover {
|
|
1401
|
-
background-color: var(--pf-button-danger-hover-bg);
|
|
1402
|
-
}
|
|
1403
1393
|
.pict-flow-floating-separator {
|
|
1404
1394
|
height: 1px;
|
|
1405
1395
|
background-color: var(--pf-divider-light);
|
|
@@ -104,8 +104,15 @@ class PictProviderFlowNodeTypes extends libPictProvider
|
|
|
104
104
|
|
|
105
105
|
this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
|
|
106
106
|
|
|
107
|
-
// Initialize with default node types
|
|
108
|
-
|
|
107
|
+
// Initialize with default node types unless explicitly disabled
|
|
108
|
+
if (pOptions && pOptions.IncludeDefaultNodeTypes === false)
|
|
109
|
+
{
|
|
110
|
+
this._NodeTypes = {};
|
|
111
|
+
}
|
|
112
|
+
else
|
|
113
|
+
{
|
|
114
|
+
this._NodeTypes = JSON.parse(JSON.stringify(_DefaultNodeTypes));
|
|
115
|
+
}
|
|
109
116
|
|
|
110
117
|
// Merge any additional node types passed in via options
|
|
111
118
|
if (pOptions && pOptions.AdditionalNodeTypes && typeof pOptions.AdditionalNodeTypes === 'object')
|
|
@@ -223,6 +223,9 @@ class PictServiceFlowConnectionHandleManager extends libFableServiceProviderBase
|
|
|
223
223
|
if (tmpConn.Data && tmpConn.Data.HandleCustomized)
|
|
224
224
|
{
|
|
225
225
|
tmpConn.Data.HandleCustomized = false;
|
|
226
|
+
// Clear multi-handle array (current format)
|
|
227
|
+
tmpConn.Data.BezierHandles = [];
|
|
228
|
+
// Clear legacy single-handle fields
|
|
226
229
|
tmpConn.Data.BezierHandleX = null;
|
|
227
230
|
tmpConn.Data.BezierHandleY = null;
|
|
228
231
|
tmpConn.Data.OrthoCorner1X = null;
|
|
@@ -75,6 +75,10 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
|
|
|
75
75
|
this._LastConnectionClickTime = 0;
|
|
76
76
|
this._LastConnectionClickHash = null;
|
|
77
77
|
|
|
78
|
+
// Double-click detection for tethers
|
|
79
|
+
this._LastTetherClickTime = 0;
|
|
80
|
+
this._LastTetherClickHash = null;
|
|
81
|
+
|
|
78
82
|
// Double-click detection for handles
|
|
79
83
|
this._LastHandleClickTime = 0;
|
|
80
84
|
this._LastHandleClickHash = null;
|
|
@@ -128,6 +132,15 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
|
|
|
128
132
|
case 'connection-handle':
|
|
129
133
|
this._removeBezierHandle(tmpTarget);
|
|
130
134
|
break;
|
|
135
|
+
|
|
136
|
+
case 'tether':
|
|
137
|
+
case 'tether-hitarea':
|
|
138
|
+
this._addTetherBezierHandle(tmpTarget, pEvent);
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
case 'tether-handle':
|
|
142
|
+
this._removeTetherBezierHandle(tmpTarget);
|
|
143
|
+
break;
|
|
131
144
|
}
|
|
132
145
|
});
|
|
133
146
|
}
|
|
@@ -252,8 +265,26 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
|
|
|
252
265
|
|
|
253
266
|
case 'tether':
|
|
254
267
|
case 'tether-hitarea':
|
|
255
|
-
|
|
268
|
+
{
|
|
269
|
+
let tmpPanelHash = this._getPanelHash(tmpTarget);
|
|
270
|
+
let tmpNow = Date.now();
|
|
271
|
+
|
|
272
|
+
// Check for double-click on same tether to add a handle
|
|
273
|
+
if (tmpPanelHash && tmpPanelHash === this._LastTetherClickHash
|
|
274
|
+
&& (tmpNow - this._LastTetherClickTime) < this._DoubleClickThreshold)
|
|
275
|
+
{
|
|
276
|
+
this._LastTetherClickTime = 0;
|
|
277
|
+
this._LastTetherClickHash = null;
|
|
278
|
+
this._addTetherBezierHandle(tmpTarget, pEvent);
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
{
|
|
282
|
+
this._LastTetherClickTime = tmpNow;
|
|
283
|
+
this._LastTetherClickHash = tmpPanelHash;
|
|
284
|
+
this._selectTether(tmpTarget);
|
|
285
|
+
}
|
|
256
286
|
break;
|
|
287
|
+
}
|
|
257
288
|
|
|
258
289
|
case 'tether-handle':
|
|
259
290
|
{
|
|
@@ -758,6 +789,41 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
|
|
|
758
789
|
this._FlowView.removeConnectionHandle(tmpConnectionHash, tmpIndex);
|
|
759
790
|
}
|
|
760
791
|
|
|
792
|
+
/**
|
|
793
|
+
* Add a bezier handle to a tether.
|
|
794
|
+
* @param {Element} pTarget - The tether SVG element that was right-clicked or double-clicked
|
|
795
|
+
* @param {Event} pEvent - The mouse event (for coordinate extraction)
|
|
796
|
+
*/
|
|
797
|
+
_addTetherBezierHandle(pTarget, pEvent)
|
|
798
|
+
{
|
|
799
|
+
let tmpPanelHash = this._getPanelHash(pTarget);
|
|
800
|
+
if (!tmpPanelHash) return;
|
|
801
|
+
|
|
802
|
+
// Select the tether so handles render after re-render
|
|
803
|
+
this._FlowView.selectTether(tmpPanelHash);
|
|
804
|
+
|
|
805
|
+
let tmpCoords = this._FlowView.screenToSVGCoords(pEvent.clientX, pEvent.clientY);
|
|
806
|
+
this._FlowView.addTetherHandle(tmpPanelHash, tmpCoords.x, tmpCoords.y);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Remove a bezier handle from a tether.
|
|
811
|
+
* @param {Element} pTarget - The tether handle SVG element that was right-clicked
|
|
812
|
+
*/
|
|
813
|
+
_removeTetherBezierHandle(pTarget)
|
|
814
|
+
{
|
|
815
|
+
let tmpPanelHash = this._getPanelHash(pTarget);
|
|
816
|
+
if (!tmpPanelHash) return;
|
|
817
|
+
|
|
818
|
+
let tmpHandleType = pTarget.getAttribute('data-handle-type');
|
|
819
|
+
if (!tmpHandleType || !tmpHandleType.startsWith('bezier-handle-')) return;
|
|
820
|
+
|
|
821
|
+
let tmpIndex = parseInt(tmpHandleType.replace('bezier-handle-', ''), 10);
|
|
822
|
+
if (isNaN(tmpIndex)) return;
|
|
823
|
+
|
|
824
|
+
this._FlowView.removeTetherHandle(tmpPanelHash, tmpIndex);
|
|
825
|
+
}
|
|
826
|
+
|
|
761
827
|
// ---- Line Mode Toggling ----
|
|
762
828
|
|
|
763
829
|
_toggleConnectionLineMode(pConnectionHash)
|
|
@@ -298,12 +298,74 @@ class PictServiceFlowTether extends libFableServiceProviderBase
|
|
|
298
298
|
}
|
|
299
299
|
else
|
|
300
300
|
{
|
|
301
|
+
// Check for multi-handle array first
|
|
302
|
+
let tmpHandles = this._getTetherBezierHandles(pPanelData);
|
|
303
|
+
if (tmpHandles.length > 0)
|
|
304
|
+
{
|
|
305
|
+
return this.generateMultiBezierPath(pFrom, pTo, tmpHandles);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Single-handle legacy path
|
|
301
309
|
let tmpHandleX = (pPanelData.TetherHandleCustomized && pPanelData.TetherBezierHandleX != null) ? pPanelData.TetherBezierHandleX : null;
|
|
302
310
|
let tmpHandleY = (pPanelData.TetherHandleCustomized && pPanelData.TetherBezierHandleY != null) ? pPanelData.TetherBezierHandleY : null;
|
|
303
311
|
return this.generateBezierPath(pFrom, pTo, tmpHandleX, tmpHandleY);
|
|
304
312
|
}
|
|
305
313
|
}
|
|
306
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Get the bezier handles array for a tether, respecting the customized flag.
|
|
317
|
+
* @param {Object} pPanelData
|
|
318
|
+
* @returns {Array<{x: number, y: number}>}
|
|
319
|
+
*/
|
|
320
|
+
_getTetherBezierHandles(pPanelData)
|
|
321
|
+
{
|
|
322
|
+
if (!pPanelData || !pPanelData.TetherHandleCustomized)
|
|
323
|
+
{
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Multi-handle format
|
|
328
|
+
if (Array.isArray(pPanelData.TetherBezierHandles) && pPanelData.TetherBezierHandles.length > 0)
|
|
329
|
+
{
|
|
330
|
+
return pPanelData.TetherBezierHandles;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Legacy single-handle format
|
|
334
|
+
if (pPanelData.TetherBezierHandleX != null && pPanelData.TetherBezierHandleY != null)
|
|
335
|
+
{
|
|
336
|
+
return [{ x: pPanelData.TetherBezierHandleX, y: pPanelData.TetherBezierHandleY }];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Generate a multi-handle bezier path for a tether.
|
|
344
|
+
* Delegates to PathGenerator.buildMultiBezierPathString.
|
|
345
|
+
* @param {Object} pFrom - {x, y, side}
|
|
346
|
+
* @param {Object} pTo - {x, y, side}
|
|
347
|
+
* @param {Array<{x: number, y: number}>} pHandles
|
|
348
|
+
* @returns {string} SVG path d attribute
|
|
349
|
+
*/
|
|
350
|
+
generateMultiBezierPath(pFrom, pTo, pHandles)
|
|
351
|
+
{
|
|
352
|
+
let tmpDepartDist = 20;
|
|
353
|
+
let tmpFromDir = this._FlowView._GeometryProvider.sideDirection(pFrom.side);
|
|
354
|
+
let tmpToDir = this._FlowView._GeometryProvider.sideDirection(pTo.side);
|
|
355
|
+
|
|
356
|
+
let tmpDepart = {
|
|
357
|
+
x: pFrom.x + tmpFromDir.dx * tmpDepartDist,
|
|
358
|
+
y: pFrom.y + tmpFromDir.dy * tmpDepartDist
|
|
359
|
+
};
|
|
360
|
+
let tmpApproach = {
|
|
361
|
+
x: pTo.x + tmpToDir.dx * tmpDepartDist,
|
|
362
|
+
y: pTo.y + tmpToDir.dy * tmpDepartDist
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
return this._FlowView._PathGenerator.buildMultiBezierPathString(
|
|
366
|
+
pFrom, tmpDepart, pFrom.side, tmpApproach, pTo.side, pTo, pHandles);
|
|
367
|
+
}
|
|
368
|
+
|
|
307
369
|
// ---- Handle State Management ----
|
|
308
370
|
|
|
309
371
|
/**
|
|
@@ -317,9 +379,34 @@ class PictServiceFlowTether extends libFableServiceProviderBase
|
|
|
317
379
|
{
|
|
318
380
|
pPanelData.TetherHandleCustomized = true;
|
|
319
381
|
|
|
382
|
+
// Multi-handle bezier: handle type is 'bezier-handle-N'
|
|
383
|
+
if (pHandleType && pHandleType.startsWith('bezier-handle-'))
|
|
384
|
+
{
|
|
385
|
+
let tmpIndex = parseInt(pHandleType.replace('bezier-handle-', ''), 10);
|
|
386
|
+
if (!isNaN(tmpIndex) && Array.isArray(pPanelData.TetherBezierHandles)
|
|
387
|
+
&& tmpIndex < pPanelData.TetherBezierHandles.length)
|
|
388
|
+
{
|
|
389
|
+
pPanelData.TetherBezierHandles[tmpIndex].x = pX;
|
|
390
|
+
pPanelData.TetherBezierHandles[tmpIndex].y = pY;
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
320
395
|
switch (pHandleType)
|
|
321
396
|
{
|
|
322
397
|
case 'bezier-midpoint':
|
|
398
|
+
// Migrate to multi-handle array
|
|
399
|
+
if (!Array.isArray(pPanelData.TetherBezierHandles)
|
|
400
|
+
|| pPanelData.TetherBezierHandles.length === 0)
|
|
401
|
+
{
|
|
402
|
+
pPanelData.TetherBezierHandles = [{ x: pX, y: pY }];
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
{
|
|
406
|
+
pPanelData.TetherBezierHandles[0].x = pX;
|
|
407
|
+
pPanelData.TetherBezierHandles[0].y = pY;
|
|
408
|
+
}
|
|
409
|
+
// Keep legacy fields in sync
|
|
323
410
|
pPanelData.TetherBezierHandleX = pX;
|
|
324
411
|
pPanelData.TetherBezierHandleY = pY;
|
|
325
412
|
break;
|
|
@@ -353,6 +440,7 @@ class PictServiceFlowTether extends libFableServiceProviderBase
|
|
|
353
440
|
if (pPanelData.TetherHandleCustomized)
|
|
354
441
|
{
|
|
355
442
|
pPanelData.TetherHandleCustomized = false;
|
|
443
|
+
pPanelData.TetherBezierHandles = [];
|
|
356
444
|
pPanelData.TetherBezierHandleX = null;
|
|
357
445
|
pPanelData.TetherBezierHandleY = null;
|
|
358
446
|
pPanelData.TetherOrthoCorner1X = null;
|
|
@@ -381,6 +469,68 @@ class PictServiceFlowTether extends libFableServiceProviderBase
|
|
|
381
469
|
}
|
|
382
470
|
}
|
|
383
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Add a bezier handle to a tether at the specified position.
|
|
474
|
+
* @param {Object} pPanelData - Panel data
|
|
475
|
+
* @param {number} pX
|
|
476
|
+
* @param {number} pY
|
|
477
|
+
* @param {Object} pFrom - Panel anchor {x, y, side}
|
|
478
|
+
* @param {Object} pTo - Node anchor {x, y, side}
|
|
479
|
+
*/
|
|
480
|
+
addHandle(pPanelData, pX, pY, pFrom, pTo)
|
|
481
|
+
{
|
|
482
|
+
// Ensure bezier mode and multi-handle array
|
|
483
|
+
pPanelData.TetherLineMode = 'bezier';
|
|
484
|
+
|
|
485
|
+
if (!Array.isArray(pPanelData.TetherBezierHandles))
|
|
486
|
+
{
|
|
487
|
+
pPanelData.TetherBezierHandles = [];
|
|
488
|
+
// Migrate legacy single-handle if present
|
|
489
|
+
if (pPanelData.TetherBezierHandleX != null && pPanelData.TetherBezierHandleY != null)
|
|
490
|
+
{
|
|
491
|
+
pPanelData.TetherBezierHandles.push({
|
|
492
|
+
x: pPanelData.TetherBezierHandleX,
|
|
493
|
+
y: pPanelData.TetherBezierHandleY
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Compute insertion index
|
|
499
|
+
let tmpInsertIndex = 0;
|
|
500
|
+
if (this._FlowView._ConnectionRenderer && pFrom && pTo)
|
|
501
|
+
{
|
|
502
|
+
tmpInsertIndex = this._FlowView._ConnectionRenderer.computeInsertionIndex(
|
|
503
|
+
pPanelData.TetherBezierHandles,
|
|
504
|
+
{ x: pX, y: pY },
|
|
505
|
+
pFrom,
|
|
506
|
+
pTo
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
pPanelData.TetherBezierHandles.splice(tmpInsertIndex, 0, { x: pX, y: pY });
|
|
511
|
+
pPanelData.TetherHandleCustomized = true;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Remove a bezier handle from a tether by index.
|
|
516
|
+
* @param {Object} pPanelData - Panel data
|
|
517
|
+
* @param {number} pIndex - Index in TetherBezierHandles array
|
|
518
|
+
*/
|
|
519
|
+
removeHandle(pPanelData, pIndex)
|
|
520
|
+
{
|
|
521
|
+
if (!Array.isArray(pPanelData.TetherBezierHandles)) return;
|
|
522
|
+
if (pIndex < 0 || pIndex >= pPanelData.TetherBezierHandles.length) return;
|
|
523
|
+
|
|
524
|
+
pPanelData.TetherBezierHandles.splice(pIndex, 1);
|
|
525
|
+
|
|
526
|
+
if (pPanelData.TetherBezierHandles.length === 0)
|
|
527
|
+
{
|
|
528
|
+
pPanelData.TetherHandleCustomized = false;
|
|
529
|
+
pPanelData.TetherBezierHandleX = null;
|
|
530
|
+
pPanelData.TetherBezierHandleY = null;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
384
534
|
/**
|
|
385
535
|
* Toggle tether line mode between bezier and orthogonal.
|
|
386
536
|
* Resets handle positions on toggle.
|
|
@@ -393,6 +543,7 @@ class PictServiceFlowTether extends libFableServiceProviderBase
|
|
|
393
543
|
pPanelData.TetherLineMode = (tmpCurrentMode === 'bezier') ? 'orthogonal' : 'bezier';
|
|
394
544
|
|
|
395
545
|
pPanelData.TetherHandleCustomized = false;
|
|
546
|
+
pPanelData.TetherBezierHandles = [];
|
|
396
547
|
pPanelData.TetherBezierHandleX = null;
|
|
397
548
|
pPanelData.TetherBezierHandleY = null;
|
|
398
549
|
pPanelData.TetherOrthoCorner1X = null;
|
|
@@ -491,22 +642,25 @@ class PictServiceFlowTether extends libFableServiceProviderBase
|
|
|
491
642
|
}
|
|
492
643
|
else
|
|
493
644
|
{
|
|
494
|
-
// Bezier
|
|
495
|
-
let
|
|
496
|
-
|
|
645
|
+
// Bezier handles
|
|
646
|
+
let tmpHandles = this._getTetherBezierHandles(pPanelData);
|
|
647
|
+
|
|
648
|
+
if (tmpHandles.length > 0)
|
|
497
649
|
{
|
|
498
|
-
|
|
499
|
-
|
|
650
|
+
// Multi-handle: render each handle as a draggable circle
|
|
651
|
+
for (let i = 0; i < tmpHandles.length; i++)
|
|
652
|
+
{
|
|
653
|
+
this._createHandle(pTethersLayer, pPanelData.Hash, 'bezier-handle-' + i,
|
|
654
|
+
tmpHandles[i].x, tmpHandles[i].y, 'pict-flow-tether-handle');
|
|
655
|
+
}
|
|
500
656
|
}
|
|
501
657
|
else
|
|
502
658
|
{
|
|
659
|
+
// No custom handles: show auto-computed midpoint
|
|
503
660
|
let tmpMid = this.getAutoMidpoint(pFrom, pTo);
|
|
504
|
-
|
|
505
|
-
|
|
661
|
+
this._createHandle(pTethersLayer, pPanelData.Hash, 'bezier-midpoint',
|
|
662
|
+
tmpMid.x, tmpMid.y, 'pict-flow-tether-handle-midpoint');
|
|
506
663
|
}
|
|
507
|
-
|
|
508
|
-
this._createHandle(pTethersLayer, pPanelData.Hash, 'bezier-midpoint',
|
|
509
|
-
tmpMidX, tmpMidY, 'pict-flow-tether-handle-midpoint');
|
|
510
664
|
}
|
|
511
665
|
}
|
|
512
666
|
|
|
@@ -25,7 +25,10 @@ const _DefaultConfiguration =
|
|
|
25
25
|
<button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node">
|
|
26
26
|
<span id="Flow-FloatingIcon-plus-{~D:Record.FlowViewIdentifier~}"></span>
|
|
27
27
|
</button>
|
|
28
|
-
<button class="pict-flow-floating-btn
|
|
28
|
+
<button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards">
|
|
29
|
+
<span id="Flow-FloatingIcon-cards-{~D:Record.FlowViewIdentifier~}"></span>
|
|
30
|
+
</button>
|
|
31
|
+
<button class="pict-flow-floating-btn" data-flow-action="delete-selected" title="Delete Selected">
|
|
29
32
|
<span id="Flow-FloatingIcon-trash-{~D:Record.FlowViewIdentifier~}"></span>
|
|
30
33
|
</button>
|
|
31
34
|
<div class="pict-flow-floating-separator"></div>
|
|
@@ -42,9 +45,6 @@ const _DefaultConfiguration =
|
|
|
42
45
|
<button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout">
|
|
43
46
|
<span id="Flow-FloatingIcon-auto-layout-{~D:Record.FlowViewIdentifier~}"></span>
|
|
44
47
|
</button>
|
|
45
|
-
<button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards">
|
|
46
|
-
<span id="Flow-FloatingIcon-cards-{~D:Record.FlowViewIdentifier~}"></span>
|
|
47
|
-
</button>
|
|
48
48
|
<button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout">
|
|
49
49
|
<span id="Flow-FloatingIcon-layout-{~D:Record.FlowViewIdentifier~}"></span>
|
|
50
50
|
</button>
|
|
@@ -155,6 +155,27 @@ class PictViewFlowFloatingToolbar extends libPictView
|
|
|
155
155
|
// Populate icons
|
|
156
156
|
this._populateIcons();
|
|
157
157
|
|
|
158
|
+
// Hide buttons based on options
|
|
159
|
+
if (tmpFloatingToolbar.length > 0)
|
|
160
|
+
{
|
|
161
|
+
if (this.options.EnableAddNode === false)
|
|
162
|
+
{
|
|
163
|
+
let tmpAddNodeBtn = tmpFloatingToolbar[0].querySelector('[data-flow-action="add-node"]');
|
|
164
|
+
if (tmpAddNodeBtn)
|
|
165
|
+
{
|
|
166
|
+
tmpAddNodeBtn.style.display = 'none';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (this.options.EnableCardPalette === false)
|
|
170
|
+
{
|
|
171
|
+
let tmpCardsBtn = tmpFloatingToolbar[0].querySelector('[data-flow-action="cards-popup"]');
|
|
172
|
+
if (tmpCardsBtn)
|
|
173
|
+
{
|
|
174
|
+
tmpCardsBtn.style.display = 'none';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
158
179
|
return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
|
|
159
180
|
}
|
|
160
181
|
|
|
@@ -12,6 +12,8 @@ const _DefaultConfiguration =
|
|
|
12
12
|
FlowViewIdentifier: 'Pict-Flow',
|
|
13
13
|
|
|
14
14
|
EnablePalette: true,
|
|
15
|
+
EnableAddNode: true,
|
|
16
|
+
EnableCardPalette: true,
|
|
15
17
|
|
|
16
18
|
CSS: false,
|
|
17
19
|
|
|
@@ -26,16 +28,14 @@ const _DefaultConfiguration =
|
|
|
26
28
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-plus-{~D:Record.FlowViewIdentifier~}"></span>
|
|
27
29
|
<span class="pict-flow-toolbar-btn-text">Node</span>
|
|
28
30
|
</button>
|
|
29
|
-
<button class="pict-flow-toolbar-btn danger" data-flow-action="delete-selected" title="Delete Node">
|
|
30
|
-
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-trash-{~D:Record.FlowViewIdentifier~}"></span>
|
|
31
|
-
</button>
|
|
32
|
-
</div>
|
|
33
|
-
<div class="pict-flow-toolbar-group">
|
|
34
31
|
<button class="pict-flow-toolbar-btn" data-flow-action="cards-popup" id="Flow-Toolbar-Cards-{~D:Record.FlowViewIdentifier~}" title="Card Palette">
|
|
35
32
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-cards-{~D:Record.FlowViewIdentifier~}"></span>
|
|
36
33
|
<span class="pict-flow-toolbar-btn-text">Cards</span>
|
|
37
34
|
<span class="pict-flow-toolbar-btn-chevron" id="Flow-Toolbar-CardsChevron-{~D:Record.FlowViewIdentifier~}"></span>
|
|
38
35
|
</button>
|
|
36
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="delete-selected" title="Delete Node">
|
|
37
|
+
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-trash-{~D:Record.FlowViewIdentifier~}"></span>
|
|
38
|
+
</button>
|
|
39
39
|
</div>
|
|
40
40
|
<div class="pict-flow-toolbar-group">
|
|
41
41
|
<button class="pict-flow-toolbar-btn" data-flow-action="layout-popup" id="Flow-Toolbar-Layout-{~D:Record.FlowViewIdentifier~}" title="Manage Layouts">
|
|
@@ -157,6 +157,24 @@ class PictViewFlowToolbar extends libPictView
|
|
|
157
157
|
// Populate SVG icons for toolbar buttons
|
|
158
158
|
this._populateToolbarIcons();
|
|
159
159
|
|
|
160
|
+
// Hide buttons based on options
|
|
161
|
+
if (this.options.EnableAddNode === false)
|
|
162
|
+
{
|
|
163
|
+
let tmpAddNodeBtn = this.pict.ContentAssignment.getElement(`#Flow-Toolbar-AddNode-${tmpFlowViewIdentifier}`);
|
|
164
|
+
if (tmpAddNodeBtn.length > 0)
|
|
165
|
+
{
|
|
166
|
+
tmpAddNodeBtn[0].style.display = 'none';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (this.options.EnableCardPalette === false)
|
|
170
|
+
{
|
|
171
|
+
let tmpCardsBtn = this.pict.ContentAssignment.getElement(`#Flow-Toolbar-Cards-${tmpFlowViewIdentifier}`);
|
|
172
|
+
if (tmpCardsBtn.length > 0)
|
|
173
|
+
{
|
|
174
|
+
tmpCardsBtn[0].style.display = 'none';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
160
178
|
return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
|
|
161
179
|
}
|
|
162
180
|
|
|
@@ -508,38 +526,100 @@ class PictViewFlowToolbar extends libPictView
|
|
|
508
526
|
// ── Cards Popup ───────────────────────────────────────────────────────
|
|
509
527
|
|
|
510
528
|
/**
|
|
511
|
-
* Build the Cards popup content
|
|
529
|
+
* Build the Cards popup content with search and categorized palette.
|
|
512
530
|
* @param {HTMLElement} pContainer
|
|
513
531
|
*/
|
|
514
532
|
_buildCardsPopup(pContainer)
|
|
515
533
|
{
|
|
516
|
-
|
|
534
|
+
// Search wrapper
|
|
535
|
+
let tmpSearchWrapper = document.createElement('div');
|
|
536
|
+
tmpSearchWrapper.className = 'pict-flow-popup-search-wrapper';
|
|
537
|
+
|
|
538
|
+
let tmpSearchIcon = document.createElement('span');
|
|
539
|
+
tmpSearchIcon.className = 'pict-flow-popup-search-icon';
|
|
540
|
+
let tmpIconProvider = this._FlowView ? this._FlowView._IconProvider : null;
|
|
541
|
+
if (tmpIconProvider)
|
|
542
|
+
{
|
|
543
|
+
tmpSearchIcon.innerHTML = tmpIconProvider.getIconSVGMarkup('search', 12);
|
|
544
|
+
}
|
|
545
|
+
tmpSearchWrapper.appendChild(tmpSearchIcon);
|
|
546
|
+
|
|
547
|
+
let tmpSearchInput = document.createElement('input');
|
|
548
|
+
tmpSearchInput.className = 'pict-flow-popup-search';
|
|
549
|
+
tmpSearchInput.setAttribute('type', 'text');
|
|
550
|
+
tmpSearchInput.setAttribute('placeholder', 'Search cards...');
|
|
551
|
+
tmpSearchWrapper.appendChild(tmpSearchInput);
|
|
552
|
+
pContainer.appendChild(tmpSearchWrapper);
|
|
553
|
+
|
|
554
|
+
// Palette list container
|
|
555
|
+
let tmpListDiv = document.createElement('div');
|
|
556
|
+
tmpListDiv.className = 'pict-flow-popup-node-list';
|
|
557
|
+
pContainer.appendChild(tmpListDiv);
|
|
558
|
+
|
|
559
|
+
// Initial population
|
|
560
|
+
this._renderPalette(tmpListDiv, '');
|
|
561
|
+
|
|
562
|
+
// Filter on input
|
|
563
|
+
tmpSearchInput.addEventListener('input', () =>
|
|
564
|
+
{
|
|
565
|
+
this._renderPalette(tmpListDiv, tmpSearchInput.value);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Focus search input
|
|
569
|
+
setTimeout(() => { tmpSearchInput.focus(); }, 50);
|
|
517
570
|
}
|
|
518
571
|
|
|
519
572
|
/**
|
|
520
573
|
* Render the card palette with categories and card chips into a container.
|
|
521
574
|
* @param {HTMLElement} pContainer - The target container element
|
|
575
|
+
* @param {string} [pFilter] - Optional search filter text
|
|
522
576
|
*/
|
|
523
|
-
_renderPalette(pContainer)
|
|
577
|
+
_renderPalette(pContainer, pFilter)
|
|
524
578
|
{
|
|
525
579
|
if (!this._FlowView || !this._FlowView._NodeTypeProvider) return;
|
|
526
580
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (tmpCategoryKeys.length === 0)
|
|
581
|
+
// Clear existing content
|
|
582
|
+
while (pContainer.firstChild)
|
|
531
583
|
{
|
|
532
|
-
|
|
533
|
-
tmpEmpty.className = 'pict-flow-popup-list-empty';
|
|
534
|
-
tmpEmpty.textContent = 'No card types available';
|
|
535
|
-
pContainer.appendChild(tmpEmpty);
|
|
536
|
-
return;
|
|
584
|
+
pContainer.removeChild(pContainer.firstChild);
|
|
537
585
|
}
|
|
538
586
|
|
|
587
|
+
let tmpCategories = this._FlowView._NodeTypeProvider.getCardsByCategory();
|
|
588
|
+
let tmpCategoryKeys = Object.keys(tmpCategories);
|
|
589
|
+
let tmpFilter = (pFilter || '').toLowerCase().trim();
|
|
590
|
+
let tmpTotalMatchCount = 0;
|
|
591
|
+
|
|
539
592
|
for (let i = 0; i < tmpCategoryKeys.length; i++)
|
|
540
593
|
{
|
|
541
594
|
let tmpCategoryName = tmpCategoryKeys[i];
|
|
542
595
|
let tmpCards = tmpCategories[tmpCategoryName];
|
|
596
|
+
let tmpMatchingCards = [];
|
|
597
|
+
|
|
598
|
+
// Filter cards within this category
|
|
599
|
+
for (let j = 0; j < tmpCards.length; j++)
|
|
600
|
+
{
|
|
601
|
+
let tmpCardConfig = tmpCards[j];
|
|
602
|
+
let tmpMeta = tmpCardConfig.CardMetadata || {};
|
|
603
|
+
|
|
604
|
+
if (tmpFilter)
|
|
605
|
+
{
|
|
606
|
+
let tmpLabel = (tmpCardConfig.Label || '').toLowerCase();
|
|
607
|
+
let tmpCode = (tmpMeta.Code || '').toLowerCase();
|
|
608
|
+
let tmpCategory = tmpCategoryName.toLowerCase();
|
|
609
|
+
if (tmpLabel.indexOf(tmpFilter) < 0 &&
|
|
610
|
+
tmpCode.indexOf(tmpFilter) < 0 &&
|
|
611
|
+
tmpCategory.indexOf(tmpFilter) < 0)
|
|
612
|
+
{
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
tmpMatchingCards.push(tmpCardConfig);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (tmpMatchingCards.length === 0) continue;
|
|
621
|
+
|
|
622
|
+
tmpTotalMatchCount += tmpMatchingCards.length;
|
|
543
623
|
|
|
544
624
|
let tmpCategoryDiv = document.createElement('div');
|
|
545
625
|
tmpCategoryDiv.className = 'pict-flow-palette-category';
|
|
@@ -553,9 +633,9 @@ class PictViewFlowToolbar extends libPictView
|
|
|
553
633
|
let tmpCardsDiv = document.createElement('div');
|
|
554
634
|
tmpCardsDiv.className = 'pict-flow-palette-cards';
|
|
555
635
|
|
|
556
|
-
for (let j = 0; j <
|
|
636
|
+
for (let j = 0; j < tmpMatchingCards.length; j++)
|
|
557
637
|
{
|
|
558
|
-
let tmpCardConfig =
|
|
638
|
+
let tmpCardConfig = tmpMatchingCards[j];
|
|
559
639
|
let tmpMeta = tmpCardConfig.CardMetadata || {};
|
|
560
640
|
|
|
561
641
|
let tmpCardEl = document.createElement('div');
|
|
@@ -635,6 +715,14 @@ class PictViewFlowToolbar extends libPictView
|
|
|
635
715
|
tmpCategoryDiv.appendChild(tmpCardsDiv);
|
|
636
716
|
pContainer.appendChild(tmpCategoryDiv);
|
|
637
717
|
}
|
|
718
|
+
|
|
719
|
+
if (tmpTotalMatchCount === 0)
|
|
720
|
+
{
|
|
721
|
+
let tmpEmpty = document.createElement('div');
|
|
722
|
+
tmpEmpty.className = 'pict-flow-popup-list-empty';
|
|
723
|
+
tmpEmpty.textContent = tmpFilter ? 'No matching cards' : 'No card types available';
|
|
724
|
+
pContainer.appendChild(tmpEmpty);
|
|
725
|
+
}
|
|
638
726
|
}
|
|
639
727
|
|
|
640
728
|
// ── Layout Popup ──────────────────────────────────────────────────────
|
|
@@ -1018,7 +1106,9 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1018
1106
|
'PictViewFlowFloatingToolbar',
|
|
1019
1107
|
{
|
|
1020
1108
|
FlowViewIdentifier: tmpFlowViewIdentifier,
|
|
1021
|
-
DefaultDestinationAddress: `#Flow-FloatingToolbar-Container-${tmpFlowViewIdentifier}
|
|
1109
|
+
DefaultDestinationAddress: `#Flow-FloatingToolbar-Container-${tmpFlowViewIdentifier}`,
|
|
1110
|
+
EnableAddNode: this.options.EnableAddNode,
|
|
1111
|
+
EnableCardPalette: this.options.EnableCardPalette
|
|
1022
1112
|
}
|
|
1023
1113
|
);
|
|
1024
1114
|
this._FloatingToolbarView._ToolbarView = this;
|
|
@@ -50,6 +50,9 @@ const _DefaultConfiguration =
|
|
|
50
50
|
TargetElementAddress: '#Flow-SVG-Container',
|
|
51
51
|
|
|
52
52
|
EnableToolbar: true,
|
|
53
|
+
EnableAddNode: true,
|
|
54
|
+
EnableCardPalette: true,
|
|
55
|
+
IncludeDefaultNodeTypes: true,
|
|
53
56
|
EnablePanning: true,
|
|
54
57
|
EnableZooming: true,
|
|
55
58
|
EnableNodeDragging: true,
|
|
@@ -145,7 +148,7 @@ class PictViewFlow extends libPictView
|
|
|
145
148
|
{ ServiceType: 'PictProviderFlowIcons', Library: libPictProviderFlowIcons, Property: '_IconProvider', PostInit: 'registerIconTemplates' },
|
|
146
149
|
{ ServiceType: 'PictProviderFlowConnectorShapes', Library: libPictProviderFlowConnectorShapes, Property: '_ConnectorShapesProvider' },
|
|
147
150
|
{ ServiceType: 'PictProviderFlowPanelChrome', Library: libPictProviderFlowPanelChrome, Property: '_PanelChromeProvider' },
|
|
148
|
-
{ ServiceType: 'PictProviderFlowNodeTypes', Library: libPictProviderFlowNodeTypes, Property: '_NodeTypeProvider', ExtraOptions: () => ({ AdditionalNodeTypes: this.options.NodeTypes }) },
|
|
151
|
+
{ ServiceType: 'PictProviderFlowNodeTypes', Library: libPictProviderFlowNodeTypes, Property: '_NodeTypeProvider', ExtraOptions: () => ({ AdditionalNodeTypes: this.options.NodeTypes, IncludeDefaultNodeTypes: this.options.IncludeDefaultNodeTypes }) },
|
|
149
152
|
{ ServiceType: 'PictProviderFlowEventHandler', Library: libPictProviderFlowEventHandler, Property: '_EventHandlerProvider' },
|
|
150
153
|
{ ServiceType: 'PictProviderFlowLayouts', Library: libPictProviderFlowLayouts, Property: '_LayoutProvider', PostInit: 'loadPersistedLayouts' },
|
|
151
154
|
|
|
@@ -410,7 +413,9 @@ class PictViewFlow extends libPictView
|
|
|
410
413
|
{
|
|
411
414
|
ViewIdentifier: `Flow-Toolbar-${tmpViewIdentifier}`,
|
|
412
415
|
DefaultDestinationAddress: `#Flow-Toolbar-${tmpViewIdentifier}`,
|
|
413
|
-
FlowViewIdentifier: tmpViewIdentifier
|
|
416
|
+
FlowViewIdentifier: tmpViewIdentifier,
|
|
417
|
+
EnableAddNode: this.options.EnableAddNode,
|
|
418
|
+
EnableCardPalette: this.options.EnableCardPalette
|
|
414
419
|
}
|
|
415
420
|
));
|
|
416
421
|
// Use the toolbar's render method after it's set up
|
|
@@ -689,6 +694,54 @@ class PictViewFlow extends libPictView
|
|
|
689
694
|
_resetHandlesForNode(pNodeHash) { return this._ConnectionHandleManager.resetHandlesForNode(pNodeHash); }
|
|
690
695
|
_resetHandlesForPanel(pPanelHash) { return this._ConnectionHandleManager.resetHandlesForPanel(pPanelHash); }
|
|
691
696
|
|
|
697
|
+
/**
|
|
698
|
+
* Add a bezier handle to a tether at the specified SVG position.
|
|
699
|
+
* @param {string} pPanelHash
|
|
700
|
+
* @param {number} pX
|
|
701
|
+
* @param {number} pY
|
|
702
|
+
*/
|
|
703
|
+
addTetherHandle(pPanelHash, pX, pY)
|
|
704
|
+
{
|
|
705
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
706
|
+
if (!tmpPanel || !this._TetherService) return;
|
|
707
|
+
|
|
708
|
+
let tmpNode = this.getNode(tmpPanel.NodeHash);
|
|
709
|
+
if (!tmpNode) return;
|
|
710
|
+
|
|
711
|
+
let tmpAnchors = this._TetherService.getSmartAnchors(tmpPanel, tmpNode);
|
|
712
|
+
|
|
713
|
+
this._TetherService.addHandle(tmpPanel, pX, pY, tmpAnchors.panelAnchor, tmpAnchors.nodeAnchor);
|
|
714
|
+
|
|
715
|
+
this.renderFlow();
|
|
716
|
+
this.marshalFromView();
|
|
717
|
+
|
|
718
|
+
if (this._EventHandlerProvider)
|
|
719
|
+
{
|
|
720
|
+
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Remove a bezier handle from a tether by index.
|
|
726
|
+
* @param {string} pPanelHash
|
|
727
|
+
* @param {number} pIndex
|
|
728
|
+
*/
|
|
729
|
+
removeTetherHandle(pPanelHash, pIndex)
|
|
730
|
+
{
|
|
731
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
732
|
+
if (!tmpPanel || !this._TetherService) return;
|
|
733
|
+
|
|
734
|
+
this._TetherService.removeHandle(tmpPanel, pIndex);
|
|
735
|
+
|
|
736
|
+
this.renderFlow();
|
|
737
|
+
this.marshalFromView();
|
|
738
|
+
|
|
739
|
+
if (this._EventHandlerProvider)
|
|
740
|
+
{
|
|
741
|
+
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
692
745
|
/**
|
|
693
746
|
* Update a tether handle position during drag (for real-time feedback).
|
|
694
747
|
* Delegates state update to the TetherService.
|