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.
Files changed (38) hide show
  1. package/.claude/launch.json +11 -0
  2. package/docs/README.md +51 -0
  3. package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +105 -0
  4. package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +36 -0
  5. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +42 -0
  6. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +1 -1
  7. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +1 -1
  8. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +1 -1
  9. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +1 -1
  10. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +1 -1
  11. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +1 -1
  12. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +1 -1
  13. package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +98 -0
  14. package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +44 -0
  15. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +1 -1
  16. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +9 -1
  17. package/package.json +2 -2
  18. package/source/Pict-Section-Flow.js +8 -1
  19. package/source/PictFlowCard.js +49 -1
  20. package/source/providers/PictProvider-Flow-CSS.js +1440 -0
  21. package/source/providers/PictProvider-Flow-ConnectorShapes.js +413 -0
  22. package/source/providers/PictProvider-Flow-Geometry.js +43 -0
  23. package/source/providers/PictProvider-Flow-Icons.js +335 -0
  24. package/source/providers/PictProvider-Flow-Layouts.js +214 -2
  25. package/source/providers/PictProvider-Flow-NodeTypes.js +30 -7
  26. package/source/providers/PictProvider-Flow-Noise.js +241 -0
  27. package/source/providers/PictProvider-Flow-PanelChrome.js +19 -0
  28. package/source/providers/PictProvider-Flow-Theme.js +755 -0
  29. package/source/services/PictService-Flow-ConnectionRenderer.js +95 -32
  30. package/source/services/PictService-Flow-PanelManager.js +188 -0
  31. package/source/services/PictService-Flow-SelectionManager.js +109 -0
  32. package/source/services/PictService-Flow-Tether.js +52 -25
  33. package/source/services/PictService-Flow-ViewportManager.js +176 -0
  34. package/source/views/PictView-Flow-FloatingToolbar.js +352 -0
  35. package/source/views/PictView-Flow-Node.js +654 -169
  36. package/source/views/PictView-Flow-PropertiesPanel.js +176 -1
  37. package/source/views/PictView-Flow-Toolbar.js +846 -379
  38. package/source/views/PictView-Flow.js +279 -671
@@ -0,0 +1,241 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ const _ProviderConfiguration =
4
+ {
5
+ ProviderIdentifier: 'PictProviderFlowNoise'
6
+ };
7
+
8
+ /**
9
+ * PictProvider-Flow-Noise
10
+ *
11
+ * Deterministic noise/jitter generator for hand-drawn visual effects.
12
+ *
13
+ * Uses seeded pseudo-random number generation so that the same node or
14
+ * connection always receives the same jitter values across re-renders,
15
+ * preventing visual "jumping" while still looking organic.
16
+ */
17
+ class PictProviderFlowNoise extends libFableServiceProviderBase
18
+ {
19
+ constructor(pFable, pOptions, pServiceHash)
20
+ {
21
+ let tmpOptions = Object.assign({}, _ProviderConfiguration, pOptions);
22
+ super(pFable, tmpOptions, pServiceHash);
23
+
24
+ this.serviceType = 'PictProviderFlowNoise';
25
+ }
26
+
27
+ // ── Hashing / PRNG ────────────────────────────────────────────────────
28
+
29
+ /**
30
+ * Convert a string to a 32-bit integer hash (djb2 algorithm).
31
+ * @param {string} pStr
32
+ * @returns {number}
33
+ */
34
+ hashString(pStr)
35
+ {
36
+ let tmpHash = 5381;
37
+ for (let i = 0; i < pStr.length; i++)
38
+ {
39
+ tmpHash = ((tmpHash << 5) + tmpHash) + pStr.charCodeAt(i);
40
+ tmpHash = tmpHash & tmpHash; // Convert to 32-bit integer
41
+ }
42
+ return tmpHash >>> 0; // Ensure unsigned
43
+ }
44
+
45
+ /**
46
+ * Create a seeded pseudo-random number generator (Mulberry32).
47
+ * Returns a function that produces deterministic floats in [0, 1).
48
+ * @param {number} pSeed - 32-bit integer seed
49
+ * @returns {Function}
50
+ */
51
+ seededRandom(pSeed)
52
+ {
53
+ let tmpSeed = pSeed | 0;
54
+ return function()
55
+ {
56
+ tmpSeed = tmpSeed + 0x6D2B79F5 | 0;
57
+ let t = Math.imul(tmpSeed ^ tmpSeed >>> 15, 1 | tmpSeed);
58
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
59
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
60
+ };
61
+ }
62
+
63
+ // ── Point Jitter ──────────────────────────────────────────────────────
64
+
65
+ /**
66
+ * Apply random jitter to a point.
67
+ * @param {number} pX
68
+ * @param {number} pY
69
+ * @param {number} pAmplitude - Maximum offset in pixels
70
+ * @param {Function} pRNG - Seeded random function
71
+ * @returns {{x: number, y: number}}
72
+ */
73
+ jitterPoint(pX, pY, pAmplitude, pRNG)
74
+ {
75
+ if (pAmplitude <= 0)
76
+ {
77
+ return { x: pX, y: pY };
78
+ }
79
+ return {
80
+ x: pX + pAmplitude * (pRNG() - 0.5) * 2,
81
+ y: pY + pAmplitude * (pRNG() - 0.5) * 2
82
+ };
83
+ }
84
+
85
+ // ── Bracket Path Generation ───────────────────────────────────────────
86
+
87
+ /**
88
+ * Generate an SVG path `d` string for a bracket-shaped node border.
89
+ *
90
+ * Draws true bracket shapes — `[` on the left and `]` on the right —
91
+ * with NO top/bottom connecting lines. The serifs (horizontal turns)
92
+ * extend inward from each corner, giving a distinctive hand-drawn
93
+ * technical-diagram look that is immediately distinguishable from a
94
+ * regular rectangle at any zoom level.
95
+ *
96
+ * The bracket consists of:
97
+ * - Left bracket `[`: top serif → vertical left side → bottom serif
98
+ * - Right bracket `]`: top serif → vertical right side → bottom serif
99
+ * - Optional title divider line across the full width
100
+ *
101
+ * @param {number} pWidth - Node width
102
+ * @param {number} pHeight - Node height
103
+ * @param {number} pSerifLength - Length of corner serifs in px
104
+ * @param {number} pTitleBarHeight - Height of title bar (0 to skip divider)
105
+ * @param {number} pAmplitude - Noise amplitude (0 = precise)
106
+ * @param {string} pSeedString - Hash string for deterministic noise
107
+ * @returns {string} SVG path d attribute
108
+ */
109
+ generateBracketPath(pWidth, pHeight, pSerifLength, pTitleBarHeight, pAmplitude, pSeedString)
110
+ {
111
+ let tmpRNG = this.seededRandom(this.hashString(pSeedString || 'default'));
112
+ let tmpS = pSerifLength || 18;
113
+ let tmpW = pWidth;
114
+ let tmpH = pHeight;
115
+
116
+ let tmpJ = (pX, pY) =>
117
+ {
118
+ return this.jitterPoint(pX, pY, pAmplitude, tmpRNG);
119
+ };
120
+
121
+ // Left bracket `[`: top serif → down left side → bottom serif
122
+ let tmpTL_serif = tmpJ(tmpS, 0);
123
+ let tmpTL_corner = tmpJ(0, 0);
124
+ let tmpBL_corner = tmpJ(0, tmpH);
125
+ let tmpBL_serif = tmpJ(tmpS, tmpH);
126
+
127
+ // Right bracket `]`: top serif → down right side → bottom serif
128
+ let tmpTR_serif = tmpJ(tmpW - tmpS, 0);
129
+ let tmpTR_corner = tmpJ(tmpW, 0);
130
+ let tmpBR_corner = tmpJ(tmpW, tmpH);
131
+ let tmpBR_serif = tmpJ(tmpW - tmpS, tmpH);
132
+
133
+ let tmpPath = '';
134
+
135
+ // Left bracket `[`
136
+ tmpPath += `M ${tmpTL_serif.x.toFixed(1)} ${tmpTL_serif.y.toFixed(1)}`;
137
+ tmpPath += ` L ${tmpTL_corner.x.toFixed(1)} ${tmpTL_corner.y.toFixed(1)}`;
138
+ tmpPath += ` L ${tmpBL_corner.x.toFixed(1)} ${tmpBL_corner.y.toFixed(1)}`;
139
+ tmpPath += ` L ${tmpBL_serif.x.toFixed(1)} ${tmpBL_serif.y.toFixed(1)}`;
140
+
141
+ // Right bracket `]`
142
+ tmpPath += ` M ${tmpTR_serif.x.toFixed(1)} ${tmpTR_serif.y.toFixed(1)}`;
143
+ tmpPath += ` L ${tmpTR_corner.x.toFixed(1)} ${tmpTR_corner.y.toFixed(1)}`;
144
+ tmpPath += ` L ${tmpBR_corner.x.toFixed(1)} ${tmpBR_corner.y.toFixed(1)}`;
145
+ tmpPath += ` L ${tmpBR_serif.x.toFixed(1)} ${tmpBR_serif.y.toFixed(1)}`;
146
+
147
+ // No horizontal lines — the title bar fill rect provides visual
148
+ // separation via its background color. The bracket outline is
149
+ // purely the `[` and `]` shapes on the sides.
150
+
151
+ return tmpPath;
152
+ }
153
+
154
+ // ── Path Jitter (for connections) ─────────────────────────────────────
155
+
156
+ /**
157
+ * Apply jitter to an existing SVG path string by offsetting coordinate
158
+ * pairs. The first M and last coordinate pair receive reduced jitter
159
+ * to keep connections aligned with their port anchors.
160
+ *
161
+ * @param {string} pPathString - SVG path d attribute
162
+ * @param {number} pAmplitude - Noise amplitude (0 = no change)
163
+ * @param {string} pSeedString - Hash string for deterministic noise
164
+ * @returns {string} Modified path string
165
+ */
166
+ jitterPath(pPathString, pAmplitude, pSeedString)
167
+ {
168
+ if (pAmplitude <= 0 || !pPathString)
169
+ {
170
+ return pPathString;
171
+ }
172
+
173
+ let tmpRNG = this.seededRandom(this.hashString(pSeedString || 'path'));
174
+
175
+ // Parse path into tokens: commands and numbers
176
+ let tmpTokens = pPathString.match(/[MLCQZmlcqz]|[-+]?[0-9]*\.?[0-9]+/g);
177
+ if (!tmpTokens)
178
+ {
179
+ return pPathString;
180
+ }
181
+
182
+ // Collect all numeric coordinate indices
183
+ let tmpNumericIndices = [];
184
+ for (let i = 0; i < tmpTokens.length; i++)
185
+ {
186
+ if (/^[-+]?[0-9]*\.?[0-9]+$/.test(tmpTokens[i]))
187
+ {
188
+ tmpNumericIndices.push(i);
189
+ }
190
+ }
191
+
192
+ // Process pairs of coordinates (x, y)
193
+ for (let i = 0; i < tmpNumericIndices.length - 1; i += 2)
194
+ {
195
+ let tmpXIdx = tmpNumericIndices[i];
196
+ let tmpYIdx = tmpNumericIndices[i + 1];
197
+
198
+ // Reduce jitter for first and last coordinate pairs (port anchors)
199
+ let tmpLocalAmplitude = pAmplitude;
200
+ if (i === 0 || i >= tmpNumericIndices.length - 2)
201
+ {
202
+ tmpLocalAmplitude = pAmplitude * 0.15; // Minimal anchor jitter
203
+ }
204
+ else if (i === 2 || i >= tmpNumericIndices.length - 4)
205
+ {
206
+ tmpLocalAmplitude = pAmplitude * 0.5; // Reduced near anchors
207
+ }
208
+
209
+ let tmpX = parseFloat(tmpTokens[tmpXIdx]);
210
+ let tmpY = parseFloat(tmpTokens[tmpYIdx]);
211
+ let tmpJittered = this.jitterPoint(tmpX, tmpY, tmpLocalAmplitude, tmpRNG);
212
+
213
+ tmpTokens[tmpXIdx] = tmpJittered.x.toFixed(1);
214
+ tmpTokens[tmpYIdx] = tmpJittered.y.toFixed(1);
215
+ }
216
+
217
+ // Reassemble path string with spaces
218
+ let tmpResult = '';
219
+ for (let i = 0; i < tmpTokens.length; i++)
220
+ {
221
+ if (i > 0 && /^[MLCQZmlcqz]$/.test(tmpTokens[i]))
222
+ {
223
+ tmpResult += ' ' + tmpTokens[i];
224
+ }
225
+ else if (i > 0)
226
+ {
227
+ tmpResult += ' ' + tmpTokens[i];
228
+ }
229
+ else
230
+ {
231
+ tmpResult += tmpTokens[i];
232
+ }
233
+ }
234
+
235
+ return tmpResult;
236
+ }
237
+ }
238
+
239
+ module.exports = PictProviderFlowNoise;
240
+
241
+ module.exports.default_configuration = _ProviderConfiguration;
@@ -54,6 +54,17 @@ class PictProviderFlowPanelChrome extends libFableServiceProviderBase
54
54
 
55
55
  tmpFO.innerHTML = tmpChromeHTML;
56
56
 
57
+ // Populate the close button icon
58
+ let tmpCloseIcon = tmpFO.querySelector('.pict-flow-panel-close-icon');
59
+ if (tmpCloseIcon && this._FlowView && this._FlowView._IconProvider)
60
+ {
61
+ tmpCloseIcon.innerHTML = this._FlowView._IconProvider.getIconSVGMarkup('close', 12);
62
+ }
63
+ else if (tmpCloseIcon)
64
+ {
65
+ tmpCloseIcon.textContent = '\u2715';
66
+ }
67
+
57
68
  // Attach event isolation to the panel body so pointer/wheel events
58
69
  // inside the panel content do not trigger SVG interactions
59
70
  let tmpBody = tmpFO.querySelector('.pict-flow-panel-body');
@@ -63,6 +74,14 @@ class PictProviderFlowPanelChrome extends libFableServiceProviderBase
63
74
  tmpBody.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
64
75
  }
65
76
 
77
+ // Isolate events on the collapsible node properties editor section
78
+ let tmpNodeProps = tmpFO.querySelector('.pict-flow-panel-node-props');
79
+ if (tmpNodeProps)
80
+ {
81
+ tmpNodeProps.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
82
+ tmpNodeProps.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
83
+ }
84
+
66
85
  pPanelsLayer.appendChild(tmpFO);
67
86
 
68
87
  return tmpBody;