pict-section-flow 0.0.1 → 0.0.2
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/docs/README.md +19 -0
- package/{example_application → example_applications/simple_cards}/html/index.html +2 -2
- package/example_applications/simple_cards/package.json +43 -0
- package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +434 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +36 -0
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +54 -0
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +48 -0
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +35 -0
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +47 -0
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +53 -0
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +95 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +37 -0
- package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +59 -0
- package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-Layout.js +5 -1
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +312 -0
- package/package.json +6 -6
- package/source/Pict-Section-Flow.js +19 -0
- package/source/PictFlowCard.js +207 -0
- package/source/PictFlowCardPropertiesPanel.js +105 -0
- package/source/panels/FlowCardPropertiesPanel-Form.js +174 -0
- package/source/panels/FlowCardPropertiesPanel-Markdown.js +148 -0
- package/source/panels/FlowCardPropertiesPanel-Template.js +88 -0
- package/source/panels/FlowCardPropertiesPanel-View.js +114 -0
- package/source/providers/PictProvider-Flow-EventHandler.js +19 -8
- package/source/providers/PictProvider-Flow-Geometry.js +64 -0
- package/source/providers/PictProvider-Flow-Layouts.js +284 -0
- package/source/providers/PictProvider-Flow-NodeTypes.js +70 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +72 -0
- package/source/providers/PictProvider-Flow-SVGHelpers.js +30 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +324 -66
- package/source/services/PictService-Flow-InteractionManager.js +399 -75
- package/source/services/PictService-Flow-Layout.js +159 -0
- package/source/services/PictService-Flow-PathGenerator.js +199 -0
- package/source/services/PictService-Flow-Tether.js +544 -0
- package/source/views/PictView-Flow-Node.js +95 -18
- package/source/views/PictView-Flow-PropertiesPanel.js +435 -0
- package/source/views/PictView-Flow-Toolbar.js +491 -5
- package/source/views/PictView-Flow.js +830 -8
- package/example_application/package.json +0 -41
- package/example_application/source/Pict-Application-FlowExample.js +0 -241
- package/example_application/source/views/PictView-FlowExample-MainWorkspace.js +0 -191
- /package/{example_application → example_applications/simple_cards}/css/flowexample.css +0 -0
- /package/{example_application → example_applications/simple_cards}/source/Pict-Application-FlowExample-Configuration.json +0 -0
- /package/{example_application → example_applications/simple_cards}/source/providers/PictRouter-FlowExample-Configuration.json +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-About.js +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-BottomBar.js +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-Documentation.js +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-TopBar.js +0 -0
|
@@ -2,13 +2,26 @@ const libPictView = require('pict-view');
|
|
|
2
2
|
|
|
3
3
|
const libPictServiceFlowInteractionManager = require('../services/PictService-Flow-InteractionManager.js');
|
|
4
4
|
const libPictServiceFlowConnectionRenderer = require('../services/PictService-Flow-ConnectionRenderer.js');
|
|
5
|
+
const libPictServiceFlowTether = require('../services/PictService-Flow-Tether.js');
|
|
5
6
|
const libPictServiceFlowLayout = require('../services/PictService-Flow-Layout.js');
|
|
7
|
+
const libPictServiceFlowPathGenerator = require('../services/PictService-Flow-PathGenerator.js');
|
|
6
8
|
|
|
7
9
|
const libPictProviderFlowNodeTypes = require('../providers/PictProvider-Flow-NodeTypes.js');
|
|
8
10
|
const libPictProviderFlowEventHandler = require('../providers/PictProvider-Flow-EventHandler.js');
|
|
11
|
+
const libPictProviderFlowLayouts = require('../providers/PictProvider-Flow-Layouts.js');
|
|
12
|
+
const libPictProviderFlowSVGHelpers = require('../providers/PictProvider-Flow-SVGHelpers.js');
|
|
13
|
+
const libPictProviderFlowGeometry = require('../providers/PictProvider-Flow-Geometry.js');
|
|
14
|
+
const libPictProviderFlowPanelChrome = require('../providers/PictProvider-Flow-PanelChrome.js');
|
|
9
15
|
|
|
10
16
|
const libPictViewFlowNode = require('./PictView-Flow-Node.js');
|
|
11
17
|
const libPictViewFlowToolbar = require('./PictView-Flow-Toolbar.js');
|
|
18
|
+
const libPictViewFlowPropertiesPanel = require('./PictView-Flow-PropertiesPanel.js');
|
|
19
|
+
|
|
20
|
+
const libPictFlowCardPropertiesPanel = require('../PictFlowCardPropertiesPanel.js');
|
|
21
|
+
const libPanelTemplate = require('../panels/FlowCardPropertiesPanel-Template.js');
|
|
22
|
+
const libPanelMarkdown = require('../panels/FlowCardPropertiesPanel-Markdown.js');
|
|
23
|
+
const libPanelForm = require('../panels/FlowCardPropertiesPanel-Form.js');
|
|
24
|
+
const libPanelView = require('../panels/FlowCardPropertiesPanel-View.js');
|
|
12
25
|
|
|
13
26
|
const _DefaultConfiguration =
|
|
14
27
|
{
|
|
@@ -49,11 +62,17 @@ const _DefaultConfiguration =
|
|
|
49
62
|
background-color: #fafafa;
|
|
50
63
|
border: 1px solid #e0e0e0;
|
|
51
64
|
border-radius: 4px;
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
}
|
|
68
|
+
.pict-flow-svg-container {
|
|
69
|
+
flex: 1;
|
|
70
|
+
min-height: 0;
|
|
71
|
+
position: relative;
|
|
52
72
|
}
|
|
53
73
|
.pict-flow-svg {
|
|
54
74
|
width: 100%;
|
|
55
75
|
height: 100%;
|
|
56
|
-
min-height: 400px;
|
|
57
76
|
cursor: grab;
|
|
58
77
|
user-select: none;
|
|
59
78
|
-webkit-user-select: none;
|
|
@@ -178,16 +197,231 @@ const _DefaultConfiguration =
|
|
|
178
197
|
rx: 25;
|
|
179
198
|
ry: 25;
|
|
180
199
|
}
|
|
200
|
+
.pict-flow-connection-handle {
|
|
201
|
+
fill: #ffffff;
|
|
202
|
+
stroke: #3498db;
|
|
203
|
+
stroke-width: 2;
|
|
204
|
+
cursor: grab;
|
|
205
|
+
transition: r 0.15s;
|
|
206
|
+
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
|
|
207
|
+
}
|
|
208
|
+
.pict-flow-connection-handle:hover {
|
|
209
|
+
r: 8;
|
|
210
|
+
stroke-width: 2.5;
|
|
211
|
+
}
|
|
212
|
+
.pict-flow-connection-handle-midpoint {
|
|
213
|
+
fill: #ffffff;
|
|
214
|
+
stroke: #e67e22;
|
|
215
|
+
stroke-width: 2;
|
|
216
|
+
cursor: grab;
|
|
217
|
+
transition: r 0.15s;
|
|
218
|
+
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
|
|
219
|
+
}
|
|
220
|
+
.pict-flow-connection-handle-midpoint:hover {
|
|
221
|
+
r: 8;
|
|
222
|
+
stroke-width: 2.5;
|
|
223
|
+
}
|
|
224
|
+
.pict-flow-tether-line {
|
|
225
|
+
fill: none;
|
|
226
|
+
stroke: #95a5a6;
|
|
227
|
+
stroke-width: 1.5;
|
|
228
|
+
stroke-dasharray: 6 4;
|
|
229
|
+
pointer-events: visibleStroke;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
}
|
|
232
|
+
.pict-flow-tether-line.selected {
|
|
233
|
+
stroke: #3498db;
|
|
234
|
+
stroke-width: 2;
|
|
235
|
+
}
|
|
236
|
+
.pict-flow-tether-hitarea {
|
|
237
|
+
fill: none;
|
|
238
|
+
stroke: transparent;
|
|
239
|
+
stroke-width: 10;
|
|
240
|
+
cursor: pointer;
|
|
241
|
+
}
|
|
242
|
+
.pict-flow-tether-handle {
|
|
243
|
+
fill: #ffffff;
|
|
244
|
+
stroke: #3498db;
|
|
245
|
+
stroke-width: 2;
|
|
246
|
+
cursor: grab;
|
|
247
|
+
transition: r 0.15s;
|
|
248
|
+
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
|
|
249
|
+
}
|
|
250
|
+
.pict-flow-tether-handle:hover {
|
|
251
|
+
r: 8;
|
|
252
|
+
stroke-width: 2.5;
|
|
253
|
+
}
|
|
254
|
+
.pict-flow-tether-handle-midpoint {
|
|
255
|
+
fill: #ffffff;
|
|
256
|
+
stroke: #e67e22;
|
|
257
|
+
stroke-width: 2;
|
|
258
|
+
cursor: grab;
|
|
259
|
+
transition: r 0.15s;
|
|
260
|
+
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
|
|
261
|
+
}
|
|
262
|
+
.pict-flow-tether-handle-midpoint:hover {
|
|
263
|
+
r: 8;
|
|
264
|
+
stroke-width: 2.5;
|
|
265
|
+
}
|
|
266
|
+
.pict-flow-node-panel-indicator {
|
|
267
|
+
fill: #3498db;
|
|
268
|
+
stroke: #2980b9;
|
|
269
|
+
stroke-width: 1;
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
}
|
|
272
|
+
.pict-flow-node-panel-indicator:hover {
|
|
273
|
+
fill: #2980b9;
|
|
274
|
+
}
|
|
275
|
+
.pict-flow-panel-foreign-object {
|
|
276
|
+
overflow: visible;
|
|
277
|
+
}
|
|
278
|
+
.pict-flow-panel {
|
|
279
|
+
background: #ffffff;
|
|
280
|
+
border: 1px solid #bdc3c7;
|
|
281
|
+
border-radius: 6px;
|
|
282
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
286
|
+
font-size: 13px;
|
|
287
|
+
overflow: hidden;
|
|
288
|
+
width: 100%;
|
|
289
|
+
height: 100%;
|
|
290
|
+
box-sizing: border-box;
|
|
291
|
+
}
|
|
292
|
+
.pict-flow-panel-titlebar {
|
|
293
|
+
display: flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
justify-content: space-between;
|
|
296
|
+
padding: 6px 10px;
|
|
297
|
+
background: #ecf0f1;
|
|
298
|
+
border-bottom: 1px solid #d5dbdb;
|
|
299
|
+
cursor: grab;
|
|
300
|
+
user-select: none;
|
|
301
|
+
-webkit-user-select: none;
|
|
302
|
+
flex-shrink: 0;
|
|
303
|
+
}
|
|
304
|
+
.pict-flow-panel-titlebar.dragging {
|
|
305
|
+
cursor: grabbing;
|
|
306
|
+
}
|
|
307
|
+
.pict-flow-panel-title-text {
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
font-size: 12px;
|
|
310
|
+
color: #2c3e50;
|
|
311
|
+
white-space: nowrap;
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
text-overflow: ellipsis;
|
|
314
|
+
}
|
|
315
|
+
.pict-flow-panel-close-btn {
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
color: #95a5a6;
|
|
318
|
+
font-size: 14px;
|
|
319
|
+
line-height: 1;
|
|
320
|
+
padding: 2px 4px;
|
|
321
|
+
border: none;
|
|
322
|
+
background: none;
|
|
323
|
+
}
|
|
324
|
+
.pict-flow-panel-close-btn:hover {
|
|
325
|
+
color: #e74c3c;
|
|
326
|
+
}
|
|
327
|
+
.pict-flow-panel-body {
|
|
328
|
+
flex: 1;
|
|
329
|
+
overflow: auto;
|
|
330
|
+
padding: 8px;
|
|
331
|
+
}
|
|
332
|
+
.pict-flow-fullscreen {
|
|
333
|
+
position: fixed;
|
|
334
|
+
top: 0;
|
|
335
|
+
left: 0;
|
|
336
|
+
width: 100vw;
|
|
337
|
+
height: 100vh;
|
|
338
|
+
z-index: 9999;
|
|
339
|
+
border-radius: 0;
|
|
340
|
+
border: none;
|
|
341
|
+
min-height: 100vh;
|
|
342
|
+
}
|
|
343
|
+
.pict-flow-fullscreen .pict-flow-svg {
|
|
344
|
+
min-height: calc(100vh - 50px);
|
|
345
|
+
}
|
|
346
|
+
.pict-flow-info-panel {
|
|
347
|
+
padding: 4px;
|
|
348
|
+
font-size: 12px;
|
|
349
|
+
line-height: 1.5;
|
|
350
|
+
color: #2c3e50;
|
|
351
|
+
}
|
|
352
|
+
.pict-flow-info-panel-header {
|
|
353
|
+
font-size: 14px;
|
|
354
|
+
font-weight: 600;
|
|
355
|
+
margin-bottom: 4px;
|
|
356
|
+
}
|
|
357
|
+
.pict-flow-info-panel-header.with-icon {
|
|
358
|
+
font-size: 16px;
|
|
359
|
+
}
|
|
360
|
+
.pict-flow-info-panel-description {
|
|
361
|
+
font-size: 11px;
|
|
362
|
+
color: #7f8c8d;
|
|
363
|
+
margin-bottom: 8px;
|
|
364
|
+
}
|
|
365
|
+
.pict-flow-info-panel-badges {
|
|
366
|
+
margin-bottom: 8px;
|
|
367
|
+
}
|
|
368
|
+
.pict-flow-info-panel-badge {
|
|
369
|
+
display: inline-block;
|
|
370
|
+
padding: 1px 6px;
|
|
371
|
+
border-radius: 3px;
|
|
372
|
+
font-size: 10px;
|
|
373
|
+
margin-right: 4px;
|
|
374
|
+
}
|
|
375
|
+
.pict-flow-info-panel-badge.category {
|
|
376
|
+
background: #ecf0f1;
|
|
377
|
+
color: #7f8c8d;
|
|
378
|
+
}
|
|
379
|
+
.pict-flow-info-panel-badge.code {
|
|
380
|
+
background: #eaf2f8;
|
|
381
|
+
color: #2980b9;
|
|
382
|
+
font-family: monospace;
|
|
383
|
+
}
|
|
384
|
+
.pict-flow-info-panel-section {
|
|
385
|
+
margin-bottom: 6px;
|
|
386
|
+
}
|
|
387
|
+
.pict-flow-info-panel-section-title {
|
|
388
|
+
font-size: 10px;
|
|
389
|
+
font-weight: 700;
|
|
390
|
+
text-transform: uppercase;
|
|
391
|
+
letter-spacing: 0.5px;
|
|
392
|
+
color: #95a5a6;
|
|
393
|
+
margin-bottom: 2px;
|
|
394
|
+
}
|
|
395
|
+
.pict-flow-info-panel-port {
|
|
396
|
+
padding: 2px 6px;
|
|
397
|
+
background: #f8f9fa;
|
|
398
|
+
margin-bottom: 2px;
|
|
399
|
+
font-size: 11px;
|
|
400
|
+
}
|
|
401
|
+
.pict-flow-info-panel-port.input {
|
|
402
|
+
border-left: 3px solid #3498db;
|
|
403
|
+
}
|
|
404
|
+
.pict-flow-info-panel-port.output {
|
|
405
|
+
border-left: 3px solid #2ecc71;
|
|
406
|
+
}
|
|
407
|
+
.pict-flow-info-panel-port-constraint {
|
|
408
|
+
color: #95a5a6;
|
|
409
|
+
font-size: 10px;
|
|
410
|
+
}
|
|
181
411
|
`,
|
|
182
412
|
|
|
183
413
|
Templates:
|
|
184
414
|
[
|
|
415
|
+
{
|
|
416
|
+
Hash: 'Flow-PanelChrome-Template',
|
|
417
|
+
Template: /*html*/`<div class="pict-flow-panel" xmlns="http://www.w3.org/1999/xhtml"><div class="pict-flow-panel-titlebar" data-element-type="panel-titlebar" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-title-text">{~D:Record.Title~}</span><span class="pict-flow-panel-close-btn" data-element-type="panel-close" data-panel-hash="{~D:Record.Hash~}">\u2715</span></div><div class="pict-flow-panel-body" data-panel-hash="{~D:Record.Hash~}"></div></div>`
|
|
418
|
+
},
|
|
185
419
|
{
|
|
186
420
|
Hash: 'Flow-Container-Template',
|
|
187
421
|
Template: /*html*/`
|
|
188
422
|
<div class="pict-flow-container" id="Flow-Wrapper-{~D:Record.ViewIdentifier~}">
|
|
189
423
|
<div id="Flow-Toolbar-{~D:Record.ViewIdentifier~}"></div>
|
|
190
|
-
<div id="Flow-SVG-Container-{~D:Record.ViewIdentifier~}">
|
|
424
|
+
<div class="pict-flow-svg-container" id="Flow-SVG-Container-{~D:Record.ViewIdentifier~}">
|
|
191
425
|
<svg class="pict-flow-svg"
|
|
192
426
|
id="Flow-SVG-{~D:Record.ViewIdentifier~}"
|
|
193
427
|
xmlns="http://www.w3.org/2000/svg">
|
|
@@ -204,6 +438,12 @@ const _DefaultConfiguration =
|
|
|
204
438
|
orient="auto" markerUnits="strokeWidth">
|
|
205
439
|
<polygon points="0 0, 5 3.5, 0 7" fill="#3498db" />
|
|
206
440
|
</marker>
|
|
441
|
+
<marker id="flow-tether-arrowhead-{~D:Record.ViewIdentifier~}"
|
|
442
|
+
markerWidth="4" markerHeight="6"
|
|
443
|
+
refX="6" refY="3"
|
|
444
|
+
orient="auto" markerUnits="strokeWidth">
|
|
445
|
+
<polygon points="0 0, 4 3, 0 6" fill="#95a5a6" />
|
|
446
|
+
</marker>
|
|
207
447
|
<pattern id="flow-grid-{~D:Record.ViewIdentifier~}"
|
|
208
448
|
width="20" height="20" patternUnits="userSpaceOnUse">
|
|
209
449
|
<line x1="20" y1="0" x2="20" y2="20" class="pict-flow-grid-pattern" />
|
|
@@ -216,6 +456,8 @@ const _DefaultConfiguration =
|
|
|
216
456
|
<g class="pict-flow-viewport" id="Flow-Viewport-{~D:Record.ViewIdentifier~}">
|
|
217
457
|
<g class="pict-flow-connections-layer" id="Flow-Connections-{~D:Record.ViewIdentifier~}"></g>
|
|
218
458
|
<g class="pict-flow-nodes-layer" id="Flow-Nodes-{~D:Record.ViewIdentifier~}"></g>
|
|
459
|
+
<g class="pict-flow-tethers-layer" id="Flow-Tethers-{~D:Record.ViewIdentifier~}"></g>
|
|
460
|
+
<g class="pict-flow-panels-layer" id="Flow-Panels-{~D:Record.ViewIdentifier~}"></g>
|
|
219
461
|
</g>
|
|
220
462
|
</svg>
|
|
221
463
|
</div>
|
|
@@ -253,10 +495,30 @@ class PictViewFlow extends libPictView
|
|
|
253
495
|
{
|
|
254
496
|
this.fable.addServiceType('PictServiceFlowConnectionRenderer', libPictServiceFlowConnectionRenderer);
|
|
255
497
|
}
|
|
498
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowTether'))
|
|
499
|
+
{
|
|
500
|
+
this.fable.addServiceType('PictServiceFlowTether', libPictServiceFlowTether);
|
|
501
|
+
}
|
|
256
502
|
if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowLayout'))
|
|
257
503
|
{
|
|
258
504
|
this.fable.addServiceType('PictServiceFlowLayout', libPictServiceFlowLayout);
|
|
259
505
|
}
|
|
506
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowPathGenerator'))
|
|
507
|
+
{
|
|
508
|
+
this.fable.addServiceType('PictServiceFlowPathGenerator', libPictServiceFlowPathGenerator);
|
|
509
|
+
}
|
|
510
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowSVGHelpers'))
|
|
511
|
+
{
|
|
512
|
+
this.fable.addServiceType('PictProviderFlowSVGHelpers', libPictProviderFlowSVGHelpers);
|
|
513
|
+
}
|
|
514
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowGeometry'))
|
|
515
|
+
{
|
|
516
|
+
this.fable.addServiceType('PictProviderFlowGeometry', libPictProviderFlowGeometry);
|
|
517
|
+
}
|
|
518
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowPanelChrome'))
|
|
519
|
+
{
|
|
520
|
+
this.fable.addServiceType('PictProviderFlowPanelChrome', libPictProviderFlowPanelChrome);
|
|
521
|
+
}
|
|
260
522
|
if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowNodeTypes'))
|
|
261
523
|
{
|
|
262
524
|
this.fable.addServiceType('PictProviderFlowNodeTypes', libPictProviderFlowNodeTypes);
|
|
@@ -265,6 +527,10 @@ class PictViewFlow extends libPictView
|
|
|
265
527
|
{
|
|
266
528
|
this.fable.addServiceType('PictProviderFlowEventHandler', libPictProviderFlowEventHandler);
|
|
267
529
|
}
|
|
530
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowLayouts'))
|
|
531
|
+
{
|
|
532
|
+
this.fable.addServiceType('PictProviderFlowLayouts', libPictProviderFlowLayouts);
|
|
533
|
+
}
|
|
268
534
|
if (!this.fable.servicesMap.hasOwnProperty('PictViewFlowNode'))
|
|
269
535
|
{
|
|
270
536
|
this.fable.addServiceType('PictViewFlowNode', libPictViewFlowNode);
|
|
@@ -273,17 +539,44 @@ class PictViewFlow extends libPictView
|
|
|
273
539
|
{
|
|
274
540
|
this.fable.addServiceType('PictViewFlowToolbar', libPictViewFlowToolbar);
|
|
275
541
|
}
|
|
542
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictViewFlowPropertiesPanel'))
|
|
543
|
+
{
|
|
544
|
+
this.fable.addServiceType('PictViewFlowPropertiesPanel', libPictViewFlowPropertiesPanel);
|
|
545
|
+
}
|
|
546
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel'))
|
|
547
|
+
{
|
|
548
|
+
this.fable.addServiceType('PictFlowCardPropertiesPanel', libPictFlowCardPropertiesPanel);
|
|
549
|
+
}
|
|
550
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-Template'))
|
|
551
|
+
{
|
|
552
|
+
this.fable.addServiceType('PictFlowCardPropertiesPanel-Template', libPanelTemplate);
|
|
553
|
+
}
|
|
554
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-Markdown'))
|
|
555
|
+
{
|
|
556
|
+
this.fable.addServiceType('PictFlowCardPropertiesPanel-Markdown', libPanelMarkdown);
|
|
557
|
+
}
|
|
558
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-Form'))
|
|
559
|
+
{
|
|
560
|
+
this.fable.addServiceType('PictFlowCardPropertiesPanel-Form', libPanelForm);
|
|
561
|
+
}
|
|
562
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-View'))
|
|
563
|
+
{
|
|
564
|
+
this.fable.addServiceType('PictFlowCardPropertiesPanel-View', libPanelView);
|
|
565
|
+
}
|
|
276
566
|
|
|
277
567
|
// Internal state
|
|
278
568
|
this._FlowData = {
|
|
279
569
|
Nodes: [],
|
|
280
570
|
Connections: [],
|
|
571
|
+
OpenPanels: [],
|
|
572
|
+
SavedLayouts: [],
|
|
281
573
|
ViewState: {
|
|
282
574
|
PanX: 0,
|
|
283
575
|
PanY: 0,
|
|
284
576
|
Zoom: 1,
|
|
285
577
|
SelectedNodeHash: null,
|
|
286
|
-
SelectedConnectionHash: null
|
|
578
|
+
SelectedConnectionHash: null,
|
|
579
|
+
SelectedTetherHash: null
|
|
287
580
|
}
|
|
288
581
|
};
|
|
289
582
|
|
|
@@ -291,14 +584,25 @@ class PictViewFlow extends libPictView
|
|
|
291
584
|
this._ViewportElement = null;
|
|
292
585
|
this._NodesLayer = null;
|
|
293
586
|
this._ConnectionsLayer = null;
|
|
587
|
+
this._TethersLayer = null;
|
|
588
|
+
this._PanelsLayer = null;
|
|
294
589
|
|
|
295
590
|
this._InteractionManager = null;
|
|
296
591
|
this._ConnectionRenderer = null;
|
|
592
|
+
this._TetherService = null;
|
|
297
593
|
this._LayoutService = null;
|
|
594
|
+
this._PathGenerator = null;
|
|
595
|
+
this._SVGHelperProvider = null;
|
|
596
|
+
this._GeometryProvider = null;
|
|
597
|
+
this._PanelChromeProvider = null;
|
|
298
598
|
this._NodeTypeProvider = null;
|
|
599
|
+
this._LayoutProvider = null;
|
|
299
600
|
this._EventHandlerProvider = null;
|
|
300
601
|
this._NodeView = null;
|
|
301
602
|
this._ToolbarView = null;
|
|
603
|
+
this._PropertiesPanelView = null;
|
|
604
|
+
|
|
605
|
+
this._IsFullscreen = false;
|
|
302
606
|
|
|
303
607
|
this.initialRenderComplete = false;
|
|
304
608
|
}
|
|
@@ -336,14 +640,22 @@ class PictViewFlow extends libPictView
|
|
|
336
640
|
{
|
|
337
641
|
super.onBeforeInitialize();
|
|
338
642
|
|
|
339
|
-
//
|
|
643
|
+
// Instantiate shared utility providers first (used by services below)
|
|
644
|
+
this._SVGHelperProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowSVGHelpers');
|
|
645
|
+
this._GeometryProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowGeometry');
|
|
646
|
+
this._PathGenerator = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowPathGenerator', { FlowView: this });
|
|
647
|
+
this._PanelChromeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowPanelChrome', { FlowView: this });
|
|
648
|
+
|
|
649
|
+
// Instantiate services
|
|
340
650
|
this._InteractionManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowInteractionManager', { FlowView: this });
|
|
341
651
|
this._ConnectionRenderer = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowConnectionRenderer', { FlowView: this });
|
|
652
|
+
this._TetherService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowTether', { FlowView: this });
|
|
342
653
|
this._LayoutService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowLayout', { FlowView: this });
|
|
343
654
|
|
|
344
|
-
//
|
|
345
|
-
this._NodeTypeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNodeTypes', { FlowView: this });
|
|
655
|
+
// Instantiate providers, passing any additional node types from view options
|
|
656
|
+
this._NodeTypeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNodeTypes', { FlowView: this, AdditionalNodeTypes: this.options.NodeTypes });
|
|
346
657
|
this._EventHandlerProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowEventHandler', { FlowView: this });
|
|
658
|
+
this._LayoutProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowLayouts', { FlowView: this });
|
|
347
659
|
|
|
348
660
|
return super.onBeforeInitialize();
|
|
349
661
|
}
|
|
@@ -389,6 +701,36 @@ class PictViewFlow extends libPictView
|
|
|
389
701
|
this._ConnectionsLayer = tmpConnectionsElements[0];
|
|
390
702
|
}
|
|
391
703
|
|
|
704
|
+
let tmpTethersElements = this.pict.ContentAssignment.getElement(`#Flow-Tethers-${tmpViewIdentifier}`);
|
|
705
|
+
if (tmpTethersElements.length > 0)
|
|
706
|
+
{
|
|
707
|
+
this._TethersLayer = tmpTethersElements[0];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
let tmpPanelsElements = this.pict.ContentAssignment.getElement(`#Flow-Panels-${tmpViewIdentifier}`);
|
|
711
|
+
if (tmpPanelsElements.length > 0)
|
|
712
|
+
{
|
|
713
|
+
this._PanelsLayer = tmpPanelsElements[0];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Initialize shared utility providers (used by services below)
|
|
717
|
+
if (!this._SVGHelperProvider)
|
|
718
|
+
{
|
|
719
|
+
this._SVGHelperProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowSVGHelpers');
|
|
720
|
+
}
|
|
721
|
+
if (!this._GeometryProvider)
|
|
722
|
+
{
|
|
723
|
+
this._GeometryProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowGeometry');
|
|
724
|
+
}
|
|
725
|
+
if (!this._PathGenerator)
|
|
726
|
+
{
|
|
727
|
+
this._PathGenerator = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowPathGenerator', { FlowView: this });
|
|
728
|
+
}
|
|
729
|
+
if (!this._PanelChromeProvider)
|
|
730
|
+
{
|
|
731
|
+
this._PanelChromeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowPanelChrome', { FlowView: this });
|
|
732
|
+
}
|
|
733
|
+
|
|
392
734
|
// Initialize services with references
|
|
393
735
|
if (!this._InteractionManager)
|
|
394
736
|
{
|
|
@@ -398,18 +740,26 @@ class PictViewFlow extends libPictView
|
|
|
398
740
|
{
|
|
399
741
|
this._ConnectionRenderer = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowConnectionRenderer', { FlowView: this });
|
|
400
742
|
}
|
|
743
|
+
if (!this._TetherService)
|
|
744
|
+
{
|
|
745
|
+
this._TetherService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowTether', { FlowView: this });
|
|
746
|
+
}
|
|
401
747
|
if (!this._LayoutService)
|
|
402
748
|
{
|
|
403
749
|
this._LayoutService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowLayout', { FlowView: this });
|
|
404
750
|
}
|
|
405
751
|
if (!this._NodeTypeProvider)
|
|
406
752
|
{
|
|
407
|
-
this._NodeTypeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNodeTypes', { FlowView: this });
|
|
753
|
+
this._NodeTypeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNodeTypes', { FlowView: this, AdditionalNodeTypes: this.options.NodeTypes });
|
|
408
754
|
}
|
|
409
755
|
if (!this._EventHandlerProvider)
|
|
410
756
|
{
|
|
411
757
|
this._EventHandlerProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowEventHandler', { FlowView: this });
|
|
412
758
|
}
|
|
759
|
+
if (!this._LayoutProvider)
|
|
760
|
+
{
|
|
761
|
+
this._LayoutProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowLayouts', { FlowView: this });
|
|
762
|
+
}
|
|
413
763
|
|
|
414
764
|
// Setup the toolbar if enabled
|
|
415
765
|
if (this.options.EnableToolbar)
|
|
@@ -442,6 +792,17 @@ class PictViewFlow extends libPictView
|
|
|
442
792
|
));
|
|
443
793
|
this._NodeView._FlowView = this;
|
|
444
794
|
|
|
795
|
+
// Setup the properties panel renderer
|
|
796
|
+
this._PropertiesPanelView = this.fable.instantiateServiceProviderWithoutRegistration('PictViewFlowPropertiesPanel',
|
|
797
|
+
Object.assign({},
|
|
798
|
+
libPictViewFlowPropertiesPanel.default_configuration,
|
|
799
|
+
{
|
|
800
|
+
ViewIdentifier: `Flow-PropertiesPanel-${tmpViewIdentifier}`,
|
|
801
|
+
AutoRender: false
|
|
802
|
+
}
|
|
803
|
+
));
|
|
804
|
+
this._PropertiesPanelView._FlowView = this;
|
|
805
|
+
|
|
445
806
|
// Bind interaction events
|
|
446
807
|
this._InteractionManager.initialize(this._SVGElement, this._ViewportElement);
|
|
447
808
|
|
|
@@ -521,8 +882,10 @@ class PictViewFlow extends libPictView
|
|
|
521
882
|
this._FlowData = {
|
|
522
883
|
Nodes: Array.isArray(pFlowData.Nodes) ? pFlowData.Nodes : [],
|
|
523
884
|
Connections: Array.isArray(pFlowData.Connections) ? pFlowData.Connections : [],
|
|
885
|
+
OpenPanels: Array.isArray(pFlowData.OpenPanels) ? pFlowData.OpenPanels : [],
|
|
886
|
+
SavedLayouts: Array.isArray(pFlowData.SavedLayouts) ? pFlowData.SavedLayouts : [],
|
|
524
887
|
ViewState: Object.assign(
|
|
525
|
-
{ PanX: 0, PanY: 0, Zoom: 1, SelectedNodeHash: null, SelectedConnectionHash: null },
|
|
888
|
+
{ PanX: 0, PanY: 0, Zoom: 1, SelectedNodeHash: null, SelectedConnectionHash: null, SelectedTetherHash: null },
|
|
526
889
|
pFlowData.ViewState || {}
|
|
527
890
|
)
|
|
528
891
|
};
|
|
@@ -610,6 +973,9 @@ class PictViewFlow extends libPictView
|
|
|
610
973
|
return pConnection.SourceNodeHash !== pNodeHash && pConnection.TargetNodeHash !== pNodeHash;
|
|
611
974
|
});
|
|
612
975
|
|
|
976
|
+
// Close any open panels for this node
|
|
977
|
+
this.closePanelForNode(pNodeHash);
|
|
978
|
+
|
|
613
979
|
// Clear selection if this node was selected
|
|
614
980
|
if (this._FlowData.ViewState.SelectedNodeHash === pNodeHash)
|
|
615
981
|
{
|
|
@@ -744,6 +1110,7 @@ class PictViewFlow extends libPictView
|
|
|
744
1110
|
let tmpPreviousSelection = this._FlowData.ViewState.SelectedNodeHash;
|
|
745
1111
|
this._FlowData.ViewState.SelectedNodeHash = pNodeHash;
|
|
746
1112
|
this._FlowData.ViewState.SelectedConnectionHash = null;
|
|
1113
|
+
this._FlowData.ViewState.SelectedTetherHash = null;
|
|
747
1114
|
|
|
748
1115
|
this.renderFlow();
|
|
749
1116
|
|
|
@@ -763,6 +1130,7 @@ class PictViewFlow extends libPictView
|
|
|
763
1130
|
let tmpPreviousSelection = this._FlowData.ViewState.SelectedConnectionHash;
|
|
764
1131
|
this._FlowData.ViewState.SelectedConnectionHash = pConnectionHash;
|
|
765
1132
|
this._FlowData.ViewState.SelectedNodeHash = null;
|
|
1133
|
+
this._FlowData.ViewState.SelectedTetherHash = null;
|
|
766
1134
|
|
|
767
1135
|
this.renderFlow();
|
|
768
1136
|
|
|
@@ -780,6 +1148,7 @@ class PictViewFlow extends libPictView
|
|
|
780
1148
|
{
|
|
781
1149
|
this._FlowData.ViewState.SelectedNodeHash = null;
|
|
782
1150
|
this._FlowData.ViewState.SelectedConnectionHash = null;
|
|
1151
|
+
this._FlowData.ViewState.SelectedTetherHash = null;
|
|
783
1152
|
this.renderFlow();
|
|
784
1153
|
}
|
|
785
1154
|
|
|
@@ -893,6 +1262,50 @@ class PictViewFlow extends libPictView
|
|
|
893
1262
|
}
|
|
894
1263
|
}
|
|
895
1264
|
|
|
1265
|
+
/**
|
|
1266
|
+
* Toggle fullscreen mode on the flow editor container.
|
|
1267
|
+
* Uses a CSS fixed-position overlay instead of the Fullscreen API.
|
|
1268
|
+
* @returns {boolean} The new fullscreen state
|
|
1269
|
+
*/
|
|
1270
|
+
toggleFullscreen()
|
|
1271
|
+
{
|
|
1272
|
+
let tmpViewIdentifier = this.options.ViewIdentifier;
|
|
1273
|
+
let tmpContainerElements = this.pict.ContentAssignment.getElement(`#Flow-Wrapper-${tmpViewIdentifier}`);
|
|
1274
|
+
if (tmpContainerElements.length < 1) return this._IsFullscreen;
|
|
1275
|
+
|
|
1276
|
+
let tmpContainer = tmpContainerElements[0];
|
|
1277
|
+
|
|
1278
|
+
this._IsFullscreen = !this._IsFullscreen;
|
|
1279
|
+
|
|
1280
|
+
if (this._IsFullscreen)
|
|
1281
|
+
{
|
|
1282
|
+
tmpContainer.classList.add('pict-flow-fullscreen');
|
|
1283
|
+
}
|
|
1284
|
+
else
|
|
1285
|
+
{
|
|
1286
|
+
tmpContainer.classList.remove('pict-flow-fullscreen');
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
return this._IsFullscreen;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Exit fullscreen mode if currently active.
|
|
1294
|
+
*/
|
|
1295
|
+
exitFullscreen()
|
|
1296
|
+
{
|
|
1297
|
+
if (!this._IsFullscreen) return;
|
|
1298
|
+
|
|
1299
|
+
let tmpViewIdentifier = this.options.ViewIdentifier;
|
|
1300
|
+
let tmpContainerElements = this.pict.ContentAssignment.getElement(`#Flow-Wrapper-${tmpViewIdentifier}`);
|
|
1301
|
+
if (tmpContainerElements.length > 0)
|
|
1302
|
+
{
|
|
1303
|
+
tmpContainerElements[0].classList.remove('pict-flow-fullscreen');
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
this._IsFullscreen = false;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
896
1309
|
/**
|
|
897
1310
|
* Get a node by hash
|
|
898
1311
|
* @param {string} pNodeHash
|
|
@@ -913,6 +1326,207 @@ class PictViewFlow extends libPictView
|
|
|
913
1326
|
return this._FlowData.Connections.find((pConn) => pConn.Hash === pConnectionHash) || null;
|
|
914
1327
|
}
|
|
915
1328
|
|
|
1329
|
+
/**
|
|
1330
|
+
* Select a tether by its panel hash.
|
|
1331
|
+
* @param {string|null} pPanelHash - Hash of the panel whose tether to select, or null to deselect
|
|
1332
|
+
*/
|
|
1333
|
+
selectTether(pPanelHash)
|
|
1334
|
+
{
|
|
1335
|
+
let tmpPreviousSelection = this._FlowData.ViewState.SelectedTetherHash;
|
|
1336
|
+
this._FlowData.ViewState.SelectedTetherHash = pPanelHash;
|
|
1337
|
+
this._FlowData.ViewState.SelectedNodeHash = null;
|
|
1338
|
+
this._FlowData.ViewState.SelectedConnectionHash = null;
|
|
1339
|
+
|
|
1340
|
+
this.renderFlow();
|
|
1341
|
+
|
|
1342
|
+
if (this._EventHandlerProvider && pPanelHash !== tmpPreviousSelection)
|
|
1343
|
+
{
|
|
1344
|
+
let tmpPanel = pPanelHash ? this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash) : null;
|
|
1345
|
+
this._EventHandlerProvider.fireEvent('onTetherSelected', tmpPanel);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
/**
|
|
1350
|
+
* Update a connection handle position during drag (for real-time feedback).
|
|
1351
|
+
* @param {string} pConnectionHash
|
|
1352
|
+
* @param {string} pHandleType - 'bezier-midpoint', 'ortho-corner1', 'ortho-corner2', 'ortho-midpoint'
|
|
1353
|
+
* @param {number} pX
|
|
1354
|
+
* @param {number} pY
|
|
1355
|
+
*/
|
|
1356
|
+
updateConnectionHandle(pConnectionHash, pHandleType, pX, pY)
|
|
1357
|
+
{
|
|
1358
|
+
let tmpConnection = this.getConnection(pConnectionHash);
|
|
1359
|
+
if (!tmpConnection) return;
|
|
1360
|
+
|
|
1361
|
+
if (!tmpConnection.Data) tmpConnection.Data = {};
|
|
1362
|
+
tmpConnection.Data.HandleCustomized = true;
|
|
1363
|
+
|
|
1364
|
+
switch (pHandleType)
|
|
1365
|
+
{
|
|
1366
|
+
case 'bezier-midpoint':
|
|
1367
|
+
tmpConnection.Data.BezierHandleX = pX;
|
|
1368
|
+
tmpConnection.Data.BezierHandleY = pY;
|
|
1369
|
+
break;
|
|
1370
|
+
|
|
1371
|
+
case 'ortho-corner1':
|
|
1372
|
+
tmpConnection.Data.OrthoCorner1X = pX;
|
|
1373
|
+
tmpConnection.Data.OrthoCorner1Y = pY;
|
|
1374
|
+
break;
|
|
1375
|
+
|
|
1376
|
+
case 'ortho-corner2':
|
|
1377
|
+
tmpConnection.Data.OrthoCorner2X = pX;
|
|
1378
|
+
tmpConnection.Data.OrthoCorner2Y = pY;
|
|
1379
|
+
break;
|
|
1380
|
+
|
|
1381
|
+
case 'ortho-midpoint':
|
|
1382
|
+
{
|
|
1383
|
+
// Midpoint drag shifts the corridor offset
|
|
1384
|
+
let tmpSourcePos = this.getPortPosition(tmpConnection.SourceNodeHash, tmpConnection.SourcePortHash);
|
|
1385
|
+
let tmpTargetPos = this.getPortPosition(tmpConnection.TargetNodeHash, tmpConnection.TargetPortHash);
|
|
1386
|
+
if (tmpSourcePos && tmpTargetPos)
|
|
1387
|
+
{
|
|
1388
|
+
let tmpGeom = this._ConnectionRenderer._computeDirectionalGeometry(tmpSourcePos, tmpTargetPos);
|
|
1389
|
+
let tmpStartDir = tmpGeom.startDir;
|
|
1390
|
+
|
|
1391
|
+
// Compute offset along the corridor axis
|
|
1392
|
+
if (Math.abs(tmpStartDir.dx) > Math.abs(tmpStartDir.dy))
|
|
1393
|
+
{
|
|
1394
|
+
// Horizontal departure — corridor is vertical, shift is along X
|
|
1395
|
+
let tmpAutoMidX = (tmpGeom.departX + tmpGeom.approachX) / 2;
|
|
1396
|
+
tmpConnection.Data.OrthoMidOffset = pX - tmpAutoMidX;
|
|
1397
|
+
}
|
|
1398
|
+
else
|
|
1399
|
+
{
|
|
1400
|
+
// Vertical departure — corridor is horizontal, shift is along Y
|
|
1401
|
+
let tmpAutoMidY = (tmpGeom.departY + tmpGeom.approachY) / 2;
|
|
1402
|
+
tmpConnection.Data.OrthoMidOffset = pY - tmpAutoMidY;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
break;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
this._renderSingleConnection(pConnectionHash);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Update a tether handle position during drag (for real-time feedback).
|
|
1414
|
+
* Delegates state update to the TetherService.
|
|
1415
|
+
* @param {string} pPanelHash
|
|
1416
|
+
* @param {string} pHandleType - 'bezier-midpoint', 'ortho-corner1', 'ortho-corner2', 'ortho-midpoint'
|
|
1417
|
+
* @param {number} pX
|
|
1418
|
+
* @param {number} pY
|
|
1419
|
+
*/
|
|
1420
|
+
updateTetherHandle(pPanelHash, pHandleType, pX, pY)
|
|
1421
|
+
{
|
|
1422
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
1423
|
+
if (!tmpPanel) return;
|
|
1424
|
+
|
|
1425
|
+
if (this._TetherService)
|
|
1426
|
+
{
|
|
1427
|
+
this._TetherService.updateHandlePosition(tmpPanel, pHandleType, pX, pY);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
this._renderSingleTether(pPanelHash);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Re-render a single connection (remove and re-add) for smooth drag performance.
|
|
1435
|
+
* @param {string} pConnectionHash
|
|
1436
|
+
*/
|
|
1437
|
+
_renderSingleConnection(pConnectionHash)
|
|
1438
|
+
{
|
|
1439
|
+
if (!this._ConnectionsLayer) return;
|
|
1440
|
+
|
|
1441
|
+
// Remove existing elements for this connection
|
|
1442
|
+
let tmpExisting = this._ConnectionsLayer.querySelectorAll(`[data-connection-hash="${pConnectionHash}"]`);
|
|
1443
|
+
for (let i = 0; i < tmpExisting.length; i++)
|
|
1444
|
+
{
|
|
1445
|
+
tmpExisting[i].remove();
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
let tmpConnection = this.getConnection(pConnectionHash);
|
|
1449
|
+
if (!tmpConnection) return;
|
|
1450
|
+
|
|
1451
|
+
let tmpIsSelected = (this._FlowData.ViewState.SelectedConnectionHash === pConnectionHash);
|
|
1452
|
+
this._ConnectionRenderer.renderConnection(tmpConnection, this._ConnectionsLayer, tmpIsSelected);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* Re-render a single tether (remove and re-add) for smooth drag performance.
|
|
1457
|
+
* @param {string} pPanelHash
|
|
1458
|
+
*/
|
|
1459
|
+
_renderSingleTether(pPanelHash)
|
|
1460
|
+
{
|
|
1461
|
+
if (!this._TethersLayer || !this._TetherService) return;
|
|
1462
|
+
|
|
1463
|
+
// Remove existing tether elements for this panel
|
|
1464
|
+
let tmpExisting = this._TethersLayer.querySelectorAll(`[data-panel-hash="${pPanelHash}"]`);
|
|
1465
|
+
for (let i = 0; i < tmpExisting.length; i++)
|
|
1466
|
+
{
|
|
1467
|
+
tmpExisting[i].remove();
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
1471
|
+
if (!tmpPanel) return;
|
|
1472
|
+
|
|
1473
|
+
let tmpNodeData = this.getNode(tmpPanel.NodeHash);
|
|
1474
|
+
if (!tmpNodeData) return;
|
|
1475
|
+
|
|
1476
|
+
let tmpIsSelected = (this._FlowData.ViewState.SelectedTetherHash === pPanelHash);
|
|
1477
|
+
this._TetherService.renderTether(tmpPanel, tmpNodeData, this._TethersLayer, tmpIsSelected, this.options.ViewIdentifier);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
/**
|
|
1481
|
+
* Reset handle positions for all connections/tethers involving a node.
|
|
1482
|
+
* Called when a node moves. Preserves LineMode but resets handle coordinates to auto.
|
|
1483
|
+
* @param {string} pNodeHash
|
|
1484
|
+
*/
|
|
1485
|
+
_resetHandlesForNode(pNodeHash)
|
|
1486
|
+
{
|
|
1487
|
+
// Reset connection handles
|
|
1488
|
+
for (let i = 0; i < this._FlowData.Connections.length; i++)
|
|
1489
|
+
{
|
|
1490
|
+
let tmpConn = this._FlowData.Connections[i];
|
|
1491
|
+
if (tmpConn.SourceNodeHash === pNodeHash || tmpConn.TargetNodeHash === pNodeHash)
|
|
1492
|
+
{
|
|
1493
|
+
if (tmpConn.Data && tmpConn.Data.HandleCustomized)
|
|
1494
|
+
{
|
|
1495
|
+
tmpConn.Data.HandleCustomized = false;
|
|
1496
|
+
tmpConn.Data.BezierHandleX = null;
|
|
1497
|
+
tmpConn.Data.BezierHandleY = null;
|
|
1498
|
+
tmpConn.Data.OrthoCorner1X = null;
|
|
1499
|
+
tmpConn.Data.OrthoCorner1Y = null;
|
|
1500
|
+
tmpConn.Data.OrthoCorner2X = null;
|
|
1501
|
+
tmpConn.Data.OrthoCorner2Y = null;
|
|
1502
|
+
tmpConn.Data.OrthoMidOffset = 0;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// Reset tether handles for panels attached to this node
|
|
1508
|
+
if (this._TetherService)
|
|
1509
|
+
{
|
|
1510
|
+
this._TetherService.resetHandlesForNode(this._FlowData.OpenPanels, pNodeHash);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Reset tether handle positions for a specific panel.
|
|
1516
|
+
* Called when a panel is dragged.
|
|
1517
|
+
* @param {string} pPanelHash
|
|
1518
|
+
*/
|
|
1519
|
+
_resetHandlesForPanel(pPanelHash)
|
|
1520
|
+
{
|
|
1521
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
1522
|
+
if (!tmpPanel) return;
|
|
1523
|
+
|
|
1524
|
+
if (this._TetherService)
|
|
1525
|
+
{
|
|
1526
|
+
this._TetherService.resetHandlePositions(tmpPanel);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
916
1530
|
/**
|
|
917
1531
|
* Get a port's absolute position in SVG coordinates.
|
|
918
1532
|
*
|
|
@@ -1047,6 +1661,12 @@ class PictViewFlow extends libPictView
|
|
|
1047
1661
|
this._NodeView.renderNode(tmpNode, this._NodesLayer, tmpIsSelected, tmpNodeTypeConfig);
|
|
1048
1662
|
}
|
|
1049
1663
|
|
|
1664
|
+
// Render properties panels and tethers
|
|
1665
|
+
if (this._PropertiesPanelView && this._PanelsLayer && this._TethersLayer)
|
|
1666
|
+
{
|
|
1667
|
+
this._PropertiesPanelView.renderPanels(this._FlowData.OpenPanels, this._PanelsLayer, this._TethersLayer, this._FlowData.ViewState.SelectedTetherHash);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1050
1670
|
// Update viewport transform
|
|
1051
1671
|
this.updateViewportTransform();
|
|
1052
1672
|
}
|
|
@@ -1071,6 +1691,9 @@ class PictViewFlow extends libPictView
|
|
|
1071
1691
|
tmpNode.X = pX;
|
|
1072
1692
|
tmpNode.Y = pY;
|
|
1073
1693
|
|
|
1694
|
+
// Reset customized handle positions for connections/tethers involving this node
|
|
1695
|
+
this._resetHandlesForNode(pNodeHash);
|
|
1696
|
+
|
|
1074
1697
|
// Update the node's SVG group transform for smooth dragging
|
|
1075
1698
|
let tmpNodeGroup = this._NodesLayer.querySelector(`[data-node-hash="${pNodeHash}"]`);
|
|
1076
1699
|
if (tmpNodeGroup)
|
|
@@ -1080,6 +1703,9 @@ class PictViewFlow extends libPictView
|
|
|
1080
1703
|
|
|
1081
1704
|
// Re-render connections that involve this node
|
|
1082
1705
|
this._renderConnectionsForNode(pNodeHash);
|
|
1706
|
+
|
|
1707
|
+
// Update tethers for any panels attached to this node
|
|
1708
|
+
this._renderTethersForNode(pNodeHash);
|
|
1083
1709
|
}
|
|
1084
1710
|
|
|
1085
1711
|
/**
|
|
@@ -1109,6 +1735,202 @@ class PictViewFlow extends libPictView
|
|
|
1109
1735
|
this._ConnectionRenderer.renderConnection(tmpConn, this._ConnectionsLayer, tmpIsSelected);
|
|
1110
1736
|
}
|
|
1111
1737
|
}
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* Re-render tethers for panels attached to a specific node (for drag performance).
|
|
1741
|
+
* @param {string} pNodeHash
|
|
1742
|
+
*/
|
|
1743
|
+
_renderTethersForNode(pNodeHash)
|
|
1744
|
+
{
|
|
1745
|
+
if (!this._TethersLayer || !this._TetherService) return;
|
|
1746
|
+
|
|
1747
|
+
let tmpAffectedPanels = this._FlowData.OpenPanels.filter((pPanel) => pPanel.NodeHash === pNodeHash);
|
|
1748
|
+
if (tmpAffectedPanels.length === 0) return;
|
|
1749
|
+
|
|
1750
|
+
// Remove existing tethers for these panels and re-render via TetherService
|
|
1751
|
+
for (let i = 0; i < tmpAffectedPanels.length; i++)
|
|
1752
|
+
{
|
|
1753
|
+
let tmpExisting = this._TethersLayer.querySelectorAll(`[data-panel-hash="${tmpAffectedPanels[i].Hash}"]`);
|
|
1754
|
+
for (let j = 0; j < tmpExisting.length; j++)
|
|
1755
|
+
{
|
|
1756
|
+
tmpExisting[j].remove();
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
let tmpNodeData = this.getNode(tmpAffectedPanels[i].NodeHash);
|
|
1760
|
+
if (!tmpNodeData) continue;
|
|
1761
|
+
|
|
1762
|
+
let tmpIsSelected = (this._FlowData.ViewState.SelectedTetherHash === tmpAffectedPanels[i].Hash);
|
|
1763
|
+
this._TetherService.renderTether(tmpAffectedPanels[i], tmpNodeData, this._TethersLayer, tmpIsSelected, this.options.ViewIdentifier);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// ---- Properties Panel Management ----
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Open a properties panel for a node.
|
|
1771
|
+
* @param {string} pNodeHash - The hash of the node to open a panel for
|
|
1772
|
+
* @returns {Object|false} The panel data, or false if the node has no PropertiesPanel config
|
|
1773
|
+
*/
|
|
1774
|
+
openPanel(pNodeHash)
|
|
1775
|
+
{
|
|
1776
|
+
let tmpNode = this.getNode(pNodeHash);
|
|
1777
|
+
if (!tmpNode) return false;
|
|
1778
|
+
|
|
1779
|
+
let tmpNodeTypeConfig = this._NodeTypeProvider.getNodeType(tmpNode.Type);
|
|
1780
|
+
if (!tmpNodeTypeConfig) return false;
|
|
1781
|
+
|
|
1782
|
+
// Check if a panel is already open for this node
|
|
1783
|
+
let tmpExisting = this._FlowData.OpenPanels.find((pPanel) => pPanel.NodeHash === pNodeHash);
|
|
1784
|
+
if (tmpExisting) return tmpExisting;
|
|
1785
|
+
|
|
1786
|
+
let tmpPanelConfig = tmpNodeTypeConfig.PropertiesPanel;
|
|
1787
|
+
let tmpPanelHash = `panel-${this.fable.getUUID()}`;
|
|
1788
|
+
let tmpWidth, tmpHeight, tmpPanelType, tmpTitle;
|
|
1789
|
+
|
|
1790
|
+
if (tmpPanelConfig)
|
|
1791
|
+
{
|
|
1792
|
+
tmpWidth = tmpPanelConfig.DefaultWidth || 300;
|
|
1793
|
+
tmpHeight = tmpPanelConfig.DefaultHeight || 200;
|
|
1794
|
+
tmpPanelType = tmpPanelConfig.PanelType || 'Base';
|
|
1795
|
+
tmpTitle = tmpPanelConfig.Title || tmpNodeTypeConfig.Label || 'Properties';
|
|
1796
|
+
}
|
|
1797
|
+
else
|
|
1798
|
+
{
|
|
1799
|
+
// No PropertiesPanel configured — open an auto-generated info panel
|
|
1800
|
+
tmpWidth = 240;
|
|
1801
|
+
tmpHeight = 180;
|
|
1802
|
+
tmpPanelType = 'Info';
|
|
1803
|
+
tmpTitle = tmpNodeTypeConfig.Label || tmpNode.Title || 'Node Info';
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
let tmpPanelData =
|
|
1807
|
+
{
|
|
1808
|
+
Hash: tmpPanelHash,
|
|
1809
|
+
NodeHash: pNodeHash,
|
|
1810
|
+
PanelType: tmpPanelType,
|
|
1811
|
+
Title: tmpTitle,
|
|
1812
|
+
X: tmpNode.X + tmpNode.Width + 30,
|
|
1813
|
+
Y: tmpNode.Y,
|
|
1814
|
+
Width: tmpWidth,
|
|
1815
|
+
Height: tmpHeight
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
this._FlowData.OpenPanels.push(tmpPanelData);
|
|
1819
|
+
this.renderFlow();
|
|
1820
|
+
this.marshalFromView();
|
|
1821
|
+
|
|
1822
|
+
if (this._EventHandlerProvider)
|
|
1823
|
+
{
|
|
1824
|
+
this._EventHandlerProvider.fireEvent('onPanelOpened', tmpPanelData);
|
|
1825
|
+
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
return tmpPanelData;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* Close a properties panel by panel hash.
|
|
1833
|
+
* @param {string} pPanelHash
|
|
1834
|
+
* @returns {boolean}
|
|
1835
|
+
*/
|
|
1836
|
+
closePanel(pPanelHash)
|
|
1837
|
+
{
|
|
1838
|
+
let tmpIndex = this._FlowData.OpenPanels.findIndex((pPanel) => pPanel.Hash === pPanelHash);
|
|
1839
|
+
if (tmpIndex < 0) return false;
|
|
1840
|
+
|
|
1841
|
+
let tmpRemovedPanel = this._FlowData.OpenPanels.splice(tmpIndex, 1)[0];
|
|
1842
|
+
|
|
1843
|
+
// Clean up the panel instance
|
|
1844
|
+
if (this._PropertiesPanelView)
|
|
1845
|
+
{
|
|
1846
|
+
this._PropertiesPanelView.destroyPanel(pPanelHash);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
this.renderFlow();
|
|
1850
|
+
this.marshalFromView();
|
|
1851
|
+
|
|
1852
|
+
if (this._EventHandlerProvider)
|
|
1853
|
+
{
|
|
1854
|
+
this._EventHandlerProvider.fireEvent('onPanelClosed', tmpRemovedPanel);
|
|
1855
|
+
this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
return true;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
/**
|
|
1862
|
+
* Close all panels for a given node.
|
|
1863
|
+
* @param {string} pNodeHash
|
|
1864
|
+
* @returns {boolean}
|
|
1865
|
+
*/
|
|
1866
|
+
closePanelForNode(pNodeHash)
|
|
1867
|
+
{
|
|
1868
|
+
let tmpPanelsToClose = this._FlowData.OpenPanels.filter((pPanel) => pPanel.NodeHash === pNodeHash);
|
|
1869
|
+
if (tmpPanelsToClose.length === 0) return false;
|
|
1870
|
+
|
|
1871
|
+
for (let i = 0; i < tmpPanelsToClose.length; i++)
|
|
1872
|
+
{
|
|
1873
|
+
let tmpIndex = this._FlowData.OpenPanels.indexOf(tmpPanelsToClose[i]);
|
|
1874
|
+
if (tmpIndex >= 0)
|
|
1875
|
+
{
|
|
1876
|
+
this._FlowData.OpenPanels.splice(tmpIndex, 1);
|
|
1877
|
+
}
|
|
1878
|
+
if (this._PropertiesPanelView)
|
|
1879
|
+
{
|
|
1880
|
+
this._PropertiesPanelView.destroyPanel(tmpPanelsToClose[i].Hash);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
return true;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/**
|
|
1888
|
+
* Toggle a properties panel for a node (open if closed, close if open).
|
|
1889
|
+
* @param {string} pNodeHash
|
|
1890
|
+
* @returns {Object|false}
|
|
1891
|
+
*/
|
|
1892
|
+
togglePanel(pNodeHash)
|
|
1893
|
+
{
|
|
1894
|
+
let tmpExisting = this._FlowData.OpenPanels.find((pPanel) => pPanel.NodeHash === pNodeHash);
|
|
1895
|
+
if (tmpExisting)
|
|
1896
|
+
{
|
|
1897
|
+
this.closePanel(tmpExisting.Hash);
|
|
1898
|
+
return false;
|
|
1899
|
+
}
|
|
1900
|
+
return this.openPanel(pNodeHash);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
/**
|
|
1904
|
+
* Update a panel's position (for drag).
|
|
1905
|
+
* @param {string} pPanelHash
|
|
1906
|
+
* @param {number} pX
|
|
1907
|
+
* @param {number} pY
|
|
1908
|
+
*/
|
|
1909
|
+
updatePanelPosition(pPanelHash, pX, pY)
|
|
1910
|
+
{
|
|
1911
|
+
let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
|
|
1912
|
+
if (!tmpPanel) return;
|
|
1913
|
+
|
|
1914
|
+
tmpPanel.X = pX;
|
|
1915
|
+
tmpPanel.Y = pY;
|
|
1916
|
+
|
|
1917
|
+
// Reset tether handle positions when panel moves
|
|
1918
|
+
this._resetHandlesForPanel(pPanelHash);
|
|
1919
|
+
|
|
1920
|
+
// Update the foreignObject position directly for smooth dragging
|
|
1921
|
+
if (this._PanelsLayer)
|
|
1922
|
+
{
|
|
1923
|
+
let tmpFO = this._PanelsLayer.querySelector(`[data-panel-hash="${pPanelHash}"]`);
|
|
1924
|
+
if (tmpFO)
|
|
1925
|
+
{
|
|
1926
|
+
tmpFO.setAttribute('x', String(pX));
|
|
1927
|
+
tmpFO.setAttribute('y', String(pY));
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// Update the tether for this panel
|
|
1932
|
+
this._renderTethersForNode(tmpPanel.NodeHash);
|
|
1933
|
+
}
|
|
1112
1934
|
}
|
|
1113
1935
|
|
|
1114
1936
|
module.exports = PictViewFlow;
|