@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.
Files changed (67) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +53 -0
  3. package/dist/css/app.d047b42b.css +1 -0
  4. package/dist/css/chunk-vendors.2378ce49.css +24 -0
  5. package/dist/fonts/materialdesignicons-webfont.3de8526e.woff +0 -0
  6. package/dist/fonts/materialdesignicons-webfont.477c6ab0.woff2 +0 -0
  7. package/dist/fonts/materialdesignicons-webfont.48a1ce0c.eot +0 -0
  8. package/dist/fonts/materialdesignicons-webfont.dfd403cf.ttf +0 -0
  9. package/dist/index.html +1 -0
  10. package/dist/js/app.854a8cd5.js +2 -0
  11. package/dist/js/app.854a8cd5.js.map +1 -0
  12. package/dist/js/chunk-vendors.174e8921.js +43 -0
  13. package/dist/js/chunk-vendors.174e8921.js.map +1 -0
  14. package/nodes/config/locales/en-US/ui_base.json +19 -0
  15. package/nodes/config/locales/en-US/ui_group.html +4 -0
  16. package/nodes/config/locales/en-US/ui_group.json +16 -0
  17. package/nodes/config/ui_base.html +807 -0
  18. package/nodes/config/ui_base.js +678 -0
  19. package/nodes/config/ui_group.html +55 -0
  20. package/nodes/config/ui_group.js +34 -0
  21. package/nodes/config/ui_page.html +84 -0
  22. package/nodes/config/ui_page.js +33 -0
  23. package/nodes/config/ui_theme.html +101 -0
  24. package/nodes/config/ui_theme.js +15 -0
  25. package/nodes/store/index.js +34 -0
  26. package/nodes/utils/index.js +35 -0
  27. package/nodes/widgets/locales/en-US/ui_button.html +7 -0
  28. package/nodes/widgets/locales/en-US/ui_button.json +24 -0
  29. package/nodes/widgets/locales/en-US/ui_chart.html +41 -0
  30. package/nodes/widgets/locales/en-US/ui_chart.json +17 -0
  31. package/nodes/widgets/locales/en-US/ui_dropdown.html +24 -0
  32. package/nodes/widgets/locales/en-US/ui_form.html +16 -0
  33. package/nodes/widgets/locales/en-US/ui_form.json +36 -0
  34. package/nodes/widgets/locales/en-US/ui_markdown.html +10 -0
  35. package/nodes/widgets/locales/en-US/ui_slider.html +9 -0
  36. package/nodes/widgets/locales/en-US/ui_switch.html +32 -0
  37. package/nodes/widgets/locales/en-US/ui_template.html +59 -0
  38. package/nodes/widgets/locales/en-US/ui_template.json +18 -0
  39. package/nodes/widgets/locales/en-US/ui_text.html +16 -0
  40. package/nodes/widgets/locales/en-US/ui_text_input.html +19 -0
  41. package/nodes/widgets/ui_button.html +146 -0
  42. package/nodes/widgets/ui_button.js +65 -0
  43. package/nodes/widgets/ui_chart.html +314 -0
  44. package/nodes/widgets/ui_chart.js +195 -0
  45. package/nodes/widgets/ui_dropdown.html +199 -0
  46. package/nodes/widgets/ui_dropdown.js +19 -0
  47. package/nodes/widgets/ui_form.html +368 -0
  48. package/nodes/widgets/ui_form.js +18 -0
  49. package/nodes/widgets/ui_markdown.html +134 -0
  50. package/nodes/widgets/ui_markdown.js +14 -0
  51. package/nodes/widgets/ui_notification.html +139 -0
  52. package/nodes/widgets/ui_notification.js +14 -0
  53. package/nodes/widgets/ui_radio_group.html +186 -0
  54. package/nodes/widgets/ui_radio_group.js +20 -0
  55. package/nodes/widgets/ui_slider.html +162 -0
  56. package/nodes/widgets/ui_slider.js +31 -0
  57. package/nodes/widgets/ui_switch.html +194 -0
  58. package/nodes/widgets/ui_switch.js +98 -0
  59. package/nodes/widgets/ui_table.html +149 -0
  60. package/nodes/widgets/ui_table.js +16 -0
  61. package/nodes/widgets/ui_template.html +283 -0
  62. package/nodes/widgets/ui_template.js +19 -0
  63. package/nodes/widgets/ui_text.html +358 -0
  64. package/nodes/widgets/ui_text.js +98 -0
  65. package/nodes/widgets/ui_text_input.html +141 -0
  66. package/nodes/widgets/ui_text_input.js +37 -0
  67. 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 &lt;style&gt; 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
+ &lt;span&gt;{{ msg?.payload }}&lt;/span&gt;
14
+ &lt;div v-if="msg?.payload === 'hello'"&gt;
15
+ I will be visible if msg.payload equals 'hello'
16
+ &lt;/div&gt;
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
+ &lt;v-btn @click="send({'hello': 'world'})"&gt;&lt;/v-btn&gt;
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
+ &lt;form @submit.prevent="submit"&gt;
31
+ &lt;v-text-field name="first" label="First Name"&gt;&lt;/v-text-field&gt;
32
+ &lt;v-text-field name="last" label="Last Name"&gt;&lt;/v-text-field&gt;
33
+ &lt;v-btn type="submit"&gt;&lt;/v-btn&gt;
34
+ &lt;/form&gt;
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&lt;WW&gt;></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>