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
@@ -0,0 +1,88 @@
1
+ const libPictFlowCardPropertiesPanel = require('../PictFlowCardPropertiesPanel.js');
2
+
3
+ /**
4
+ * FlowCardPropertiesPanel-Template
5
+ *
6
+ * Renders pict templates into the panel body.
7
+ *
8
+ * Configuration:
9
+ * {
10
+ * PanelType: 'Template',
11
+ * Configuration: {
12
+ * Templates: [
13
+ * { Hash: 'my-template', Template: '<div>{~D:Record.Data.SomeValue~}</div>' }
14
+ * ],
15
+ * TemplateHash: 'my-template'
16
+ * }
17
+ * }
18
+ */
19
+ class FlowCardPropertiesPanelTemplate extends libPictFlowCardPropertiesPanel
20
+ {
21
+ constructor(pFable, pOptions, pServiceHash)
22
+ {
23
+ super(pFable, pOptions, pServiceHash);
24
+
25
+ this.serviceType = 'PictFlowCardPropertiesPanel-Template';
26
+
27
+ this._TemplatesRegistered = false;
28
+ }
29
+
30
+ /**
31
+ * Register templates with the pict template provider and render.
32
+ */
33
+ render(pContainer, pNodeData)
34
+ {
35
+ super.render(pContainer, pNodeData);
36
+
37
+ if (!this._Configuration || !this._Configuration.Templates) return;
38
+
39
+ // Register templates with pict (only once)
40
+ if (!this._TemplatesRegistered)
41
+ {
42
+ let tmpTemplates = this._Configuration.Templates;
43
+ for (let i = 0; i < tmpTemplates.length; i++)
44
+ {
45
+ if (tmpTemplates[i].Hash && tmpTemplates[i].Template)
46
+ {
47
+ this.fable.TemplateProvider.addTemplate(tmpTemplates[i].Hash, tmpTemplates[i].Template);
48
+ }
49
+ }
50
+ this._TemplatesRegistered = true;
51
+ }
52
+
53
+ this._renderTemplate();
54
+ }
55
+
56
+ marshalToPanel(pNodeData)
57
+ {
58
+ super.marshalToPanel(pNodeData);
59
+ this._renderTemplate();
60
+ }
61
+
62
+ _renderTemplate()
63
+ {
64
+ if (!this._ContentContainer || !this._NodeData) return;
65
+
66
+ let tmpTemplateHash = this._Configuration.TemplateHash;
67
+ if (!tmpTemplateHash) return;
68
+
69
+ let tmpRecord = this._NodeData;
70
+ let tmpHTML = this.fable.parseTemplate(tmpTemplateHash, tmpRecord, null, [tmpRecord]);
71
+ this._ContentContainer.innerHTML = tmpHTML;
72
+ }
73
+ }
74
+
75
+ module.exports = FlowCardPropertiesPanelTemplate;
76
+
77
+ module.exports.default_configuration = Object.assign(
78
+ {},
79
+ libPictFlowCardPropertiesPanel.default_configuration,
80
+ {
81
+ PanelType: 'Template',
82
+ Configuration:
83
+ {
84
+ Templates: [],
85
+ TemplateHash: ''
86
+ }
87
+ }
88
+ );
@@ -0,0 +1,114 @@
1
+ const libPictFlowCardPropertiesPanel = require('../PictFlowCardPropertiesPanel.js');
2
+
3
+ /**
4
+ * FlowCardPropertiesPanel-View
5
+ *
6
+ * Renders an existing registered pict-view into the panel body.
7
+ * The view's destination is temporarily overridden to render inside
8
+ * the panel container.
9
+ *
10
+ * Configuration:
11
+ * {
12
+ * PanelType: 'View',
13
+ * Configuration: {
14
+ * ViewHash: 'MyCustomViewIdentifier'
15
+ * }
16
+ * }
17
+ */
18
+ class FlowCardPropertiesPanelView extends libPictFlowCardPropertiesPanel
19
+ {
20
+ constructor(pFable, pOptions, pServiceHash)
21
+ {
22
+ super(pFable, pOptions, pServiceHash);
23
+
24
+ this.serviceType = 'PictFlowCardPropertiesPanel-View';
25
+
26
+ this._OriginalDestination = null;
27
+ this._ViewInstance = null;
28
+ }
29
+
30
+ /**
31
+ * Render the referenced pict-view into the panel body.
32
+ */
33
+ render(pContainer, pNodeData)
34
+ {
35
+ super.render(pContainer, pNodeData);
36
+
37
+ if (!this._Configuration || !this._Configuration.ViewHash)
38
+ {
39
+ pContainer.innerHTML = '<em>No ViewHash configured</em>';
40
+ return;
41
+ }
42
+
43
+ let tmpViewHash = this._Configuration.ViewHash;
44
+
45
+ // Create a unique container ID
46
+ let tmpContainerID = `pict-flow-panel-view-${pNodeData.Hash}`;
47
+ pContainer.innerHTML = `<div id="${tmpContainerID}"></div>`;
48
+
49
+ try
50
+ {
51
+ // Look up the view in the pict instance
52
+ let tmpPict = this.pict || this.fable;
53
+ if (tmpPict.views && tmpPict.views[tmpViewHash])
54
+ {
55
+ this._ViewInstance = tmpPict.views[tmpViewHash];
56
+
57
+ // Save original destination
58
+ this._OriginalDestination = this._ViewInstance.options.DefaultDestinationAddress;
59
+
60
+ // Override destination to our panel container
61
+ this._ViewInstance.options.DefaultDestinationAddress = `#${tmpContainerID}`;
62
+
63
+ if (typeof this._ViewInstance.render === 'function')
64
+ {
65
+ this._ViewInstance.render();
66
+ }
67
+ }
68
+ else
69
+ {
70
+ pContainer.innerHTML = `<em>View "${tmpViewHash}" not found</em>`;
71
+ }
72
+ }
73
+ catch (pError)
74
+ {
75
+ this.log.warn(`FlowCardPropertiesPanel-View render error: ${pError.message}`);
76
+ pContainer.innerHTML = `<em>View render error: ${pError.message}</em>`;
77
+ }
78
+ }
79
+
80
+ marshalFromPanel(pNodeData)
81
+ {
82
+ if (this._ViewInstance && typeof this._ViewInstance.marshalFromView === 'function')
83
+ {
84
+ this._ViewInstance.marshalFromView();
85
+ }
86
+ }
87
+
88
+ destroy()
89
+ {
90
+ // Restore original destination
91
+ if (this._ViewInstance && this._OriginalDestination)
92
+ {
93
+ this._ViewInstance.options.DefaultDestinationAddress = this._OriginalDestination;
94
+ }
95
+
96
+ this._ViewInstance = null;
97
+ this._OriginalDestination = null;
98
+ super.destroy();
99
+ }
100
+ }
101
+
102
+ module.exports = FlowCardPropertiesPanelView;
103
+
104
+ module.exports.default_configuration = Object.assign(
105
+ {},
106
+ libPictFlowCardPropertiesPanel.default_configuration,
107
+ {
108
+ PanelType: 'View',
109
+ Configuration:
110
+ {
111
+ ViewHash: ''
112
+ }
113
+ }
114
+ );
@@ -11,14 +11,25 @@ const _ProviderConfiguration =
11
11
  * for flow events like node selection, movement, connection creation, etc.
12
12
  *
13
13
  * Supported events:
14
- * - onNodeSelected(node) - A node was selected (or null for deselection)
15
- * - onNodeAdded(node) - A new node was added
16
- * - onNodeRemoved(node) - A node was removed
17
- * - onNodeMoved(node) - A node was moved to a new position
18
- * - onConnectionSelected(conn) - A connection was selected
19
- * - onConnectionCreated(conn) - A new connection was created
20
- * - onConnectionRemoved(conn) - A connection was removed
21
- * - onFlowChanged(flowData) - The flow data changed (catch-all)
14
+ * - onNodeSelected(node) - A node was selected (or null for deselection)
15
+ * - onNodeAdded(node) - A new node was added
16
+ * - onNodeRemoved(node) - A node was removed
17
+ * - onNodeMoved(node) - A node was moved to a new position
18
+ * - onConnectionSelected(conn) - A connection was selected
19
+ * - onConnectionCreated(conn) - A new connection was created
20
+ * - onConnectionRemoved(conn) - A connection was removed
21
+ * - onConnectionHandleMoved(conn) - A connection's drag handle was repositioned
22
+ * - onConnectionModeChanged(conn) - A connection's line mode was toggled (bezier/orthogonal)
23
+ * - onPanelOpened(panelData) - A properties panel was opened
24
+ * - onPanelClosed(panelData) - A properties panel was closed
25
+ * - onPanelMoved(panelData) - A properties panel was moved
26
+ * - onTetherSelected(panelData) - A tether line was selected (or null for deselection)
27
+ * - onTetherHandleMoved(panelData) - A tether's drag handle was repositioned
28
+ * - onTetherModeChanged(panelData) - A tether's line mode was toggled (bezier/orthogonal)
29
+ * - onLayoutSaved(layoutData) - A layout snapshot was saved
30
+ * - onLayoutRestored(layoutData) - A saved layout was restored
31
+ * - onLayoutDeleted(layoutData) - A saved layout was deleted
32
+ * - onFlowChanged(flowData) - The flow data changed (catch-all)
22
33
  */
23
34
  class PictProviderFlowEventHandler extends libPictProvider
24
35
  {
@@ -0,0 +1,64 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ /**
4
+ * PictProvider-Flow-Geometry
5
+ *
6
+ * Shared geometry utilities for the flow diagram.
7
+ * Provides direction vectors and edge center calculations used by
8
+ * connections, tethers, and other flow components.
9
+ */
10
+ class PictProviderFlowGeometry extends libFableServiceProviderBase
11
+ {
12
+ constructor(pFable, pOptions, pServiceHash)
13
+ {
14
+ super(pFable, pOptions, pServiceHash);
15
+
16
+ this.serviceType = 'PictProviderFlowGeometry';
17
+ }
18
+
19
+ /**
20
+ * Get the outward unit direction vector for a given side.
21
+ *
22
+ * @param {string} pSide - 'left', 'right', 'top', or 'bottom'
23
+ * @returns {{dx: number, dy: number}}
24
+ */
25
+ sideDirection(pSide)
26
+ {
27
+ switch (pSide)
28
+ {
29
+ case 'left': return { dx: -1, dy: 0 };
30
+ case 'right': return { dx: 1, dy: 0 };
31
+ case 'top': return { dx: 0, dy: -1 };
32
+ case 'bottom': return { dx: 0, dy: 1 };
33
+ default: return { dx: 1, dy: 0 };
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Get the center point of a rectangle's edge.
39
+ * Works for any object with X, Y, Width, Height properties
40
+ * (nodes, panels, or any rectangular element).
41
+ *
42
+ * @param {Object} pRectData - Object with X, Y, Width, Height
43
+ * @param {string} pSide - 'left', 'right', 'top', 'bottom'
44
+ * @returns {{x: number, y: number}}
45
+ */
46
+ getEdgeCenter(pRectData, pSide)
47
+ {
48
+ switch (pSide)
49
+ {
50
+ case 'left':
51
+ return { x: pRectData.X, y: pRectData.Y + pRectData.Height / 2 };
52
+ case 'right':
53
+ return { x: pRectData.X + pRectData.Width, y: pRectData.Y + pRectData.Height / 2 };
54
+ case 'top':
55
+ return { x: pRectData.X + pRectData.Width / 2, y: pRectData.Y };
56
+ case 'bottom':
57
+ return { x: pRectData.X + pRectData.Width / 2, y: pRectData.Y + pRectData.Height };
58
+ default:
59
+ return { x: pRectData.X + pRectData.Width, y: pRectData.Y + pRectData.Height / 2 };
60
+ }
61
+ }
62
+ }
63
+
64
+ module.exports = PictProviderFlowGeometry;
@@ -0,0 +1,284 @@
1
+ const libPictProvider = require('pict-provider');
2
+
3
+ const _ProviderConfiguration =
4
+ {
5
+ ProviderIdentifier: 'PictProviderFlowLayouts'
6
+ };
7
+
8
+ /**
9
+ * Provider for managing saved flow diagram layouts.
10
+ *
11
+ * Layouts capture the spatial arrangement of nodes and panels on the canvas
12
+ * without storing any card content. When a layout is restored, nodes that
13
+ * still exist are placed at their saved positions and any new nodes are
14
+ * auto-laid-out to the right.
15
+ *
16
+ * Saved layout data structure:
17
+ * {
18
+ * Hash: "layout-<UUID>",
19
+ * Name: "My Layout",
20
+ * CreatedAt: "2026-02-26T12:00:00.000Z",
21
+ * NodePositions: { "node-hash": { X, Y, Width, Height } },
22
+ * PanelPositions: { "node-hash": { X, Y, Width, Height } },
23
+ * ViewState: { PanX, PanY, Zoom }
24
+ * }
25
+ */
26
+ class PictProviderFlowLayouts extends libPictProvider
27
+ {
28
+ constructor(pFable, pOptions, pServiceHash)
29
+ {
30
+ let tmpOptions = Object.assign({}, _ProviderConfiguration, pOptions);
31
+ super(pFable, tmpOptions, pServiceHash);
32
+
33
+ this.serviceType = 'PictProviderFlowLayouts';
34
+
35
+ this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
36
+ }
37
+
38
+ /**
39
+ * Save the current node and panel positions as a named layout.
40
+ * @param {string} pName - The display name for this layout
41
+ * @returns {Object} The saved layout entry
42
+ */
43
+ saveLayout(pName)
44
+ {
45
+ if (!this._FlowView)
46
+ {
47
+ this.log.warn('PictProviderFlowLayouts saveLayout: no FlowView reference');
48
+ return null;
49
+ }
50
+
51
+ let tmpFlowData = this._FlowView._FlowData;
52
+ let tmpLayoutHash = `layout-${this.fable.getUUID()}`;
53
+ let tmpNodePositions = {};
54
+ let tmpPanelPositions = {};
55
+
56
+ // Capture node positions (arrangement only, no content)
57
+ for (let i = 0; i < tmpFlowData.Nodes.length; i++)
58
+ {
59
+ let tmpNode = tmpFlowData.Nodes[i];
60
+ tmpNodePositions[tmpNode.Hash] =
61
+ {
62
+ X: tmpNode.X,
63
+ Y: tmpNode.Y,
64
+ Width: tmpNode.Width,
65
+ Height: tmpNode.Height
66
+ };
67
+ }
68
+
69
+ // Capture panel positions keyed by NodeHash (panels get new hashes on each open)
70
+ for (let i = 0; i < tmpFlowData.OpenPanels.length; i++)
71
+ {
72
+ let tmpPanel = tmpFlowData.OpenPanels[i];
73
+ tmpPanelPositions[tmpPanel.NodeHash] =
74
+ {
75
+ X: tmpPanel.X,
76
+ Y: tmpPanel.Y,
77
+ Width: tmpPanel.Width,
78
+ Height: tmpPanel.Height
79
+ };
80
+ }
81
+
82
+ let tmpLayout =
83
+ {
84
+ Hash: tmpLayoutHash,
85
+ Name: pName || 'Untitled Layout',
86
+ CreatedAt: new Date().toISOString(),
87
+ NodePositions: tmpNodePositions,
88
+ PanelPositions: tmpPanelPositions,
89
+ ViewState:
90
+ {
91
+ PanX: tmpFlowData.ViewState.PanX,
92
+ PanY: tmpFlowData.ViewState.PanY,
93
+ Zoom: tmpFlowData.ViewState.Zoom
94
+ }
95
+ };
96
+
97
+ tmpFlowData.SavedLayouts.push(tmpLayout);
98
+ this._FlowView.marshalFromView();
99
+
100
+ if (this._FlowView._EventHandlerProvider)
101
+ {
102
+ this._FlowView._EventHandlerProvider.fireEvent('onLayoutSaved', tmpLayout);
103
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', tmpFlowData);
104
+ }
105
+
106
+ this.log.trace(`PictProviderFlowLayouts saved layout '${tmpLayout.Name}' (${tmpLayout.Hash})`);
107
+
108
+ return tmpLayout;
109
+ }
110
+
111
+ /**
112
+ * Restore a saved layout by hash.
113
+ * Nodes present in the saved layout are placed at their saved positions.
114
+ * Nodes not in the saved layout are auto-laid-out to the right of the
115
+ * positioned nodes.
116
+ * @param {string} pLayoutHash - The hash of the layout to restore
117
+ * @returns {boolean} Whether the layout was restored
118
+ */
119
+ restoreLayout(pLayoutHash)
120
+ {
121
+ if (!this._FlowView)
122
+ {
123
+ this.log.warn('PictProviderFlowLayouts restoreLayout: no FlowView reference');
124
+ return false;
125
+ }
126
+
127
+ let tmpFlowData = this._FlowView._FlowData;
128
+ let tmpLayout = tmpFlowData.SavedLayouts.find(
129
+ (pLayout) => pLayout.Hash === pLayoutHash
130
+ );
131
+
132
+ if (!tmpLayout)
133
+ {
134
+ this.log.warn(`PictProviderFlowLayouts restoreLayout: layout '${pLayoutHash}' not found`);
135
+ return false;
136
+ }
137
+
138
+ let tmpMatchedNodes = [];
139
+ let tmpUnmatchedNodes = [];
140
+
141
+ // Apply saved positions to matched nodes; collect unmatched ones
142
+ for (let i = 0; i < tmpFlowData.Nodes.length; i++)
143
+ {
144
+ let tmpNode = tmpFlowData.Nodes[i];
145
+ let tmpSaved = tmpLayout.NodePositions[tmpNode.Hash];
146
+
147
+ if (tmpSaved)
148
+ {
149
+ tmpNode.X = tmpSaved.X;
150
+ tmpNode.Y = tmpSaved.Y;
151
+ if (typeof tmpSaved.Width === 'number') tmpNode.Width = tmpSaved.Width;
152
+ if (typeof tmpSaved.Height === 'number') tmpNode.Height = tmpSaved.Height;
153
+ tmpMatchedNodes.push(tmpNode);
154
+ }
155
+ else
156
+ {
157
+ tmpUnmatchedNodes.push(tmpNode);
158
+ }
159
+ }
160
+
161
+ // Apply saved panel positions (keyed by NodeHash)
162
+ if (tmpLayout.PanelPositions)
163
+ {
164
+ for (let i = 0; i < tmpFlowData.OpenPanels.length; i++)
165
+ {
166
+ let tmpPanel = tmpFlowData.OpenPanels[i];
167
+ let tmpSavedPanel = tmpLayout.PanelPositions[tmpPanel.NodeHash];
168
+
169
+ if (tmpSavedPanel)
170
+ {
171
+ tmpPanel.X = tmpSavedPanel.X;
172
+ tmpPanel.Y = tmpSavedPanel.Y;
173
+ if (typeof tmpSavedPanel.Width === 'number') tmpPanel.Width = tmpSavedPanel.Width;
174
+ if (typeof tmpSavedPanel.Height === 'number') tmpPanel.Height = tmpSavedPanel.Height;
175
+ }
176
+ }
177
+ }
178
+
179
+ // Auto-layout unmatched nodes to the right of positioned nodes
180
+ if (tmpUnmatchedNodes.length > 0 && this._FlowView._LayoutService)
181
+ {
182
+ this._FlowView._LayoutService.autoLayoutSubset(
183
+ tmpUnmatchedNodes,
184
+ tmpMatchedNodes,
185
+ tmpFlowData.Connections
186
+ );
187
+ }
188
+
189
+ // Restore view state (camera position)
190
+ if (tmpLayout.ViewState)
191
+ {
192
+ if (typeof tmpLayout.ViewState.PanX === 'number')
193
+ {
194
+ tmpFlowData.ViewState.PanX = tmpLayout.ViewState.PanX;
195
+ }
196
+ if (typeof tmpLayout.ViewState.PanY === 'number')
197
+ {
198
+ tmpFlowData.ViewState.PanY = tmpLayout.ViewState.PanY;
199
+ }
200
+ if (typeof tmpLayout.ViewState.Zoom === 'number')
201
+ {
202
+ tmpFlowData.ViewState.Zoom = tmpLayout.ViewState.Zoom;
203
+ }
204
+ }
205
+
206
+ this._FlowView.renderFlow();
207
+ this._FlowView.marshalFromView();
208
+
209
+ if (this._FlowView._EventHandlerProvider)
210
+ {
211
+ this._FlowView._EventHandlerProvider.fireEvent('onLayoutRestored', tmpLayout);
212
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', tmpFlowData);
213
+ }
214
+
215
+ this.log.trace(`PictProviderFlowLayouts restored layout '${tmpLayout.Name}' (${tmpLayout.Hash})`);
216
+
217
+ return true;
218
+ }
219
+
220
+ /**
221
+ * Delete a saved layout by hash.
222
+ * @param {string} pLayoutHash - The hash of the layout to delete
223
+ * @returns {boolean} Whether the layout was deleted
224
+ */
225
+ deleteLayout(pLayoutHash)
226
+ {
227
+ if (!this._FlowView)
228
+ {
229
+ this.log.warn('PictProviderFlowLayouts deleteLayout: no FlowView reference');
230
+ return false;
231
+ }
232
+
233
+ let tmpFlowData = this._FlowView._FlowData;
234
+ let tmpIndex = tmpFlowData.SavedLayouts.findIndex(
235
+ (pLayout) => pLayout.Hash === pLayoutHash
236
+ );
237
+
238
+ if (tmpIndex < 0)
239
+ {
240
+ this.log.warn(`PictProviderFlowLayouts deleteLayout: layout '${pLayoutHash}' not found`);
241
+ return false;
242
+ }
243
+
244
+ let tmpRemovedLayout = tmpFlowData.SavedLayouts.splice(tmpIndex, 1)[0];
245
+ this._FlowView.marshalFromView();
246
+
247
+ if (this._FlowView._EventHandlerProvider)
248
+ {
249
+ this._FlowView._EventHandlerProvider.fireEvent('onLayoutDeleted', tmpRemovedLayout);
250
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', tmpFlowData);
251
+ }
252
+
253
+ this.log.trace(`PictProviderFlowLayouts deleted layout '${tmpRemovedLayout.Name}' (${tmpRemovedLayout.Hash})`);
254
+
255
+ return true;
256
+ }
257
+
258
+ /**
259
+ * Get the list of saved layouts.
260
+ * @returns {Array} Array of saved layout objects
261
+ */
262
+ getLayouts()
263
+ {
264
+ if (!this._FlowView) return [];
265
+ return this._FlowView._FlowData.SavedLayouts;
266
+ }
267
+
268
+ /**
269
+ * Get a specific saved layout by hash.
270
+ * @param {string} pLayoutHash
271
+ * @returns {Object|null}
272
+ */
273
+ getLayout(pLayoutHash)
274
+ {
275
+ if (!this._FlowView) return null;
276
+ return this._FlowView._FlowData.SavedLayouts.find(
277
+ (pLayout) => pLayout.Hash === pLayoutHash
278
+ ) || null;
279
+ }
280
+ }
281
+
282
+ module.exports = PictProviderFlowLayouts;
283
+
284
+ module.exports.default_configuration = _ProviderConfiguration;
@@ -93,6 +93,20 @@ class PictProviderFlowNodeTypes extends libPictProvider
93
93
 
94
94
  // Initialize with default node types
95
95
  this._NodeTypes = JSON.parse(JSON.stringify(_DefaultNodeTypes));
96
+
97
+ // Merge any additional node types passed in via options
98
+ if (pOptions && pOptions.AdditionalNodeTypes && typeof pOptions.AdditionalNodeTypes === 'object')
99
+ {
100
+ let tmpAdditionalKeys = Object.keys(pOptions.AdditionalNodeTypes);
101
+ for (let i = 0; i < tmpAdditionalKeys.length; i++)
102
+ {
103
+ this._NodeTypes[tmpAdditionalKeys[i]] = Object.assign(
104
+ {},
105
+ this._NodeTypes[tmpAdditionalKeys[i]] || {},
106
+ JSON.parse(JSON.stringify(pOptions.AdditionalNodeTypes[tmpAdditionalKeys[i]]))
107
+ );
108
+ }
109
+ }
96
110
  }
97
111
 
98
112
  /**
@@ -166,6 +180,62 @@ class PictProviderFlowNodeTypes extends libPictProvider
166
180
  {
167
181
  return Object.keys(this._NodeTypes);
168
182
  }
183
+
184
+ /**
185
+ * Get all enabled node types that have FlowCard metadata.
186
+ * Returns only types that are cards and whose Enabled flag is true.
187
+ * @returns {Array<Object>} Array of node type configurations
188
+ */
189
+ getEnabledCards()
190
+ {
191
+ let tmpCards = [];
192
+ let tmpKeys = Object.keys(this._NodeTypes);
193
+ for (let i = 0; i < tmpKeys.length; i++)
194
+ {
195
+ let tmpType = this._NodeTypes[tmpKeys[i]];
196
+ if (tmpType.CardMetadata)
197
+ {
198
+ if (tmpType.CardMetadata.Enabled !== false)
199
+ {
200
+ tmpCards.push(JSON.parse(JSON.stringify(tmpType)));
201
+ }
202
+ }
203
+ }
204
+ return tmpCards;
205
+ }
206
+
207
+ /**
208
+ * Get all enabled cards grouped by category.
209
+ * @returns {Object} Map of category name to array of node type configurations
210
+ */
211
+ getCardsByCategory()
212
+ {
213
+ let tmpCards = this.getEnabledCards();
214
+ let tmpCategories = {};
215
+ for (let i = 0; i < tmpCards.length; i++)
216
+ {
217
+ let tmpCategory = (tmpCards[i].CardMetadata && tmpCards[i].CardMetadata.Category)
218
+ ? tmpCards[i].CardMetadata.Category
219
+ : 'General';
220
+ if (!tmpCategories[tmpCategory])
221
+ {
222
+ tmpCategories[tmpCategory] = [];
223
+ }
224
+ tmpCategories[tmpCategory].push(tmpCards[i]);
225
+ }
226
+ return tmpCategories;
227
+ }
228
+
229
+ /**
230
+ * Check whether a node type hash refers to a FlowCard (has CardMetadata).
231
+ * @param {string} pTypeHash
232
+ * @returns {boolean}
233
+ */
234
+ isFlowCard(pTypeHash)
235
+ {
236
+ let tmpType = this._NodeTypes[pTypeHash];
237
+ return !!(tmpType && tmpType.CardMetadata);
238
+ }
169
239
  }
170
240
 
171
241
  module.exports = PictProviderFlowNodeTypes;