123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- <style>
- input.gauge-color {
- width: 100px;
- text-align: center;
- }
- input.gauge-color::-webkit-color-swatch {
- border: none;
- }
- </style>
-
- <script type="text/javascript">
- function validateGaugeSegments (segments) {
- $('#node-input-validation-segments').hide()
- const min = parseFloat(this.min)
- const max = parseFloat(this.max)
-
- // check we havea segment covering the smallest values of the gauge
- let minCovered = false
- for (let i = 0; i < segments.length; i++) {
- const from = parseFloat(segments[i].from)
- if (from <= min) {
- minCovered = true
- }
- }
- if (!minCovered) {
- $('#node-input-validation-segments').text("It's advised to make sure your first segment's 'from' value and the gauge's 'min' value are the same.").show()
- }
- // check if we have any extra, unneccessary, segments
- let extras = false
- for (let i = 0; i < segments.length; i++) {
- const from = parseFloat(segments[i].from)
- if (from > max) {
- extras = true
- }
- }
- if (extras) {
- $('#node-input-validation-segments').text('You have segments defined outside of the min/max range of your gauge').show()
- }
- return minCovered && !extras
- }
- RED.nodes.registerType('ui-gauge', {
- category: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.label.category'),
- color: RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.colors.medium'),
- defaults: {
- name: { value: '' },
- group: { type: 'ui-group', required: true },
- order: { value: 0 },
- width: {
- value: 3,
- validate: function (v) {
- const width = v || 0
- const currentGroup = $('#node-input-group').val() || this.group
- const groupNode = RED.nodes.node(currentGroup)
- const valid = !groupNode || +width <= +groupNode.width
- $('#node-input-size').toggleClass('input-error', !valid)
- return valid
- }
- },
- height: { value: 3 },
- gtype: { value: 'gauge-half' },
- gstyle: { value: 'needle' },
- title: { value: 'gauge' },
- units: { value: 'units' },
- icon: { value: '' },
- prefix: { value: '' },
- suffix: { value: '' },
- segments: {
- value: [{
- color: '#5CD65C',
- from: 0
- }, {
- color: '#FFC800',
- from: 4
- }, {
- color: '#EA5353',
- from: 7
- }],
- validate: validateGaugeSegments
- },
- min: { value: 0, required: true, validate: RED.validators.number() },
- max: { value: 10, required: true, validate: RED.validators.number() },
- // sizes
- sizeThickness: { value: 16, validate: RED.validators.number() },
- sizeGap: { value: 4, validate: RED.validators.number() },
- sizeKeyThickness: { value: 8, validate: RED.validators.number() },
- // style
- styleRounded: { value: true },
- styleGlow: { value: false },
- // CSS
- className: { value: '' }
- },
- inputs: 1,
- outputs: 0,
- inputLabels: function () { return this.min + ' - ' + this.max },
- align: 'right',
- icon: 'ui-gauge.svg',
- paletteLabel: 'gauge',
- label: function () { return this.name || this.title || this.gtype },
- labelStyle: function () { return this.name ? 'node_label_italic' : '' },
- oneditprepare: function () {
- // store reference to initial gtype so we can check if it's changed
- const initGType = this.gtype
-
- // if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
- // as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
- if (RED.nodes.subflow(this.z)) {
- // change inputs from hidden to text & display them
- $('#node-input-width').attr('type', 'text')
- $('#node-input-height').attr('type', 'text')
- $('div.form-row.nr-db-ui-element-sizer-row').hide()
- $('div.form-row.nr-db-ui-manual-size-row').show()
- } else {
- // not in a subflow, use the elementSizer
- $('div.form-row.nr-db-ui-element-sizer-row').show()
- $('div.form-row.nr-db-ui-manual-size-row').hide()
- $('#node-input-size').elementSizer({
- width: '#node-input-width',
- height: '#node-input-height',
- group: '#node-input-group'
- })
- }
-
- // check for duplicate values
- const unique = new Set(this.segments.map(function (o) { return o.from }))
- if (this.segments.length === unique.size) { $('#valWarning').hide() } else { $('#valWarning').show() }
-
- validateGaugeSegments.call(this, this.segments)
-
- function generateSegment (i, segment) {
- const container = $('<li/>', { style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);' })
- const row = $('<div/>').appendTo(container)
- $('<div/>', { style: 'padding-top:5px; padding-left:175px;' }).appendTo(container)
- $('<div/>', { style: 'padding-top:5px; padding-left:120px;' }).appendTo(container)
-
- $('<i style="color: var(--red-ui-form-text-color, #eee); cursor:move; margin-left:3px;" class="node-input-segment-handle fa fa-bars"></i>').appendTo(row)
-
- $('<input/>', { class: 'node-input-segment-color', type: 'color', style: 'margin-left:7px; width: 50px;', placeholder: 'Color', value: segment.color }).appendTo(row)
- $('<input/>', { class: 'node-input-segment-from', type: 'text', style: 'margin-left:7px; width:calc(100% - 175px);', placeholder: 'From', value: segment.from }).appendTo(row)
-
- const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row)
- const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan)
- $('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton)
-
- deleteButton.click(function () {
- container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' })
- container.fadeOut(300, function () {
- $(this).remove()
- })
- })
-
- $('#node-input-segments-container').append(container)
- }
-
- $('#node-input-add-segment').click(function () {
- generateSegment($('#node-input-segments-container').children().length + 1, {})
- $('#node-input-segments-container-div').scrollTop($('#node-input-segments-container-div').get(0).scrollHeight)
- })
-
- $('#node-input-segments-container').sortable({
- axis: 'y',
- handle: '.node-input-segment-handle',
- cursor: 'move'
- })
-
- for (let i = 0; i < this.segments.length; i++) {
- const segment = this.segments[i]
- generateSegment(i + 1, segment)
- }
-
- // only show relevant options
-
- function showTypeOptions (type) {
- if (type === 'gauge-tile' || type === 'gauge-battery' || type === 'gauge-tank') {
- $('#node-input-container-sizes').hide()
- $('#node-input-container-gstyle').hide()
- $('#node-input-container-label-extras').hide()
- } else {
- $('#node-input-container-sizes').show()
- $('#node-input-container-gstyle').show()
- $('#node-input-container-label-extras').show()
- }
-
- if (type === 'gauge-tank' && initGType !== 'gauge-tank') {
- // set new min/max
- $('#node-input-min').val(0)
- $('#node-input-max').val(100)
-
- // set some sensible segments
- const segments = [{
- color: '#A8F5FF',
- from: 0
- }, {
- color: '#55DBEC',
- from: 15
- }, {
- color: '#53B4FD',
- from: 35
- }, {
- color: '#2397D1',
- from: 50
- }]
- // clear existing segments
- $('#node-input-segments-container').empty()
- // add the new "defaults" for this gType
- for (let i = 0; i < segments.length; i++) {
- const segment = segments[i]
- generateSegment(i + 1, segment)
- }
- } else if (type === 'gauge-battery' && initGType !== 'gauge-battery') {
- // set new min/max
- $('#node-input-min').val(0)
- $('#node-input-max').val(100)
-
- // set some sensible segments
- const segments = [{
- color: '#EA5353',
- from: 0
- }, {
- color: '#FFC800',
- from: 40
- }, {
- color: '#5CD65C',
- from: 70
- }]
- // clear existing segments
- $('#node-input-segments-container').empty()
- // add the new "defaults" for this gType
- for (let i = 0; i < segments.length; i++) {
- const segment = segments[i]
- generateSegment(i + 1, segment)
- }
- } else if (initGType !== type) {
- // we've actually changed type, not just being triggered by the initial NR load of palette
-
- // set new min/max
- $('#node-input-min').val(0)
- $('#node-input-max').val(10)
-
- // set some sensible segments
- const segments = [{
- color: '#5CD65C',
- from: 0
- }, {
- color: '#FFC800',
- from: 4
- }, {
- color: '#EA5353',
- from: 7
- }]
- // clear existing segments
- $('#node-input-segments-container').empty()
- // add the new "defaults" for this gType
- for (let i = 0; i < segments.length; i++) {
- const segment = segments[i]
- generateSegment(i + 1, segment)
- }
- }
- }
-
- $('#node-input-gtype').change((data) => {
- const gType = $('#node-input-gtype').val()
- showTypeOptions(gType)
- })
- },
- oneditsave: function () {
- const segments = $('#node-input-segments-container').children()
-
- const node = this
- node.segments = []
- segments.each(function (i) {
- const segment = $(this)
- const s = {
- from: segment.find('.node-input-segment-from').val(),
- color: segment.find('.node-input-segment-color').val()
- }
- node.segments.push(s)
- })
- }
- })
- </script>
-
- <script type="text/html" data-template-name="ui-gauge">
- <div class="form-row">
- <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.name"></span></label>
- <input type="text" id="node-input-name">
- </div>
- <div class="form-row">
- <label for="node-input-group"><i class="fa fa-table"></i> <span data-i18n="ui-gauge.label.group"></span></label>
- <input type="text" id="node-input-group">
- </div>
- <div class="form-row nr-db-ui-element-sizer-row">
- <label><i class="fa fa-object-group"></i> <span data-i18n="ui-gauge.label.size">Size</label>
- <button class="editor-button" id="node-input-size"></button>
- </div>
- <div class="form-row nr-db-ui-manual-size-row">
- <label><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.width">Width</label>
- <input type="hidden" id="node-input-width">
- </div>
- <div class="form-row nr-db-ui-manual-size-row">
- <label><i class="fa fa-arrows-v"></i> <span data-i18n="ui-gauge.label.height">Height</label>
- <input type="hidden" id="node-input-height">
- </div>
- <div class="form-row">
- <label for="node-input-gtype"><i class="fa fa-list"></i> <span data-i18n="ui-gauge.label.type"></span></label>
- <select id="node-input-gtype" style="width:150px">
- <option value="gauge-tile" data-i18n="ui-gauge.label.gaugeTile"></option>
- <option value="gauge-34" data-i18n="ui-gauge.label.gauge34"></option>
- <option value="gauge-half" data-i18n="ui-gauge.label.gaugeHalf"></option>
- <option value="gauge-battery" data-i18n="ui-gauge.label.gaugeBattery"></option>
- <option value="gauge-tank" data-i18n="ui-gauge.label.gaugeTank"></option>
- <!-- <segment value="donut">Donut</segment>
- <segment value="compass">Compass</segment>
- <segment value="wave">Level</segment> -->
- </select>
- </div>
- <div id="node-input-container-gstyle" class="form-row">
- <label for="node-input-gstyle"><i class="fa fa-list"></i> <span data-i18n="ui-gauge.label.style"></span></label>
- <select id="node-input-gstyle" style="width:150px">
- <option value="needle" data-i18n="ui-gauge.label.needle"></option>
- <option value="rounded" data-i18n="ui-gauge.label.rounded"></option>
- <!-- <option value=rounded">Rounded</option> -->
- </select>
- </div>
- <div class="form-row">
- <h3><span data-i18n="ui-gauge.label.limits"></span></h3>
- </div>
- <div class="form-row">
- <label for="node-input-min"><i class="fa fa-arrows-h"></i><span data-i18n="ui-gauge.label.range"></span></label>
- <span for="node-input-min"><span data-i18n="ui-gauge.label.min"></span></span>
- <input type="text" id="node-input-min" style="width:80px">
- <span for="node-input-max" style="margin-left:20px;"><span data-i18n="ui-gauge.label.max"></span></span>
- <input type="text" id="node-input-max" style="width:80px">
- </div>
- <span id="node-input-validation-segments" style="color: var(--red-ui-text-color-error, #910000)"></span>
- <div class="form-row node-input-segments-container-row" style="margin-bottom: 0px;width: 100%">
- <label for="node-input-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> <span data-i18n="ui-gauge.label.segments"></span></label>
- <div id="node-input-segments-container-div" style="box-sizing:border-box; border-radius:5px; height:257px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:scroll; display:inline-block; width: 70%;">
- <span id="valWarning" style="color: var(--red-ui-text-color-error, #910000)"><b data-i18n="ui-gauge.errors.unique"></b></span>
- <ol id="node-input-segments-container" style="list-style-type:none; margin:0;"></ol>
- </div>
- <!-- <a
- data-html="true"
- title="Dynamic Property: Send 'msg.segments' in order to override this property."
- class="red-ui-button ui-node-popover-title"
- 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;">
- <i style="font-family: ui-serif;">fx</i>
- </a> -->
- </div>
- <div class="form-row">
- <a href="#" class="editor-button editor-button-small" id="node-input-add-segment" style="margin-top:4px; margin-left:103px;"><i class="fa fa-plus"></i> <span data-i18n="ui-gauge.label.segment"></span></a>
- </div>
- <div class="form-row">
- <h3 data-i18n="ui-gauge.label.labelling"></h3>
- </div>
- <div id="ui-gauge-labels">
- <div class="form-row">
- <label for="node-input-title"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.label"></span></label>
- <input type="text" id="node-input-title">
- </div>
- <div id="node-input-container-label-extras">
- <div class="form-row form-row-flex">
- <div>
- <label for="node-input-prefix"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.prefix"></span></label>
- <input type="text" id="node-input-prefix" style="width: 75px">
- </div>
- <div>
- <label for="node-input-suffix" style="margin-left: 48px;"><i class="fa fa-tag"></i> <span data-i18n="ui-gauge.label.suffix"></span></label>
- <input type="text" id="node-input-suffix" style="width: 75px">
- </div>
- </div>
- <div class="form-row" id="ui-gauge-units">
- <label for="node-input-units"><i class="fa fa-calculator"></i> <span data-i18n="ui-gauge.label.units"></span></label>
- <input type="text" id="node-input-units" placeholder="Unit">
- </div>
- <div class="form-row" id="ui-gauge-icon">
- <label for="node-input-icon"><i class="fa fa-home"></i> <span data-i18n="ui-gauge.label.icon"></span></label>
- <input type="text" id="node-input-icon" placeholder="Icon from mdi library, e.g. 'home'">
- </div>
- </div>
- </div>
- <div id="node-input-container-sizes">
- <div class="form-row">
- <h3 data-i18n="ui-gauge.label.sizes"></h3>
- </div>
- <div class="form-row">
- <label for="node-input-sizeThickness"><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.gauge"></span></label>
- <input type="text" id="node-input-sizeThickness" style="width:75px; margin-right: 3px;">px
- </div>
- <div class="form-row">
- <label for="node-input-sizeGap"><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.gap"></span></label>
- <input type="text" id="node-input-sizeGap" style="width:75px; margin-right: 3px;">px
- </div>
- <div class="form-row">
- <label for="node-input-sizeKeyThickness"><i class="fa fa-arrows-h"></i> <span data-i18n="ui-gauge.label.segments"></span></label>
- <input type="text" id="node-input-sizeKeyThickness" style="width:75px; margin-right: 3px;">px
- </div>
- <div class="form-row">
- <h3 data-i18n="ui-gauge.label.styling"></h3>
- </div>
- </div>
- <div class="form-row">
- <label for="node-input-className"><i class="fa fa-code"></i> <span data-i18n="ui-gauge.label.class"></span></label>
- <input type="text" id="node-input-className" placeholder="segmental CSS class name(s) for widget"/>
- </div>
- </script>
|