pict-section-flow 0.0.18 → 0.0.19
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/Theme_Integration.md +150 -0
- package/docs/_sidebar.md +1 -0
- package/package.json +8 -8
- package/source/providers/PictProvider-Flow-CSS.js +197 -47
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +9 -5
- package/source/providers/PictProvider-Flow-NodeTypes.js +10 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +7 -17
- package/source/services/PictService-Flow-InteractionManager.js +39 -42
- package/source/services/PictService-Flow-PortRenderer.js +10 -24
- package/source/views/PictView-Flow-FloatingToolbar.js +69 -61
- package/source/views/PictView-Flow-Node.js +19 -6
- package/source/views/PictView-Flow-PropertiesPanel.js +46 -53
- package/source/views/PictView-Flow-Toolbar.js +664 -789
- package/source/views/PictView-Flow.js +183 -2
|
@@ -21,72 +21,152 @@ const _DefaultConfiguration =
|
|
|
21
21
|
[
|
|
22
22
|
{
|
|
23
23
|
Hash: 'Flow-Toolbar-Template',
|
|
24
|
+
// Inline onclick handlers route to the toolbar via the FlowView
|
|
25
|
+
// (the flow toolbar is reachable as ._ToolbarView from the
|
|
26
|
+
// registered FlowView). Each button is responsible for its own
|
|
27
|
+
// action — no event delegation.
|
|
24
28
|
Template: /*html*/`
|
|
25
29
|
<div class="pict-flow-toolbar" id="Flow-Toolbar-Bar-{~D:Record.FlowViewIdentifier~}">
|
|
26
30
|
<div class="pict-flow-toolbar-group">
|
|
27
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="add-node" id="Flow-Toolbar-AddNode-{~D:Record.FlowViewIdentifier~}" title="Add Node"
|
|
31
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="add-node" id="Flow-Toolbar-AddNode-{~D:Record.FlowViewIdentifier~}" title="Add Node"
|
|
32
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('add-node')">
|
|
28
33
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-plus-{~D:Record.FlowViewIdentifier~}"></span>
|
|
29
34
|
<span class="pict-flow-toolbar-btn-text">Node</span>
|
|
30
35
|
</button>
|
|
31
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="cards-popup" id="Flow-Toolbar-Cards-{~D:Record.FlowViewIdentifier~}" title="Card Palette"
|
|
36
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="cards-popup" id="Flow-Toolbar-Cards-{~D:Record.FlowViewIdentifier~}" title="Card Palette"
|
|
37
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('cards-popup')">
|
|
32
38
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-cards-{~D:Record.FlowViewIdentifier~}"></span>
|
|
33
39
|
<span class="pict-flow-toolbar-btn-text">Cards</span>
|
|
34
40
|
<span class="pict-flow-toolbar-btn-chevron" id="Flow-Toolbar-CardsChevron-{~D:Record.FlowViewIdentifier~}"></span>
|
|
35
41
|
</button>
|
|
36
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="delete-selected" title="Delete Node"
|
|
42
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="delete-selected" title="Delete Node"
|
|
43
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('delete-selected')">
|
|
37
44
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-trash-{~D:Record.FlowViewIdentifier~}"></span>
|
|
38
45
|
</button>
|
|
39
46
|
</div>
|
|
40
47
|
<div class="pict-flow-toolbar-group">
|
|
41
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="layout-popup" id="Flow-Toolbar-Layout-{~D:Record.FlowViewIdentifier~}" title="Manage saved layouts"
|
|
48
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="layout-popup" id="Flow-Toolbar-Layout-{~D:Record.FlowViewIdentifier~}" title="Manage saved layouts"
|
|
49
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('layout-popup')">
|
|
42
50
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-layout-{~D:Record.FlowViewIdentifier~}"></span>
|
|
43
51
|
<span class="pict-flow-toolbar-btn-text">Layouts</span>
|
|
44
52
|
<span class="pict-flow-toolbar-btn-chevron" id="Flow-Toolbar-LayoutChevron-{~D:Record.FlowViewIdentifier~}"></span>
|
|
45
53
|
</button>
|
|
46
54
|
<div class="pict-flow-toolbar-btn-split" id="Flow-Toolbar-Auto-{~D:Record.FlowViewIdentifier~}">
|
|
47
|
-
<button class="pict-flow-toolbar-btn pict-flow-toolbar-btn-split-main" data-flow-action="apply-current-layout" title="Apply current layout algorithm"
|
|
55
|
+
<button class="pict-flow-toolbar-btn pict-flow-toolbar-btn-split-main" data-flow-action="apply-current-layout" title="Apply current layout algorithm"
|
|
56
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('apply-current-layout')">
|
|
48
57
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-auto-{~D:Record.FlowViewIdentifier~}"></span>
|
|
49
58
|
<span class="pict-flow-toolbar-btn-text">Auto</span>
|
|
50
59
|
</button>
|
|
51
|
-
<button class="pict-flow-toolbar-btn pict-flow-toolbar-btn-split-chevron" data-flow-action="layout-algorithm-popup" title="Choose layout algorithm"
|
|
60
|
+
<button class="pict-flow-toolbar-btn pict-flow-toolbar-btn-split-chevron" data-flow-action="layout-algorithm-popup" title="Choose layout algorithm"
|
|
61
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('layout-algorithm-popup')">
|
|
52
62
|
<span class="pict-flow-toolbar-btn-chevron" id="Flow-Toolbar-AutoChevron-{~D:Record.FlowViewIdentifier~}"></span>
|
|
53
63
|
</button>
|
|
54
64
|
</div>
|
|
55
65
|
</div>
|
|
56
66
|
<div class="pict-flow-toolbar-group">
|
|
57
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="zoom-in" title="Zoom In"
|
|
67
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="zoom-in" title="Zoom In"
|
|
68
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('zoom-in')">
|
|
58
69
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-zoom-in-{~D:Record.FlowViewIdentifier~}"></span>
|
|
59
70
|
</button>
|
|
60
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="zoom-out" title="Zoom Out"
|
|
71
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="zoom-out" title="Zoom Out"
|
|
72
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('zoom-out')">
|
|
61
73
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-zoom-out-{~D:Record.FlowViewIdentifier~}"></span>
|
|
62
74
|
</button>
|
|
63
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="zoom-fit" title="Fit to View"
|
|
75
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="zoom-fit" title="Fit to View"
|
|
76
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('zoom-fit')">
|
|
64
77
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-zoom-fit-{~D:Record.FlowViewIdentifier~}"></span>
|
|
65
78
|
</button>
|
|
66
79
|
</div>
|
|
67
80
|
<div class="pict-flow-toolbar-group pict-flow-toolbar-right">
|
|
68
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="settings-popup" id="Flow-Toolbar-Settings-{~D:Record.FlowViewIdentifier~}" title="Theme Settings"
|
|
81
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="settings-popup" id="Flow-Toolbar-Settings-{~D:Record.FlowViewIdentifier~}" title="Theme Settings"
|
|
82
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('settings-popup')">
|
|
69
83
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-settings-{~D:Record.FlowViewIdentifier~}"></span>
|
|
70
84
|
</button>
|
|
71
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="fullscreen" id="Flow-Toolbar-Fullscreen-{~D:Record.FlowViewIdentifier~}" title="Toggle Fullscreen"
|
|
85
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="fullscreen" id="Flow-Toolbar-Fullscreen-{~D:Record.FlowViewIdentifier~}" title="Toggle Fullscreen"
|
|
86
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('fullscreen')">
|
|
72
87
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Fullscreen-Icon-{~D:Record.FlowViewIdentifier~}"></span>
|
|
73
88
|
</button>
|
|
74
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="toggle-floating" title="Float"
|
|
89
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="toggle-floating" title="Float"
|
|
90
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('toggle-floating')">
|
|
75
91
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-grip-{~D:Record.FlowViewIdentifier~}"></span>
|
|
76
92
|
</button>
|
|
77
|
-
<button class="pict-flow-toolbar-btn" data-flow-action="collapse-toolbar" title="Collapse Toolbar"
|
|
93
|
+
<button class="pict-flow-toolbar-btn" data-flow-action="collapse-toolbar" title="Collapse Toolbar"
|
|
94
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('collapse-toolbar')">
|
|
78
95
|
<span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-collapse-{~D:Record.FlowViewIdentifier~}"></span>
|
|
79
96
|
</button>
|
|
80
97
|
</div>
|
|
81
98
|
</div>
|
|
82
99
|
<div class="pict-flow-toolbar-collapsed" id="Flow-Toolbar-Collapsed-{~D:Record.FlowViewIdentifier~}">
|
|
83
|
-
<button class="pict-flow-toolbar-expand-btn" data-flow-action="expand-toolbar" title="Expand Toolbar" id="Flow-Toolbar-ExpandBtn-{~D:Record.FlowViewIdentifier~}"
|
|
100
|
+
<button class="pict-flow-toolbar-expand-btn" data-flow-action="expand-toolbar" title="Expand Toolbar" id="Flow-Toolbar-ExpandBtn-{~D:Record.FlowViewIdentifier~}"
|
|
101
|
+
onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('expand-toolbar')">
|
|
84
102
|
<span id="Flow-Toolbar-Icon-expand-{~D:Record.FlowViewIdentifier~}"></span>
|
|
85
103
|
</button>
|
|
86
104
|
</div>
|
|
87
105
|
<div class="pict-flow-toolbar-popup-anchor" id="Flow-Toolbar-PopupAnchor-{~D:Record.FlowViewIdentifier~}">
|
|
88
106
|
</div>
|
|
89
107
|
`
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
Hash: 'Flow-AddNode-List',
|
|
111
|
+
// Iteration source is `Record.Rows` — the outer call sets the
|
|
112
|
+
// FlowViewIdentifier on each row so inline handlers resolve their
|
|
113
|
+
// owning view independently. Nested {~D:~} inside {~TS:~}
|
|
114
|
+
// addresses isn't supported by the template engine, so we keep
|
|
115
|
+
// addresses static.
|
|
116
|
+
Template: '{~TS:Flow-AddNode-Row:Record.Rows~}'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
Hash: 'Flow-AddNode-Row',
|
|
120
|
+
Template: '<div class="pict-flow-popup-list-item" data-node-type="{~D:Record.NodeType~}"'
|
|
121
|
+
+ ' onclick="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._ToolbarView._addNodeFromPopup(this.getAttribute(\'data-node-type\'))">'
|
|
122
|
+
+ '<span class="pict-flow-popup-list-item-icon">{~D:Record.IconHTML~}</span>'
|
|
123
|
+
+ '<span class="pict-flow-popup-list-item-label">{~D:Record.Label~}</span>'
|
|
124
|
+
+ '{~NE:Record.Code^<span class="pict-flow-popup-list-item-code">{~D:Record.Code~}</span>~}'
|
|
125
|
+
+ '</div>'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
Hash: 'Flow-Cards-List',
|
|
129
|
+
Template: '{~TS:Flow-Cards-Category:Record.Categories~}'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
Hash: 'Flow-Cards-Category',
|
|
133
|
+
Template: '<div class="pict-flow-palette-category" style="padding:0.35em 0.5em;">'
|
|
134
|
+
+ '<div class="pict-flow-palette-category-label">{~D:Record.Name~}</div>'
|
|
135
|
+
+ '<div class="pict-flow-palette-cards">{~TS:Flow-Cards-Card:Record.Cards~}</div>'
|
|
136
|
+
+ '</div>'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
Hash: 'Flow-Cards-Card',
|
|
140
|
+
Template: '<div class="pict-flow-palette-card{~D:Record.DisabledClass~}" data-card-type="{~D:Record.CardType~}" title="{~D:Record.Tooltip~}"'
|
|
141
|
+
+ ' onclick="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._ToolbarView._addCardFromPopup(this.getAttribute(\'data-card-type\'))">'
|
|
142
|
+
+ '{~NE:Record.IconHTML^<span class="pict-flow-palette-card-icon">{~D:Record.IconHTML~}</span>~}'
|
|
143
|
+
+ '{~NE:Record.IconEmoji^<span class="pict-flow-palette-card-icon">{~D:Record.IconEmoji~}</span>~}'
|
|
144
|
+
+ '{~NE:Record.SwatchColor^<span class="pict-flow-palette-card-swatch" style="background-color: {~D:Record.SwatchColor~};"></span>~}'
|
|
145
|
+
+ '<span class="pict-flow-palette-card-title">{~D:Record.Label~}</span>'
|
|
146
|
+
+ '{~NE:Record.Code^<span class="pict-flow-palette-card-code">{~D:Record.Code~}</span>~}'
|
|
147
|
+
+ '</div>'
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
Hash: 'Flow-Layout-List',
|
|
151
|
+
Template: '{~TS:Flow-Layout-Row:Record.Rows~}'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
Hash: 'Flow-Layout-Row',
|
|
155
|
+
Template: '<div class="pict-flow-popup-layout-row" data-layout-hash="{~D:Record.LayoutHash~}"'
|
|
156
|
+
+ ' onclick="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._ToolbarView._restoreLayoutFromPopup(\'{~D:Record.LayoutHash~}\', event)">'
|
|
157
|
+
+ '<span class="pict-flow-popup-layout-name">{~D:Record.Name~}</span>'
|
|
158
|
+
+ '<button class="pict-flow-popup-layout-delete" title="Delete layout"'
|
|
159
|
+
+ ' onclick="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._ToolbarView._deleteLayoutFromPopup(\'{~D:Record.LayoutHash~}\', event)">'
|
|
160
|
+
+ '{~D:Record.DeleteIconHTML~}</button>'
|
|
161
|
+
+ '</div>'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
Hash: 'Flow-Layout-OptionList',
|
|
165
|
+
Template: '{~TS:Flow-Layout-Option:Record.Options~}'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
Hash: 'Flow-Layout-Option',
|
|
169
|
+
Template: '<option value="{~D:Record.Value~}"{~D:Record.SelectedAttr~}>{~D:Record.Label~}</option>'
|
|
90
170
|
}
|
|
91
171
|
],
|
|
92
172
|
|
|
@@ -141,33 +221,8 @@ class PictViewFlowToolbar extends libPictView
|
|
|
141
221
|
{
|
|
142
222
|
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
143
223
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
if (tmpToolbarBar.length > 0)
|
|
147
|
-
{
|
|
148
|
-
tmpToolbarBar[0].addEventListener('click', (pEvent) =>
|
|
149
|
-
{
|
|
150
|
-
let tmpTarget = pEvent.target;
|
|
151
|
-
if (!tmpTarget) return;
|
|
152
|
-
|
|
153
|
-
// Walk up to find the button with the action
|
|
154
|
-
let tmpButton = tmpTarget.closest('[data-flow-action]');
|
|
155
|
-
if (!tmpButton) return;
|
|
156
|
-
|
|
157
|
-
let tmpAction = tmpButton.getAttribute('data-flow-action');
|
|
158
|
-
this._handleToolbarAction(tmpAction);
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Bind expand button click (it's outside the main toolbar bar)
|
|
163
|
-
let tmpExpandBtn = this.pict.ContentAssignment.getElement(`#Flow-Toolbar-ExpandBtn-${tmpFlowViewIdentifier}`);
|
|
164
|
-
if (tmpExpandBtn.length > 0)
|
|
165
|
-
{
|
|
166
|
-
tmpExpandBtn[0].addEventListener('click', () =>
|
|
167
|
-
{
|
|
168
|
-
this._setToolbarMode('docked');
|
|
169
|
-
});
|
|
170
|
-
}
|
|
224
|
+
// Click handlers live on each button as inline `onclick` attributes
|
|
225
|
+
// in Flow-Toolbar-Template — they call _handleToolbarAction directly.
|
|
171
226
|
|
|
172
227
|
// Populate SVG icons for toolbar buttons
|
|
173
228
|
this._populateToolbarIcons();
|
|
@@ -425,212 +480,180 @@ class PictViewFlowToolbar extends libPictView
|
|
|
425
480
|
// ── Add Node Popup ────────────────────────────────────────────────────
|
|
426
481
|
|
|
427
482
|
/**
|
|
428
|
-
* Build the searchable Add Node popup content
|
|
483
|
+
* Build the searchable Add Node popup content via templates with inline
|
|
484
|
+
* handlers. Search input fires `_filterNodeList` which re-renders the
|
|
485
|
+
* list section in place.
|
|
486
|
+
*
|
|
429
487
|
* @param {HTMLElement} pContainer
|
|
430
488
|
*/
|
|
431
489
|
_buildAddNodePopup(pContainer)
|
|
432
490
|
{
|
|
433
|
-
|
|
434
|
-
let tmpSearchWrapper = document.createElement('div');
|
|
435
|
-
tmpSearchWrapper.className = 'pict-flow-popup-search-wrapper';
|
|
436
|
-
|
|
437
|
-
let tmpSearchIcon = document.createElement('span');
|
|
438
|
-
tmpSearchIcon.className = 'pict-flow-popup-search-icon';
|
|
491
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
439
492
|
let tmpIconProvider = this._FlowView ? this._FlowView._IconProvider : null;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// Initial population
|
|
459
|
-
this._populateNodeList(tmpListDiv, '');
|
|
460
|
-
|
|
461
|
-
// Filter on input
|
|
462
|
-
tmpSearchInput.addEventListener('input', () =>
|
|
463
|
-
{
|
|
464
|
-
this._populateNodeList(tmpListDiv, tmpSearchInput.value);
|
|
465
|
-
});
|
|
493
|
+
let tmpSearchIconHTML = tmpIconProvider ? tmpIconProvider.getIconSVGMarkup('search', 12) : '';
|
|
494
|
+
|
|
495
|
+
let tmpListID = `Flow-Toolbar-AddNodeList-${tmpFlowViewIdentifier}`;
|
|
496
|
+
let tmpSearchID = `Flow-Toolbar-AddNodeSearch-${tmpFlowViewIdentifier}`;
|
|
497
|
+
|
|
498
|
+
// Stage AppData and render the initial list HTML inline — the popup
|
|
499
|
+
// element isn't in the DOM yet, so we can't look it up via
|
|
500
|
+
// getElementById here. _filterNodeList handles updates after open.
|
|
501
|
+
let tmpInitialListHTML = this._renderNodeListHTML('');
|
|
502
|
+
|
|
503
|
+
this.pict.ContentAssignment.assignContent(pContainer,
|
|
504
|
+
'<div class="pict-flow-popup-search-wrapper">'
|
|
505
|
+
+ '<span class="pict-flow-popup-search-icon">' + tmpSearchIconHTML + '</span>'
|
|
506
|
+
+ '<input id="' + tmpSearchID + '" class="pict-flow-popup-search" type="text" placeholder="Search node types..." '
|
|
507
|
+
+ 'oninput="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._filterNodeList(\'' + tmpListID + '\', this.value)" />'
|
|
508
|
+
+ '</div>'
|
|
509
|
+
+ '<div id="' + tmpListID + '" class="pict-flow-popup-node-list">' + tmpInitialListHTML + '</div>');
|
|
466
510
|
}
|
|
467
511
|
|
|
468
512
|
/**
|
|
469
|
-
*
|
|
470
|
-
*
|
|
513
|
+
* Build the HTML string for the Add Node list filtered by the given
|
|
514
|
+
* text. Stages AppData and renders the {~TS:~} template into a string;
|
|
515
|
+
* returns "no matches" markup when nothing matches.
|
|
516
|
+
*
|
|
471
517
|
* @param {string} pFilter
|
|
518
|
+
* @returns {string}
|
|
472
519
|
*/
|
|
473
|
-
|
|
520
|
+
_renderNodeListHTML(pFilter)
|
|
474
521
|
{
|
|
475
|
-
if (!this._FlowView || !this._FlowView._NodeTypeProvider) return;
|
|
476
|
-
|
|
477
|
-
// Clear
|
|
478
|
-
while (pListDiv.firstChild)
|
|
479
|
-
{
|
|
480
|
-
pListDiv.removeChild(pListDiv.firstChild);
|
|
481
|
-
}
|
|
522
|
+
if (!this._FlowView || !this._FlowView._NodeTypeProvider) return '';
|
|
482
523
|
|
|
524
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
483
525
|
let tmpTypes = this._FlowView._NodeTypeProvider.getNodeTypes();
|
|
484
526
|
let tmpTypeKeys = Object.keys(tmpTypes);
|
|
485
527
|
let tmpFilter = (pFilter || '').toLowerCase().trim();
|
|
486
528
|
let tmpIconProvider = this._FlowView._IconProvider;
|
|
487
|
-
let tmpMatchCount = 0;
|
|
488
529
|
|
|
530
|
+
let tmpRows = [];
|
|
489
531
|
for (let i = 0; i < tmpTypeKeys.length; i++)
|
|
490
532
|
{
|
|
491
533
|
let tmpTypeConfig = tmpTypes[tmpTypeKeys[i]];
|
|
492
534
|
let tmpMeta = tmpTypeConfig.CardMetadata || {};
|
|
493
|
-
|
|
494
|
-
// Skip disabled cards
|
|
495
535
|
if (tmpMeta.Enabled === false) continue;
|
|
496
536
|
|
|
497
|
-
// Filter match: label, code, or category
|
|
498
537
|
if (tmpFilter)
|
|
499
538
|
{
|
|
500
539
|
let tmpLabel = (tmpTypeConfig.Label || '').toLowerCase();
|
|
501
540
|
let tmpCode = (tmpMeta.Code || '').toLowerCase();
|
|
502
541
|
let tmpCategory = (tmpMeta.Category || '').toLowerCase();
|
|
503
|
-
if (tmpLabel.indexOf(tmpFilter) < 0 &&
|
|
504
|
-
tmpCode.indexOf(tmpFilter) < 0 &&
|
|
505
|
-
tmpCategory.indexOf(tmpFilter) < 0)
|
|
506
|
-
{
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
542
|
+
if (tmpLabel.indexOf(tmpFilter) < 0 && tmpCode.indexOf(tmpFilter) < 0 && tmpCategory.indexOf(tmpFilter) < 0) continue;
|
|
509
543
|
}
|
|
510
544
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
let tmpRow = document.createElement('div');
|
|
514
|
-
tmpRow.className = 'pict-flow-popup-list-item';
|
|
515
|
-
tmpRow.setAttribute('data-node-type', tmpTypeKeys[i]);
|
|
516
|
-
|
|
517
|
-
// Icon
|
|
518
|
-
let tmpIconSpan = document.createElement('span');
|
|
519
|
-
tmpIconSpan.className = 'pict-flow-popup-list-item-icon';
|
|
545
|
+
let tmpIconHTML = '';
|
|
520
546
|
if (tmpIconProvider)
|
|
521
547
|
{
|
|
522
548
|
let tmpResolvedKey = tmpIconProvider.resolveIconKey(tmpMeta);
|
|
523
|
-
|
|
549
|
+
tmpIconHTML = tmpIconProvider.getIconSVGMarkup(tmpResolvedKey, 16);
|
|
524
550
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
// Label
|
|
528
|
-
let tmpLabelSpan = document.createElement('span');
|
|
529
|
-
tmpLabelSpan.className = 'pict-flow-popup-list-item-label';
|
|
530
|
-
tmpLabelSpan.textContent = tmpTypeConfig.Label;
|
|
531
|
-
tmpRow.appendChild(tmpLabelSpan);
|
|
532
|
-
|
|
533
|
-
// Code badge
|
|
534
|
-
if (tmpMeta.Code)
|
|
535
|
-
{
|
|
536
|
-
let tmpCodeSpan = document.createElement('span');
|
|
537
|
-
tmpCodeSpan.className = 'pict-flow-popup-list-item-code';
|
|
538
|
-
tmpCodeSpan.textContent = tmpMeta.Code;
|
|
539
|
-
tmpRow.appendChild(tmpCodeSpan);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Click handler
|
|
543
|
-
tmpRow.addEventListener('click', () =>
|
|
551
|
+
tmpRows.push(
|
|
544
552
|
{
|
|
545
|
-
|
|
546
|
-
|
|
553
|
+
NodeType: tmpTypeKeys[i],
|
|
554
|
+
Label: tmpTypeConfig.Label || '',
|
|
555
|
+
IconHTML: tmpIconHTML,
|
|
556
|
+
Code: tmpMeta.Code || '',
|
|
557
|
+
FlowViewIdentifier: tmpFlowViewIdentifier
|
|
547
558
|
});
|
|
548
|
-
|
|
549
|
-
pListDiv.appendChild(tmpRow);
|
|
550
559
|
}
|
|
551
560
|
|
|
552
|
-
if (
|
|
561
|
+
if (tmpRows.length === 0)
|
|
553
562
|
{
|
|
554
|
-
|
|
555
|
-
tmpEmpty.className = 'pict-flow-popup-list-empty';
|
|
556
|
-
tmpEmpty.textContent = 'No matching node types';
|
|
557
|
-
pListDiv.appendChild(tmpEmpty);
|
|
563
|
+
return '<div class="pict-flow-popup-list-empty">No matching node types</div>';
|
|
558
564
|
}
|
|
565
|
+
return this.pict.parseTemplateByHash('Flow-AddNode-List', { Rows: tmpRows });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Update the Add Node list contents in response to a search input
|
|
570
|
+
* change. Called from the inline `oninput` handler. The list element
|
|
571
|
+
* is already in the DOM by the time this fires.
|
|
572
|
+
*
|
|
573
|
+
* @param {string} pListID
|
|
574
|
+
* @param {string} pFilter
|
|
575
|
+
*/
|
|
576
|
+
_filterNodeList(pListID, pFilter)
|
|
577
|
+
{
|
|
578
|
+
let tmpListEl = document.getElementById(pListID);
|
|
579
|
+
if (!tmpListEl) return;
|
|
580
|
+
this.pict.ContentAssignment.assignContent(tmpListEl, this._renderNodeListHTML(pFilter));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Inline handler for an Add Node row click. Adds the node at viewport
|
|
585
|
+
* center and closes the popup.
|
|
586
|
+
*
|
|
587
|
+
* @param {string} pNodeType
|
|
588
|
+
*/
|
|
589
|
+
_addNodeFromPopup(pNodeType)
|
|
590
|
+
{
|
|
591
|
+
this._addNodeAtCenter(pNodeType);
|
|
592
|
+
this._closePopup();
|
|
559
593
|
}
|
|
560
594
|
|
|
561
595
|
// ── Cards Popup ───────────────────────────────────────────────────────
|
|
562
596
|
|
|
563
597
|
/**
|
|
564
|
-
* Build the Cards popup
|
|
598
|
+
* Build the Cards popup using templates with inline handlers. Search
|
|
599
|
+
* input fires `_filterCardsList` which re-renders the categorized list.
|
|
600
|
+
*
|
|
565
601
|
* @param {HTMLElement} pContainer
|
|
566
602
|
*/
|
|
567
603
|
_buildCardsPopup(pContainer)
|
|
568
604
|
{
|
|
569
|
-
|
|
570
|
-
let tmpSearchWrapper = document.createElement('div');
|
|
571
|
-
tmpSearchWrapper.className = 'pict-flow-popup-search-wrapper';
|
|
572
|
-
|
|
573
|
-
let tmpSearchIcon = document.createElement('span');
|
|
574
|
-
tmpSearchIcon.className = 'pict-flow-popup-search-icon';
|
|
605
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
575
606
|
let tmpIconProvider = this._FlowView ? this._FlowView._IconProvider : null;
|
|
576
|
-
|
|
577
|
-
{
|
|
578
|
-
tmpSearchIcon.innerHTML = tmpIconProvider.getIconSVGMarkup('search', 12);
|
|
579
|
-
}
|
|
580
|
-
tmpSearchWrapper.appendChild(tmpSearchIcon);
|
|
607
|
+
let tmpSearchIconHTML = tmpIconProvider ? tmpIconProvider.getIconSVGMarkup('search', 12) : '';
|
|
581
608
|
|
|
582
|
-
let
|
|
583
|
-
|
|
584
|
-
tmpSearchInput.setAttribute('type', 'text');
|
|
585
|
-
tmpSearchInput.setAttribute('placeholder', 'Search cards...');
|
|
586
|
-
tmpSearchWrapper.appendChild(tmpSearchInput);
|
|
587
|
-
pContainer.appendChild(tmpSearchWrapper);
|
|
609
|
+
let tmpListID = `Flow-Toolbar-CardsList-${tmpFlowViewIdentifier}`;
|
|
610
|
+
let tmpSearchID = `Flow-Toolbar-CardsSearch-${tmpFlowViewIdentifier}`;
|
|
588
611
|
|
|
589
|
-
//
|
|
590
|
-
let
|
|
591
|
-
tmpListDiv.className = 'pict-flow-popup-node-list';
|
|
592
|
-
pContainer.appendChild(tmpListDiv);
|
|
612
|
+
// Initial render inline — popup not yet in DOM.
|
|
613
|
+
let tmpInitialListHTML = this._renderCardsListHTML('');
|
|
593
614
|
|
|
594
|
-
|
|
595
|
-
|
|
615
|
+
this.pict.ContentAssignment.assignContent(pContainer,
|
|
616
|
+
'<div class="pict-flow-popup-search-wrapper">'
|
|
617
|
+
+ '<span class="pict-flow-popup-search-icon">' + tmpSearchIconHTML + '</span>'
|
|
618
|
+
+ '<input id="' + tmpSearchID + '" class="pict-flow-popup-search" type="text" placeholder="Search cards..." '
|
|
619
|
+
+ 'oninput="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._filterCardsList(\'' + tmpListID + '\', this.value)" />'
|
|
620
|
+
+ '</div>'
|
|
621
|
+
+ '<div id="' + tmpListID + '" class="pict-flow-popup-node-list">' + tmpInitialListHTML + '</div>');
|
|
596
622
|
|
|
597
|
-
//
|
|
598
|
-
|
|
623
|
+
// Focus search input — defer past the popup append.
|
|
624
|
+
setTimeout(() =>
|
|
599
625
|
{
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
// Focus search input
|
|
604
|
-
setTimeout(() => { tmpSearchInput.focus(); }, 50);
|
|
626
|
+
let tmpSearch = document.getElementById(tmpSearchID);
|
|
627
|
+
if (tmpSearch) tmpSearch.focus();
|
|
628
|
+
}, 50);
|
|
605
629
|
}
|
|
606
630
|
|
|
607
631
|
/**
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
632
|
+
* Build the cards-list HTML string for the given filter. Stages
|
|
633
|
+
* AppData and renders via the {~TS:~} templates; returns "no matches"
|
|
634
|
+
* markup when nothing matches.
|
|
635
|
+
*
|
|
636
|
+
* @param {string} pFilter
|
|
637
|
+
* @returns {string}
|
|
611
638
|
*/
|
|
612
|
-
|
|
639
|
+
_renderCardsListHTML(pFilter)
|
|
613
640
|
{
|
|
614
|
-
if (!this._FlowView || !this._FlowView._NodeTypeProvider) return;
|
|
615
|
-
|
|
616
|
-
// Clear existing content
|
|
617
|
-
while (pContainer.firstChild)
|
|
618
|
-
{
|
|
619
|
-
pContainer.removeChild(pContainer.firstChild);
|
|
620
|
-
}
|
|
641
|
+
if (!this._FlowView || !this._FlowView._NodeTypeProvider) return '';
|
|
621
642
|
|
|
643
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
622
644
|
let tmpCategories = this._FlowView._NodeTypeProvider.getCardsByCategory();
|
|
623
645
|
let tmpCategoryKeys = Object.keys(tmpCategories);
|
|
624
646
|
let tmpFilter = (pFilter || '').toLowerCase().trim();
|
|
647
|
+
let tmpIconProvider = this._FlowView._IconProvider;
|
|
625
648
|
let tmpTotalMatchCount = 0;
|
|
626
649
|
|
|
650
|
+
let tmpCategoryRecords = [];
|
|
627
651
|
for (let i = 0; i < tmpCategoryKeys.length; i++)
|
|
628
652
|
{
|
|
629
653
|
let tmpCategoryName = tmpCategoryKeys[i];
|
|
630
654
|
let tmpCards = tmpCategories[tmpCategoryName];
|
|
631
|
-
let
|
|
655
|
+
let tmpMatching = [];
|
|
632
656
|
|
|
633
|
-
// Filter cards within this category
|
|
634
657
|
for (let j = 0; j < tmpCards.length; j++)
|
|
635
658
|
{
|
|
636
659
|
let tmpCardConfig = tmpCards[j];
|
|
@@ -641,318 +664,225 @@ class PictViewFlowToolbar extends libPictView
|
|
|
641
664
|
let tmpLabel = (tmpCardConfig.Label || '').toLowerCase();
|
|
642
665
|
let tmpCode = (tmpMeta.Code || '').toLowerCase();
|
|
643
666
|
let tmpCategory = tmpCategoryName.toLowerCase();
|
|
644
|
-
if (tmpLabel.indexOf(tmpFilter) < 0 &&
|
|
645
|
-
tmpCode.indexOf(tmpFilter) < 0 &&
|
|
646
|
-
tmpCategory.indexOf(tmpFilter) < 0)
|
|
647
|
-
{
|
|
648
|
-
continue;
|
|
649
|
-
}
|
|
667
|
+
if (tmpLabel.indexOf(tmpFilter) < 0 && tmpCode.indexOf(tmpFilter) < 0 && tmpCategory.indexOf(tmpFilter) < 0) continue;
|
|
650
668
|
}
|
|
651
669
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (tmpMatchingCards.length === 0) continue;
|
|
656
|
-
|
|
657
|
-
tmpTotalMatchCount += tmpMatchingCards.length;
|
|
658
|
-
|
|
659
|
-
let tmpCategoryDiv = document.createElement('div');
|
|
660
|
-
tmpCategoryDiv.className = 'pict-flow-palette-category';
|
|
661
|
-
tmpCategoryDiv.style.padding = '0.35em 0.5em';
|
|
662
|
-
|
|
663
|
-
let tmpCategoryLabel = document.createElement('div');
|
|
664
|
-
tmpCategoryLabel.className = 'pict-flow-palette-category-label';
|
|
665
|
-
tmpCategoryLabel.textContent = tmpCategoryName;
|
|
666
|
-
tmpCategoryDiv.appendChild(tmpCategoryLabel);
|
|
667
|
-
|
|
668
|
-
let tmpCardsDiv = document.createElement('div');
|
|
669
|
-
tmpCardsDiv.className = 'pict-flow-palette-cards';
|
|
670
|
-
|
|
671
|
-
for (let j = 0; j < tmpMatchingCards.length; j++)
|
|
672
|
-
{
|
|
673
|
-
let tmpCardConfig = tmpMatchingCards[j];
|
|
674
|
-
let tmpMeta = tmpCardConfig.CardMetadata || {};
|
|
675
|
-
|
|
676
|
-
let tmpCardEl = document.createElement('div');
|
|
677
|
-
tmpCardEl.className = 'pict-flow-palette-card';
|
|
678
|
-
if (tmpMeta.Enabled === false)
|
|
670
|
+
let tmpIconHTML = '';
|
|
671
|
+
let tmpIsEmoji = false;
|
|
672
|
+
if (tmpMeta.Icon && tmpIconProvider && !tmpIconProvider.isEmojiIcon(tmpMeta.Icon))
|
|
679
673
|
{
|
|
680
|
-
|
|
674
|
+
tmpIconHTML = tmpIconProvider.getIconSVGMarkup(tmpIconProvider.resolveIconKey(tmpMeta), 14);
|
|
681
675
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (tmpMeta.Tooltip)
|
|
676
|
+
else if (tmpMeta.Icon)
|
|
685
677
|
{
|
|
686
|
-
|
|
678
|
+
tmpIsEmoji = true;
|
|
687
679
|
}
|
|
688
|
-
else if (
|
|
680
|
+
else if (tmpIconProvider)
|
|
689
681
|
{
|
|
690
|
-
|
|
682
|
+
tmpIconHTML = tmpIconProvider.getIconSVGMarkup('default', 14);
|
|
691
683
|
}
|
|
692
684
|
|
|
693
|
-
|
|
694
|
-
if (tmpMeta.Icon)
|
|
685
|
+
tmpMatching.push(
|
|
695
686
|
{
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
{
|
|
706
|
-
tmpIconSpan.textContent = tmpMeta.Icon;
|
|
707
|
-
}
|
|
708
|
-
tmpCardEl.appendChild(tmpIconSpan);
|
|
709
|
-
}
|
|
710
|
-
else if (this._FlowView._IconProvider)
|
|
711
|
-
{
|
|
712
|
-
let tmpIconSpan = document.createElement('span');
|
|
713
|
-
tmpIconSpan.className = 'pict-flow-palette-card-icon';
|
|
714
|
-
tmpIconSpan.innerHTML = this._FlowView._IconProvider.getIconSVGMarkup('default', 14);
|
|
715
|
-
tmpCardEl.appendChild(tmpIconSpan);
|
|
716
|
-
}
|
|
717
|
-
else if (tmpCardConfig.TitleBarColor)
|
|
718
|
-
{
|
|
719
|
-
let tmpSwatch = document.createElement('span');
|
|
720
|
-
tmpSwatch.className = 'pict-flow-palette-card-swatch';
|
|
721
|
-
tmpSwatch.style.backgroundColor = tmpCardConfig.TitleBarColor;
|
|
722
|
-
tmpCardEl.appendChild(tmpSwatch);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// Title
|
|
726
|
-
let tmpTitleSpan = document.createElement('span');
|
|
727
|
-
tmpTitleSpan.className = 'pict-flow-palette-card-title';
|
|
728
|
-
tmpTitleSpan.textContent = tmpCardConfig.Label;
|
|
729
|
-
tmpCardEl.appendChild(tmpTitleSpan);
|
|
730
|
-
|
|
731
|
-
// Code badge
|
|
732
|
-
if (tmpMeta.Code)
|
|
733
|
-
{
|
|
734
|
-
let tmpCodeSpan = document.createElement('span');
|
|
735
|
-
tmpCodeSpan.className = 'pict-flow-palette-card-code';
|
|
736
|
-
tmpCodeSpan.textContent = tmpMeta.Code;
|
|
737
|
-
tmpCardEl.appendChild(tmpCodeSpan);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// Click handler
|
|
741
|
-
tmpCardEl.addEventListener('click', () =>
|
|
742
|
-
{
|
|
743
|
-
this._addCardFromPalette(tmpCardConfig.Hash);
|
|
744
|
-
this._closePopup();
|
|
687
|
+
CardType: tmpCardConfig.Hash,
|
|
688
|
+
Label: tmpCardConfig.Label || '',
|
|
689
|
+
Code: tmpMeta.Code || '',
|
|
690
|
+
IconHTML: tmpIconHTML,
|
|
691
|
+
IconEmoji: tmpIsEmoji ? tmpMeta.Icon : '',
|
|
692
|
+
DisabledClass: (tmpMeta.Enabled === false) ? ' disabled' : '',
|
|
693
|
+
Tooltip: tmpMeta.Tooltip || tmpMeta.Description || '',
|
|
694
|
+
SwatchColor: (!tmpIconHTML && !tmpIsEmoji && tmpCardConfig.TitleBarColor) ? tmpCardConfig.TitleBarColor : '',
|
|
695
|
+
FlowViewIdentifier: tmpFlowViewIdentifier
|
|
745
696
|
});
|
|
746
|
-
|
|
747
|
-
tmpCardsDiv.appendChild(tmpCardEl);
|
|
748
697
|
}
|
|
749
698
|
|
|
750
|
-
|
|
751
|
-
|
|
699
|
+
if (tmpMatching.length === 0) continue;
|
|
700
|
+
tmpTotalMatchCount += tmpMatching.length;
|
|
701
|
+
tmpCategoryRecords.push(
|
|
702
|
+
{
|
|
703
|
+
Name: tmpCategoryName,
|
|
704
|
+
Cards: tmpMatching,
|
|
705
|
+
FlowViewIdentifier: tmpFlowViewIdentifier
|
|
706
|
+
});
|
|
752
707
|
}
|
|
753
708
|
|
|
754
709
|
if (tmpTotalMatchCount === 0)
|
|
755
710
|
{
|
|
756
|
-
|
|
757
|
-
tmpEmpty.className = 'pict-flow-popup-list-empty';
|
|
758
|
-
tmpEmpty.textContent = tmpFilter ? 'No matching cards' : 'No card types available';
|
|
759
|
-
pContainer.appendChild(tmpEmpty);
|
|
711
|
+
return '<div class="pict-flow-popup-list-empty">' + (tmpFilter ? 'No matching cards' : 'No card types available') + '</div>';
|
|
760
712
|
}
|
|
713
|
+
return this.pict.parseTemplateByHash('Flow-Cards-List', { Categories: tmpCategoryRecords });
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Inline `oninput` handler — refresh card list with the new filter.
|
|
718
|
+
*/
|
|
719
|
+
_filterCardsList(pListID, pFilter)
|
|
720
|
+
{
|
|
721
|
+
let tmpListEl = document.getElementById(pListID);
|
|
722
|
+
if (!tmpListEl) return;
|
|
723
|
+
this.pict.ContentAssignment.assignContent(tmpListEl, this._renderCardsListHTML(pFilter));
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Inline handler for a card click. Adds the node and closes the popup.
|
|
728
|
+
*
|
|
729
|
+
* @param {string} pCardType
|
|
730
|
+
*/
|
|
731
|
+
_addCardFromPopup(pCardType)
|
|
732
|
+
{
|
|
733
|
+
this._addCardFromPalette(pCardType);
|
|
734
|
+
this._closePopup();
|
|
761
735
|
}
|
|
762
736
|
|
|
763
737
|
// ── Layout Popup ──────────────────────────────────────────────────────
|
|
764
738
|
|
|
765
739
|
/**
|
|
766
|
-
* Build the Layout popup
|
|
740
|
+
* Build the Layout popup using templates with inline handlers.
|
|
741
|
+
*
|
|
742
|
+
* Layout records (name + hash) are pushed into AppData and iterated via
|
|
743
|
+
* a {~TS:~} template. The save section is a static fragment with inline
|
|
744
|
+
* handlers that call the toolbar's helper methods.
|
|
745
|
+
*
|
|
767
746
|
* @param {HTMLElement} pContainer
|
|
768
747
|
*/
|
|
769
748
|
_buildLayoutPopup(pContainer)
|
|
770
749
|
{
|
|
750
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
771
751
|
let tmpIconProvider = this._FlowView ? this._FlowView._IconProvider : null;
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
let
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
tmpSaveInput.setAttribute('placeholder', 'Layout name...');
|
|
786
|
-
tmpSaveInputRow.appendChild(tmpSaveInput);
|
|
787
|
-
|
|
788
|
-
let tmpSaveConfirmBtn = document.createElement('button');
|
|
789
|
-
tmpSaveConfirmBtn.className = 'pict-flow-popup-layout-save-confirm';
|
|
790
|
-
tmpSaveConfirmBtn.title = 'Save';
|
|
791
|
-
if (tmpIconProvider)
|
|
792
|
-
{
|
|
793
|
-
tmpSaveConfirmBtn.innerHTML = tmpIconProvider.getIconSVGMarkup('save', 14);
|
|
794
|
-
}
|
|
795
|
-
else
|
|
752
|
+
let tmpSaveIconHTML = tmpIconProvider ? tmpIconProvider.getIconSVGMarkup('save', 14) : '✓';
|
|
753
|
+
let tmpDeleteIconHTML = tmpIconProvider ? tmpIconProvider.getIconSVGMarkup('trash', 12) : '×';
|
|
754
|
+
|
|
755
|
+
let tmpSaveRowID = `Flow-Toolbar-LayoutSaveRow-${tmpFlowViewIdentifier}`;
|
|
756
|
+
let tmpSaveInputRowID = `Flow-Toolbar-LayoutSaveInputRow-${tmpFlowViewIdentifier}`;
|
|
757
|
+
let tmpSaveInputID = `Flow-Toolbar-LayoutSaveInput-${tmpFlowViewIdentifier}`;
|
|
758
|
+
let tmpListID = `Flow-Toolbar-LayoutList-${tmpFlowViewIdentifier}`;
|
|
759
|
+
|
|
760
|
+
// Build layout records for the {~TS:~} iteration
|
|
761
|
+
let tmpLayouts = (this._FlowView && this._FlowView._LayoutProvider)
|
|
762
|
+
? this._FlowView._LayoutProvider.getLayouts() : [];
|
|
763
|
+
let tmpLayoutRecords = [];
|
|
764
|
+
for (let i = 0; i < tmpLayouts.length; i++)
|
|
796
765
|
{
|
|
797
|
-
|
|
766
|
+
tmpLayoutRecords.push(
|
|
767
|
+
{
|
|
768
|
+
LayoutHash: tmpLayouts[i].Hash,
|
|
769
|
+
Name: tmpLayouts[i].Name || '',
|
|
770
|
+
DeleteIconHTML: tmpDeleteIconHTML,
|
|
771
|
+
FlowViewIdentifier: tmpFlowViewIdentifier
|
|
772
|
+
});
|
|
798
773
|
}
|
|
799
|
-
tmpSaveInputRow.appendChild(tmpSaveConfirmBtn);
|
|
800
774
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
775
|
+
let tmpListHTML = (tmpLayoutRecords.length === 0)
|
|
776
|
+
? '<div class="pict-flow-popup-list-empty">No saved layouts</div>'
|
|
777
|
+
: this.pict.parseTemplateByHash('Flow-Layout-List', { Rows: tmpLayoutRecords });
|
|
778
|
+
|
|
779
|
+
this.pict.ContentAssignment.assignContent(pContainer,
|
|
780
|
+
'<div class="pict-flow-popup-layout-save-section">'
|
|
781
|
+
+ '<div id="' + tmpSaveRowID + '" class="pict-flow-popup-layout-save"'
|
|
782
|
+
+ ' onclick="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._showLayoutSaveInput(\'' + tmpSaveRowID + '\', \'' + tmpSaveInputRowID + '\', \'' + tmpSaveInputID + '\')">'
|
|
783
|
+
+ '<span class="pict-flow-popup-layout-save-icon">' + tmpSaveIconHTML + '</span>'
|
|
784
|
+
+ '<span>Save Current Layout</span>'
|
|
785
|
+
+ '</div>'
|
|
786
|
+
+ '<div id="' + tmpSaveInputRowID + '" class="pict-flow-popup-layout-save-input-row" style="display:none;">'
|
|
787
|
+
+ '<input id="' + tmpSaveInputID + '" class="pict-flow-popup-layout-save-input" type="text" placeholder="Layout name..."'
|
|
788
|
+
+ ' onclick="event.stopPropagation()"'
|
|
789
|
+
+ ' onkeydown="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleLayoutSaveKey(event, \'' + tmpSaveInputID + '\', \'' + tmpSaveRowID + '\', \'' + tmpSaveInputRowID + '\')" />'
|
|
790
|
+
+ '<button class="pict-flow-popup-layout-save-confirm" title="Save"'
|
|
791
|
+
+ ' onclick="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._confirmLayoutSave(\'' + tmpSaveInputID + '\')">'
|
|
792
|
+
+ tmpSaveIconHTML + '</button>'
|
|
793
|
+
+ '</div>'
|
|
794
|
+
+ '</div>'
|
|
795
|
+
+ '<div class="pict-flow-popup-divider"></div>'
|
|
796
|
+
+ '<div id="' + tmpListID + '">' + tmpListHTML + '</div>');
|
|
797
|
+
}
|
|
804
798
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
799
|
+
/**
|
|
800
|
+
* Inline handler — reveal the layout-name input and focus it.
|
|
801
|
+
*/
|
|
802
|
+
_showLayoutSaveInput(pSaveRowID, pInputRowID, pInputID)
|
|
803
|
+
{
|
|
804
|
+
let tmpRow = document.getElementById(pSaveRowID);
|
|
805
|
+
let tmpInputRow = document.getElementById(pInputRowID);
|
|
806
|
+
let tmpInput = document.getElementById(pInputID);
|
|
807
|
+
if (tmpRow) tmpRow.style.display = 'none';
|
|
808
|
+
if (tmpInputRow) tmpInputRow.style.display = '';
|
|
809
|
+
if (tmpInput)
|
|
810
|
+
{
|
|
811
|
+
tmpInput.value = '';
|
|
812
|
+
setTimeout(() => { tmpInput.focus(); }, 50);
|
|
810
813
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
let tmpSaveText = document.createElement('span');
|
|
814
|
-
tmpSaveText.textContent = 'Save Current Layout';
|
|
815
|
-
tmpSaveRow.appendChild(tmpSaveText);
|
|
816
|
-
|
|
817
|
-
// Click "Save Current Layout" to reveal the input row
|
|
818
|
-
tmpSaveRow.addEventListener('click', () =>
|
|
819
|
-
{
|
|
820
|
-
tmpSaveRow.style.display = 'none';
|
|
821
|
-
tmpSaveInputRow.style.display = '';
|
|
822
|
-
tmpSaveInput.value = '';
|
|
823
|
-
setTimeout(() => { tmpSaveInput.focus(); }, 50);
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
// Confirm save via button click
|
|
827
|
-
let tmpDoSave = () =>
|
|
828
|
-
{
|
|
829
|
-
let tmpName = tmpSaveInput.value.trim();
|
|
830
|
-
if (tmpName === '') return;
|
|
831
|
-
this._FlowView._LayoutProvider.saveLayout(tmpName);
|
|
832
|
-
// Refresh the popup content
|
|
833
|
-
while (pContainer.firstChild)
|
|
834
|
-
{
|
|
835
|
-
pContainer.removeChild(pContainer.firstChild);
|
|
836
|
-
}
|
|
837
|
-
this._buildLayoutPopup(pContainer);
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
tmpSaveConfirmBtn.addEventListener('click', tmpDoSave);
|
|
841
|
-
|
|
842
|
-
// Confirm save via Enter key
|
|
843
|
-
tmpSaveInput.addEventListener('keydown', (pEvent) =>
|
|
844
|
-
{
|
|
845
|
-
if (pEvent.key === 'Enter')
|
|
846
|
-
{
|
|
847
|
-
pEvent.preventDefault();
|
|
848
|
-
tmpDoSave();
|
|
849
|
-
}
|
|
850
|
-
else if (pEvent.key === 'Escape')
|
|
851
|
-
{
|
|
852
|
-
// Cancel — hide input, show the save row again
|
|
853
|
-
tmpSaveInputRow.style.display = 'none';
|
|
854
|
-
tmpSaveRow.style.display = '';
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
// Prevent clicks inside the input from closing the popup
|
|
859
|
-
tmpSaveInput.addEventListener('click', (pEvent) =>
|
|
860
|
-
{
|
|
861
|
-
pEvent.stopPropagation();
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
tmpSaveSection.appendChild(tmpSaveRow);
|
|
865
|
-
tmpSaveSection.appendChild(tmpSaveInputRow);
|
|
866
|
-
pContainer.appendChild(tmpSaveSection);
|
|
867
|
-
|
|
868
|
-
// Divider
|
|
869
|
-
let tmpDivider = document.createElement('div');
|
|
870
|
-
tmpDivider.className = 'pict-flow-popup-divider';
|
|
871
|
-
pContainer.appendChild(tmpDivider);
|
|
814
|
+
}
|
|
872
815
|
|
|
873
|
-
|
|
874
|
-
|
|
816
|
+
/**
|
|
817
|
+
* Inline handler — Enter saves, Escape cancels.
|
|
818
|
+
*/
|
|
819
|
+
_handleLayoutSaveKey(pEvent, pInputID, pSaveRowID, pInputRowID)
|
|
820
|
+
{
|
|
821
|
+
if (pEvent.key === 'Enter')
|
|
875
822
|
{
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
tmpEmpty.textContent = 'No saved layouts';
|
|
879
|
-
pContainer.appendChild(tmpEmpty);
|
|
880
|
-
return;
|
|
823
|
+
pEvent.preventDefault();
|
|
824
|
+
this._confirmLayoutSave(pInputID);
|
|
881
825
|
}
|
|
882
|
-
|
|
883
|
-
let tmpLayouts = this._FlowView._LayoutProvider.getLayouts();
|
|
884
|
-
|
|
885
|
-
if (tmpLayouts.length === 0)
|
|
826
|
+
else if (pEvent.key === 'Escape')
|
|
886
827
|
{
|
|
887
|
-
let
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
return;
|
|
828
|
+
let tmpRow = document.getElementById(pSaveRowID);
|
|
829
|
+
let tmpInputRow = document.getElementById(pInputRowID);
|
|
830
|
+
if (tmpInputRow) tmpInputRow.style.display = 'none';
|
|
831
|
+
if (tmpRow) tmpRow.style.display = '';
|
|
892
832
|
}
|
|
833
|
+
}
|
|
893
834
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
835
|
+
/**
|
|
836
|
+
* Inline handler — save the layout under the entered name and rebuild
|
|
837
|
+
* the popup so the new entry appears.
|
|
838
|
+
*/
|
|
839
|
+
_confirmLayoutSave(pInputID)
|
|
840
|
+
{
|
|
841
|
+
let tmpInput = document.getElementById(pInputID);
|
|
842
|
+
if (!tmpInput) return;
|
|
843
|
+
let tmpName = (tmpInput.value || '').trim();
|
|
844
|
+
if (tmpName === '') return;
|
|
845
|
+
this._FlowView._LayoutProvider.saveLayout(tmpName);
|
|
905
846
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (tmpIconProvider)
|
|
911
|
-
{
|
|
912
|
-
tmpDeleteBtn.innerHTML = tmpIconProvider.getIconSVGMarkup('trash', 12);
|
|
913
|
-
}
|
|
914
|
-
else
|
|
915
|
-
{
|
|
916
|
-
tmpDeleteBtn.textContent = '×';
|
|
917
|
-
}
|
|
918
|
-
tmpRow.appendChild(tmpDeleteBtn);
|
|
847
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
848
|
+
let tmpPopupEl = document.getElementById(`Flow-Toolbar-Popup-${tmpFlowViewIdentifier}`);
|
|
849
|
+
if (tmpPopupEl) this._buildLayoutPopup(tmpPopupEl);
|
|
850
|
+
}
|
|
919
851
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
});
|
|
852
|
+
/**
|
|
853
|
+
* Inline handler — restore layout when the row (not its delete button)
|
|
854
|
+
* is clicked.
|
|
855
|
+
*/
|
|
856
|
+
_restoreLayoutFromPopup(pLayoutHash, pEvent)
|
|
857
|
+
{
|
|
858
|
+
if (pEvent && pEvent.target && pEvent.target.closest('.pict-flow-popup-layout-delete')) return;
|
|
859
|
+
this._FlowView._LayoutProvider.restoreLayout(pLayoutHash);
|
|
860
|
+
this._closePopup();
|
|
861
|
+
}
|
|
931
862
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
{
|
|
940
|
-
pContainer.removeChild(pContainer.firstChild);
|
|
941
|
-
}
|
|
942
|
-
this._buildLayoutPopup(pContainer);
|
|
943
|
-
});
|
|
863
|
+
/**
|
|
864
|
+
* Inline handler — delete a saved layout and refresh the popup.
|
|
865
|
+
*/
|
|
866
|
+
_deleteLayoutFromPopup(pLayoutHash, pEvent)
|
|
867
|
+
{
|
|
868
|
+
if (pEvent && typeof pEvent.stopPropagation === 'function') pEvent.stopPropagation();
|
|
869
|
+
this._FlowView._LayoutProvider.deleteLayout(pLayoutHash);
|
|
944
870
|
|
|
945
|
-
|
|
946
|
-
}
|
|
871
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
872
|
+
let tmpPopupEl = document.getElementById(`Flow-Toolbar-Popup-${tmpFlowViewIdentifier}`);
|
|
873
|
+
if (tmpPopupEl) this._buildLayoutPopup(tmpPopupEl);
|
|
947
874
|
}
|
|
948
875
|
|
|
949
876
|
// ── Layout Algorithm Popup ────────────────────────────────────────────
|
|
950
877
|
|
|
951
878
|
/**
|
|
952
|
-
* Build the Layout Algorithm popup
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
879
|
+
* Build the Layout Algorithm popup using templates with inline handlers.
|
|
880
|
+
* The popup has three logical sections (algorithm chooser, edge-theme
|
|
881
|
+
* chooser, auto-apply toggle + apply button). Each control's behavior
|
|
882
|
+
* lives on the toolbar as a helper method called from inline handlers.
|
|
883
|
+
*
|
|
884
|
+
* Iterations (algorithm options, edge themes) are template-driven via
|
|
885
|
+
* AppData arrays.
|
|
956
886
|
*
|
|
957
887
|
* @param {HTMLElement} pContainer
|
|
958
888
|
*/
|
|
@@ -960,251 +890,202 @@ class PictViewFlowToolbar extends libPictView
|
|
|
960
890
|
{
|
|
961
891
|
if (!this._FlowView || !this._FlowView._LayoutService) return;
|
|
962
892
|
|
|
893
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
963
894
|
let tmpLayoutService = this._FlowView._LayoutService;
|
|
964
895
|
let tmpCurrentSettings = this._FlowView.getLayoutAlgorithm();
|
|
965
896
|
let tmpAlgoDescriptor = tmpLayoutService.getAlgorithm(tmpCurrentSettings.Algorithm);
|
|
897
|
+
let tmpEdgeSettings = this._FlowView.getEdgeTheme();
|
|
898
|
+
let tmpResolvedEdge = tmpEdgeSettings.Theme;
|
|
899
|
+
let tmpExplicitEdgeOverride = tmpEdgeSettings.Override;
|
|
900
|
+
let tmpDefaultThemeName = (tmpAlgoDescriptor && tmpAlgoDescriptor.DefaultEdgeTheme) || 'Bezier';
|
|
901
|
+
let tmpIconProvider = this._FlowView ? this._FlowView._IconProvider : null;
|
|
902
|
+
let tmpSettingsIconHTML = tmpIconProvider ? tmpIconProvider.getIconSVGMarkup('settings', 13) : '⚙';
|
|
966
903
|
|
|
967
|
-
//
|
|
968
|
-
let
|
|
969
|
-
tmpAlgoSection.className = 'pict-flow-popup-settings-section pict-flow-popup-layout-algorithm-row';
|
|
970
|
-
|
|
971
|
-
let tmpAlgoLabel = document.createElement('label');
|
|
972
|
-
tmpAlgoLabel.className = 'pict-flow-popup-settings-label';
|
|
973
|
-
tmpAlgoLabel.textContent = 'Algorithm';
|
|
974
|
-
tmpAlgoSection.appendChild(tmpAlgoLabel);
|
|
975
|
-
|
|
976
|
-
let tmpAlgoControls = document.createElement('div');
|
|
977
|
-
tmpAlgoControls.className = 'pict-flow-popup-layout-algorithm-controls';
|
|
978
|
-
|
|
979
|
-
let tmpAlgoSelect = document.createElement('select');
|
|
980
|
-
tmpAlgoSelect.className = 'pict-flow-popup-settings-select pict-flow-popup-layout-algorithm-select';
|
|
981
|
-
tmpAlgoSelect.setAttribute('data-layout-control', 'algorithm');
|
|
982
|
-
|
|
983
|
-
let tmpAlgorithms = tmpLayoutService.listAlgorithms();
|
|
984
|
-
for (let i = 0; i < tmpAlgorithms.length; i++)
|
|
904
|
+
// Build option records for the dropdowns
|
|
905
|
+
let tmpAlgoOptions = tmpLayoutService.listAlgorithms().map((pAlgo) => (
|
|
985
906
|
{
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
{
|
|
991
|
-
tmpOption.selected = true;
|
|
992
|
-
}
|
|
993
|
-
tmpAlgoSelect.appendChild(tmpOption);
|
|
994
|
-
}
|
|
907
|
+
Value: pAlgo.Name,
|
|
908
|
+
Label: pAlgo.Label || pAlgo.Name,
|
|
909
|
+
SelectedAttr: (pAlgo.Name === tmpCurrentSettings.Algorithm) ? ' selected="selected"' : ''
|
|
910
|
+
}));
|
|
995
911
|
|
|
996
|
-
|
|
912
|
+
let tmpEdgeOptionRecords = [];
|
|
913
|
+
tmpEdgeOptionRecords.push(
|
|
997
914
|
{
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
this._FlowView.setLayoutAlgorithm(tmpName, tmpDefaults);
|
|
1002
|
-
// Refresh the popup so the parameter form reflects the new algorithm
|
|
1003
|
-
while (pContainer.firstChild)
|
|
1004
|
-
{
|
|
1005
|
-
pContainer.removeChild(pContainer.firstChild);
|
|
1006
|
-
}
|
|
1007
|
-
this._buildLayoutAlgorithmPopup(pContainer);
|
|
915
|
+
Value: '__inherit__',
|
|
916
|
+
Label: `Inherit from layout (${tmpDefaultThemeName})`,
|
|
917
|
+
SelectedAttr: !tmpExplicitEdgeOverride ? ' selected="selected"' : ''
|
|
1008
918
|
});
|
|
1009
|
-
|
|
919
|
+
let tmpEdgeThemes = tmpLayoutService.listEdgeThemes();
|
|
920
|
+
for (let i = 0; i < tmpEdgeThemes.length; i++)
|
|
921
|
+
{
|
|
922
|
+
tmpEdgeOptionRecords.push(
|
|
923
|
+
{
|
|
924
|
+
Value: tmpEdgeThemes[i].Name,
|
|
925
|
+
Label: tmpEdgeThemes[i].Label || tmpEdgeThemes[i].Name,
|
|
926
|
+
SelectedAttr: (tmpEdgeThemes[i].Name === tmpExplicitEdgeOverride) ? ' selected="selected"' : ''
|
|
927
|
+
});
|
|
928
|
+
}
|
|
1010
929
|
|
|
1011
|
-
|
|
930
|
+
let tmpAlgoOptionsHTML = this.pict.parseTemplateByHash('Flow-Layout-OptionList', { Options: tmpAlgoOptions });
|
|
931
|
+
let tmpEdgeOptionsHTML = this.pict.parseTemplateByHash('Flow-Layout-OptionList', { Options: tmpEdgeOptionRecords });
|
|
1012
932
|
|
|
1013
|
-
// Collapse toggle for the parameter form. Only meaningful when
|
|
1014
|
-
// the algorithm actually has parameters (Custom doesn't).
|
|
1015
933
|
let tmpHasParameters = !!(tmpAlgoDescriptor && (
|
|
1016
934
|
(tmpAlgoDescriptor.ParameterManifest && tmpAlgoDescriptor.ParameterManifest.Descriptors) ||
|
|
1017
935
|
(tmpAlgoDescriptor.ParameterSchema && Object.keys(tmpAlgoDescriptor.ParameterSchema).length > 0)
|
|
1018
936
|
));
|
|
1019
|
-
let tmpFormToggle = null;
|
|
1020
|
-
if (tmpHasParameters)
|
|
1021
|
-
{
|
|
1022
|
-
tmpFormToggle = document.createElement('button');
|
|
1023
|
-
tmpFormToggle.type = 'button';
|
|
1024
|
-
tmpFormToggle.className = 'pict-flow-popup-collapse-toggle';
|
|
1025
|
-
tmpFormToggle.title = this._LayoutFormExpanded ? 'Hide parameters' : 'Show parameters';
|
|
1026
|
-
tmpFormToggle.setAttribute('aria-expanded', this._LayoutFormExpanded ? 'true' : 'false');
|
|
1027
|
-
let tmpIconProvider = this._FlowView ? this._FlowView._IconProvider : null;
|
|
1028
|
-
tmpFormToggle.innerHTML = tmpIconProvider
|
|
1029
|
-
? tmpIconProvider.getIconSVGMarkup('settings', 13)
|
|
1030
|
-
: '⚙';
|
|
1031
|
-
tmpAlgoControls.appendChild(tmpFormToggle);
|
|
1032
|
-
}
|
|
1033
937
|
|
|
1034
|
-
|
|
1035
|
-
|
|
938
|
+
let tmpAlgoDescription = (tmpAlgoDescriptor && tmpAlgoDescriptor.Description)
|
|
939
|
+
? '<div class="pict-flow-popup-control-description">' + tmpAlgoDescriptor.Description + '</div>'
|
|
940
|
+
: '';
|
|
1036
941
|
|
|
1037
|
-
|
|
1038
|
-
if (
|
|
942
|
+
let tmpFormToggleHTML = '';
|
|
943
|
+
if (tmpHasParameters)
|
|
1039
944
|
{
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
945
|
+
tmpFormToggleHTML = '<button type="button" class="pict-flow-popup-collapse-toggle"'
|
|
946
|
+
+ ' aria-expanded="' + (this._LayoutFormExpanded ? 'true' : 'false') + '"'
|
|
947
|
+
+ ' title="' + (this._LayoutFormExpanded ? 'Hide parameters' : 'Show parameters') + '"'
|
|
948
|
+
+ ' onclick="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._toggleLayoutFormCollapsed(this)">'
|
|
949
|
+
+ tmpSettingsIconHTML
|
|
950
|
+
+ '</button>';
|
|
1044
951
|
}
|
|
1045
952
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
this._buildLayoutParameterFormSection(pContainer, tmpAlgoDescriptor, tmpCurrentSettings);
|
|
953
|
+
let tmpAutoApplyID = `Flow-Toolbar-AutoApply-${tmpFlowViewIdentifier}`;
|
|
954
|
+
let tmpApplyDisabled = (tmpCurrentSettings.Algorithm === 'Custom') ? ' disabled' : '';
|
|
955
|
+
let tmpApplyTitle = (tmpCurrentSettings.Algorithm === 'Custom') ? ' title="Custom does not auto-position nodes"' : '';
|
|
1050
956
|
|
|
1051
|
-
|
|
1052
|
-
if (
|
|
957
|
+
let tmpEdgeDescriptionHTML = '';
|
|
958
|
+
if (tmpResolvedEdge)
|
|
1053
959
|
{
|
|
1054
|
-
|
|
960
|
+
let tmpResolvedDescriptor = tmpLayoutService.getEdgeTheme(tmpResolvedEdge);
|
|
961
|
+
if (tmpResolvedDescriptor && tmpResolvedDescriptor.Description)
|
|
1055
962
|
{
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
tmpFormToggle.setAttribute('aria-expanded', this._LayoutFormExpanded ? 'true' : 'false');
|
|
1059
|
-
tmpFormToggle.title = this._LayoutFormExpanded ? 'Hide parameters' : 'Show parameters';
|
|
1060
|
-
let tmpHost = this._LayoutFormHostID ? document.getElementById(this._LayoutFormHostID) : null;
|
|
1061
|
-
if (tmpHost)
|
|
1062
|
-
{
|
|
1063
|
-
tmpHost.setAttribute('data-collapsed', this._LayoutFormExpanded ? 'false' : 'true');
|
|
1064
|
-
}
|
|
1065
|
-
});
|
|
963
|
+
tmpEdgeDescriptionHTML = '<div class="pict-flow-popup-control-description">' + tmpResolvedDescriptor.Description + '</div>';
|
|
964
|
+
}
|
|
1066
965
|
}
|
|
1067
966
|
|
|
1068
|
-
//
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
967
|
+
// Place a host div for the parameter form; mount the form into it
|
|
968
|
+
// after the popup is in the DOM (the metacontroller resolves
|
|
969
|
+
// destinations via document.querySelector).
|
|
970
|
+
let tmpParamFormHostID = `Flow-Toolbar-LayoutParamFormHost-${tmpFlowViewIdentifier}`;
|
|
971
|
+
|
|
972
|
+
this.pict.ContentAssignment.assignContent(pContainer,
|
|
973
|
+
'<div class="pict-flow-popup-settings-section pict-flow-popup-layout-algorithm-row">'
|
|
974
|
+
+ '<label class="pict-flow-popup-settings-label">Algorithm</label>'
|
|
975
|
+
+ '<div class="pict-flow-popup-layout-algorithm-controls">'
|
|
976
|
+
+ '<select class="pict-flow-popup-settings-select pict-flow-popup-layout-algorithm-select" data-layout-control="algorithm"'
|
|
977
|
+
+ ' onclick="event.stopPropagation()"'
|
|
978
|
+
+ ' onchange="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleLayoutAlgoChange(this.value)">'
|
|
979
|
+
+ tmpAlgoOptionsHTML
|
|
980
|
+
+ '</select>'
|
|
981
|
+
+ tmpFormToggleHTML
|
|
982
|
+
+ '</div>'
|
|
983
|
+
+ '</div>'
|
|
984
|
+
+ tmpAlgoDescription
|
|
985
|
+
+ '<div id="' + tmpParamFormHostID + '"></div>'
|
|
986
|
+
+ '<div class="pict-flow-popup-divider"></div>'
|
|
987
|
+
+ '<div class="pict-flow-popup-settings-section">'
|
|
988
|
+
+ '<label class="pict-flow-popup-settings-label">Edge theme</label>'
|
|
989
|
+
+ '<select class="pict-flow-popup-settings-select" data-layout-control="edge-theme"'
|
|
990
|
+
+ ' onclick="event.stopPropagation()"'
|
|
991
|
+
+ ' onchange="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleEdgeThemeChange(this.value)">'
|
|
992
|
+
+ tmpEdgeOptionsHTML
|
|
993
|
+
+ '</select>'
|
|
994
|
+
+ '</div>'
|
|
995
|
+
+ tmpEdgeDescriptionHTML
|
|
996
|
+
+ '<div class="pict-flow-popup-divider"></div>'
|
|
997
|
+
+ '<div class="pict-flow-popup-settings-section" style="display:flex;align-items:center;gap:8px;">'
|
|
998
|
+
+ '<input type="checkbox" id="' + tmpAutoApplyID + '"' + (tmpCurrentSettings.AutoApply ? ' checked="checked"' : '') + ''
|
|
999
|
+
+ ' onclick="event.stopPropagation()"'
|
|
1000
|
+
+ ' onchange="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleAutoApplyChange(this.checked)" />'
|
|
1001
|
+
+ '<label class="pict-flow-popup-settings-label" for="' + tmpAutoApplyID + '" style="cursor:pointer;">Auto-apply on changes</label>'
|
|
1002
|
+
+ '</div>'
|
|
1003
|
+
+ '<div class="pict-flow-popup-divider"></div>'
|
|
1004
|
+
+ '<div class="pict-flow-popup-settings-section" style="padding:4px 8px;">'
|
|
1005
|
+
+ '<button class="pict-flow-popup-layout-save-confirm" style="width:100%;padding:6px;"' + tmpApplyDisabled + tmpApplyTitle
|
|
1006
|
+
+ ' onclick="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleApplyLayoutNow()">Apply Now</button>'
|
|
1007
|
+
+ '</div>');
|
|
1008
|
+
|
|
1009
|
+
// Mount the parameter form (handles its own DOM building /
|
|
1010
|
+
// metacontroller). Use pContainer.querySelector — the popup may
|
|
1011
|
+
// not be in the document yet, so document.getElementById would miss.
|
|
1012
|
+
let tmpFormHostEl = pContainer.querySelector('#' + tmpParamFormHostID);
|
|
1013
|
+
if (tmpFormHostEl)
|
|
1014
|
+
{
|
|
1015
|
+
this._buildLayoutParameterFormSection(tmpFormHostEl, tmpAlgoDescriptor, tmpCurrentSettings);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1084
1018
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1019
|
+
/**
|
|
1020
|
+
* Inline handler — switch layout algorithm, refresh popup.
|
|
1021
|
+
*/
|
|
1022
|
+
_handleLayoutAlgoChange(pName)
|
|
1023
|
+
{
|
|
1024
|
+
if (!this._FlowView || !this._FlowView._LayoutService) return;
|
|
1025
|
+
let tmpAlgo = this._FlowView._LayoutService.getAlgorithm(pName);
|
|
1026
|
+
let tmpDefaults = (tmpAlgo && tmpAlgo.DefaultParameters)
|
|
1027
|
+
? JSON.parse(JSON.stringify(tmpAlgo.DefaultParameters)) : {};
|
|
1028
|
+
this._FlowView.setLayoutAlgorithm(pName, tmpDefaults);
|
|
1088
1029
|
|
|
1089
|
-
|
|
1090
|
-
let
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
tmpInheritOpt.textContent = `Inherit from layout (${tmpDefaultThemeName})`;
|
|
1094
|
-
if (!tmpExplicitEdgeOverride) tmpInheritOpt.selected = true;
|
|
1095
|
-
tmpEdgeSelect.appendChild(tmpInheritOpt);
|
|
1030
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
1031
|
+
let tmpPopupEl = document.getElementById(`Flow-Toolbar-Popup-${tmpFlowViewIdentifier}`);
|
|
1032
|
+
if (tmpPopupEl) this._buildLayoutAlgorithmPopup(tmpPopupEl);
|
|
1033
|
+
}
|
|
1096
1034
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1035
|
+
/**
|
|
1036
|
+
* Inline handler — switch edge theme, refresh popup so descriptions
|
|
1037
|
+
* track the new theme.
|
|
1038
|
+
*/
|
|
1039
|
+
_handleEdgeThemeChange(pValue)
|
|
1040
|
+
{
|
|
1041
|
+
if (!this._FlowView) return;
|
|
1042
|
+
if (pValue === '__inherit__')
|
|
1099
1043
|
{
|
|
1100
|
-
|
|
1101
|
-
tmpOpt.value = tmpEdgeThemes[i].Name;
|
|
1102
|
-
tmpOpt.textContent = tmpEdgeThemes[i].Label || tmpEdgeThemes[i].Name;
|
|
1103
|
-
if (tmpEdgeThemes[i].Name === tmpExplicitEdgeOverride)
|
|
1104
|
-
{
|
|
1105
|
-
tmpOpt.selected = true;
|
|
1106
|
-
}
|
|
1107
|
-
tmpEdgeSelect.appendChild(tmpOpt);
|
|
1044
|
+
this._FlowView.setEdgeTheme(null);
|
|
1108
1045
|
}
|
|
1109
|
-
|
|
1110
|
-
tmpEdgeSelect.addEventListener('change', () =>
|
|
1111
|
-
{
|
|
1112
|
-
let tmpVal = tmpEdgeSelect.value;
|
|
1113
|
-
if (tmpVal === '__inherit__')
|
|
1114
|
-
{
|
|
1115
|
-
this._FlowView.setEdgeTheme(null);
|
|
1116
|
-
}
|
|
1117
|
-
else
|
|
1118
|
-
{
|
|
1119
|
-
this._FlowView.setEdgeTheme(tmpVal);
|
|
1120
|
-
}
|
|
1121
|
-
// Rebuild the popup so the description and any theme-specific
|
|
1122
|
-
// controls reflect the new theme.
|
|
1123
|
-
while (pContainer.firstChild)
|
|
1124
|
-
{
|
|
1125
|
-
pContainer.removeChild(pContainer.firstChild);
|
|
1126
|
-
}
|
|
1127
|
-
this._buildLayoutAlgorithmPopup(pContainer);
|
|
1128
|
-
});
|
|
1129
|
-
tmpEdgeSelect.addEventListener('click', (pEvent) => { pEvent.stopPropagation(); });
|
|
1130
|
-
|
|
1131
|
-
tmpEdgeSection.appendChild(tmpEdgeSelect);
|
|
1132
|
-
pContainer.appendChild(tmpEdgeSection);
|
|
1133
|
-
|
|
1134
|
-
// Edge theme description (uses the *resolved* theme so users see
|
|
1135
|
-
// what's actually rendering, regardless of inherit/explicit choice)
|
|
1136
|
-
if (tmpResolvedEdge)
|
|
1046
|
+
else
|
|
1137
1047
|
{
|
|
1138
|
-
|
|
1139
|
-
if (tmpResolvedDescriptor && tmpResolvedDescriptor.Description)
|
|
1140
|
-
{
|
|
1141
|
-
let tmpEdgeDesc = document.createElement('div');
|
|
1142
|
-
tmpEdgeDesc.className = 'pict-flow-popup-control-description';
|
|
1143
|
-
tmpEdgeDesc.textContent = tmpResolvedDescriptor.Description;
|
|
1144
|
-
pContainer.appendChild(tmpEdgeDesc);
|
|
1145
|
-
}
|
|
1048
|
+
this._FlowView.setEdgeTheme(pValue);
|
|
1146
1049
|
}
|
|
1147
1050
|
|
|
1148
|
-
|
|
1149
|
-
let
|
|
1150
|
-
|
|
1151
|
-
|
|
1051
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
1052
|
+
let tmpPopupEl = document.getElementById(`Flow-Toolbar-Popup-${tmpFlowViewIdentifier}`);
|
|
1053
|
+
if (tmpPopupEl) this._buildLayoutAlgorithmPopup(tmpPopupEl);
|
|
1054
|
+
}
|
|
1152
1055
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1056
|
+
/**
|
|
1057
|
+
* Inline handler — toggle auto-apply.
|
|
1058
|
+
*/
|
|
1059
|
+
_handleAutoApplyChange(pChecked)
|
|
1060
|
+
{
|
|
1061
|
+
if (!this._FlowView) return;
|
|
1062
|
+
this._FlowView.setLayoutAutoApply(pChecked);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Inline handler — fire the layout (when not Custom).
|
|
1067
|
+
*/
|
|
1068
|
+
_handleApplyLayoutNow()
|
|
1069
|
+
{
|
|
1070
|
+
if (!this._FlowView) return;
|
|
1071
|
+
let tmpSettings = this._FlowView.getLayoutAlgorithm();
|
|
1072
|
+
if (tmpSettings.Algorithm === 'Custom') return;
|
|
1073
|
+
this._FlowView.applyCurrentLayout();
|
|
1074
|
+
}
|
|
1158
1075
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1076
|
+
/**
|
|
1077
|
+
* Inline handler — toggle the parameter-form collapse state.
|
|
1078
|
+
*/
|
|
1079
|
+
_toggleLayoutFormCollapsed(pToggleButton)
|
|
1080
|
+
{
|
|
1081
|
+
this._LayoutFormExpanded = !this._LayoutFormExpanded;
|
|
1082
|
+
pToggleButton.setAttribute('aria-expanded', this._LayoutFormExpanded ? 'true' : 'false');
|
|
1083
|
+
pToggleButton.title = this._LayoutFormExpanded ? 'Hide parameters' : 'Show parameters';
|
|
1084
|
+
let tmpHost = this._LayoutFormHostID ? document.getElementById(this._LayoutFormHostID) : null;
|
|
1085
|
+
if (tmpHost)
|
|
1164
1086
|
{
|
|
1165
|
-
|
|
1166
|
-
});
|
|
1167
|
-
tmpAutoApplyCheckbox.addEventListener('click', (pEvent) => { pEvent.stopPropagation(); });
|
|
1168
|
-
|
|
1169
|
-
let tmpAutoApplyLabel = document.createElement('label');
|
|
1170
|
-
tmpAutoApplyLabel.className = 'pict-flow-popup-settings-label';
|
|
1171
|
-
tmpAutoApplyLabel.setAttribute('for', tmpAutoApplyCheckbox.id);
|
|
1172
|
-
tmpAutoApplyLabel.textContent = 'Auto-apply on changes';
|
|
1173
|
-
tmpAutoApplyLabel.style.cursor = 'pointer';
|
|
1174
|
-
|
|
1175
|
-
tmpAutoApplySection.appendChild(tmpAutoApplyCheckbox);
|
|
1176
|
-
tmpAutoApplySection.appendChild(tmpAutoApplyLabel);
|
|
1177
|
-
pContainer.appendChild(tmpAutoApplySection);
|
|
1178
|
-
|
|
1179
|
-
// (Parameter form is rendered above, right under the algorithm row,
|
|
1180
|
-
// so it reads as part of the same "this is the algorithm" section.)
|
|
1181
|
-
|
|
1182
|
-
// ── Apply Now button ─────────────────────────────────
|
|
1183
|
-
let tmpApplyDivider = document.createElement('div');
|
|
1184
|
-
tmpApplyDivider.className = 'pict-flow-popup-divider';
|
|
1185
|
-
pContainer.appendChild(tmpApplyDivider);
|
|
1186
|
-
|
|
1187
|
-
let tmpApplyRow = document.createElement('div');
|
|
1188
|
-
tmpApplyRow.className = 'pict-flow-popup-settings-section';
|
|
1189
|
-
tmpApplyRow.style.padding = '4px 8px';
|
|
1190
|
-
|
|
1191
|
-
let tmpApplyBtn = document.createElement('button');
|
|
1192
|
-
tmpApplyBtn.className = 'pict-flow-popup-layout-save-confirm';
|
|
1193
|
-
tmpApplyBtn.textContent = 'Apply Now';
|
|
1194
|
-
tmpApplyBtn.style.width = '100%';
|
|
1195
|
-
tmpApplyBtn.style.padding = '6px';
|
|
1196
|
-
if (tmpCurrentSettings.Algorithm === 'Custom')
|
|
1197
|
-
{
|
|
1198
|
-
tmpApplyBtn.disabled = true;
|
|
1199
|
-
tmpApplyBtn.title = 'Custom does not auto-position nodes';
|
|
1087
|
+
tmpHost.setAttribute('data-collapsed', this._LayoutFormExpanded ? 'false' : 'true');
|
|
1200
1088
|
}
|
|
1201
|
-
tmpApplyBtn.addEventListener('click', () =>
|
|
1202
|
-
{
|
|
1203
|
-
if (tmpCurrentSettings.Algorithm === 'Custom') return;
|
|
1204
|
-
this._FlowView.applyCurrentLayout();
|
|
1205
|
-
});
|
|
1206
|
-
tmpApplyRow.appendChild(tmpApplyBtn);
|
|
1207
|
-
pContainer.appendChild(tmpApplyRow);
|
|
1208
1089
|
}
|
|
1209
1090
|
|
|
1210
1091
|
/**
|
|
@@ -1346,7 +1227,9 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1346
1227
|
tmpDivider.className = 'pict-flow-popup-divider';
|
|
1347
1228
|
pContainer.appendChild(tmpDivider);
|
|
1348
1229
|
|
|
1349
|
-
// Form host div — the metacontroller renders into here
|
|
1230
|
+
// Form host div — the metacontroller renders into here. Inline
|
|
1231
|
+
// onclick stops popup-close propagation; onchange/oninput drive the
|
|
1232
|
+
// _FlowData.LayoutParameters writeback (see _pushFormBack).
|
|
1350
1233
|
let tmpHostID = `Flow-Toolbar-LayoutForm-${tmpFlowViewIdentifier}`;
|
|
1351
1234
|
this._LayoutFormHostID = tmpHostID;
|
|
1352
1235
|
|
|
@@ -1354,8 +1237,11 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1354
1237
|
tmpHostDiv.id = tmpHostID;
|
|
1355
1238
|
tmpHostDiv.className = 'pict-flow-popup-layout-form-host';
|
|
1356
1239
|
tmpHostDiv.setAttribute('data-collapsed', this._LayoutFormExpanded ? 'false' : 'true');
|
|
1357
|
-
|
|
1358
|
-
tmpHostDiv.
|
|
1240
|
+
tmpHostDiv.setAttribute('onclick', 'event.stopPropagation()');
|
|
1241
|
+
tmpHostDiv.setAttribute('onchange',
|
|
1242
|
+
"_Pict.views['" + tmpFlowViewIdentifier + "']._ToolbarView._pushLayoutFormBack('" + pCurrentSettings.Algorithm + "')");
|
|
1243
|
+
tmpHostDiv.setAttribute('oninput',
|
|
1244
|
+
"_Pict.views['" + tmpFlowViewIdentifier + "']._ToolbarView._pushLayoutFormBack('" + pCurrentSettings.Algorithm + "')");
|
|
1359
1245
|
pContainer.appendChild(tmpHostDiv);
|
|
1360
1246
|
|
|
1361
1247
|
// Bind the layout parameters as the data source for the form.
|
|
@@ -1467,28 +1353,32 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1467
1353
|
return;
|
|
1468
1354
|
}
|
|
1469
1355
|
|
|
1470
|
-
//
|
|
1471
|
-
//
|
|
1472
|
-
//
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1356
|
+
// Form-change writeback runs through the host div's inline
|
|
1357
|
+
// onchange/oninput attributes (set above), which call
|
|
1358
|
+
// `_pushLayoutFormBack(algorithm)`.
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Inline handler for layout-parameter form changes. Pushes Informary's
|
|
1363
|
+
* write into AppData back into `_FlowData.LayoutParameters` and
|
|
1364
|
+
* re-applies the layout (when non-Custom). De-bounces via microtask so
|
|
1365
|
+
* multi-key edits collapse to a single layout pass.
|
|
1366
|
+
*
|
|
1367
|
+
* @param {string} pAlgorithm
|
|
1368
|
+
*/
|
|
1369
|
+
_pushLayoutFormBack(pAlgorithm)
|
|
1370
|
+
{
|
|
1371
|
+
if (this._LayoutFormPushScheduled) return;
|
|
1372
|
+
this._LayoutFormPushScheduled = true;
|
|
1373
|
+
Promise.resolve().then(() =>
|
|
1374
|
+
{
|
|
1375
|
+
this._LayoutFormPushScheduled = false;
|
|
1376
|
+
if (!this._FlowView) return;
|
|
1377
|
+
let tmpScopedRoot = this.pict.AppData[pAlgorithm] || {};
|
|
1378
|
+
let tmpEditorParams = (tmpScopedRoot.PictFlowLayoutEditor || {}).Parameters || {};
|
|
1379
|
+
let tmpMerged = Object.assign({}, this._FlowView.getLayoutAlgorithm().Parameters || {}, tmpEditorParams);
|
|
1380
|
+
this._FlowView.setLayoutAlgorithm(pAlgorithm, tmpMerged);
|
|
1381
|
+
});
|
|
1492
1382
|
}
|
|
1493
1383
|
|
|
1494
1384
|
/**
|
|
@@ -1503,6 +1393,9 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1503
1393
|
*/
|
|
1504
1394
|
_buildLayoutParamInput(pKey, pSchema, pCurrentValue)
|
|
1505
1395
|
{
|
|
1396
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
1397
|
+
let tmpViewPath = "_Pict.views['" + tmpFlowViewIdentifier + "']._ToolbarView";
|
|
1398
|
+
let tmpKeyEsc = pKey.replace(/'/g, "\\'");
|
|
1506
1399
|
let tmpInput;
|
|
1507
1400
|
|
|
1508
1401
|
if (pSchema.Type === 'boolean')
|
|
@@ -1510,10 +1403,8 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1510
1403
|
tmpInput = document.createElement('input');
|
|
1511
1404
|
tmpInput.type = 'checkbox';
|
|
1512
1405
|
tmpInput.checked = !!pCurrentValue;
|
|
1513
|
-
tmpInput.
|
|
1514
|
-
|
|
1515
|
-
this._updateLayoutParameter(pKey, tmpInput.checked);
|
|
1516
|
-
});
|
|
1406
|
+
tmpInput.setAttribute('onchange',
|
|
1407
|
+
tmpViewPath + "._updateLayoutParameter('" + tmpKeyEsc + "', this.checked)");
|
|
1517
1408
|
}
|
|
1518
1409
|
else if (pSchema.Type === 'enum' && Array.isArray(pSchema.Options))
|
|
1519
1410
|
{
|
|
@@ -1527,42 +1418,23 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1527
1418
|
if (pSchema.Options[i] === pCurrentValue) tmpOpt.selected = true;
|
|
1528
1419
|
tmpInput.appendChild(tmpOpt);
|
|
1529
1420
|
}
|
|
1530
|
-
tmpInput.
|
|
1531
|
-
|
|
1532
|
-
this._updateLayoutParameter(pKey, tmpInput.value);
|
|
1533
|
-
});
|
|
1421
|
+
tmpInput.setAttribute('onchange',
|
|
1422
|
+
tmpViewPath + "._updateLayoutParameter('" + tmpKeyEsc + "', this.value)");
|
|
1534
1423
|
}
|
|
1535
1424
|
else if (pSchema.Type === 'number' || pSchema.Type === 'Number' || pSchema.Type === 'PreciseNumber')
|
|
1536
1425
|
{
|
|
1537
1426
|
tmpInput = document.createElement('input');
|
|
1538
1427
|
tmpInput.type = 'number';
|
|
1539
1428
|
tmpInput.className = 'pict-flow-popup-settings-input';
|
|
1540
|
-
// PreciseNumber: arbitrary precision (string-stored, big.js / ExpressionParser-friendly).
|
|
1541
|
-
// Number: integer or simple float.
|
|
1542
1429
|
let tmpIsPrecise = (pSchema.Type === 'PreciseNumber');
|
|
1543
1430
|
if (typeof pSchema.Min === 'number') tmpInput.min = String(pSchema.Min);
|
|
1544
1431
|
if (typeof pSchema.Max === 'number') tmpInput.max = String(pSchema.Max);
|
|
1545
1432
|
tmpInput.step = tmpIsPrecise ? 'any' : '1';
|
|
1546
|
-
|
|
1547
|
-
tmpInput.value = tmpDisplayValue;
|
|
1433
|
+
tmpInput.value = (pCurrentValue == null) ? '' : String(pCurrentValue);
|
|
1548
1434
|
tmpInput.style.width = '90px';
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
if (tmpRaw === '') return;
|
|
1553
|
-
if (tmpIsPrecise)
|
|
1554
|
-
{
|
|
1555
|
-
// Preserve the user-entered string so big.js / ExpressionParser
|
|
1556
|
-
// can use it without float round-trip drift.
|
|
1557
|
-
this._updateLayoutParameter(pKey, tmpRaw);
|
|
1558
|
-
}
|
|
1559
|
-
else
|
|
1560
|
-
{
|
|
1561
|
-
let tmpNum = parseFloat(tmpRaw);
|
|
1562
|
-
if (isNaN(tmpNum)) return;
|
|
1563
|
-
this._updateLayoutParameter(pKey, tmpNum);
|
|
1564
|
-
}
|
|
1565
|
-
});
|
|
1435
|
+
let tmpPreciseFlag = tmpIsPrecise ? 'true' : 'false';
|
|
1436
|
+
tmpInput.setAttribute('onchange',
|
|
1437
|
+
tmpViewPath + "._updateLayoutNumberParameter('" + tmpKeyEsc + "', this.value, " + tmpPreciseFlag + ")");
|
|
1566
1438
|
}
|
|
1567
1439
|
else
|
|
1568
1440
|
{
|
|
@@ -1571,14 +1443,11 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1571
1443
|
tmpInput.className = 'pict-flow-popup-settings-input';
|
|
1572
1444
|
tmpInput.value = (pCurrentValue == null) ? '' : String(pCurrentValue);
|
|
1573
1445
|
tmpInput.style.width = '90px';
|
|
1574
|
-
tmpInput.
|
|
1575
|
-
|
|
1576
|
-
this._updateLayoutParameter(pKey, tmpInput.value);
|
|
1577
|
-
});
|
|
1446
|
+
tmpInput.setAttribute('onchange',
|
|
1447
|
+
tmpViewPath + "._updateLayoutParameter('" + tmpKeyEsc + "', this.value)");
|
|
1578
1448
|
}
|
|
1579
1449
|
|
|
1580
|
-
tmpInput.
|
|
1581
|
-
|
|
1450
|
+
tmpInput.setAttribute('onclick', 'event.stopPropagation()');
|
|
1582
1451
|
return tmpInput;
|
|
1583
1452
|
}
|
|
1584
1453
|
|
|
@@ -1597,108 +1466,114 @@ class PictViewFlowToolbar extends libPictView
|
|
|
1597
1466
|
this._FlowView.setLayoutAlgorithm(tmpSettings.Algorithm, tmpParams);
|
|
1598
1467
|
}
|
|
1599
1468
|
|
|
1469
|
+
/**
|
|
1470
|
+
* Inline-handler helper for numeric parameter inputs. Preserves the
|
|
1471
|
+
* raw string when PreciseNumber so big.js / ExpressionParser can use
|
|
1472
|
+
* it without float round-trip drift.
|
|
1473
|
+
*
|
|
1474
|
+
* @param {string} pKey
|
|
1475
|
+
* @param {string} pRawValue
|
|
1476
|
+
* @param {boolean} pIsPrecise
|
|
1477
|
+
*/
|
|
1478
|
+
_updateLayoutNumberParameter(pKey, pRawValue, pIsPrecise)
|
|
1479
|
+
{
|
|
1480
|
+
if (pRawValue === '') return;
|
|
1481
|
+
if (pIsPrecise)
|
|
1482
|
+
{
|
|
1483
|
+
this._updateLayoutParameter(pKey, pRawValue);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
let tmpNum = parseFloat(pRawValue);
|
|
1487
|
+
if (isNaN(tmpNum)) return;
|
|
1488
|
+
this._updateLayoutParameter(pKey, tmpNum);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1600
1491
|
// ── Settings Popup ───────────────────────────────────────────────────
|
|
1601
1492
|
|
|
1602
1493
|
/**
|
|
1603
|
-
* Build the Settings popup
|
|
1494
|
+
* Build the Settings popup using a template with inline handlers.
|
|
1495
|
+
*
|
|
1496
|
+
* Theme options are iterated via `{~TS:~}` from AppData. Sliders and
|
|
1497
|
+
* selects fire inline handlers that update the FlowView's theme state.
|
|
1498
|
+
*
|
|
1604
1499
|
* @param {HTMLElement} pContainer
|
|
1605
1500
|
*/
|
|
1606
1501
|
_buildSettingsPopup(pContainer)
|
|
1607
1502
|
{
|
|
1608
1503
|
if (!this._FlowView || !this._FlowView._ThemeProvider) return;
|
|
1609
1504
|
|
|
1505
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
1610
1506
|
let tmpThemeProvider = this._FlowView._ThemeProvider;
|
|
1611
|
-
|
|
1612
|
-
// Theme selector section
|
|
1613
|
-
let tmpThemeSection = document.createElement('div');
|
|
1614
|
-
tmpThemeSection.className = 'pict-flow-popup-settings-section';
|
|
1615
|
-
|
|
1616
|
-
let tmpThemeLabel = document.createElement('label');
|
|
1617
|
-
tmpThemeLabel.className = 'pict-flow-popup-settings-label';
|
|
1618
|
-
tmpThemeLabel.textContent = 'Theme';
|
|
1619
|
-
tmpThemeSection.appendChild(tmpThemeLabel);
|
|
1620
|
-
|
|
1621
|
-
let tmpThemeSelect = document.createElement('select');
|
|
1622
|
-
tmpThemeSelect.className = 'pict-flow-popup-settings-select';
|
|
1623
|
-
|
|
1624
1507
|
let tmpThemeKeys = tmpThemeProvider.getThemeKeys();
|
|
1625
1508
|
let tmpActiveKey = tmpThemeProvider.getActiveThemeKey();
|
|
1626
1509
|
|
|
1510
|
+
let tmpThemeOptions = [];
|
|
1627
1511
|
for (let i = 0; i < tmpThemeKeys.length; i++)
|
|
1628
1512
|
{
|
|
1629
|
-
let tmpOption = document.createElement('option');
|
|
1630
|
-
tmpOption.value = tmpThemeKeys[i];
|
|
1631
|
-
|
|
1632
1513
|
let tmpTheme = tmpThemeProvider._Themes[tmpThemeKeys[i]];
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
if (tmpThemeKeys[i] === tmpActiveKey)
|
|
1514
|
+
tmpThemeOptions.push(
|
|
1636
1515
|
{
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1516
|
+
Value: tmpThemeKeys[i],
|
|
1517
|
+
Label: tmpTheme.Label || tmpThemeKeys[i],
|
|
1518
|
+
SelectedAttr: (tmpThemeKeys[i] === tmpActiveKey) ? ' selected="selected"' : ''
|
|
1519
|
+
});
|
|
1640
1520
|
}
|
|
1641
1521
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
let tmpNoiseRow = document.createElement('div');
|
|
1671
|
-
tmpNoiseRow.className = 'pict-flow-popup-settings-slider-row';
|
|
1672
|
-
|
|
1673
|
-
let tmpNoiseSlider = document.createElement('input');
|
|
1674
|
-
tmpNoiseSlider.type = 'range';
|
|
1675
|
-
tmpNoiseSlider.className = 'pict-flow-popup-settings-slider';
|
|
1676
|
-
tmpNoiseSlider.min = '0';
|
|
1677
|
-
tmpNoiseSlider.max = '100';
|
|
1678
|
-
tmpNoiseSlider.value = String(Math.round(tmpThemeProvider.getNoiseLevel() * 100));
|
|
1679
|
-
|
|
1680
|
-
let tmpNoiseValue = document.createElement('span');
|
|
1681
|
-
tmpNoiseValue.className = 'pict-flow-popup-settings-slider-value';
|
|
1682
|
-
tmpNoiseValue.textContent = tmpNoiseSlider.value + '%';
|
|
1683
|
-
|
|
1684
|
-
tmpNoiseSlider.addEventListener('input', () =>
|
|
1685
|
-
{
|
|
1686
|
-
let tmpLevel = parseInt(tmpNoiseSlider.value, 10) / 100;
|
|
1687
|
-
tmpNoiseValue.textContent = tmpNoiseSlider.value + '%';
|
|
1688
|
-
this._FlowView.setNoiseLevel(tmpLevel);
|
|
1689
|
-
});
|
|
1522
|
+
let tmpThemeOptionsHTML = this.pict.parseTemplateByHash('Flow-Layout-OptionList', { Options: tmpThemeOptions });
|
|
1523
|
+
|
|
1524
|
+
let tmpNoiseLevel = Math.round(tmpThemeProvider.getNoiseLevel() * 100);
|
|
1525
|
+
let tmpActiveTheme = tmpThemeProvider.getActiveTheme();
|
|
1526
|
+
let tmpNoiseEnabled = !!(tmpActiveTheme && tmpActiveTheme.NoiseConfig && tmpActiveTheme.NoiseConfig.Enabled);
|
|
1527
|
+
let tmpNoiseDisplay = tmpNoiseEnabled ? '' : 'display:none;';
|
|
1528
|
+
|
|
1529
|
+
this.pict.ContentAssignment.assignContent(pContainer,
|
|
1530
|
+
'<div class="pict-flow-popup-settings-section">'
|
|
1531
|
+
+ '<label class="pict-flow-popup-settings-label">Theme</label>'
|
|
1532
|
+
+ '<select class="pict-flow-popup-settings-select"'
|
|
1533
|
+
+ ' onclick="event.stopPropagation()"'
|
|
1534
|
+
+ ' onchange="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleThemeSelectChange(this.value)">'
|
|
1535
|
+
+ tmpThemeOptionsHTML
|
|
1536
|
+
+ '</select>'
|
|
1537
|
+
+ '</div>'
|
|
1538
|
+
+ '<div class="pict-flow-popup-divider"></div>'
|
|
1539
|
+
+ '<div class="pict-flow-popup-settings-section pict-flow-popup-settings-noise" data-settings-type="noise" style="' + tmpNoiseDisplay + '">'
|
|
1540
|
+
+ '<label class="pict-flow-popup-settings-label">Noise</label>'
|
|
1541
|
+
+ '<div class="pict-flow-popup-settings-slider-row">'
|
|
1542
|
+
+ '<input type="range" class="pict-flow-popup-settings-slider" min="0" max="100" value="' + tmpNoiseLevel + '"'
|
|
1543
|
+
+ ' onclick="event.stopPropagation()"'
|
|
1544
|
+
+ ' onpointerdown="event.stopPropagation()"'
|
|
1545
|
+
+ ' oninput="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleNoiseSliderInput(this)" />'
|
|
1546
|
+
+ '<span class="pict-flow-popup-settings-slider-value">' + tmpNoiseLevel + '%</span>'
|
|
1547
|
+
+ '</div>'
|
|
1548
|
+
+ '</div>');
|
|
1549
|
+
}
|
|
1690
1550
|
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1551
|
+
/**
|
|
1552
|
+
* Inline handler — apply selected flow theme and refresh the noise
|
|
1553
|
+
* slider visibility (some themes don't expose noise).
|
|
1554
|
+
*/
|
|
1555
|
+
_handleThemeSelectChange(pThemeKey)
|
|
1556
|
+
{
|
|
1557
|
+
if (!this._FlowView) return;
|
|
1558
|
+
this._FlowView.setTheme(pThemeKey);
|
|
1694
1559
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1560
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
1561
|
+
let tmpPopupEl = document.getElementById(`Flow-Toolbar-Popup-${tmpFlowViewIdentifier}`);
|
|
1562
|
+
if (tmpPopupEl) this._refreshNoiseSlider(tmpPopupEl);
|
|
1563
|
+
}
|
|
1699
1564
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1565
|
+
/**
|
|
1566
|
+
* Inline handler — push slider position into the noise level and
|
|
1567
|
+
* update the percentage label adjacent to it.
|
|
1568
|
+
*/
|
|
1569
|
+
_handleNoiseSliderInput(pSlider)
|
|
1570
|
+
{
|
|
1571
|
+
let tmpRaw = parseInt(pSlider.value, 10);
|
|
1572
|
+
if (isNaN(tmpRaw)) return;
|
|
1573
|
+
let tmpLevel = tmpRaw / 100;
|
|
1574
|
+
let tmpValueLabel = pSlider.parentNode ? pSlider.parentNode.querySelector('.pict-flow-popup-settings-slider-value') : null;
|
|
1575
|
+
if (tmpValueLabel) tmpValueLabel.textContent = tmpRaw + '%';
|
|
1576
|
+
if (this._FlowView) this._FlowView.setNoiseLevel(tmpLevel);
|
|
1702
1577
|
}
|
|
1703
1578
|
|
|
1704
1579
|
/**
|