const { create } = require('domain'); var path = require('path'); module.exports = function (RED) { var counter = 0; function MultiGroupNode(config) { const iid = ++counter; var ui = undefined; try { var node = this; if (ui === undefined) { ui = RED.require("node-red-dashboard")(RED); } RED.nodes.createNode(this, config); var html = `
`; var done = ui.addWidget({ node: node, order: config.order, group: config.group, width: config.width, height: config.height, format: html, templateScope: 'local', emitOnlyNewValues: false, forwardInputMessages: false, storeFrontEndInputAsState: false, convertBack: function (value) { return value; }, beforeEmit: function (msg, value) { return { msg: { iid: iid, width: config.width, height: config.height, sizes: ui.getSizes(), items: value, socketid: msg.socketid } }; }, beforeSend: function (msg, orig) { if (orig) { orig.msg.topic = config.topic; return orig.msg; } }, initController: function ($scope, events) { let sizes; let lastIid = -1; let superStorage = {}; function isSameObject(obj1, obj2) { for (var p in obj1) { if (p === 'action' || p === 'internal') { continue; } if (obj1.hasOwnProperty(p) !== obj2.hasOwnProperty(p)) { return false; } switch (typeof (obj1[p])) { // Deep compare objects case 'object': if (!isSameObject(obj1[p], obj2[p])) { return false; } break; // Compare function code case 'function': if (typeof (obj2[p]) == 'undefined' || (obj1[p].toString() != obj2[p].toString())) { return false; } break; // Compare array elements case 'array': if (typeof (obj2[p]) == 'undefined' || obj1[p].length != obj2[p].length) { return false; } for (let i = 0; i < obj1[p].length; i++) { if (!isSameObject(obj1[p][i], obj2[p][i])) { return false; } } // Compare values default: if (obj1[p] != obj2[p]) { return false; } } } // Check object 2 for any extra properties for (var p in obj2) { if (typeof (obj1[p]) == 'undefined') { return false; } } return true; } function widthToPx(width, includeMargin = true) { let widthPx = (width * sizes.sx); if (includeMargin) { widthPx = widthPx - (sizes.gx * 2); } return widthPx; } function heightToPx(height, includeMargin = true) { let heightPx = (height * sizes.sy); if (includeMargin) { heightPx = heightPx - (sizes.gy * 2); } return heightPx; } function checkIntegrity(items, parentId) { let id = 0; // Check if parentId ends with a number (It is the root node then and does not need to be checked) if (!(/\d$/.test(parentId))) { const parentFromStorage = superStorage[parentId]; if (!parentFromStorage) { return false; } if (items.length !== parentFromStorage.items.length) { return false; } } for (const item of items) { id = id + 1; const itemId = `${parentId}-${id}-${item.type}`; const itemFromStorage = superStorage[itemId]; if (!itemFromStorage) { return false; } if (item.width !== itemFromStorage.width || item.height !== itemFromStorage.height) { return false; } if (isSameObject(item, itemFromStorage)) { item.action = 'skip'; } else { item.action = 'update'; } if (item.type === 'container') { if (!checkIntegrity(item.items, itemId)) { return false; } item.action = 'update'; } } return true; } function createContainer(parent, config, containerId, includeMargin = false) { const widthPx = widthToPx(config.width, includeMargin); const heightPx = heightToPx(config.height, includeMargin); const container = $(`
`); container.css("width", `${widthPx}px`); container.css("height", `${heightPx}px`); if (includeMargin) { container.css("margin", `${sizes.gx}px ${sizes.gy}px`); } else { container.css("margin", `0px 0px`); } parent.append(container); updateContainer(container, config); } function updateContainer(container, config) { let id = 0; for (const item of config.items) { id = id + 1; switch (item.action) { case 'skip': break; case 'update': switch (item.type) { case 'container': const containerId = `${container.attr('id')}-${id}-container`; updateContainer(container.children(`#${containerId}`), item); break; case 'button': const buttonId = `${container.attr('id')}-${id}-button`; updateButton(container.children(`#${buttonId}`), item); break; case 'gauge': const gaugeId = `${container.attr('id')}-${id}-gauge`; updateGauge(container.children(`#${gaugeId}`), item); break; } break; default: switch (item.type) { case 'container': const containerId = `${container.attr('id')}-${id}-container`; createContainer(container, item, containerId); break; case 'button': const buttonId = `${container.attr('id')}-${id}-button`; createButton(container, item, buttonId); break; case 'gauge': const gaugeId = `${container.attr('id')}-${id}-gauge`; createGauge(container, item, gaugeId); break; default: console.log(`Unknown msg.items[i] type: ${msg.items[i].type}`); break; } break; } } superStorage[container.attr('id')] = config; } function createButton(parent, config, buttonId) { const widthPx = widthToPx(config.width); const heightPx = heightToPx(config.height); const button = $(` `); button.css("width", `${widthPx}px`); button.css("height", `${heightPx}px`); button.css("margin", `${sizes.gx}px ${sizes.gy}px`); button.click(function (e) { if (config.payload) { $scope.send({ "payload": config.payload }); } }); parent.append(button); updateButton(button, config); } function updateButton(button, config) { button.text(config.label); button.css("background-color", config.color ? config.color : ""); button.attr("disabled", config.disabled || false); superStorage[button.attr('id')] = config; } function createGauge(parent, config, gaugeId) { const canvasId = `${gaugeId}-canvas`; const labelId = `${gaugeId}-label`; const widthPx = widthToPx(config.width); const heightPx = heightToPx(config.height); const gaugeContainer = $(`
`); gaugeContainer.css("width", `${widthPx}px`); gaugeContainer.css("height", `${heightPx}px`); // gaugeContainer.css("margin", `${sizes.gx}px ${sizes.gy}px`); const gaugeLabel = $(`
`); gaugeContainer.append(gaugeLabel); const gaugeCanvas = $(``); gaugeContainer.append(gaugeCanvas); parent.append(gaugeContainer); let opts = { angle: 0, // The span of the gauge arc lineWidth: 0.4, // The line thickness radiusScale: 1, // Relative radius pointer: { length: 0.6, // // Relative to gauge radius strokeWidth: 0.07, // The thickness color: '#000000' // Fill color }, limitMax: true, // If false, max value increases automatically if value > maxValue limitMin: true, // If true, the min value of the gauge will be fixed colorStart: '#6FADCF', // Colors colorStop: '#8FC0DA', // just experiment with them strokeColor: '#E0E0E0', // to see which ones work best for you generateGradient: true, highDpiSupport: true, // High resolution support percentColors: config.smoothColors, }; const gauge = new Gauge(gaugeCanvas[0]).setOptions(opts); gauge.setMinValue(0); superStorage[`${gaugeId}-internal`] = gauge; updateGauge(gaugeContainer, config) } function updateGauge(gauge, config) { const gaugeId = gauge.attr('id'); const gaugeLabel = $(`#${gaugeId}-label`); const gaugeObj = superStorage[`${gaugeId}-internal`]; gaugeObj.maxValue = config.max; gaugeObj.set(config.value); gaugeLabel.html(config.label); superStorage[gaugeId] = config; } $scope.$watch('msg', function (msg) { if (!msg) { return; } const iid = msg.iid; sizes = msg.sizes; var root = $(`#multi-${iid}`); const rootItems = [{ type: "container", width: msg.width, height: msg.height, items: msg.items }]; if (!checkIntegrity(rootItems, `${root.attr('id')}`) || lastIid !== iid) { // Clear data root.children().remove(); superStorage = {}; // Set new iid lastIid = iid; // Reinitialize root = $(`#multi-${iid}`); checkIntegrity(rootItems, `${root.attr('id')}`); if (!root.length) { return; } const rootContainerId = `${root.attr('id')}-1-container`; createContainer(root, rootItems[0], rootContainerId); } else { updateContainer(root.children(), rootItems[0]); } }); } }); } catch (e) { console.log(e) } node.on('close', done); } RED.nodes.registerType("ui_multi_group", MultiGroupNode); var uipath = 'ui'; if (RED.settings.ui) { uipath = RED.settings.ui.path; } var fullPath = path.join('/', uipath, '/ui-multi-group/*').replace(/\\/g, '/'); RED.httpNode.get(fullPath, function (req, res) { var options = { root: __dirname + '/lib/', dotfiles: 'deny' }; res.sendFile(req.params[0], options) }); }