Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI Gauge - Dynamic Properties Support #1238

Merged
merged 11 commits into from
Sep 3, 2024
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this state "only applicable to for 3/4 and Half gauges"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're correct and also "Style", "Units" and "Icon".

I've updated them in the markdown

dynamic: true
Suffix:
description: Text to be shown _after_ the value in the middle of the gauge.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this state "only applicable to for 3/4 and Half gauges"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated

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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be gtype?

And what is gstyle / where is that documented?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should be gtype and documented gstyle

<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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to mention "Not used by tile gauge"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understood the range is not necessary for the "Tile Gauge" if so we need to conditionally render when only for the other gauge types in NR

Screenshot 2024-09-03 at 23 30 01

<dt class="optional">max <span class="property-type">number</span></dt>
<dd>Change the maximum value the gauge supports</dd>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to mention "Not used by tile gauge"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned above we may need to conditionally show the range except the "Tile Gauge" type it we mention it here

<dt class="optional">prefix <span class="property-type">string</span></dt>
<dd>Change the text rendered after the Gauge's value</dd>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this mean to say "before" or "above? (it is the exact same text as the suffix has

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this state "only applicable to for 3/4 and Half gauges"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, also "gstyle", "units" and "icon". Updated them as well

<dt class="optional">suffix <span class="property-type">string</span></dt>
<dd>Change the text rendered after the Gauge's value</dd>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this state "only applicable to for 3/4 and Half gauges"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're correct. I've corrected it in the helper documentation

<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" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced conditional gauge type from dynamic component except 3/4 and half as doesn't support for dynamic component

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all gauge types seem to be reacting to dynamic prop changes. What do you mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took all three "ui-gauge-battery", "ui-gauge-tank" and "ui-gauge-dial" into one dynamic component Vue inbuilt element just to reduce few lines of codes and to avoid v-else-if as component element handles them naturally but couldn't include ui-gauge-dial to that as it looses reactivity and didn't get rendered properly

<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-/, '')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced return this.props.icon?.replace(/^mdi-/, '') from return this.getProperty('icon') as material icon maker is not being used

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as material icon maker is not being used

It is. Users can pass in mdi-<icon> or <icon> directly and we should support that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it currently seems to support both formats. Stripping the 'mdi-' prefix seems to have no effect on the outcome. Tested with only a couple of icons but proved the point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joepavitt @cstns
To be more clearer this dynamic icon only supports "Half Gauge" and "3/4 Gauge" and the functionality is already implemented in UIGaugeDialog.vue which is a child component comes under the UIGauage.vue therefore I used icon () computed property to fetch dynamic icon changes

Screenshot 2024-09-03 at 13 58 45

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
Loading