pict-section-flow 0.0.19 → 1.0.1

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.
@@ -0,0 +1,516 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ const _ProviderConfiguration =
4
+ {
5
+ ProviderIdentifier: 'PictProviderFlowRenderer'
6
+ };
7
+
8
+ /**
9
+ * PictProvider-Flow-Renderer
10
+ *
11
+ * Registry + active-state holder for the FLOW RENDERER axis.
12
+ *
13
+ * A "renderer" answers: *how do we draw a flow* — node body shape, border
14
+ * jitter, connection stroke style, arrowhead style, supplementary CSS for
15
+ * text treatment. It is **independent of color**. Color comes from the
16
+ * active pict-section-theme theme; the renderer only describes the visual
17
+ * vocabulary applied on top of those colors.
18
+ *
19
+ * This is the analogue of `EdgeTheme` for node + arrow + filter treatment.
20
+ * See PictService-Flow-Layout.js for the EdgeTheme pattern this mirrors.
21
+ *
22
+ * ## Renderer shape
23
+ *
24
+ * ```javascript
25
+ * {
26
+ * Key: 'sketch',
27
+ * Label: 'Sketch (jittery)',
28
+ *
29
+ * NodeBodyMode: 'bracket', // 'rect' | 'bracket'
30
+ * BracketConfig: { SerifLength: 20, TitleSeparator: true },
31
+ *
32
+ * NoiseConfig: { // hand-drawn jitter
33
+ * Enabled: true,
34
+ * DefaultLevel: 0.4,
35
+ * MaxJitterPx: 4,
36
+ * AffectsNodes: true,
37
+ * AffectsConnections: true
38
+ * },
39
+ *
40
+ * ConnectionConfig: { // edge stroke style
41
+ * StrokeDashArray: null,
42
+ * StrokeWidth: 1.5,
43
+ * ArrowheadStyle: 'triangle'
44
+ * },
45
+ *
46
+ * ShapeOverrides: { // per-shape SVG attrs
47
+ * 'arrowhead-connection': { Fill: 'var(--theme-color-text-secondary, #555)' },
48
+ * 'arrowhead-connection-selected': { Fill: 'var(--theme-color-brand-primary, #2255aa)' }
49
+ * },
50
+ *
51
+ * GeometryCSS: '', // optional :root :root rules for
52
+ * // --pf-node-body-stroke-width,
53
+ * // --pf-node-body-radius,
54
+ * // --pf-node-shadow*,
55
+ * // --pf-panel-shadow, --pf-panel-radius,
56
+ * // --pf-node-title-size/weight, etc.
57
+ *
58
+ * AdditionalCSS: '' // optional per-renderer extras
59
+ * // (font-family swaps, filters, scanlines)
60
+ * }
61
+ * ```
62
+ *
63
+ * ## API
64
+ *
65
+ * ```javascript
66
+ * pict.providers['PictProviderFlowRenderer'].register(key, definition);
67
+ * pict.providers['PictProviderFlowRenderer'].setRenderer('sketch');
68
+ * pict.providers['PictProviderFlowRenderer'].getActiveRenderer();
69
+ * pict.providers['PictProviderFlowRenderer'].getActiveRendererKey();
70
+ * pict.providers['PictProviderFlowRenderer'].getRendererKeys();
71
+ * pict.providers['PictProviderFlowRenderer'].getNoiseLevel();
72
+ * pict.providers['PictProviderFlowRenderer'].setNoiseLevel(0.5);
73
+ * pict.providers['PictProviderFlowRenderer'].getNodeNoiseAmplitude();
74
+ * pict.providers['PictProviderFlowRenderer'].processPathString(d, seed);
75
+ * ```
76
+ *
77
+ * The FlowView wires this provider's GeometryCSS + AdditionalCSS into the
78
+ * CSS cascade on each `setRenderer()` call, plus applies ShapeOverrides
79
+ * through ConnectorShapesProvider (same flow the old monolithic theme used).
80
+ */
81
+ class PictProviderFlowRenderer extends libFableServiceProviderBase
82
+ {
83
+ constructor(pFable, pOptions, pServiceHash)
84
+ {
85
+ let tmpOptions = Object.assign({}, _ProviderConfiguration, pOptions);
86
+ super(pFable, tmpOptions, pServiceHash);
87
+
88
+ this.serviceType = 'PictProviderFlowRenderer';
89
+
90
+ this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
91
+
92
+ this._ActiveRendererKey = 'clean';
93
+ this._NoiseLevel = 0;
94
+ this._Renderers = {};
95
+
96
+ this._registerBuiltInRenderers();
97
+ }
98
+
99
+ // ── Registry ──────────────────────────────────────────────────────────
100
+
101
+ _registerBuiltInRenderers()
102
+ {
103
+ // 1. Clean — rectangle bodies, soft shadows, no jitter. Modern default.
104
+ this._Renderers['clean'] =
105
+ {
106
+ Key: 'clean',
107
+ Label: 'Clean',
108
+ NodeBodyMode: 'rect',
109
+ BracketConfig: null,
110
+ NoiseConfig:
111
+ {
112
+ Enabled: false,
113
+ DefaultLevel: 0,
114
+ MaxJitterPx: 0,
115
+ AffectsNodes: false,
116
+ AffectsConnections: false
117
+ },
118
+ ConnectionConfig:
119
+ {
120
+ StrokeDashArray: null,
121
+ StrokeWidth: 2,
122
+ ArrowheadStyle: 'triangle'
123
+ },
124
+ ShapeOverrides: {},
125
+ GeometryCSS: `
126
+ .pict-flow-container {
127
+ --pf-node-body-stroke-width: 1.5;
128
+ --pf-node-body-radius: 6px;
129
+ --pf-node-shadow: drop-shadow(0 1px 3px rgba(0,0,0,0.10));
130
+ --pf-node-shadow-hover: drop-shadow(0 2px 6px rgba(0,0,0,0.15));
131
+ --pf-node-shadow-selected: drop-shadow(0 0 6px var(--theme-color-brand-primary, #2255aa));
132
+ --pf-node-shadow-dragging: drop-shadow(0 4px 10px rgba(0,0,0,0.20));
133
+ --pf-node-title-size: 12px;
134
+ --pf-node-title-weight: 600;
135
+ --pf-panel-radius: 8px;
136
+ --pf-panel-shadow: 0 2px 8px rgba(0,0,0,0.12);
137
+ }
138
+ `,
139
+ AdditionalCSS: ''
140
+ };
141
+
142
+ // 2. Bracket — `[ ]` body, no jitter. Sketch-like but precise.
143
+ this._Renderers['bracket'] =
144
+ {
145
+ Key: 'bracket',
146
+ Label: 'Bracket',
147
+ NodeBodyMode: 'bracket',
148
+ BracketConfig:
149
+ {
150
+ SerifLength: 20,
151
+ TitleSeparator: true
152
+ },
153
+ NoiseConfig:
154
+ {
155
+ Enabled: false,
156
+ DefaultLevel: 0,
157
+ MaxJitterPx: 0,
158
+ AffectsNodes: false,
159
+ AffectsConnections: false
160
+ },
161
+ ConnectionConfig:
162
+ {
163
+ StrokeDashArray: null,
164
+ StrokeWidth: 1.5,
165
+ ArrowheadStyle: 'triangle'
166
+ },
167
+ ShapeOverrides:
168
+ {
169
+ 'arrowhead-connection': { Fill: 'var(--theme-color-text-secondary, #555555)' },
170
+ 'arrowhead-connection-selected': { Fill: 'var(--theme-color-brand-primary, #2255aa)' }
171
+ },
172
+ GeometryCSS: `
173
+ .pict-flow-container {
174
+ --pf-node-body-stroke-width: 1.5;
175
+ --pf-node-body-radius: 0px;
176
+ --pf-node-shadow: none;
177
+ --pf-node-shadow-hover: none;
178
+ --pf-node-shadow-selected: none;
179
+ --pf-node-shadow-dragging: none;
180
+ --pf-node-title-size: 12px;
181
+ --pf-node-title-weight: 500;
182
+ --pf-panel-radius: 0px;
183
+ --pf-panel-shadow: 2px 2px 0px rgba(0,0,0,0.08);
184
+ }
185
+ `,
186
+ AdditionalCSS: ''
187
+ };
188
+
189
+ // 3. Sketch — bracket + jitter on nodes and connections. Hand-drawn.
190
+ this._Renderers['sketch'] =
191
+ {
192
+ Key: 'sketch',
193
+ Label: 'Sketch',
194
+ NodeBodyMode: 'bracket',
195
+ BracketConfig:
196
+ {
197
+ SerifLength: 20,
198
+ TitleSeparator: true
199
+ },
200
+ NoiseConfig:
201
+ {
202
+ Enabled: true,
203
+ DefaultLevel: 0.4,
204
+ MaxJitterPx: 4,
205
+ AffectsNodes: true,
206
+ AffectsConnections: true
207
+ },
208
+ ConnectionConfig:
209
+ {
210
+ StrokeDashArray: null,
211
+ StrokeWidth: 1.5,
212
+ ArrowheadStyle: 'triangle'
213
+ },
214
+ ShapeOverrides:
215
+ {
216
+ 'arrowhead-connection': { Fill: 'var(--theme-color-text-secondary, #555555)' },
217
+ 'arrowhead-connection-selected': { Fill: 'var(--theme-color-brand-primary, #2255aa)' }
218
+ },
219
+ GeometryCSS: `
220
+ .pict-flow-container {
221
+ --pf-node-body-stroke-width: 1.5;
222
+ --pf-node-body-radius: 0px;
223
+ --pf-node-shadow: none;
224
+ --pf-node-shadow-hover: none;
225
+ --pf-node-shadow-selected: none;
226
+ --pf-node-shadow-dragging: none;
227
+ --pf-node-title-size: 12px;
228
+ --pf-node-title-weight: 400;
229
+ --pf-panel-radius: 0px;
230
+ --pf-panel-shadow: 2px 2px 0px rgba(0,0,0,0.08);
231
+ }
232
+ `,
233
+ AdditionalCSS: `
234
+ .pict-flow-node-title,
235
+ .pict-flow-node-type-label,
236
+ .pict-flow-port-label,
237
+ .pict-flow-node-card-code {
238
+ font-family: "Courier New", "Courier", monospace !important;
239
+ }
240
+ .pict-flow-panel-title-text,
241
+ .pict-flow-panel-node-props-title,
242
+ .pict-flow-info-panel {
243
+ font-family: "Courier New", "Courier", monospace !important;
244
+ }
245
+ .pict-flow-node-title-icon {
246
+ filter: brightness(0) !important;
247
+ }
248
+ `
249
+ };
250
+
251
+ // 4. CRT — rectangular body + neon drop-shadow glow. No jitter, but a
252
+ // scanline / monospace text treatment for retro vibes.
253
+ this._Renderers['crt'] =
254
+ {
255
+ Key: 'crt',
256
+ Label: 'CRT',
257
+ NodeBodyMode: 'rect',
258
+ BracketConfig: null,
259
+ NoiseConfig:
260
+ {
261
+ Enabled: false,
262
+ DefaultLevel: 0,
263
+ MaxJitterPx: 0,
264
+ AffectsNodes: false,
265
+ AffectsConnections: false
266
+ },
267
+ ConnectionConfig:
268
+ {
269
+ StrokeDashArray: null,
270
+ StrokeWidth: 2,
271
+ ArrowheadStyle: 'triangle'
272
+ },
273
+ ShapeOverrides:
274
+ {
275
+ 'arrowhead-connection': { Fill: 'var(--theme-color-brand-primary, #ff00ff)' },
276
+ 'arrowhead-connection-selected': { Fill: 'var(--theme-color-brand-accent, #00ffff)' }
277
+ },
278
+ GeometryCSS: `
279
+ .pict-flow-container {
280
+ --pf-node-body-stroke-width: 2;
281
+ --pf-node-body-radius: 0px;
282
+ --pf-node-shadow: drop-shadow(0 0 8px color-mix(in srgb, var(--theme-color-brand-primary, #ff00ff) 40%, transparent));
283
+ --pf-node-shadow-hover: drop-shadow(0 0 12px color-mix(in srgb, var(--theme-color-brand-primary, #ff00ff) 60%, transparent));
284
+ --pf-node-shadow-selected: drop-shadow(0 0 16px color-mix(in srgb, var(--theme-color-brand-accent, #00ffff) 50%, transparent));
285
+ --pf-node-shadow-dragging: drop-shadow(0 0 20px color-mix(in srgb, var(--theme-color-brand-primary, #ff00ff) 70%, transparent));
286
+ --pf-node-title-size: 11px;
287
+ --pf-node-title-weight: 700;
288
+ --pf-panel-radius: 0px;
289
+ --pf-panel-shadow: 0 0 20px color-mix(in srgb, var(--theme-color-brand-primary, #ff00ff) 30%, transparent);
290
+ }
291
+ `,
292
+ AdditionalCSS: `
293
+ .pict-flow-node-title,
294
+ .pict-flow-node-type-label,
295
+ .pict-flow-port-label,
296
+ .pict-flow-node-card-code {
297
+ font-family: "Courier New", monospace !important;
298
+ text-transform: uppercase;
299
+ letter-spacing: 0.5px;
300
+ }
301
+ .pict-flow-connection {
302
+ filter: drop-shadow(0 0 3px color-mix(in srgb, var(--theme-color-brand-primary, #ff00ff) 40%, transparent));
303
+ }
304
+ `
305
+ };
306
+
307
+ // 5. Workstation — chunky 90s OS aesthetic. Rectangle with hard offset
308
+ // drop-shadow, no jitter, weighty title bar.
309
+ this._Renderers['workstation'] =
310
+ {
311
+ Key: 'workstation',
312
+ Label: 'Workstation',
313
+ NodeBodyMode: 'rect',
314
+ BracketConfig: null,
315
+ NoiseConfig:
316
+ {
317
+ Enabled: false,
318
+ DefaultLevel: 0,
319
+ MaxJitterPx: 0,
320
+ AffectsNodes: false,
321
+ AffectsConnections: false
322
+ },
323
+ ConnectionConfig:
324
+ {
325
+ StrokeDashArray: null,
326
+ StrokeWidth: 2,
327
+ ArrowheadStyle: 'triangle'
328
+ },
329
+ ShapeOverrides:
330
+ {
331
+ 'arrowhead-connection': { Fill: 'var(--theme-color-border-default, #808080)' },
332
+ 'arrowhead-connection-selected': { Fill: 'var(--theme-color-focus-outline, #008080)' }
333
+ },
334
+ GeometryCSS: `
335
+ .pict-flow-container {
336
+ --pf-node-body-stroke-width: 1;
337
+ --pf-node-body-radius: 0px;
338
+ --pf-node-shadow: drop-shadow(2px 2px 0px var(--theme-color-border-strong, #404040));
339
+ --pf-node-shadow-hover: drop-shadow(3px 3px 0px var(--theme-color-border-strong, #404040));
340
+ --pf-node-shadow-selected: drop-shadow(2px 2px 0px var(--theme-color-focus-outline, #008080));
341
+ --pf-node-shadow-dragging: drop-shadow(4px 4px 0px var(--theme-color-border-strong, #404040));
342
+ --pf-node-title-size: 11px;
343
+ --pf-node-title-weight: 700;
344
+ --pf-panel-radius: 0px;
345
+ --pf-panel-shadow: 2px 2px 0px var(--theme-color-border-strong, #404040);
346
+ }
347
+ `,
348
+ AdditionalCSS: `
349
+ .pict-flow-node-title,
350
+ .pict-flow-node-type-label,
351
+ .pict-flow-port-label,
352
+ .pict-flow-node-card-code {
353
+ font-family: "MS Sans Serif", "Arial", sans-serif !important;
354
+ }
355
+ `
356
+ };
357
+ }
358
+
359
+ // ── Public API ────────────────────────────────────────────────────────
360
+
361
+ /**
362
+ * Get the active renderer definition.
363
+ * @returns {Object}
364
+ */
365
+ getActiveRenderer()
366
+ {
367
+ return this._Renderers[this._ActiveRendererKey] || this._Renderers['clean'];
368
+ }
369
+
370
+ /**
371
+ * Get the active renderer key.
372
+ * @returns {string}
373
+ */
374
+ getActiveRendererKey()
375
+ {
376
+ return this._ActiveRendererKey;
377
+ }
378
+
379
+ /**
380
+ * Switch the active renderer. Updates the noise default, applies shape
381
+ * overrides, and (if available) hands the GeometryCSS + AdditionalCSS
382
+ * to the FlowView's CSS provider for re-injection.
383
+ *
384
+ * @param {string} pRendererKey
385
+ * @returns {boolean}
386
+ */
387
+ setRenderer(pRendererKey)
388
+ {
389
+ if (!this._Renderers[pRendererKey])
390
+ {
391
+ this.log.warn(`PictProviderFlowRenderer: renderer '${pRendererKey}' not found`);
392
+ return false;
393
+ }
394
+
395
+ this._ActiveRendererKey = pRendererKey;
396
+ let tmpRenderer = this._Renderers[pRendererKey];
397
+
398
+ // Apply noise defaults from the renderer
399
+ if (tmpRenderer.NoiseConfig && typeof tmpRenderer.NoiseConfig.DefaultLevel === 'number')
400
+ {
401
+ this._NoiseLevel = tmpRenderer.NoiseConfig.DefaultLevel;
402
+ }
403
+ else
404
+ {
405
+ this._NoiseLevel = 0;
406
+ }
407
+
408
+ // Apply shape overrides through the connector-shapes provider
409
+ if (this._FlowView && this._FlowView._ConnectorShapesProvider)
410
+ {
411
+ this._FlowView._ConnectorShapesProvider.resetToDefaults();
412
+ if (tmpRenderer.ShapeOverrides && Object.keys(tmpRenderer.ShapeOverrides).length > 0)
413
+ {
414
+ this._FlowView._ConnectorShapesProvider.applyThemeOverrides(tmpRenderer.ShapeOverrides);
415
+ }
416
+ }
417
+
418
+ this.log.trace(`PictProviderFlowRenderer: switched to '${pRendererKey}'`);
419
+ return true;
420
+ }
421
+
422
+ /**
423
+ * Register a custom renderer.
424
+ * @param {string} pKey
425
+ * @param {Object} pDefinition
426
+ */
427
+ register(pKey, pDefinition)
428
+ {
429
+ if (!pKey || !pDefinition)
430
+ {
431
+ this.log.warn('PictProviderFlowRenderer: register requires key and definition');
432
+ return;
433
+ }
434
+ pDefinition.Key = pKey;
435
+ this._Renderers[pKey] = pDefinition;
436
+ }
437
+
438
+ /**
439
+ * Get all registered renderer keys.
440
+ * @returns {Array<string>}
441
+ */
442
+ getRendererKeys()
443
+ {
444
+ return Object.keys(this._Renderers);
445
+ }
446
+
447
+ // ── Noise APIs (kept on this provider so consumers don't depend on the
448
+ // legacy Flow-Theme shim) ──────────────────────────────────────────
449
+
450
+ /**
451
+ * Get the current noise level (0 to 1).
452
+ * @returns {number}
453
+ */
454
+ getNoiseLevel()
455
+ {
456
+ return this._NoiseLevel;
457
+ }
458
+
459
+ /**
460
+ * Set the noise level (0 to 1).
461
+ * @param {number} pLevel
462
+ */
463
+ setNoiseLevel(pLevel)
464
+ {
465
+ this._NoiseLevel = Math.max(0, Math.min(1, pLevel || 0));
466
+ }
467
+
468
+ /**
469
+ * Get the noise amplitude for node bracket rendering.
470
+ * Returns 0 if the active renderer disables noise for nodes.
471
+ * @returns {number}
472
+ */
473
+ getNodeNoiseAmplitude()
474
+ {
475
+ let tmpRenderer = this.getActiveRenderer();
476
+ if (!tmpRenderer || !tmpRenderer.NoiseConfig || !tmpRenderer.NoiseConfig.Enabled || !tmpRenderer.NoiseConfig.AffectsNodes)
477
+ {
478
+ return 0;
479
+ }
480
+ return this._NoiseLevel * (tmpRenderer.NoiseConfig.MaxJitterPx || 3);
481
+ }
482
+
483
+ /**
484
+ * Post-process an SVG path string to apply jitter when the active
485
+ * renderer enables noise for connections.
486
+ *
487
+ * @param {string} pPathString
488
+ * @param {string} pSeedString
489
+ * @returns {string}
490
+ */
491
+ processPathString(pPathString, pSeedString)
492
+ {
493
+ let tmpRenderer = this.getActiveRenderer();
494
+ if (!tmpRenderer || !tmpRenderer.NoiseConfig || !tmpRenderer.NoiseConfig.Enabled || !tmpRenderer.NoiseConfig.AffectsConnections)
495
+ {
496
+ return pPathString;
497
+ }
498
+
499
+ let tmpAmplitude = this._NoiseLevel * (tmpRenderer.NoiseConfig.MaxJitterPx || 3);
500
+ if (tmpAmplitude <= 0)
501
+ {
502
+ return pPathString;
503
+ }
504
+
505
+ if (this._FlowView && this._FlowView._NoiseProvider)
506
+ {
507
+ return this._FlowView._NoiseProvider.jitterPath(pPathString, tmpAmplitude, pSeedString);
508
+ }
509
+
510
+ return pPathString;
511
+ }
512
+ }
513
+
514
+ module.exports = PictProviderFlowRenderer;
515
+
516
+ module.exports.default_configuration = _ProviderConfiguration;