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
|
@@ -43,6 +43,12 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
|
|
|
43
43
|
let tmpPortLabelsOutside = (pNodeTypeConfig && pNodeTypeConfig.PortLabelsOutside);
|
|
44
44
|
let tmpGeometryProvider = this._FlowView._GeometryProvider;
|
|
45
45
|
|
|
46
|
+
// Connected ports get their colored dot drawn by the connection
|
|
47
|
+
// renderer at the actual line attachment (which may have been
|
|
48
|
+
// reshaped by an edge theme like Perimeter). Unconnected ports
|
|
49
|
+
// still draw their dot at the static card-defined position.
|
|
50
|
+
let tmpConnectedPortHashes = this._collectConnectedPortHashes(pNodeData.Hash);
|
|
51
|
+
|
|
46
52
|
// Group ports by their Side value (supports all 12 positions)
|
|
47
53
|
let tmpPortsBySide = {};
|
|
48
54
|
for (let i = 0; i < pNodeData.Ports.length; i++)
|
|
@@ -167,6 +173,16 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
|
|
|
167
173
|
+ ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + (tmpBadgeY + tmpBadgeHeight);
|
|
168
174
|
}
|
|
169
175
|
|
|
176
|
+
// Wrap the badge elements in a hoverable group so we can
|
|
177
|
+
// listen for mouseenter/mouseleave on the badge as a
|
|
178
|
+
// whole. Tagged with data-port-hash + data-node-hash so
|
|
179
|
+
// the hover handler can surface this port's hint paths.
|
|
180
|
+
let tmpBadgeGroup = this._FlowView._SVGHelperProvider.createSVGElement('g');
|
|
181
|
+
tmpBadgeGroup.setAttribute('class', 'pict-flow-port-badge');
|
|
182
|
+
tmpBadgeGroup.setAttribute('data-port-hash', tmpPort.Hash);
|
|
183
|
+
tmpBadgeGroup.setAttribute('data-node-hash', pNodeData.Hash);
|
|
184
|
+
this._wirePortHintHover(tmpBadgeGroup, tmpPort.Hash, null);
|
|
185
|
+
|
|
170
186
|
// Background rect (cream, no stroke — border drawn separately)
|
|
171
187
|
let tmpBgRect = this._FlowView._SVGHelperProvider.createSVGElement('rect');
|
|
172
188
|
tmpBgRect.setAttribute('class', 'pict-flow-port-label-bg');
|
|
@@ -175,7 +191,7 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
|
|
|
175
191
|
tmpBgRect.setAttribute('width', String(tmpBadgeWidth));
|
|
176
192
|
tmpBgRect.setAttribute('height', String(tmpBadgeHeight));
|
|
177
193
|
tmpBgRect.setAttribute('fill', 'var(--pf-port-label-bg, rgba(255, 253, 240, 0.5))');
|
|
178
|
-
|
|
194
|
+
tmpBadgeGroup.appendChild(tmpBgRect);
|
|
179
195
|
|
|
180
196
|
// 3-sided border path (open on the edge-facing side)
|
|
181
197
|
let tmpBorderPathEl = this._FlowView._SVGHelperProvider.createSVGElement('path');
|
|
@@ -184,7 +200,7 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
|
|
|
184
200
|
tmpBorderPathEl.setAttribute('fill', 'none');
|
|
185
201
|
tmpBorderPathEl.setAttribute('stroke', tmpBorderColor);
|
|
186
202
|
tmpBorderPathEl.setAttribute('stroke-width', '0.75');
|
|
187
|
-
|
|
203
|
+
tmpBadgeGroup.appendChild(tmpBorderPathEl);
|
|
188
204
|
|
|
189
205
|
// Colored stripe on the inner side
|
|
190
206
|
let tmpStripe = this._FlowView._SVGHelperProvider.createSVGElement('rect');
|
|
@@ -194,7 +210,9 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
|
|
|
194
210
|
tmpStripe.setAttribute('width', String(tmpStripeW));
|
|
195
211
|
tmpStripe.setAttribute('height', String(tmpStripeH));
|
|
196
212
|
tmpStripe.setAttribute('fill', tmpBorderColor);
|
|
197
|
-
|
|
213
|
+
tmpBadgeGroup.appendChild(tmpStripe);
|
|
214
|
+
|
|
215
|
+
pGroup.appendChild(tmpBadgeGroup);
|
|
198
216
|
|
|
199
217
|
// Text label — appended after circle for z-order
|
|
200
218
|
tmpLabelElement = this._FlowView._SVGHelperProvider.createSVGElement('text');
|
|
@@ -207,35 +225,41 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
|
|
|
207
225
|
tmpLabelElement.setAttribute('dominant-baseline', 'central');
|
|
208
226
|
}
|
|
209
227
|
|
|
210
|
-
// Port circle (rendered on top of badge background)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
228
|
+
// Port circle (rendered on top of badge background) —
|
|
229
|
+
// only for unconnected ports. Connected ports get their
|
|
230
|
+
// dot painted by the connection renderer at the actual
|
|
231
|
+
// attachment point (so it follows edge-theme overrides).
|
|
232
|
+
if (!tmpConnectedPortHashes[tmpPort.Hash])
|
|
214
233
|
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
tmpCircle = this._FlowView._SVGHelperProvider.createSVGElement('circle');
|
|
220
|
-
let tmpPortClass = `pict-flow-port ${tmpPort.Direction}`;
|
|
221
|
-
if (tmpPort.PortType)
|
|
234
|
+
let tmpShapeProvider = this._FlowView._ConnectorShapesProvider;
|
|
235
|
+
let tmpCircle;
|
|
236
|
+
if (tmpShapeProvider)
|
|
222
237
|
{
|
|
223
|
-
|
|
238
|
+
tmpCircle = tmpShapeProvider.createPortElement(tmpPort, tmpPosition, pNodeData.Hash);
|
|
224
239
|
}
|
|
225
|
-
|
|
226
|
-
tmpCircle.setAttribute('cx', String(tmpPosition.x));
|
|
227
|
-
tmpCircle.setAttribute('cy', String(tmpPosition.y));
|
|
228
|
-
tmpCircle.setAttribute('r', '5');
|
|
229
|
-
tmpCircle.setAttribute('data-port-hash', tmpPort.Hash);
|
|
230
|
-
tmpCircle.setAttribute('data-node-hash', pNodeData.Hash);
|
|
231
|
-
tmpCircle.setAttribute('data-port-direction', tmpPort.Direction);
|
|
232
|
-
if (tmpPort.PortType)
|
|
240
|
+
else
|
|
233
241
|
{
|
|
234
|
-
tmpCircle.
|
|
242
|
+
tmpCircle = this._FlowView._SVGHelperProvider.createSVGElement('circle');
|
|
243
|
+
let tmpPortClass = `pict-flow-port ${tmpPort.Direction}`;
|
|
244
|
+
if (tmpPort.PortType)
|
|
245
|
+
{
|
|
246
|
+
tmpPortClass += ` port-type-${tmpPort.PortType}`;
|
|
247
|
+
}
|
|
248
|
+
tmpCircle.setAttribute('class', tmpPortClass);
|
|
249
|
+
tmpCircle.setAttribute('cx', String(tmpPosition.x));
|
|
250
|
+
tmpCircle.setAttribute('cy', String(tmpPosition.y));
|
|
251
|
+
tmpCircle.setAttribute('r', '5');
|
|
252
|
+
tmpCircle.setAttribute('data-port-hash', tmpPort.Hash);
|
|
253
|
+
tmpCircle.setAttribute('data-node-hash', pNodeData.Hash);
|
|
254
|
+
tmpCircle.setAttribute('data-port-direction', tmpPort.Direction);
|
|
255
|
+
if (tmpPort.PortType)
|
|
256
|
+
{
|
|
257
|
+
tmpCircle.setAttribute('data-port-type', tmpPort.PortType);
|
|
258
|
+
}
|
|
259
|
+
tmpCircle.setAttribute('data-element-type', 'port');
|
|
235
260
|
}
|
|
236
|
-
|
|
261
|
+
pGroup.appendChild(tmpCircle);
|
|
237
262
|
}
|
|
238
|
-
pGroup.appendChild(tmpCircle);
|
|
239
263
|
|
|
240
264
|
// Port label text (on top of everything)
|
|
241
265
|
if (tmpLabelElement)
|
|
@@ -246,6 +270,64 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
|
|
|
246
270
|
}
|
|
247
271
|
}
|
|
248
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Wire mouseenter/mouseleave on a hoverable element so it activates
|
|
275
|
+
* port-hint paths in the port-hints layer. When `pPortHash` is set,
|
|
276
|
+
* only hints for that port light up; when `pNodeHash` is set (and
|
|
277
|
+
* port hash is null), every hint on that node lights up. Static —
|
|
278
|
+
* no closure-state dependency on the node/port objects themselves.
|
|
279
|
+
*
|
|
280
|
+
* @param {SVGElement} pHoverEl
|
|
281
|
+
* @param {string|null} pPortHash
|
|
282
|
+
* @param {string|null} pNodeHash
|
|
283
|
+
*/
|
|
284
|
+
_wirePortHintHover(pHoverEl, pPortHash, pNodeHash)
|
|
285
|
+
{
|
|
286
|
+
if (!pHoverEl || typeof pHoverEl.setAttribute !== 'function') return;
|
|
287
|
+
if (!this._FlowView || !this._FlowView.options) return;
|
|
288
|
+
|
|
289
|
+
// Inline attribute handlers — survive the connection-layer rebuild
|
|
290
|
+
// without leaving stale listeners on detached DOM nodes.
|
|
291
|
+
let tmpFlowViewIdentifier = this._FlowView.options.ViewIdentifier;
|
|
292
|
+
let tmpHashArg = pPortHash ? ('"' + pPortHash + '", null') : ('null, "' + pNodeHash + '"');
|
|
293
|
+
pHoverEl.setAttribute('onmouseenter',
|
|
294
|
+
"_Pict.views['" + tmpFlowViewIdentifier + "']._activatePortHints(" + tmpHashArg + ")");
|
|
295
|
+
pHoverEl.setAttribute('onmouseleave',
|
|
296
|
+
"_Pict.views['" + tmpFlowViewIdentifier + "']._deactivatePortHints(" + tmpHashArg + ")");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Build a hash-set of port hashes on the given node that participate
|
|
301
|
+
* in any connection (either as source or target). Used to suppress
|
|
302
|
+
* the static port circle so the connection renderer can paint the
|
|
303
|
+
* dot at the resolved attachment point instead.
|
|
304
|
+
*
|
|
305
|
+
* @param {string} pNodeHash
|
|
306
|
+
* @returns {Object} keyed by port hash → true
|
|
307
|
+
*/
|
|
308
|
+
_collectConnectedPortHashes(pNodeHash)
|
|
309
|
+
{
|
|
310
|
+
let tmpResult = {};
|
|
311
|
+
if (!this._FlowView || !this._FlowView._FlowData || !Array.isArray(this._FlowView._FlowData.Connections))
|
|
312
|
+
{
|
|
313
|
+
return tmpResult;
|
|
314
|
+
}
|
|
315
|
+
let tmpConns = this._FlowView._FlowData.Connections;
|
|
316
|
+
for (let i = 0; i < tmpConns.length; i++)
|
|
317
|
+
{
|
|
318
|
+
let tmpConn = tmpConns[i];
|
|
319
|
+
if (tmpConn.SourceNodeHash === pNodeHash && tmpConn.SourcePortHash)
|
|
320
|
+
{
|
|
321
|
+
tmpResult[tmpConn.SourcePortHash] = true;
|
|
322
|
+
}
|
|
323
|
+
if (tmpConn.TargetNodeHash === pNodeHash && tmpConn.TargetPortHash)
|
|
324
|
+
{
|
|
325
|
+
tmpResult[tmpConn.TargetPortHash] = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return tmpResult;
|
|
329
|
+
}
|
|
330
|
+
|
|
249
331
|
/**
|
|
250
332
|
* Calculate port position relative to node origin.
|
|
251
333
|
*
|
|
@@ -37,6 +37,14 @@ class PictServiceFlowRenderManager extends libFableServiceProviderBase
|
|
|
37
37
|
{
|
|
38
38
|
this._FlowView._ConnectionsLayer.removeChild(this._FlowView._ConnectionsLayer.firstChild);
|
|
39
39
|
}
|
|
40
|
+
if (this._FlowView._EndpointsLayer)
|
|
41
|
+
{
|
|
42
|
+
while (this._FlowView._EndpointsLayer.firstChild) this._FlowView._EndpointsLayer.removeChild(this._FlowView._EndpointsLayer.firstChild);
|
|
43
|
+
}
|
|
44
|
+
if (this._FlowView._PortHintsLayer)
|
|
45
|
+
{
|
|
46
|
+
while (this._FlowView._PortHintsLayer.firstChild) this._FlowView._PortHintsLayer.removeChild(this._FlowView._PortHintsLayer.firstChild);
|
|
47
|
+
}
|
|
40
48
|
|
|
41
49
|
// Render connections first (behind nodes)
|
|
42
50
|
for (let i = 0; i < this._FlowView._FlowData.Connections.length; i++)
|
|
@@ -112,12 +120,10 @@ class PictServiceFlowRenderManager extends libFableServiceProviderBase
|
|
|
112
120
|
{
|
|
113
121
|
if (!this._FlowView || !this._FlowView._ConnectionsLayer) return;
|
|
114
122
|
|
|
115
|
-
// Remove existing elements for this connection
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
tmpExisting[i].remove();
|
|
120
|
-
}
|
|
123
|
+
// Remove existing elements for this connection across all layers
|
|
124
|
+
// where it might live (path / hit-area in connections, dots in
|
|
125
|
+
// endpoints, hint paths in port-hints).
|
|
126
|
+
this._removeConnectionElements(pConnectionHash);
|
|
121
127
|
|
|
122
128
|
let tmpConnection = this._FlowView.getConnection(pConnectionHash);
|
|
123
129
|
if (!tmpConnection) return;
|
|
@@ -208,15 +214,39 @@ class PictServiceFlowRenderManager extends libFableServiceProviderBase
|
|
|
208
214
|
let tmpConn = tmpAffectedConnections[i];
|
|
209
215
|
let tmpIsSelected = (this._FlowView._FlowData.ViewState.SelectedConnectionHash === tmpConn.Hash);
|
|
210
216
|
|
|
211
|
-
// Remove existing connection SVG elements
|
|
212
|
-
|
|
217
|
+
// Remove existing connection SVG elements across all layers
|
|
218
|
+
this._removeConnectionElements(tmpConn.Hash);
|
|
219
|
+
|
|
220
|
+
// Re-render this connection
|
|
221
|
+
this._FlowView._ConnectionRenderer.renderConnection(tmpConn, this._FlowView._ConnectionsLayer, tmpIsSelected);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Remove every SVG element tagged with `data-connection-hash="<hash>"`
|
|
227
|
+
* from any of the layers a connection might write into (connections,
|
|
228
|
+
* endpoints, port-hints). Centralised so per-connection re-render
|
|
229
|
+
* paths stay in sync with whatever layers ConnectionRenderer touches.
|
|
230
|
+
*
|
|
231
|
+
* @param {string} pConnectionHash
|
|
232
|
+
*/
|
|
233
|
+
_removeConnectionElements(pConnectionHash)
|
|
234
|
+
{
|
|
235
|
+
let tmpLayers =
|
|
236
|
+
[
|
|
237
|
+
this._FlowView._ConnectionsLayer,
|
|
238
|
+
this._FlowView._EndpointsLayer,
|
|
239
|
+
this._FlowView._PortHintsLayer
|
|
240
|
+
];
|
|
241
|
+
for (let i = 0; i < tmpLayers.length; i++)
|
|
242
|
+
{
|
|
243
|
+
let tmpLayer = tmpLayers[i];
|
|
244
|
+
if (!tmpLayer) continue;
|
|
245
|
+
let tmpExisting = tmpLayer.querySelectorAll(`[data-connection-hash="${pConnectionHash}"]`);
|
|
213
246
|
for (let j = 0; j < tmpExisting.length; j++)
|
|
214
247
|
{
|
|
215
248
|
tmpExisting[j].remove();
|
|
216
249
|
}
|
|
217
|
-
|
|
218
|
-
// Re-render this connection
|
|
219
|
-
this._FlowView._ConnectionRenderer.renderConnection(tmpConn, this._FlowView._ConnectionsLayer, tmpIsSelected);
|
|
220
250
|
}
|
|
221
251
|
}
|
|
222
252
|
|
|
@@ -17,42 +17,56 @@ const _DefaultConfiguration =
|
|
|
17
17
|
[
|
|
18
18
|
{
|
|
19
19
|
Hash: 'Flow-FloatingToolbar-Template',
|
|
20
|
+
// Inline handlers route to the floating toolbar view via _Pict.views
|
|
21
|
+
// (see _handleButtonClick / _startDrag / _toggleCollapse).
|
|
20
22
|
Template: /*html*/`
|
|
21
23
|
<div class="pict-flow-floating-toolbar" id="Flow-FloatingToolbar-{~D:Record.FlowViewIdentifier~}">
|
|
22
|
-
<div class="pict-flow-floating-grip" id="Flow-FloatingGrip-{~D:Record.FlowViewIdentifier~}" title="Drag to move · Double-click to collapse"
|
|
24
|
+
<div class="pict-flow-floating-grip" id="Flow-FloatingGrip-{~D:Record.FlowViewIdentifier~}" title="Drag to move · Double-click to collapse"
|
|
25
|
+
onmousedown="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._startDrag(event)"
|
|
26
|
+
ondblclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleGripDoubleClick(event)">
|
|
23
27
|
<span id="Flow-FloatingIcon-grip-{~D:Record.FlowViewIdentifier~}"></span>
|
|
24
28
|
</div>
|
|
25
|
-
<button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node"
|
|
29
|
+
<button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node"
|
|
30
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('add-node')">
|
|
26
31
|
<span id="Flow-FloatingIcon-plus-{~D:Record.FlowViewIdentifier~}"></span>
|
|
27
32
|
</button>
|
|
28
|
-
<button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards"
|
|
33
|
+
<button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards"
|
|
34
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('cards-popup')">
|
|
29
35
|
<span id="Flow-FloatingIcon-cards-{~D:Record.FlowViewIdentifier~}"></span>
|
|
30
36
|
</button>
|
|
31
|
-
<button class="pict-flow-floating-btn" data-flow-action="delete-selected" title="Delete Selected"
|
|
37
|
+
<button class="pict-flow-floating-btn" data-flow-action="delete-selected" title="Delete Selected"
|
|
38
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('delete-selected')">
|
|
32
39
|
<span id="Flow-FloatingIcon-trash-{~D:Record.FlowViewIdentifier~}"></span>
|
|
33
40
|
</button>
|
|
34
41
|
<div class="pict-flow-floating-separator"></div>
|
|
35
|
-
<button class="pict-flow-floating-btn" data-flow-action="zoom-in" title="Zoom In"
|
|
42
|
+
<button class="pict-flow-floating-btn" data-flow-action="zoom-in" title="Zoom In"
|
|
43
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-in')">
|
|
36
44
|
<span id="Flow-FloatingIcon-zoom-in-{~D:Record.FlowViewIdentifier~}"></span>
|
|
37
45
|
</button>
|
|
38
|
-
<button class="pict-flow-floating-btn" data-flow-action="zoom-out" title="Zoom Out"
|
|
46
|
+
<button class="pict-flow-floating-btn" data-flow-action="zoom-out" title="Zoom Out"
|
|
47
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-out')">
|
|
39
48
|
<span id="Flow-FloatingIcon-zoom-out-{~D:Record.FlowViewIdentifier~}"></span>
|
|
40
49
|
</button>
|
|
41
|
-
<button class="pict-flow-floating-btn" data-flow-action="zoom-fit" title="Fit to View"
|
|
50
|
+
<button class="pict-flow-floating-btn" data-flow-action="zoom-fit" title="Fit to View"
|
|
51
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-fit')">
|
|
42
52
|
<span id="Flow-FloatingIcon-zoom-fit-{~D:Record.FlowViewIdentifier~}"></span>
|
|
43
53
|
</button>
|
|
44
54
|
<div class="pict-flow-floating-separator"></div>
|
|
45
|
-
<button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout"
|
|
55
|
+
<button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout"
|
|
56
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('auto-layout')">
|
|
46
57
|
<span id="Flow-FloatingIcon-auto-layout-{~D:Record.FlowViewIdentifier~}"></span>
|
|
47
58
|
</button>
|
|
48
|
-
<button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout"
|
|
59
|
+
<button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout"
|
|
60
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('layout-popup')">
|
|
49
61
|
<span id="Flow-FloatingIcon-layout-{~D:Record.FlowViewIdentifier~}"></span>
|
|
50
62
|
</button>
|
|
51
|
-
<button class="pict-flow-floating-btn" data-flow-action="fullscreen" title="Toggle Fullscreen"
|
|
63
|
+
<button class="pict-flow-floating-btn" data-flow-action="fullscreen" title="Toggle Fullscreen"
|
|
64
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('fullscreen')">
|
|
52
65
|
<span id="Flow-FloatingIcon-fullscreen-{~D:Record.FlowViewIdentifier~}"></span>
|
|
53
66
|
</button>
|
|
54
67
|
<div class="pict-flow-floating-separator"></div>
|
|
55
|
-
<button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar"
|
|
68
|
+
<button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar"
|
|
69
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('dock-toolbar')">
|
|
56
70
|
<span id="Flow-FloatingIcon-dock-{~D:Record.FlowViewIdentifier~}"></span>
|
|
57
71
|
</button>
|
|
58
72
|
</div>
|
|
@@ -104,58 +118,13 @@ class PictViewFlowFloatingToolbar extends libPictView
|
|
|
104
118
|
{
|
|
105
119
|
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
106
120
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
tmpFloatingToolbar[0].addEventListener('click', (pEvent) =>
|
|
112
|
-
{
|
|
113
|
-
let tmpTarget = pEvent.target;
|
|
114
|
-
if (!tmpTarget) return;
|
|
115
|
-
|
|
116
|
-
let tmpButton = tmpTarget.closest('[data-flow-action]');
|
|
117
|
-
if (!tmpButton) return;
|
|
118
|
-
|
|
119
|
-
let tmpAction = tmpButton.getAttribute('data-flow-action');
|
|
120
|
-
if (tmpAction === 'dock-toolbar')
|
|
121
|
-
{
|
|
122
|
-
if (this._ToolbarView)
|
|
123
|
-
{
|
|
124
|
-
this._ToolbarView._setToolbarMode('docked');
|
|
125
|
-
}
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Delegate all other actions to the docked toolbar
|
|
130
|
-
if (this._ToolbarView)
|
|
131
|
-
{
|
|
132
|
-
this._ToolbarView._handleToolbarAction(tmpAction);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Bind drag behavior on the grip
|
|
138
|
-
let tmpGrip = this.pict.ContentAssignment.getElement(`#Flow-FloatingGrip-${tmpFlowViewIdentifier}`);
|
|
139
|
-
if (tmpGrip.length > 0)
|
|
140
|
-
{
|
|
141
|
-
tmpGrip[0].addEventListener('mousedown', (pEvent) =>
|
|
142
|
-
{
|
|
143
|
-
this._startDrag(pEvent);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Double-click grip to toggle collapsed state
|
|
147
|
-
tmpGrip[0].addEventListener('dblclick', (pEvent) =>
|
|
148
|
-
{
|
|
149
|
-
pEvent.preventDefault();
|
|
150
|
-
pEvent.stopPropagation();
|
|
151
|
-
this._toggleCollapse();
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Populate icons
|
|
121
|
+
// Button clicks and grip drag/dblclick are wired inline in
|
|
122
|
+
// Flow-FloatingToolbar-Template — see _handleButtonClick / _startDrag /
|
|
123
|
+
// _handleGripDoubleClick. Only icon population and option-based
|
|
124
|
+
// pruning happen here.
|
|
156
125
|
this._populateIcons();
|
|
157
126
|
|
|
158
|
-
|
|
127
|
+
let tmpFloatingToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
|
|
159
128
|
if (tmpFloatingToolbar.length > 0)
|
|
160
129
|
{
|
|
161
130
|
if (this.options.EnableAddNode === false)
|
|
@@ -179,6 +148,45 @@ class PictViewFlowFloatingToolbar extends libPictView
|
|
|
179
148
|
return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
|
|
180
149
|
}
|
|
181
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Handle a click on a floating toolbar action button. Called from the
|
|
153
|
+
* inline `onclick` handler on each button.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} pAction - Value of the button's data-flow-action attr
|
|
156
|
+
*/
|
|
157
|
+
_handleButtonClick(pAction)
|
|
158
|
+
{
|
|
159
|
+
if (pAction === 'dock-toolbar')
|
|
160
|
+
{
|
|
161
|
+
if (this._ToolbarView)
|
|
162
|
+
{
|
|
163
|
+
this._ToolbarView._setToolbarMode('docked');
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (this._ToolbarView)
|
|
168
|
+
{
|
|
169
|
+
this._ToolbarView._handleToolbarAction(pAction);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Handle a double-click on the grip. Toggles collapsed state and
|
|
175
|
+
* prevents the event from reaching the surrounding canvas. Called
|
|
176
|
+
* from the inline `ondblclick` handler.
|
|
177
|
+
*
|
|
178
|
+
* @param {Event} pEvent
|
|
179
|
+
*/
|
|
180
|
+
_handleGripDoubleClick(pEvent)
|
|
181
|
+
{
|
|
182
|
+
if (pEvent && typeof pEvent.preventDefault === 'function')
|
|
183
|
+
{
|
|
184
|
+
pEvent.preventDefault();
|
|
185
|
+
pEvent.stopPropagation();
|
|
186
|
+
}
|
|
187
|
+
this._toggleCollapse();
|
|
188
|
+
}
|
|
189
|
+
|
|
182
190
|
/**
|
|
183
191
|
* Populate SVG icons into all floating toolbar button spans.
|
|
184
192
|
*/
|
|
@@ -40,11 +40,31 @@ class PictViewFlowNode extends libPictView
|
|
|
40
40
|
if (pNodeTypeConfig.PortLabelsOnHover) tmpClassList += ' pict-flow-node-port-labels-hover';
|
|
41
41
|
if (pNodeTypeConfig.PortLabelsVertical) tmpClassList += ' pict-flow-node-port-labels-vertical';
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
// Resolve theme-aware ColorRole. Per-node override wins; falls back
|
|
45
|
+
// to the node type's role. A role of 'none' (or empty string)
|
|
46
|
+
// explicitly opts out — useful when a card wants to keep its
|
|
47
|
+
// hex BodyStyle/TitleBarColor regardless of the host theme.
|
|
48
|
+
let tmpColorRole = (typeof pNodeData.ColorRole !== 'undefined')
|
|
49
|
+
? pNodeData.ColorRole
|
|
50
|
+
: (pNodeTypeConfig && pNodeTypeConfig.ColorRole);
|
|
51
|
+
if (tmpColorRole && tmpColorRole !== 'none')
|
|
52
|
+
{
|
|
53
|
+
tmpClassList += ' pict-flow-node-color-' + tmpColorRole;
|
|
54
|
+
}
|
|
43
55
|
tmpGroup.setAttribute('class', tmpClassList);
|
|
44
56
|
tmpGroup.setAttribute('transform', `translate(${pNodeData.X}, ${pNodeData.Y})`);
|
|
45
57
|
tmpGroup.setAttribute('data-node-hash', pNodeData.Hash);
|
|
46
58
|
tmpGroup.setAttribute('data-element-type', 'node');
|
|
47
59
|
|
|
60
|
+
// Hover the whole node → activate every port-hint that points
|
|
61
|
+
// at any of this node's ports. Lets the user see *all* of the
|
|
62
|
+
// node's reroute lines at once, not just one badge at a time.
|
|
63
|
+
if (this._FlowView._PortRenderer && typeof this._FlowView._PortRenderer._wirePortHintHover === 'function')
|
|
64
|
+
{
|
|
65
|
+
this._FlowView._PortRenderer._wirePortHintHover(tmpGroup, null, pNodeData.Hash);
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
let tmpWidth = pNodeData.Width || 180;
|
|
49
69
|
let tmpHeight = pNodeData.Height || 80;
|
|
50
70
|
let tmpTitleBarHeight = this.options.NodeTitleBarHeight;
|
|
@@ -422,9 +442,10 @@ class PictViewFlowNode extends libPictView
|
|
|
422
442
|
tmpDiv.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
|
423
443
|
tmpDiv.setAttribute('class', 'pict-flow-node-body-content-html');
|
|
424
444
|
|
|
425
|
-
// Pointer event isolation — prevent node drag/canvas pan
|
|
426
|
-
|
|
427
|
-
tmpDiv.
|
|
445
|
+
// Pointer event isolation — prevent node drag/canvas pan.
|
|
446
|
+
// Inline attribute handlers (visible in DOM, no closure leak).
|
|
447
|
+
tmpDiv.setAttribute('onpointerdown', 'event.stopPropagation()');
|
|
448
|
+
tmpDiv.setAttribute('onwheel', 'event.stopPropagation()');
|
|
428
449
|
|
|
429
450
|
// Render template content
|
|
430
451
|
let tmpRenderedContent = this._resolveBodyTemplate(pBodyContent, pNodeData, pPict);
|
|
@@ -463,9 +484,9 @@ class PictViewFlowNode extends libPictView
|
|
|
463
484
|
tmpCanvas.style.width = '100%';
|
|
464
485
|
tmpCanvas.style.height = '100%';
|
|
465
486
|
|
|
466
|
-
// Pointer event isolation
|
|
467
|
-
tmpCanvas.
|
|
468
|
-
tmpCanvas.
|
|
487
|
+
// Pointer event isolation — inline attribute handlers.
|
|
488
|
+
tmpCanvas.setAttribute('onpointerdown', 'event.stopPropagation()');
|
|
489
|
+
tmpCanvas.setAttribute('onwheel', 'event.stopPropagation()');
|
|
469
490
|
|
|
470
491
|
// Invoke render callback (the primary rendering path for canvas)
|
|
471
492
|
if (typeof pBodyContent.RenderCallback === 'function')
|
|
@@ -529,6 +550,17 @@ class PictViewFlowNode extends libPictView
|
|
|
529
550
|
}
|
|
530
551
|
}
|
|
531
552
|
|
|
553
|
+
// Per-node BodyStyle override (wins over the node-type default).
|
|
554
|
+
// Lets a flow author paint individual cards distinctly without
|
|
555
|
+
// registering a new node type per category.
|
|
556
|
+
if (pNodeData.BodyStyle)
|
|
557
|
+
{
|
|
558
|
+
for (let tmpStyleKey in pNodeData.BodyStyle)
|
|
559
|
+
{
|
|
560
|
+
tmpBody.setAttribute(tmpStyleKey, pNodeData.BodyStyle[tmpStyleKey]);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
532
564
|
// Apply per-instance style overrides (for node-specific editing)
|
|
533
565
|
// These must be applied as inline styles so they override CSS rules
|
|
534
566
|
// (CSS declarations take precedence over SVG presentation attributes).
|
|
@@ -561,6 +593,11 @@ class PictViewFlowNode extends libPictView
|
|
|
561
593
|
{
|
|
562
594
|
tmpTitleBar.setAttribute('fill', pNodeTypeConfig.TitleBarColor);
|
|
563
595
|
}
|
|
596
|
+
// Per-node TitleBarColor (wins over node-type default).
|
|
597
|
+
if (pNodeData.TitleBarColor)
|
|
598
|
+
{
|
|
599
|
+
tmpTitleBar.setAttribute('fill', pNodeData.TitleBarColor);
|
|
600
|
+
}
|
|
564
601
|
|
|
565
602
|
pGroup.appendChild(tmpTitleBar);
|
|
566
603
|
|
|
@@ -578,6 +615,10 @@ class PictViewFlowNode extends libPictView
|
|
|
578
615
|
{
|
|
579
616
|
tmpTitleBarBottom.setAttribute('fill', pNodeTypeConfig.TitleBarColor);
|
|
580
617
|
}
|
|
618
|
+
if (pNodeData.TitleBarColor)
|
|
619
|
+
{
|
|
620
|
+
tmpTitleBarBottom.setAttribute('fill', pNodeData.TitleBarColor);
|
|
621
|
+
}
|
|
581
622
|
|
|
582
623
|
// Per-instance title bar color override
|
|
583
624
|
// Applied as inline style to override CSS rules.
|