@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,59 @@
|
|
|
1
|
+
<script type="text/markdown" data-help-name="ui-template">
|
|
2
|
+
Provides a means to render custom content or a set of node-styles depending on scope.
|
|
3
|
+
* HTML (including any <a href="https://vuetifyjs.com/en/components/all/" target="blank">
|
|
4
|
+
Vuetify component</a>) can be rendered in the Dashboard when the scope is set to **Widget in Group**
|
|
5
|
+
* Custom CSS can be included in the Dashboard when the scope is set to **Page style** or **Site style**.
|
|
6
|
+
If using this for CSS, you do not need to include any <style> tags, as these will be automatically added.
|
|
7
|
+
|
|
8
|
+
### Details - Widget in Group
|
|
9
|
+
When the scope is set to **Widget in Group**, the template will be rendered in the Dashboard as HTML.
|
|
10
|
+
You can render dynamic content using any VueJS data-binding expressions, (e.g. `v-if`, `v-for`)
|
|
11
|
+
and access incoming data to the node via the `msg` object, e.g:
|
|
12
|
+
```
|
|
13
|
+
<span>{{ msg?.payload }}</span>
|
|
14
|
+
<div v-if="msg?.payload === 'hello'">
|
|
15
|
+
I will be visible if msg.payload equals 'hello'
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
#### **send(msg)**
|
|
20
|
+
The template comes with two built-in functions the first of which is a `send(msg)` function that
|
|
21
|
+
can be used to send a `msg` from the template, e.g:
|
|
22
|
+
```html
|
|
23
|
+
<v-btn @click="send({'hello': 'world'})"></v-btn>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### **submit()**
|
|
27
|
+
The second built-in function can be attached to a form. The `msg` sent will be an object
|
|
28
|
+
representation of the `FormData` from the attached form, e.g:
|
|
29
|
+
```html
|
|
30
|
+
<form @submit.prevent="submit">
|
|
31
|
+
<v-text-field name="first" label="First Name"></v-text-field>
|
|
32
|
+
<v-text-field name="last" label="Last Name"></v-text-field>
|
|
33
|
+
<v-btn type="submit"></v-btn>
|
|
34
|
+
</form>
|
|
35
|
+
```
|
|
36
|
+
Would send the following `msg` when submitted:
|
|
37
|
+
```js
|
|
38
|
+
{ "first": <value>, "last": <value> }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Details - Site Scoped Style
|
|
42
|
+
When the scope is set to **Site Scoped Style**, the CSS in the template will be added to the `head` of all pages.
|
|
43
|
+
e.g.
|
|
44
|
+
```css
|
|
45
|
+
.my-class {
|
|
46
|
+
color: red;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Details - Page Scoped Style
|
|
51
|
+
When the scope is set to **Site Scoped Style**, the CSS in the template will be added to the `head` of the selected page.
|
|
52
|
+
e.g.
|
|
53
|
+
```css
|
|
54
|
+
.my-page1-only-class {
|
|
55
|
+
color: green;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ui-template": {
|
|
3
|
+
"label": {
|
|
4
|
+
"scope": "Type",
|
|
5
|
+
"local": "Widget",
|
|
6
|
+
"site-style": "CSS (All Pages)",
|
|
7
|
+
"page-style": "CSS (Single Page)",
|
|
8
|
+
"group": "Group",
|
|
9
|
+
"size": "Size",
|
|
10
|
+
"name": "Name",
|
|
11
|
+
"pass-through": "Pass through messages from input.",
|
|
12
|
+
"store-state": "Add output messages to stored state.",
|
|
13
|
+
"template": "Template",
|
|
14
|
+
"expand": "Expand",
|
|
15
|
+
"resend": "Reload last value on refresh."
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script type="text/html" data-help-name="ui-text">
|
|
2
|
+
<p>
|
|
3
|
+
Displays a non-editable text field on the user interface.
|
|
4
|
+
Each received <code>msg.payload</code> will update the value shown
|
|
5
|
+
alongside the (optional) label.
|
|
6
|
+
</p>
|
|
7
|
+
<p>
|
|
8
|
+
You can also customise the style of the text by enabling "Apply Custom Style".
|
|
9
|
+
This will show the following options:
|
|
10
|
+
<ul>
|
|
11
|
+
<li><b>Font:</b> Choose from a collection of pre-defined fonts.</li>
|
|
12
|
+
<li><b>Text Size:</b> Numerical value defining (in px) how big the text should display.</li>
|
|
13
|
+
<li><b>Text Color:</b> Color picker to define the color to display the text in.</li>
|
|
14
|
+
</ul>
|
|
15
|
+
</p>
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script type="text/html" data-help-name="ui-text-input">
|
|
2
|
+
<p>
|
|
3
|
+
Adds a single text input row to your dashboard, with a configurable "type" (text, password, etc).
|
|
4
|
+
</p>
|
|
5
|
+
<p>
|
|
6
|
+
Available modes include:
|
|
7
|
+
<ul>
|
|
8
|
+
<li><b>Text Input:</b> Standard text input field</li>
|
|
9
|
+
<li><b>E-Mail Address:</b> Offers validation on any input to ensure it's a valid e-mail address.</li>
|
|
10
|
+
<li><b>Password:</b> Hides the input from the user to offer protected input.</li>
|
|
11
|
+
<li><b>Number:</b> Adds an up/down selector to choose an integer.</li>
|
|
12
|
+
<li><b>Color Picker:</b> Displays a single block color selector</li>
|
|
13
|
+
<li><b>Time Picker:</b> HH:mm formatted time picker</li>
|
|
14
|
+
<li><b>Week Picker:</b> A Calendar widget to select a given full week, returns YYYY-W<WW>></li>
|
|
15
|
+
<li><b>Month Picker:</b> A Calendar widget to select a given month, returns YYYY-MM</li>
|
|
16
|
+
<li><b>Datetime Picker:</b> A Calendar widget to select full datetime value. Returns YYY-MM-DDTHH:mm</li>
|
|
17
|
+
</ul>
|
|
18
|
+
</p>
|
|
19
|
+
</script>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
(function () {
|
|
3
|
+
function hasProperty (obj, prop) {
|
|
4
|
+
return Object.prototype.hasOwnProperty.call(obj, prop)
|
|
5
|
+
}
|
|
6
|
+
RED.nodes.registerType('ui-button', {
|
|
7
|
+
category: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.label.category'),
|
|
8
|
+
color: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.colors.light'),
|
|
9
|
+
defaults: {
|
|
10
|
+
group: { type: 'ui-group', required: true },
|
|
11
|
+
name: { value: '' },
|
|
12
|
+
label: { value: 'button' },
|
|
13
|
+
order: { value: 0 },
|
|
14
|
+
width: {
|
|
15
|
+
value: 0,
|
|
16
|
+
validate: function (v) {
|
|
17
|
+
const width = v || 0
|
|
18
|
+
const currentGroup = $('#node-input-group').val() || this.group
|
|
19
|
+
const groupNode = RED.nodes.node(currentGroup)
|
|
20
|
+
const valid = !groupNode || +width <= +groupNode.width
|
|
21
|
+
$('#node-input-size').toggleClass('input-error', !valid)
|
|
22
|
+
return valid
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
height: { value: 0 },
|
|
26
|
+
passthru: { value: false },
|
|
27
|
+
tooltip: { value: '' },
|
|
28
|
+
color: { value: '' },
|
|
29
|
+
bgcolor: { value: '' },
|
|
30
|
+
className: { value: '' },
|
|
31
|
+
icon: { value: '' },
|
|
32
|
+
payload: { value: '', validate: (hasProperty(RED.validators, 'typedInput') ? RED.validators.typedInput('payloadType') : function (v) { return true }) },
|
|
33
|
+
payloadType: { value: 'str' },
|
|
34
|
+
topic: { value: 'topic', validate: (hasProperty(RED.validators, 'typedInput') ? RED.validators.typedInput('topicType') : function (v) { return true }) },
|
|
35
|
+
topicType: { value: 'msg' }
|
|
36
|
+
},
|
|
37
|
+
inputs: 1,
|
|
38
|
+
outputs: 1,
|
|
39
|
+
outputLabels: function () {
|
|
40
|
+
if (this.payloadType === 'str') {
|
|
41
|
+
return this.payload
|
|
42
|
+
} else {
|
|
43
|
+
return this.payloadType
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
icon: 'font-awesome/fa-hand-pointer-o',
|
|
47
|
+
paletteLabel: 'button',
|
|
48
|
+
label: function () { return this.name || (~this.label.indexOf('{' + '{') ? null : this.label) || 'button' },
|
|
49
|
+
labelStyle: function () { return this.name ? 'node_label_italic' : '' },
|
|
50
|
+
oneditprepare: function () {
|
|
51
|
+
$('#node-input-size').elementSizer({
|
|
52
|
+
width: '#node-input-width',
|
|
53
|
+
height: '#node-input-height',
|
|
54
|
+
group: '#node-input-group'
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
$('#node-input-payload').typedInput({
|
|
58
|
+
default: 'str',
|
|
59
|
+
typeField: $('#node-input-payloadType'),
|
|
60
|
+
types: ['str', 'num', 'bool', 'json', 'bin', 'date', 'flow', 'global']
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
$('#node-input-topic').typedInput({
|
|
64
|
+
default: 'str',
|
|
65
|
+
typeField: $('#node-input-topicType'),
|
|
66
|
+
types: ['str', 'msg', 'flow', 'global']
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip
|
|
70
|
+
$('.ui-node-popover-title').tooltip({
|
|
71
|
+
show: {
|
|
72
|
+
effect: 'slideDown',
|
|
73
|
+
delay: 150
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
})()
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<script type="text/html" data-template-name="ui-button">
|
|
82
|
+
<div class="form-row">
|
|
83
|
+
<label for="node-input-group"><i class="fa fa-table"></i> Group</label>
|
|
84
|
+
<input type="text" id="node-input-group">
|
|
85
|
+
</div>
|
|
86
|
+
<div class="form-row">
|
|
87
|
+
<label><i class="fa fa-object-group"></i> <span data-i18n="ui-button.label.size"></label>
|
|
88
|
+
<input type="hidden" id="node-input-width">
|
|
89
|
+
<input type="hidden" id="node-input-height">
|
|
90
|
+
<button class="editor-button" id="node-input-size"></button>
|
|
91
|
+
</div>
|
|
92
|
+
<!--<div class="form-row">
|
|
93
|
+
<label for="node-input-icon"><i class="fa fa-picture-o"></i> <span data-i18n="ui-button.label.icon"></label>
|
|
94
|
+
<input type="text" id="node-input-icon" data-i18n="[placeholder]ui-button.label.optionalIcon">
|
|
95
|
+
</div>-->
|
|
96
|
+
<div class="form-row">
|
|
97
|
+
<label for="node-input-label"><i class="fa fa-i-cursor"></i> Label</label>
|
|
98
|
+
<input type="text" id="node-input-label" data-i18n="[placeholder]ui-button.label.optionalLabel">
|
|
99
|
+
</div>
|
|
100
|
+
<div class="form-row">
|
|
101
|
+
<label for="node-input-className"><i class="fa fa-code"></i> Class</label>
|
|
102
|
+
<div style="display: inline;">
|
|
103
|
+
<input style="width: 70%;" type="text" id="node-input-className" placeholder="Optional CSS class name(s)" style="flex-grow: 1;">
|
|
104
|
+
<a
|
|
105
|
+
data-html="true"
|
|
106
|
+
title="Dynamic Property: Class names can also be set by sending a message to the node with a msg.topic of 'ui-property:class' and a payload containing the class name(s) to be applied. NOTE: classes set at runtime will be applied in addition to any class(es) set in the nodes class field."
|
|
107
|
+
class="red-ui-button ui-node-popover-title"
|
|
108
|
+
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;">
|
|
109
|
+
<i style="font-family: ui-serif;">fx</i>
|
|
110
|
+
</a>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<!--<div class="form-row">
|
|
114
|
+
<label for="node-input-tooltip"><i class="fa fa-info-circle"></i> <span data-i18n="ui-button.label.tooltip"></label>
|
|
115
|
+
<input type="text" id="node-input-tooltip" data-i18n="[placeholder]ui-button.label.optionalTooltip">
|
|
116
|
+
</div>
|
|
117
|
+
<div class="form-row">
|
|
118
|
+
<label for="node-input-color"><i class="fa fa-tint"></i> <span data-i18n="ui-button.label.color"></label>
|
|
119
|
+
<input type="text" id="node-input-color" data-i18n="[placeholder]ui-button.label.optionalColor">
|
|
120
|
+
</div>
|
|
121
|
+
<div class="form-row">
|
|
122
|
+
<label for="node-input-bgcolor"><i class="fa fa-tint"></i> <span data-i18n="ui-button.label.background"></label>
|
|
123
|
+
<input type="text" id="node-input-bgcolor" data-i18n="[placeholder]ui-button.label.optionalBackgroundColor">
|
|
124
|
+
</div>-->
|
|
125
|
+
<div class="form-row">
|
|
126
|
+
<label style="width:auto" for="node-input-payload"><i class="fa fa-envelope-o"></i> <span data-i18n="ui-button.label.whenClicked"></label>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="form-row">
|
|
129
|
+
<label for="node-input-payload" style="padding-left: 25px; margin-right: -25px"><span data-i18n="ui-button.label.payload"></label>
|
|
130
|
+
<input type="text" id="node-input-payload" style="width:70%">
|
|
131
|
+
<input type="hidden" id="node-input-payloadType">
|
|
132
|
+
</div>
|
|
133
|
+
<div class="form-row">
|
|
134
|
+
<label for="node-input-topic" style="padding-left: 25px; margin-right: -25px"><span data-i18n="ui-button.label.topic"></label>
|
|
135
|
+
<input type="text" id="node-input-topic" style="width:70%">
|
|
136
|
+
<input type="hidden" id="node-input-topicType">
|
|
137
|
+
</div>
|
|
138
|
+
<!--<div class="form-row">
|
|
139
|
+
<label style="width:auto" for="node-input-passthru"><i class="fa fa-arrow-right"></i> <span data-i18n="ui-button.label.emulateClick"></label>
|
|
140
|
+
<input type="checkbox" id="node-input-passthru" style="display:inline-block; width:auto; vertical-align:top;">
|
|
141
|
+
</div>-->
|
|
142
|
+
<div class="form-row">
|
|
143
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></label>
|
|
144
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
|
145
|
+
</div>
|
|
146
|
+
</script>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
function ButtonNode (config) {
|
|
3
|
+
// create node in Node-RED
|
|
4
|
+
RED.nodes.createNode(this, config)
|
|
5
|
+
const node = this
|
|
6
|
+
|
|
7
|
+
// which group are we rendering this widget
|
|
8
|
+
const group = RED.nodes.getNode(config.group)
|
|
9
|
+
|
|
10
|
+
const evts = {
|
|
11
|
+
onAction: true,
|
|
12
|
+
beforeSend: async function (msg) {
|
|
13
|
+
let error = null
|
|
14
|
+
|
|
15
|
+
// retrieve the payload we're sending from this button
|
|
16
|
+
let payloadType = config.payloadType
|
|
17
|
+
let payload = config.payload
|
|
18
|
+
|
|
19
|
+
if (payloadType === 'flow' || payloadType === 'global') {
|
|
20
|
+
try {
|
|
21
|
+
const parts = RED.util.normalisePropertyExpression(payload)
|
|
22
|
+
if (parts.length === 0) {
|
|
23
|
+
throw new Error()
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {
|
|
26
|
+
node.warn('Invalid payload property expression - defaulting to node id')
|
|
27
|
+
payload = node.id
|
|
28
|
+
payloadType = 'str'
|
|
29
|
+
}
|
|
30
|
+
} else if (payloadType === 'date') {
|
|
31
|
+
payload = Date.now()
|
|
32
|
+
} else {
|
|
33
|
+
try {
|
|
34
|
+
payload = RED.util.evaluateNodeProperty(payload, payloadType, node)
|
|
35
|
+
} catch (err) {
|
|
36
|
+
error = err
|
|
37
|
+
if (payloadType === 'bin') {
|
|
38
|
+
node.error('Badly formatted buffer')
|
|
39
|
+
} else {
|
|
40
|
+
node.error(err, payload)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
msg.payload = payload
|
|
46
|
+
|
|
47
|
+
if (!error) {
|
|
48
|
+
return msg
|
|
49
|
+
} else {
|
|
50
|
+
node.error(error)
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// inform the dashboard UI that we are adding this node
|
|
57
|
+
if (group) {
|
|
58
|
+
group.register(node, config, evts)
|
|
59
|
+
} else {
|
|
60
|
+
node.error('No group configured')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
RED.nodes.registerType('ui-button', ButtonNode)
|
|
65
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
#ui-chart-colors input[type="color"] {
|
|
3
|
+
font-weight: bold;
|
|
4
|
+
}
|
|
5
|
+
#ui-chart-colors input[type="color"]::-webkit-color-swatch,
|
|
6
|
+
#ui-chart-colors input[type="color"]::-moz-color-swatch {
|
|
7
|
+
border: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.node-chart-properties {
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
width: 70%;
|
|
14
|
+
gap: 16px;
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
}
|
|
17
|
+
.node-chart-properties .red-ui-typedInput-container {
|
|
18
|
+
flex-grow: 1;
|
|
19
|
+
}
|
|
20
|
+
.node-chart-property {
|
|
21
|
+
display: flex;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
align-items: center;
|
|
24
|
+
flex-grow: 1;
|
|
25
|
+
}
|
|
26
|
+
.node-chart-property label {
|
|
27
|
+
margin: 0;
|
|
28
|
+
}
|
|
29
|
+
.node-chart-property#node-container-category {
|
|
30
|
+
width: 100%;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
|
|
34
|
+
<script type="text/javascript">
|
|
35
|
+
(function () {
|
|
36
|
+
function hexToRgb (hex) {
|
|
37
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
38
|
+
return result
|
|
39
|
+
? {
|
|
40
|
+
r: parseInt(result[1], 16),
|
|
41
|
+
g: parseInt(result[2], 16),
|
|
42
|
+
b: parseInt(result[3], 16)
|
|
43
|
+
}
|
|
44
|
+
: null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
RED.nodes.registerType('ui-chart', {
|
|
48
|
+
category: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.label.category'),
|
|
49
|
+
color: RED._('@flowforge/node-red-dashboard/ui-base:ui-base.colors.medium'),
|
|
50
|
+
defaults: {
|
|
51
|
+
group: { type: 'ui-group', required: true },
|
|
52
|
+
name: { value: '' },
|
|
53
|
+
label: { value: 'chart' },
|
|
54
|
+
order: { value: Number.MAX_SAFE_INTEGER },
|
|
55
|
+
chartType: { value: 'line' },
|
|
56
|
+
category: { value: 'topic' },
|
|
57
|
+
categoryType: { value: 'msg' },
|
|
58
|
+
xAxisProperty: { value: null },
|
|
59
|
+
xAxisPropertyType: { value: 'msg' },
|
|
60
|
+
yAxisProperty: { value: null },
|
|
61
|
+
xAxisType: { value: 'time' },
|
|
62
|
+
showLegend: { value: true },
|
|
63
|
+
removeOlder: { value: 1, validate: RED.validators.number(), required: true },
|
|
64
|
+
removeOlderUnit: { value: '3600', required: true },
|
|
65
|
+
removeOlderPoints: { value: '', validate: function (value) { return value === '' || RED.validators.number() } },
|
|
66
|
+
colors: { value: ['#1F77B4', '#AEC7E8', '#FF7F0E', '#2CA02C', '#98DF8A', '#D62728', '#FF9896', '#9467BD', '#C5B0D5'] },
|
|
67
|
+
width: {
|
|
68
|
+
value: 0,
|
|
69
|
+
validate: function (v) {
|
|
70
|
+
const width = v || 0
|
|
71
|
+
const currentGroup = $('#node-input-group').val() || this.group
|
|
72
|
+
const groupNode = RED.nodes.node(currentGroup)
|
|
73
|
+
const valid = !groupNode || +width <= +groupNode.width
|
|
74
|
+
$('#node-input-size').toggleClass('input-error', !valid)
|
|
75
|
+
return valid
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
height: { value: 0 },
|
|
79
|
+
className: { value: '' }
|
|
80
|
+
},
|
|
81
|
+
inputs: 1,
|
|
82
|
+
outputs: 1,
|
|
83
|
+
inputLabels: function () { return this.chartType },
|
|
84
|
+
outputLabels: ['chart state'],
|
|
85
|
+
icon: 'font-awesome/fa-line-chart',
|
|
86
|
+
align: 'right',
|
|
87
|
+
paletteLabel: 'chart',
|
|
88
|
+
label: function () {
|
|
89
|
+
return this.name || (~this.label.indexOf('{' + '{') ? null : this.label) || this.mode + ' chart'
|
|
90
|
+
},
|
|
91
|
+
labelStyle: function () { return this.name ? 'node_label_italic' : '' },
|
|
92
|
+
oneditprepare: function () {
|
|
93
|
+
const propertyType = { value: 'property', label: 'key:' }
|
|
94
|
+
|
|
95
|
+
$('#node-input-size').elementSizer({
|
|
96
|
+
width: '#node-input-width',
|
|
97
|
+
height: '#node-input-height',
|
|
98
|
+
group: '#node-input-group'
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// use jQuery UI tooltip to convert the plain old title attribute to a nice tooltip
|
|
102
|
+
$('.ui-node-popover-title').tooltip({
|
|
103
|
+
show: {
|
|
104
|
+
effect: 'slideDown',
|
|
105
|
+
delay: 150
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// set value options for chart's type
|
|
110
|
+
$('#node-input-chartType').typedInput({
|
|
111
|
+
type: 'chartType',
|
|
112
|
+
types: [{
|
|
113
|
+
value: 'line',
|
|
114
|
+
options: [
|
|
115
|
+
{ value: 'line', label: 'Line' },
|
|
116
|
+
{ value: 'bar', label: 'Bar' },
|
|
117
|
+
{ value: 'scatter', label: 'Scatter' }
|
|
118
|
+
]
|
|
119
|
+
}],
|
|
120
|
+
typeField: '#node-input-chartTypeType'
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// line = time, linear, log
|
|
124
|
+
// scatter = time, linear, log
|
|
125
|
+
// bar = categorical (or just hide)
|
|
126
|
+
|
|
127
|
+
// provide options for x-axis type
|
|
128
|
+
$('#node-input-xAxisType').typedInput()
|
|
129
|
+
|
|
130
|
+
$('#node-input-category').typedInput({
|
|
131
|
+
default: 'msg',
|
|
132
|
+
typeField: $('#node-input-categoryType'),
|
|
133
|
+
types: ['msg', 'str', propertyType]
|
|
134
|
+
})
|
|
135
|
+
$('#node-input-xAxisProperty').typedInput({
|
|
136
|
+
default: propertyType.value,
|
|
137
|
+
typeField: $('#node-input-xAxisPropertyType'),
|
|
138
|
+
types: [propertyType]
|
|
139
|
+
})
|
|
140
|
+
$('#node-input-yAxisProperty').typedInput({
|
|
141
|
+
default: propertyType.value,
|
|
142
|
+
typeField: $('#node-input-yAxisPropertyType'),
|
|
143
|
+
types: [propertyType]
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// handle event when chart's type is changed
|
|
147
|
+
$('#node-input-chartType').on('change', (evt) => {
|
|
148
|
+
const value = $('#node-input-chartType').typedInput('value')
|
|
149
|
+
|
|
150
|
+
if (value === 'line' || value === 'scatter') {
|
|
151
|
+
// for line and scatter
|
|
152
|
+
// types - time, linear
|
|
153
|
+
$('#node-input-xAxisType').typedInput('types', [{
|
|
154
|
+
value: 'linear',
|
|
155
|
+
options: [
|
|
156
|
+
{ value: 'time', label: 'Timescale' },
|
|
157
|
+
{ value: 'linear', label: 'Linear' }
|
|
158
|
+
]
|
|
159
|
+
}])
|
|
160
|
+
$('#node-input-xAxisType').typedInput('type', 'time')
|
|
161
|
+
// show x-axis property setting
|
|
162
|
+
$('#node-container-xAxisProperty').show()
|
|
163
|
+
// show x-axis limit options
|
|
164
|
+
$('#x-axis-show').show()
|
|
165
|
+
} else {
|
|
166
|
+
// for bar
|
|
167
|
+
// types - categorical
|
|
168
|
+
$('#node-input-xAxisType').typedInput('types', [{
|
|
169
|
+
value: 'linear',
|
|
170
|
+
options: [
|
|
171
|
+
{ value: 'category', label: 'Categorical' }
|
|
172
|
+
]
|
|
173
|
+
}])
|
|
174
|
+
$('#node-input-xAxisType').typedInput('type', 'category')
|
|
175
|
+
// show x-axis property setting
|
|
176
|
+
$('#node-container-xAxisProperty').hide()
|
|
177
|
+
// hide x-axis limit options
|
|
178
|
+
$('#x-axis-show').hide()
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// Series Color Pickers
|
|
183
|
+
const setColor = function (id, value) {
|
|
184
|
+
$(id).val(value)
|
|
185
|
+
$(id).css('background-color', value)
|
|
186
|
+
const rgb = hexToRgb(value)
|
|
187
|
+
const level = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000
|
|
188
|
+
const textColor = (level >= 128) ? '#111111' : '#eeeeee'
|
|
189
|
+
$(id).css('color', textColor)
|
|
190
|
+
}
|
|
191
|
+
$('.series-color').on('change', function () {
|
|
192
|
+
setColor('#' + $(this).attr('id'), $(this).val())
|
|
193
|
+
})
|
|
194
|
+
const defaultColors = ['#1F77B4', '#AEC7E8', '#FF7F0E', '#2CA02C', '#98DF8A', '#D62728', '#FF9896', '#9467BD', '#C5B0D5']
|
|
195
|
+
|
|
196
|
+
if (this.colors) {
|
|
197
|
+
for (let i = 0; i < this.colors.length; i++) {
|
|
198
|
+
const value = this.colors[i] || defaultColors[i]
|
|
199
|
+
setColor('#node-input-color' + (i + 1), value)
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
for (let c = 0; c < defaultColors.length; c++) {
|
|
203
|
+
setColor('#node-input-color' + (c + 1), defaultColors[c])
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
oneditsave: function () {
|
|
208
|
+
const colors = []
|
|
209
|
+
for (let i = 0; i < 9; i++) {
|
|
210
|
+
const color = $('#node-input-color' + (i + 1)).val()
|
|
211
|
+
if (color) {
|
|
212
|
+
colors.push(color)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this.colors = colors
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
})()
|
|
219
|
+
</script>
|
|
220
|
+
|
|
221
|
+
<script type="text/html" data-template-name="ui-chart">
|
|
222
|
+
<div class="form-row">
|
|
223
|
+
<label for="node-input-group"><i class="fa fa-table"></i> Group</label>
|
|
224
|
+
<input type="text" id="node-input-group">
|
|
225
|
+
</div>
|
|
226
|
+
<div class="form-row">
|
|
227
|
+
<label><i class="fa fa-object-group"></i> Size</label>
|
|
228
|
+
<input type="hidden" id="node-input-width">
|
|
229
|
+
<input type="hidden" id="node-input-height">
|
|
230
|
+
<button class="editor-button" id="node-input-size"></button>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="form-row">
|
|
233
|
+
<label for="node-input-label"><i class="fa fa-i-cursor"></i> Label</label>
|
|
234
|
+
<input type="text" id="node-input-label" data-i18n="[placeholder]ui-chart.label.optionalChartTitle">
|
|
235
|
+
</div>
|
|
236
|
+
<div class="form-row">
|
|
237
|
+
<label for="node-input-className"><i class="fa fa-code"></i> Class</label>
|
|
238
|
+
<div style="display: inline;">
|
|
239
|
+
<input style="width: 70%;" type="text" id="node-input-className" placeholder="Optional CSS class name(s)" style="flex-grow: 1;">
|
|
240
|
+
<a
|
|
241
|
+
data-html="true"
|
|
242
|
+
title="Dynamic Property: Class names can also be set by sending a message to the node with a msg.topic of 'ui-property:class' and a payload containing the class name(s) to be applied. NOTE: classes set at runtime will be applied in addition to any class(es) set in the nodes class field."
|
|
243
|
+
class="red-ui-button ui-node-popover-title"
|
|
244
|
+
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;">
|
|
245
|
+
<i style="font-family: ui-serif;">fx</i>
|
|
246
|
+
</a>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="form-row" style="display: flex; align-items: center; gap: 36px;">
|
|
250
|
+
<div>
|
|
251
|
+
<label for="node-input-chartType"><i class="fa fa-bookmark"></i> Type</label>
|
|
252
|
+
<input type="text" id="node-input-chartType">
|
|
253
|
+
<input type="hidden" id="node-input-chartTypeType">
|
|
254
|
+
</div>
|
|
255
|
+
<div>
|
|
256
|
+
<label style="width:auto" for="node-input-showLegend"><i class="fa fa-th-list"></i> Show Legend </label>
|
|
257
|
+
<input type="checkbox" checked id="node-input-showLegend" style="display: inline-block; width: auto; margin: 0px 0px 0px 4px;">
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
<div class="form-row">
|
|
261
|
+
<label for="node-input-xAxisType" data-i18n="ui-chart.label.xType"></label></label>
|
|
262
|
+
<input type="text" id="node-input-xAxisType">
|
|
263
|
+
<input type="hidden" id="node-input-xAxisTypeType">
|
|
264
|
+
</div>
|
|
265
|
+
<div class="form-row" id="x-axis-show">
|
|
266
|
+
<label for="node-input-removeOlder" data-i18n="ui-chart.label.xLimit"></label>
|
|
267
|
+
<label for="node-input-removeOlder" style="width:auto" data-i18n="ui-chart.label.last"></label>
|
|
268
|
+
<input type="text" id="node-input-removeOlder" style="width:50px;">
|
|
269
|
+
<select id="node-input-removeOlderUnit" style="width:100px;">
|
|
270
|
+
<option value="1" data-i18n="ui-chart.label.seconds"></option>
|
|
271
|
+
<option value="60" data-i18n="ui-chart.label.minutes"></option>
|
|
272
|
+
<option value="3600" data-i18n="ui-chart.label.hours"></option>
|
|
273
|
+
<option value="86400" data-i18n="ui-chart.label.days"></option>
|
|
274
|
+
<option value="604800" data-i18n="ui-chart.label.weeks"></option>
|
|
275
|
+
</select>
|
|
276
|
+
<label for="node-input-removeOlderPoints" style="width:auto; margin-left:10px; margin-right:10px;" data-i18n="ui-chart.label.or"></label>
|
|
277
|
+
<input type="text" id="node-input-removeOlderPoints" style="width:60px;" placeholder="1000">
|
|
278
|
+
<span style="margin-left:5px;" data-i18n="ui-chart.label.points"></span>
|
|
279
|
+
</div>
|
|
280
|
+
<div class="form-row">
|
|
281
|
+
<label>Properties</label>
|
|
282
|
+
<div id="node-container-axisKeys" class="node-chart-properties">
|
|
283
|
+
<div id="node-container-category" class="node-chart-property">
|
|
284
|
+
<label style="width: auto" for="node-input-category">Series:</label>
|
|
285
|
+
<input style="flex-grow: 1" type="text" id="node-input-category" placeholder="topic">
|
|
286
|
+
<input type="hidden" id="node-input-categoryType">
|
|
287
|
+
</div>
|
|
288
|
+
<div id="node-container-xAxisProperty" class="node-chart-property">
|
|
289
|
+
<label style="width: auto" for="node-input-xAxisProperty">X:</label>
|
|
290
|
+
<input style="flex-grow: 1" type="text" id="node-input-xAxisProperty" placeholder="msg.payload[key] or msg.payload[n][key]">
|
|
291
|
+
</div>
|
|
292
|
+
<div id="node-container-yAxisProperty" class="node-chart-property">
|
|
293
|
+
<label style="width: auto" for="node-input-yAxisProperty">Y:</label>
|
|
294
|
+
<input style="flex-grow: 1" type="text" id="node-input-yAxisProperty" placeholder="msg.payload[key] or msg.payload[n][key]">
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="form-row" id="ui-chart-colors">
|
|
299
|
+
<label for="node-input-color1" data-i18n="ui-chart.label.seriesColors"></label>
|
|
300
|
+
<input type="color" id="node-input-color1" class="series-color" style="width:100px;"/>
|
|
301
|
+
<input type="color" id="node-input-color2" class="series-color" style="width:100px;"/>
|
|
302
|
+
<input type="color" id="node-input-color3" class="series-color" style="width:100px;"/>
|
|
303
|
+
<div style="margin-top:5px; margin-left:104px;">
|
|
304
|
+
<input type="color" id="node-input-color4" class="series-color" style="width:100px;"/>
|
|
305
|
+
<input type="color" id="node-input-color5" class="series-color" style="width:100px;"/>
|
|
306
|
+
<input type="color" id="node-input-color6" class="series-color" style="width:100px;"/>
|
|
307
|
+
</div>
|
|
308
|
+
<div style="margin-top:5px; margin-left:104px;">
|
|
309
|
+
<input type="color" id="node-input-color7" class="series-color" style="width:100px;"/>
|
|
310
|
+
<input type="color" id="node-input-color8" class="series-color" style="width:100px;"/>
|
|
311
|
+
<input type="color" id="node-input-color9" class="series-color" style="width:100px;"/>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</script>
|