pict-section-flow 0.0.13 → 0.0.14
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
CHANGED
|
@@ -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
|
|
|
@@ -689,6 +689,54 @@ class PictViewFlow extends libPictView
|
|
|
689
689
|
_resetHandlesForNode(pNodeHash) { return this._ConnectionHandleManager.resetHandlesForNode(pNodeHash); }
|
|
690
690
|
_resetHandlesForPanel(pPanelHash) { return this._ConnectionHandleManager.resetHandlesForPanel(pPanelHash); }
|
|
691
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Add a bezier handle to a tether at the specified SVG position.
|
|
694
|
+
* @param {string} pPanelHash
|
|
695
|
+
* @param {number} pX
|
|
696
|
+
* @param {number} pY
|
|
697
|
+
*/
|
|
698
|
+
addTetherHandle(pPanelHash, pX, pY)
|
|
699
|
+
{
|
|
700
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
701
|
+
if (!tmpPanel || !this._TetherService) return;
|
|
702
|
+
|
|
703
|
+
let tmpNode = this.getNode(tmpPanel.NodeHash);
|
|
704
|
+
if (!tmpNode) return;
|
|
705
|
+
|
|
706
|
+
let tmpAnchors = this._TetherService.getSmartAnchors(tmpPanel, tmpNode);
|
|
707
|
+
|
|
708
|
+
this._TetherService.addHandle(tmpPanel, pX, pY, tmpAnchors.panelAnchor, tmpAnchors.nodeAnchor);
|
|
709
|
+
|
|
710
|
+
this.renderFlow();
|
|
711
|
+
this.marshalFromView();
|
|
712
|
+
|
|
713
|
+
if (this._EventHandlerProvider)
|
|
714
|
+
{
|
|
715
|
+
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Remove a bezier handle from a tether by index.
|
|
721
|
+
* @param {string} pPanelHash
|
|
722
|
+
* @param {number} pIndex
|
|
723
|
+
*/
|
|
724
|
+
removeTetherHandle(pPanelHash, pIndex)
|
|
725
|
+
{
|
|
726
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
727
|
+
if (!tmpPanel || !this._TetherService) return;
|
|
728
|
+
|
|
729
|
+
this._TetherService.removeHandle(tmpPanel, pIndex);
|
|
730
|
+
|
|
731
|
+
this.renderFlow();
|
|
732
|
+
this.marshalFromView();
|
|
733
|
+
|
|
734
|
+
if (this._EventHandlerProvider)
|
|
735
|
+
{
|
|
736
|
+
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
692
740
|
/**
|
|
693
741
|
* Update a tether handle position during drag (for real-time feedback).
|
|
694
742
|
* Delegates state update to the TetherService.
|