Skip to content

Commit

Permalink
Merge pull request #1238 from FlowFuse/1180-dynamic-properties-ui-gauge
Browse files Browse the repository at this point in the history
UI Gauge - Dynamic Properties Support
  • Loading branch information
gayanSandamal authored Sep 3, 2024
2 parents dc0b261 + d035a58 commit 3db5503
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 33 deletions.
2 changes: 1 addition & 1 deletion docs/components/DynamicPropsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<code v-else>{{ value.structure[0] }}</code>
</td>
<td>
<div v-if="value.examples" style="display: flex; gap: 4px; align-items: center;">
<div v-if="value.examples" style="display: flex; gap: 4px; align-items: center; flex-wrap: wrap;">
<template v-for="(example, i) in value.examples" :key="example">
<code >{{ example }}</code>
<span v-if="i !== value.examples?.length - 1">|</span>
Expand Down
84 changes: 74 additions & 10 deletions docs/nodes/widgets/ui-gauge.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,75 @@ description: Display real-time metrics with ui-gauge in Node-RED Dashboard 2.0 f
props:
Group: Defines which group of the UI Dashboard this widget will render in.
Size: Controls the width of the dropdown with respect to the parent group. Maximum value is the width of the group.
Type: Defines the shape of the gauge, "Tile", "Battery", "Water Tank", "Half Gauge" or "3/4 Gauge"
Style: Defines the style of arc rendered, "Needle" or "Rounded"
Range (min): The smallest value that can be shown on the gauge
Range (max): The largest value that can be shown on the gauge
Segments: Defines the barriers by which the arc is color coded. These segments can also be shown on the gauge.
Label: Text shown above the gauge, labelling what the gauge is showing.
Prefix: Text to be added _before_ the value in the middle of the gauge.
Suffix: Text to be shown _after_ the value in the middle of the gauge.
Units: Small text to be shown below the value in the middle of the gauge.
Icon: Icon to be shown below the value in the middle of the gauge. Uses <a href="https://pictogrammers.com/library/mdi/">Material Designs Icon</a>, no need to include the <code>mdi-</code> prefix.
Type:
description: Defines the shape of the gauge, "Tile", "Battery", "Water Tank", "Half Gauge" or "3/4 Gauge"
dynamic: true
Style:
description: Defines the style of arc rendered, "Needle" or "Rounded"
dynamic: true
Range (min):
description: The smallest value that can be shown on the gauge
dynamic: true
Range (max):
description: The largest value that can be shown on the gauge
dynamic: true
Segments:
description: Defines the barriers by which the arc is color coded. These segments can also be shown on the gauge.
dynamic: true
Label:
description: Text shown above the gauge, labelling what the gauge is showing.
dynamic: true
Prefix:
description: Text to be added _before_ the value in the middle of the gauge.
dynamic: true
Suffix:
description: Text to be shown _after_ the value in the middle of the gauge.
dynamic: true
Units:
description: Small text to be shown below the value in the middle of the gauge.
dynamic: true
Icon:
description: Icon to be shown below the value in the middle of the gauge. Uses <a href="https://pictogrammers.com/library/mdi/">Material Designs Icon</a>, no need to include the <code>mdi-</code> prefix.
dynamic: true
Sizes (Gauge): (px) How thick the arc and backdrop of the gauge are rendered.
Sizes (Gap): (px) How big the gap/padding is between the Gauge and the "Segments"
Sizes (Segments): (px) How thick the segments are rendered.
controls:
enabled:
example: true | false
description: Allow control over whether or not the number-input is enabled
dynamic:
Label:
payload: msg.ui_update.title
structure: ["String"]
Icon:
payload: msg.ui_update.icon
structure: ["String"]
Type:
payload: msg.ui_update.gtype
structure: ["String"]
examples: ['gauge-tile', 'gauge-battery', 'gauge-tank', 'gauge-half', 'gauge-34']
Style:
payload: msg.ui_update.gstyle
structure: ["String"]
Min:
payload: msg.ui_update.min
structure: ["Number"]
Max:
payload: msg.ui_update.max
structure: ["Number"]
Segments:
payload: msg.ui_update.segments
structure: ["Array<{color: String, from: Number}>"]
Prefix:
payload: msg.ui_update.prefix
structure: ["String"]
Suffix:
payload: msg.ui_update.suffix
structure: ["String"]
Units:
payload: msg.ui_update.units
structure: ["String"]
---


Expand Down Expand Up @@ -43,6 +99,14 @@ Values for the gauges can be set by sending a numerical value in `msg.payload`.

<PropsTable/>

## Dynamic Properties

<DynamicPropsTable/>

## Controls

<ControlsTable/>

## Examples

### Half Gauge - Rounded
Expand Down
37 changes: 37 additions & 0 deletions nodes/widgets/locales/en-US/ui_gauge.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,41 @@ <h3>Properties (Half &amp; 3/4 Gauges Only)</h3>
<dt>Sizes (Segments) <span class="property-type">number</span></dt>
<dd>A numerical value, in pixels, that defines the thickness of the segments rendered in the gauge.</dd>
</dl>
<h3>Dynamic Properties (Inputs)</h3>
<p>Any of the following can be appended to a <code>msg.ui_update</code> in order to override or set properties on this node at runtime.</p>
<dl class="message-properties">
<dt class="optional">title <span class="property-type">string</span></dt>
<dd>Update the label rendered above the Gauge</dd>
<dt class="optional">segments <span class="property-type">array</span></dt>
<dd>
Change the options available in the dropdown at runtime
<ul>
<li><code>Array&lt;{color: String, from: Number}&gt;</code></li>
</ul>
</dd>
<dt class="optional">gstyle <span class="property-type">see detail</span></dt>
<dd>Modify the type of Gauge rendered, with the following options:
<ul>
<li><code>gauge-battery</code></li>
<li><code>gauge-34</code></li>
<li><code>gauge-half</code></li>
<li><code>gauge-tile</code></li>
<li><code>gauge-tank</code></li>
</ul>
</dd>
<dt class="optional">min <span class="property-type">number</span></dt>
<dd>Change the minimum value the gauge supports</dd>
<dt class="optional">max <span class="property-type">number</span></dt>
<dd>Change the maximum value the gauge supports</dd>
<dt class="optional">prefix <span class="property-type">string</span></dt>
<dd>Change the text rendered after the Gauge's value</dd>
<dt class="optional">suffix <span class="property-type">string</span></dt>
<dd>Change the text rendered after the Gauge's value</dd>
<dt class="optional">units <span class="property-type">array</span></dt>
<dd>Controls the "unit" display underneath the gauge's value for 3/4 and Half gauges.</dd>
<dt class="optional">icon <span class="property-type">string</span></dt>
<dd>Modify which icon is rendered within the gauge (must be a Material Design icon)</dd>
<dt class="optional">class <span class="property-type">string</span></dt>
<dd>Add a CSS class, or more, to the Button at runtime.</dd>
</dl>
</script>
51 changes: 50 additions & 1 deletion nodes/widgets/ui_gauge.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const statestore = require('../store/state.js')
const { appendTopic } = require('../utils/index.js')

module.exports = function (RED) {
function GaugeNode (config) {
RED.nodes.createNode(this, config)
Expand All @@ -7,7 +10,53 @@ module.exports = function (RED) {
const group = RED.nodes.getNode(config.group)

const evts = {
onChange: true
beforeSend: async function (msg) {
const updates = msg.ui_update
if (updates) {
if (typeof updates.title !== 'undefined') {
// dynamically set "label" property
statestore.set(group.getBase(), node, msg, 'title', updates.title)
}
if (typeof updates.gtype !== 'undefined') {
// dynamically set "gauge type" property
statestore.set(group.getBase(), node, msg, 'gtype', updates.gtype)
}
if (typeof updates.gstyle !== 'undefined') {
// dynamically set "gauge style" property
statestore.set(group.getBase(), node, msg, 'gstyle', updates.gstyle)
}
if (typeof updates.prefix !== 'undefined') {
// dynamically set "prefix" property
statestore.set(group.getBase(), node, msg, 'prefix', updates.prefix)
}
if (typeof updates.suffix !== 'undefined') {
// dynamically set "suffix" property
statestore.set(group.getBase(), node, msg, 'suffix', updates.suffix)
}
if (typeof updates.units !== 'undefined') {
// dynamically set "units" property
statestore.set(group.getBase(), node, msg, 'units', updates.units)
}
if (typeof updates.icon !== 'undefined') {
// dynamically set "icon" property
statestore.set(group.getBase(), node, msg, 'icon', updates.icon)
}
if (typeof updates.segments !== 'undefined') {
// dynamically set "segments" property
statestore.set(group.getBase(), node, msg, 'segments', updates.segments)
}
if (typeof updates.min !== 'undefined') {
// dynamically set "min" property
statestore.set(group.getBase(), node, msg, 'min', updates.min)
}
if (typeof updates.max !== 'undefined') {
// dynamically set "max" property
statestore.set(group.getBase(), node, msg, 'max', updates.max)
}
}
msg = await appendTopic(RED, config, node, msg)
return msg
}
}

// ensure values are numerical, not strings
Expand Down
71 changes: 65 additions & 6 deletions ui/src/widgets/ui-gauge/UIGauge.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<template>
<ui-gauge-tile v-if="props.gtype === 'gauge-tile'" :id="id" :props="props" :value="value" />
<ui-gauge-battery v-else-if="props.gtype === 'gauge-battery'" :id="id" :props="props" :value="value" />
<ui-gauge-tank v-else-if="props.gtype === 'gauge-tank'" :id="id" :props="props" :value="value" />
<ui-gauge-dial v-else :id="id" :props="props" :value="value" />
<component :is="`ui-${gtype}`" v-if="['gauge-tile', 'gauge-battery', 'gauge-tank'].includes(gtype)" :id="id" :props="dynamicProps" :value="value" />
<ui-gauge-dial v-else :id="id" :props="dynamicProps" :value="value" />
</template>

<script>
Expand Down Expand Up @@ -32,12 +30,73 @@ export default {
value: function () {
return this.messages[this.id]?.payload
},
title () {
return this.getProperty('title')
},
gtype () {
return this.getProperty('gtype')
},
gstyle () {
return this.getProperty('gstyle')
},
prefix () {
return this.getProperty('prefix')
},
suffix () {
return this.getProperty('suffix')
},
units () {
return this.getProperty('units')
},
icon () {
return this.props.icon?.replace(/^mdi-/, '')
return this.getProperty('icon')
},
segments () {
return this.getProperty('segments')
},
min () {
return this.getProperty('min')
},
max () {
return this.getProperty('max')
},
dynamicProps () {
const props = {
...this.props,
title: this.title,
gtype: this.gtype,
gstyle: this.gstyle,
prefix: this.prefix,
suffix: this.suffix,
units: this.units,
icon: this.icon,
segments: this.segments,
min: this.min,
max: this.max
}
return props
}
},
created () {
this.$dataTracker(this.id)
this.$dataTracker(this.id, null, null, this.onDynamicProperties)
},
methods: {
onDynamicProperties (msg) {
const updates = msg.ui_update
if (!updates) {
return
}
this.updateDynamicProperty('title', updates.title)
this.updateDynamicProperty('gtype', updates.gtype)
this.updateDynamicProperty('gstyle', updates.gstyle)
this.updateDynamicProperty('prefix', updates.prefix)
this.updateDynamicProperty('suffix', updates.suffix)
this.updateDynamicProperty('units', updates.units)
this.updateDynamicProperty('icon', updates.icon)
this.updateDynamicProperty('segments', updates.segments)
this.updateDynamicProperty('min', updates.min)
this.updateDynamicProperty('max', updates.max)
}
}
}
</script>
Expand Down
Loading

0 comments on commit 3db5503

Please sign in to comment.