UNPKG

@maddytp/node-red-dashboard-2-ui-reef

Version:

Set of UI nodes for aquarium automation based on @flowfuse/node-red-dashboard

728 lines (725 loc) 35.6 kB
<script type="text/html" data-template-name="ui-output"> <style> .func-tabs-row { margin-bottom: 0; } #node-input-libs-container-row .red-ui-editableList-container { padding: 0px; } #node-input-libs-container-row .red-ui-editableList-container li { padding: 0px; } #node-input-libs-container-row .red-ui-editableList-item-remove { right: 5px; } #node-input-libs-container-row .red-ui-editableList-header { display: flex; background: var(--red-ui-tertiary-background); padding-right: 75px; border-top-left-radius: 3px; border-top-right-radius: 3px; } #node-input-libs-container-row .red-ui-editableList-header>div { flex-grow: 1; } .node-libs-entry { display: flex; } .node-libs-entry .red-ui-typedInput-container { border-radius: 0; border: none; } .node-libs-entry .red-ui-typedInput-type-select { border-radius: 0 !important; height: 34px; } .node-libs-entry>span>input[type=text] { border-radius: 0; border-top-color: var(--red-ui-form-background); border-bottom-color: var(--red-ui-form-background); border-right-color: var(--red-ui-form-background); } .node-libs-entry>span>input[type=text].input-error {} .node-libs-entry>span { flex-grow: 1; width: 50%; position: relative; } .node-libs-entry span .node-input-libs-var, .node-libs-entry span .red-ui-typedInput-container { width: 100%; } .node-libs-entry>span>span>i { display: none; } .node-libs-entry>span>span.input-error>i { display: inline; } </style> <input type="hidden" id="node-input-func"> <input type="hidden" id="node-input-noerr"> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name"></div> </div> <div class="form-row func-tabs-row"> <ul style="min-width: 400px; margin-bottom: 20px;" id="func-tabs"></ul> </div> <div id="func-tabs-content" style="min-height: calc(100% - 220px);"> <div id="func-tab-config" style="display:none"> <div class="form-row"> <label for="node-input-group"><i class="fa fa-table"></i> Group</span></label> <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-group"></div> </div> <div class="form-row"> <label for="node-input-label"><i class="fa fa-i-cursor"></i> Label</label> <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-label"></div> </div> <div class="form-row" id="template-row-size"> <label><i class="fa fa-object-group"></i> Size</span></label> <input type="hidden" id="node-input-width"> <input type="hidden" id="node-input-height"> <button class="editor-button" id="node-input-size"></button> </div> <div class="form-row"> <label for="node-input-topic"><i class="fa fa-envelope-o"></i> Topic</label> <input type="text" id="node-input-topic"> </div> <div class="form-row"> <label for='node-input-useThemeColors'><i class='fa fa-paint-brush'></i> Colors</label> <input type='checkbox' id='node-input-useThemeColors' style='width: auto; border: none; vertical-align: baseline;'> <span for='node-input-useThemeColors' style="padding-left: 5px;">Use theme colors?</span> </div> <div class="form-row" style="margin-bottom:0;"> <label style="width: 100px"><i class="fa fa-list"></i> Options:</label> <span id="configError" style="color:#DD0000"><b>All Values must be unique.</b></span> </div> <div class="form-row"> <ol id="node-input-opts-container"></ol> </div> </div> <div id="func-tab-body" style="display:none; min-height: calc(100% - 95px);"> <div class="form-row"> <label for="node-input-outputs" style="width: 100px"><i class="fa fa-random"></i> Outputs:</label> <input id="node-input-outputs" style="width: 50px;" value="1"> </div> <div class="form-row node-text-editor-row" style="position:relative"> <div style="height: calc(100% - 100px); min-height:100px;" class="node-text-editor" id="node-input-func-editor" ></div> <div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 5;"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div> </div> </div> <div id="func-tab-module" style="display:none; min-height: calc(100% - 95px);"> <div class="form-row node-input-libs-row hide" id="node-input-libs-container-row"> <ol id="node-input-libs-container"></ol> </div> </div> </div> </script> <script type="text/javascript"> (function () { var invalidModuleVNames = [ 'console', 'util', 'Buffer', 'Date', 'RED', 'node', '__node__', 'context', 'flow', 'global', 'env', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'promisify' ] var knownFunctionNodes = {}; RED.events.on("nodes:add", function (n) { if (n.type === "ui-output") { knownFunctionNodes[n.id] = n; } }) RED.events.on("nodes:remove", function (n) { if (n.type === "ui-output") { delete knownFunctionNodes[n.id]; } }) var missingModules = []; var missingModuleReasons = {}; RED.events.on("runtime-state", function (event) { if (event.error === "missing-modules") { missingModules = event.modules.map(function (m) { missingModuleReasons[m.module] = m.error; return m.module }); for (var id in knownFunctionNodes) { if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) { RED.editor.validateNode(knownFunctionNodes[id]) } } } else if (!event.text) { missingModuleReasons = {}; missingModules = []; for (var id in knownFunctionNodes) { if (knownFunctionNodes.hasOwnProperty(id) && knownFunctionNodes[id].libs && knownFunctionNodes[id].libs.length > 0) { RED.editor.validateNode(knownFunctionNodes[id]) } } } RED.view.redraw(); }); var installAllowList = ['*']; var installDenyList = []; var modulesEnabled = true; if (RED.settings.get('externalModules.modules.allowInstall', true) === false) { modulesEnabled = false; } var settingsAllowList = RED.settings.get("externalModules.modules.allowList") var settingsDenyList = RED.settings.get("externalModules.modules.denyList") if (settingsAllowList || settingsDenyList) { installAllowList = settingsAllowList; installDenyList = settingsDenyList } installAllowList = RED.utils.parseModuleList(installAllowList); installDenyList = RED.utils.parseModuleList(installDenyList); var allLibs = []; function moduleName(module) { var match = /^([^@]+)@(.+)/.exec(module); if (match) { return [match[1], match[2]]; } return [module, undefined]; } function getAllUsedModules() { var moduleSet = new Set(); for (var id in knownFunctionNodes) { if (knownFunctionNodes.hasOwnProperty(id)) { if (knownFunctionNodes[id].libs) { for (var i = 0, l = knownFunctionNodes[id].libs.length; i < l; i++) { if (RED.utils.checkModuleAllowed(knownFunctionNodes[id].libs[i].module, null, installAllowList, installDenyList)) { moduleSet.add(knownFunctionNodes[id].libs[i].module); } } } } } var modules = Array.from(moduleSet); modules.sort(); return modules; } function prepareLibraryConfig(node) { $(".node-input-libs-row").show(); var usedModules = getAllUsedModules(); var typedModules = usedModules.map(function (l) { return { icon: "fa fa-cube", value: l, label: l, hasValue: false } }) typedModules.push({ value: "_custom_", label: RED._("editor:subflow.licenseOther"), icon: "red/images/typedInput/az.svg" }) var libList = $("#node-input-libs-container").css('min-height', '100px').css('min-width', '450px').editableList({ header: $('<div><div data-i18n="node-red:function.require.moduleName"></div><div data-i18n="node-red:function.require.importAs"></div></div>'), addItem: function (container, i, opt) { var parent = container.parent(); var row0 = $("<div/>").addClass("node-libs-entry").appendTo(container); var fmoduleSpan = $("<span>").appendTo(row0); var fmodule = $("<input/>", { class: "node-input-libs-val", placeholder: RED._("node-red:function.require.module"), type: "text" }).css({ }).appendTo(fmoduleSpan).typedInput({ types: typedModules, default: usedModules.indexOf(opt.module) > -1 ? opt.module : "_custom_" }); if (usedModules.indexOf(opt.module) === -1) { fmodule.typedInput('value', opt.module); } var moduleWarning = $('<span style="position: absolute;right:2px;top:7px; display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fmoduleSpan); RED.popover.tooltip(moduleWarning.find("i"), function () { var val = fmodule.typedInput("type"); if (val === "_custom_") { val = fmodule.val(); } var errors = []; if (!RED.utils.checkModuleAllowed(val, null, installAllowList, installDenyList)) { return RED._("node-red:function.error.moduleNotAllowed", { module: val }); } else { return RED._("node-red:function.error.moduleLoadError", { module: val, error: missingModuleReasons[val] }); } }) var fvarSpan = $("<span>").appendTo(row0); var fvar = $("<input/>", { class: "node-input-libs-var red-ui-font-code", placeholder: RED._("node-red:function.require.var"), type: "text" }).css({ }).appendTo(fvarSpan).val(opt.var); var vnameWarning = $('<span style="position: absolute; right:2px;top:7px;display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fvarSpan); RED.popover.tooltip(vnameWarning.find("i"), function () { var val = fvar.val(); if (invalidModuleVNames.indexOf(val) !== -1) { return RED._("node-red:function.error.moduleNameReserved", { name: val }) } else { return RED._("node-red:function.error.moduleNameError", { name: val }) } }) fvar.on("change keyup paste", function (e) { var v = $(this).val().trim(); if (v === "" || / /.test(v) || invalidModuleVNames.indexOf(v) !== -1) { fvar.addClass("input-error"); vnameWarning.addClass("input-error"); } else { fvar.removeClass("input-error"); vnameWarning.removeClass("input-error"); } }); fmodule.on("change keyup paste", function (e) { var val = $(this).typedInput("type"); if (val === "_custom_") { val = $(this).val(); } var varName = val.trim().replace(/^@/, "").replace(/@.*$/, "").replace(/[-_/\.].?/g, function (v) { return v[1] ? v[1].toUpperCase() : "" }); fvar.val(varName); fvar.trigger("change"); if (RED.utils.checkModuleAllowed(val, null, installAllowList, installDenyList) && (missingModules.indexOf(val) === -1)) { fmodule.removeClass("input-error"); moduleWarning.removeClass("input-error"); } else { fmodule.addClass("input-error"); moduleWarning.addClass("input-error"); } }); if (RED.utils.checkModuleAllowed(opt.module, null, installAllowList, installDenyList) && (missingModules.indexOf(opt.module) === -1)) { fmodule.removeClass("input-error"); moduleWarning.removeClass("input-error"); } else { fmodule.addClass("input-error"); moduleWarning.addClass("input-error"); } if (opt.var) { fvar.trigger("change"); } }, removable: true }); var libs = node.libs || []; for (var i = 0, l = libs.length; i < l; i++) { libList.editableList('addItem', libs[i]) } } function getLibsList() { var _libs = []; if (RED.settings.functionExternalModules !== false) { var libs = $("#node-input-libs-container").editableList("items"); libs.each(function (i) { var item = $(this); var v = item.find(".node-input-libs-var").val(); var n = item.find(".node-input-libs-val").typedInput("type"); if (n === "_custom_") { n = item.find(".node-input-libs-val").val(); } if ((!v || (v === "")) || (!n || (n === ""))) { return; } _libs.push({ var: v, module: n }); }); } return _libs; } RED.nodes.registerType('ui-output', { color: 'rgb( 63, 173, 181)', category: 'ui reef', defaults: { name: { value: '' }, group: { type: 'ui-group', required: true }, order: { value: 0 }, width: { value: 6, validate: function (v) { let valid = true; const width = v || 0; const currentGroup = $('#node-input-group').val() || this.group; const groupNode = RED.nodes.node(currentGroup); valid = !groupNode || +width <= +groupNode.width; $('#node-input-size').toggleClass('input-error', !valid); return valid; }, }, height: { value: 1 }, label: { value: 'output' }, useThemeColors: { value: true }, options: { value: [], validate: function (v) { const unique = new Set(v.map(function (o) { return o.value; })); return v.length === unique.size && v.length > 1; }, }, topic: { value: '' }, func: { value: "\nreturn msg;" }, repeat: { value: 5, validate(v) { return ((v === '') || (RED.validators.number(v) && (v >= 1) && (v <= 2147483))); }, }, repeatUnit: { value: '1000', required: true, }, outputs: { value: 1 }, timeout: { value: RED.settings.functionTimeout || 0 }, noerr: { value: 0, required: true, validate: function (v, opt) { if (!v) { return true; } return RED._("node-red:function.error.invalid-js"); } }, initialize: { value: "" }, finalize: { value: "" }, libs: { value: [], validate: function (v, opt) { if (!v) { return true; } for (var i = 0, l = v.length; i < l; i++) { var m = v[i]; if (!RED.utils.checkModuleAllowed(m.module, null, installAllowList, installDenyList)) { return RED._("node-red:function.error.moduleNotAllowed", { module: m.module }); } if (m.var === "" || / /.test(m.var)) { return RED._("node-red:function.error.moduleNameError", { name: m.var }); } if (missingModules.indexOf(m.module) > -1) { return RED._("node-red:function.error.missing-module", { module: m.module }); } if (invalidModuleVNames.indexOf(m.var) !== -1) { return RED._("node-red:function.error.moduleNameError", { name: m.var }); } } return true; } } }, inputs: 1, outputs: 1, icon: 'font-awesome/fa-toggle-on', align: 'right', paletteLabel: 'output', label: function () { return this.name || 'output'; }, labelStyle: function () { return this.name ? "node_label_italic" : ""; }, oneditprepare: function () { var that = this; if (RED.nodes.subflow(this.z)) { $('#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 { $('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' }) } $('#node-input-size').elementSizer({ width: '#node-input-width', group: '#node-input-group', }); function getColor(idx) { const colors = ['#009933', '#999999', '#ff6666', '#009999', '#cccc00', '#ff33cc', '#cc6600', '#99ff66', '#660033', ]; if (idx > colors.length - 1) { return colors[Math.floor(Math.random() * colors.length)]; } return colors[idx]; } const optionsList = $('#node-input-opts-container').css('min-height', '200px').editableList({ header: $('<div>').css({ 'padding-left': '32px', 'padding-right': '32px', gap: '12px', display: 'flex' }).append($.parseHTML( "<div style='width:40%; display: inline-grid'><b>Label</b></div>" + "<div style='width:40%; display: inline-grid'><b>Value</b></div>" + "<div style='width:10%; display: inline-grid' class='node-input-option-color'><b>Color</b></div>", )), addItem(container, i, option) { const row = $('<div/>').appendTo(container); const labelField = $('<input/>', { class: 'node-input-option-label', type: 'text' }).css({ width: '40%', 'margin-left': '5px', 'margin-right': '5px' }).appendTo(row); labelField.val(option.label || `Option ${i}`); const valueField = $('<input/>', { class: 'node-input-option-value', type: 'text' }).css({ width: '40%', 'margin-left': '5px', 'margin-right': '5px' }).appendTo(row); valueField.typedInput({ types: ['str', 'num', 'bool', { value: 'func', label: 'function', icon: 'fa fa-code', hasValue: false, }], }); valueField.typedInput('type', option.valueType || 'str'); valueField.typedInput('value', option.value || `option_${i}`); valueField.on('change', function (event, type, value) { if (type === 'func' && value !== 'func') { valueField.typedInput('value', 'func'); } validate(); }); const colofield = $('#node-input-useThemeColors').prop('checked') ? 'none' : 'inline-block'; const colorField = $('<input/>', { class: 'node-input-option-color', type: 'color' }).css({ width: '10%', 'margin-left': '5px', display: colofield }).appendTo(row); colorField.val(option.color || getColor(i)); validate(); }, removeItem: function (data) { validate(); }, removable: true, sortable: true, }); if (this.options) { this.options.forEach(function (option, index) { optionsList.editableList('addItem', { index: index, label: option.label, value: option.value, valueType: option.valueType, color: option.color, }); }); } $('#node-input-useThemeColors').on('change', function () { const colorFields = $('.node-input-option-color'); if ($(this)[0].checked) { colorFields.hide(); } else { colorFields.show(); } }); var tabs = RED.tabs.create({ id: "func-tabs", onchange: function (tab) { $("#func-tabs-content").children().hide(); $("#" + tab.id).show(); let editor = $("#" + tab.id).find('.monaco-editor').first(); if (editor.length) { if (that.editor.nodered && that.editor.type == "monaco") { that.editor.nodered.refreshModuleLibs(getLibsList()); } RED.tray.resize(); //auto focus editor on tab switch if (that.editor.getDomNode() == editor[0]) { that.editor.focus(); } } } }); tabs.addTab({ id: "func-tab-config", iconClass: "fa fa-cog", label: 'Setup' }); tabs.addTab({ id: "func-tab-body", iconClass: "fa fa-sign-in", label: 'On Message' }); tabs.addTab({ id: "func-tab-module", iconClass: "fa fa-cubes", label: ' Modules' }); tabs.activateTab("func-tab-config"); $("#node-input-outputs").spinner({ min: 0, max: 500, change: function (event, ui) { var value = parseInt(this.value); value = isNaN(value) ? 1 : value; value = Math.max(value, parseInt($(this).attr("aria-valuemin"))); value = Math.min(value, parseInt($(this).attr("aria-valuemax"))); if (value !== this.value) { $(this).spinner("value", value); } } }); const buildEditor = function (id, stateId, focus, value, defaultValue, extraLibs, offset) { const editor = RED.editor.createEditor({ id: id, mode: 'ace/mode/nrjavascript', value: value || defaultValue || "", stateId: stateId, focus: true, globals: { msg: true, context: true, RED: true, util: true, flow: true, global: true, console: true, Buffer: true, setTimeout: true, clearTimeout: true, setInterval: true, clearInterval: true }, extraLibs: extraLibs }); if (defaultValue && value === "") { editor.moveCursorTo(defaultValue.split("\n").length + offset, 0); } editor.__stateId = stateId; return editor; } this.editor = buildEditor('node-input-func-editor', this.id + "/" + "editor", true, $("#node-input-func").val(), undefined, that.libs || [], undefined, -1); RED.library.create({ url: "functions", type: "ui-output", editor: this.editor, mode: "ace/mode/nrjavascript", fields: [ 'name', 'outputs', 'timeout', { name: 'info', get: function () { return that.infoEditor.getValue(); }, set: function (v) { that.infoEditor.setValue(v || "", -1); } } ], ext: "js" }); var expandButtonClickHandler = function (editor) { return function (e) { e.preventDefault(); var value = editor.getValue(); editor.saveView(`inside function-expandButtonClickHandler ${editor.__stateId}`); var extraLibs = that.libs || []; RED.editor.editJavaScript({ value: value, width: "Infinity", stateId: editor.__stateId, mode: "ace/mode/nrjavascript", focus: true, cancel: function () { setTimeout(function () { editor.focus(); }, 250); }, complete: function (v, cursor) { editor.setValue(v, -1); setTimeout(function () { editor.restoreView(); editor.focus(); }, 250); }, extraLibs: extraLibs }); } } $("#node-function-expand-js").on("click", expandButtonClickHandler(this.editor)); RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand")); if (RED.settings.functionExternalModules !== false) { prepareLibraryConfig(that); } function validate() { const that = this; this.options = []; const optionsList = $('#node-input-opts-container').editableList('items'); optionsList.each(function (i) { const option = $(this); const index = i; const label = option.find('.node-input-option-label').val(); const valueType = option.find('.node-input-option-value').typedInput('type'); const value = option.find('.node-input-option-value').typedInput('value'); const color = option.find('.node-input-option-color').val(); that.options.push({ index: index, label: label, value: value, valueType: valueType, color: color, }); }); const un = new Set(this.options.map(function (o) { return o.value; })); if (this.options.length !== un.size) { $('#configError').text('Values must be unique'); $('#configError').show(); } else if (this.options.length < 2) { $('#configError').text('Configure at least two options'); $('#configError').show(); } else { $('#configError').hide(); } } if (this.options) { validate(); } }, oneditsave: function () { var node = this; node.options = []; const optionsList = $('#node-input-opts-container').editableList('items'); optionsList.each(function (i) { const option = $(this); const index = i; const label = option.find('.node-input-option-label').val(); const valueType = option.find('.node-input-option-value').typedInput('type'); const value = option.find('.node-input-option-value').typedInput('value'); const color = option.find('.node-input-option-color').val(); node.options.push({ index, label, value, valueType, color, }); }); var noerr = 0; $("#node-input-noerr").val(0); var disposeEditor = function (editorName, targetName, defaultValue) { var editor = node[editorName]; var annot = editor.getSession().getAnnotations(); for (var k = 0; k < annot.length; k++) { if (annot[k].type === "error") { noerr += annot.length; break; } } var val = editor.getValue(); if (defaultValue) { if (val.trim() == defaultValue.trim()) { val = ""; } } editor.destroy(); delete node[editorName]; $("#" + targetName).val(val); } disposeEditor("editor", "node-input-func"); $("#node-input-noerr").val(noerr); this.noerr = noerr; node.libs = getLibsList(); }, oneditcancel: function () { var node = this; node.editor.destroy(); delete node.editor; }, oneditresize: function (size) { var rows = $("#dialog-form>div:not(.node-text-editor-row)"); var height = $("#dialog-form").height(); for (var i = 0; i < rows.length; i++) { height -= $(rows[i]).outerHeight(true); } var editorRow = $("#dialog-form>div.node-text-editor-row"); height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom"))); $("#dialog-form .node-text-editor").css("height", height + "px"); var height = size.height; $("#node-input-func-editor").css("height", (height - 125) + "px"); this.editor.resize(); $("#node-input-libs-container").css("height", (height - 125) + "px"); } }); })(); </script>