UNPKG

node-red-contrib-sun-position

Version:
874 lines (849 loc) 241 kB
<!DOCTYPE HTML> <!-- This code is licensed under the Apache License Version 2.0. Copyright (c) 2022 Robert Gester Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. --> <script type="text/javascript"> /* eslint-env browser es6 */ // Isolate this code (function() { 'use strict'; /// <reference path="./types/typedefs.js" /> /// <reference lib="./static/htmlglobal.js" /> /** --- Type Defs --- * @typedef {import("node-red").Red} Red */ const cNBC_RULE_EXEC = { auto: 0, first:1, last:2 }; const cNBC_RULE_TYPE_UNTIL = 0; const cNBC_RULE_TYPE_FROM = 1; /** @type {Red} */ // @ts-ignore RED.nodes.registerType('clock-timer', { category: 'time and astro', color: '#FFCC66', icon: 'clock-timer-white.svg', inputs: 1, outputs: 2, defaults: { name: { value: '', required: false }, topic: { value: '', required: false }, addIdType: { value: 'none' }, addId: { value: '', // @ts-ignore validate: RED.validators.typedInput('addIdType') }, positionConfig: { value: '', type: 'position-config', required: true }, autoTrigger: { value: true }, autoTriggerTime: { value: 1200000, validate(v) { const n = parseFloat(v); return (typeof v === 'undefined') || // @ts-ignore (RED.validators.number()(v) && (n >= 60000) && (n < 0x7FFFFFFF)); } }, startDelayTime: { value: 10000, validate(v) { const n = parseFloat(v); return (typeof v === 'undefined') || // @ts-ignore (RED.validators.number()(v) && (n >= 0) && (n < 0x7FFFFFFF)); }, required: false }, contextStore: {value: ''}, results: { value: [ { p: '', pt: 'msgTopic', v: '', vt: 'topic' }, { p: '', pt: 'msgPayload', v: '', vt: 'payload' }, { p: 'timeCtrl', pt: 'msg', v: '', vt: 'ctrlObj' } ] }, // #region overwrite settings overwriteExpire: { value: '', required: false, validate(v) { const n = parseFloat(v); return (typeof v === 'undefined') || (v === '') || // @ts-ignore (RED.validators.number()(v) && (n >= 200) && (n < 0x7FFFFFFF)); } }, // #endregion overwrite settings // #region rule settings rules: { value: {} }, payloadDefault: { value: '', // @ts-ignore validate: RED.validators.typedInput('payloadDefaultType') }, payloadDefaultType: { value: 'none' }, payloadDefaultTimeFormat: { value: 0 }, payloadDefaultOffset: { value: 0, validate(v) { return ( (typeof v === 'undefined') || (typeof this.payloadOffsetType === 'undefined') || // @ts-ignore RED.validators.typedInput('payloadOffsetType')(v) ); } }, payloadDefaultOffsetType: { value: 'none' }, payloadDefaultOffsetMultiplier: { value: 60000, // @ts-ignore validate : RED.validators.number() } // #endregion time settings }, outputLabels(index) { if (index > 0) { return this._('clock-timer.label.output' + (index + 1)); } const types = { msgPayload : 'msg.payload', msgTopic : 'msg.topic', msgValue : 'msg.value' }; const labels = { payload: 'the payload', topic: 'topic', ctrlObj: 'status object', str: 'string', num: 'number', bool: 'boolean', flow: 'flow context', global: 'global context', bin: 'binary data', date: 'timestamp', dateSpecific: 'special timestamp', entered: 'timestamp', dateEntered: 'timestamp', env: 'Environment variable', jsonata: 'result of jsonata expression', nodeId: 'additional node ID', nodeName: this.name, nodePath: this._path || this.name }; // if only payload and topic - display payload type // if only one property - show it's type // if more than one property (other than payload and topic) - show "x properties" where x is the number of properties. // this.results will not be an array for legacy inject nodes until they are re-deployed // let results = this.results; if (!results || !Array.isArray(results)) { results = [ { p: '', pt: 'msgTopic', v: '', vt: 'topic' }, { p: '', pt: 'msgPayload', v: '', vt: 'payload' }, { p: 'timeCtrl', pt: 'msg', v: '', vt: 'ctrlObj' }]; } let lab = ''; if (results) { let cnt = -1; for (let i=0, l=results.length; i<l; i++) { if (!results[i].pt.includes('msg')) { continue; } cnt++; if (cnt > 0) lab += '\n'; if (cnt === 5) { lab += ' + ' + (results.length - 4); break; } lab += (types[results[i].pt] ? types[results[i].pt] : (results[i].pt + '.' + results[i].p)) + ': '; let propType = labels[results[i].vt] ? labels[results[i].vt] : results[i].vt; if (propType === 'json' || propType === 'object') { try { const parsedProp = JSON.parse(results[i].v); propType = typeof parsedProp; if (propType === 'object' && Array.isArray(parsedProp)) { propType = 'Array'; } } catch (ex) { propType = 'invalid'; } } lab += propType; } } return lab || '?'; }, label() { if (this.name) { return this.name; } const result = this._('clock-timer.label.clock-timer'); if (this.topic && (this.topic.length + result.length <= 32)) { return this.topic + ':' + result; } return result; }, labelStyle() { return this.name ? 'node_label_italic' : ''; }, paletteLabel() { return this._('clock-timer.label.clock-timer-palette'); }, oneditprepare() { // @ts-check /** resizes a rule container */ function resizeRuleContainer() { try { const editorRow = $('.node-input-rule-container-row ol'); const height = editorRow.outerHeight(true); const rowCount = $('#node-input-rule-container').editableList('length'); if (rowCount > 0) { // $('#node-input-rule-container').editableList('height', height + 40); $('#node-input-rule-container').css('min-height', height + 'px'); } else { // $('#node-input-rule-container').editableList('height', 40); $('#node-input-rule-container').css('min-height', '40px'); } } catch (ex) { console.log(ex); // eslint-disable-line } } /** resizes the rule container */ function resizeRuleContainerCheck(node) { if (node.dialogAddData.resizeRuleContainerTO) { clearTimeout(node.dialogAddData.resizeRuleContainerTO); } node.dialogAddData.resizeRuleContainerTO = setTimeout(() => { delete node.dialogAddData.resizeRuleContainerTO; resizeRuleContainer(); }, 600); } setTimeout(() => { $('.is-to-show-initially').show(); $('.is-to-hide-initially').hide(); }, 300); $('.rdg-ui-btn').button(); const $nodeConfig = $('#node-input-positionConfig'); const node = this; const $nodeInputContextStore = $('#node-input-contextStore'); // @ts-ignore RED.settings.context.stores.forEach(store => { $nodeInputContextStore.append('<option value="' + store + '"' + (this.contextStore === store ? ' selected' : '') + '>' + store + '</option>'); }); // @ts-ignore const tabs = RED.tabs.create({ id: 'node-config-clock-timer-tabs', onchange(tab) { $('#node-config-clock-timer-tabs-content').children().hide(); $('#' + tab.id).show(); resizeRuleContainerCheck(node); }, scrollable: true, collapsible: true }); node.dialogAddData = { time:{ start:{ min:{}, max:{} }, end:{ min:{}, max:{} } }, timeReg:{} }; const setup = function(node) { /* global getTypes getSelectFields setupTInput appendOptions getBackendData bdDateToTime initCombobox initCheckboxesBlock autocomplete getCheckboxesStr setTInputValue */ // #region initialize const types = /** @type {Object} */ getTypes(node, () => $nodeConfig.val()); types.OutputPayload = { value: 'payload', label: node._('node-red-contrib-sun-position/position-config:common.types.payload'), hasValue: false }; types.OutputTopic = { value: 'topic', label: node._('node-red-contrib-sun-position/position-config:common.types.topic'), hasValue: false }; types.OutputCtrlObj = { value: 'ctrlObj', label: node._('node-red-contrib-sun-position/position-config:common.types.ctrlObj'), hasValue: false }; types.OutputCtrlObj = { value: 'ctrlObj', label: node._('node-red-contrib-sun-position/position-config:common.types.ctrlObj'), hasValue: false }; types.OutputCtrlObj = { value: 'ctrlObj', label: node._('node-red-contrib-sun-position/position-config:common.types.ctrlObj'), hasValue: false }; types.UndefinedTimeFrom = Object.assign({}, types.Undefined, { label: node._('node-red-contrib-sun-position/position-config:common.types.undefinedTimeFrom') }); types.UndefinedTimeUntil = Object.assign({}, types.Undefined, { label: node._('node-red-contrib-sun-position/position-config:common.types.undefinedTimeUntil') }); const selFields = getSelectFields(); /** * update multiplier settings from a previous version * @param {number} mp - the multiplier value * @param {string} name - the name of the element * @param {function} onchange - the function to be called on field change * @returns {number} the updated multiplier value */ function multiplierUpdate(mp, name, onchange) { const $field = $('#node-input-' + name); appendOptions(node, $field, 'multiplier', data => (data.id > 0)); if (mp === null || typeof mp === 'undefined' || isNaN(mp) || mp === '' || mp === 0) { mp = 60000; } else { mp = parseFloat(mp); if (mp === 1) { mp = 1000; } else if (mp === 60) { mp = 60000; } else if (mp === 3600) { mp = 3600000; } } $field.val(mp); $field.on('change', onchange); return mp; } /** * save rules * @param {*} node - the node representation * @param {string} name - the node representation * @param {*} element - the node representation * @param {function} validate - ovalidation function of the input * @param {number} defMultiplier - default multiplier (in ms) * @param {number} minMultiplier - minimum multiplier (in ms) */ function setMultiplier(node, name, element, validate, defMultiplier, minMultiplier) { const c = Number(node[name]); if (node[name] !== '' && !isNaN(c) && c !== 0) { let r = minMultiplier || 1000; if (c % 86400000 === 0) { r = 86400000; // Days } else if (c % 3600000 === 0) { r = 3600000; // Hours } else if (c % 60000 === 0) { r = 60000; // Minutes } $('#time-control-' + name).val(c/r); $('#time-control-' + name + '-multiplier').val(r); } else { $('#time-control-' + name).val(''); $('#time-control-' + name + '-multiplier').val(defMultiplier || 1000); } $('#time-control-' + name).on('change focus focusout', () => { const el = $('#time-control-' + name); let val = el.val(); const mp = $('#time-control-' + name + '-multiplier').val(); if (isNaN(val) || val === '' || val === 0) { val = ''; $('node-input-' + name).val(''); } else { val = val * mp; $('#node-input-' + name).val(val); } if (typeof validate === 'function') { if (validate(val)) { el.removeClass( 'input-error' ); } else { el.addClass( 'input-error' ); } } }); $('#time-control-' + name + '-multiplier').change( () => { $('#time-control-' + name).change(); }); $('#time-control-' + name).change(); } // #endregion initialize // #region payloadDefault // console.log('clock-timer payloadDefault'); if (node.payloadDefaultType === null) { node.payloadDefaultType = types.Undefined.value; } else if (node.payloadDefaultType === 'string') { node.payloadDefaultType = 'str'; } setupTInput(node, { typeProp: 'payloadDefaultType', valueProp: 'payloadDefault', width: 'calc(100% - 110px)', defaultType: types.Undefined.value, defaultValue: '', types: [ types.Undefined, 'str', 'num', 'bool', 'date', types.DateSpecific, types.MsgPayload, types.MsgValue, 'msg', 'flow', 'global', 'json', 'bin', 'env', 'jsonata', types.randomNumber, types.TimeEntered, types.DateEntered, types.TimeSun, types.TimeSunCustom, types.TimeSunNow, types.TimeMoon, types.DayOfMonth, types.SunCalc, types.SunInSky, types.MoonCalc, types.MoonPhase, types.SunAzimuth, types.SunElevation, types.SunTimeByAzimuth, types.SunTimeByElevationObj, types.SunTimeByElevationNext, types.SunTimeByElevationRise, types.SunTimeByElevationSet, types.isDST, types.WeekOfYear, types.WeekOfYearEven, types.DayOfYear, types.DayOfYearEven ], onChange(_type, _value) { const plVType = $('#node-input-payloadDefault').typedInput('type'); if (plVType === types.TimeEntered.value || plVType === types.TimeSun.value || plVType === types.TimeSunCustom.value || plVType === types.TimeMoon.value || plVType === types.DateSpecific.value) { $('.rdgtimer-rule-row-payloadDefaultTimeFormat').show(); $('.rdgtimer-rule-row-payloadDefaultOffset').show(); } else if (plVType === types.TimeSunNow.value) { $('.rdgtimer-rule-row-payloadDefaultTimeFormat').hide(); $('.rdgtimer-rule-row-payloadDefaultOffset').show(); } else { $('.rdgtimer-rule-row-payloadDefaultTimeFormat').hide(); $('.rdgtimer-rule-row-payloadDefaultOffset').hide(); } getBackendData(d => { const $div = $('#node-input-payloadDefault-div'); const titleOrg = $div.attr('titleOrg'); // $div.attr('title', ((d) ? (String(d) + ' - ') : '') + titleOrg); $div.attr('title', bdDateToTime(d, ' - ') + titleOrg); }, { nodeId: node.id, kind: 'getOutDataData', config: $nodeConfig.val(), type: plVType, value: $('#node-input-payloadDefault').typedInput('value'), format: $('#node-input-payloadDefaultTimeFormat').val(), offsetType: $('#node-input-payloadDefaultOffset').typedInput('type'), offset: $('#node-input-payloadDefaultOffset').typedInput('value'), multiplier: $('#node-input-payloadDefaultOffsetMultiplier').val(), noOffsetError: true }); } }); initCombobox(node, $('#node-input-payloadDefaultTimeFormatsel'), $('#node-input-payloadDefaultTimeFormat'), 'dateOutFormat', 'outputFormats', node.payloadDefaultTimeFormat || 0, 30); node.payloadDefaultOffsetMultiplier = multiplierUpdate(node.payloadDefaultOffsetMultiplier, 'payloadDefaultOffsetMultiplier', () => $('#node-input-payloadDefault').change()); setupTInput(node, { typeProp: 'payloadDefaultOffsetType', valueProp: 'payloadDefaultOffset', width: 'calc(100% - 255px)', defaultType: (node.payloadDefaultOffset === 0 || node.payloadDefaultOffset === '') ? types.Undefined.value : 'num', defaultValue: 0, types: [types.Undefined, 'num', 'flow', 'global', 'env', types.randomNumber, types.randmNumCachedDay, types.randmNumCachedWeek], onChange(_type, _value) { const type = $('#node-input-payloadDefaultOffset').typedInput('type'); if (type === types.Undefined.value) { $('#node-input-payloadDefaultOffsetMultiplier').prop('disabled', true); } else { $('#node-input-payloadDefaultOffsetMultiplier').prop('disabled', false); } $('#node-input-payloadDefault').change(); } }); setupTInput(node, { typeProp: 'addIdType', valueProp: 'addId', width: 'calc(100% - 110px)', defaultType: node.addIdType || (node.addId ? 'str' : types.Undefined.value), defaultValue: '', types: [types.Undefined, 'str', 'flow', 'global', 'env'] // types.MsgPayload, types.MsgTopic, types.MsgPayloadByTopic, types.MsgValue, 'msg', }); // #endregion payloadDefault // #region rules const ruleVersion = 4; /** * get a time in millisecond as string of the format hh:mm, hh:mm:ss or hh:mm:ss.mss * @param {number} s - milisecond * @returns {string} time as string */ function msToTime(s) { // Pad to 2 or 3 digits, default is 2 const pad = (n, z = 2) => ('00' + n).slice(-z); const sec = (s%6e4)/1000|0; const msec = s%1000; return pad(s/3.6e6|0) + ':' + pad((s%3.6e6)/6e4 | 0) + ((msec >0) ? (':' + pad(sec) +'.' + pad(msec, 3) ) : ((sec>0) ? ':' + pad(sec) : '')); } /** * get the time offset text */ function _getOffsetText(node, t,v,m) { let result = ''; if (t && t !== 'none') { if (t === 'num') { const osmillis = v * m; if (osmillis>0) { result += ' + ' + msToTime(osmillis); } else { result += ' - ' +msToTime(osmillis * -1); } } else { result += ' offset <var>' + RED.nodes.getType('position-config').getRDGNodeValLbl(node, t, v) + '</var>'; // result += ' * ' + data.multiplier/1000 + 's'; } } return result; } /** * get a formated date limit */ function getDateShort(value) { if (value) { const val = value.split('-'); if (val.length > 2) { return val[1] + '-' + val[2]; } else if (val.length === 2) { return val[0] + '-' + val[1]; } } return ''; } /** * sets a property of an object * @param {object} obj - object to set property * @param {string|array} arr - property of object to set 'sub1.sub2.propA' or ['sub1', 'sub2', 'propA'] * @param {*} val - value to set * @returns {object} description of the rule */ function addProps(obj, arr, val) { if (typeof arr === 'string') arr = arr.split('.'); obj[arr[0]] = obj[arr[0]] || {}; const tmpObj = obj[arr[0]]; if (arr.length > 1) { arr.shift(); addProps(tmpObj, arr, val); // eslint-disable-line no-unused-vars } else { obj[arr[0]] = val; } return obj; } /** * get number of operand for a comparision opeator * @param {object} selFields - fields object * @param {string} operator - value of comperator selection * @returns {number} description of the rule */ function getOperandCount(selFields, operator) { operator = operator || selFields.comparator[0].id; const fields = selFields.comparator; const fieldsLength = fields.length; for (let index = 0; index < fieldsLength; index++) { const el = fields[index]; if (el.id === operator) { return el.operandCount; } } return 1; } /** * get a description of the rule rules * @param {object} data - a rule description * @returns {string} description of the rule */ function getRuleDescription(node, selFields, data, enh) { let result = ''; const length = (enh) ? 60 : 30; try { if (typeof data.isValid === 'undefined' || typeof data.version === 'undefined' || (!data.isValid && !data.validationError && typeof data.validation === 'undefined')) { result += '<div>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.outDated'); result += '</div>'; } else if (!data.isValid) { result += '<div>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.isInValid'); result += '</div><div>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleInValidText'); result += data.validationError; result += '</div>'; if (data.validation && Object.keys(data.validation).length > 0) { const lines = JSON.stringify(data.validation, (key, value) => { if (value === true || (Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0)) return undefined; if (value === false) return node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleInValidElement'); return value; }, '\t').split('\n'); lines.splice(0,1); // remove first line lines.pop(); // remove last line result += '<div><pre>'; result += lines.join('\n'); result += '</pre></div>'; } } if (data.conditions && data.conditions.length > 0) { result += '<div>'; result += '<i class="fa fa-code-fork" aria-hidden="true"></i> '; const l = data.conditions.length-1; const addBrac = l > 1; for (let i = 0; i <= l; i++) { const cond = data.conditions[i]; if (i > 0) { result += '&nbsp;<strong>' + cond.conditionText + '</strong><br/><span class="indent-enh-text" >&nbsp;</span>'; } if (addBrac && i > 0 && i < l) { result += '(&nbsp;'; } if (cond.valueType === 'pdmPhaseCheck') { result += 'Moon phase is <var>' + cond.value + '</var>'; } else { result += '<var>' + RED.nodes.getType('position-config').getRDGNodeValLbl(node, cond.valueType, cond.value, null, null, null, length) + '</var>'; result += ' ' + cond.operatorText; const oc = getOperandCount(selFields, cond.operator); if (oc > 1) { const ttext = RED.nodes.getType('position-config').getRDGNodeValLbl(node, cond.thresholdType, cond.threshold, null, null, null, length); result += ' '; result += ttext; if (enh && enh.cond && enh.cond[i] && enh.cond[i].operand) { result += ' <span class="indent-enh-text">[<i class="fa fa-hand-o-right" aria-hidden="true"></i>&nbsp;<code>' + enh.cond[i].operand + '</code>'; result += ' ' + cond.operatorText + '&nbsp;<code>'; if(enh.cond[i].threshold) { result += enh.cond[i].threshold; } else { result += ttext; } result += '</code>]</span>'; } } else if (enh && enh.cond && enh.cond[i] && enh.cond[i].operand) { result += ' <span class="indent-enh-text">[<i class="fa fa-hand-o-right" aria-hidden="true"></i>&nbsp;is&nbsp;<code>' + enh.cond[i].operand + '</code>]</span>'; } } } if (addBrac) { result += '&nbsp;'; result += Array(l).join(')'); } result += '</div>'; } if (data.time) { const gettime = (ttype, text) => { if (data.time[ttype] && data.time[ttype].type && data.timeType !== 'none') { result += '<div>'; result += '<i class="fa fa-clock-o" aria-hidden="true"></i> '; result += text; result += ' <var>' + RED.nodes.getType('position-config').getRDGNodeValLbl(node, data.time[ttype].type, data.time[ttype].value, null, null, null, length) + '</var>'; result += _getOffsetText(node, data.time[ttype].offsetType, data.time[ttype].offset, data.time[ttype].multiplier); if (enh && enh.timeReg && enh.timeReg.text) { result += ' <span class="indent-enh-text">[<i class="fa fa-hand-o-right" aria-hidden="true"></i>&nbsp;<code>' + enh.timeReg.text + '</code>]</span>'; } if (data.time[ttype].min && data.time[ttype].min.type && data.time[ttype].min.type !== 'none') { result += '<div class="indent-time-text"><i class="fa fa-step-backward" aria-hidden="true"></i> <span>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeMin_'+ttype); result += '</span> <var>'; result += RED.nodes.getType('position-config').getRDGNodeValLbl(node, data.time[ttype].min.type, data.time[ttype].min.value, null, null, null, length); result += '</var>'; result += _getOffsetText(node, data.time[ttype].min.offsetType, data.time[ttype].min.offset, data.time[ttype].min.multiplier); if (enh && enh.time[ttype].min && enh.time[ttype].min.text) { result += ' <span class="indent-enh-text">[<i class="fa fa-hand-o-right" aria-hidden="true"></i>&nbsp;<code>' + enh.time[ttype].min.text + '</code>]</span>'; } result += '</div>'; } if (data.time[ttype].max && data.time[ttype].max.type && data.time[ttype].max.type !== 'none') { result += '<div class="indent-time-text"><i class="fa fa-step-forward" aria-hidden="true"></i> <span>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeMax_'+ttype); result += '</span> <var>'; result += RED.nodes.getType('position-config').getRDGNodeValLbl(node, data.time[ttype].max.type, data.time[ttype].max.value, null, null, null, length); result += '</var>'; result += _getOffsetText(node, data.time[ttype].max.offsetType, data.time[ttype].max.offset, data.time[ttype].max.multiplier); if (enh && enh.time[ttype].max && enh.time[ttype].max.text) { result += ' <span class="indent-enh-text">[<i class="fa fa-hand-o-right" aria-hidden="true"></i>&nbsp;<code>' + enh.time[ttype].max.text + '</code>]</span>'; } result += '</div>'; } result += '</div>'; } }; gettime('start',node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeStart')); gettime('end',node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeEnd')); result += '<div>'; if (data.time.days && data.time.days !== '*') { result += '<div class="indent-time-days"><i class="fa fa-calendar-o" aria-hidden="true"></i> <span>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeDays'); result += '</span> <var>'; const daysarr = data.time.days.split(','); if (daysarr.length === 1) { result += (node._('node-red-contrib-sun-position/position-config:common.days.'+(parseInt(daysarr[0]) + 7))); } else if (data.time.days === '5,6,0') { result += node._('node-red-contrib-sun-position/position-config:common.days.12'); result += '-'; result += node._('node-red-contrib-sun-position/position-config:common.days.7'); } else if (daysarr.length === 2) { result += node._('node-red-contrib-sun-position/position-config:common.days.'+(parseInt(daysarr[0]) + 7)); result += ' '; result += node._('node-red-contrib-sun-position/position-config:common.label.and'); result += ' '; result += node._('node-red-contrib-sun-position/position-config:common.days.'+(parseInt(daysarr[1]) + 7)); } else { daysarr.sort((a, b) => parseInt(a) - parseInt(b)); let isok = true; let elF = parseInt(daysarr[0]); daysarr[0] = node._('node-red-contrib-sun-position/position-config:common.days.'+(elF + 7)); for (let index = 1; index < daysarr.length; index++) { const element = parseInt(daysarr[index]); isok = isok && (element === elF+1); elF = element; daysarr[index] = node._('node-red-contrib-sun-position/position-config:common.days.'+(parseInt(element) + 7)); } if (isok) { result += (daysarr[0] + '-' + daysarr[daysarr.length-1]); } else { result += (daysarr.join(',')); } } result += '</var></div>'; } if (data.time.onlyEvenDays || data.time.onlyOddDays || data.time.onlyEvenWeeks || data.time.onlyOddWeeks) { result += '<div class="indent-time-special"><i class="fa fa-calendar-o" aria-hidden="true"></i> <span>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeLimit'); result += '</span> <var>'; const resultArr = []; if (data.time.onlyEvenDays) { resultArr.push(node._('node-red-contrib-sun-position/position-config:common.label.onlyEvenDays')); } if (data.time.onlyOddDays) { resultArr.push(node._('node-red-contrib-sun-position/position-config:common.label.onlyOddDays')); } if (data.time.onlyEvenWeeks) { resultArr.push(node._('node-red-contrib-sun-position/position-config:common.label.onlyEvenWeeks')); } if (data.time.onlyOddWeeks) { resultArr.push(node._('node-red-contrib-sun-position/position-config:common.label.onlyOddWeeks')); } result += resultArr.join(' <b>' + node._('node-red-contrib-sun-position/position-config:common.label.and') + '</b> '); result += '</var></div>'; } if (data.time.months && data.time.months !== '*') { result += '<div class="indent-time-months"><i class="fa fa-calendar-o" aria-hidden="true"></i> <span>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeMonths'); result += '</span> <var>'; const montharr = data.time.months.split(','); if (montharr.length === 2) { result += node._('node-red-contrib-sun-position/position-config:common.months.'+(parseInt(montharr[0]) + 12)); result += ' '; result += node._('node-red-contrib-sun-position/position-config:common.label.and'); result += ' '; result += node._('node-red-contrib-sun-position/position-config:common.months.'+(parseInt(montharr[1]) + 12)); } else if (montharr.length === 1) { result += node._('node-red-contrib-sun-position/position-config:common.months.'+(parseInt(montharr[0]) + 12)); } else { montharr.sort((a, b) => parseInt(a) - parseInt(b)); let isok = true; let elF = parseInt(montharr[0]); montharr[0] = node._('node-red-contrib-sun-position/position-config:common.months.'+(elF + 12)); for (let index = 1; index < montharr.length; index++) { const element = parseInt(montharr[index]); isok = isok && (element === elF+1); elF = element; montharr[index] = node._('node-red-contrib-sun-position/position-config:common.months.'+(parseInt(element) + 12)); } if (isok) { result += montharr[0]; result += '-'; result += montharr[montharr.length-1]; } else { result += montharr.join(','); } } result += '</var></div>'; } if (data.time.dateStart || data.time.dateEnd) { result += '<div class="indent-time-datelimit"><i class="fa fa-calendar-o" aria-hidden="true"></i> <span>'; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeLimitStart'); result += '</span> <var>'; if (enh) { const year = (new Date()).getFullYear(); result += data.time.dateStart || year + '-01-01'; } else { result += (getDateShort(data.time.dateStart) || '01-01'); } result += '</var> <span> '; result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleTimeLimitEnd'); result += '</span> <var>'; if (enh) { const year = (new Date()).getFullYear()