pict-section-flow 0.0.1 → 0.0.2

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.
Files changed (48) hide show
  1. package/docs/README.md +19 -0
  2. package/{example_application → example_applications/simple_cards}/html/index.html +2 -2
  3. package/example_applications/simple_cards/package.json +43 -0
  4. package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +434 -0
  5. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +36 -0
  6. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +54 -0
  7. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +48 -0
  8. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +35 -0
  9. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +47 -0
  10. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +53 -0
  11. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +95 -0
  12. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +37 -0
  13. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +59 -0
  14. package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-Layout.js +5 -1
  15. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +312 -0
  16. package/package.json +6 -6
  17. package/source/Pict-Section-Flow.js +19 -0
  18. package/source/PictFlowCard.js +207 -0
  19. package/source/PictFlowCardPropertiesPanel.js +105 -0
  20. package/source/panels/FlowCardPropertiesPanel-Form.js +174 -0
  21. package/source/panels/FlowCardPropertiesPanel-Markdown.js +148 -0
  22. package/source/panels/FlowCardPropertiesPanel-Template.js +88 -0
  23. package/source/panels/FlowCardPropertiesPanel-View.js +114 -0
  24. package/source/providers/PictProvider-Flow-EventHandler.js +19 -8
  25. package/source/providers/PictProvider-Flow-Geometry.js +64 -0
  26. package/source/providers/PictProvider-Flow-Layouts.js +284 -0
  27. package/source/providers/PictProvider-Flow-NodeTypes.js +70 -0
  28. package/source/providers/PictProvider-Flow-PanelChrome.js +72 -0
  29. package/source/providers/PictProvider-Flow-SVGHelpers.js +30 -0
  30. package/source/services/PictService-Flow-ConnectionRenderer.js +324 -66
  31. package/source/services/PictService-Flow-InteractionManager.js +399 -75
  32. package/source/services/PictService-Flow-Layout.js +159 -0
  33. package/source/services/PictService-Flow-PathGenerator.js +199 -0
  34. package/source/services/PictService-Flow-Tether.js +544 -0
  35. package/source/views/PictView-Flow-Node.js +95 -18
  36. package/source/views/PictView-Flow-PropertiesPanel.js +435 -0
  37. package/source/views/PictView-Flow-Toolbar.js +491 -5
  38. package/source/views/PictView-Flow.js +830 -8
  39. package/example_application/package.json +0 -41
  40. package/example_application/source/Pict-Application-FlowExample.js +0 -241
  41. package/example_application/source/views/PictView-FlowExample-MainWorkspace.js +0 -191
  42. /package/{example_application → example_applications/simple_cards}/css/flowexample.css +0 -0
  43. /package/{example_application → example_applications/simple_cards}/source/Pict-Application-FlowExample-Configuration.json +0 -0
  44. /package/{example_application → example_applications/simple_cards}/source/providers/PictRouter-FlowExample-Configuration.json +0 -0
  45. /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-About.js +0 -0
  46. /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-BottomBar.js +0 -0
  47. /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-Documentation.js +0 -0
  48. /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-TopBar.js +0 -0
@@ -7,6 +7,8 @@ const INTERACTION_STATES =
7
7
  {
8
8
  IDLE: 'idle',
9
9
  DRAGGING_NODE: 'dragging-node',
10
+ DRAGGING_PANEL: 'dragging-panel',
11
+ DRAGGING_HANDLE: 'dragging-handle',
10
12
  CONNECTING: 'connecting',
11
13
  PANNING: 'panning'
12
14
  };
@@ -34,6 +36,19 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
34
36
  this._DragNodeStartX = 0;
35
37
  this._DragNodeStartY = 0;
36
38
 
39
+ // Panel drag state
40
+ this._DragPanelHash = null;
41
+ this._DragPanelStartX = 0;
42
+ this._DragPanelStartY = 0;
43
+ this._DragPanelDataStartX = 0;
44
+ this._DragPanelDataStartY = 0;
45
+
46
+ // Handle drag state
47
+ this._DragHandleConnectionHash = null;
48
+ this._DragHandlePanelHash = null;
49
+ this._DragHandleType = null;
50
+ this._DragHandleIsTether = false;
51
+
37
52
  // Pan state
38
53
  this._PanStartX = 0;
39
54
  this._PanStartY = 0;
@@ -45,6 +60,16 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
45
60
  this._ConnectSourcePortHash = null;
46
61
  this._ConnectDragLine = null;
47
62
 
63
+ // Double-click detection
64
+ this._LastClickTime = 0;
65
+ this._LastClickNodeHash = null;
66
+ this._DoubleClickThreshold = 400;
67
+
68
+ // Double-click detection for handles
69
+ this._LastHandleClickTime = 0;
70
+ this._LastHandleClickHash = null;
71
+ this._LastHandleClickType = null;
72
+
48
73
  // Bound event handlers (for removeEventListener)
49
74
  this._boundOnPointerDown = this._onPointerDown.bind(this);
50
75
  this._boundOnPointerMove = this._onPointerMove.bind(this);
@@ -110,6 +135,12 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
110
135
  let tmpTarget = pEvent.target;
111
136
  let tmpElementType = this._getElementType(tmpTarget);
112
137
 
138
+ // Check if click is inside a panel body — let HTML handle its own events
139
+ if (tmpTarget.closest && tmpTarget.closest('.pict-flow-panel-body'))
140
+ {
141
+ return;
142
+ }
143
+
113
144
  // Capture pointer for smooth dragging
114
145
  this._SVGElement.setPointerCapture(pEvent.pointerId);
115
146
 
@@ -121,8 +152,102 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
121
152
 
122
153
  case 'node':
123
154
  case 'node-body':
124
- this._startNodeDrag(pEvent, tmpTarget);
155
+ case 'panel-indicator':
156
+ {
157
+ let tmpNodeHash = this._getNodeHash(tmpTarget);
158
+ let tmpNow = Date.now();
159
+
160
+ // Check for double-click on same node
161
+ if (tmpNodeHash && tmpNodeHash === this._LastClickNodeHash
162
+ && (tmpNow - this._LastClickTime) < this._DoubleClickThreshold)
163
+ {
164
+ // Double-click: toggle panel
165
+ this._LastClickTime = 0;
166
+ this._LastClickNodeHash = null;
167
+ this._FlowView.togglePanel(tmpNodeHash);
168
+ }
169
+ else
170
+ {
171
+ // Single click: start node drag
172
+ this._LastClickTime = tmpNow;
173
+ this._LastClickNodeHash = tmpNodeHash;
174
+ this._startNodeDrag(pEvent, tmpTarget);
175
+ }
125
176
  break;
177
+ }
178
+
179
+ case 'panel-titlebar':
180
+ this._startPanelDrag(pEvent, tmpTarget);
181
+ break;
182
+
183
+ case 'panel-close':
184
+ {
185
+ let tmpPanelHash = this._getPanelHash(tmpTarget);
186
+ if (tmpPanelHash)
187
+ {
188
+ this._FlowView.closePanel(tmpPanelHash);
189
+ }
190
+ break;
191
+ }
192
+
193
+ case 'connection-handle':
194
+ {
195
+ let tmpConnectionHash = this._getConnectionHash(tmpTarget);
196
+ let tmpHandleType = tmpTarget.getAttribute('data-handle-type');
197
+ let tmpNow = Date.now();
198
+
199
+ // Check for double-click on handle to toggle mode
200
+ if (tmpConnectionHash === this._LastHandleClickHash
201
+ && tmpHandleType === this._LastHandleClickType
202
+ && (tmpNow - this._LastHandleClickTime) < this._DoubleClickThreshold)
203
+ {
204
+ this._toggleConnectionLineMode(tmpConnectionHash);
205
+ this._LastHandleClickTime = 0;
206
+ this._LastHandleClickHash = null;
207
+ this._LastHandleClickType = null;
208
+ }
209
+ else
210
+ {
211
+ this._LastHandleClickTime = tmpNow;
212
+ this._LastHandleClickHash = tmpConnectionHash;
213
+ this._LastHandleClickType = tmpHandleType;
214
+ this._startHandleDrag(pEvent, tmpConnectionHash, null, tmpHandleType, false);
215
+ }
216
+ pEvent.stopPropagation();
217
+ break;
218
+ }
219
+
220
+ case 'tether':
221
+ case 'tether-hitarea':
222
+ this._selectTether(tmpTarget);
223
+ break;
224
+
225
+ case 'tether-handle':
226
+ {
227
+ let tmpPanelHash = this._getPanelHash(tmpTarget);
228
+ let tmpHandleType = tmpTarget.getAttribute('data-handle-type');
229
+ let tmpNow = Date.now();
230
+
231
+ // Check for double-click on tether handle to toggle mode
232
+ if (tmpPanelHash === this._LastHandleClickHash
233
+ && tmpHandleType === this._LastHandleClickType
234
+ && (tmpNow - this._LastHandleClickTime) < this._DoubleClickThreshold)
235
+ {
236
+ this._toggleTetherLineMode(tmpPanelHash);
237
+ this._LastHandleClickTime = 0;
238
+ this._LastHandleClickHash = null;
239
+ this._LastHandleClickType = null;
240
+ }
241
+ else
242
+ {
243
+ this._LastHandleClickTime = tmpNow;
244
+ this._LastHandleClickHash = tmpPanelHash;
245
+ this._LastHandleClickType = tmpHandleType;
246
+ this._startHandleDrag(pEvent, null, tmpPanelHash, tmpHandleType, true);
247
+ }
248
+ pEvent.stopPropagation();
249
+ break;
250
+ }
126
251
 
127
252
  case 'connection':
128
253
  case 'connection-hitarea':
@@ -153,6 +278,14 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
153
278
  this._onNodeDrag(pEvent);
154
279
  break;
155
280
 
281
+ case INTERACTION_STATES.DRAGGING_PANEL:
282
+ this._onPanelDrag(pEvent);
283
+ break;
284
+
285
+ case INTERACTION_STATES.DRAGGING_HANDLE:
286
+ this._onHandleDrag(pEvent);
287
+ break;
288
+
156
289
  case INTERACTION_STATES.CONNECTING:
157
290
  this._onConnectionDrag(pEvent);
158
291
  break;
@@ -183,6 +316,14 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
183
316
  this._endNodeDrag(pEvent);
184
317
  break;
185
318
 
319
+ case INTERACTION_STATES.DRAGGING_PANEL:
320
+ this._endPanelDrag(pEvent);
321
+ break;
322
+
323
+ case INTERACTION_STATES.DRAGGING_HANDLE:
324
+ this._endHandleDrag(pEvent);
325
+ break;
326
+
186
327
  case INTERACTION_STATES.CONNECTING:
187
328
  this._endConnection(pEvent);
188
329
  break;
@@ -225,11 +366,15 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
225
366
  // Only handle events when the flow is focused/visible
226
367
  if (pEvent.key === 'Delete' || pEvent.key === 'Backspace')
227
368
  {
228
- // Don't delete if user is typing in an input
369
+ // Don't delete if user is typing in an input or inside a panel
229
370
  if (pEvent.target && (pEvent.target.tagName === 'INPUT' || pEvent.target.tagName === 'TEXTAREA' || pEvent.target.tagName === 'SELECT'))
230
371
  {
231
372
  return;
232
373
  }
374
+ if (pEvent.target && pEvent.target.closest && pEvent.target.closest('.pict-flow-panel'))
375
+ {
376
+ return;
377
+ }
233
378
 
234
379
  this._FlowView.deleteSelected();
235
380
  pEvent.preventDefault();
@@ -240,17 +385,31 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
240
385
  {
241
386
  this._cancelConnection();
242
387
  }
388
+
389
+ // Exit fullscreen if currently in fullscreen mode
390
+ if (this._FlowView._IsFullscreen)
391
+ {
392
+ this._FlowView.exitFullscreen();
393
+ // Update the toolbar button text
394
+ if (this._FlowView._ToolbarView)
395
+ {
396
+ let tmpFlowViewIdentifier = this._FlowView.options.ViewIdentifier;
397
+ let tmpBtnElements = this._FlowView.pict.ContentAssignment.getElement(`#Flow-Toolbar-Fullscreen-${tmpFlowViewIdentifier}`);
398
+ if (tmpBtnElements.length > 0)
399
+ {
400
+ tmpBtnElements[0].innerHTML = '&#x26F6; Fullscreen';
401
+ }
402
+ }
403
+ pEvent.preventDefault();
404
+ return;
405
+ }
406
+
243
407
  this._FlowView.deselectAll();
244
408
  }
245
409
  }
246
410
 
247
411
  // ---- Node Dragging ----
248
412
 
249
- /**
250
- * Start dragging a node
251
- * @param {PointerEvent} pEvent
252
- * @param {Element} pTarget
253
- */
254
413
  _startNodeDrag(pEvent, pTarget)
255
414
  {
256
415
  if (!this._FlowView.options.EnableNodeDragging) return;
@@ -258,7 +417,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
258
417
  let tmpNodeHash = this._getNodeHash(pTarget);
259
418
  if (!tmpNodeHash) return;
260
419
 
261
- // Select the node
262
420
  this._FlowView.selectNode(tmpNodeHash);
263
421
 
264
422
  let tmpNode = this._FlowView.getNode(tmpNodeHash);
@@ -271,7 +429,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
271
429
  this._DragNodeStartX = tmpNode.X;
272
430
  this._DragNodeStartY = tmpNode.Y;
273
431
 
274
- // Add dragging class
275
432
  this._SVGElement.classList.add('panning');
276
433
 
277
434
  let tmpNodeGroup = this._FlowView._NodesLayer.querySelector(`[data-node-hash="${tmpNodeHash}"]`);
@@ -281,10 +438,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
281
438
  }
282
439
  }
283
440
 
284
- /**
285
- * Handle node dragging
286
- * @param {PointerEvent} pEvent
287
- */
288
441
  _onNodeDrag(pEvent)
289
442
  {
290
443
  if (!this._DragNodeHash) return;
@@ -299,10 +452,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
299
452
  this._FlowView.updateNodePosition(this._DragNodeHash, tmpNewX, tmpNewY);
300
453
  }
301
454
 
302
- /**
303
- * End node dragging
304
- * @param {PointerEvent} pEvent
305
- */
306
455
  _endNodeDrag(pEvent)
307
456
  {
308
457
  this._SVGElement.classList.remove('panning');
@@ -313,7 +462,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
313
462
  tmpNodeGroup.classList.remove('dragging');
314
463
  }
315
464
 
316
- // Full re-render to finalize positions
317
465
  this._FlowView.renderFlow();
318
466
  this._FlowView.marshalFromView();
319
467
 
@@ -328,13 +476,196 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
328
476
  this._DragNodeHash = null;
329
477
  }
330
478
 
479
+ // ---- Panel Dragging ----
480
+
481
+ _startPanelDrag(pEvent, pTarget)
482
+ {
483
+ let tmpPanelHash = this._getPanelHash(pTarget);
484
+ if (!tmpPanelHash) return;
485
+
486
+ let tmpPanel = this._FlowView._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === tmpPanelHash);
487
+ if (!tmpPanel) return;
488
+
489
+ this._State = INTERACTION_STATES.DRAGGING_PANEL;
490
+ this._DragPanelHash = tmpPanelHash;
491
+ this._DragPanelStartX = pEvent.clientX;
492
+ this._DragPanelStartY = pEvent.clientY;
493
+ this._DragPanelDataStartX = tmpPanel.X;
494
+ this._DragPanelDataStartY = tmpPanel.Y;
495
+
496
+ this._SVGElement.classList.add('panning');
497
+ }
498
+
499
+ _onPanelDrag(pEvent)
500
+ {
501
+ if (!this._DragPanelHash) return;
502
+
503
+ let tmpVS = this._FlowView.viewState;
504
+ let tmpDX = (pEvent.clientX - this._DragPanelStartX) / tmpVS.Zoom;
505
+ let tmpDY = (pEvent.clientY - this._DragPanelStartY) / tmpVS.Zoom;
506
+
507
+ let tmpNewX = this._DragPanelDataStartX + tmpDX;
508
+ let tmpNewY = this._DragPanelDataStartY + tmpDY;
509
+
510
+ this._FlowView.updatePanelPosition(this._DragPanelHash, tmpNewX, tmpNewY);
511
+ }
512
+
513
+ _endPanelDrag(pEvent)
514
+ {
515
+ this._SVGElement.classList.remove('panning');
516
+
517
+ this._FlowView.marshalFromView();
518
+
519
+ let tmpPanel = this._FlowView._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === this._DragPanelHash);
520
+ if (tmpPanel && this._FlowView._EventHandlerProvider)
521
+ {
522
+ this._FlowView._EventHandlerProvider.fireEvent('onPanelMoved', tmpPanel);
523
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
524
+ }
525
+
526
+ this._State = INTERACTION_STATES.IDLE;
527
+ this._DragPanelHash = null;
528
+ }
529
+
530
+ // ---- Handle Dragging ----
531
+
532
+ _startHandleDrag(pEvent, pConnectionHash, pPanelHash, pHandleType, pIsTether)
533
+ {
534
+ this._State = INTERACTION_STATES.DRAGGING_HANDLE;
535
+ this._DragHandleConnectionHash = pConnectionHash;
536
+ this._DragHandlePanelHash = pPanelHash;
537
+ this._DragHandleType = pHandleType;
538
+ this._DragHandleIsTether = pIsTether;
539
+ this._DragStartX = pEvent.clientX;
540
+ this._DragStartY = pEvent.clientY;
541
+ this._SVGElement.classList.add('panning');
542
+ }
543
+
544
+ _onHandleDrag(pEvent)
545
+ {
546
+ let tmpCoords = this._FlowView.screenToSVGCoords(pEvent.clientX, pEvent.clientY);
547
+
548
+ if (this._DragHandleIsTether)
549
+ {
550
+ this._FlowView.updateTetherHandle(
551
+ this._DragHandlePanelHash,
552
+ this._DragHandleType,
553
+ tmpCoords.x,
554
+ tmpCoords.y
555
+ );
556
+ }
557
+ else
558
+ {
559
+ this._FlowView.updateConnectionHandle(
560
+ this._DragHandleConnectionHash,
561
+ this._DragHandleType,
562
+ tmpCoords.x,
563
+ tmpCoords.y
564
+ );
565
+ }
566
+ }
567
+
568
+ _endHandleDrag(pEvent)
569
+ {
570
+ this._SVGElement.classList.remove('panning');
571
+
572
+ this._FlowView.renderFlow();
573
+ this._FlowView.marshalFromView();
574
+
575
+ if (this._FlowView._EventHandlerProvider)
576
+ {
577
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
578
+
579
+ if (this._DragHandleIsTether)
580
+ {
581
+ let tmpPanel = this._FlowView._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === this._DragHandlePanelHash);
582
+ if (tmpPanel)
583
+ {
584
+ this._FlowView._EventHandlerProvider.fireEvent('onTetherHandleMoved', tmpPanel);
585
+ }
586
+ }
587
+ else
588
+ {
589
+ let tmpConnection = this._FlowView.getConnection(this._DragHandleConnectionHash);
590
+ if (tmpConnection)
591
+ {
592
+ this._FlowView._EventHandlerProvider.fireEvent('onConnectionHandleMoved', tmpConnection);
593
+ }
594
+ }
595
+ }
596
+
597
+ this._State = INTERACTION_STATES.IDLE;
598
+ this._DragHandleConnectionHash = null;
599
+ this._DragHandlePanelHash = null;
600
+ this._DragHandleType = null;
601
+ this._DragHandleIsTether = false;
602
+ }
603
+
604
+ // ---- Line Mode Toggling ----
605
+
606
+ _toggleConnectionLineMode(pConnectionHash)
607
+ {
608
+ let tmpConnection = this._FlowView.getConnection(pConnectionHash);
609
+ if (!tmpConnection) return;
610
+
611
+ if (!tmpConnection.Data) tmpConnection.Data = {};
612
+
613
+ let tmpCurrentMode = tmpConnection.Data.LineMode || 'bezier';
614
+ tmpConnection.Data.LineMode = (tmpCurrentMode === 'bezier') ? 'orthogonal' : 'bezier';
615
+
616
+ // Reset handle positions when switching modes
617
+ tmpConnection.Data.HandleCustomized = false;
618
+ tmpConnection.Data.BezierHandleX = null;
619
+ tmpConnection.Data.BezierHandleY = null;
620
+ tmpConnection.Data.OrthoCorner1X = null;
621
+ tmpConnection.Data.OrthoCorner1Y = null;
622
+ tmpConnection.Data.OrthoCorner2X = null;
623
+ tmpConnection.Data.OrthoCorner2Y = null;
624
+ tmpConnection.Data.OrthoMidOffset = 0;
625
+
626
+ this._FlowView.renderFlow();
627
+ this._FlowView.marshalFromView();
628
+
629
+ if (this._FlowView._EventHandlerProvider)
630
+ {
631
+ this._FlowView._EventHandlerProvider.fireEvent('onConnectionModeChanged', tmpConnection);
632
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
633
+ }
634
+ }
635
+
636
+ _toggleTetherLineMode(pPanelHash)
637
+ {
638
+ let tmpPanel = this._FlowView._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
639
+ if (!tmpPanel) return;
640
+
641
+ if (this._FlowView._TetherService)
642
+ {
643
+ this._FlowView._TetherService.toggleLineMode(tmpPanel);
644
+ }
645
+
646
+ this._FlowView.renderFlow();
647
+ this._FlowView.marshalFromView();
648
+
649
+ if (this._FlowView._EventHandlerProvider)
650
+ {
651
+ this._FlowView._EventHandlerProvider.fireEvent('onTetherModeChanged', tmpPanel);
652
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
653
+ }
654
+ }
655
+
656
+ // ---- Tether Selection ----
657
+
658
+ _selectTether(pTarget)
659
+ {
660
+ let tmpPanelHash = this._getPanelHash(pTarget);
661
+ if (tmpPanelHash)
662
+ {
663
+ this._FlowView.selectTether(tmpPanelHash);
664
+ }
665
+ }
666
+
331
667
  // ---- Connection Creation ----
332
668
 
333
- /**
334
- * Start creating a connection from a port
335
- * @param {PointerEvent} pEvent
336
- * @param {Element} pTarget
337
- */
338
669
  _startConnection(pEvent, pTarget)
339
670
  {
340
671
  if (!this._FlowView.options.EnableConnectionCreation) return;
@@ -345,7 +676,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
345
676
 
346
677
  if (!tmpNodeHash || !tmpPortHash) return;
347
678
 
348
- // Only allow starting connections from output ports
349
679
  if (tmpPortDirection !== 'output')
350
680
  {
351
681
  return;
@@ -357,25 +687,18 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
357
687
 
358
688
  this._SVGElement.classList.add('connecting');
359
689
 
360
- // Create drag line
361
690
  let tmpPortPos = this._FlowView.getPortPosition(tmpNodeHash, tmpPortHash);
362
691
  if (tmpPortPos)
363
692
  {
364
693
  this._ConnectDragLine = document.createElementNS('http://www.w3.org/2000/svg', 'path');
365
694
  this._ConnectDragLine.setAttribute('class', 'pict-flow-drag-connection');
366
695
  this._ConnectDragLine.setAttribute('d', `M ${tmpPortPos.x} ${tmpPortPos.y} L ${tmpPortPos.x} ${tmpPortPos.y}`);
367
-
368
- // Add to viewport (so it transforms with pan/zoom)
369
696
  this._FlowView._ViewportElement.appendChild(this._ConnectDragLine);
370
697
  }
371
698
 
372
699
  pEvent.stopPropagation();
373
700
  }
374
701
 
375
- /**
376
- * Handle connection drag
377
- * @param {PointerEvent} pEvent
378
- */
379
702
  _onConnectionDrag(pEvent)
380
703
  {
381
704
  if (!this._ConnectDragLine) return;
@@ -385,19 +708,13 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
385
708
 
386
709
  let tmpEndCoords = this._FlowView.screenToSVGCoords(pEvent.clientX, pEvent.clientY);
387
710
 
388
- // Render a bezier curve for the drag line
389
711
  let tmpDX = Math.abs(tmpEndCoords.x - tmpSourcePos.x) * 0.5;
390
712
  let tmpPath = `M ${tmpSourcePos.x} ${tmpSourcePos.y} C ${tmpSourcePos.x + tmpDX} ${tmpSourcePos.y}, ${tmpEndCoords.x - tmpDX} ${tmpEndCoords.y}, ${tmpEndCoords.x} ${tmpEndCoords.y}`;
391
713
  this._ConnectDragLine.setAttribute('d', tmpPath);
392
714
  }
393
715
 
394
- /**
395
- * End connection creation
396
- * @param {PointerEvent} pEvent
397
- */
398
716
  _endConnection(pEvent)
399
717
  {
400
- // Remove drag line
401
718
  if (this._ConnectDragLine && this._ConnectDragLine.parentNode)
402
719
  {
403
720
  this._ConnectDragLine.parentNode.removeChild(this._ConnectDragLine);
@@ -406,7 +723,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
406
723
 
407
724
  this._SVGElement.classList.remove('connecting');
408
725
 
409
- // Check if we're over a valid target port
410
726
  let tmpTarget = document.elementFromPoint(pEvent.clientX, pEvent.clientY);
411
727
  if (tmpTarget)
412
728
  {
@@ -430,9 +746,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
430
746
  this._ConnectSourcePortHash = null;
431
747
  }
432
748
 
433
- /**
434
- * Cancel connection creation (e.g., on Escape)
435
- */
436
749
  _cancelConnection()
437
750
  {
438
751
  if (this._ConnectDragLine && this._ConnectDragLine.parentNode)
@@ -450,13 +763,8 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
450
763
 
451
764
  // ---- Panning ----
452
765
 
453
- /**
454
- * Start panning the viewport
455
- * @param {PointerEvent} pEvent
456
- */
457
766
  _startPanning(pEvent)
458
767
  {
459
- // Deselect if clicking on empty space
460
768
  this._FlowView.deselectAll();
461
769
 
462
770
  this._State = INTERACTION_STATES.PANNING;
@@ -468,10 +776,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
468
776
  this._SVGElement.classList.add('panning');
469
777
  }
470
778
 
471
- /**
472
- * Handle panning
473
- * @param {PointerEvent} pEvent
474
- */
475
779
  _onPan(pEvent)
476
780
  {
477
781
  let tmpDX = pEvent.clientX - this._PanStartX;
@@ -483,10 +787,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
483
787
  this._FlowView.updateViewportTransform();
484
788
  }
485
789
 
486
- /**
487
- * End panning
488
- * @param {PointerEvent} pEvent
489
- */
490
790
  _endPanning(pEvent)
491
791
  {
492
792
  this._SVGElement.classList.remove('panning');
@@ -495,13 +795,9 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
495
795
 
496
796
  // ---- Connection Selection ----
497
797
 
498
- /**
499
- * Select a connection
500
- * @param {Element} pTarget
501
- */
502
798
  _selectConnection(pTarget)
503
799
  {
504
- let tmpConnectionHash = pTarget.getAttribute('data-connection-hash');
800
+ let tmpConnectionHash = this._getConnectionHash(pTarget);
505
801
  if (tmpConnectionHash)
506
802
  {
507
803
  this._FlowView.selectConnection(tmpConnectionHash);
@@ -510,25 +806,18 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
510
806
 
511
807
  // ---- Utilities ----
512
808
 
513
- /**
514
- * Get the element type from a target element (walks up to find data attributes)
515
- * @param {Element} pTarget
516
- * @returns {string} The element type
517
- */
518
809
  _getElementType(pTarget)
519
810
  {
520
811
  if (!pTarget) return 'background';
521
812
 
522
- // Check the element itself
523
- let tmpType = pTarget.getAttribute('data-element-type');
813
+ let tmpType = pTarget.getAttribute ? pTarget.getAttribute('data-element-type') : null;
524
814
  if (tmpType) return tmpType;
525
815
 
526
- // Walk up to find the closest element with a data attribute
527
816
  let tmpParent = pTarget.parentElement;
528
817
  let tmpDepth = 0;
529
818
  while (tmpParent && tmpDepth < 5)
530
819
  {
531
- tmpType = tmpParent.getAttribute('data-element-type');
820
+ tmpType = tmpParent.getAttribute ? tmpParent.getAttribute('data-element-type') : null;
532
821
  if (tmpType) return tmpType;
533
822
  tmpParent = tmpParent.parentElement;
534
823
  tmpDepth++;
@@ -537,23 +826,58 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
537
826
  return 'background';
538
827
  }
539
828
 
540
- /**
541
- * Get the node hash from a target element (walks up parents)
542
- * @param {Element} pTarget
543
- * @returns {string|null}
544
- */
545
829
  _getNodeHash(pTarget)
546
830
  {
547
831
  if (!pTarget) return null;
548
832
 
549
- let tmpHash = pTarget.getAttribute('data-node-hash');
833
+ let tmpHash = pTarget.getAttribute ? pTarget.getAttribute('data-node-hash') : null;
834
+ if (tmpHash) return tmpHash;
835
+
836
+ let tmpParent = pTarget.parentElement;
837
+ let tmpDepth = 0;
838
+ while (tmpParent && tmpDepth < 5)
839
+ {
840
+ tmpHash = tmpParent.getAttribute ? tmpParent.getAttribute('data-node-hash') : null;
841
+ if (tmpHash) return tmpHash;
842
+ tmpParent = tmpParent.parentElement;
843
+ tmpDepth++;
844
+ }
845
+
846
+ return null;
847
+ }
848
+
849
+ _getPanelHash(pTarget)
850
+ {
851
+ if (!pTarget) return null;
852
+
853
+ let tmpHash = pTarget.getAttribute ? pTarget.getAttribute('data-panel-hash') : null;
854
+ if (tmpHash) return tmpHash;
855
+
856
+ let tmpParent = pTarget.parentElement;
857
+ let tmpDepth = 0;
858
+ while (tmpParent && tmpDepth < 5)
859
+ {
860
+ tmpHash = tmpParent.getAttribute ? tmpParent.getAttribute('data-panel-hash') : null;
861
+ if (tmpHash) return tmpHash;
862
+ tmpParent = tmpParent.parentElement;
863
+ tmpDepth++;
864
+ }
865
+
866
+ return null;
867
+ }
868
+
869
+ _getConnectionHash(pTarget)
870
+ {
871
+ if (!pTarget) return null;
872
+
873
+ let tmpHash = pTarget.getAttribute ? pTarget.getAttribute('data-connection-hash') : null;
550
874
  if (tmpHash) return tmpHash;
551
875
 
552
876
  let tmpParent = pTarget.parentElement;
553
877
  let tmpDepth = 0;
554
878
  while (tmpParent && tmpDepth < 5)
555
879
  {
556
- tmpHash = tmpParent.getAttribute('data-node-hash');
880
+ tmpHash = tmpParent.getAttribute ? tmpParent.getAttribute('data-connection-hash') : null;
557
881
  if (tmpHash) return tmpHash;
558
882
  tmpParent = tmpParent.parentElement;
559
883
  tmpDepth++;