UNPKG

node-red-contrib-sun-position

Version:
1,074 lines (1,037 loc) 319 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_MODE_OFF = 0; const cNBC_MODE_MAXIMIZE = 1; const cNBC_MODE_MINIMIZE = 3; const cNBC_MODE_SUN_CONTROL = 16; const cNBC_RULE_EXEC = { auto: 0, first:1, last:2 }; const cNBC_RULE_TYPE_UNTIL = 0; const cNBC_RULE_TYPE_FROM = 1; const cNBC_RULE_OP = { levelAbsolute : 0, levelMinOversteer : 1, // ⭳❗ minimum (oversteer) levelMaxOversteer : 2, // ⭱️❗ maximum (oversteer) slatOversteer : 5, topicOversteer : 8, off : 9 }; /** * get a value * @param {*} node - the node representation * @param {string} name - name of the value * @returns {*} the value of th element */ function getVal(node,name) { const v = $('#node-input-' + name).val(); if (typeof v === 'undefined') { return node[name]; } return v; } /** * get the min and maximum blind positions * @param {*} node - the node representation * @returns {object} object with property __min__ and __max__ */ function getMinMaxBlindPos(node) { const max = parseFloat(getVal(node, 'blindOpenPos')) || 0; const min = parseFloat(getVal(node, 'blindClosedPos')) || 0; return { min:Math.min(min, max), max:Math.max(min, max) }; } /** @type {Red} */ // @ts-ignore RED.nodes.registerType('blind-control', { category: 'time and astro', color: '#FFCC66', icon: 'blind-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: 3600000, 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: 'level' }, { p: 'slat', pt: 'msg', v: '', vt: 'slat' }, { p: 'blindCtrl', pt: 'msg', v: '', vt: 'ctrlObj' } ] }, // #region blind settings blindIncrement: { value: 0.01, required: true, validate(v) { const n = parseFloat(v); const mm = getMinMaxBlindPos(this); // @ts-ignore return (RED.validators.number()(v) && (n < mm.max) && (n > 0)); } }, blindOpenPos: { value: 1.00, required: true, validate(v) { const n = parseFloat(v); // @ts-ignore return RED.validators.number()(v) && (Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) && (n !== parseFloat(getVal(this, 'blindClosedPos'))); } }, blindClosedPos: { value: 0.00, required: true, validate(v) { const n = parseFloat(v); // @ts-ignore return RED.validators.number()(v) && (Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) && (n !== parseFloat(getVal(this, 'blindOpenPos'))); } }, blindPosReverse: { value: false }, blindPosDefault: { value: 'open (max)', // @ts-ignore validate: RED.validators.typedInput('blindPosDefaultType') }, blindPosDefaultType: { value: 'levelFixed' }, slatPosDefault: { value: '', // @ts-ignore validate: RED.validators.typedInput('slatPosDefaultType') }, slatPosDefaultType: { value: 'none' }, // #endregion blind settings // #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: {} }, // #endregion rule settings // #region sun settings sunControlMode: { value: '0', required: true, validate(v) { const n = Number(v); // @ts-ignore return (RED.validators.number()(v) && ((n === cNBC_MODE_OFF) || (n === cNBC_MODE_MINIMIZE) || (n === cNBC_MODE_MAXIMIZE) || (n === cNBC_MODE_SUN_CONTROL))); } }, sunFloorLengthType: { value: 'num' }, sunFloorLength: { value: '', required: false, validate(v) { const mode = Number(getVal(this, 'sunControlMode')); return (mode === cNBC_MODE_OFF) || (mode === cNBC_MODE_MAXIMIZE) || (mode === cNBC_MODE_MINIMIZE) || // @ts-ignore RED.validators.typedInput('sunFloorLengthType')(v); } }, sunMinDelta: { value: '', required: false, validate(v) { const n = parseFloat(v); const mm = getMinMaxBlindPos(this); const mode = Number(getVal(this, 'sunControlMode')); return (mode === cNBC_MODE_OFF) || (mode === cNBC_MODE_MAXIMIZE) || (mode === cNBC_MODE_MINIMIZE) || (v === '') || ((n <= mm.max) && (n >= mm.min) && (n >= parseFloat(getVal(this, 'blindIncrement'))) ); } }, blindPosMin: { value: 'closed (min)', validate(v) { return ((Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || // @ts-ignore RED.validators.typedInput('blindPosMinType')(v)); } }, blindPosMinType: { value: 'levelFixed' }, blindPosMax: { value: 'open (max)', validate(v) { return ((Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || // @ts-ignore RED.validators.typedInput('blindPosMaxType')(v)); } }, blindPosMaxType: { value: 'levelFixed' }, blindOpenPosOffset: { value: 0.00, required: false, validate(v) { if (typeof v === 'undefined' || v === '') return true; const n = parseFloat(v); const mm = getMinMaxBlindPos(this); return n === 0.00 || // @ts-ignore (RED.validators.number()(v) && (Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) && (n < mm.max) && (n > mm.min)); } }, blindClosedPosOffset: { value: 0.00, required: false, validate(v) { if (typeof v === 'undefined' || v === '') return true; const n = parseFloat(v); const mm = getMinMaxBlindPos(this); return n === 0.00 || // @ts-ignore (RED.validators.number()(v) && (Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) && (n < mm.max) && (n > mm.min)); } }, sunSlat: { value: '', // @ts-ignore validate: RED.validators.typedInput('sunSlatType') }, sunSlatType: { value: 'none' }, smoothTime: { value: '', validate(v) { const n = parseFloat(v); return ((Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || (v === '') || // @ts-ignore (RED.validators.number()(v) && (n > -2147483647) && (n < 0x7FFFFFFF))); } }, sunTopic: { value: '', required: false }, // #endregion sun settings // #region window settings windowTopType: { value: 'num' }, windowTop: { value: '', validate(v) { return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || // @ts-ignore RED.validators.typedInput('blindPosMaxType')(v); } }, windowBottomType: { value: 'num' }, windowBottom: { value: '', validate(v) { return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || // @ts-ignore RED.validators.typedInput('blindPosMaxType')(v); } }, windowSetMode: { value: 'startEnd' }, windowAzimuthStartType: { value: 'numAzimuth' }, windowAzimuthStart: { value: '', validate(v) { return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || getVal(this, 'windowSetMode') !== 'startEnd' || // @ts-ignore RED.validators.typedInput('windowAzimuthStartType')(v); } }, windowAzimuthEndType: { value: 'numAzimuth' }, windowAzimuthEnd: { value: '', validate(v) { return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || getVal(this, 'windowSetMode') !== 'startEnd' || // @ts-ignore RED.validators.typedInput('windowAzimuthEndType')(v); } }, windowOrientationType: { value: 'numAzimuth' }, windowOrientation: { value: '', validate(v) { return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || getVal(this, 'windowSetMode') !== 'orientation' || // @ts-ignore RED.validators.typedInput('windowOrientationType')(v); } }, windowOffsetN: { value: '', validate(v) { const n = parseFloat(v); return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || getVal(this, 'windowSetMode') !== 'orientation' || v==='' || // @ts-ignore RED.validators.number()(v) && (n >= 0) && (n <= 90); } }, windowOffsetP: { value: '', validate(v) { const n = parseFloat(v); return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) || getVal(this, 'windowSetMode') !== 'orientation' || v==='' || // @ts-ignore RED.validators.number()(v) && (n >= 0) && (n <= 90); } }, // #endregion window settings // #region oversteering settings oversteers: { value: {} }, oversteerTopic: { value: '', required: false }, oversteerValue : { value: ''}, oversteerValueType : { value: ''}, oversteerCompare : { value: ''}, oversteerThreshold : { value: ''}, oversteerThresholdType : { value: ''}, oversteerBlindPos : { value: ''}, oversteerBlindPosType : { value: ''}, oversteer2Value : { value: ''}, oversteer2ValueType : { value: ''}, oversteer2Compare : { value: ''}, oversteer2Threshold : { value: ''}, oversteer2ThresholdType : { value: ''}, oversteer2BlindPos : { value: ''}, oversteer2BlindPosType : { value: ''}, oversteer3Value : { value: ''}, oversteer3ValueType : { value: ''}, oversteer3Compare : { value: ''}, oversteer3Threshold : { value: ''}, oversteer3ThresholdType : { value: ''}, oversteer3BlindPos : { value: ''}, oversteer3BlindPosType : { value: ''}, sunMinAltitude : { value: ''} // #endregion oversteering settings }, outputLabels(index) { if (index > 0) { return this._('blind-control.label.output' + (index + 1)); } const types = { msgPayload : 'msg.payload', msgTopic : 'msg.topic', msgValue : 'msg.value' }; const labels = { level: 'blind position', slat: 'slat position', 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: 'level' }, { p: 'slat', pt: 'msg', v: '', vt: 'slat' }, { p: 'blindCtrl', 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._('blind-control.label.blind-control'); 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._('blind-control.label.blind-control-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; $('#node-input-sunControlMode').change(); 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-blind-control-tabs', onchange(tab) { $('#node-config-blind-control-tabs-content').children().hide(); $('#' + tab.id).show(); resizeRuleContainerCheck(node); }, scrollable: true, collapsible: true }); this.sunControlMode = this.sunControlMode || cNBC_MODE_OFF; if (this.sunControlMode === 2 || this.sunControlMode === '2') { this.sunControlMode = cNBC_MODE_SUN_CONTROL; $('#node-input-sunControlMode').val(cNBC_MODE_SUN_CONTROL); } node.dialogAddData = { time:{ start:{ min:{}, max:{} }, end:{ min:{}, max:{} } }, timeReg:{} }; const setup = function(node) { /* global getTypes getSelectFields setupTInput appendOptions addLabel getBackendData bdDateToTime initCheckboxesBlock getCheckboxesStr setTInputValue */ // #region initialize const types = getTypes(node, () => $nodeConfig.val()); types.LevelEntered = { value: 'num', label: node._('node-red-contrib-sun-position/position-config:common.types.levelfree'), icon: 'red/images/typedInput/09.svg', validate(v) { if (!RED.validators.number()(v) || (v === '')) { return false; } const inc = parseFloat(getVal(this, 'blindIncrement')); if (isNaN(inc)) { return true; } const mm = getMinMaxBlindPos(this); const n = parseFloat(v); if (isNaN(n) || (n > mm.max) || (n < mm.min)) { return false; } return Number.isInteger(Number((n / inc).toFixed((inc === inc>> 0 ? 0 : inc.toString().split('.')[1].length || 0) + 2))); } }; types.LevelFix = { value: 'levelFixed', label: node._('node-red-contrib-sun-position/position-config:common.types.levelfix'), icon: 'icons/node-red-contrib-sun-position/inputTypeLevel.svg', options: [{ value: 'open (max)', label: node._('blind-control.typeOptions.100') }, { value: '75%', label: node._('blind-control.typeOptions.75') }, { value: '66%', label: node._('blind-control.typeOptions.66') }, { value: '50%', label: node._('blind-control.typeOptions.50') }, { value: '33%', label: node._('blind-control.typeOptions.33') }, { value: '25%', label: node._('blind-control.typeOptions.25') }, { value: '10%', label: node._('blind-control.typeOptions.10') }, { value: 'closed (min)', label: node._('blind-control.typeOptions.0') }] }; types.LevelND= { value: 'levelND', label: node._('node-red-contrib-sun-position/position-config:common.types.levelND'), icon: 'icons/node-red-contrib-sun-position/inputTypeNone2.svg', hasValue: false }; types.SunControlModeType = { value: 'sunControlMode', label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlMode','moon phase'), icon: 'icons/node-red-contrib-sun-position/inputTypeSunControl.svg', options: [{ value: cNBC_MODE_OFF + '-off', label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlOff') }, { value: cNBC_MODE_MAXIMIZE + '-maximize', label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlMaximize') }, { value: cNBC_MODE_MINIMIZE + '-minimize', label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlMinimize') }, { value: cNBC_MODE_SUN_CONTROL + '-restrict', label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlRestrict') }] }; types.OutputLevel = { value: 'level', label: node._('node-red-contrib-sun-position/position-config:common.types.level'), hasValue: false }; types.OutputLevelInverse = { value: 'levelInverse', label: node._('node-red-contrib-sun-position/position-config:common.types.levelInverse'), hasValue: false }; types.OutputSlat = { value: 'slat', label: node._('node-red-contrib-sun-position/position-config:common.types.slat'), 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(); /** * 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 } $('#blind-control-' + name).val(c/r); $('#blind-control-' + name + '-multiplier').val(r); } else { $('#blind-control-' + name).val(''); $('#blind-control-' + name + '-multiplier').val(defMultiplier || 1000); } $('#blind-control-' + name).on('change focus focusout', () => { const el = $('#blind-control-' + name); let val = el.val(); const mp = $('#blind-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' ); } } }); $('#blind-control-' + name + '-multiplier').change( () => { $('#blind-control-' + name).change(); }); $('#blind-control-' + name).change(); } /** * get the default level * @param {string} type - the type value * @param {string} value - value * @param {string} def - the default value * @returns {string} the default value for the level */ function getDefLevel(type, value, def) { if (value) { return value; } if (!type || type === types.LevelFix.value) { return def; } return def; } // #endregion initialize // #region blind setMultiplier(node,'overwriteExpire', v => { const n = parseFloat(v); return (v === '') || (RED.validators.number()(v) && (n >= 200) && (n < 0x7FFFFFFF)); }, 3600000, 1000); setMultiplier(node,'autoTriggerTime', v => { const n = parseFloat(v); return (RED.validators.number()(v) && (n >= 60000) && (n < 0x7FFFFFFF)); }, 3600000, 60000); setMultiplier(node,'startDelayTime', v => { const n = parseFloat(v); return (RED.validators.number()(v) && (n >= 0) && (n < 0x7FFFFFFF)); }, 600000, 0); setupTInput(node, { typeProp: 'blindPosDefaultType', valueProp: 'blindPosDefault', width: 'calc(100% - 110px)', defaultType: types.LevelFix.value, defaultValue: getDefLevel(node.blindPosDefaultType, node.blindPosDefault, 'open (max)'), types: [ types.LevelFix, types.LevelEntered, 'env', 'flow', 'global'] }); setupTInput(node, { typeProp: 'slatPosDefaultType', valueProp: 'slatPosDefault', width: 'calc(100% - 110px)', defaultType: types.Undefined.value, defaultValue: '', types: [ types.Undefined, 'str', 'num', 'bool', types.MsgPayload, types.MsgValue, 'msg', 'flow', 'global', 'json', 'bin', 'env', 'jsonata', types.randomNumber, types.randmNumCachedDay, types.randmNumCachedWeek ] }); setupTInput(node, { typeProp: 'sunFloorLengthType', valueProp: 'sunFloorLength', width: 'calc(100% - 110px)', defaultType: 'num', defaultValue: '0', types: ['num', 'msg', 'flow', 'global', 'env'] }); setupTInput(node, { typeProp: 'windowTopType', valueProp: 'windowTop', width: 'calc(100% - 110px)', defaultType: 'num', defaultValue: '', types: ['num', types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env'] }); setupTInput(node, { typeProp: 'windowBottomType', valueProp: 'windowBottom', width: 'calc(100% - 110px)', defaultType: 'num', defaultValue: '', types: ['num', types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env'] }); $('#node-input-windowSetMode').on('change focus focusout', () => { $('#node-input-sunControlMode').change(); }); if (node.windowSetMode !== 'orientation' && node.windowSetMode !== 'startEnd') { node.windowSetMode = 'startEnd'; $('#node-input-windowSetMode').val('startEnd'); } setupTInput(node, { typeProp: 'windowAzimuthStartType', valueProp: 'windowAzimuthStart', width: 'calc(100% - 110px)', defaultType: types.numAzimuth.value, defaultValue: '', types: [types.numAzimuth, types.numAnglePreDef, types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env'] }); setupTInput(node, { typeProp: 'windowAzimuthEndType', valueProp: 'windowAzimuthEnd', width: 'calc(100% - 110px)', defaultType: types.numAzimuth.value, defaultValue: '', types: [types.numAzimuth, types.numAnglePreDef, types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env'] }); setupTInput(node, { typeProp: 'windowOrientationType', valueProp: 'windowOrientation', width: 'calc(100% - 110px)', defaultType: types.numAzimuth.value, defaultValue: '', types: [types.numAzimuth, types.numAnglePreDef, types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env'] }); 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 blind // #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