pict-section-flow 1.3.0 → 2.0.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/package.json +7 -2
- package/source/Pict-Section-Flow.js +20 -14
- package/source/providers/PictProvider-Flow-Background.js +303 -0
- package/source/providers/PictProvider-Flow-CSS.js +99 -7
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +8 -0
- package/source/providers/PictProvider-Flow-Geometry.js +11 -421
- package/source/providers/PictProvider-Flow-Icons.js +20 -0
- package/source/providers/PictProvider-Flow-Layouts.js +107 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +77 -5
- package/source/services/PictService-Flow-CursorManager.js +113 -0
- package/source/services/PictService-Flow-InteractionManager.js +443 -61
- package/source/services/PictService-Flow-Layout.js +21 -16
- package/source/services/PictService-Flow-PathGenerator.js +30 -417
- package/source/services/PictService-Flow-RenderManager.js +9 -1
- package/source/services/PictService-Flow-ViewportManager.js +102 -0
- package/source/views/PictView-Flow-FloatingToolbar.js +57 -0
- package/source/views/PictView-Flow-Node.js +36 -0
- package/source/views/PictView-Flow-PropertiesPanel.js +27 -5
- package/source/views/PictView-Flow-Toolbar.js +148 -13
- package/source/views/PictView-Flow.js +628 -3
- package/.claude/launch.json +0 -11
- package/docs/.nojekyll +0 -0
- package/docs/Architecture.md +0 -163
- package/docs/Custom-Styling.md +0 -275
- package/docs/Data_Model.md +0 -149
- package/docs/Event_System.md +0 -156
- package/docs/Getting_Started.md +0 -237
- package/docs/Implementation_Reference.md +0 -528
- package/docs/Layout_Persistence.md +0 -117
- package/docs/README.md +0 -103
- package/docs/Theme_Integration.md +0 -150
- package/docs/_brand.json +0 -18
- package/docs/_cover.md +0 -17
- package/docs/_playground.json +0 -24
- package/docs/_sidebar.md +0 -57
- package/docs/_topbar.md +0 -8
- package/docs/_version.json +0 -7
- package/docs/api/PictFlowCard.md +0 -216
- package/docs/api/PictFlowCardPropertiesPanel.md +0 -235
- package/docs/api/addConnection.md +0 -101
- package/docs/api/addNode.md +0 -137
- package/docs/api/autoLayout.md +0 -77
- package/docs/api/getFlowData.md +0 -112
- package/docs/api/marshalToView.md +0 -95
- package/docs/api/openPanel.md +0 -128
- package/docs/api/registerHandler.md +0 -174
- package/docs/api/registerNodeType.md +0 -142
- package/docs/api/removeConnection.md +0 -57
- package/docs/api/removeNode.md +0 -80
- package/docs/api/saveLayout.md +0 -152
- package/docs/api/screenToSVGCoords.md +0 -68
- package/docs/api/selectNode.md +0 -116
- package/docs/api/setTheme.md +0 -168
- package/docs/api/setZoom.md +0 -97
- package/docs/api/toggleFullscreen.md +0 -68
- package/docs/card-help/EACH.md +0 -19
- package/docs/card-help/FREAD.md +0 -24
- package/docs/card-help/FWRITE.md +0 -24
- package/docs/card-help/GET.md +0 -22
- package/docs/card-help/ITE.md +0 -23
- package/docs/card-help/LOG.md +0 -23
- package/docs/card-help/NOTE.md +0 -17
- package/docs/card-help/PREV.md +0 -18
- package/docs/card-help/SET.md +0 -27
- package/docs/card-help/SPKL.md +0 -22
- package/docs/card-help/STAT.md +0 -23
- package/docs/card-help/SW.md +0 -25
- package/docs/diagrams/architecture-at-a-glance.excalidraw +0 -4270
- package/docs/diagrams/architecture-at-a-glance.mmd +0 -30
- package/docs/diagrams/architecture-at-a-glance.svg +0 -2
- package/docs/diagrams/data-flow.excalidraw +0 -1451
- package/docs/diagrams/data-flow.mmd +0 -17
- package/docs/diagrams/data-flow.svg +0 -2
- package/docs/diagrams/high-level-design.excalidraw +0 -5767
- package/docs/diagrams/high-level-design.mmd +0 -86
- package/docs/diagrams/high-level-design.svg +0 -2
- package/docs/diagrams/relationships.excalidraw +0 -3852
- package/docs/diagrams/relationships.mmd +0 -9
- package/docs/diagrams/relationships.svg +0 -2
- package/docs/diagrams/service-initialization-sequence.excalidraw +0 -1466
- package/docs/diagrams/service-initialization-sequence.mmd +0 -19
- package/docs/diagrams/service-initialization-sequence.svg +0 -2
- package/docs/diagrams/svg-layer-structure.excalidraw +0 -1060
- package/docs/diagrams/svg-layer-structure.mmd +0 -18
- package/docs/diagrams/svg-layer-structure.svg +0 -2
- package/docs/examples/README.md +0 -9
- package/docs/examples/simple_cards/README.md +0 -677
- package/docs/examples/simple_cards/css/flowexample.css +0 -65
- package/docs/examples/simple_cards/index.html +0 -32
- package/docs/examples/simple_cards/js/pict.min.js +0 -12
- package/docs/examples/simple_cards/pict-section-flow-example-simple-cards.compatible.min.js +0 -1
- package/docs/index.html +0 -38
- package/docs/playground/app.json +0 -6
- package/docs/playground/appdata.json +0 -85
- package/docs/playground/application.js +0 -23
- package/docs/playground/pict.json +0 -17
- package/docs/playground/runtime/pict-application.min.js +0 -2
- package/docs/playground/runtime/pict-section-flow.min.js +0 -2
- package/docs/playground/runtime/pict-section-modal.min.js +0 -2
- package/docs/playground/runtime/pict.min.js +0 -12
- package/docs/retold-catalog.json +0 -244
- package/docs/retold-keyword-index.json +0 -26028
- package/example_applications/simple_cards/css/flowexample.css +0 -65
- package/example_applications/simple_cards/html/index.html +0 -32
- package/example_applications/simple_cards/package.json +0 -52
- package/example_applications/simple_cards/source/Pict-Application-FlowExample-Configuration.json +0 -15
- package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +0 -539
- package/example_applications/simple_cards/source/card-help-content.js +0 -16
- package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +0 -38
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +0 -44
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +0 -38
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +0 -56
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +0 -50
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +0 -37
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +0 -49
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +0 -55
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +0 -97
- package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +0 -100
- package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +0 -46
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +0 -39
- package/example_applications/simple_cards/source/providers/PictRouter-FlowExample-Configuration.json +0 -22
- package/example_applications/simple_cards/source/sample-flows.js +0 -410
- package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +0 -184
- package/example_applications/simple_cards/source/views/PictView-FlowExample-BottomBar.js +0 -77
- package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +0 -325
- package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +0 -59
- package/example_applications/simple_cards/source/views/PictView-FlowExample-Layout.js +0 -90
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +0 -453
- package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +0 -95
- package/scripts/generate-card-help.js +0 -214
- package/source/providers/edges/Edge-Bezier.js +0 -41
- package/source/providers/edges/Edge-Orthogonal.js +0 -37
- package/source/providers/edges/Edge-OrthogonalSnap.js +0 -72
- package/source/providers/edges/Edge-Perimeter-Linear.js +0 -31
- package/source/providers/edges/Edge-Perimeter-Orthogonal.js +0 -39
- package/source/providers/edges/Edge-Perimeter.js +0 -48
- package/source/providers/edges/Edge-PerimeterMath.js +0 -92
- package/source/providers/edges/Edge-Straight.js +0 -24
- package/source/providers/layouts/Layout-Circular.js +0 -203
- package/source/providers/layouts/Layout-Coerce.js +0 -40
- package/source/providers/layouts/Layout-Columnar.js +0 -134
- package/source/providers/layouts/Layout-Custom.js +0 -27
- package/source/providers/layouts/Layout-ForcedFromCenter.js +0 -256
- package/source/providers/layouts/Layout-Grid.js +0 -134
- package/source/providers/layouts/Layout-Layered.js +0 -155
- package/source/providers/layouts/Layout-Rank.js +0 -141
- package/source/providers/layouts/Layout-Staggered.js +0 -131
- package/source/providers/layouts/Layout-Tabular.js +0 -94
- package/test/ConnectionHandleManager_tests.js +0 -717
- package/test/ConnectionRenderer_tests.js +0 -591
- package/test/DataManager_tests.js +0 -859
- package/test/Geometry_tests.js +0 -767
- package/test/InteractionManager_tests.js +0 -279
- package/test/Layout_tests.js +0 -1604
- package/test/NodeView_tests.js +0 -66
- package/test/PanelManager_tests.js +0 -172
- package/test/PathGenerator_tests.js +0 -978
- package/test/PortRenderer_tests.js +0 -376
- package/test/RenderManager_tests.js +0 -756
- package/test/Renderer_tests.js +0 -133
- package/test/SelectionManager_tests.js +0 -185
- package/test/StylePresets_tests.js +0 -153
|
@@ -12,6 +12,7 @@ const libPictServiceFlowDataManager = require('../services/PictService-Flow-Data
|
|
|
12
12
|
const libPictServiceFlowConnectionHandleManager = require('../services/PictService-Flow-ConnectionHandleManager.js');
|
|
13
13
|
const libPictServiceFlowRenderManager = require('../services/PictService-Flow-RenderManager.js');
|
|
14
14
|
const libPictServiceFlowPortRenderer = require('../services/PictService-Flow-PortRenderer.js');
|
|
15
|
+
const libPictServiceFlowCursorManager = require('../services/PictService-Flow-CursorManager.js');
|
|
15
16
|
|
|
16
17
|
const libPictProviderFlowNodeTypes = require('../providers/PictProvider-Flow-NodeTypes.js');
|
|
17
18
|
const libPictProviderFlowEventHandler = require('../providers/PictProvider-Flow-EventHandler.js');
|
|
@@ -26,6 +27,7 @@ const libPictProviderFlowTheme = require('../providers/PictProvider-Flow-Theme.j
|
|
|
26
27
|
const libPictProviderFlowRenderer = require('../providers/PictProvider-Flow-Renderer.js');
|
|
27
28
|
const libPictProviderFlowStylePresets = require('../providers/PictProvider-Flow-StylePresets.js');
|
|
28
29
|
const libPictProviderFlowNoise = require('../providers/PictProvider-Flow-Noise.js');
|
|
30
|
+
const libPictProviderFlowBackground = require('../providers/PictProvider-Flow-Background.js');
|
|
29
31
|
|
|
30
32
|
const libPictViewFlowNode = require('./PictView-Flow-Node.js');
|
|
31
33
|
const libPictViewFlowToolbar = require('./PictView-Flow-Toolbar.js');
|
|
@@ -51,6 +53,11 @@ const _DefaultConfiguration =
|
|
|
51
53
|
|
|
52
54
|
TargetElementAddress: '#Flow-SVG-Container',
|
|
53
55
|
|
|
56
|
+
// Config profile: expands into a coherent bundle of the options below before
|
|
57
|
+
// your explicit options apply. null/'graph' = the historical behavior; also
|
|
58
|
+
// 'whiteboard', 'moodboard', 'presentation', 'erd'.
|
|
59
|
+
Profile: null,
|
|
60
|
+
|
|
54
61
|
EnableToolbar: true,
|
|
55
62
|
EnableAddNode: true,
|
|
56
63
|
EnableCardPalette: true,
|
|
@@ -59,9 +66,18 @@ const _DefaultConfiguration =
|
|
|
59
66
|
EnableZooming: true,
|
|
60
67
|
EnableNodeDragging: true,
|
|
61
68
|
EnableConnectionCreation: true,
|
|
69
|
+
// When on, a connection can be drawn between ANY two ports (any port can start a drag, any port can
|
|
70
|
+
// receive it), rather than only output -> input. Off by default so directed graphs (workflows) keep
|
|
71
|
+
// their source/target semantics; free-form canvases (a moodboard, whose links are undirected) turn it
|
|
72
|
+
// on so a card's ports connect in any direction.
|
|
73
|
+
EnableUndirectedConnections: false,
|
|
62
74
|
// When on, the selected node shows a bottom-right grip that resizes it by drag. Off by default
|
|
63
75
|
// so existing diagrams are unaffected; free-form canvases (moodboards) turn it on.
|
|
64
76
|
EnableNodeResizing: false,
|
|
77
|
+
// When on, the selected node shows a rotate grip on an arm above it; drag it to set the node's
|
|
78
|
+
// Rotation (degrees), Shift to snap to 15. Off by default; free-form canvases turn it on. The
|
|
79
|
+
// properties panel can still set Rotation directly, so both ways work.
|
|
80
|
+
EnableNodeRotation: false,
|
|
65
81
|
EnableGridSnap: false,
|
|
66
82
|
GridSnapSize: 20,
|
|
67
83
|
// When on, several nodes can be selected at once: shift-click a node to toggle it, drag on the
|
|
@@ -73,10 +89,62 @@ const _DefaultConfiguration =
|
|
|
73
89
|
EnableAlignmentGuides: false,
|
|
74
90
|
EnableLayoutMenu: true,
|
|
75
91
|
|
|
92
|
+
// Read-only / presentation mode. When true, editing interactions are inert
|
|
93
|
+
// (no drag, resize, connect, delete, panel edit); panning, zoom, selection,
|
|
94
|
+
// and node activation still work, and an onNodeActivate event fires on a node
|
|
95
|
+
// click so a host can show a read-only inspector. Toggle at runtime with
|
|
96
|
+
// setReadOnly(). The container also gets a 'pict-flow-readonly' class so chrome
|
|
97
|
+
// (ports, delete affordance, resize grips) can be hidden via CSS.
|
|
98
|
+
ReadOnly: false,
|
|
99
|
+
|
|
100
|
+
// Host-supplied toolbar buttons. Each entry is { Hash, Icon, Label?, Tooltip?, Active? } where Icon
|
|
101
|
+
// is a flow icon-provider key (edit, check, background, ...). They render in BOTH the docked and the
|
|
102
|
+
// floating toolbar (so they survive every toolbar mode) and, on click, fire onToolbarButton below.
|
|
103
|
+
// Empty by default, so existing consumers are unaffected.
|
|
104
|
+
ToolbarExtraButtons: [],
|
|
105
|
+
// Fired when a ToolbarExtraButtons button is clicked: onToolbarButton(pHash, pElement). The element
|
|
106
|
+
// lets the host anchor a popover next to the button. Off (false) by default.
|
|
107
|
+
onToolbarButton: false,
|
|
108
|
+
|
|
76
109
|
MinZoom: 0.1,
|
|
77
110
|
MaxZoom: 5.0,
|
|
78
111
|
ZoomStep: 0.1,
|
|
79
112
|
|
|
113
|
+
// Mouse-wheel behavior. 'zoom' (default) keeps the historical wheel-zoom; set
|
|
114
|
+
// 'pan' so the wheel scrolls the canvas (ctrl/cmd+wheel still zooms), or
|
|
115
|
+
// 'none' to ignore the wheel. WheelZoomRequiresModifier makes a plain wheel
|
|
116
|
+
// pan and only ctrl/cmd+wheel zoom while in 'zoom' mode (tames the
|
|
117
|
+
// "scrolling zooms too fast" feel). Sensitivities scale the per-tick amount.
|
|
118
|
+
WheelMode: 'zoom',
|
|
119
|
+
WheelZoomRequiresModifier: false,
|
|
120
|
+
WheelZoomSensitivity: 1,
|
|
121
|
+
WheelPanSensitivity: 1,
|
|
122
|
+
|
|
123
|
+
// Canvas background. false (default) keeps the built-in grid from the
|
|
124
|
+
// container template. Set an object to control it natively:
|
|
125
|
+
// { Style: 'grid'|'dots'|'solid'|'image'|'none', Color?, Image?, GridSize?, DotSize? }
|
|
126
|
+
// This replaces hand-painting the SVG from a consumer.
|
|
127
|
+
Background: false,
|
|
128
|
+
|
|
129
|
+
// Content frame: a dashed "page" rectangle in content space marking the
|
|
130
|
+
// intended bounds (origin + width + preferred end). false (default) draws
|
|
131
|
+
// nothing. Set { Enabled?, X, Y, Width, Height, FitOnLoad? }; fitToFrame()
|
|
132
|
+
// fits it to the viewport (handy for read-only / presentation). Drag-to-resize
|
|
133
|
+
// handles are a later addition.
|
|
134
|
+
Frame: false,
|
|
135
|
+
|
|
136
|
+
// Presentation fit. 'contain' (default) keeps the legacy fitToFrame/FitOnLoad
|
|
137
|
+
// behavior. 'width' fits the frame's WIDTH to the container (anchored top-left
|
|
138
|
+
// + FitTopMargin) and keeps it fit as the container resizes — the jumbotron /
|
|
139
|
+
// background / fullscreen presentation. Content outside the frame bleeds.
|
|
140
|
+
FitMode: 'contain',
|
|
141
|
+
FitTopMargin: 0,
|
|
142
|
+
|
|
143
|
+
// When true (and not read-only), the content frame gets drag handles: four edge
|
|
144
|
+
// handles to set top/width/height and a move handle to reposition the view-area box.
|
|
145
|
+
EnableFrameEditing: false,
|
|
146
|
+
MinimumFrameSize: 40,
|
|
147
|
+
|
|
80
148
|
DefaultNodeType: 'default',
|
|
81
149
|
DefaultNodeWidth: 180,
|
|
82
150
|
DefaultNodeHeight: 80,
|
|
@@ -93,6 +161,15 @@ const _DefaultConfiguration =
|
|
|
93
161
|
DefaultLayoutParameters: {},
|
|
94
162
|
DefaultLayoutAutoApply: false,
|
|
95
163
|
|
|
164
|
+
// Saved layouts (named position snapshots). LayoutStorage lets a host wire a
|
|
165
|
+
// server / IndexedDB backend by config instead of overriding the layout
|
|
166
|
+
// provider's methods: { read(cb), write(layouts, cb), delete(cb) } with
|
|
167
|
+
// Node-style cb(err, result). false (default) uses localStorage. When
|
|
168
|
+
// ApplyDefaultLayoutOnLoad is on, the layout marked default is applied once
|
|
169
|
+
// the graph first renders.
|
|
170
|
+
LayoutStorage: false,
|
|
171
|
+
ApplyDefaultLayoutOnLoad: false,
|
|
172
|
+
|
|
96
173
|
// Edge-theme subsystem defaults — null = inherit from active layout's
|
|
97
174
|
// `DefaultEdgeTheme` field, with hard fallback to 'Bezier'.
|
|
98
175
|
DefaultEdgeTheme: null,
|
|
@@ -161,11 +238,88 @@ const _DefaultConfiguration =
|
|
|
161
238
|
]
|
|
162
239
|
};
|
|
163
240
|
|
|
241
|
+
// Config profiles. Each is a partial option bundle that expands under the
|
|
242
|
+
// defaults and beneath the host's explicit options, so a single Profile knob
|
|
243
|
+
// gives a coherent setup that the host can still override field by field.
|
|
244
|
+
const _PROFILES =
|
|
245
|
+
{
|
|
246
|
+
// Directed graph editor (the historical default). Empty so the defaults stand.
|
|
247
|
+
graph: {},
|
|
248
|
+
|
|
249
|
+
// Free-form whiteboard: undirected links, resize, multi-select + alignment
|
|
250
|
+
// guides, wheel pans (ctrl/cmd zooms), dotted background, no layout menu.
|
|
251
|
+
whiteboard:
|
|
252
|
+
{
|
|
253
|
+
EnableUndirectedConnections: true,
|
|
254
|
+
EnableNodeResizing: true,
|
|
255
|
+
EnableNodeRotation: true,
|
|
256
|
+
EnableMultiSelect: true,
|
|
257
|
+
EnableAlignmentGuides: true,
|
|
258
|
+
EnableLayoutMenu: false,
|
|
259
|
+
WheelMode: 'pan',
|
|
260
|
+
WheelZoomRequiresModifier: true,
|
|
261
|
+
Background: { Style: 'dots' }
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// Moodboard: free-form content cards, undirected links, no default node types
|
|
265
|
+
// or add-node button, edge-to-edge cards (no title bar), wheel pans.
|
|
266
|
+
moodboard:
|
|
267
|
+
{
|
|
268
|
+
EnableUndirectedConnections: true,
|
|
269
|
+
EnableNodeResizing: true,
|
|
270
|
+
EnableNodeRotation: true,
|
|
271
|
+
EnableAlignmentGuides: true,
|
|
272
|
+
EnableAddNode: false,
|
|
273
|
+
EnableLayoutMenu: false,
|
|
274
|
+
IncludeDefaultNodeTypes: false,
|
|
275
|
+
NodeTitleBarHeight: 0,
|
|
276
|
+
WheelMode: 'pan',
|
|
277
|
+
WheelZoomRequiresModifier: true,
|
|
278
|
+
// Flat, light canvas (no grid) by default; a host can recolor it via setBackground.
|
|
279
|
+
Background: { Style: 'solid', Color: '#f4f6f9' }
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
// Read-only presentation surface: no editing, no toolbar; plain wheel pans,
|
|
283
|
+
// ctrl/cmd wheel zooms.
|
|
284
|
+
presentation:
|
|
285
|
+
{
|
|
286
|
+
ReadOnly: true,
|
|
287
|
+
EnableToolbar: false,
|
|
288
|
+
WheelMode: 'pan',
|
|
289
|
+
WheelZoomRequiresModifier: true
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
// Entity-relationship diagrams (fleshed out in a later phase). Orthogonal
|
|
293
|
+
// edges read best for ERDs.
|
|
294
|
+
erd:
|
|
295
|
+
{
|
|
296
|
+
DefaultEdgeTheme: 'Orthogonal'
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
164
300
|
class PictViewFlow extends libPictView
|
|
165
301
|
{
|
|
302
|
+
/**
|
|
303
|
+
* Merge a Profile's option bundle between the defaults and the host's
|
|
304
|
+
* explicit options: defaults < profile < pOptions. Pure (no side effects),
|
|
305
|
+
* so it is unit testable without constructing the view.
|
|
306
|
+
* @param {Object} pOptions - the host-supplied options (may name a Profile)
|
|
307
|
+
* @returns {Object} the fully merged option set
|
|
308
|
+
*/
|
|
309
|
+
static mergeProfileOptions(pOptions)
|
|
310
|
+
{
|
|
311
|
+
let tmpProfileName = (pOptions && pOptions.Profile) ? pOptions.Profile : (_DefaultConfiguration.Profile || 'graph');
|
|
312
|
+
let tmpProfile = _PROFILES[tmpProfileName] || {};
|
|
313
|
+
return Object.assign(
|
|
314
|
+
{},
|
|
315
|
+
JSON.parse(JSON.stringify(_DefaultConfiguration)),
|
|
316
|
+
JSON.parse(JSON.stringify(tmpProfile)),
|
|
317
|
+
pOptions || {});
|
|
318
|
+
}
|
|
319
|
+
|
|
166
320
|
constructor(pFable, pOptions, pServiceHash)
|
|
167
321
|
{
|
|
168
|
-
let tmpOptions =
|
|
322
|
+
let tmpOptions = PictViewFlow.mergeProfileOptions(pOptions);
|
|
169
323
|
super(pFable, tmpOptions, pServiceHash);
|
|
170
324
|
|
|
171
325
|
this.serviceType = 'PictSectionFlow';
|
|
@@ -197,6 +351,7 @@ class PictViewFlow extends libPictView
|
|
|
197
351
|
{ ServiceType: 'PictProviderFlowNodeTypes', Library: libPictProviderFlowNodeTypes, Property: '_NodeTypeProvider', ExtraOptions: () => ({ AdditionalNodeTypes: this.options.NodeTypes, IncludeDefaultNodeTypes: this.options.IncludeDefaultNodeTypes }) },
|
|
198
352
|
{ ServiceType: 'PictProviderFlowEventHandler', Library: libPictProviderFlowEventHandler, Property: '_EventHandlerProvider' },
|
|
199
353
|
{ ServiceType: 'PictProviderFlowLayouts', Library: libPictProviderFlowLayouts, Property: '_LayoutProvider', PostInit: 'loadPersistedLayouts' },
|
|
354
|
+
{ ServiceType: 'PictProviderFlowBackground', Library: libPictProviderFlowBackground, Property: '_BackgroundProvider' },
|
|
200
355
|
|
|
201
356
|
// Services
|
|
202
357
|
{ ServiceType: 'PictServiceFlowPathGenerator', Library: libPictServiceFlowPathGenerator, Property: '_PathGenerator' },
|
|
@@ -205,6 +360,7 @@ class PictViewFlow extends libPictView
|
|
|
205
360
|
{ ServiceType: 'PictServiceFlowRenderManager', Library: libPictServiceFlowRenderManager, Property: '_RenderManager' },
|
|
206
361
|
{ ServiceType: 'PictServiceFlowPortRenderer', Library: libPictServiceFlowPortRenderer, Property: '_PortRenderer' },
|
|
207
362
|
{ ServiceType: 'PictServiceFlowInteractionManager', Library: libPictServiceFlowInteractionManager, Property: '_InteractionManager' },
|
|
363
|
+
{ ServiceType: 'PictServiceFlowCursorManager', Library: libPictServiceFlowCursorManager, Property: '_CursorManager' },
|
|
208
364
|
{ ServiceType: 'PictServiceFlowConnectionRenderer', Library: libPictServiceFlowConnectionRenderer, Property: '_ConnectionRenderer' },
|
|
209
365
|
{ ServiceType: 'PictServiceFlowTether', Library: libPictServiceFlowTether, Property: '_TetherService' },
|
|
210
366
|
{ ServiceType: 'PictServiceFlowLayout', Library: libPictServiceFlowLayout, Property: '_LayoutService' },
|
|
@@ -271,6 +427,7 @@ class PictViewFlow extends libPictView
|
|
|
271
427
|
this._PortRenderer = null;
|
|
272
428
|
|
|
273
429
|
this._InteractionManager = null;
|
|
430
|
+
this._CursorManager = null;
|
|
274
431
|
this._ConnectionRenderer = null;
|
|
275
432
|
this._TetherService = null;
|
|
276
433
|
this._LayoutService = null;
|
|
@@ -290,11 +447,26 @@ class PictViewFlow extends libPictView
|
|
|
290
447
|
this._PanelChromeProvider = null;
|
|
291
448
|
this._NodeTypeProvider = null;
|
|
292
449
|
this._LayoutProvider = null;
|
|
450
|
+
this._BackgroundProvider = null;
|
|
293
451
|
this._EventHandlerProvider = null;
|
|
294
452
|
this._NodeView = null;
|
|
295
453
|
this._ToolbarView = null;
|
|
296
454
|
this._PropertiesPanelView = null;
|
|
297
455
|
|
|
456
|
+
// Registry of renderable renderers keyed by RenderableType. The default
|
|
457
|
+
// 'card' renderer (the node view) is registered once it exists; consumers
|
|
458
|
+
// register additional renderers (shape, sticky, text, image, category,
|
|
459
|
+
// connector) to draw renderables that are not cards.
|
|
460
|
+
this._RenderableRenderers = {};
|
|
461
|
+
|
|
462
|
+
// Read-only / presentation flag (runtime; defaults to the option).
|
|
463
|
+
this._ReadOnly = !!this.options.ReadOnly;
|
|
464
|
+
|
|
465
|
+
// In read-only, canvas pan + wheel-zoom are OFF by default (the wheel scrolls the page and the
|
|
466
|
+
// canvas reads as static content). A host flips it on with a "hand" toggle via
|
|
467
|
+
// setReadOnlyNavigation. No effect in edit mode.
|
|
468
|
+
this._ReadOnlyNavigation = false;
|
|
469
|
+
|
|
298
470
|
this.initialRenderComplete = false;
|
|
299
471
|
}
|
|
300
472
|
|
|
@@ -310,6 +482,377 @@ class PictViewFlow extends libPictView
|
|
|
310
482
|
}
|
|
311
483
|
}
|
|
312
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Register a renderable renderer under a key. The default 'card' renderer is
|
|
487
|
+
* the node view; consumers register additional renderers (shape, sticky,
|
|
488
|
+
* text, image, category, connector) to draw non-card renderables. A renderer
|
|
489
|
+
* must expose renderNode(node, layerElement, isSelected, typeConfig).
|
|
490
|
+
* @param {string} pKey
|
|
491
|
+
* @param {Object} pRenderer
|
|
492
|
+
* @returns {boolean}
|
|
493
|
+
*/
|
|
494
|
+
registerRenderableRenderer(pKey, pRenderer)
|
|
495
|
+
{
|
|
496
|
+
if (!pKey || !pRenderer)
|
|
497
|
+
{
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
this._RenderableRenderers[pKey] = pRenderer;
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Resolve the renderable renderer for a node. A node type may name its
|
|
506
|
+
* renderer via the RenderableType field (default 'card'); unknown keys fall
|
|
507
|
+
* back to the card renderer so existing diagrams are unaffected.
|
|
508
|
+
* @param {Object} pNode
|
|
509
|
+
* @param {Object} pNodeTypeConfig
|
|
510
|
+
* @returns {Object} a renderer exposing renderNode(...)
|
|
511
|
+
*/
|
|
512
|
+
resolveRenderableRenderer(pNode, pNodeTypeConfig)
|
|
513
|
+
{
|
|
514
|
+
// A node may name its renderer directly via pNode.RenderableType (wins), else
|
|
515
|
+
// the node type config's RenderableType, else the default 'card'. The node-level
|
|
516
|
+
// override lets a free-form canvas mint shape/sticky/text renderables without
|
|
517
|
+
// registering a node type for each. Unknown keys fall back to card so existing
|
|
518
|
+
// diagrams are unaffected.
|
|
519
|
+
let tmpKey = 'card';
|
|
520
|
+
if (pNode && typeof pNode.RenderableType === 'string' && pNode.RenderableType)
|
|
521
|
+
{
|
|
522
|
+
tmpKey = pNode.RenderableType;
|
|
523
|
+
}
|
|
524
|
+
else if (pNodeTypeConfig && pNodeTypeConfig.RenderableType)
|
|
525
|
+
{
|
|
526
|
+
tmpKey = pNodeTypeConfig.RenderableType;
|
|
527
|
+
}
|
|
528
|
+
return this._RenderableRenderers[tmpKey] || this._RenderableRenderers['card'] || this._NodeView;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Apply the configured canvas background to the live SVG. No-op when no
|
|
533
|
+
* Background is configured (the template grid stands). Safe to call on every
|
|
534
|
+
* render; idempotent.
|
|
535
|
+
*/
|
|
536
|
+
_applyBackground()
|
|
537
|
+
{
|
|
538
|
+
if (this._BackgroundProvider)
|
|
539
|
+
{
|
|
540
|
+
return this._BackgroundProvider.apply(this);
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Set the canvas background and repaint it. Pass an object
|
|
547
|
+
* { Style, Color?, Image?, GridSize?, DotSize? } or false to clear it back to
|
|
548
|
+
* the template grid. Stored on ViewState so it travels with the flow data.
|
|
549
|
+
* @param {Object|false} pBackground
|
|
550
|
+
*/
|
|
551
|
+
setBackground(pBackground)
|
|
552
|
+
{
|
|
553
|
+
this._FlowData.ViewState.Background = pBackground || null;
|
|
554
|
+
return this._applyBackground();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Whether the flow is in read-only / presentation mode.
|
|
559
|
+
* @returns {boolean}
|
|
560
|
+
*/
|
|
561
|
+
isReadOnly()
|
|
562
|
+
{
|
|
563
|
+
return !!this._ReadOnly;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Toggle read-only / presentation mode at runtime. Editing interactions go
|
|
568
|
+
* inert; the container gets the 'pict-flow-readonly' class so chrome can be
|
|
569
|
+
* hidden via CSS. Repaints so port/grip visibility updates.
|
|
570
|
+
* @param {boolean} pReadOnly
|
|
571
|
+
* @returns {boolean} the new read-only state
|
|
572
|
+
*/
|
|
573
|
+
setReadOnly(pReadOnly)
|
|
574
|
+
{
|
|
575
|
+
this._ReadOnly = !!pReadOnly;
|
|
576
|
+
// Navigation is a read-only concept; leaving read-only clears it.
|
|
577
|
+
if (!this._ReadOnly) { this._ReadOnlyNavigation = false; }
|
|
578
|
+
this._applyReadOnlyClass();
|
|
579
|
+
if (this._CursorManager) { this._CursorManager.update(); }
|
|
580
|
+
if (this.initialRenderComplete)
|
|
581
|
+
{
|
|
582
|
+
this.renderFlow();
|
|
583
|
+
}
|
|
584
|
+
return this._ReadOnly;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Whether read-only canvas navigation (pan + wheel-zoom) is enabled. When off
|
|
589
|
+
* (the default in read-only), the wheel scrolls the page and the canvas does
|
|
590
|
+
* not pan, so it reads as static content.
|
|
591
|
+
* @returns {boolean}
|
|
592
|
+
*/
|
|
593
|
+
isReadOnlyNavigation()
|
|
594
|
+
{
|
|
595
|
+
return !!this._ReadOnlyNavigation;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Turn read-only canvas navigation (pan + wheel-zoom) on or off. This is the
|
|
600
|
+
* "hand" toggle for a presentation surface; it has no effect in edit mode.
|
|
601
|
+
* @param {boolean} pOn
|
|
602
|
+
* @returns {boolean} the new navigation state
|
|
603
|
+
*/
|
|
604
|
+
setReadOnlyNavigation(pOn)
|
|
605
|
+
{
|
|
606
|
+
this._ReadOnlyNavigation = !!pOn;
|
|
607
|
+
this._applyReadOnlyClass();
|
|
608
|
+
if (this._CursorManager) { this._CursorManager.update(); }
|
|
609
|
+
if (this._EventHandlerProvider)
|
|
610
|
+
{
|
|
611
|
+
this._EventHandlerProvider.fireEvent('onReadOnlyNavigationChanged', this._ReadOnlyNavigation);
|
|
612
|
+
}
|
|
613
|
+
return this._ReadOnlyNavigation;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Reflect the read-only flag as a class on the flow container so CSS can hide
|
|
618
|
+
* editing chrome (ports, delete affordance, resize grips). Internal.
|
|
619
|
+
*/
|
|
620
|
+
_applyReadOnlyClass()
|
|
621
|
+
{
|
|
622
|
+
let tmpWrapper = this.pict.ContentAssignment.getElement(`#Flow-Wrapper-${this.options.ViewIdentifier}`);
|
|
623
|
+
if (tmpWrapper && tmpWrapper.length > 0 && tmpWrapper[0].classList)
|
|
624
|
+
{
|
|
625
|
+
if (this._ReadOnly)
|
|
626
|
+
{
|
|
627
|
+
tmpWrapper[0].classList.add('pict-flow-readonly');
|
|
628
|
+
}
|
|
629
|
+
else
|
|
630
|
+
{
|
|
631
|
+
tmpWrapper[0].classList.remove('pict-flow-readonly');
|
|
632
|
+
}
|
|
633
|
+
// 'navigating' = read-only with pan/zoom enabled; drives the grab cursor.
|
|
634
|
+
if (this._ReadOnly && this._ReadOnlyNavigation)
|
|
635
|
+
{
|
|
636
|
+
tmpWrapper[0].classList.add('pict-flow-navigating');
|
|
637
|
+
}
|
|
638
|
+
else
|
|
639
|
+
{
|
|
640
|
+
tmpWrapper[0].classList.remove('pict-flow-navigating');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Resolve the effective content frame: ViewState wins, then the option.
|
|
647
|
+
* @returns {Object|null}
|
|
648
|
+
*/
|
|
649
|
+
_resolveFrame()
|
|
650
|
+
{
|
|
651
|
+
let tmpFrame = (this._FlowData && this._FlowData.ViewState && this._FlowData.ViewState.Frame)
|
|
652
|
+
? this._FlowData.ViewState.Frame
|
|
653
|
+
: this.options.Frame;
|
|
654
|
+
return (tmpFrame && typeof tmpFrame === 'object') ? tmpFrame : null;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Set (or clear, with false) the content frame and repaint it. Stored on
|
|
659
|
+
* ViewState so it travels with the flow data.
|
|
660
|
+
* @param {Object|false} pFrame
|
|
661
|
+
*/
|
|
662
|
+
setFrame(pFrame)
|
|
663
|
+
{
|
|
664
|
+
this._FlowData.ViewState.Frame = (pFrame && typeof pFrame === 'object') ? pFrame : null;
|
|
665
|
+
this._applyFrame();
|
|
666
|
+
return this._FlowData.ViewState.Frame;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Read the effective content frame (ViewState wins, then the option), or null
|
|
671
|
+
* when none is set. The public read counterpart to setFrame.
|
|
672
|
+
* @returns {Object|null}
|
|
673
|
+
*/
|
|
674
|
+
getFrame()
|
|
675
|
+
{
|
|
676
|
+
return this._resolveFrame();
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Toggle the content frame's drag handles at runtime (e.g. an editor turning on
|
|
681
|
+
* "set the view area"). Repaints the frame so the handles appear / disappear.
|
|
682
|
+
* @param {boolean} pEnabled
|
|
683
|
+
* @returns {boolean}
|
|
684
|
+
*/
|
|
685
|
+
setFrameEditing(pEnabled)
|
|
686
|
+
{
|
|
687
|
+
this.options.EnableFrameEditing = !!pEnabled;
|
|
688
|
+
this._applyFrame();
|
|
689
|
+
return this.options.EnableFrameEditing;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Draw, update, or remove the content-frame rectangle inside the viewport
|
|
694
|
+
* group (behind the content layers). No-op until the viewport exists.
|
|
695
|
+
*/
|
|
696
|
+
_applyFrame()
|
|
697
|
+
{
|
|
698
|
+
if (!this._ViewportElement) return;
|
|
699
|
+
|
|
700
|
+
let tmpId = `Flow-Frame-${this.options.ViewIdentifier}`;
|
|
701
|
+
let tmpExisting = this._ViewportElement.querySelector(`#${tmpId}`);
|
|
702
|
+
let tmpFrame = this._resolveFrame();
|
|
703
|
+
|
|
704
|
+
if (!tmpFrame || tmpFrame.Enabled === false || !tmpFrame.Width || !tmpFrame.Height)
|
|
705
|
+
{
|
|
706
|
+
if (tmpExisting) tmpExisting.remove();
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
let tmpRect = tmpExisting;
|
|
711
|
+
if (!tmpRect)
|
|
712
|
+
{
|
|
713
|
+
tmpRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
714
|
+
tmpRect.setAttribute('id', tmpId);
|
|
715
|
+
tmpRect.setAttribute('class', 'pict-flow-frame');
|
|
716
|
+
// First child of the viewport group: behind connections and nodes.
|
|
717
|
+
this._ViewportElement.insertBefore(tmpRect, this._ViewportElement.firstChild);
|
|
718
|
+
}
|
|
719
|
+
tmpRect.setAttribute('x', tmpFrame.X || 0);
|
|
720
|
+
tmpRect.setAttribute('y', tmpFrame.Y || 0);
|
|
721
|
+
tmpRect.setAttribute('width', tmpFrame.Width);
|
|
722
|
+
tmpRect.setAttribute('height', tmpFrame.Height);
|
|
723
|
+
|
|
724
|
+
this._applyFrameHandles(tmpFrame);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Draw, refresh, or remove the content frame's drag handles. Shown only when
|
|
729
|
+
* EnableFrameEditing is on and the view is editable: four edge handles (set top /
|
|
730
|
+
* width / height) and a move handle at the top-left. Rendered on top of the content
|
|
731
|
+
* (appended last in the viewport) so they are grabbable; their data-element-type routes
|
|
732
|
+
* pointer-down to the InteractionManager's frame edit paths.
|
|
733
|
+
* @param {Object} pFrame - the resolved frame ({X,Y,Width,Height})
|
|
734
|
+
*/
|
|
735
|
+
_applyFrameHandles(pFrame)
|
|
736
|
+
{
|
|
737
|
+
if (!this._ViewportElement) return;
|
|
738
|
+
|
|
739
|
+
let tmpId = `Flow-FrameHandles-${this.options.ViewIdentifier}`;
|
|
740
|
+
let tmpExisting = this._ViewportElement.querySelector(`#${tmpId}`);
|
|
741
|
+
if (tmpExisting) tmpExisting.remove();
|
|
742
|
+
|
|
743
|
+
let tmpEditable = this.options.EnableFrameEditing
|
|
744
|
+
&& !(typeof this.isReadOnly === 'function' && this.isReadOnly())
|
|
745
|
+
&& pFrame && pFrame.Width && pFrame.Height;
|
|
746
|
+
if (!tmpEditable) return;
|
|
747
|
+
|
|
748
|
+
let tmpX = pFrame.X || 0;
|
|
749
|
+
let tmpY = pFrame.Y || 0;
|
|
750
|
+
let tmpW = pFrame.Width;
|
|
751
|
+
let tmpH = pFrame.Height;
|
|
752
|
+
let tmpSize = 12;
|
|
753
|
+
|
|
754
|
+
let tmpGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
755
|
+
tmpGroup.setAttribute('id', tmpId);
|
|
756
|
+
tmpGroup.setAttribute('class', 'pict-flow-frame-handles');
|
|
757
|
+
|
|
758
|
+
let tmpHandles =
|
|
759
|
+
[
|
|
760
|
+
{ Edge: 'n', Type: 'frame-resize', CX: tmpX + tmpW / 2, CY: tmpY },
|
|
761
|
+
{ Edge: 's', Type: 'frame-resize', CX: tmpX + tmpW / 2, CY: tmpY + tmpH },
|
|
762
|
+
{ Edge: 'e', Type: 'frame-resize', CX: tmpX + tmpW, CY: tmpY + tmpH / 2 },
|
|
763
|
+
{ Edge: 'w', Type: 'frame-resize', CX: tmpX, CY: tmpY + tmpH / 2 },
|
|
764
|
+
{ Edge: 'move', Type: 'frame-move', CX: tmpX, CY: tmpY }
|
|
765
|
+
];
|
|
766
|
+
for (let i = 0; i < tmpHandles.length; i++)
|
|
767
|
+
{
|
|
768
|
+
let tmpHandle = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
769
|
+
tmpHandle.setAttribute('class', 'pict-flow-frame-handle pict-flow-frame-handle-' + tmpHandles[i].Edge);
|
|
770
|
+
tmpHandle.setAttribute('x', String(tmpHandles[i].CX - tmpSize / 2));
|
|
771
|
+
tmpHandle.setAttribute('y', String(tmpHandles[i].CY - tmpSize / 2));
|
|
772
|
+
tmpHandle.setAttribute('width', String(tmpSize));
|
|
773
|
+
tmpHandle.setAttribute('height', String(tmpSize));
|
|
774
|
+
tmpHandle.setAttribute('rx', '2');
|
|
775
|
+
tmpHandle.setAttribute('data-element-type', tmpHandles[i].Type);
|
|
776
|
+
if (tmpHandles[i].Edge !== 'move')
|
|
777
|
+
{
|
|
778
|
+
tmpHandle.setAttribute('data-frame-edge', tmpHandles[i].Edge);
|
|
779
|
+
}
|
|
780
|
+
tmpGroup.appendChild(tmpHandle);
|
|
781
|
+
}
|
|
782
|
+
this._ViewportElement.appendChild(tmpGroup);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Fit the viewport to the content frame (zoom + pan so it fills the view).
|
|
787
|
+
* @returns {boolean}
|
|
788
|
+
*/
|
|
789
|
+
fitToFrame()
|
|
790
|
+
{
|
|
791
|
+
return this._ViewportManager ? this._ViewportManager.fitToFrame(this._resolveFrame()) : false;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Fit the viewport so the content frame's WIDTH fills the view (anchored top-left + an
|
|
796
|
+
* optional top margin), letting content bleed past and vertical overflow scroll. This is
|
|
797
|
+
* the presentation fit a jumbotron / background board uses; fitToFrame() contains instead.
|
|
798
|
+
* @param {Object} [pOptions] - { TopMargin }
|
|
799
|
+
* @returns {boolean}
|
|
800
|
+
*/
|
|
801
|
+
fitToWidth(pOptions)
|
|
802
|
+
{
|
|
803
|
+
if (!this._ViewportManager) return false;
|
|
804
|
+
let tmpOptions = pOptions || {};
|
|
805
|
+
if (typeof tmpOptions.TopMargin !== 'number')
|
|
806
|
+
{
|
|
807
|
+
tmpOptions = { TopMargin: this.options.FitTopMargin || 0 };
|
|
808
|
+
}
|
|
809
|
+
return this._ViewportManager.fitToFrameWidth(this._resolveFrame(), tmpOptions);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* When FitMode is 'width', fit the frame to the container width now and keep it fit as the
|
|
814
|
+
* container resizes (a ResizeObserver, the documented exception to the no-listener rule).
|
|
815
|
+
* No-op without a frame or when FitMode is not 'width'.
|
|
816
|
+
*/
|
|
817
|
+
_setupFitObserver()
|
|
818
|
+
{
|
|
819
|
+
if (this.options.FitMode !== 'width' || !this._resolveFrame())
|
|
820
|
+
{
|
|
821
|
+
// Not width-fit (e.g. switched back to 'contain'): drop any observer we
|
|
822
|
+
// installed so a stale ResizeObserver does not keep re-fitting the canvas.
|
|
823
|
+
this._teardownFitObserver();
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
this.fitToWidth();
|
|
827
|
+
if ((typeof ResizeObserver !== 'undefined') && this._SVGElement)
|
|
828
|
+
{
|
|
829
|
+
// (Re)observe the CURRENT SVG element. A re-render (e.g. a host toggling presentation mode)
|
|
830
|
+
// can replace the SVG, which would leave a previously-installed observer watching a stale,
|
|
831
|
+
// detached node — so re-point it whenever the observed element has changed.
|
|
832
|
+
if (this._FitObserver && this._FitObservedElement === this._SVGElement) { return; }
|
|
833
|
+
this._teardownFitObserver();
|
|
834
|
+
let tmpSelf = this;
|
|
835
|
+
this._FitObserver = new ResizeObserver(function () { tmpSelf.fitToWidth(); });
|
|
836
|
+
this._FitObserver.observe(this._SVGElement);
|
|
837
|
+
this._FitObservedElement = this._SVGElement;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Disconnect the width-fit ResizeObserver, if one is installed. Called when a
|
|
843
|
+
* consumer leaves 'width' FitMode (e.g. a moodboard switching back to canvas)
|
|
844
|
+
* so the observer stops firing. Safe to call when no observer exists.
|
|
845
|
+
*/
|
|
846
|
+
_teardownFitObserver()
|
|
847
|
+
{
|
|
848
|
+
if (this._FitObserver)
|
|
849
|
+
{
|
|
850
|
+
this._FitObserver.disconnect();
|
|
851
|
+
this._FitObserver = null;
|
|
852
|
+
}
|
|
853
|
+
this._FitObservedElement = null;
|
|
854
|
+
}
|
|
855
|
+
|
|
313
856
|
_instantiateServices()
|
|
314
857
|
{
|
|
315
858
|
for (let i = 0; i < this._ServiceRegistry.length; i++)
|
|
@@ -488,12 +1031,24 @@ class PictViewFlow extends libPictView
|
|
|
488
1031
|
}
|
|
489
1032
|
this._SVGElement = tmpSVGElements[0];
|
|
490
1033
|
|
|
1034
|
+
// Paint a configured background (no-op when none is set; template grid stands).
|
|
1035
|
+
this._applyBackground();
|
|
1036
|
+
|
|
1037
|
+
// Reflect read-only mode on the container (drives chrome-hiding CSS).
|
|
1038
|
+
this._applyReadOnlyClass();
|
|
1039
|
+
|
|
1040
|
+
// Set the initial canvas cursor from the (idle) state + current mode.
|
|
1041
|
+
if (this._CursorManager) { this._CursorManager.update(); }
|
|
1042
|
+
|
|
491
1043
|
let tmpViewportElements = this.pict.ContentAssignment.getElement(`#Flow-Viewport-${tmpViewIdentifier}`);
|
|
492
1044
|
if (tmpViewportElements.length > 0)
|
|
493
1045
|
{
|
|
494
1046
|
this._ViewportElement = tmpViewportElements[0];
|
|
495
1047
|
}
|
|
496
1048
|
|
|
1049
|
+
// Draw the content frame, if configured (needs the viewport <g>).
|
|
1050
|
+
this._applyFrame();
|
|
1051
|
+
|
|
497
1052
|
let tmpNodesElements = this.pict.ContentAssignment.getElement(`#Flow-Nodes-${tmpViewIdentifier}`);
|
|
498
1053
|
if (tmpNodesElements.length > 0)
|
|
499
1054
|
{
|
|
@@ -562,7 +1117,8 @@ class PictViewFlow extends libPictView
|
|
|
562
1117
|
DefaultDestinationAddress: `#Flow-Toolbar-${tmpViewIdentifier}`,
|
|
563
1118
|
FlowViewIdentifier: tmpViewIdentifier,
|
|
564
1119
|
EnableAddNode: this.options.EnableAddNode,
|
|
565
|
-
EnableCardPalette: this.options.EnableCardPalette
|
|
1120
|
+
EnableCardPalette: this.options.EnableCardPalette,
|
|
1121
|
+
ToolbarExtraButtons: this.options.ToolbarExtraButtons
|
|
566
1122
|
}
|
|
567
1123
|
));
|
|
568
1124
|
// Use the toolbar's render method after it's set up
|
|
@@ -581,6 +1137,8 @@ class PictViewFlow extends libPictView
|
|
|
581
1137
|
this._NodeView = this.fable.instantiateServiceProviderWithoutRegistration('PictViewFlowNode',
|
|
582
1138
|
Object.assign({}, libPictViewFlowNode.default_configuration, tmpNodeViewOptions));
|
|
583
1139
|
this._NodeView._FlowView = this;
|
|
1140
|
+
// The default renderable renderer. Non-card renderers register alongside it.
|
|
1141
|
+
this._RenderableRenderers['card'] = this._NodeView;
|
|
584
1142
|
|
|
585
1143
|
// Setup the properties panel renderer
|
|
586
1144
|
this._PropertiesPanelView = this.fable.instantiateServiceProviderWithoutRegistration('PictViewFlowPropertiesPanel',
|
|
@@ -607,8 +1165,45 @@ class PictViewFlow extends libPictView
|
|
|
607
1165
|
|
|
608
1166
|
// Render the initial flow
|
|
609
1167
|
this.renderFlow();
|
|
1168
|
+
|
|
1169
|
+
// Optionally snap to the default saved layout once the graph is drawn. A host with an async
|
|
1170
|
+
// LayoutStorage that resolves later should call applyDefaultLayout() from its load callback.
|
|
1171
|
+
if (this.options.ApplyDefaultLayoutOnLoad && this._LayoutProvider && this._LayoutProvider.getDefaultLayout())
|
|
1172
|
+
{
|
|
1173
|
+
this._LayoutProvider.applyDefaultLayout();
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Presentation fit. FitMode 'width' fits the frame to the container width now and on
|
|
1177
|
+
// resize; otherwise honor a frame's legacy FitOnLoad (contain). Both need the SVG sized,
|
|
1178
|
+
// so this runs after the initial render.
|
|
1179
|
+
if (this.options.FitMode === 'width')
|
|
1180
|
+
{
|
|
1181
|
+
this._setupFitObserver();
|
|
1182
|
+
}
|
|
1183
|
+
else
|
|
1184
|
+
{
|
|
1185
|
+
let tmpFrame = this._resolveFrame();
|
|
1186
|
+
if (tmpFrame && tmpFrame.FitOnLoad)
|
|
1187
|
+
{
|
|
1188
|
+
this.fitToFrame();
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
610
1191
|
}
|
|
611
1192
|
|
|
1193
|
+
// ---- Saved-layout delegations ----
|
|
1194
|
+
// A clean view-level API over the layout provider for saving, restoring, and
|
|
1195
|
+
// choosing a default arrangement of the same graph. The toolbar uses the
|
|
1196
|
+
// provider directly; these are additive.
|
|
1197
|
+
|
|
1198
|
+
saveLayout(pName) { return this._LayoutProvider ? this._LayoutProvider.saveLayout(pName) : null; }
|
|
1199
|
+
restoreLayout(pHash) { return this._LayoutProvider ? this._LayoutProvider.restoreLayout(pHash) : false; }
|
|
1200
|
+
applyLayout(pHash) { return this.restoreLayout(pHash); }
|
|
1201
|
+
deleteLayout(pHash) { return this._LayoutProvider ? this._LayoutProvider.deleteLayout(pHash) : false; }
|
|
1202
|
+
getLayouts() { return this._LayoutProvider ? this._LayoutProvider.getLayouts() : []; }
|
|
1203
|
+
setDefaultLayout(pHash) { return this._LayoutProvider ? this._LayoutProvider.setDefaultLayout(pHash) : false; }
|
|
1204
|
+
getDefaultLayout() { return this._LayoutProvider ? this._LayoutProvider.getDefaultLayout() : null; }
|
|
1205
|
+
applyDefaultLayout() { return this._LayoutProvider ? this._LayoutProvider.applyDefaultLayout() : false; }
|
|
1206
|
+
|
|
612
1207
|
// ---- Data Manager Delegations ----
|
|
613
1208
|
|
|
614
1209
|
marshalToView() { return this._DataManager.marshalToView(); }
|
|
@@ -1418,6 +2013,27 @@ class PictViewFlow extends libPictView
|
|
|
1418
2013
|
let tmpSource = this.getPortPosition(tmpConnection.SourceNodeHash, tmpConnection.SourcePortHash);
|
|
1419
2014
|
let tmpTarget = this.getPortPosition(tmpConnection.TargetNodeHash, tmpConnection.TargetPortHash);
|
|
1420
2015
|
if (!tmpSource || !tmpTarget) return null;
|
|
2016
|
+
// A connection renders as a curve, so the straight-line average of its endpoints sits OFF the
|
|
2017
|
+
// line (the panel tether would point into empty space). Prefer the true midpoint of the rendered
|
|
2018
|
+
// path -- getPointAtLength at half its length, which is genuinely on the line. Fall back to the
|
|
2019
|
+
// endpoint average when the path element or SVG geometry is unavailable (e.g. server-side render).
|
|
2020
|
+
if (this._SVGElement && typeof this._SVGElement.querySelector === 'function')
|
|
2021
|
+
{
|
|
2022
|
+
let tmpPathElement = this._SVGElement.querySelector('.pict-flow-connection[data-connection-hash="' + pConnectionHash + '"]');
|
|
2023
|
+
if (tmpPathElement && typeof tmpPathElement.getTotalLength === 'function' && typeof tmpPathElement.getPointAtLength === 'function')
|
|
2024
|
+
{
|
|
2025
|
+
try
|
|
2026
|
+
{
|
|
2027
|
+
let tmpLength = tmpPathElement.getTotalLength();
|
|
2028
|
+
if (tmpLength > 0)
|
|
2029
|
+
{
|
|
2030
|
+
let tmpPoint = tmpPathElement.getPointAtLength(tmpLength / 2);
|
|
2031
|
+
return { x: tmpPoint.x, y: tmpPoint.y };
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
catch (pError) { /* fall through to the straight-line midpoint */ }
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
1421
2037
|
return { x: (tmpSource.x + tmpTarget.x) / 2, y: (tmpSource.y + tmpTarget.y) / 2 };
|
|
1422
2038
|
}
|
|
1423
2039
|
|
|
@@ -1551,4 +2167,13 @@ class PictViewFlow extends libPictView
|
|
|
1551
2167
|
|
|
1552
2168
|
module.exports = PictViewFlow;
|
|
1553
2169
|
|
|
1554
|
-
|
|
2170
|
+
// default_configuration is intentionally NOT exported. Pict's addView merges a
|
|
2171
|
+
// view's default_configuration UNDER the host's options, which would make those
|
|
2172
|
+
// now-explicit defaults override Profile values. Defaults are applied inside the
|
|
2173
|
+
// constructor via mergeProfileOptions (defaults < profile < host) so Profile
|
|
2174
|
+
// works as a real config knob. Inspect resolved defaults with
|
|
2175
|
+
// PictViewFlow.mergeProfileOptions({}).
|
|
2176
|
+
|
|
2177
|
+
// Config profiles (graph/whiteboard/moodboard/presentation/erd), exposed so
|
|
2178
|
+
// consumers can inspect or extend them.
|
|
2179
|
+
module.exports.Profiles = _PROFILES;
|