pict-section-flow 0.0.2 → 0.0.3
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/.claude/launch.json +11 -0
- package/docs/README.md +51 -0
- package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +105 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +36 -0
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +42 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +98 -0
- package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +44 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +1 -1
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +9 -1
- package/package.json +2 -2
- package/source/Pict-Section-Flow.js +8 -1
- package/source/PictFlowCard.js +49 -1
- package/source/providers/PictProvider-Flow-CSS.js +1440 -0
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +413 -0
- package/source/providers/PictProvider-Flow-Geometry.js +43 -0
- package/source/providers/PictProvider-Flow-Icons.js +335 -0
- package/source/providers/PictProvider-Flow-Layouts.js +214 -2
- package/source/providers/PictProvider-Flow-NodeTypes.js +30 -7
- package/source/providers/PictProvider-Flow-Noise.js +241 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +19 -0
- package/source/providers/PictProvider-Flow-Theme.js +755 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +95 -32
- package/source/services/PictService-Flow-PanelManager.js +188 -0
- package/source/services/PictService-Flow-SelectionManager.js +109 -0
- package/source/services/PictService-Flow-Tether.js +52 -25
- package/source/services/PictService-Flow-ViewportManager.js +176 -0
- package/source/views/PictView-Flow-FloatingToolbar.js +352 -0
- package/source/views/PictView-Flow-Node.js +654 -169
- package/source/views/PictView-Flow-PropertiesPanel.js +176 -1
- package/source/views/PictView-Flow-Toolbar.js +846 -379
- package/source/views/PictView-Flow.js +279 -671
|
@@ -55,6 +55,10 @@ const _DefaultConfiguration =
|
|
|
55
55
|
{
|
|
56
56
|
Hash: 'Flow-InfoPanel-Port-Constraint',
|
|
57
57
|
Template: ' <span class="pict-flow-info-panel-port-constraint">{~D:Record.ConstraintText~}</span>'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
Hash: 'Flow-NodeProps-Editor',
|
|
61
|
+
Template: '<div class="pict-flow-node-props-fields"><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title</label><input type="text" class="pict-flow-node-props-input" data-prop="Title" value="{~D:Record.Title~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Width" value="{~D:Record.Width~}" min="60" step="10" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Height</label><input type="number" class="pict-flow-node-props-input" data-prop="Height" value="{~D:Record.Height~}" min="40" step="10" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Fill</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyFill" value="{~D:Record.BodyFillValue~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Stroke</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyStroke" value="{~D:Record.BodyStrokeValue~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Stroke Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Style.BodyStrokeWidth" value="{~D:Record.BodyStrokeWidthValue~}" min="0" max="10" step="0.5" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title Bar</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.TitleBarColor" value="{~D:Record.TitleBarColorValue~}" /></div></div>'
|
|
58
62
|
}
|
|
59
63
|
]
|
|
60
64
|
};
|
|
@@ -192,6 +196,13 @@ class PictViewFlowPropertiesPanel extends libPictView
|
|
|
192
196
|
{
|
|
193
197
|
this._renderPanelContent(pPanelData, tmpBody);
|
|
194
198
|
}
|
|
199
|
+
|
|
200
|
+
// Render the collapsible node properties editor at the bottom of the panel
|
|
201
|
+
let tmpFO = pPanelsLayer.querySelector(`[data-panel-hash="${pPanelData.Hash}"]`);
|
|
202
|
+
if (tmpFO)
|
|
203
|
+
{
|
|
204
|
+
this._renderNodePropsEditor(pPanelData, tmpFO);
|
|
205
|
+
}
|
|
195
206
|
}
|
|
196
207
|
|
|
197
208
|
/**
|
|
@@ -279,10 +290,24 @@ class PictViewFlowPropertiesPanel extends libPictView
|
|
|
279
290
|
let tmpContentParts = [];
|
|
280
291
|
|
|
281
292
|
// Header
|
|
282
|
-
|
|
293
|
+
let tmpIconProvider = this._FlowView._IconProvider;
|
|
294
|
+
if (tmpMeta.Icon && tmpIconProvider && !tmpIconProvider.isEmojiIcon(tmpMeta.Icon))
|
|
295
|
+
{
|
|
296
|
+
// SVG icon markup for the header
|
|
297
|
+
let tmpResolvedKey = tmpIconProvider.resolveIconKey(tmpMeta);
|
|
298
|
+
let tmpIconMarkup = tmpIconProvider.getIconSVGMarkup(tmpResolvedKey, 18);
|
|
299
|
+
tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header-Icon', { Icon: tmpIconMarkup, Label: tmpLabel }));
|
|
300
|
+
}
|
|
301
|
+
else if (tmpMeta.Icon)
|
|
283
302
|
{
|
|
284
303
|
tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header-Icon', { Icon: tmpMeta.Icon, Label: tmpLabel }));
|
|
285
304
|
}
|
|
305
|
+
else if (tmpIconProvider)
|
|
306
|
+
{
|
|
307
|
+
// No icon specified — render default fallback
|
|
308
|
+
let tmpDefaultMarkup = tmpIconProvider.getIconSVGMarkup('default', 18);
|
|
309
|
+
tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header-Icon', { Icon: tmpDefaultMarkup, Label: tmpLabel }));
|
|
310
|
+
}
|
|
286
311
|
else
|
|
287
312
|
{
|
|
288
313
|
tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header', { Label: tmpLabel }));
|
|
@@ -368,6 +393,156 @@ class PictViewFlowPropertiesPanel extends libPictView
|
|
|
368
393
|
return '';
|
|
369
394
|
}
|
|
370
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Render the collapsible node properties editor into a panel's foreignObject.
|
|
398
|
+
* Populates the editor fields with current node values and wires up live
|
|
399
|
+
* change handlers for immediate visual feedback.
|
|
400
|
+
*
|
|
401
|
+
* @param {Object} pPanelData - Panel data from OpenPanels
|
|
402
|
+
* @param {Element} pForeignObject - The panel's SVG foreignObject element
|
|
403
|
+
*/
|
|
404
|
+
_renderNodePropsEditor(pPanelData, pForeignObject)
|
|
405
|
+
{
|
|
406
|
+
let tmpNodeData = this._FlowView.getNode(pPanelData.NodeHash);
|
|
407
|
+
if (!tmpNodeData) return;
|
|
408
|
+
|
|
409
|
+
let tmpPropsBody = pForeignObject.querySelector('.pict-flow-panel-node-props-body');
|
|
410
|
+
if (!tmpPropsBody) return;
|
|
411
|
+
|
|
412
|
+
// Build the template record with safe defaults for Style values
|
|
413
|
+
let tmpStyle = tmpNodeData.Style || {};
|
|
414
|
+
|
|
415
|
+
// Resolve default colors from the node type config or CSS token defaults
|
|
416
|
+
let tmpNodeTypeConfig = this._FlowView._NodeTypeProvider.getNodeType(tmpNodeData.Type);
|
|
417
|
+
let tmpDefaultTitleBarColor = '#2c3e50';
|
|
418
|
+
let tmpDefaultBodyFill = '#ffffff';
|
|
419
|
+
let tmpDefaultBodyStroke = '#d0d4d8';
|
|
420
|
+
if (tmpNodeTypeConfig)
|
|
421
|
+
{
|
|
422
|
+
if (tmpNodeTypeConfig.TitleBarColor) tmpDefaultTitleBarColor = tmpNodeTypeConfig.TitleBarColor;
|
|
423
|
+
if (tmpNodeTypeConfig.BodyStyle)
|
|
424
|
+
{
|
|
425
|
+
if (tmpNodeTypeConfig.BodyStyle.fill) tmpDefaultBodyFill = tmpNodeTypeConfig.BodyStyle.fill;
|
|
426
|
+
if (tmpNodeTypeConfig.BodyStyle.stroke) tmpDefaultBodyStroke = tmpNodeTypeConfig.BodyStyle.stroke;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
let tmpRecord =
|
|
431
|
+
{
|
|
432
|
+
Title: tmpNodeData.Title || '',
|
|
433
|
+
Width: tmpNodeData.Width || 180,
|
|
434
|
+
Height: tmpNodeData.Height || 80,
|
|
435
|
+
BodyFillValue: tmpStyle.BodyFill || tmpDefaultBodyFill,
|
|
436
|
+
BodyStrokeValue: tmpStyle.BodyStroke || tmpDefaultBodyStroke,
|
|
437
|
+
BodyStrokeWidthValue: tmpStyle.BodyStrokeWidth || 1,
|
|
438
|
+
TitleBarColorValue: tmpStyle.TitleBarColor || tmpDefaultTitleBarColor
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
tmpPropsBody.innerHTML = this.pict.parseTemplateByHash('Flow-NodeProps-Editor', tmpRecord);
|
|
442
|
+
|
|
443
|
+
// Wire up the expand/collapse toggle with dynamic panel height adjustment
|
|
444
|
+
let tmpHeader = pForeignObject.querySelector('.pict-flow-panel-node-props-header');
|
|
445
|
+
if (tmpHeader)
|
|
446
|
+
{
|
|
447
|
+
// Store the original panel height before the section was expanded
|
|
448
|
+
let tmpOriginalHeight = parseInt(pForeignObject.getAttribute('height'), 10) || 200;
|
|
449
|
+
|
|
450
|
+
tmpHeader.addEventListener('click', (pEvent) =>
|
|
451
|
+
{
|
|
452
|
+
pEvent.stopPropagation();
|
|
453
|
+
let tmpIsExpanded = tmpPropsBody.style.display !== 'none';
|
|
454
|
+
tmpPropsBody.style.display = tmpIsExpanded ? 'none' : 'block';
|
|
455
|
+
let tmpChevron = tmpHeader.querySelector('.pict-flow-panel-node-props-chevron');
|
|
456
|
+
if (tmpChevron)
|
|
457
|
+
{
|
|
458
|
+
tmpChevron.classList.toggle('expanded', !tmpIsExpanded);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Resize the foreignObject to accommodate the expanded/collapsed section
|
|
462
|
+
let tmpEditorHeight = tmpIsExpanded ? 0 : tmpPropsBody.scrollHeight;
|
|
463
|
+
let tmpNewHeight = tmpOriginalHeight + tmpEditorHeight;
|
|
464
|
+
pForeignObject.setAttribute('height', String(tmpNewHeight));
|
|
465
|
+
|
|
466
|
+
// Update the panel data so tethers and position tracking stay in sync
|
|
467
|
+
let tmpPanelDataEntry = this._FlowView._FlowData.OpenPanels.find(
|
|
468
|
+
(pPanel) => pPanel.Hash === pPanelData.Hash);
|
|
469
|
+
if (tmpPanelDataEntry)
|
|
470
|
+
{
|
|
471
|
+
tmpPanelDataEntry.Height = tmpNewHeight;
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Wire up live change handlers on all input fields
|
|
477
|
+
let tmpInputs = tmpPropsBody.querySelectorAll('.pict-flow-node-props-input');
|
|
478
|
+
for (let i = 0; i < tmpInputs.length; i++)
|
|
479
|
+
{
|
|
480
|
+
let tmpInput = tmpInputs[i];
|
|
481
|
+
let tmpProp = tmpInput.getAttribute('data-prop');
|
|
482
|
+
|
|
483
|
+
tmpInput.addEventListener('input', (pEvent) =>
|
|
484
|
+
{
|
|
485
|
+
pEvent.stopPropagation();
|
|
486
|
+
this._applyNodePropChange(pPanelData.NodeHash, tmpProp, tmpInput.value, tmpInput.type);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Prevent pointer events from propagating to SVG drag handler
|
|
490
|
+
tmpInput.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Apply a node property change from the properties editor and re-render.
|
|
496
|
+
*
|
|
497
|
+
* @param {string} pNodeHash - Hash of the node to update
|
|
498
|
+
* @param {string} pPropPath - Property path (e.g. 'Title', 'Width', 'Style.BodyFill')
|
|
499
|
+
* @param {string} pValue - The new value from the input
|
|
500
|
+
* @param {string} pInputType - The input element type ('text', 'number', 'color')
|
|
501
|
+
*/
|
|
502
|
+
_applyNodePropChange(pNodeHash, pPropPath, pValue, pInputType)
|
|
503
|
+
{
|
|
504
|
+
let tmpNodeData = this._FlowView.getNode(pNodeHash);
|
|
505
|
+
if (!tmpNodeData) return;
|
|
506
|
+
|
|
507
|
+
// Parse numeric values
|
|
508
|
+
let tmpValue = pValue;
|
|
509
|
+
if (pInputType === 'number')
|
|
510
|
+
{
|
|
511
|
+
tmpValue = parseFloat(pValue);
|
|
512
|
+
if (isNaN(tmpValue)) return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Apply the value based on the property path
|
|
516
|
+
if (pPropPath === 'Title')
|
|
517
|
+
{
|
|
518
|
+
tmpNodeData.Title = tmpValue;
|
|
519
|
+
}
|
|
520
|
+
else if (pPropPath === 'Width')
|
|
521
|
+
{
|
|
522
|
+
tmpNodeData.Width = tmpValue;
|
|
523
|
+
}
|
|
524
|
+
else if (pPropPath === 'Height')
|
|
525
|
+
{
|
|
526
|
+
tmpNodeData.Height = tmpValue;
|
|
527
|
+
}
|
|
528
|
+
else if (pPropPath.startsWith('Style.'))
|
|
529
|
+
{
|
|
530
|
+
if (!tmpNodeData.Style) tmpNodeData.Style = {};
|
|
531
|
+
let tmpStyleKey = pPropPath.substring(6); // Remove 'Style.' prefix
|
|
532
|
+
tmpNodeData.Style[tmpStyleKey] = tmpValue;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Re-render the flow to reflect changes
|
|
536
|
+
this._FlowView.renderFlow();
|
|
537
|
+
this._FlowView.marshalFromView();
|
|
538
|
+
|
|
539
|
+
// Fire change event
|
|
540
|
+
if (this._FlowView._EventHandlerProvider)
|
|
541
|
+
{
|
|
542
|
+
this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView._FlowData);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
371
546
|
/**
|
|
372
547
|
* Render a tether from a panel to its node.
|
|
373
548
|
* Delegates to the TetherService for geometry, path generation, and SVG element creation.
|