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
|
@@ -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;
|