@flowfuse/node-red-dashboard 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +53 -0
- package/dist/css/app.d047b42b.css +1 -0
- package/dist/css/chunk-vendors.2378ce49.css +24 -0
- package/dist/fonts/materialdesignicons-webfont.3de8526e.woff +0 -0
- package/dist/fonts/materialdesignicons-webfont.477c6ab0.woff2 +0 -0
- package/dist/fonts/materialdesignicons-webfont.48a1ce0c.eot +0 -0
- package/dist/fonts/materialdesignicons-webfont.dfd403cf.ttf +0 -0
- package/dist/index.html +1 -0
- package/dist/js/app.854a8cd5.js +2 -0
- package/dist/js/app.854a8cd5.js.map +1 -0
- package/dist/js/chunk-vendors.174e8921.js +43 -0
- package/dist/js/chunk-vendors.174e8921.js.map +1 -0
- package/nodes/config/locales/en-US/ui_base.json +19 -0
- package/nodes/config/locales/en-US/ui_group.html +4 -0
- package/nodes/config/locales/en-US/ui_group.json +16 -0
- package/nodes/config/ui_base.html +807 -0
- package/nodes/config/ui_base.js +678 -0
- package/nodes/config/ui_group.html +55 -0
- package/nodes/config/ui_group.js +34 -0
- package/nodes/config/ui_page.html +84 -0
- package/nodes/config/ui_page.js +33 -0
- package/nodes/config/ui_theme.html +101 -0
- package/nodes/config/ui_theme.js +15 -0
- package/nodes/store/index.js +34 -0
- package/nodes/utils/index.js +35 -0
- package/nodes/widgets/locales/en-US/ui_button.html +7 -0
- package/nodes/widgets/locales/en-US/ui_button.json +24 -0
- package/nodes/widgets/locales/en-US/ui_chart.html +41 -0
- package/nodes/widgets/locales/en-US/ui_chart.json +17 -0
- package/nodes/widgets/locales/en-US/ui_dropdown.html +24 -0
- package/nodes/widgets/locales/en-US/ui_form.html +16 -0
- package/nodes/widgets/locales/en-US/ui_form.json +36 -0
- package/nodes/widgets/locales/en-US/ui_markdown.html +10 -0
- package/nodes/widgets/locales/en-US/ui_slider.html +9 -0
- package/nodes/widgets/locales/en-US/ui_switch.html +32 -0
- package/nodes/widgets/locales/en-US/ui_template.html +59 -0
- package/nodes/widgets/locales/en-US/ui_template.json +18 -0
- package/nodes/widgets/locales/en-US/ui_text.html +16 -0
- package/nodes/widgets/locales/en-US/ui_text_input.html +19 -0
- package/nodes/widgets/ui_button.html +146 -0
- package/nodes/widgets/ui_button.js +65 -0
- package/nodes/widgets/ui_chart.html +314 -0
- package/nodes/widgets/ui_chart.js +195 -0
- package/nodes/widgets/ui_dropdown.html +199 -0
- package/nodes/widgets/ui_dropdown.js +19 -0
- package/nodes/widgets/ui_form.html +368 -0
- package/nodes/widgets/ui_form.js +18 -0
- package/nodes/widgets/ui_markdown.html +134 -0
- package/nodes/widgets/ui_markdown.js +14 -0
- package/nodes/widgets/ui_notification.html +139 -0
- package/nodes/widgets/ui_notification.js +14 -0
- package/nodes/widgets/ui_radio_group.html +186 -0
- package/nodes/widgets/ui_radio_group.js +20 -0
- package/nodes/widgets/ui_slider.html +162 -0
- package/nodes/widgets/ui_slider.js +31 -0
- package/nodes/widgets/ui_switch.html +194 -0
- package/nodes/widgets/ui_switch.js +98 -0
- package/nodes/widgets/ui_table.html +149 -0
- package/nodes/widgets/ui_table.js +16 -0
- package/nodes/widgets/ui_template.html +283 -0
- package/nodes/widgets/ui_template.js +19 -0
- package/nodes/widgets/ui_text.html +358 -0
- package/nodes/widgets/ui_text.js +98 -0
- package/nodes/widgets/ui_text_input.html +141 -0
- package/nodes/widgets/ui_text_input.js +37 -0
- package/package.json +114 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.red-ui-editor {
|
|
3
|
+
--nrdb-node-light: rgb(160, 230, 236);
|
|
4
|
+
--nrdb-node-medium: rgb(90, 210, 220);
|
|
5
|
+
--nrdb-node-dark: rgb(39, 183, 195);
|
|
6
|
+
}
|
|
7
|
+
.red-ui-editor .form-row-flex {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 4px;
|
|
11
|
+
}
|
|
12
|
+
.red-ui-editor .form-row-flex input,
|
|
13
|
+
.red-ui-editor .form-row-flex label {
|
|
14
|
+
margin: 0;
|
|
15
|
+
width: auto;
|
|
16
|
+
}
|
|
17
|
+
#ff-node-red-dashboard {
|
|
18
|
+
--ff-grey-50: #F9FAFB;
|
|
19
|
+
--ff-grey-100: #F3F4F6;
|
|
20
|
+
--ff-grey-200: #E5E7EB;
|
|
21
|
+
position: absolute;
|
|
22
|
+
top: 1px;
|
|
23
|
+
bottom: 2px;
|
|
24
|
+
left: 1px;
|
|
25
|
+
right: 1px;
|
|
26
|
+
overflow-y: auto;
|
|
27
|
+
}
|
|
28
|
+
#ff-node-red-dashboard .red-ui-sidebar-header {
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
}
|
|
32
|
+
#ff-node-red-dashboard .red-ui-sidebar-header label {
|
|
33
|
+
margin-bottom: 0;
|
|
34
|
+
}
|
|
35
|
+
#ff-node-red-dashboard .red-ui-editableList-container {
|
|
36
|
+
padding: 0;
|
|
37
|
+
}
|
|
38
|
+
/* don't show border for nexted editable lists */
|
|
39
|
+
.red-ui-editableList-border .red-ui-editableList-border {
|
|
40
|
+
border: 0;
|
|
41
|
+
}
|
|
42
|
+
/* Dashboard 2.0 Sidebar */
|
|
43
|
+
.nrdb2-layout-order-editor {
|
|
44
|
+
padding: 8px 10px;
|
|
45
|
+
}
|
|
46
|
+
.nrdb2-layout-helptext {
|
|
47
|
+
padding: 0 0 9px;
|
|
48
|
+
font-style: italic;
|
|
49
|
+
color: #a2a2a2;
|
|
50
|
+
font-size: 8pt;
|
|
51
|
+
line-height: 12pt;
|
|
52
|
+
}
|
|
53
|
+
.nrdb2-layout-order-editor--pages {
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
align-items: center;
|
|
57
|
+
margin-bottom: 9px;
|
|
58
|
+
}
|
|
59
|
+
.nrdb2-sb-pages-list li {
|
|
60
|
+
padding: 0;
|
|
61
|
+
border-bottom: 0;
|
|
62
|
+
}
|
|
63
|
+
.nrdb2-sb-list-header {
|
|
64
|
+
display: flex;
|
|
65
|
+
gap: 6px;
|
|
66
|
+
align-items: center;
|
|
67
|
+
padding: 9px 6px;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
}
|
|
70
|
+
.nrdb2-sb-list-header.nrdb2-sb-pages-list-header {
|
|
71
|
+
border-top: 1px solid var(--ff-grey-200);
|
|
72
|
+
border-bottom: 1px solid var(--ff-grey-200);
|
|
73
|
+
}
|
|
74
|
+
.nrdb2-sb-list-header .nrdb2-sb-title {
|
|
75
|
+
text-overflow: ellipsis;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
}
|
|
79
|
+
.nrdb2-sb-list-header-button-group {
|
|
80
|
+
position: absolute;
|
|
81
|
+
right: 1rem;
|
|
82
|
+
}
|
|
83
|
+
.nrdb2-sb-list-header-button-group,
|
|
84
|
+
.nrdb2-sb-list-handle {
|
|
85
|
+
opacity: 0;
|
|
86
|
+
transition: 0.15s opacity;
|
|
87
|
+
}
|
|
88
|
+
.nrdb2-sb-list-header:hover {
|
|
89
|
+
background-color: var(--ff-grey-100);
|
|
90
|
+
}
|
|
91
|
+
.nrdb2-sb-list-header:hover .nrdb2-sb-list-handle,
|
|
92
|
+
.nrdb2-sb-list-header:hover .nrdb2-sb-list-header-button-group {
|
|
93
|
+
opacity: 1;
|
|
94
|
+
}
|
|
95
|
+
/* indent the groups */
|
|
96
|
+
.nrdb2-sb-groups-list-header .nrdb2-sb-list-chevron {
|
|
97
|
+
margin-left: 1.5rem;
|
|
98
|
+
}
|
|
99
|
+
/* indent the widgets */
|
|
100
|
+
.nrdb2-sb-widgets-list-header .nrdb2-sb-widget-icon {
|
|
101
|
+
margin-left: 3.5rem;
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
<script type="text/javascript">
|
|
107
|
+
(function () {
|
|
108
|
+
const sidebarContainer = '<div style="position: relative; height: 100%;"></div>'
|
|
109
|
+
const sidebarContentTemplate = $('<div id="ff-node-red-dashboard"></div>').appendTo(sidebarContainer)
|
|
110
|
+
|
|
111
|
+
// convert to i18 text
|
|
112
|
+
function c_ (x) {
|
|
113
|
+
return RED._('@flowforge/node-red-dashboard/ui-base:ui-base.' + x)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function hasProperty (obj, prop) {
|
|
117
|
+
return Object.prototype.hasOwnProperty.call(obj, prop)
|
|
118
|
+
}
|
|
119
|
+
function debounce (func, wait, immediate) {
|
|
120
|
+
let timeout
|
|
121
|
+
return function () {
|
|
122
|
+
const context = this; const args = arguments
|
|
123
|
+
const later = function () {
|
|
124
|
+
timeout = null
|
|
125
|
+
if (!immediate) func.apply(context, args)
|
|
126
|
+
}
|
|
127
|
+
const callNow = immediate && !timeout
|
|
128
|
+
clearTimeout(timeout)
|
|
129
|
+
timeout = setTimeout(later, wait)
|
|
130
|
+
if (callNow) func.apply(context, args)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
RED.nodes.registerType('ui-base', {
|
|
134
|
+
category: 'config',
|
|
135
|
+
defaults: {
|
|
136
|
+
name: {
|
|
137
|
+
value: 'UI Name',
|
|
138
|
+
required: true
|
|
139
|
+
},
|
|
140
|
+
path: {
|
|
141
|
+
value: '/dashboard',
|
|
142
|
+
required: true
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
label: function () {
|
|
146
|
+
return `${this.name} [${this.path}]` || 'UI Config'
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Add Custom Dashboard Side Menu
|
|
152
|
+
* */
|
|
153
|
+
|
|
154
|
+
const sidebar = $(sidebarContentTemplate)
|
|
155
|
+
|
|
156
|
+
function uiLink (name, path) {
|
|
157
|
+
const base = RED.settings.httpNodeRoot || '/'
|
|
158
|
+
const basePart = base.endsWith('/') ? base : `${base}/`
|
|
159
|
+
const dashPart = path.startsWith('/') ? path.slice(1) : path
|
|
160
|
+
const fullPath = `${basePart}${dashPart}`
|
|
161
|
+
return `<div class="red-ui-sidebar-header"><label>${name}</label><a id="open-dashboard" href="${fullPath}" target="nr-dashboard" class="editor-button editor-button-small nr-db-sb-list-header-button">Open Dashboard<i style="margin-left: 3px;" class="fa fa-external-link"></i></a></div>`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Add an editor to control the ordering of groups & widgets
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
function updateItemOrder (items, events) {
|
|
169
|
+
items.each((i, el) => {
|
|
170
|
+
const node = el.data('data')
|
|
171
|
+
const nodeBefore = RED.nodes.node(node.id)
|
|
172
|
+
if (node.order !== i + 1) {
|
|
173
|
+
const originalOrder = nodeBefore.order
|
|
174
|
+
const wasDirty = node.dirty
|
|
175
|
+
const wasChanged = node.changed
|
|
176
|
+
// update Node-RED node properties
|
|
177
|
+
node.order = i + 1 // start from 1, makes backup to SAFE_INT logic easier on frontend
|
|
178
|
+
node.dirty = true
|
|
179
|
+
node.changed = true
|
|
180
|
+
// generate a history event
|
|
181
|
+
const hev = {
|
|
182
|
+
t: 'edit',
|
|
183
|
+
node,
|
|
184
|
+
changes: {
|
|
185
|
+
order: originalOrder
|
|
186
|
+
},
|
|
187
|
+
dirty: wasDirty,
|
|
188
|
+
changed: wasChanged
|
|
189
|
+
}
|
|
190
|
+
events.push(hev)
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// toggle slide tab group content
|
|
196
|
+
const titleToggle = function (id, content, chevron) {
|
|
197
|
+
return function (evt) {
|
|
198
|
+
if (content.is(':visible')) {
|
|
199
|
+
content.slideUp()
|
|
200
|
+
chevron.css({ transform: 'rotate(-90deg)' })
|
|
201
|
+
content.addClass('nr-db-sb-collapsed')
|
|
202
|
+
} else {
|
|
203
|
+
content.slideDown()
|
|
204
|
+
chevron.css({ transform: '' })
|
|
205
|
+
content.removeClass('nr-db-sb-collapsed')
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Utility function to store events in NR history, trigger a redraw, and detect if a re-deploy is necessary
|
|
211
|
+
function recordEvents (events) {
|
|
212
|
+
if (events.length === 0) { return } // nothing to record
|
|
213
|
+
|
|
214
|
+
// note the state of the editor before pushing to history
|
|
215
|
+
const isDirty = RED.nodes.dirty()
|
|
216
|
+
if (RED._db2debug) { console.log('recordEvents', isDirty, events) }
|
|
217
|
+
|
|
218
|
+
// add our changes to NR history and trigger whether or not we need to redeploy
|
|
219
|
+
RED.history.push({
|
|
220
|
+
t: 'multi',
|
|
221
|
+
events,
|
|
222
|
+
dirty: isDirty
|
|
223
|
+
})
|
|
224
|
+
RED.nodes.dirty(true)
|
|
225
|
+
RED.view.redraw()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// watch for nodes changed, added, removed - use this to refresh the sidebar
|
|
229
|
+
const refreshLayoutEditorDebounced = debounce(refreshLayoutEditor, 300)
|
|
230
|
+
RED.events.on('nodes:change', function (event) {
|
|
231
|
+
if (RED._db2debug) { console.log('nodes:change', event) }
|
|
232
|
+
if (event.dirty && event.type && event.type.startsWith('ui-')) {
|
|
233
|
+
if (RED._db2debug) { console.log('nodes:change - this is a ui- node! queuing a call to refreshLayoutEditor') }
|
|
234
|
+
// debounce the call to refreshLayoutEditor as multiple events can be fired in quick succession
|
|
235
|
+
refreshLayoutEditorDebounced()
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
RED.events.on('nodes:add', function (event) {
|
|
239
|
+
if (RED._db2debug) { console.log('nodes:add', event) }
|
|
240
|
+
if (event.dirty && event.type && event.type.startsWith('ui-')) {
|
|
241
|
+
if (RED._db2debug) { console.log('nodes:add - this is a ui- node! queuing a call to refreshLayoutEditor') }
|
|
242
|
+
// debounce the call to refreshLayoutEditor as multiple events can be fired in quick succession
|
|
243
|
+
refreshLayoutEditorDebounced()
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
RED.events.on('nodes:remove', function (event) {
|
|
247
|
+
if (RED._db2debug) { console.log('nodes:remove', event) }
|
|
248
|
+
if (event.dirty && event.type && event.type.startsWith('ui-')) {
|
|
249
|
+
if (RED._db2debug) { console.log('nodes:remove - this is a ui- node! queuing a call to refreshLayoutEditor') }
|
|
250
|
+
// debounce the call to refreshLayoutEditor as multiple events can be fired in quick succession
|
|
251
|
+
refreshLayoutEditorDebounced()
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Add group of actions to the right-side of a row in the sidebar editable list.
|
|
257
|
+
* @param {Object} row - jQuery object to add this button group as a child element to
|
|
258
|
+
* @param {Object} row - The page/group/widget that these actions are bound to
|
|
259
|
+
*/
|
|
260
|
+
function addRowActions (row, item) {
|
|
261
|
+
const btnGroup = $('<div>', { class: 'nrdb2-sb-list-header-button-group', id: item.id }).appendTo(row)
|
|
262
|
+
const editButton = $('<a href="#" class="nr-db-sb-tab-edit-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> ' + c_('layout.edit') + '</a>').appendTo(btnGroup)
|
|
263
|
+
editButton.on('click', function (evt) {
|
|
264
|
+
if (item.type === 'ui-page' || item.type === 'ui-group') {
|
|
265
|
+
RED.editor.editConfig('', item.type, item.id)
|
|
266
|
+
} else {
|
|
267
|
+
RED.editor.edit(item)
|
|
268
|
+
}
|
|
269
|
+
evt.stopPropagation()
|
|
270
|
+
evt.preventDefault()
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Adds child list of groups for a given page
|
|
275
|
+
function addGroupOrderingList (pageId, container, groups, widgetsByGroup) {
|
|
276
|
+
// ordered list of groupss to live within a container (e.g. page list item)
|
|
277
|
+
const groupsOL = $('<ol>', { class: 'nrdb2-sb-group-list' }).appendTo(container).editableList({
|
|
278
|
+
sortable: '.nrdb2-sb-groups-list-header',
|
|
279
|
+
addButton: false,
|
|
280
|
+
height: 'auto',
|
|
281
|
+
connectWith: '.nrdb2-sb-group-list',
|
|
282
|
+
addItem: function (container, i, group) {
|
|
283
|
+
const titleRow = $('<div>', { class: 'nrdb2-sb-list-header nrdb2-sb-groups-list-header' }).appendTo(container)
|
|
284
|
+
$('<i class="nrdb2-sb-list-handle nrdb2-sb-group-list-handle fa fa-bars"></i>').appendTo(titleRow)
|
|
285
|
+
const chevron = $('<i class="fa fa-angle-down nrdb2-sb-list-chevron">', { style: 'width:10px;' }).appendTo(titleRow)
|
|
286
|
+
const groupicon = 'fa-table'
|
|
287
|
+
$('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-group-icon fa ' + groupicon }).appendTo(titleRow)
|
|
288
|
+
$('<span>', { class: 'nrdb2-sb-title' }).text(group.name || group.id).appendTo(titleRow)
|
|
289
|
+
|
|
290
|
+
addRowActions(titleRow, group)
|
|
291
|
+
|
|
292
|
+
// adds widgets within this group
|
|
293
|
+
const widgets = widgetsByGroup[group.id] || []
|
|
294
|
+
const widgetsList = $('<div>', { class: 'nrdb2-sb-widget-list-container' }).appendTo(container)
|
|
295
|
+
|
|
296
|
+
// add chevron/list toggle
|
|
297
|
+
titleRow.click(titleToggle(group.id, widgetsList, chevron))
|
|
298
|
+
|
|
299
|
+
addWidgetToList(group.id, widgetsList, widgets)
|
|
300
|
+
},
|
|
301
|
+
sortItems: function (items) {
|
|
302
|
+
// track any changes
|
|
303
|
+
const events = []
|
|
304
|
+
|
|
305
|
+
// check if we have any new widgets added to this list
|
|
306
|
+
items.each((i, el) => {
|
|
307
|
+
const widget = el.data('data')
|
|
308
|
+
if (widget.page !== pageId) {
|
|
309
|
+
const oldPageId = widget.page
|
|
310
|
+
widget.page = pageId
|
|
311
|
+
events.push({
|
|
312
|
+
t: 'edit',
|
|
313
|
+
node: widget,
|
|
314
|
+
changes: {
|
|
315
|
+
page: oldPageId
|
|
316
|
+
},
|
|
317
|
+
dirty: widget.changed,
|
|
318
|
+
changed: widget.dirty
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
updateItemOrder(items, events)
|
|
324
|
+
|
|
325
|
+
// add our changes to NR history and trigger whether or not we need to redeploy
|
|
326
|
+
recordEvents(events)
|
|
327
|
+
},
|
|
328
|
+
sort: function (a, b) {
|
|
329
|
+
return Number(a.order) - Number(b.order)
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
groups.forEach(function (group) {
|
|
334
|
+
if (RED._db2debug) { if (RED._db2debug) { console.log(group) } }
|
|
335
|
+
groupsOL.editableList('addItem', group)
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Adds list of widgets underneath a group
|
|
340
|
+
function addWidgetToList (groupId, container, widgets) {
|
|
341
|
+
// ordered list of groupss to live within a container (e.g. page list item)
|
|
342
|
+
const widgetsOL = $('<ol>', { class: 'nrdb2-sb-widget-list' }).appendTo(container).editableList({
|
|
343
|
+
sortable: '.nrdb2-sb-widgets-list-header',
|
|
344
|
+
addButton: false,
|
|
345
|
+
height: 'auto',
|
|
346
|
+
connectWith: '.nrdb2-sb-widget-list',
|
|
347
|
+
addItem: function (container, i, widget) {
|
|
348
|
+
const titleRow = $('<div>', { class: 'nrdb2-sb-list-header nrdb2-sb-widgets-list-header' }).appendTo(container)
|
|
349
|
+
$('<i class="nrdb2-sb-list-handle nrdb2-sb-widget-list-handle fa fa-bars"></i>').appendTo(titleRow)
|
|
350
|
+
|
|
351
|
+
const groupicon = 'fa-image'
|
|
352
|
+
$('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-widget-icon fa ' + groupicon }).appendTo(titleRow)
|
|
353
|
+
$('<span>', { class: 'nrdb2-sb-title' }).text(widget.name || widget.label || widget.type || widget.id).appendTo(titleRow)
|
|
354
|
+
|
|
355
|
+
addRowActions(titleRow, widget)
|
|
356
|
+
},
|
|
357
|
+
sortItems: function (items) {
|
|
358
|
+
// track any changes
|
|
359
|
+
const events = []
|
|
360
|
+
|
|
361
|
+
// check if we have any new widgets added to this list
|
|
362
|
+
items.each((i, el) => {
|
|
363
|
+
const widget = el.data('data')
|
|
364
|
+
if (widget.group !== groupId) {
|
|
365
|
+
const oldGroupId = widget.group
|
|
366
|
+
widget.group = groupId
|
|
367
|
+
events.push({
|
|
368
|
+
t: 'edit',
|
|
369
|
+
node: widget,
|
|
370
|
+
changes: {
|
|
371
|
+
group: oldGroupId
|
|
372
|
+
},
|
|
373
|
+
dirty: widget.dirty,
|
|
374
|
+
changed: widget.changed
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
updateItemOrder(items, events)
|
|
380
|
+
|
|
381
|
+
// add our changes to NR history and trigger whether or not we need to redeploy
|
|
382
|
+
recordEvents(events)
|
|
383
|
+
},
|
|
384
|
+
sort: function (a, b) {
|
|
385
|
+
return Number(a.order) - Number(b.order)
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
widgets.forEach(function (w) {
|
|
390
|
+
widgetsOL.editableList('addItem', w)
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// expand / collapse buttons
|
|
395
|
+
let layoutDisplayLevel = 2 // all open by default
|
|
396
|
+
const getGroupsInLayout = function () {
|
|
397
|
+
const content = $('.nrdb2-layout-order-editor > .red-ui-editableList .nrdb2-sb-widget-list-container')
|
|
398
|
+
return {
|
|
399
|
+
content,
|
|
400
|
+
chevrons: content.parent().find('div.nrdb2-sb-list-header > .nrdb2-sb-list-chevron')
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const getPagesInLayout = function () {
|
|
404
|
+
const content = $('.nrdb2-layout-order-editor > .red-ui-editableList .nrdb2-sb-group-list-container')
|
|
405
|
+
return {
|
|
406
|
+
content,
|
|
407
|
+
chevrons: content.parent().find('div.nrdb2-sb-pages-list-header > .nrdb2-sb-list-chevron')
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const collapseLayoutItems = function ({ chevrons, content }) {
|
|
411
|
+
chevrons.css({ transform: 'rotate(-90deg)' })
|
|
412
|
+
content.slideUp()
|
|
413
|
+
content.addClass('nr-db-sb-collapsed')
|
|
414
|
+
}
|
|
415
|
+
const expandLayoutItems = function ({ chevrons, content }) {
|
|
416
|
+
chevrons.css({ transform: '' })
|
|
417
|
+
content.slideDown()
|
|
418
|
+
content.removeClass('nr-db-sb-collapsed')
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Update the visibility of the layout editor expandable lists
|
|
422
|
+
* @param {0|1|2} level - 0 = collapse all, 1 = expand pages (groups collapsed), 2 = expand pages and groups (to expose widgets)
|
|
423
|
+
*/
|
|
424
|
+
const updateLayoutVisibility = function (level) {
|
|
425
|
+
if (RED._db2debug) { console.log('updateLayoutVisibility', level) }
|
|
426
|
+
if (level === 2) {
|
|
427
|
+
expandLayoutItems(getGroupsInLayout())
|
|
428
|
+
expandLayoutItems(getPagesInLayout())
|
|
429
|
+
} else if (level === 1) {
|
|
430
|
+
expandLayoutItems(getPagesInLayout())
|
|
431
|
+
collapseLayoutItems(getGroupsInLayout())
|
|
432
|
+
} else {
|
|
433
|
+
collapseLayoutItems(getGroupsInLayout())
|
|
434
|
+
collapseLayoutItems(getPagesInLayout())
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function buildLayoutOrderEditor () {
|
|
439
|
+
// layout/order editor
|
|
440
|
+
const divTabs = $('.nrdb2-layout-order-editor').length ? $('.nrdb2-layout-order-editor') : $('<div>', { class: 'nrdb2-layout-order-editor' }).appendTo(sidebar)
|
|
441
|
+
|
|
442
|
+
// section header - Pages
|
|
443
|
+
const pagesHeader = $('<div>', { class: 'nrdb2-layout-order-editor--pages' }).appendTo(divTabs)
|
|
444
|
+
$('<b>').html(c_('layout.pages')).appendTo(pagesHeader)
|
|
445
|
+
|
|
446
|
+
// toggle "all" buttons
|
|
447
|
+
const buttonGroup = $('<div>', { class: 'nrdb2-sb-list-button-group' }).appendTo(pagesHeader)
|
|
448
|
+
|
|
449
|
+
const buttonCollapse = $('<a href="#" class="editor-button editor-button-small nrdb2-sb-list-header-button"><i class="fa fa-angle-double-up"></i></a>')
|
|
450
|
+
.click(function (evt) {
|
|
451
|
+
evt.preventDefault()
|
|
452
|
+
if (--layoutDisplayLevel < 0) { layoutDisplayLevel = 0 }
|
|
453
|
+
updateLayoutVisibility(layoutDisplayLevel)
|
|
454
|
+
})
|
|
455
|
+
.appendTo(buttonGroup)
|
|
456
|
+
RED.popover.tooltip(buttonCollapse, c_('layout.collapse'))
|
|
457
|
+
|
|
458
|
+
// expand button
|
|
459
|
+
const buttonExpand = $('<a href="#" class="editor-button editor-button-small nrdb2-sb-list-header-button"><i class="fa fa-angle-double-down"></i></a>')
|
|
460
|
+
.click(function (evt) {
|
|
461
|
+
if (++layoutDisplayLevel > 2) { layoutDisplayLevel = 2 }
|
|
462
|
+
updateLayoutVisibility(layoutDisplayLevel)
|
|
463
|
+
}).appendTo(buttonGroup)
|
|
464
|
+
.appendTo(buttonGroup)
|
|
465
|
+
RED.popover.tooltip(buttonExpand, c_('layout.expand'))
|
|
466
|
+
|
|
467
|
+
divTabs.append('<div class="nrdb2-layout-helptext">Here you can re-order and move your widgets, groups and pages.</div>')
|
|
468
|
+
|
|
469
|
+
const pages = {}
|
|
470
|
+
const groupsByPage = {}
|
|
471
|
+
const widgetsByGroup = {}
|
|
472
|
+
|
|
473
|
+
// get all pages & all groups
|
|
474
|
+
RED.nodes.eachConfig(function (n) {
|
|
475
|
+
if (n.type === 'ui-page' && !!n.ui) {
|
|
476
|
+
pages[n.id] = n
|
|
477
|
+
}
|
|
478
|
+
if (n.type === 'ui-group' && !!n.page) {
|
|
479
|
+
if (!groupsByPage[n.page]) {
|
|
480
|
+
groupsByPage[n.page] = []
|
|
481
|
+
}
|
|
482
|
+
groupsByPage[n.page].push(n)
|
|
483
|
+
}
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
// get all widgets
|
|
487
|
+
RED.nodes.eachNode(function (n) {
|
|
488
|
+
if (/^ui-/.test(n.type) && n.group) {
|
|
489
|
+
if (!widgetsByGroup[n.group]) {
|
|
490
|
+
widgetsByGroup[n.group] = []
|
|
491
|
+
}
|
|
492
|
+
widgetsByGroup[n.group].push(n)
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
const pagesOL = $('<ol>', { class: 'nrdb2-sb-pages-list' }).appendTo(divTabs).editableList({
|
|
497
|
+
sortable: '.nrdb2-sb-pages-list-header',
|
|
498
|
+
addButton: false,
|
|
499
|
+
addItem: function (container, i, page) {
|
|
500
|
+
container.addClass('nrdb2-sb-pages-list-item')
|
|
501
|
+
|
|
502
|
+
const titleRow = $('<div>', { class: 'nrdb2-sb-list-header nrdb2-sb-pages-list-header' }).appendTo(container)
|
|
503
|
+
$('<i class="nrdb2-sb-list-handle nrdb2-sb-page-list-handle fa fa-bars"></i>').appendTo(titleRow)
|
|
504
|
+
const chevron = $('<i class="fa fa-angle-down nrdb2-sb-list-chevron">', { style: 'width:10px;' }).appendTo(titleRow)
|
|
505
|
+
const tabicon = 'fa-object-group'
|
|
506
|
+
$('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-tab-icon fa ' + tabicon }).appendTo(titleRow)
|
|
507
|
+
$('<span>', { class: 'nrdb2-sb-title' }).text(page.name || page.id).appendTo(titleRow)
|
|
508
|
+
|
|
509
|
+
// page - actions
|
|
510
|
+
addRowActions(titleRow, page)
|
|
511
|
+
|
|
512
|
+
// adds groups within this page
|
|
513
|
+
const groups = groupsByPage[page.id] || []
|
|
514
|
+
const groupsList = $('<div>', { class: 'nrdb2-sb-group-list-container' }).appendTo(container)
|
|
515
|
+
|
|
516
|
+
titleRow.click(titleToggle(page.id, groupsList, chevron))
|
|
517
|
+
|
|
518
|
+
addGroupOrderingList(page.id, groupsList, groups, widgetsByGroup)
|
|
519
|
+
},
|
|
520
|
+
sortItems: function (items) {
|
|
521
|
+
// track any changes
|
|
522
|
+
const events = []
|
|
523
|
+
updateItemOrder(items, events)
|
|
524
|
+
|
|
525
|
+
// add our changes to NR history and trigger whether or not we need to redeploy
|
|
526
|
+
recordEvents(events)
|
|
527
|
+
},
|
|
528
|
+
sort: function (a, b) {
|
|
529
|
+
return Number(a.order) - Number(b.order)
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
Object.values(groupsByPage).sort((a, b) => a.order - b.order).forEach(function (groups) {
|
|
534
|
+
if (RED._db2debug) { console.log(groups) }
|
|
535
|
+
const page = pages[groups[0].page]
|
|
536
|
+
if (page) {
|
|
537
|
+
pagesOL.editableList('addItem', page)
|
|
538
|
+
}
|
|
539
|
+
// groups.forEach(() => {
|
|
540
|
+
|
|
541
|
+
// })
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
// call updateLayoutVisibility to sync display level
|
|
545
|
+
updateLayoutVisibility(layoutDisplayLevel)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function refreshLayoutEditor () {
|
|
549
|
+
if (RED._db2debug) { console.log('refreshLayoutEditor called') }
|
|
550
|
+
const layoutOrderDiv = $('.nrdb2-layout-order-editor')
|
|
551
|
+
// empty the list if any items exist
|
|
552
|
+
if (layoutOrderDiv.length) {
|
|
553
|
+
// TODO: create a lookup of which items are expanded / collapsed
|
|
554
|
+
layoutOrderDiv.empty()
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// now rebuild
|
|
558
|
+
buildLayoutOrderEditor()
|
|
559
|
+
|
|
560
|
+
// finally, restore previous state of expanded/collapsed items
|
|
561
|
+
// TODO: expand/collapse any items that were expanded before
|
|
562
|
+
// for now, we will just re-sync the display level
|
|
563
|
+
updateLayoutVisibility(layoutDisplayLevel)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
RED.sidebar.addTab({
|
|
567
|
+
id: 'dashboard-2.0',
|
|
568
|
+
label: 'Dashboard 2.0',
|
|
569
|
+
name: 'Dashboard 2.0',
|
|
570
|
+
content: sidebar,
|
|
571
|
+
closeable: true,
|
|
572
|
+
pinned: true,
|
|
573
|
+
disableOnEdit: true,
|
|
574
|
+
iconClass: 'fa fa-bar-chart',
|
|
575
|
+
action: '@flowforge/node-red-dashboard:show-dashboard-2.0-tab',
|
|
576
|
+
onchange: function () {
|
|
577
|
+
sidebar.empty()
|
|
578
|
+
RED.nodes.eachConfig(function (n) {
|
|
579
|
+
if (n.type === 'ui-base') {
|
|
580
|
+
sidebar.append(uiLink(n.name, n.path))
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
// add layout editor
|
|
585
|
+
buildLayoutOrderEditor()
|
|
586
|
+
}
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
RED.actions.add('@flowforge/node-red-dashboard:show-dashboard-2.0-tab', function () {
|
|
590
|
+
RED.sidebar.show('flowforge-nr-tools')
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* jQuery widget to provide a selector for the sizing (width & height) of a widget & group
|
|
595
|
+
*/
|
|
596
|
+
$.widget('nodereddashboard.elementSizer', {
|
|
597
|
+
_create: function () {
|
|
598
|
+
// convert to i18 text
|
|
599
|
+
function c_ (x) {
|
|
600
|
+
return RED._(`@flowforge/node-red-dashboard/ui-base:ui-base.${x}`)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const thisWidget = this
|
|
604
|
+
let gridWidth = 6
|
|
605
|
+
const width = parseInt($(this.options.width).val() || 0)
|
|
606
|
+
const height = parseInt(hasProperty(this.options, 'height') ? $(this.options.height).val() : '1') || 0
|
|
607
|
+
const hasAuto = (!hasProperty(this.options, 'auto') || this.options.auto)
|
|
608
|
+
|
|
609
|
+
this.element.css({
|
|
610
|
+
minWidth: this.element.height() + 4
|
|
611
|
+
})
|
|
612
|
+
const autoText = c_('auto')
|
|
613
|
+
const sizeLabel = (width === 0 && height === 0) ? autoText : width + (hasProperty(this.options, 'height') ? ' x ' + height : '')
|
|
614
|
+
this.element.text(sizeLabel).on('mousedown', function (evt) {
|
|
615
|
+
evt.stopPropagation()
|
|
616
|
+
evt.preventDefault()
|
|
617
|
+
|
|
618
|
+
const width = parseInt($(thisWidget.options.width).val() || 0)
|
|
619
|
+
const height = parseInt(hasProperty(thisWidget.options, 'height') ? $(thisWidget.options.height).val() : '1') || 0
|
|
620
|
+
let maxWidth = 0
|
|
621
|
+
let maxHeight
|
|
622
|
+
let fixedWidth = false
|
|
623
|
+
const fixedHeight = false
|
|
624
|
+
const group = $(thisWidget.options.group).val()
|
|
625
|
+
if (group) {
|
|
626
|
+
const groupNode = RED.nodes.node(group)
|
|
627
|
+
if (groupNode) {
|
|
628
|
+
gridWidth = Math.max(6, groupNode.width, +width)
|
|
629
|
+
maxWidth = groupNode.width || gridWidth
|
|
630
|
+
fixedWidth = true
|
|
631
|
+
}
|
|
632
|
+
maxHeight = Math.max(6, +height + 1)
|
|
633
|
+
} else {
|
|
634
|
+
gridWidth = Math.max(12, +width)
|
|
635
|
+
maxWidth = gridWidth
|
|
636
|
+
maxHeight = height + 1
|
|
637
|
+
// fixedHeight = false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const pos = $(this).offset()
|
|
641
|
+
const container = $('<div>').css({
|
|
642
|
+
position: 'absolute',
|
|
643
|
+
background: 'var(--red-ui-secondary-background, white)',
|
|
644
|
+
padding: '5px 10px 10px 10px',
|
|
645
|
+
border: '1px solid var(--red-ui-primary-border-color, grey)',
|
|
646
|
+
zIndex: '20',
|
|
647
|
+
borderRadius: '4px',
|
|
648
|
+
display: 'none'
|
|
649
|
+
}).appendTo(document.body)
|
|
650
|
+
|
|
651
|
+
let closeTimer
|
|
652
|
+
container.on('mouseleave', function (evt) {
|
|
653
|
+
closeTimer = setTimeout(function () {
|
|
654
|
+
container.fadeOut(200, function () { $(this).remove() })
|
|
655
|
+
}, 100)
|
|
656
|
+
})
|
|
657
|
+
container.on('mouseenter', function () {
|
|
658
|
+
clearTimeout(closeTimer)
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
const label = $('<div>').css({
|
|
662
|
+
fontSize: '13px',
|
|
663
|
+
color: 'var(--red-ui-tertiary-text-color, #aaa)',
|
|
664
|
+
float: 'left',
|
|
665
|
+
paddingTop: '1px'
|
|
666
|
+
}).appendTo(container).text((width === 0 && height === 0) ? autoText : (width + (hasProperty(thisWidget.options, 'height') ? ' x ' + height : '')))
|
|
667
|
+
label.hover(function () {
|
|
668
|
+
$(this).css('text-decoration', 'underline')
|
|
669
|
+
}, function () {
|
|
670
|
+
$(this).css('text-decoration', 'none')
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
label.click(function (e) {
|
|
674
|
+
const group = $(thisWidget.options.group).val()
|
|
675
|
+
let groupNode = null
|
|
676
|
+
if (group) {
|
|
677
|
+
groupNode = RED.nodes.node(group)
|
|
678
|
+
if (groupNode === null) {
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
$(thisWidget).elementSizerByNum({
|
|
683
|
+
width: thisWidget.options.width,
|
|
684
|
+
height: thisWidget.options.height,
|
|
685
|
+
groupNode,
|
|
686
|
+
pos,
|
|
687
|
+
label: thisWidget.element,
|
|
688
|
+
has_height: hasProperty(thisWidget.options, 'height')
|
|
689
|
+
})
|
|
690
|
+
closeTimer = setTimeout(function () {
|
|
691
|
+
container.fadeOut(200, function () {
|
|
692
|
+
$(this).remove()
|
|
693
|
+
})
|
|
694
|
+
}, 100)
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
const buttonRow = $('<div>', { style: 'text-align:right; height:25px;' }).appendTo(container)
|
|
698
|
+
|
|
699
|
+
if (hasAuto) {
|
|
700
|
+
$('<a>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-bottom:5px' })
|
|
701
|
+
.text(autoText)
|
|
702
|
+
.appendTo(buttonRow)
|
|
703
|
+
.on('mouseup', function (evt) {
|
|
704
|
+
thisWidget.element.text(autoText)
|
|
705
|
+
$(thisWidget.options.width).val(0).change()
|
|
706
|
+
$(thisWidget.options.height).val(0).change()
|
|
707
|
+
evt.preventDefault()
|
|
708
|
+
container.fadeOut(200, function () { $(this).remove() })
|
|
709
|
+
})
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const cellBorder = '1px dashed var(--red-ui-secondary-border-color, lightGray)'
|
|
713
|
+
const cellBorderExisting = '1px solid gray'
|
|
714
|
+
const cellBorderHighlight = '1px dashed var(--red-ui-primary-border-color, black)'
|
|
715
|
+
const rows = []
|
|
716
|
+
const cells = []
|
|
717
|
+
|
|
718
|
+
function addRow (i) {
|
|
719
|
+
const row = $('<div>').css({ padding: 0, margin: 0, height: '25px', 'box-sizing': 'border-box' }).appendTo(container)
|
|
720
|
+
rows.push(row)
|
|
721
|
+
cells.push([])
|
|
722
|
+
for (let j = 0; j < gridWidth; j++) {
|
|
723
|
+
addCell(i, j)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function addCell (i, j) {
|
|
728
|
+
const row = rows[i]
|
|
729
|
+
const cell = $('<div>').css({
|
|
730
|
+
display: 'inline-block',
|
|
731
|
+
width: '25px',
|
|
732
|
+
height: '25px',
|
|
733
|
+
borderRight: (j === (width - 1) && i < height) ? cellBorderExisting : cellBorder,
|
|
734
|
+
borderBottom: (i === (height - 1) && j < width) ? cellBorderExisting : cellBorder,
|
|
735
|
+
boxSizing: 'border-box',
|
|
736
|
+
cursor: 'pointer',
|
|
737
|
+
background: (j < maxWidth) ? 'var(--red-ui-secondary-background, #fff)' : 'var(--red-ui-node-background-placeholder, #eee)'
|
|
738
|
+
}).appendTo(row)
|
|
739
|
+
cells[i].push(cell)
|
|
740
|
+
if (j === 0) {
|
|
741
|
+
cell.css({ borderLeft: ((i <= height - 1) ? cellBorderExisting : cellBorder) })
|
|
742
|
+
}
|
|
743
|
+
if (i === 0) {
|
|
744
|
+
cell.css({ borderTop: ((j <= width - 1) ? cellBorderExisting : cellBorder) })
|
|
745
|
+
}
|
|
746
|
+
if (j < maxWidth) {
|
|
747
|
+
cell.data('w', j)
|
|
748
|
+
cell.data('h', i)
|
|
749
|
+
cell.on('mouseup', function () {
|
|
750
|
+
thisWidget.element.text(($(this).data('w') + 1) + (hasProperty(thisWidget.options, 'height') ? ' x ' + ($(this).data('h') + 1) : ''))
|
|
751
|
+
$(thisWidget.options.width).val($(this).data('w') + 1).change()
|
|
752
|
+
$(thisWidget.options.height).val($(this).data('h') + 1).change()
|
|
753
|
+
container.fadeOut(200, function () { $(this).remove() })
|
|
754
|
+
})
|
|
755
|
+
cell.on('mouseover', function () {
|
|
756
|
+
const w = $(this).data('w')
|
|
757
|
+
const h = $(this).data('h')
|
|
758
|
+
label.text((w + 1) + (hasProperty(thisWidget.options, 'height') ? ' x ' + (h + 1) : ''))
|
|
759
|
+
for (let y = 0; y < maxHeight; y++) {
|
|
760
|
+
for (let x = 0; x < maxWidth; x++) {
|
|
761
|
+
cells[y][x].css({
|
|
762
|
+
background: (y <= h && x <= w) ? 'var(--red-ui-secondary-background-selected, #ddd)' : 'var(--red-ui-secondary-background, #fff)',
|
|
763
|
+
borderLeft: (x === 0 && y <= h) ? cellBorderHighlight : (x === 0) ? ((y <= height - 1) ? cellBorderExisting : cellBorder) : '',
|
|
764
|
+
borderTop: (y === 0 && x <= w) ? cellBorderHighlight : (y === 0) ? ((x <= width - 1) ? cellBorderExisting : cellBorder) : '',
|
|
765
|
+
borderRight: (x === w && y <= h) ? cellBorderHighlight : ((x === width - 1 && y <= height - 1) ? cellBorderExisting : cellBorder),
|
|
766
|
+
borderBottom: (y === h && x <= w) ? cellBorderHighlight : ((y === height - 1 && x <= width - 1) ? cellBorderExisting : cellBorder)
|
|
767
|
+
})
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (!fixedHeight && h === maxHeight - 1) {
|
|
771
|
+
addRow(maxHeight++)
|
|
772
|
+
}
|
|
773
|
+
if (!fixedWidth && w === maxWidth - 1) {
|
|
774
|
+
maxWidth++
|
|
775
|
+
gridWidth++
|
|
776
|
+
for (let r = 0; r < maxHeight; r++) {
|
|
777
|
+
addCell(r, maxWidth - 1)
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
for (let i = 0; i < maxHeight; i++) {
|
|
784
|
+
addRow(i)
|
|
785
|
+
}
|
|
786
|
+
container.css({
|
|
787
|
+
top: (pos.top) + 'px',
|
|
788
|
+
left: (pos.left) + 'px'
|
|
789
|
+
})
|
|
790
|
+
container.fadeIn(200)
|
|
791
|
+
})
|
|
792
|
+
}
|
|
793
|
+
})
|
|
794
|
+
})()
|
|
795
|
+
</script>
|
|
796
|
+
|
|
797
|
+
<script type="text/html" data-template-name="ui-base">
|
|
798
|
+
<div class="form-row">
|
|
799
|
+
<label for="node-config-input-name"><i class="fa fa-bookmark"></i> Name</label>
|
|
800
|
+
<input type="text" id="node-config-input-name">
|
|
801
|
+
</div>
|
|
802
|
+
<div class="form-row">
|
|
803
|
+
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> Path</label>
|
|
804
|
+
<input type="text" id="node-config-input-path" disabled>
|
|
805
|
+
<span style="display: block; margin-left: 105px; margin-top: 0px; font-style: italic; color: #bbb; font-size: 8pt;">This option is currently disabled and still in-development.</span>
|
|
806
|
+
</div>
|
|
807
|
+
</script>
|