pict-section-flow 0.0.17 → 0.0.19
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/README.md +18 -18
- package/docs/Architecture.md +1 -1
- package/docs/Data_Model.md +2 -2
- package/docs/Getting_Started.md +5 -5
- package/docs/Implementation_Reference.md +6 -6
- package/docs/Layout_Persistence.md +3 -3
- package/docs/README.md +12 -12
- package/docs/Theme_Integration.md +150 -0
- package/docs/_cover.md +1 -1
- package/docs/_sidebar.md +7 -6
- package/docs/_version.json +7 -0
- package/docs/api/PictFlowCard.md +6 -6
- package/docs/api/PictFlowCardPropertiesPanel.md +2 -2
- package/docs/api/addConnection.md +4 -4
- package/docs/api/addNode.md +6 -6
- package/docs/api/autoLayout.md +2 -2
- package/docs/api/getFlowData.md +5 -5
- package/docs/api/marshalToView.md +3 -3
- package/docs/api/openPanel.md +2 -2
- package/docs/api/registerHandler.md +3 -3
- package/docs/api/registerNodeType.md +3 -3
- package/docs/api/removeConnection.md +5 -5
- package/docs/api/removeNode.md +6 -6
- package/docs/api/saveLayout.md +2 -2
- package/docs/api/screenToSVGCoords.md +2 -2
- package/docs/api/selectNode.md +3 -3
- package/docs/api/setTheme.md +2 -2
- package/docs/api/setZoom.md +3 -3
- package/docs/api/toggleFullscreen.md +2 -2
- package/docs/card-help/EACH.md +3 -3
- package/docs/card-help/FREAD.md +5 -5
- package/docs/card-help/FWRITE.md +5 -5
- package/docs/card-help/GET.md +2 -2
- package/docs/card-help/ITE.md +3 -3
- package/docs/card-help/LOG.md +4 -4
- package/docs/card-help/NOTE.md +1 -1
- package/docs/card-help/PREV.md +2 -2
- package/docs/card-help/SET.md +5 -5
- package/docs/card-help/SPKL.md +2 -2
- package/docs/card-help/STAT.md +3 -3
- package/docs/card-help/SW.md +4 -4
- package/docs/css/docuserve.css +277 -23
- package/docs/index.html +2 -2
- package/docs/retold-catalog.json +1 -1
- package/docs/retold-keyword-index.json +1 -1
- package/example_applications/simple_cards/css/flowexample.css +2 -2
- package/example_applications/simple_cards/source/card-help-content.js +12 -12
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +1 -1
- package/example_applications/simple_cards/source/sample-flows.js +410 -0
- package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +5 -5
- package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +5 -5
- package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +4 -4
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +141 -8
- package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +2 -2
- package/package.json +8 -7
- package/source/Pict-Section-Flow.js +26 -0
- package/source/providers/PictProvider-Flow-CSS.js +435 -61
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +9 -5
- package/source/providers/PictProvider-Flow-NodeTypes.js +10 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +7 -17
- package/source/providers/PictProvider-Flow-Theme.js +7 -7
- package/source/providers/edges/Edge-Bezier.js +41 -0
- package/source/providers/edges/Edge-Orthogonal.js +37 -0
- package/source/providers/edges/Edge-OrthogonalSnap.js +72 -0
- package/source/providers/edges/Edge-Perimeter-Linear.js +31 -0
- package/source/providers/edges/Edge-Perimeter-Orthogonal.js +39 -0
- package/source/providers/edges/Edge-Perimeter.js +48 -0
- package/source/providers/edges/Edge-PerimeterMath.js +92 -0
- package/source/providers/edges/Edge-Straight.js +24 -0
- package/source/providers/layouts/Layout-Circular.js +203 -0
- package/source/providers/layouts/Layout-Coerce.js +40 -0
- package/source/providers/layouts/Layout-Columnar.js +134 -0
- package/source/providers/layouts/Layout-Custom.js +27 -0
- package/source/providers/layouts/Layout-ForcedFromCenter.js +256 -0
- package/source/providers/layouts/Layout-Grid.js +134 -0
- package/source/providers/layouts/Layout-Layered.js +209 -0
- package/source/providers/layouts/Layout-Tabular.js +94 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +532 -28
- package/source/services/PictService-Flow-DataManager.js +12 -1
- package/source/services/PictService-Flow-InteractionManager.js +39 -42
- package/source/services/PictService-Flow-Layout.js +305 -121
- package/source/services/PictService-Flow-PortRenderer.js +108 -26
- package/source/services/PictService-Flow-RenderManager.js +41 -11
- package/source/views/PictView-Flow-FloatingToolbar.js +69 -61
- package/source/views/PictView-Flow-Node.js +47 -6
- package/source/views/PictView-Flow-PropertiesPanel.js +46 -53
- package/source/views/PictView-Flow-Toolbar.js +1065 -485
- package/source/views/PictView-Flow.js +455 -7
- package/test/Layout_tests.js +1400 -0
- package/test/PortRenderer_tests.js +11 -2
|
@@ -59,6 +59,7 @@ const _DefaultConfiguration =
|
|
|
59
59
|
EnableConnectionCreation: true,
|
|
60
60
|
EnableGridSnap: false,
|
|
61
61
|
GridSnapSize: 20,
|
|
62
|
+
EnableLayoutMenu: true,
|
|
62
63
|
|
|
63
64
|
MinZoom: 0.1,
|
|
64
65
|
MaxZoom: 5.0,
|
|
@@ -68,16 +69,29 @@ const _DefaultConfiguration =
|
|
|
68
69
|
DefaultNodeWidth: 180,
|
|
69
70
|
DefaultNodeHeight: 80,
|
|
70
71
|
|
|
72
|
+
// Layout-algorithm subsystem defaults
|
|
73
|
+
DefaultLayoutAlgorithm: 'Custom',
|
|
74
|
+
DefaultLayoutParameters: {},
|
|
75
|
+
DefaultLayoutAutoApply: false,
|
|
76
|
+
|
|
77
|
+
// Edge-theme subsystem defaults — null = inherit from active layout's
|
|
78
|
+
// `DefaultEdgeTheme` field, with hard fallback to 'Bezier'.
|
|
79
|
+
DefaultEdgeTheme: null,
|
|
80
|
+
DefaultEdgeThemeParameters: {},
|
|
81
|
+
|
|
71
82
|
CSS: false,
|
|
72
83
|
|
|
73
84
|
Templates:
|
|
74
85
|
[
|
|
75
86
|
{
|
|
76
87
|
Hash: 'Flow-PanelChrome-Template',
|
|
77
|
-
Template: /*html*/`<div class="pict-flow-panel" xmlns="http://www.w3.org/1999/xhtml"><div class="pict-flow-panel-titlebar" data-element-type="panel-titlebar" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-title-text">{~D:Record.Title~}</span><span class="pict-flow-panel-close-btn" data-element-type="panel-close" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-close-icon"></span></span></div><div class="pict-flow-panel-content" data-panel-hash="{~D:Record.Hash~}"><div class="pict-flow-panel-tab-pane active" data-tab="properties" data-panel-hash="{~D:Record.Hash~}"></div><div class="pict-flow-panel-tab-pane" data-tab="help" data-panel-hash="{~D:Record.Hash~}" style="display:none;"></div><div class="pict-flow-panel-tab-pane" data-tab="appearance" data-panel-hash="{~D:Record.Hash~}" style="display:none;"></div></div><div class="pict-flow-panel-resize-handle" data-element-type="panel-resize" data-panel-hash="{~D:Record.Hash~}"></div><div class="pict-flow-panel-tabbar" data-panel-hash="{~D:Record.Hash~}"><div class="pict-flow-panel-tab active" data-tab-target="properties" data-panel-hash="{~D:Record.Hash~}">Properties</div><div class="pict-flow-panel-tab" data-tab-target="help" data-panel-hash="{~D:Record.Hash~}" style="display:none;">Help</div><div class="pict-flow-panel-tab" data-tab-target="appearance" data-panel-hash="{~D:Record.Hash~}">Appearance</div></div></div>`
|
|
88
|
+
Template: /*html*/`<div class="pict-flow-panel" xmlns="http://www.w3.org/1999/xhtml"><div class="pict-flow-panel-titlebar" data-element-type="panel-titlebar" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-title-text">{~D:Record.Title~}</span><span class="pict-flow-panel-close-btn" data-element-type="panel-close" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-close-icon"></span></span></div><div class="pict-flow-panel-content" data-panel-hash="{~D:Record.Hash~}" onpointerdown="event.stopPropagation()" onwheel="event.stopPropagation()"><div class="pict-flow-panel-tab-pane active" data-tab="properties" data-panel-hash="{~D:Record.Hash~}"></div><div class="pict-flow-panel-tab-pane" data-tab="help" data-panel-hash="{~D:Record.Hash~}" style="display:none;"></div><div class="pict-flow-panel-tab-pane" data-tab="appearance" data-panel-hash="{~D:Record.Hash~}" style="display:none;"></div></div><div class="pict-flow-panel-resize-handle" data-element-type="panel-resize" data-panel-hash="{~D:Record.Hash~}"></div><div class="pict-flow-panel-tabbar" data-panel-hash="{~D:Record.Hash~}" onpointerdown="event.stopPropagation()" onwheel="event.stopPropagation()"><div class="pict-flow-panel-tab active" data-tab-target="properties" data-panel-hash="{~D:Record.Hash~}" onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._handlePanelTabClick(this, event)">Properties</div><div class="pict-flow-panel-tab" data-tab-target="help" data-panel-hash="{~D:Record.Hash~}" style="display:none;" onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._handlePanelTabClick(this, event)">Help</div><div class="pict-flow-panel-tab" data-tab-target="appearance" data-panel-hash="{~D:Record.Hash~}" onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._handlePanelTabClick(this, event)">Appearance</div></div></div>`
|
|
78
89
|
},
|
|
79
90
|
{
|
|
80
91
|
Hash: 'Flow-Container-Template',
|
|
92
|
+
// Inline pointer/wheel/contextmenu handlers route to the
|
|
93
|
+
// InteractionManager via the FlowView. Keyboard events stay
|
|
94
|
+
// document-level (no element-level inline equivalent).
|
|
81
95
|
Template: /*html*/`
|
|
82
96
|
<div class="pict-flow-container" id="Flow-Wrapper-{~D:Record.ViewIdentifier~}">
|
|
83
97
|
<div id="Flow-Toolbar-{~D:Record.ViewIdentifier~}"></div>
|
|
@@ -85,7 +99,13 @@ const _DefaultConfiguration =
|
|
|
85
99
|
<div class="pict-flow-svg-container" id="Flow-SVG-Container-{~D:Record.ViewIdentifier~}">
|
|
86
100
|
<svg class="pict-flow-svg"
|
|
87
101
|
id="Flow-SVG-{~D:Record.ViewIdentifier~}"
|
|
88
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
102
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
103
|
+
onpointerdown="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerDown(event)"
|
|
104
|
+
onpointermove="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerMove(event)"
|
|
105
|
+
onpointerup="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerUp(event)"
|
|
106
|
+
onpointerleave="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerUp(event)"
|
|
107
|
+
onwheel="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGWheel(event)"
|
|
108
|
+
oncontextmenu="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGContextMenu(event)">
|
|
89
109
|
<defs>
|
|
90
110
|
<pattern id="flow-grid-{~D:Record.ViewIdentifier~}"
|
|
91
111
|
width="20" height="20" patternUnits="userSpaceOnUse">
|
|
@@ -99,6 +119,8 @@ const _DefaultConfiguration =
|
|
|
99
119
|
<g class="pict-flow-viewport" id="Flow-Viewport-{~D:Record.ViewIdentifier~}">
|
|
100
120
|
<g class="pict-flow-connections-layer" id="Flow-Connections-{~D:Record.ViewIdentifier~}"></g>
|
|
101
121
|
<g class="pict-flow-nodes-layer" id="Flow-Nodes-{~D:Record.ViewIdentifier~}"></g>
|
|
122
|
+
<g class="pict-flow-endpoints-layer" id="Flow-Endpoints-{~D:Record.ViewIdentifier~}"></g>
|
|
123
|
+
<g class="pict-flow-port-hints-layer" id="Flow-PortHints-{~D:Record.ViewIdentifier~}"></g>
|
|
102
124
|
<g class="pict-flow-tethers-layer" id="Flow-Tethers-{~D:Record.ViewIdentifier~}"></g>
|
|
103
125
|
<g class="pict-flow-panels-layer" id="Flow-Panels-{~D:Record.ViewIdentifier~}"></g>
|
|
104
126
|
</g>
|
|
@@ -195,13 +217,24 @@ class PictViewFlow extends libPictView
|
|
|
195
217
|
SelectedNodeHash: null,
|
|
196
218
|
SelectedConnectionHash: null,
|
|
197
219
|
SelectedTetherHash: null
|
|
198
|
-
}
|
|
220
|
+
},
|
|
221
|
+
LayoutAlgorithm: this.options.DefaultLayoutAlgorithm || 'Custom',
|
|
222
|
+
LayoutParameters: JSON.parse(JSON.stringify(this.options.DefaultLayoutParameters || {})),
|
|
223
|
+
LayoutAutoApply: !!this.options.DefaultLayoutAutoApply,
|
|
224
|
+
EdgeTheme: this.options.DefaultEdgeTheme || null,
|
|
225
|
+
EdgeThemeParameters: JSON.parse(JSON.stringify(this.options.DefaultEdgeThemeParameters || {}))
|
|
199
226
|
};
|
|
200
227
|
|
|
228
|
+
// Re-entrancy guard for the auto-apply event handler
|
|
229
|
+
this._AutoApplyInProgress = false;
|
|
230
|
+
this._AutoApplyHandlerHashes = [];
|
|
231
|
+
|
|
201
232
|
this._SVGElement = null;
|
|
202
233
|
this._ViewportElement = null;
|
|
203
234
|
this._NodesLayer = null;
|
|
204
235
|
this._ConnectionsLayer = null;
|
|
236
|
+
this._EndpointsLayer = null;
|
|
237
|
+
this._PortHintsLayer = null;
|
|
205
238
|
this._TethersLayer = null;
|
|
206
239
|
this._PanelsLayer = null;
|
|
207
240
|
|
|
@@ -327,9 +360,65 @@ class PictViewFlow extends libPictView
|
|
|
327
360
|
// Instantiate all remaining services (skips Theme + Noise since already set)
|
|
328
361
|
this._instantiateServices();
|
|
329
362
|
|
|
363
|
+
// Subscribe to the host application's pict-provider-theme so the flow
|
|
364
|
+
// editor's marker arrowhead colors and shape overrides update when the
|
|
365
|
+
// host swaps light/dark or palette themes. CSS variables (--theme-*)
|
|
366
|
+
// re-resolve automatically — only the SVG <marker> defs (which inline
|
|
367
|
+
// fill colors at build time) and ConnectorShapesProvider state need a
|
|
368
|
+
// manual refresh.
|
|
369
|
+
this._subscribeToHostTheme();
|
|
370
|
+
|
|
330
371
|
return super.onBeforeInitialize();
|
|
331
372
|
}
|
|
332
373
|
|
|
374
|
+
/**
|
|
375
|
+
* If the host application has registered pict-provider-theme, subscribe
|
|
376
|
+
* to its onApply hook so we can refresh anything that doesn't pick up
|
|
377
|
+
* the new --theme-* CSS variables automatically (SVG marker fills,
|
|
378
|
+
* connector shape overrides). No-op when no host theme provider is
|
|
379
|
+
* available — the flow editor still functions with its built-in themes.
|
|
380
|
+
*/
|
|
381
|
+
_subscribeToHostTheme()
|
|
382
|
+
{
|
|
383
|
+
let tmpHostTheme = this._resolveHostThemeProvider();
|
|
384
|
+
if (!tmpHostTheme || typeof tmpHostTheme.onApply !== 'function') return;
|
|
385
|
+
|
|
386
|
+
this._HostThemeUnsubscribe = tmpHostTheme.onApply(() =>
|
|
387
|
+
{
|
|
388
|
+
if (this._CSSProvider) this._CSSProvider.registerCSS();
|
|
389
|
+
this._reinjectMarkerDefs();
|
|
390
|
+
if (this.initialRenderComplete) this.renderFlow();
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Locate the host's pict-provider-theme instance. It is normally
|
|
396
|
+
* registered under a stable service hash by the host app; we check
|
|
397
|
+
* common locations so consumers don't have to wire it manually.
|
|
398
|
+
* @returns {Object|null}
|
|
399
|
+
*/
|
|
400
|
+
_resolveHostThemeProvider()
|
|
401
|
+
{
|
|
402
|
+
let tmpPict = this.pict;
|
|
403
|
+
if (!tmpPict) return null;
|
|
404
|
+
if (tmpPict.providers)
|
|
405
|
+
{
|
|
406
|
+
let tmpKeys = Object.keys(tmpPict.providers);
|
|
407
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
408
|
+
{
|
|
409
|
+
let tmpProvider = tmpPict.providers[tmpKeys[i]];
|
|
410
|
+
if (tmpProvider
|
|
411
|
+
&& typeof tmpProvider.onApply === 'function'
|
|
412
|
+
&& typeof tmpProvider.applyTheme === 'function'
|
|
413
|
+
&& typeof tmpProvider.listThemes === 'function')
|
|
414
|
+
{
|
|
415
|
+
return tmpProvider;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
333
422
|
onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
|
|
334
423
|
{
|
|
335
424
|
if (!this.initialRenderComplete)
|
|
@@ -371,6 +460,18 @@ class PictViewFlow extends libPictView
|
|
|
371
460
|
this._ConnectionsLayer = tmpConnectionsElements[0];
|
|
372
461
|
}
|
|
373
462
|
|
|
463
|
+
let tmpEndpointsElements = this.pict.ContentAssignment.getElement(`#Flow-Endpoints-${tmpViewIdentifier}`);
|
|
464
|
+
if (tmpEndpointsElements.length > 0)
|
|
465
|
+
{
|
|
466
|
+
this._EndpointsLayer = tmpEndpointsElements[0];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let tmpPortHintsElements = this.pict.ContentAssignment.getElement(`#Flow-PortHints-${tmpViewIdentifier}`);
|
|
470
|
+
if (tmpPortHintsElements.length > 0)
|
|
471
|
+
{
|
|
472
|
+
this._PortHintsLayer = tmpPortHintsElements[0];
|
|
473
|
+
}
|
|
474
|
+
|
|
374
475
|
let tmpTethersElements = this.pict.ContentAssignment.getElement(`#Flow-Tethers-${tmpViewIdentifier}`);
|
|
375
476
|
if (tmpTethersElements.length > 0)
|
|
376
477
|
{
|
|
@@ -451,6 +552,9 @@ class PictViewFlow extends libPictView
|
|
|
451
552
|
// Bind interaction events
|
|
452
553
|
this._InteractionManager.initialize(this._SVGElement, this._ViewportElement);
|
|
453
554
|
|
|
555
|
+
// Wire the auto-apply handler to structural-change events
|
|
556
|
+
this._subscribeAutoApplyHandlers();
|
|
557
|
+
|
|
454
558
|
// Load initial flow data if an address is configured
|
|
455
559
|
if (this.options.FlowDataAddress)
|
|
456
560
|
{
|
|
@@ -535,13 +639,104 @@ class PictViewFlow extends libPictView
|
|
|
535
639
|
}
|
|
536
640
|
|
|
537
641
|
/**
|
|
538
|
-
* Apply auto-layout to all nodes
|
|
642
|
+
* Apply auto-layout to all nodes.
|
|
643
|
+
*
|
|
644
|
+
* Backwards-compatible signature: when called with no arguments,
|
|
645
|
+
* dispatches to the Layered algorithm with its default parameters
|
|
646
|
+
* (matching the pre-subsystem behavior).
|
|
647
|
+
*
|
|
648
|
+
* If `pAlgorithmName` is provided it is used directly; otherwise the
|
|
649
|
+
* configured `_FlowData.LayoutAlgorithm` is used (unless that value
|
|
650
|
+
* is 'Custom' or unset, in which case it falls back to 'Layered').
|
|
651
|
+
*
|
|
652
|
+
* @param {string} [pAlgorithmName]
|
|
653
|
+
* @param {Object} [pParameters]
|
|
539
654
|
*/
|
|
540
|
-
autoLayout()
|
|
655
|
+
autoLayout(pAlgorithmName, pParameters)
|
|
541
656
|
{
|
|
542
|
-
if (this._LayoutService)
|
|
657
|
+
if (!this._LayoutService) return;
|
|
658
|
+
|
|
659
|
+
let tmpName;
|
|
660
|
+
let tmpParams;
|
|
661
|
+
if (typeof pAlgorithmName === 'string' && pAlgorithmName !== '')
|
|
543
662
|
{
|
|
544
|
-
|
|
663
|
+
tmpName = pAlgorithmName;
|
|
664
|
+
tmpParams = pParameters || {};
|
|
665
|
+
}
|
|
666
|
+
else
|
|
667
|
+
{
|
|
668
|
+
let tmpConfigured = this._FlowData.LayoutAlgorithm;
|
|
669
|
+
tmpName = (typeof tmpConfigured === 'string' && tmpConfigured !== '' && tmpConfigured !== 'Custom') ? tmpConfigured : 'Layered';
|
|
670
|
+
tmpParams = (tmpName === this._FlowData.LayoutAlgorithm) ? (this._FlowData.LayoutParameters || {}) : {};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
this._LayoutService.autoLayout(this._FlowData.Nodes, this._FlowData.Connections, tmpName, tmpParams);
|
|
674
|
+
this.renderFlow();
|
|
675
|
+
this.marshalFromView();
|
|
676
|
+
|
|
677
|
+
if (this._EventHandlerProvider)
|
|
678
|
+
{
|
|
679
|
+
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Apply the currently configured layout algorithm to the flow, then
|
|
685
|
+
* call the active edge theme's `AdjustLayout` callback (if any) to
|
|
686
|
+
* let the theme adjust node positions for cleaner edge rendering
|
|
687
|
+
* (e.g. snap to grid, optimize for crossing minimization).
|
|
688
|
+
*
|
|
689
|
+
* Reads `_FlowData.LayoutAlgorithm` / `LayoutParameters` /
|
|
690
|
+
* `EdgeTheme` / `EdgeThemeParameters`. No-op when algorithm is
|
|
691
|
+
* 'Custom' — but the edge theme's adjustment still runs (so themes
|
|
692
|
+
* like OrthogonalSnap can snap hand-placed nodes too).
|
|
693
|
+
*/
|
|
694
|
+
applyCurrentLayout()
|
|
695
|
+
{
|
|
696
|
+
if (!this._LayoutService) return;
|
|
697
|
+
let tmpAlgorithm = this._FlowData.LayoutAlgorithm || 'Custom';
|
|
698
|
+
|
|
699
|
+
this._AutoApplyInProgress = true;
|
|
700
|
+
try
|
|
701
|
+
{
|
|
702
|
+
if (tmpAlgorithm !== 'Custom')
|
|
703
|
+
{
|
|
704
|
+
this._LayoutService.applyLayout(
|
|
705
|
+
this._FlowData.Nodes,
|
|
706
|
+
this._FlowData.Connections,
|
|
707
|
+
tmpAlgorithm,
|
|
708
|
+
this._FlowData.LayoutParameters || {}
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Edge-theme post-processing (placement adjustment).
|
|
713
|
+
let tmpEdgeTheme = this._LayoutService.resolveActiveEdgeTheme({});
|
|
714
|
+
if (tmpEdgeTheme && typeof tmpEdgeTheme.AdjustLayout === 'function')
|
|
715
|
+
{
|
|
716
|
+
try
|
|
717
|
+
{
|
|
718
|
+
let tmpEdgeParams = this._LayoutService.getMergedEdgeThemeParameters(
|
|
719
|
+
tmpEdgeTheme.Name,
|
|
720
|
+
this._FlowData.EdgeThemeParameters || {}
|
|
721
|
+
);
|
|
722
|
+
tmpEdgeTheme.AdjustLayout(this._FlowData.Nodes, this._FlowData.Connections, tmpEdgeParams);
|
|
723
|
+
}
|
|
724
|
+
catch (pError)
|
|
725
|
+
{
|
|
726
|
+
this.log.warn(`PictSectionFlow edge theme '${tmpEdgeTheme.Name}' AdjustLayout threw: ${pError.message}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
let tmpHadAdjust = !!(tmpEdgeTheme && typeof tmpEdgeTheme.AdjustLayout === 'function');
|
|
731
|
+
let tmpDidLayout = (tmpAlgorithm !== 'Custom');
|
|
732
|
+
|
|
733
|
+
if (!tmpDidLayout && !tmpHadAdjust)
|
|
734
|
+
{
|
|
735
|
+
// Custom + theme-without-AdjustLayout → no positions changed.
|
|
736
|
+
// Don't render or fire onFlowChanged.
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
|
|
545
740
|
this.renderFlow();
|
|
546
741
|
this.marshalFromView();
|
|
547
742
|
|
|
@@ -550,6 +745,143 @@ class PictViewFlow extends libPictView
|
|
|
550
745
|
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
551
746
|
}
|
|
552
747
|
}
|
|
748
|
+
finally
|
|
749
|
+
{
|
|
750
|
+
this._AutoApplyInProgress = false;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Set the active edge theme (and optionally its parameters). Re-runs
|
|
756
|
+
* the current layout so any `AdjustLayout` callback takes effect.
|
|
757
|
+
*
|
|
758
|
+
* @param {string|null} pThemeName - null clears the override (theme
|
|
759
|
+
* then resolves via the active layout's `DefaultEdgeTheme`).
|
|
760
|
+
* @param {Object} [pParameters]
|
|
761
|
+
*/
|
|
762
|
+
setEdgeTheme(pThemeName, pParameters)
|
|
763
|
+
{
|
|
764
|
+
if (!this._LayoutService) return;
|
|
765
|
+
if (pThemeName != null && typeof pThemeName !== 'string') return;
|
|
766
|
+
|
|
767
|
+
if (pThemeName)
|
|
768
|
+
{
|
|
769
|
+
let tmpTheme = this._LayoutService.getEdgeTheme(pThemeName);
|
|
770
|
+
if (!tmpTheme)
|
|
771
|
+
{
|
|
772
|
+
this.log.warn(`PictSectionFlow setEdgeTheme: unknown theme '${pThemeName}'`);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
this._FlowData.EdgeTheme = pThemeName;
|
|
776
|
+
this._FlowData.EdgeThemeParameters = Object.assign({}, tmpTheme.DefaultParameters || {}, pParameters || {});
|
|
777
|
+
}
|
|
778
|
+
else
|
|
779
|
+
{
|
|
780
|
+
this._FlowData.EdgeTheme = null;
|
|
781
|
+
this._FlowData.EdgeThemeParameters = pParameters ? Object.assign({}, pParameters) : {};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
this.applyCurrentLayout();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Read the currently configured edge theme settings.
|
|
789
|
+
* Returns the *resolved* active theme (factoring in fallback to the
|
|
790
|
+
* active layout's `DefaultEdgeTheme`) plus the explicit override
|
|
791
|
+
* (if any) for round-tripping into UI.
|
|
792
|
+
*
|
|
793
|
+
* @returns {{ Theme: string, Override: string|null, Parameters: Object }}
|
|
794
|
+
*/
|
|
795
|
+
getEdgeTheme()
|
|
796
|
+
{
|
|
797
|
+
let tmpResolved = this._LayoutService ? this._LayoutService.resolveActiveEdgeTheme({}) : null;
|
|
798
|
+
return {
|
|
799
|
+
Theme: tmpResolved ? tmpResolved.Name : null,
|
|
800
|
+
Override: this._FlowData.EdgeTheme || null,
|
|
801
|
+
Parameters: JSON.parse(JSON.stringify(this._FlowData.EdgeThemeParameters || {}))
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Set the configured layout algorithm and (optionally) its parameters
|
|
807
|
+
* and auto-apply flag. Persists to `_FlowData` and applies the layout
|
|
808
|
+
* immediately when the algorithm is non-Custom.
|
|
809
|
+
*
|
|
810
|
+
* @param {string} pAlgorithmName
|
|
811
|
+
* @param {Object} [pParameters] - merged over the algorithm's defaults
|
|
812
|
+
* @param {boolean} [pAutoApply] - if provided, also sets `LayoutAutoApply`
|
|
813
|
+
*/
|
|
814
|
+
setLayoutAlgorithm(pAlgorithmName, pParameters, pAutoApply)
|
|
815
|
+
{
|
|
816
|
+
if (!this._LayoutService) return;
|
|
817
|
+
if (typeof pAlgorithmName !== 'string' || pAlgorithmName === '') return;
|
|
818
|
+
|
|
819
|
+
let tmpAlgo = this._LayoutService.getAlgorithm(pAlgorithmName);
|
|
820
|
+
if (!tmpAlgo)
|
|
821
|
+
{
|
|
822
|
+
this.log.warn(`PictSectionFlow setLayoutAlgorithm: unknown algorithm '${pAlgorithmName}'`);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
this._FlowData.LayoutAlgorithm = pAlgorithmName;
|
|
827
|
+
this._FlowData.LayoutParameters = Object.assign({}, tmpAlgo.DefaultParameters || {}, pParameters || {});
|
|
828
|
+
if (typeof pAutoApply === 'boolean')
|
|
829
|
+
{
|
|
830
|
+
this._FlowData.LayoutAutoApply = pAutoApply;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Always run applyCurrentLayout so the new layout's `DefaultEdgeTheme`
|
|
834
|
+
// takes effect on connection rendering — even when switching to
|
|
835
|
+
// 'Custom' (which doesn't reposition nodes but does re-render edges).
|
|
836
|
+
this.applyCurrentLayout();
|
|
837
|
+
this.renderFlow();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Toggle whether the configured layout re-applies on structural changes.
|
|
842
|
+
* @param {boolean} pAutoApply
|
|
843
|
+
*/
|
|
844
|
+
setLayoutAutoApply(pAutoApply)
|
|
845
|
+
{
|
|
846
|
+
this._FlowData.LayoutAutoApply = !!pAutoApply;
|
|
847
|
+
this.marshalFromView();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Read the currently configured layout algorithm settings.
|
|
852
|
+
* @returns {{ Algorithm: string, Parameters: Object, AutoApply: boolean }}
|
|
853
|
+
*/
|
|
854
|
+
getLayoutAlgorithm()
|
|
855
|
+
{
|
|
856
|
+
return {
|
|
857
|
+
Algorithm: this._FlowData.LayoutAlgorithm || 'Custom',
|
|
858
|
+
Parameters: JSON.parse(JSON.stringify(this._FlowData.LayoutParameters || {})),
|
|
859
|
+
AutoApply: !!this._FlowData.LayoutAutoApply
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Subscribe the auto-apply handler to structural-change events. Idempotent.
|
|
865
|
+
* Only structural events trigger re-layout — `onNodeMoved` is intentionally
|
|
866
|
+
* NOT subscribed so user drags are never clobbered until the next add/remove.
|
|
867
|
+
*/
|
|
868
|
+
_subscribeAutoApplyHandlers()
|
|
869
|
+
{
|
|
870
|
+
if (!this._EventHandlerProvider) return;
|
|
871
|
+
if (this._AutoApplyHandlerHashes.length > 0) return;
|
|
872
|
+
|
|
873
|
+
let tmpEvents = ['onNodeAdded', 'onNodeRemoved', 'onConnectionCreated', 'onConnectionRemoved'];
|
|
874
|
+
for (let i = 0; i < tmpEvents.length; i++)
|
|
875
|
+
{
|
|
876
|
+
let tmpHash = this._EventHandlerProvider.registerHandler(tmpEvents[i], () =>
|
|
877
|
+
{
|
|
878
|
+
if (this._AutoApplyInProgress) return;
|
|
879
|
+
if (!this._FlowData.LayoutAutoApply) return;
|
|
880
|
+
if (!this._FlowData.LayoutAlgorithm || this._FlowData.LayoutAlgorithm === 'Custom') return;
|
|
881
|
+
this.applyCurrentLayout();
|
|
882
|
+
});
|
|
883
|
+
if (tmpHash) this._AutoApplyHandlerHashes.push({ Event: tmpEvents[i], Hash: tmpHash });
|
|
884
|
+
}
|
|
553
885
|
}
|
|
554
886
|
|
|
555
887
|
/**
|
|
@@ -881,6 +1213,122 @@ class PictViewFlow extends libPictView
|
|
|
881
1213
|
{
|
|
882
1214
|
return this._PanelManager.updatePanelPosition(pPanelHash, pX, pY);
|
|
883
1215
|
}
|
|
1216
|
+
|
|
1217
|
+
// ── Inline-handler bridges (called from template onclick/oninput attrs)
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Handle a click on a panel tab button. Stops propagation so the
|
|
1221
|
+
* underlying SVG doesn't see it, then delegates to the properties
|
|
1222
|
+
* panel view for the actual visibility toggle.
|
|
1223
|
+
*
|
|
1224
|
+
* @param {Element} pTabElement
|
|
1225
|
+
* @param {Event} pEvent
|
|
1226
|
+
*/
|
|
1227
|
+
_handlePanelTabClick(pTabElement, pEvent)
|
|
1228
|
+
{
|
|
1229
|
+
if (pEvent && typeof pEvent.stopPropagation === 'function')
|
|
1230
|
+
{
|
|
1231
|
+
pEvent.stopPropagation();
|
|
1232
|
+
}
|
|
1233
|
+
if (this._PropertiesPanelView)
|
|
1234
|
+
{
|
|
1235
|
+
this._PropertiesPanelView.switchPanelTab(pTabElement);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Apply a node property change driven from the appearance editor's
|
|
1241
|
+
* inline `oninput` handler. Stops propagation, then delegates to the
|
|
1242
|
+
* properties panel view.
|
|
1243
|
+
*
|
|
1244
|
+
* @param {string} pNodeHash
|
|
1245
|
+
* @param {string} pPropPath
|
|
1246
|
+
* @param {string} pValue
|
|
1247
|
+
* @param {string} pInputType - 'text' | 'number' | 'color'
|
|
1248
|
+
* @param {Event} [pEvent]
|
|
1249
|
+
*/
|
|
1250
|
+
_applyNodePropChange(pNodeHash, pPropPath, pValue, pInputType, pEvent)
|
|
1251
|
+
{
|
|
1252
|
+
if (pEvent && typeof pEvent.stopPropagation === 'function')
|
|
1253
|
+
{
|
|
1254
|
+
pEvent.stopPropagation();
|
|
1255
|
+
}
|
|
1256
|
+
if (this._PropertiesPanelView)
|
|
1257
|
+
{
|
|
1258
|
+
this._PropertiesPanelView._applyNodePropChange(pNodeHash, pPropPath, pValue, pInputType);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Inline-handler bridge — light up port-hint paths matching the given
|
|
1264
|
+
* port (and optionally node) hash. Called from inline `onmouseenter`
|
|
1265
|
+
* on port badges; see PictService-Flow-PortRenderer._wirePortHintHover.
|
|
1266
|
+
*
|
|
1267
|
+
* @param {string|null} pPortHash
|
|
1268
|
+
* @param {string|null} pNodeHash
|
|
1269
|
+
*/
|
|
1270
|
+
_activatePortHints(pPortHash, pNodeHash)
|
|
1271
|
+
{
|
|
1272
|
+
this._togglePortHints(pPortHash, pNodeHash, true);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Inline-handler bridge — clear port-hint highlights.
|
|
1277
|
+
*
|
|
1278
|
+
* @param {string|null} pPortHash
|
|
1279
|
+
* @param {string|null} pNodeHash
|
|
1280
|
+
*/
|
|
1281
|
+
_deactivatePortHints(pPortHash, pNodeHash)
|
|
1282
|
+
{
|
|
1283
|
+
this._togglePortHints(pPortHash, pNodeHash, false);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
_togglePortHints(pPortHash, pNodeHash, pActive)
|
|
1287
|
+
{
|
|
1288
|
+
let tmpScope = this._SVGElement || document;
|
|
1289
|
+
let tmpSelector = pPortHash
|
|
1290
|
+
? '.pict-flow-port-hint[data-port-hash="' + pPortHash + '"]'
|
|
1291
|
+
: '.pict-flow-port-hint[data-node-hash="' + pNodeHash + '"]';
|
|
1292
|
+
let tmpHints = tmpScope.querySelectorAll(tmpSelector);
|
|
1293
|
+
for (let i = 0; i < tmpHints.length; i++)
|
|
1294
|
+
{
|
|
1295
|
+
if (pActive)
|
|
1296
|
+
{
|
|
1297
|
+
tmpHints[i].setAttribute('data-active', 'true');
|
|
1298
|
+
}
|
|
1299
|
+
else
|
|
1300
|
+
{
|
|
1301
|
+
tmpHints[i].removeAttribute('data-active');
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// ── SVG inline pointer/wheel bridges ─────────────────────────────────
|
|
1307
|
+
|
|
1308
|
+
_handleSVGPointerDown(pEvent)
|
|
1309
|
+
{
|
|
1310
|
+
if (this._InteractionManager) this._InteractionManager._onPointerDown(pEvent);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
_handleSVGPointerMove(pEvent)
|
|
1314
|
+
{
|
|
1315
|
+
if (this._InteractionManager) this._InteractionManager._onPointerMove(pEvent);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
_handleSVGPointerUp(pEvent)
|
|
1319
|
+
{
|
|
1320
|
+
if (this._InteractionManager) this._InteractionManager._onPointerUp(pEvent);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
_handleSVGWheel(pEvent)
|
|
1324
|
+
{
|
|
1325
|
+
if (this._InteractionManager) this._InteractionManager._onWheel(pEvent);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
_handleSVGContextMenu(pEvent)
|
|
1329
|
+
{
|
|
1330
|
+
if (this._InteractionManager) this._InteractionManager.handleContextMenu(pEvent);
|
|
1331
|
+
}
|
|
884
1332
|
}
|
|
885
1333
|
|
|
886
1334
|
module.exports = PictViewFlow;
|