@flowfuse/node-red-dashboard 1.16.1-ee4508a-202409161000.0 → 1.17.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.
Files changed (77) hide show
  1. package/dist/apple-touch-icon.png +0 -0
  2. package/dist/assets/Tableau10-B-NsZVaP.js +1 -0
  3. package/dist/assets/array-BKyUJesY.js +1 -0
  4. package/dist/assets/blockDiagram-9f4a6865-BY3wYgfY.js +118 -0
  5. package/dist/assets/c4Diagram-ae766693-B-AG7NIy.js +10 -0
  6. package/dist/assets/channel-C_v5mHlo.js +1 -0
  7. package/dist/assets/classDiagram-fb54d2a0-Cve0-MS1.js +2 -0
  8. package/dist/assets/classDiagram-v2-a2b738ad-DikSiPRc.js +2 -0
  9. package/dist/assets/clone-BXaIEimD.js +1 -0
  10. package/dist/assets/createText-ca0c5216-DvPZ4lSa.js +7 -0
  11. package/dist/assets/edges-066a5561-DMO7edq0.js +4 -0
  12. package/dist/assets/erDiagram-09d1c15f-C8g8w-fe.js +51 -0
  13. package/dist/assets/flowDb-c1833063-ByiZML3Q.js +10 -0
  14. package/dist/assets/flowDiagram-b222e15a-B7kVs9sh.js +4 -0
  15. package/dist/assets/flowDiagram-v2-13329dc7-BQ7oM3Wh.js +1 -0
  16. package/dist/assets/flowchart-elk-definition-ae0efee6-CAWlGS57.js +139 -0
  17. package/dist/assets/ganttDiagram-b62c793e-cVXtk4xr.js +257 -0
  18. package/dist/assets/gitGraphDiagram-942e62fe-ytnvaJ2O.js +70 -0
  19. package/dist/assets/graph-Bq9AO4pT.js +1 -0
  20. package/dist/assets/index--pBozGw-.js +245 -0
  21. package/dist/assets/index-01f381cb-CuQuHO3g.js +1 -0
  22. package/dist/assets/index-40_V--R5.css +13 -0
  23. package/dist/assets/infoDiagram-94cd232f-D3qHX83u.js +7 -0
  24. package/dist/assets/init-Gi6I4Gst.js +1 -0
  25. package/dist/assets/journeyDiagram-6625b456-DrDjeExJ.js +139 -0
  26. package/dist/assets/katex-CvgdMzdh.js +261 -0
  27. package/dist/assets/layout-dTROeK7M.js +1 -0
  28. package/dist/assets/line--V00syVo.js +1 -0
  29. package/dist/assets/linear-C2oDpuMJ.js +1 -0
  30. package/dist/assets/logo-DIAzbBuw.png +0 -0
  31. package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  32. package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  33. package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  34. package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  35. package/dist/assets/mindmap-definition-307c710a-CMUeraG8.js +110 -0
  36. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  37. package/dist/assets/pieDiagram-bb1d19e5-P5mFu8pA.js +35 -0
  38. package/dist/assets/quadrantDiagram-c759a472-DRSID9uQ.js +7 -0
  39. package/dist/assets/requirementDiagram-87253d64-98AMKGz3.js +52 -0
  40. package/dist/assets/sankeyDiagram-707fac0f-c6m9e_oG.js +8 -0
  41. package/dist/assets/sequenceDiagram-6894f283-z-gVG6uq.js +122 -0
  42. package/dist/assets/stateDiagram-5dee940d-DWfP8BzL.js +1 -0
  43. package/dist/assets/stateDiagram-v2-1992cada-DBpwYzQS.js +1 -0
  44. package/dist/assets/styles-0784dbeb-DIb0daye.js +207 -0
  45. package/dist/assets/styles-483fbfea-DqDTwju_.js +116 -0
  46. package/dist/assets/styles-b83b31c9-CSuCi6iq.js +160 -0
  47. package/dist/assets/svgDrawCommon-5e1cfd1d-CDAD6QkU.js +1 -0
  48. package/dist/assets/timeline-definition-bf702344-Dv2jO1Md.js +61 -0
  49. package/dist/assets/workbox-window.prod.es5-D5gOYdM7.js +2 -0
  50. package/dist/assets/xychartDiagram-f11f50a6-CEl6xRTu.js +7 -0
  51. package/dist/favicon.ico +0 -0
  52. package/dist/favicon.svg +482 -0
  53. package/dist/index.html +22 -0
  54. package/dist/logo-512x512.png +0 -0
  55. package/dist/logo.svg +482 -0
  56. package/dist/manifest.webmanifest +1 -0
  57. package/dist/maskable-icon-512x512.png +0 -0
  58. package/dist/pwa-192x192.png +0 -0
  59. package/dist/pwa-512x512.png +0 -0
  60. package/dist/pwa-64x64.png +0 -0
  61. package/dist/sw.js +2 -0
  62. package/nodes/config/locales/en-US/ui_page.html +7 -0
  63. package/nodes/config/locales/en-US/ui_page.json +5 -0
  64. package/nodes/config/ui_page.html +260 -96
  65. package/nodes/config/ui_page.js +9 -0
  66. package/nodes/widgets/locales/en-US/ui_gauge.html +4 -2
  67. package/nodes/widgets/locales/en-US/ui_gauge.json +2 -1
  68. package/nodes/widgets/locales/en-US/ui_number_input.html +21 -10
  69. package/nodes/widgets/locales/en-US/ui_table.html +8 -3
  70. package/nodes/widgets/locales/en-US/ui_table.json +4 -1
  71. package/nodes/widgets/ui_gauge.html +251 -232
  72. package/nodes/widgets/ui_gauge.js +9 -2
  73. package/nodes/widgets/ui_number_input.html +21 -2
  74. package/nodes/widgets/ui_number_input.js +33 -0
  75. package/nodes/widgets/ui_table.html +13 -1
  76. package/nodes/widgets/ui_table.js +21 -3
  77. package/package.json +5 -4
@@ -9,63 +9,75 @@
9
9
  </style>
10
10
 
11
11
  <script type="text/javascript">
12
- function validateGaugeSegments (segments) {
13
- $('#node-input-validation-segments').hide()
14
- const min = parseFloat(this.min)
15
- const max = parseFloat(this.max)
16
-
17
- // check we havea segment covering the smallest values of the gauge
18
- let minCovered = false
19
- for (let i = 0; i < segments.length; i++) {
20
- const from = parseFloat(segments[i].from)
21
- if (from <= min) {
22
- minCovered = true
12
+ (function () {
13
+ function validateGaugeSegments (segments) {
14
+ $('#node-input-validation-segments').hide()
15
+ const min = parseFloat(this.min)
16
+ const max = parseFloat(this.max)
17
+
18
+ // check we have a segment covering the smallest values of the gauge
19
+ let minCovered = false
20
+ for (let i = 0; i < segments.length; i++) {
21
+ const from = parseFloat(segments[i].from)
22
+ if (from <= min) {
23
+ minCovered = true
24
+ }
23
25
  }
24
- }
25
- if (!minCovered) {
26
- $('#node-input-validation-segments').text("It's advised to make sure your first segment's 'from' value and the gauge's 'min' value are the same.").show()
27
- }
28
- // check if we have any extra, unneccessary, segments
29
- let extras = false
30
- for (let i = 0; i < segments.length; i++) {
31
- const from = parseFloat(segments[i].from)
32
- if (from > max) {
33
- extras = true
26
+ if (!minCovered) {
27
+ $('#node-input-validation-segments').text("It's advised to make sure your first segment's 'from' value and the gauge's 'min' value are the same.").show()
34
28
  }
35
- }
36
- if (extras) {
37
- $('#node-input-validation-segments').text('You have segments defined outside of the min/max range of your gauge').show()
38
- }
39
- return minCovered && !extras
40
- }
41
- RED.nodes.registerType('ui-gauge', {
42
- category: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.label.category'),
43
- color: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.colors.medium'),
44
- defaults: {
45
- name: { value: '' },
46
- group: { type: 'ui-group', required: true },
47
- order: { value: 0 },
48
- width: {
49
- value: 3,
50
- validate: function (v) {
51
- const width = v || 0
52
- const currentGroup = $('#node-input-group').val() || this.group
53
- const groupNode = RED.nodes.node(currentGroup)
54
- const valid = !groupNode || +width <= +groupNode.width
55
- $('#node-input-size').toggleClass('input-error', !valid)
56
- return valid
29
+ // check if we have any extra, unneccessary, segments
30
+ let extras = false
31
+ for (let i = 0; i < segments.length; i++) {
32
+ const from = parseFloat(segments[i].from)
33
+ if (from > max) {
34
+ extras = true
57
35
  }
36
+ }
37
+ if (extras) {
38
+ $('#node-input-validation-segments').text('You have segments defined outside of the min/max range of your gauge').show()
39
+ }
40
+ const unique = new Set(this.segments.map(function (o) { return o.from }))
41
+ if (this.segments.length === unique.size) { $('#valWarning').hide() } else { $('#valWarning').show() }
42
+ return minCovered && !extras && this.segments.length === unique.size
43
+ }
44
+
45
+ const DEFAULTS = {
46
+ 'gauge-tank': {
47
+ min: 0,
48
+ max: 100,
49
+ segments: [{
50
+ color: '#A8F5FF',
51
+ from: 0
52
+ }, {
53
+ color: '#55DBEC',
54
+ from: 15
55
+ }, {
56
+ color: '#53B4FD',
57
+ from: 35
58
+ }, {
59
+ color: '#2397D1',
60
+ from: 50
61
+ }]
58
62
  },
59
- height: { value: 3 },
60
- gtype: { value: 'gauge-half' },
61
- gstyle: { value: 'needle' },
62
- title: { value: 'gauge' },
63
- units: { value: 'units' },
64
- icon: { value: '' },
65
- prefix: { value: '' },
66
- suffix: { value: '' },
67
- segments: {
68
- value: [{
63
+ 'gauge-battery': {
64
+ min: 0,
65
+ max: 100,
66
+ segments: [{
67
+ color: '#EA5353',
68
+ from: 0
69
+ }, {
70
+ color: '#FFC800',
71
+ from: 40
72
+ }, {
73
+ color: '#5CD65C',
74
+ from: 70
75
+ }]
76
+ },
77
+ 'gauge-tile': {
78
+ min: 0,
79
+ max: 10,
80
+ segments: [{
69
81
  color: '#5CD65C',
70
82
  from: 0
71
83
  }, {
@@ -74,209 +86,215 @@
74
86
  }, {
75
87
  color: '#EA5353',
76
88
  from: 7
77
- }],
78
- validate: validateGaugeSegments
79
- },
80
- min: { value: 0, required: true, validate: RED.validators.number() },
81
- max: { value: 10, required: true, validate: RED.validators.number() },
82
- // sizes
83
- sizeThickness: { value: 16, validate: RED.validators.number() },
84
- sizeGap: { value: 4, validate: RED.validators.number() },
85
- sizeKeyThickness: { value: 8, validate: RED.validators.number() },
86
- // style
87
- styleRounded: { value: true },
88
- styleGlow: { value: false },
89
- // CSS
90
- className: { value: '' }
91
- },
92
- inputs: 1,
93
- outputs: 0,
94
- inputLabels: function () { return this.min + ' - ' + this.max },
95
- align: 'right',
96
- icon: 'ui-gauge.svg',
97
- paletteLabel: 'gauge',
98
- label: function () { return this.name || this.title || this.gtype },
99
- labelStyle: function () { return this.name ? 'node_label_italic' : '' },
100
- oneditprepare: function () {
101
- // store reference to initial gtype so we can check if it's changed
102
- const initGType = this.gtype
103
-
104
- // if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
105
- // as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
106
- if (RED.nodes.subflow(this.z)) {
107
- // change inputs from hidden to text & display them
108
- $('#node-input-width').attr('type', 'text')
109
- $('#node-input-height').attr('type', 'text')
110
- $('div.form-row.nr-db-ui-element-sizer-row').hide()
111
- $('div.form-row.nr-db-ui-manual-size-row').show()
112
- } else {
113
- // not in a subflow, use the elementSizer
114
- $('div.form-row.nr-db-ui-element-sizer-row').show()
115
- $('div.form-row.nr-db-ui-manual-size-row').hide()
116
- $('#node-input-size').elementSizer({
117
- width: '#node-input-width',
118
- height: '#node-input-height',
119
- group: '#node-input-group'
120
- })
89
+ }]
121
90
  }
91
+ }
92
+ DEFAULTS['gauge-34'] = DEFAULTS['gauge-tile']
93
+ DEFAULTS['gauge-half'] = DEFAULTS['gauge-tile']
122
94
 
123
- // check for duplicate values
124
- const unique = new Set(this.segments.map(function (o) { return o.from }))
125
- if (this.segments.length === unique.size) { $('#valWarning').hide() } else { $('#valWarning').show() }
95
+ function isDefault (gtype) {
96
+ // get min, max and segments from UI and check if they are different from the defaults
97
+ const min = +$('#node-input-min').val()
98
+ const max = +$('#node-input-max').val()
99
+ const segments = $('#node-input-segments-container').children()
100
+ gtype = gtype || $('#node-input-gtype').val()
101
+ const defaults = DEFAULTS[gtype]
102
+ if (!defaults) {
103
+ return false
104
+ }
105
+ if (min !== defaults.min || max !== defaults.max || segments.length !== defaults.segments.length) {
106
+ return false
107
+ }
108
+ for (let i = 0; i < segments.length; i++) {
109
+ const segment = $(segments[i])
110
+ const from = +segment.find('.node-input-segment-from').val()
111
+ const color = segment.find('.node-input-segment-color').val()
112
+ if (from !== defaults.segments[i].from || (color + '').toLowerCase() !== defaults.segments[i].color.toLowerCase()) {
113
+ return false
114
+ }
115
+ }
116
+ return true
117
+ }
126
118
 
127
- validateGaugeSegments.call(this, this.segments)
119
+ RED.nodes.registerType('ui-gauge', {
120
+ category: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.label.category'),
121
+ color: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.colors.medium'),
122
+ defaults: {
123
+ name: { value: '' },
124
+ group: { type: 'ui-group', required: true },
125
+ order: { value: 0 },
126
+ width: {
127
+ value: 3,
128
+ validate: function (v) {
129
+ const width = v || 0
130
+ const currentGroup = $('#node-input-group').val() || this.group
131
+ const groupNode = RED.nodes.node(currentGroup)
132
+ const valid = !groupNode || +width <= +groupNode.width
133
+ $('#node-input-size').toggleClass('input-error', !valid)
134
+ return valid
135
+ }
136
+ },
137
+ height: { value: 3 },
138
+ gtype: { value: 'gauge-half' },
139
+ gstyle: { value: 'needle' },
140
+ title: { value: 'gauge' },
141
+ units: { value: 'units' },
142
+ icon: { value: '' },
143
+ prefix: { value: '' },
144
+ suffix: { value: '' },
145
+ segments: {
146
+ value: [...DEFAULTS['gauge-half'].segments],
147
+ validate: validateGaugeSegments
148
+ },
149
+ min: { value: DEFAULTS['gauge-half'].min, required: true, validate: RED.validators.number() },
150
+ max: { value: DEFAULTS['gauge-half'].max, required: true, validate: RED.validators.number() },
151
+ // sizes
152
+ sizeThickness: { value: 16, validate: RED.validators.number() },
153
+ sizeGap: { value: 4, validate: RED.validators.number() },
154
+ sizeKeyThickness: { value: 8, validate: RED.validators.number() },
155
+ // style
156
+ styleRounded: { value: true },
157
+ styleGlow: { value: false },
158
+ // CSS
159
+ className: { value: '' }
160
+ },
161
+ inputs: 1,
162
+ outputs: 0,
163
+ inputLabels: function () { return this.min + ' - ' + this.max },
164
+ align: 'right',
165
+ icon: 'ui-gauge.svg',
166
+ paletteLabel: 'gauge',
167
+ label: function () { return this.name || this.title || this.gtype },
168
+ labelStyle: function () { return this.name ? 'node_label_italic' : '' },
169
+ oneditprepare: function () {
170
+ let isInitialised = false
171
+ let currentType = this.gtype
172
+ // if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
173
+ // as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
174
+ if (RED.nodes.subflow(this.z)) {
175
+ // change inputs from hidden to text & display them
176
+ $('#node-input-width').attr('type', 'text')
177
+ $('#node-input-height').attr('type', 'text')
178
+ $('div.form-row.nr-db-ui-element-sizer-row').hide()
179
+ $('div.form-row.nr-db-ui-manual-size-row').show()
180
+ } else {
181
+ // not in a subflow, use the elementSizer
182
+ $('div.form-row.nr-db-ui-element-sizer-row').show()
183
+ $('div.form-row.nr-db-ui-manual-size-row').hide()
184
+ $('#node-input-size').elementSizer({
185
+ width: '#node-input-width',
186
+ height: '#node-input-height',
187
+ group: '#node-input-group'
188
+ })
189
+ }
128
190
 
129
- function generateSegment (i, segment) {
130
- const container = $('<li/>', { style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);' })
131
- const row = $('<div/>').appendTo(container)
132
- $('<div/>', { style: 'padding-top:5px; padding-left:175px;' }).appendTo(container)
133
- $('<div/>', { style: 'padding-top:5px; padding-left:120px;' }).appendTo(container)
191
+ function generateSegment (i, segment) {
192
+ const container = $('<li/>', { style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);' })
193
+ const row = $('<div/>').appendTo(container)
194
+ $('<div/>', { style: 'padding-top:5px; padding-left:175px;' }).appendTo(container)
195
+ $('<div/>', { style: 'padding-top:5px; padding-left:120px;' }).appendTo(container)
134
196
 
135
- $('<i style="color: var(--red-ui-form-text-color, #eee); cursor:move; margin-left:3px;" class="node-input-segment-handle fa fa-bars"></i>').appendTo(row)
197
+ $('<i style="color: var(--red-ui-form-text-color, #eee); cursor:move; margin-left:3px;" class="node-input-segment-handle fa fa-bars"></i>').appendTo(row)
136
198
 
137
- $('<input/>', { class: 'node-input-segment-color', type: 'color', style: 'margin-left:7px; width: 50px;', placeholder: 'Color', value: segment.color }).appendTo(row)
138
- $('<input/>', { class: 'node-input-segment-from', type: 'text', style: 'margin-left:7px; width:calc(100% - 175px);', placeholder: 'From', value: segment.from }).appendTo(row)
199
+ $('<input/>', { class: 'node-input-segment-color', type: 'color', style: 'margin-left:7px; width: 50px;', placeholder: 'Color', value: segment.color }).appendTo(row)
200
+ $('<input/>', { class: 'node-input-segment-from', type: 'text', style: 'margin-left:7px; width:calc(100% - 175px);', placeholder: 'From', value: segment.from }).appendTo(row)
139
201
 
140
- const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row)
141
- const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan)
142
- $('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton)
202
+ const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row)
203
+ const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan)
204
+ $('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton)
143
205
 
144
- deleteButton.click(function () {
145
- container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' })
146
- container.fadeOut(300, function () {
147
- $(this).remove()
206
+ deleteButton.click(function () {
207
+ container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' })
208
+ container.fadeOut(300, function () {
209
+ $(this).remove()
210
+ })
148
211
  })
149
- })
150
-
151
- $('#node-input-segments-container').append(container)
152
- }
153
-
154
- $('#node-input-add-segment').click(function () {
155
- generateSegment($('#node-input-segments-container').children().length + 1, {})
156
- $('#node-input-segments-container-div').scrollTop($('#node-input-segments-container-div').get(0).scrollHeight)
157
- })
158
212
 
159
- $('#node-input-segments-container').sortable({
160
- axis: 'y',
161
- handle: '.node-input-segment-handle',
162
- cursor: 'move'
163
- })
164
-
165
- for (let i = 0; i < this.segments.length; i++) {
166
- const segment = this.segments[i]
167
- generateSegment(i + 1, segment)
168
- }
213
+ $('#node-input-segments-container').append(container)
214
+ }
169
215
 
170
- // only show relevant options
216
+ function showTypeOptions (type) {
217
+ const isChanging = currentType !== type
218
+ const oldType = currentType
219
+ currentType = type
220
+ if (type === 'gauge-tile' || type === 'gauge-battery' || type === 'gauge-tank') {
221
+ $('#node-input-container-sizes').hide()
222
+ $('#node-input-container-gstyle').hide()
223
+ $('#node-input-container-label-extras').hide()
224
+ } else {
225
+ $('#node-input-container-sizes').show()
226
+ $('#node-input-container-gstyle').show()
227
+ $('#node-input-container-label-extras').show()
228
+ }
171
229
 
172
- function showTypeOptions (type) {
173
- if (type === 'gauge-tile' || type === 'gauge-battery' || type === 'gauge-tank') {
174
- $('#node-input-container-sizes').hide()
175
- $('#node-input-container-gstyle').hide()
176
- $('#node-input-container-label-extras').hide()
177
- } else {
178
- $('#node-input-container-sizes').show()
179
- $('#node-input-container-gstyle').show()
180
- $('#node-input-container-label-extras').show()
230
+ if (isInitialised && isChanging && isDefault(oldType)) {
231
+ const defaults = DEFAULTS[currentType]
232
+ setup(defaults?.segments, defaults?.min, defaults?.max)
233
+ }
181
234
  }
182
235
 
183
- if (type === 'gauge-tank' && initGType !== 'gauge-tank') {
184
- // set new min/max
185
- $('#node-input-min').val(0)
186
- $('#node-input-max').val(100)
187
-
188
- // set some sensible segments
189
- const segments = [{
190
- color: '#A8F5FF',
191
- from: 0
192
- }, {
193
- color: '#55DBEC',
194
- from: 15
195
- }, {
196
- color: '#53B4FD',
197
- from: 35
198
- }, {
199
- color: '#2397D1',
200
- from: 50
201
- }]
202
- // clear existing segments
236
+ function setup (segments, min, max) {
237
+ $('#node-input-min').val(min)
238
+ $('#node-input-max').val(max)
203
239
  $('#node-input-segments-container').empty()
204
- // add the new "defaults" for this gType
205
240
  for (let i = 0; i < segments.length; i++) {
206
241
  const segment = segments[i]
207
242
  generateSegment(i + 1, segment)
208
243
  }
209
- } else if (type === 'gauge-battery' && initGType !== 'gauge-battery') {
210
- // set new min/max
211
- $('#node-input-min').val(0)
212
- $('#node-input-max').val(100)
244
+ }
213
245
 
214
- // set some sensible segments
215
- const segments = [{
216
- color: '#EA5353',
217
- from: 0
218
- }, {
219
- color: '#FFC800',
220
- from: 40
221
- }, {
222
- color: '#5CD65C',
223
- from: 70
224
- }]
225
- // clear existing segments
226
- $('#node-input-segments-container').empty()
227
- // add the new "defaults" for this gType
228
- for (let i = 0; i < segments.length; i++) {
229
- const segment = segments[i]
230
- generateSegment(i + 1, segment)
246
+ $('#node-input-add-segment').click(function () {
247
+ generateSegment($('#node-input-segments-container').children().length + 1, {})
248
+ $('#node-input-segments-container-div').scrollTop($('#node-input-segments-container-div').get(0).scrollHeight)
249
+ })
250
+
251
+ $('#node-input-segments-container').sortable({
252
+ axis: 'y',
253
+ handle: '.node-input-segment-handle',
254
+ cursor: 'move'
255
+ })
256
+
257
+ // set default segments based on the current gauge type
258
+ $('#node-input-get-defaults').click(function () {
259
+ const selectedType = $('#node-input-gtype').val()
260
+ const defaults = DEFAULTS[selectedType]
261
+ if (defaults) {
262
+ setup(defaults.segments, defaults.min, defaults.max)
231
263
  }
232
- } else if (initGType !== type) {
233
- // we've actually changed type, not just being triggered by the initial NR load of palette
234
-
235
- // set new min/max
236
- $('#node-input-min').val(0)
237
- $('#node-input-max').val(10)
264
+ })
238
265
 
239
- // set some sensible segments
240
- const segments = [{
241
- color: '#5CD65C',
242
- from: 0
243
- }, {
244
- color: '#FFC800',
245
- from: 4
246
- }, {
247
- color: '#EA5353',
248
- from: 7
249
- }]
250
- // clear existing segments
251
- $('#node-input-segments-container').empty()
252
- // add the new "defaults" for this gType
253
- for (let i = 0; i < segments.length; i++) {
254
- const segment = segments[i]
255
- generateSegment(i + 1, segment)
266
+ $('#node-input-gtype').change((data) => {
267
+ const gType = $('#node-input-gtype').val()
268
+ showTypeOptions(gType)
269
+ })
270
+
271
+ // now event handlers are set up, set the initial state of the form
272
+ setup(this.segments, this.min, this.max)
273
+
274
+ // trigger the change event to show the correct options
275
+ $('#node-input-gtype').change()
276
+
277
+ // validate the segments
278
+ validateGaugeSegments.call(this, this.segments)
279
+
280
+ isInitialised = true
281
+ },
282
+ oneditsave: function () {
283
+ const segments = $('#node-input-segments-container').children()
284
+
285
+ const node = this
286
+ node.segments = []
287
+ segments.each(function (i) {
288
+ const segment = $(this)
289
+ const s = {
290
+ from: segment.find('.node-input-segment-from').val(),
291
+ color: segment.find('.node-input-segment-color').val()
256
292
  }
257
- }
293
+ node.segments.push(s)
294
+ })
258
295
  }
259
-
260
- $('#node-input-gtype').change((data) => {
261
- const gType = $('#node-input-gtype').val()
262
- showTypeOptions(gType)
263
- })
264
- },
265
- oneditsave: function () {
266
- const segments = $('#node-input-segments-container').children()
267
-
268
- const node = this
269
- node.segments = []
270
- segments.each(function (i) {
271
- const segment = $(this)
272
- const s = {
273
- from: segment.find('.node-input-segment-from').val(),
274
- color: segment.find('.node-input-segment-color').val()
275
- }
276
- node.segments.push(s)
277
- })
278
- }
279
- })
296
+ })
297
+ })()
280
298
  </script>
281
299
 
282
300
  <script type="text/html" data-template-name="ui-gauge">
@@ -348,6 +366,7 @@
348
366
  </div>
349
367
  <div class="form-row">
350
368
  <a href="#" class="editor-button editor-button-small" id="node-input-add-segment" style="margin-top:4px; margin-left:103px;"><i class="fa fa-plus"></i> <span data-i18n="ui-gauge.label.segment"></span></a>
369
+ <a href="#" class="editor-button editor-button-small" id="node-input-get-defaults" style="margin-top:4px;"><span data-i18n="ui-gauge.label.defaults"></span></a>
351
370
  </div>
352
371
  <div class="form-row">
353
372
  <h3 data-i18n="ui-gauge.label.labelling"></h3>
@@ -13,9 +13,16 @@ module.exports = function (RED) {
13
13
  beforeSend: async function (msg) {
14
14
  const updates = msg.ui_update
15
15
  if (updates) {
16
- if (typeof updates.title !== 'undefined') {
16
+ const hasLabelKey = Object.keys(updates).includes('label')
17
+ const hasTitleKey = Object.keys(updates).includes('title')
18
+
19
+ if (!hasLabelKey && hasTitleKey) {
20
+ updates.label = updates.title
21
+ }
22
+
23
+ if (typeof updates.label !== 'undefined') {
17
24
  // dynamically set "label" property
18
- statestore.set(group.getBase(), node, msg, 'title', updates.title)
25
+ statestore.set(group.getBase(), node, msg, 'label', updates.label)
19
26
  }
20
27
  if (typeof updates.gtype !== 'undefined') {
21
28
  // dynamically set "gauge type" property
@@ -27,7 +27,14 @@
27
27
  topicType: { value: 'msg' },
28
28
  min: { value: 0, required: true, validate: RED.validators.number() },
29
29
  max: { value: 10, required: true, validate: RED.validators.number() },
30
- step: { value: 1 },
30
+ step: {
31
+ value: 1,
32
+ validate: function (v) {
33
+ const isValid = RED.validators.number()(v) && v > 0
34
+ $('#node-input-step').toggleClass('input-error', !isValid)
35
+ return isValid
36
+ }
37
+ },
31
38
  tooltip: { value: '' },
32
39
  passthru: { value: true },
33
40
  sendOnBlur: { value: true },
@@ -36,7 +43,8 @@
36
43
  clearable: { value: false },
37
44
  icon: { value: '' },
38
45
  iconPosition: { value: 'left' },
39
- iconInnerPosition: { value: 'inside' }
46
+ iconInnerPosition: { value: 'inside' },
47
+ spinner: { value: 'default' }
40
48
  },
41
49
  inputs: 1,
42
50
  outputs: 1,
@@ -101,6 +109,10 @@
101
109
  if (!this.iconInnerPosition) {
102
110
  $('#node-input-iconInnerPosition').val('inside')
103
111
  }
112
+
113
+ if (!this.spinner) {
114
+ $('#node-input-spinner').val('default')
115
+ }
104
116
  },
105
117
  label: function () {
106
118
  return this.name || (~this.label.indexOf('{' + '{') ? null : this.label) || this.mode + ' input'
@@ -164,6 +176,13 @@
164
176
  </div>
165
177
  </div>
166
178
  </div>
179
+ <div class="form-row">
180
+ <label for="node-input-spinner">Spinner</label>
181
+ <select id="node-input-spinner">
182
+ <option value="default">Inline</option>
183
+ <option value="stacked">Stacked</option>
184
+ </select>
185
+ </div>
167
186
  <div class="form-row">
168
187
  <label for="node-input-className"><i class="fa fa-code"></i> Class</label>
169
188
  <div style="display: inline;">
@@ -1,4 +1,6 @@
1
1
  const datastore = require('../store/data.js')
2
+ const statestore = require('../store/state.js')
3
+ const { appendTopic } = require('../utils/index.js')
2
4
 
3
5
  module.exports = function (RED) {
4
6
  function NumberInputNode (config) {
@@ -15,6 +17,37 @@ module.exports = function (RED) {
15
17
 
16
18
  const evts = {
17
19
  onChange: true,
20
+ beforeSend: async function (msg) {
21
+ const updates = msg.ui_update
22
+ if (updates) {
23
+ if (typeof updates.label !== 'undefined') {
24
+ // dynamically set "label" property
25
+ statestore.set(group.getBase(), node, msg, 'label', updates.label)
26
+ }
27
+ if (typeof updates.clearable !== 'undefined') {
28
+ // dynamically set "clearable" property
29
+ statestore.set(group.getBase(), node, msg, 'clearable', updates.clearable)
30
+ }
31
+ if (typeof updates.icon !== 'undefined') {
32
+ // dynamically set "icon" property
33
+ statestore.set(group.getBase(), node, msg, 'icon', updates.icon)
34
+ }
35
+ if (typeof updates.iconPosition !== 'undefined') {
36
+ // dynamically set "iconPosition" property
37
+ statestore.set(group.getBase(), node, msg, 'iconPosition', updates.iconPosition)
38
+ }
39
+ if (typeof updates.iconInnerPosition !== 'undefined') {
40
+ // dynamically set "iconInnerPosition" property
41
+ statestore.set(group.getBase(), node, msg, 'iconInnerPosition', updates.iconInnerPosition)
42
+ }
43
+ if (typeof updates.spinner !== 'undefined') {
44
+ // dynamically set "spinner" property
45
+ statestore.set(group.getBase(), node, msg, 'spinner', updates.spinner)
46
+ }
47
+ }
48
+ msg = await appendTopic(RED, config, node, msg)
49
+ return msg
50
+ },
18
51
  onInput: function (msg, send) {
19
52
  // store the latest msg passed to node
20
53
  datastore.save(group.getBase(), node, msg)