node-red-contrib-sun-position
Version:
NodeRED nodes to get sun and moon position
874 lines (849 loc) • 241 kB
HTML
<!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 += ' <strong>' + cond.conditionText + '</strong><br/><span class="indent-enh-text" > </span>';
}
if (addBrac && i > 0 && i < l) {
result += '( ';
}
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> <code>' + enh.cond[i].operand + '</code>';
result += ' ' + cond.operatorText + ' <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> is <code>' + enh.cond[i].operand + '</code>]</span>';
}
}
}
if (addBrac) {
result += ' ';
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> <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> <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> <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()