Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ui_gauge.html 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <style>
  2. input.gauge-color {
  3. width: 100px;
  4. text-align: center;
  5. }
  6. input.gauge-color::-webkit-color-swatch {
  7. border: none;
  8. }
  9. </style>
  10. <script type="text/javascript">
  11. function validateGaugeSegments (segments) {
  12. $('#node-input-validation-segments').hide()
  13. const min = parseFloat(this.min)
  14. const max = parseFloat(this.max)
  15. // check we havea segment covering the smallest values of the gauge
  16. let minCovered = false
  17. for (let i = 0; i < segments.length; i++) {
  18. const from = parseFloat(segments[i].from)
  19. if (from <= min) {
  20. minCovered = true
  21. }
  22. }
  23. if (!minCovered) {
  24. $('#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()
  25. }
  26. // check if we have any extra, unneccessary, segments
  27. let extras = false
  28. for (let i = 0; i < segments.length; i++) {
  29. const from = parseFloat(segments[i].from)
  30. if (from > max) {
  31. extras = true
  32. }
  33. }
  34. if (extras) {
  35. $('#node-input-validation-segments').text('You have segments defined outside of the min/max range of your gauge').show()
  36. }
  37. return minCovered && !extras
  38. }
  39. RED.nodes.registerType('ui-gauge', {
  40. category: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.label.category'),
  41. color: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.colors.medium'),
  42. defaults: {
  43. name: { value: '' },
  44. group: { type: 'ui-group', required: true },
  45. order: { value: 0 },
  46. width: {
  47. value: 3,
  48. validate: function (v) {
  49. const width = v || 0
  50. const currentGroup = $('#node-input-group').val() || this.group
  51. const groupNode = RED.nodes.node(currentGroup)
  52. const valid = !groupNode || +width <= +groupNode.width
  53. $('#node-input-size').toggleClass('input-error', !valid)
  54. return valid
  55. }
  56. },
  57. height: { value: 3 },
  58. gtype: { value: 'gauge-half' },
  59. gstyle: { value: 'needle' },
  60. title: { value: 'gauge' },
  61. units: { value: 'units' },
  62. icon: { value: '' },
  63. prefix: { value: '' },
  64. suffix: { value: '' },
  65. segments: {
  66. value: [{
  67. color: '#5CD65C',
  68. from: 0
  69. }, {
  70. color: '#FFC800',
  71. from: 4
  72. }, {
  73. color: '#EA5353',
  74. from: 7
  75. }],
  76. validate: validateGaugeSegments
  77. },
  78. min: { value: 0, required: true, validate: RED.validators.number() },
  79. max: { value: 10, required: true, validate: RED.validators.number() },
  80. // sizes
  81. sizeThickness: { value: 16, validate: RED.validators.number() },
  82. sizeGap: { value: 4, validate: RED.validators.number() },
  83. sizeKeyThickness: { value: 8, validate: RED.validators.number() },
  84. // style
  85. styleRounded: { value: true },
  86. styleGlow: { value: false },
  87. // CSS
  88. className: { value: '' }
  89. },
  90. inputs: 1,
  91. outputs: 0,
  92. inputLabels: function () { return this.min + ' - ' + this.max },
  93. align: 'right',
  94. icon: 'ui-gauge.svg',
  95. paletteLabel: 'gauge',
  96. label: function () { return this.name || this.title || this.gtype },
  97. labelStyle: function () { return this.name ? 'node_label_italic' : '' },
  98. oneditprepare: function () {
  99. // store reference to initial gtype so we can check if it's changed
  100. const initGType = this.gtype
  101. // if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
  102. // as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
  103. if (RED.nodes.subflow(this.z)) {
  104. // change inputs from hidden to text & display them
  105. $('#node-input-width').attr('type', 'text')
  106. $('#node-input-height').attr('type', 'text')
  107. $('div.form-row.nr-db-ui-element-sizer-row').hide()
  108. $('div.form-row.nr-db-ui-manual-size-row').show()
  109. } else {
  110. // not in a subflow, use the elementSizer
  111. $('div.form-row.nr-db-ui-element-sizer-row').show()
  112. $('div.form-row.nr-db-ui-manual-size-row').hide()
  113. $('#node-input-size').elementSizer({
  114. width: '#node-input-width',
  115. height: '#node-input-height',
  116. group: '#node-input-group'
  117. })
  118. }
  119. // check for duplicate values
  120. const unique = new Set(this.segments.map(function (o) { return o.from }))
  121. if (this.segments.length === unique.size) { $('#valWarning').hide() } else { $('#valWarning').show() }
  122. validateGaugeSegments.call(this, this.segments)
  123. function generateSegment (i, segment) {
  124. 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);' })
  125. const row = $('<div/>').appendTo(container)
  126. $('<div/>', { style: 'padding-top:5px; padding-left:175px;' }).appendTo(container)
  127. $('<div/>', { style: 'padding-top:5px; padding-left:120px;' }).appendTo(container)
  128. $('<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)
  129. $('<input/>', { class: 'node-input-segment-color', type: 'color', style: 'margin-left:7px; width: 50px;', placeholder: 'Color', value: segment.color }).appendTo(row)
  130. $('<input/>', { class: 'node-input-segment-from', type: 'text', style: 'margin-left:7px; width:calc(100% - 175px);', placeholder: 'From', value: segment.from }).appendTo(row)
  131. const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row)
  132. const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan)
  133. $('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton)
  134. deleteButton.click(function () {
  135. container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' })
  136. container.fadeOut(300, function () {
  137. $(this).remove()
  138. })
  139. })
  140. $('#node-input-segments-container').append(container)
  141. }
  142. $('#node-input-add-segment').click(function () {
  143. generateSegment($('#node-input-segments-container').children().length + 1, {})
  144. $('#node-input-segments-container-div').scrollTop($('#node-input-segments-container-div').get(0).scrollHeight)
  145. })
  146. $('#node-input-segments-container').sortable({
  147. axis: 'y',
  148. handle: '.node-input-segment-handle',
  149. cursor: 'move'
  150. })
  151. for (let i = 0; i < this.segments.length; i++) {
  152. const segment = this.segments[i]
  153. generateSegment(i + 1, segment)
  154. }
  155. // only show relevant options
  156. function showTypeOptions (type) {
  157. if (type === 'gauge-tile' || type === 'gauge-battery' || type === 'gauge-tank') {
  158. $('#node-input-container-sizes').hide()
  159. $('#node-input-container-gstyle').hide()
  160. $('#node-input-container-label-extras').hide()
  161. } else {
  162. $('#node-input-container-sizes').show()
  163. $('#node-input-container-gstyle').show()
  164. $('#node-input-container-label-extras').show()
  165. }
  166. if (type === 'gauge-tank' && initGType !== 'gauge-tank') {
  167. // set new min/max
  168. $('#node-input-min').val(0)
  169. $('#node-input-max').val(100)
  170. // set some sensible segments
  171. const segments = [{
  172. color: '#A8F5FF',
  173. from: 0
  174. }, {
  175. color: '#55DBEC',
  176. from: 15
  177. }, {
  178. color: '#53B4FD',
  179. from: 35
  180. }, {
  181. color: '#2397D1',
  182. from: 50
  183. }]
  184. // clear existing segments
  185. $('#node-input-segments-container').empty()
  186. // add the new "defaults" for this gType
  187. for (let i = 0; i < segments.length; i++) {
  188. const segment = segments[i]
  189. generateSegment(i + 1, segment)
  190. }
  191. } else if (type === 'gauge-battery' && initGType !== 'gauge-battery') {
  192. // set new min/max
  193. $('#node-input-min').val(0)
  194. $('#node-input-max').val(100)
  195. // set some sensible segments
  196. const segments = [{
  197. color: '#EA5353',
  198. from: 0
  199. }, {
  200. color: '#FFC800',
  201. from: 40
  202. }, {
  203. color: '#5CD65C',
  204. from: 70
  205. }]
  206. // clear existing segments
  207. $('#node-input-segments-container').empty()
  208. // add the new "defaults" for this gType
  209. for (let i = 0; i < segments.length; i++) {
  210. const segment = segments[i]
  211. generateSegment(i + 1, segment)
  212. }
  213. } else if (initGType !== type) {
  214. // we've actually changed type, not just being triggered by the initial NR load of palette
  215. // set new min/max
  216. $('#node-input-min').val(0)
  217. $('#node-input-max').val(10)
  218. // set some sensible segments
  219. const segments = [{
  220. color: '#5CD65C',
  221. from: 0
  222. }, {
  223. color: '#FFC800',
  224. from: 4
  225. }, {
  226. color: '#EA5353',
  227. from: 7
  228. }]
  229. // clear existing segments
  230. $('#node-input-segments-container').empty()
  231. // add the new "defaults" for this gType
  232. for (let i = 0; i < segments.length; i++) {
  233. const segment = segments[i]
  234. generateSegment(i + 1, segment)
  235. }
  236. }
  237. }
  238. $('#node-input-gtype').change((data) => {
  239. const gType = $('#node-input-gtype').val()
  240. showTypeOptions(gType)
  241. })
  242. },
  243. oneditsave: function () {
  244. const segments = $('#node-input-segments-container').children()
  245. const node = this
  246. node.segments = []
  247. segments.each(function (i) {
  248. const segment = $(this)
  249. const s = {
  250. from: segment.find('.node-input-segment-from').val(),
  251. color: segment.find('.node-input-segment-color').val()
  252. }
  253. node.segments.push(s)
  254. })
  255. }
  256. })
  257. </script>
  258. <script type="text/html" data-template-name="ui-gauge">
  259. <div class="form-row">
  260. <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.name"></span></label>
  261. <input type="text" id="node-input-name">
  262. </div>
  263. <div class="form-row">
  264. <label for="node-input-group"><i class="fa fa-table"></i> <span data-i18n="ui-gauge.label.group"></span></label>
  265. <input type="text" id="node-input-group">
  266. </div>
  267. <div class="form-row nr-db-ui-element-sizer-row">
  268. <label><i class="fa fa-object-group"></i> <span data-i18n="ui-gauge.label.size">Size</label>
  269. <button class="editor-button" id="node-input-size"></button>
  270. </div>
  271. <div class="form-row nr-db-ui-manual-size-row">
  272. <label><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.width">Width</label>
  273. <input type="hidden" id="node-input-width">
  274. </div>
  275. <div class="form-row nr-db-ui-manual-size-row">
  276. <label><i class="fa fa-arrows-v"></i> <span data-i18n="ui-gauge.label.height">Height</label>
  277. <input type="hidden" id="node-input-height">
  278. </div>
  279. <div class="form-row">
  280. <label for="node-input-gtype"><i class="fa fa-list"></i> <span data-i18n="ui-gauge.label.type"></span></label>
  281. <select id="node-input-gtype" style="width:150px">
  282. <option value="gauge-tile" data-i18n="ui-gauge.label.gaugeTile"></option>
  283. <option value="gauge-34" data-i18n="ui-gauge.label.gauge34"></option>
  284. <option value="gauge-half" data-i18n="ui-gauge.label.gaugeHalf"></option>
  285. <option value="gauge-battery" data-i18n="ui-gauge.label.gaugeBattery"></option>
  286. <option value="gauge-tank" data-i18n="ui-gauge.label.gaugeTank"></option>
  287. <!-- <segment value="donut">Donut</segment>
  288. <segment value="compass">Compass</segment>
  289. <segment value="wave">Level</segment> -->
  290. </select>
  291. </div>
  292. <div id="node-input-container-gstyle" class="form-row">
  293. <label for="node-input-gstyle"><i class="fa fa-list"></i> <span data-i18n="ui-gauge.label.style"></span></label>
  294. <select id="node-input-gstyle" style="width:150px">
  295. <option value="needle" data-i18n="ui-gauge.label.needle"></option>
  296. <option value="rounded" data-i18n="ui-gauge.label.rounded"></option>
  297. <!-- <option value=rounded">Rounded</option> -->
  298. </select>
  299. </div>
  300. <div class="form-row">
  301. <h3><span data-i18n="ui-gauge.label.limits"></span></h3>
  302. </div>
  303. <div class="form-row">
  304. <label for="node-input-min"><i class="fa fa-arrows-h"></i><span data-i18n="ui-gauge.label.range"></span></label>
  305. <span for="node-input-min"><span data-i18n="ui-gauge.label.min"></span></span>
  306. <input type="text" id="node-input-min" style="width:80px">
  307. <span for="node-input-max" style="margin-left:20px;"><span data-i18n="ui-gauge.label.max"></span></span>
  308. <input type="text" id="node-input-max" style="width:80px">
  309. </div>
  310. <span id="node-input-validation-segments" style="color: var(--red-ui-text-color-error, #910000)"></span>
  311. <div class="form-row node-input-segments-container-row" style="margin-bottom: 0px;width: 100%">
  312. <label for="node-input-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> <span data-i18n="ui-gauge.label.segments"></span></label>
  313. <div id="node-input-segments-container-div" style="box-sizing:border-box; border-radius:5px; height:257px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:scroll; display:inline-block; width: 70%;">
  314. <span id="valWarning" style="color: var(--red-ui-text-color-error, #910000)"><b data-i18n="ui-gauge.errors.unique"></b></span>
  315. <ol id="node-input-segments-container" style="list-style-type:none; margin:0;"></ol>
  316. </div>
  317. <!-- <a
  318. data-html="true"
  319. title="Dynamic Property: Send 'msg.segments' in order to override this property."
  320. class="red-ui-button ui-node-popover-title"
  321. style="margin-left: 4px; cursor: help; font-size: 0.625rem; border-radius: 50%; width: 24px; height: 24px; display: inline-flex; justify-content: center; align-items: center;">
  322. <i style="font-family: ui-serif;">fx</i>
  323. </a> -->
  324. </div>
  325. <div class="form-row">
  326. <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>
  327. </div>
  328. <div class="form-row">
  329. <h3 data-i18n="ui-gauge.label.labelling"></h3>
  330. </div>
  331. <div id="ui-gauge-labels">
  332. <div class="form-row">
  333. <label for="node-input-title"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.label"></span></label>
  334. <input type="text" id="node-input-title">
  335. </div>
  336. <div id="node-input-container-label-extras">
  337. <div class="form-row form-row-flex">
  338. <div>
  339. <label for="node-input-prefix"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.prefix"></span></label>
  340. <input type="text" id="node-input-prefix" style="width: 75px">
  341. </div>
  342. <div>
  343. <label for="node-input-suffix" style="margin-left: 48px;"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.suffix"></span></label>
  344. <input type="text" id="node-input-suffix" style="width: 75px">
  345. </div>
  346. </div>
  347. <div class="form-row" id="ui-gauge-units">
  348. <label for="node-input-units"><i class="fa fa-calculator"></i> <span data-i18n="ui-gauge.label.units"></span></label>
  349. <input type="text" id="node-input-units" placeholder="Unit">
  350. </div>
  351. <div class="form-row" id="ui-gauge-icon">
  352. <label for="node-input-icon"><i class="fa fa-home"></i> <span data-i18n="ui-gauge.label.icon"></span></label>
  353. <input type="text" id="node-input-icon" placeholder="Icon from mdi library, e.g. 'home'">
  354. </div>
  355. </div>
  356. </div>
  357. <div id="node-input-container-sizes">
  358. <div class="form-row">
  359. <h3 data-i18n="ui-gauge.label.sizes"></h3>
  360. </div>
  361. <div class="form-row">
  362. <label for="node-input-sizeThickness"><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.gauge"></span></label>
  363. <input type="text" id="node-input-sizeThickness" style="width:75px; margin-right: 3px;">px
  364. </div>
  365. <div class="form-row">
  366. <label for="node-input-sizeGap"><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.gap"></span></label>
  367. <input type="text" id="node-input-sizeGap" style="width:75px; margin-right: 3px;">px
  368. </div>
  369. <div class="form-row">
  370. <label for="node-input-sizeKeyThickness"><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.segments"></span></label>
  371. <input type="text" id="node-input-sizeKeyThickness" style="width:75px; margin-right: 3px;">px
  372. </div>
  373. <div class="form-row">
  374. <h3 data-i18n="ui-gauge.label.styling"></h3>
  375. </div>
  376. </div>
  377. <div class="form-row">
  378. <label for="node-input-className"><i class="fa fa-code"></i> <span data-i18n="ui-gauge.label.class"></span></label>
  379. <input type="text" id="node-input-className" placeholder="segmental CSS class name(s) for widget"/>
  380. </div>
  381. </script>