pict-section-flow 1.0.0 → 1.1.0
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/README.md +44 -13
- package/docs/Architecture.md +8 -148
- package/docs/Data_Model.md +2 -11
- package/docs/README.md +8 -38
- package/docs/Theme_Integration.md +13 -13
- package/docs/_cover.md +7 -1
- package/docs/_playground.json +24 -0
- package/docs/_sidebar.md +4 -0
- package/docs/_topbar.md +1 -1
- package/docs/_version.json +3 -3
- package/docs/card-help/FREAD.md +1 -1
- package/docs/diagrams/architecture-at-a-glance.excalidraw +4270 -0
- package/docs/diagrams/architecture-at-a-glance.mmd +30 -0
- package/docs/diagrams/architecture-at-a-glance.svg +2 -0
- package/docs/diagrams/data-flow.excalidraw +1451 -0
- package/docs/diagrams/data-flow.mmd +17 -0
- package/docs/diagrams/data-flow.svg +2 -0
- package/docs/diagrams/high-level-design.excalidraw +5767 -0
- package/docs/diagrams/high-level-design.mmd +86 -0
- package/docs/diagrams/high-level-design.svg +2 -0
- package/docs/diagrams/relationships.excalidraw +3852 -0
- package/docs/diagrams/relationships.mmd +9 -0
- package/docs/diagrams/relationships.svg +2 -0
- package/docs/diagrams/service-initialization-sequence.excalidraw +1466 -0
- package/docs/diagrams/service-initialization-sequence.mmd +19 -0
- package/docs/diagrams/service-initialization-sequence.svg +2 -0
- package/docs/diagrams/svg-layer-structure.excalidraw +1060 -0
- package/docs/diagrams/svg-layer-structure.mmd +18 -0
- package/docs/diagrams/svg-layer-structure.svg +2 -0
- package/docs/examples/README.md +9 -0
- package/docs/examples/simple_cards/README.md +677 -0
- package/docs/examples/simple_cards/css/flowexample.css +65 -0
- package/docs/examples/simple_cards/index.html +32 -0
- package/docs/examples/simple_cards/js/pict.min.js +12 -0
- package/docs/examples/simple_cards/pict-section-flow-example-simple-cards.compatible.min.js +1 -0
- package/docs/index.html +6 -7
- package/docs/playground/app.json +6 -0
- package/docs/playground/appdata.json +85 -0
- package/docs/playground/application.js +23 -0
- package/docs/playground/pict.json +17 -0
- package/docs/playground/runtime/pict-application.min.js +2 -0
- package/docs/playground/runtime/pict-section-flow.min.js +2 -0
- package/docs/playground/runtime/pict-section-modal.min.js +2 -0
- package/docs/playground/runtime/pict.min.js +12 -0
- package/docs/retold-catalog.json +241 -166
- package/docs/retold-keyword-index.json +19312 -7226
- package/example_applications/simple_cards/package.json +9 -1
- package/example_applications/simple_cards/source/views/PictView-FlowExample-BottomBar.js +2 -2
- package/package.json +5 -5
- package/source/PictFlowCard.js +2 -2
- package/source/providers/PictProvider-Flow-CSS.js +38 -12
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +8 -8
- package/source/providers/PictProvider-Flow-Icons.js +33 -33
- package/source/providers/PictProvider-Flow-NodeTypes.js +9 -9
- package/source/providers/PictProvider-Flow-PanelChrome.js +2 -1
- package/source/providers/PictProvider-Flow-Renderer.js +516 -0
- package/source/providers/PictProvider-Flow-StylePresets.js +259 -0
- package/source/providers/PictProvider-Flow-Theme.js +97 -669
- package/source/services/PictService-Flow-ConnectionRenderer.js +6 -6
- package/source/services/PictService-Flow-DataManager.js +6 -0
- package/source/services/PictService-Flow-InteractionManager.js +10 -1
- package/source/services/PictService-Flow-PanelManager.js +106 -2
- package/source/services/PictService-Flow-PortRenderer.js +6 -6
- package/source/views/PictView-Flow-Node.js +1 -1
- package/source/views/PictView-Flow-PropertiesPanel.js +71 -4
- package/source/views/PictView-Flow-Toolbar.js +24 -16
- package/source/views/PictView-Flow.js +225 -47
- package/test/PanelManager_tests.js +172 -0
- package/test/Renderer_tests.js +133 -0
- package/test/StylePresets_tests.js +153 -0
- package/docs/css/docuserve.css +0 -327
|
@@ -23,6 +23,8 @@ const libPictProviderFlowCSS = require('../providers/PictProvider-Flow-CSS.js');
|
|
|
23
23
|
const libPictProviderFlowIcons = require('../providers/PictProvider-Flow-Icons.js');
|
|
24
24
|
const libPictProviderFlowConnectorShapes = require('../providers/PictProvider-Flow-ConnectorShapes.js');
|
|
25
25
|
const libPictProviderFlowTheme = require('../providers/PictProvider-Flow-Theme.js');
|
|
26
|
+
const libPictProviderFlowRenderer = require('../providers/PictProvider-Flow-Renderer.js');
|
|
27
|
+
const libPictProviderFlowStylePresets = require('../providers/PictProvider-Flow-StylePresets.js');
|
|
26
28
|
const libPictProviderFlowNoise = require('../providers/PictProvider-Flow-Noise.js');
|
|
27
29
|
|
|
28
30
|
const libPictViewFlowNode = require('./PictView-Flow-Node.js');
|
|
@@ -69,6 +71,11 @@ const _DefaultConfiguration =
|
|
|
69
71
|
DefaultNodeWidth: 180,
|
|
70
72
|
DefaultNodeHeight: 80,
|
|
71
73
|
|
|
74
|
+
// Properties panel for connections (edges). Connections are not typed, so one config serves
|
|
75
|
+
// them all: { PanelType, DefaultWidth, DefaultHeight, Title, Configuration }. When set, a
|
|
76
|
+
// double-click on a connection opens this panel; when false, double-click adds a bezier handle.
|
|
77
|
+
ConnectionPropertiesPanel: false,
|
|
78
|
+
|
|
72
79
|
// Layout-algorithm subsystem defaults
|
|
73
80
|
DefaultLayoutAlgorithm: 'Custom',
|
|
74
81
|
DefaultLayoutParameters: {},
|
|
@@ -165,6 +172,11 @@ class PictViewFlow extends libPictView
|
|
|
165
172
|
{ ServiceType: 'PictProviderFlowNoise', Library: libPictProviderFlowNoise, Property: '_NoiseProvider', NoFlowView: true },
|
|
166
173
|
|
|
167
174
|
// Providers (need FlowView)
|
|
175
|
+
// Renderer + StylePresets must be created before the legacy Theme
|
|
176
|
+
// shim (which delegates to them) and before CSS PostInit so that
|
|
177
|
+
// registerCSS() sees an initialized renderer.
|
|
178
|
+
{ ServiceType: 'PictProviderFlowRenderer', Library: libPictProviderFlowRenderer, Property: '_RendererProvider' },
|
|
179
|
+
{ ServiceType: 'PictProviderFlowStylePresets', Library: libPictProviderFlowStylePresets, Property: '_StylePresetsProvider' },
|
|
168
180
|
{ ServiceType: 'PictProviderFlowTheme', Library: libPictProviderFlowTheme, Property: '_ThemeProvider' },
|
|
169
181
|
{ ServiceType: 'PictProviderFlowCSS', Library: libPictProviderFlowCSS, Property: '_CSSProvider', PostInit: 'registerCSS' },
|
|
170
182
|
{ ServiceType: 'PictProviderFlowIcons', Library: libPictProviderFlowIcons, Property: '_IconProvider', PostInit: 'registerIconTemplates' },
|
|
@@ -255,6 +267,8 @@ class PictViewFlow extends libPictView
|
|
|
255
267
|
this._IconProvider = null;
|
|
256
268
|
this._ConnectorShapesProvider = null;
|
|
257
269
|
this._ThemeProvider = null;
|
|
270
|
+
this._RendererProvider = null;
|
|
271
|
+
this._StylePresetsProvider = null;
|
|
258
272
|
this._NoiseProvider = null;
|
|
259
273
|
this._SVGHelperProvider = null;
|
|
260
274
|
this._GeometryProvider = null;
|
|
@@ -343,23 +357,40 @@ class PictViewFlow extends libPictView
|
|
|
343
357
|
{
|
|
344
358
|
super.onBeforeInitialize();
|
|
345
359
|
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
this._NoiseProvider
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
360
|
+
// Noise + Renderer + StylePresets + Theme shim must be created before
|
|
361
|
+
// CSS PostInit so registerCSS() sees an initialized renderer + presets.
|
|
362
|
+
this._NoiseProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNoise');
|
|
363
|
+
this._RendererProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowRenderer', { FlowView: this });
|
|
364
|
+
this._StylePresetsProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowStylePresets', { FlowView: this });
|
|
365
|
+
this._ThemeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowTheme', { FlowView: this });
|
|
366
|
+
|
|
367
|
+
// Apply initial style preset / per-axis overrides from options.
|
|
368
|
+
// `Theme` and `StylePreset` are aliases for the same preset-by-hash apply.
|
|
369
|
+
let tmpInitialPreset = this.options.StylePreset || this.options.Theme;
|
|
370
|
+
if (tmpInitialPreset)
|
|
371
|
+
{
|
|
372
|
+
this._StylePresetsProvider.applyPreset(tmpInitialPreset);
|
|
373
|
+
}
|
|
374
|
+
if (this.options.Renderer)
|
|
352
375
|
{
|
|
353
|
-
this.
|
|
376
|
+
this._RendererProvider.setRenderer(this.options.Renderer);
|
|
377
|
+
this._StylePresetsProvider.markCustomized();
|
|
354
378
|
}
|
|
355
379
|
if (typeof this.options.NoiseLevel === 'number')
|
|
356
380
|
{
|
|
357
|
-
this.
|
|
381
|
+
this._RendererProvider.setNoiseLevel(this.options.NoiseLevel);
|
|
358
382
|
}
|
|
359
383
|
|
|
360
|
-
// Instantiate all remaining services (skips Theme
|
|
384
|
+
// Instantiate all remaining services (skips Noise/Renderer/StylePresets/Theme
|
|
385
|
+
// since already set above)
|
|
361
386
|
this._instantiateServices();
|
|
362
387
|
|
|
388
|
+
// Now that CSSProvider exists, inject the active renderer's GeometryCSS.
|
|
389
|
+
if (this._CSSProvider && typeof this._CSSProvider.registerRendererCSS === 'function')
|
|
390
|
+
{
|
|
391
|
+
this._CSSProvider.registerRendererCSS(this._RendererProvider.getActiveRenderer());
|
|
392
|
+
}
|
|
393
|
+
|
|
363
394
|
// Subscribe to the host application's pict-provider-theme so the flow
|
|
364
395
|
// editor's marker arrowhead colors and shape overrides update when the
|
|
365
396
|
// host swaps light/dark or palette themes. CSS variables (--theme-*)
|
|
@@ -902,89 +933,188 @@ class PictViewFlow extends libPictView
|
|
|
902
933
|
return this._ViewportManager.exitFullscreen();
|
|
903
934
|
}
|
|
904
935
|
|
|
905
|
-
// ── Theme API
|
|
936
|
+
// ── Theme / Renderer / Style-Preset API ─────────────────────────────
|
|
937
|
+
//
|
|
938
|
+
// Three axes you can drive independently:
|
|
939
|
+
// - ColorTheme — delegates to pict-provider-theme (a pict-section-theme
|
|
940
|
+
// catalog hash like 'flow-sketch' or 'pict-default')
|
|
941
|
+
// - Renderer — delegates to PictProviderFlowRenderer (controls
|
|
942
|
+
// bracket/rect node body, jitter, shadows, fonts)
|
|
943
|
+
// - EdgeTheme — delegates to PictService-Flow-Layout (Bezier /
|
|
944
|
+
// Straight / Orthogonal / Perimeter / …; see
|
|
945
|
+
// PictView-Flow.setEdgeTheme below)
|
|
946
|
+
//
|
|
947
|
+
// Most users pick a curated combo via `setStylePreset()` — the preset
|
|
948
|
+
// applies all three axes in order. Per-axis overrides mark the active
|
|
949
|
+
// preset as 'customized' (getStylePreset returns null afterward).
|
|
950
|
+
//
|
|
951
|
+
// For backwards-compatibility, `setTheme()` / `getThemeKey()` continue
|
|
952
|
+
// to work as aliases for `setStylePreset()` / `getStylePreset()`.
|
|
906
953
|
|
|
907
954
|
/**
|
|
908
|
-
*
|
|
909
|
-
*
|
|
955
|
+
* Apply a named style preset — sets ColorTheme, Renderer, EdgeTheme
|
|
956
|
+
* (and optional NoiseLevel) in one call.
|
|
957
|
+
* @param {string} pPresetHash
|
|
910
958
|
*/
|
|
911
|
-
|
|
959
|
+
setStylePreset(pPresetHash)
|
|
912
960
|
{
|
|
913
|
-
if (!this.
|
|
961
|
+
if (!this._StylePresetsProvider)
|
|
914
962
|
{
|
|
915
|
-
this.log.warn('PictSectionFlow
|
|
963
|
+
this.log.warn('PictSectionFlow setStylePreset: StylePresets provider not available');
|
|
916
964
|
return;
|
|
917
965
|
}
|
|
966
|
+
let tmpApplied = this._StylePresetsProvider.applyPreset(pPresetHash);
|
|
967
|
+
if (!tmpApplied) { return; }
|
|
968
|
+
this._refreshAfterStyleChange();
|
|
969
|
+
if (this._EventHandlerProvider)
|
|
970
|
+
{
|
|
971
|
+
this._EventHandlerProvider.fireEvent('onStylePresetChanged', pPresetHash);
|
|
972
|
+
// Back-compat — old code listens for 'onThemeChanged'
|
|
973
|
+
this._EventHandlerProvider.fireEvent('onThemeChanged', pPresetHash);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
918
976
|
|
|
919
|
-
|
|
920
|
-
|
|
977
|
+
/**
|
|
978
|
+
* Hash of the active style preset, or null when in customized state.
|
|
979
|
+
* @returns {string|null}
|
|
980
|
+
*/
|
|
981
|
+
getStylePreset()
|
|
982
|
+
{
|
|
983
|
+
return this._StylePresetsProvider ? this._StylePresetsProvider.getActivePresetHash() : null;
|
|
984
|
+
}
|
|
921
985
|
|
|
922
|
-
|
|
923
|
-
|
|
986
|
+
/**
|
|
987
|
+
* Override just the color theme — delegates to pict-provider-theme.
|
|
988
|
+
* @param {string} pThemeHash - a pict-section-theme catalog hash
|
|
989
|
+
*/
|
|
990
|
+
setColorTheme(pThemeHash)
|
|
991
|
+
{
|
|
992
|
+
if (this.fable.providers && this.fable.providers.Theme)
|
|
924
993
|
{
|
|
925
|
-
this.
|
|
994
|
+
try { this.fable.providers.Theme.applyTheme(pThemeHash); }
|
|
995
|
+
catch (pErr) { this.log.warn(`PictSectionFlow setColorTheme: applyTheme failed — ${pErr.message}`); return; }
|
|
926
996
|
}
|
|
997
|
+
else
|
|
998
|
+
{
|
|
999
|
+
this.log.warn('PictSectionFlow setColorTheme: pict-provider-theme not available in host');
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (this._StylePresetsProvider) { this._StylePresetsProvider.markCustomized(); }
|
|
1003
|
+
this._refreshAfterStyleChange();
|
|
1004
|
+
if (this._EventHandlerProvider)
|
|
1005
|
+
{
|
|
1006
|
+
this._EventHandlerProvider.fireEvent('onColorThemeChanged', pThemeHash);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
927
1009
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1010
|
+
/**
|
|
1011
|
+
* The active color theme hash (from pict-provider-theme).
|
|
1012
|
+
* @returns {string|null}
|
|
1013
|
+
*/
|
|
1014
|
+
getColorThemeKey()
|
|
1015
|
+
{
|
|
1016
|
+
if (this.fable.providers && this.fable.providers.Theme && typeof this.fable.providers.Theme.getActiveTheme === 'function')
|
|
933
1017
|
{
|
|
934
|
-
this.
|
|
1018
|
+
let tmpActive = this.fable.providers.Theme.getActiveTheme();
|
|
1019
|
+
if (tmpActive && tmpActive.Hash) { return tmpActive.Hash; }
|
|
935
1020
|
}
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
936
1023
|
|
|
1024
|
+
/**
|
|
1025
|
+
* Override just the renderer — controls node body shape, jitter, shadows.
|
|
1026
|
+
* @param {string} pRendererKey
|
|
1027
|
+
*/
|
|
1028
|
+
setRenderer(pRendererKey)
|
|
1029
|
+
{
|
|
1030
|
+
if (!this._RendererProvider)
|
|
1031
|
+
{
|
|
1032
|
+
this.log.warn('PictSectionFlow setRenderer: Renderer provider not available');
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
let tmpApplied = this._RendererProvider.setRenderer(pRendererKey);
|
|
1036
|
+
if (!tmpApplied) { return; }
|
|
1037
|
+
if (this._StylePresetsProvider) { this._StylePresetsProvider.markCustomized(); }
|
|
1038
|
+
this._refreshAfterStyleChange();
|
|
937
1039
|
if (this._EventHandlerProvider)
|
|
938
1040
|
{
|
|
939
|
-
this._EventHandlerProvider.fireEvent('
|
|
1041
|
+
this._EventHandlerProvider.fireEvent('onRendererChanged', pRendererKey);
|
|
940
1042
|
}
|
|
941
1043
|
}
|
|
942
1044
|
|
|
943
1045
|
/**
|
|
944
|
-
*
|
|
1046
|
+
* The active renderer key.
|
|
1047
|
+
* @returns {string}
|
|
1048
|
+
*/
|
|
1049
|
+
getRendererKey()
|
|
1050
|
+
{
|
|
1051
|
+
return this._RendererProvider ? this._RendererProvider.getActiveRendererKey() : 'clean';
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Set the noise level (0 to 1) and re-render. Noise applies only when
|
|
1056
|
+
* the active renderer enables it (see Renderer.NoiseConfig).
|
|
945
1057
|
* @param {number} pLevel - 0 = precise, 1 = maximum wobble
|
|
946
1058
|
*/
|
|
947
1059
|
setNoiseLevel(pLevel)
|
|
948
1060
|
{
|
|
949
|
-
if (
|
|
1061
|
+
if (this._RendererProvider)
|
|
950
1062
|
{
|
|
951
|
-
this.
|
|
952
|
-
return;
|
|
1063
|
+
this._RendererProvider.setNoiseLevel(pLevel);
|
|
953
1064
|
}
|
|
954
|
-
|
|
955
|
-
this._ThemeProvider.setNoiseLevel(pLevel);
|
|
956
|
-
|
|
957
|
-
// Full re-render to apply new noise
|
|
958
|
-
if (this.initialRenderComplete)
|
|
1065
|
+
else if (this._ThemeProvider)
|
|
959
1066
|
{
|
|
960
|
-
this.
|
|
1067
|
+
this._ThemeProvider.setNoiseLevel(pLevel);
|
|
961
1068
|
}
|
|
1069
|
+
if (this.initialRenderComplete) { this.renderFlow(); }
|
|
962
1070
|
}
|
|
963
1071
|
|
|
964
1072
|
/**
|
|
965
|
-
*
|
|
1073
|
+
* Current noise level (0 to 1).
|
|
966
1074
|
* @returns {number}
|
|
967
1075
|
*/
|
|
968
1076
|
getNoiseLevel()
|
|
969
1077
|
{
|
|
970
|
-
if (this.
|
|
971
|
-
{
|
|
972
|
-
return this._ThemeProvider.getNoiseLevel();
|
|
973
|
-
}
|
|
1078
|
+
if (this._RendererProvider) { return this._RendererProvider.getNoiseLevel(); }
|
|
1079
|
+
if (this._ThemeProvider) { return this._ThemeProvider.getNoiseLevel(); }
|
|
974
1080
|
return 0;
|
|
975
1081
|
}
|
|
976
1082
|
|
|
977
1083
|
/**
|
|
978
|
-
*
|
|
979
|
-
*
|
|
1084
|
+
* @deprecated since the 3-axis refactor — use setStylePreset() instead.
|
|
1085
|
+
* Kept as an alias for back-compat with existing host apps and views.
|
|
1086
|
+
* @param {string} pPresetHash
|
|
1087
|
+
*/
|
|
1088
|
+
setTheme(pPresetHash)
|
|
1089
|
+
{
|
|
1090
|
+
this.setStylePreset(pPresetHash);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* @deprecated since the 3-axis refactor — use getStylePreset() instead.
|
|
1095
|
+
* Returns the active preset hash (or null if customized).
|
|
1096
|
+
* @returns {string|null}
|
|
980
1097
|
*/
|
|
981
1098
|
getThemeKey()
|
|
982
1099
|
{
|
|
983
|
-
|
|
1100
|
+
return this.getStylePreset();
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Common refresh path used by all axis-change methods.
|
|
1105
|
+
* Re-registers the renderer CSS, re-injects marker defs (arrowhead colors
|
|
1106
|
+
* may have shifted with the new theme), and full-renders the flow.
|
|
1107
|
+
* @private
|
|
1108
|
+
*/
|
|
1109
|
+
_refreshAfterStyleChange()
|
|
1110
|
+
{
|
|
1111
|
+
if (this._CSSProvider && typeof this._CSSProvider.registerRendererCSS === 'function' && this._RendererProvider)
|
|
984
1112
|
{
|
|
985
|
-
|
|
1113
|
+
this._CSSProvider.registerRendererCSS(this._RendererProvider.getActiveRenderer());
|
|
986
1114
|
}
|
|
987
|
-
|
|
1115
|
+
if (this._CSSProvider) { this._CSSProvider.registerCSS(); }
|
|
1116
|
+
this._reinjectMarkerDefs();
|
|
1117
|
+
if (this.initialRenderComplete) { this.renderFlow(); }
|
|
988
1118
|
}
|
|
989
1119
|
|
|
990
1120
|
_reinjectMarkerDefs() { return this._RenderManager.reinjectMarkerDefs(); }
|
|
@@ -1203,6 +1333,54 @@ class PictViewFlow extends libPictView
|
|
|
1203
1333
|
return this._PanelManager.togglePanel(pNodeHash);
|
|
1204
1334
|
}
|
|
1205
1335
|
|
|
1336
|
+
/**
|
|
1337
|
+
* Open a properties panel for a connection (edge). Requires the ConnectionPropertiesPanel
|
|
1338
|
+
* option; returns false otherwise.
|
|
1339
|
+
* @param {string} pConnectionHash
|
|
1340
|
+
* @returns {Object|false}
|
|
1341
|
+
*/
|
|
1342
|
+
openConnectionPanel(pConnectionHash)
|
|
1343
|
+
{
|
|
1344
|
+
return this._PanelManager.openConnectionPanel(pConnectionHash);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* Toggle a properties panel for a connection.
|
|
1349
|
+
* @param {string} pConnectionHash
|
|
1350
|
+
* @returns {Object|false}
|
|
1351
|
+
*/
|
|
1352
|
+
toggleConnectionPanel(pConnectionHash)
|
|
1353
|
+
{
|
|
1354
|
+
return this._PanelManager.toggleConnectionPanel(pConnectionHash);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Close all panels for a given connection.
|
|
1359
|
+
* @param {string} pConnectionHash
|
|
1360
|
+
* @returns {boolean}
|
|
1361
|
+
*/
|
|
1362
|
+
closePanelForConnection(pConnectionHash)
|
|
1363
|
+
{
|
|
1364
|
+
return this._PanelManager.closePanelForConnection(pConnectionHash);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* The midpoint of a connection in SVG coordinates, averaged from its two endpoint ports. Used
|
|
1369
|
+
* to place and tether a connection's properties panel. Returns null if the connection or
|
|
1370
|
+
* either port can not be resolved.
|
|
1371
|
+
* @param {string} pConnectionHash
|
|
1372
|
+
* @returns {{x: number, y: number}|null}
|
|
1373
|
+
*/
|
|
1374
|
+
getConnectionMidpoint(pConnectionHash)
|
|
1375
|
+
{
|
|
1376
|
+
let tmpConnection = this.getConnection(pConnectionHash);
|
|
1377
|
+
if (!tmpConnection) return null;
|
|
1378
|
+
let tmpSource = this.getPortPosition(tmpConnection.SourceNodeHash, tmpConnection.SourcePortHash);
|
|
1379
|
+
let tmpTarget = this.getPortPosition(tmpConnection.TargetNodeHash, tmpConnection.TargetPortHash);
|
|
1380
|
+
if (!tmpSource || !tmpTarget) return null;
|
|
1381
|
+
return { x: (tmpSource.x + tmpTarget.x) / 2, y: (tmpSource.y + tmpTarget.y) / 2 };
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1206
1384
|
/**
|
|
1207
1385
|
* Update a panel's position (for drag).
|
|
1208
1386
|
* @param {string} pPanelHash
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const libFable = require('fable');
|
|
2
|
+
const libChai = require('chai');
|
|
3
|
+
const libExpect = libChai.expect;
|
|
4
|
+
|
|
5
|
+
const libPanelManager = require('../source/services/PictService-Flow-PanelManager.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Connection (edge) properties panels. The node-panel path is well covered through the view; these
|
|
9
|
+
* focus on the connection additions: gating on ConnectionPropertiesPanel, placement near the edge
|
|
10
|
+
* midpoint, the open/toggle/close lifecycle, and that node panels are not disturbed.
|
|
11
|
+
*/
|
|
12
|
+
suite
|
|
13
|
+
(
|
|
14
|
+
'PictService-Flow-PanelManager (connection panels)',
|
|
15
|
+
function ()
|
|
16
|
+
{
|
|
17
|
+
let _Fable;
|
|
18
|
+
let _PanelManager;
|
|
19
|
+
let _MockFlowView;
|
|
20
|
+
|
|
21
|
+
setup
|
|
22
|
+
(
|
|
23
|
+
function ()
|
|
24
|
+
{
|
|
25
|
+
_Fable = new libFable({});
|
|
26
|
+
|
|
27
|
+
_MockFlowView =
|
|
28
|
+
{
|
|
29
|
+
fable: _Fable,
|
|
30
|
+
log: _Fable.log,
|
|
31
|
+
options:
|
|
32
|
+
{
|
|
33
|
+
ViewIdentifier: 'Test-Flow',
|
|
34
|
+
ConnectionPropertiesPanel: false
|
|
35
|
+
},
|
|
36
|
+
_FlowData:
|
|
37
|
+
{
|
|
38
|
+
Nodes:
|
|
39
|
+
[
|
|
40
|
+
{ Hash: 'n1', Type: 'state', X: 0, Y: 0, Width: 100, Height: 60, Ports: [ { Hash: 'n1-out', Direction: 'output' } ] },
|
|
41
|
+
{ Hash: 'n2', Type: 'state', X: 300, Y: 0, Width: 100, Height: 60, Ports: [ { Hash: 'n2-in', Direction: 'input' } ] }
|
|
42
|
+
],
|
|
43
|
+
Connections:
|
|
44
|
+
[
|
|
45
|
+
{ Hash: 'c1', SourceNodeHash: 'n1', SourcePortHash: 'n1-out', TargetNodeHash: 'n2', TargetPortHash: 'n2-in', Data: {} }
|
|
46
|
+
],
|
|
47
|
+
OpenPanels: [],
|
|
48
|
+
ViewState: { SelectedTetherHash: null }
|
|
49
|
+
},
|
|
50
|
+
getConnection: function (pHash) { return this._FlowData.Connections.find((pConn) => pConn.Hash === pHash) || null; },
|
|
51
|
+
getNode: function (pHash) { return this._FlowData.Nodes.find((pNode) => pNode.Hash === pHash) || null; },
|
|
52
|
+
getConnectionMidpoint: function (pHash) { return this.getConnection(pHash) ? { x: 200, y: 30 } : null; },
|
|
53
|
+
_NodeTypeProvider:
|
|
54
|
+
{
|
|
55
|
+
getNodeType: function () { return { Label: 'State', PropertiesPanel: { PanelType: 'Form', DefaultWidth: 300, DefaultHeight: 220, Title: 'State' } }; }
|
|
56
|
+
},
|
|
57
|
+
renderFlow: function () {},
|
|
58
|
+
marshalFromView: function () {},
|
|
59
|
+
_PropertiesPanelView: { destroyPanel: function () {} },
|
|
60
|
+
_EventHandlerProvider: { fireEvent: function () {} }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
_PanelManager = new libPanelManager(_Fable, { FlowView: _MockFlowView }, 'PM-Test');
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
test
|
|
68
|
+
(
|
|
69
|
+
'openConnectionPanel returns false when no ConnectionPropertiesPanel is configured',
|
|
70
|
+
function ()
|
|
71
|
+
{
|
|
72
|
+
let tmpResult = _PanelManager.openConnectionPanel('c1');
|
|
73
|
+
libExpect(tmpResult).to.equal(false);
|
|
74
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(0);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
test
|
|
79
|
+
(
|
|
80
|
+
'openConnectionPanel returns false for an unknown connection',
|
|
81
|
+
function ()
|
|
82
|
+
{
|
|
83
|
+
_MockFlowView.options.ConnectionPropertiesPanel = { PanelType: 'Form' };
|
|
84
|
+
let tmpResult = _PanelManager.openConnectionPanel('no-such-connection');
|
|
85
|
+
libExpect(tmpResult).to.equal(false);
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
test
|
|
90
|
+
(
|
|
91
|
+
'openConnectionPanel opens a panel carrying the ConnectionHash, placed near the midpoint',
|
|
92
|
+
function ()
|
|
93
|
+
{
|
|
94
|
+
_MockFlowView.options.ConnectionPropertiesPanel = { PanelType: 'Form', DefaultWidth: 320, DefaultHeight: 240, Title: 'Transition' };
|
|
95
|
+
let tmpPanel = _PanelManager.openConnectionPanel('c1');
|
|
96
|
+
|
|
97
|
+
libExpect(tmpPanel).to.be.an('object');
|
|
98
|
+
libExpect(tmpPanel.ConnectionHash).to.equal('c1');
|
|
99
|
+
libExpect(tmpPanel.NodeHash).to.equal(null);
|
|
100
|
+
libExpect(tmpPanel.Title).to.equal('Transition');
|
|
101
|
+
libExpect(tmpPanel.Width).to.equal(320);
|
|
102
|
+
libExpect(tmpPanel.Height).to.equal(240);
|
|
103
|
+
// Midpoint is (200, 30); the panel is offset from it.
|
|
104
|
+
libExpect(tmpPanel.X).to.equal(240);
|
|
105
|
+
libExpect(tmpPanel.Y).to.equal(50);
|
|
106
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(1);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
test
|
|
111
|
+
(
|
|
112
|
+
'openConnectionPanel is idempotent: a second open returns the same panel',
|
|
113
|
+
function ()
|
|
114
|
+
{
|
|
115
|
+
_MockFlowView.options.ConnectionPropertiesPanel = { PanelType: 'Form' };
|
|
116
|
+
let tmpFirst = _PanelManager.openConnectionPanel('c1');
|
|
117
|
+
let tmpSecond = _PanelManager.openConnectionPanel('c1');
|
|
118
|
+
libExpect(tmpSecond.Hash).to.equal(tmpFirst.Hash);
|
|
119
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(1);
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
test
|
|
124
|
+
(
|
|
125
|
+
'toggleConnectionPanel opens then closes',
|
|
126
|
+
function ()
|
|
127
|
+
{
|
|
128
|
+
_MockFlowView.options.ConnectionPropertiesPanel = { PanelType: 'Form' };
|
|
129
|
+
let tmpOpened = _PanelManager.toggleConnectionPanel('c1');
|
|
130
|
+
libExpect(tmpOpened).to.be.an('object');
|
|
131
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(1);
|
|
132
|
+
|
|
133
|
+
let tmpClosed = _PanelManager.toggleConnectionPanel('c1');
|
|
134
|
+
libExpect(tmpClosed).to.equal(false);
|
|
135
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(0);
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
test
|
|
140
|
+
(
|
|
141
|
+
'closePanelForConnection removes the connection panel',
|
|
142
|
+
function ()
|
|
143
|
+
{
|
|
144
|
+
_MockFlowView.options.ConnectionPropertiesPanel = { PanelType: 'Form' };
|
|
145
|
+
_PanelManager.openConnectionPanel('c1');
|
|
146
|
+
let tmpRemoved = _PanelManager.closePanelForConnection('c1');
|
|
147
|
+
libExpect(tmpRemoved).to.equal(true);
|
|
148
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(0);
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
test
|
|
153
|
+
(
|
|
154
|
+
'node panels still open alongside connection panels, keyed separately',
|
|
155
|
+
function ()
|
|
156
|
+
{
|
|
157
|
+
_MockFlowView.options.ConnectionPropertiesPanel = { PanelType: 'Form' };
|
|
158
|
+
let tmpNodePanel = _PanelManager.openPanel('n1');
|
|
159
|
+
let tmpConnPanel = _PanelManager.openConnectionPanel('c1');
|
|
160
|
+
|
|
161
|
+
libExpect(tmpNodePanel.NodeHash).to.equal('n1');
|
|
162
|
+
libExpect(tmpConnPanel.ConnectionHash).to.equal('c1');
|
|
163
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(2);
|
|
164
|
+
|
|
165
|
+
// Closing the connection panel leaves the node panel intact.
|
|
166
|
+
_PanelManager.closePanelForConnection('c1');
|
|
167
|
+
libExpect(_MockFlowView._FlowData.OpenPanels.length).to.equal(1);
|
|
168
|
+
libExpect(_MockFlowView._FlowData.OpenPanels[0].NodeHash).to.equal('n1');
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const libFable = require('fable');
|
|
2
|
+
const libChai = require('chai');
|
|
3
|
+
const libExpect = libChai.expect;
|
|
4
|
+
|
|
5
|
+
const libRenderer = require('../source/providers/PictProvider-Flow-Renderer.js');
|
|
6
|
+
|
|
7
|
+
suite('PictProvider-Flow-Renderer',
|
|
8
|
+
function ()
|
|
9
|
+
{
|
|
10
|
+
let _Fable;
|
|
11
|
+
let _Renderer;
|
|
12
|
+
|
|
13
|
+
setup(function ()
|
|
14
|
+
{
|
|
15
|
+
_Fable = new libFable({});
|
|
16
|
+
_Renderer = new libRenderer(_Fable, {}, 'Renderer-Test');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
suite('Built-in renderer registry', function ()
|
|
20
|
+
{
|
|
21
|
+
test('registers clean, bracket, sketch, crt, workstation by default', function ()
|
|
22
|
+
{
|
|
23
|
+
let tmpKeys = _Renderer.getRendererKeys();
|
|
24
|
+
libExpect(tmpKeys).to.include('clean');
|
|
25
|
+
libExpect(tmpKeys).to.include('bracket');
|
|
26
|
+
libExpect(tmpKeys).to.include('sketch');
|
|
27
|
+
libExpect(tmpKeys).to.include('crt');
|
|
28
|
+
libExpect(tmpKeys).to.include('workstation');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('clean is the default active renderer', function ()
|
|
32
|
+
{
|
|
33
|
+
libExpect(_Renderer.getActiveRendererKey()).to.equal('clean');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('each built-in renderer has the required shape', function ()
|
|
37
|
+
{
|
|
38
|
+
let tmpKeys = _Renderer.getRendererKeys();
|
|
39
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
40
|
+
{
|
|
41
|
+
let tmpR = _Renderer.getActiveRenderer.call(
|
|
42
|
+
{ _Renderers: _Renderer._Renderers, _ActiveRendererKey: tmpKeys[i] });
|
|
43
|
+
libExpect(tmpR, `renderer ${tmpKeys[i]}`).to.have.property('Key', tmpKeys[i]);
|
|
44
|
+
libExpect(tmpR).to.have.property('NodeBodyMode');
|
|
45
|
+
libExpect(tmpR).to.have.property('NoiseConfig');
|
|
46
|
+
libExpect(tmpR).to.have.property('ConnectionConfig');
|
|
47
|
+
libExpect(tmpR).to.have.property('GeometryCSS');
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
suite('setRenderer()', function ()
|
|
53
|
+
{
|
|
54
|
+
test('switches the active renderer + updates noise default', function ()
|
|
55
|
+
{
|
|
56
|
+
_Renderer.setRenderer('sketch');
|
|
57
|
+
libExpect(_Renderer.getActiveRendererKey()).to.equal('sketch');
|
|
58
|
+
let tmpActive = _Renderer.getActiveRenderer();
|
|
59
|
+
libExpect(tmpActive.NodeBodyMode).to.equal('bracket');
|
|
60
|
+
libExpect(_Renderer.getNoiseLevel()).to.equal(0.4);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('returns false for unknown renderer + leaves state unchanged', function ()
|
|
64
|
+
{
|
|
65
|
+
_Renderer.setRenderer('sketch');
|
|
66
|
+
let tmpResult = _Renderer.setRenderer('nonexistent');
|
|
67
|
+
libExpect(tmpResult).to.equal(false);
|
|
68
|
+
libExpect(_Renderer.getActiveRendererKey()).to.equal('sketch');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('switching to a renderer with noise disabled resets noise to 0', function ()
|
|
72
|
+
{
|
|
73
|
+
_Renderer.setRenderer('sketch'); // 0.4
|
|
74
|
+
_Renderer.setRenderer('clean'); // 0
|
|
75
|
+
libExpect(_Renderer.getNoiseLevel()).to.equal(0);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
suite('register()', function ()
|
|
80
|
+
{
|
|
81
|
+
test('registers a custom renderer + reads back through getActiveRenderer', function ()
|
|
82
|
+
{
|
|
83
|
+
_Renderer.register('custom', {
|
|
84
|
+
Label: 'Custom',
|
|
85
|
+
NodeBodyMode: 'rect',
|
|
86
|
+
NoiseConfig: { Enabled: false, DefaultLevel: 0, MaxJitterPx: 0, AffectsNodes: false, AffectsConnections: false },
|
|
87
|
+
ConnectionConfig: { StrokeWidth: 3, ArrowheadStyle: 'triangle' },
|
|
88
|
+
GeometryCSS: '',
|
|
89
|
+
AdditionalCSS: ''
|
|
90
|
+
});
|
|
91
|
+
let tmpResult = _Renderer.setRenderer('custom');
|
|
92
|
+
libExpect(tmpResult).to.equal(true);
|
|
93
|
+
let tmpActive = _Renderer.getActiveRenderer();
|
|
94
|
+
libExpect(tmpActive.Key).to.equal('custom');
|
|
95
|
+
libExpect(tmpActive.Label).to.equal('Custom');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
suite('Noise APIs', function ()
|
|
100
|
+
{
|
|
101
|
+
test('setNoiseLevel clamps to [0,1]', function ()
|
|
102
|
+
{
|
|
103
|
+
_Renderer.setNoiseLevel(-1);
|
|
104
|
+
libExpect(_Renderer.getNoiseLevel()).to.equal(0);
|
|
105
|
+
_Renderer.setNoiseLevel(2);
|
|
106
|
+
libExpect(_Renderer.getNoiseLevel()).to.equal(1);
|
|
107
|
+
_Renderer.setNoiseLevel(0.5);
|
|
108
|
+
libExpect(_Renderer.getNoiseLevel()).to.equal(0.5);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('getNodeNoiseAmplitude is 0 when active renderer disables noise', function ()
|
|
112
|
+
{
|
|
113
|
+
_Renderer.setRenderer('clean');
|
|
114
|
+
_Renderer.setNoiseLevel(1);
|
|
115
|
+
libExpect(_Renderer.getNodeNoiseAmplitude()).to.equal(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('getNodeNoiseAmplitude scales with noise level when sketch is active', function ()
|
|
119
|
+
{
|
|
120
|
+
_Renderer.setRenderer('sketch');
|
|
121
|
+
_Renderer.setNoiseLevel(0.5);
|
|
122
|
+
// sketch.MaxJitterPx is 4 — 0.5 * 4 = 2
|
|
123
|
+
libExpect(_Renderer.getNodeNoiseAmplitude()).to.equal(2);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('processPathString is a no-op when noise is disabled', function ()
|
|
127
|
+
{
|
|
128
|
+
_Renderer.setRenderer('clean');
|
|
129
|
+
let tmpResult = _Renderer.processPathString('M 0 0 L 10 10', 'seed');
|
|
130
|
+
libExpect(tmpResult).to.equal('M 0 0 L 10 10');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|