UNPKG

node-red-contrib-displayext-node

Version:

A node for software DisplayEXT

1,244 lines (1,178 loc) 178 kB
<style> :root { --nr-db-dark-text: #444; --nr-db-light-text: #eee; --nr-db-disabled-text: #999; --nr-db-mid-grey: #7f7f7f; } .nr-db-sb { position: absolute; top: 1px; bottom: 2px; left: 1px; right: 1px; overflow-y: auto; padding: 10px; } .nr-db-sb .form-row label { display: block; width: auto; } .nr-db-sb .form-row input, .nr-db-sb .form-row select { width: calc(100% - 100px); margin-bottom:0; } .nr-db-sb .compact { margin-bottom: 8px !important; } .nr-db-sb .red-ui-editableList-container { padding: 0; min-height: 250px; height: auto; } .nr-db-sb-tab-list { min-height: 250px; height: auto; } .nr-db-sb-tab-list li { padding: 0; } .nr-db-sb-tab-list-item { border-radius: 4px; color: var(--red-ui-primary-text-color, var(--nr-db-dark-text)); } .nr-db-sb-list-header { cursor: pointer; position:relative; color: var(--red-ui-header-text-color, var(--nr-db-dark-text)); padding:3px; white-space: nowrap; } .nr-db-sb-list-header:hover { color: var(--red-ui-secondary-text-color-hover, var(--nr-db-dark-text)); } .nr-db-sb-title-hidden { text-decoration: line-through; } .nr-db-sb-title-disabled { color: var(--red-ui-secondary-text-color-disabled, var(--nr-db-disabled-text)); } .nr-db-sb-tab-list-header { background: var(--red-ui-primary-background, var(--nr-db-light-text)); padding:5px; } .nr-db-sb-group-list-header:hover, .nr-db-sb-widget-list-header:hover { background: var(--red-ui-secondary-background-hover, var(--nr-db-light-text)); } .nr-db-sb-list-chevron { width: 15px; text-align: center; margin: 3px 5px 3px 5px; } .nr-db-sb-tab-list-item .red-ui-editableList-container { border-radius: 0; border: none; height: auto !important; min-height: unset; } .nr-db-sb-list-handle { vertical-align: top; opacity: 0; cursor: move; } .nr-db-sb-list-header:hover>.nr-db-sb-list-handle, .nr-db-sb-list-header:hover>.nr-db-sb-list-header-button-group { opacity: 1; } .nr-db-sb-list-header-button-group { opacity: 0; } .nr-db-sb-list-handle { color: var(--red-ui-tertiary-text-color, var(--nr-db-light-text)); padding:5px; } .nr-db-sb-tab-list-header>.nr-db-sb-list-chevron { margin-left: 0px; transition: transform 0.2s ease-in-out; } .nr-db-sb-group-list-header>.nr-db-sb-list-chevron { margin-left: 20px; transition: transform 0.2s ease-in-out; } .nr-db-sb-group-list { min-height: 10px; } .nr-db-sb-group-list li { border-bottom-color: var(--red-ui-secondary-border-color, var(--nr-db-light-text)); } .nr-db-sb-group-list>li.ui-sortable-helper { border-top: 1px solid var(--red-ui-secondary-border-color, var(--nr-db-light-text)); } .nr-db-sb-group-list>li:last-child { border-bottom: none; } .nr-db-sb-widget-list>li { border: none !important; } .nr-db-sb-group-list>li>.red-ui-editableList-item-handle { left: 10px; } .nr-db-sb-list-button-group { position: absolute; right: 3px; top: 0px; z-index: 2; } .nr-db-sb-list-header-button-group { position: absolute; right: 3px; top: 4px; } .nr-db-sb-list-header-button { margin-left: 5px; } .nr-db-sb li.ui-sortable-helper { opacity: 0.9; } .nr-db-sb-widget-icon { margin-left: 56px; } .nr-db-sb-icon { margin-right: 10px; } .nr-db-sb-link { display: inline-block; padding-left: 20px; } .nr-db-sb-link-name-container .fa-external-link { margin-right: 10px; } .nr-db-sb-link-url { font-size: 0.8em; color: var(--red-ui-secondary-text-color, var(--nr-db-mid-grey)); } span.nr-db-color-pick-container { max-width: 50px; border-radius: 3px; margin-left: 15px; } input.nr-db-field-themeColor[type="color"] { width: 60px !important; padding: 0px; height: 20px; box-shadow: none; position: absolute; right: 36px; border-radius: 3px !important; border: solid 1px var(--red-ui-form-input-border-color, #ccc); -webkit-appearance: none; font-size: smaller; text-align: center; } input.nr-db-field-themeColor::-webkit-color-swatch { border: none; } .red-ui-tabs { margin-bottom: 15px; } .red-ui-tab.hidden { display: none; } #dashboard-tabs-list li a:hover { cursor: pointer; } #dash-link-button { background: none; border: none; margin-top: 3px; display: inline-block; margin: 3px 0px 0px 3px; height: 32px; line-height: 29px; max-width: 200px; overflow: hidden; white-space: nowrap; position: relative; padding: 0px 7px 0px 7px; } ul.red-ui-dashboard-theme-styles { list-style: none; } ul.red-ui-dashboard-theme-styles li { margin-bottom: 6px; } .nr-db-resetIcon { margin: 3px 6px 0px 6px; float: right; color: var(--red-ui-secondary-text-color, var(--nr-db-mid-grey)); opacity: 0.8; display: block; } .nr-db-resetIcon:hover { cursor: pointer; } #nr-db-field-font { margin-left: 2em; width: calc(100% - 81px); } .nr-db-theme-label { font-weight: bold; } #custom-theme-library-container .btn-group { margin-bottom: 10px; } </style> <!-- Dashboard layout tool --> <link rel="stylesheet" href="./ui_base/gs/gridstack.min.css"> <link rel="stylesheet" href="./ui_base/css/gridstack-extra.min.css"> <style> .grid-stack { background-color: var(--red-ui-primary-background, #f8f8f8); border: solid 2px var(--red-ui-tertiary-border-color, #C0C0C0); margin: auto; min-height: 42px; display: table-cell; background-image: linear-gradient(var(--red-ui-tertiary-border-color, #C0C0C0) 1px, transparent 0), linear-gradient(90deg, var(--red-ui-tertiary-border-color, #C0C0C0) 1px, transparent 0); background-size: 40px 43px; } .grid-stack>.grid-stack-item>.grid-stack-item-content { top: 3px; left: 5px; right: 5px; bottom: 3px; } .grid-stack-item-content { color: #2c3e50; text-align: center; background-color: #b0dfe3; border-radius: 2px; font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif; white-space: nowrap; font-size: 12px; opacity: 0.7; } .grid-stack-item { cursor: move; } .nr-dashboard-layout-container-fluid { width: 100%; padding-right: 0px; padding-left: 0px; margin-right: 0px; margin-left: 0px; } .nr-dashboard-layout-row { width: 100%; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: 0px; margin-left: 0px; } .nr-dashboard-layout-span12 { width: 98.4%; padding: 2px; margin-left: 2px; } .nr-dashboard-layout-span6 { width: 49.2%; padding: 2px; margin-left: 2px; } .nr-dashboard-layout-span4 { width: 32.7%; padding: 2px; margin-left: 2px; } .nr-dashboard-layout-span3 { width: 24.3%; padding: 2px; margin-left: 2px; } .nr-dashboard-layout-span2 { width: 16.0%; padding: 2px; margin-left: 2px; } .nr-dashboard-layout-resize-disable { cursor: pointer; float: right; position: relative; z-index: 90; margin-right: 4px; } .nr-dashboard-layout-resize-enable { cursor: pointer; float: right; position: relative; z-index: 90; margin-right: 1px; } .grid-stack>.ui-state-disabled { opacity: 1; background-image: none; } .grid-stack>.grid-stack-item>.ui-resizable-handle { z-index: 90; margin-right: -7px; } </style> <script type="text/javascript"> (function($) { var editSaveEventHandler; var nodesAddEventHandler; var nodesRemoveEventHandler; var layoutUpdateEventHandler; // Dashboard layout tool var uip = 'ui'; var attemptedVendorLoad = false; var ensureDashboardNode; var isTabLockSupported = ((typeof RED.workspaces.isLocked) === "function"); // check for new Node-RED version function notifyLocked() { RED.notify("Can't change tab with nodes in locked tab"); } // check if specified tab is locked function isLockedTab(id) { if (!isTabLockSupported) { return false; } return (RED.workspaces.isLocked(id)); } // check if some node in tab placed on locked tab function isLocked(id) { if (!isTabLockSupported) { return false; } const tab = getTabDataFromNodes(id); const groups = tab.groups; for (let i = 0; i < groups.length; i++) { const group = groups[i]; const widgets = group.widgets; for (let j = 0; j < widgets.length; j++) { const widget = RED.nodes.node(widgets[j].id); if (RED.workspaces.isLocked(widget.z)) { return true; } } } return false; } var loadTinyColor = function(path) { $.ajax({ url: path, success: function (data) { var jsScript = document.createElement("script"); jsScript.type = "application/javascript"; jsScript.src = path; document.body.appendChild(jsScript); //console.log('Tiny Color Loaded:',path); }, error: function (xhr, ajaxOptions, thrownError) { if (xhr.status === 404 && !attemptedVendorLoad) { loadTinyColor('/'+uip+'/vendor/tinycolor2/dist/tinycolor-min.js'); attemptedVendorLoad = true; } //console.log('Tiny Color Failed to load:',path); } }); } // convert to i18 text function c_(x) { return RED._("node-red-dashboard/ui_base:ui_base."+x); } // Try to load dist version first // then if fails, load non dist version loadTinyColor('ui_baseDE/js/tinycolor-min.js'); //loadTinyColor('ui_base/tinycolor2/dist/tinycolor-min.js'); // Dashboard layout tool // Load gridstack library var loadGsLib = function(path, callback) { $.ajax({ url: path, success: function (data) { var jsScript = document.createElement("script"); jsScript.type = "application/javascript"; jsScript.src = path; document.body.appendChild(jsScript); if (callback) { callback(); } }, error: function (xhr, ajaxOptions, thrownError) { // TODO } }); }; loadGsLib('ui_baseDE/gs/gridstack.min.js', function() { loadGsLib('ui_baseDE/gs/gridstack.jQueryUI.min.js', null) }); var tabDatas; // Layout editing tab data var oldSpacer; // Spacer not needed after editing var widthChange; // Group width change var widgetResize; // Change widget event var widgetDrag; // Drag wiget event var MAX_GROUP_WIDTH = 50; // The maximum width is 30 ///////////////////////////////////////////////////////// // Get widget under specified tab from node information ///////////////////////////////////////////////////////// function getTabDataFromNodes(tabID) { var nodes = RED.nodes.createCompleteNodeSet(false); var tab = {}; // Tab information for (var cnt = 0; cnt < nodes.length; cnt++) { if (nodes[cnt].id == tabID) { if (nodes[cnt].type == "ui_tabDE" || nodes[cnt].type == "ui_linkDE") { tab = { id: nodes[cnt].id, name: nodes[cnt].name, type: nodes[cnt].type, order: nodes[cnt].order, groups: [] }; break; } } } // Group information for (var cnt = 0; cnt < nodes.length; cnt++) { if (nodes[cnt].type == "ui_groupDE" && nodes[cnt].tab == tabID) { var group = { id: nodes[cnt].id, name: nodes[cnt].name, type: nodes[cnt].type, order: nodes[cnt].order, width: nodes[cnt].width, widgets: [] }; tab.groups.push(group); } } // Widget information var groupsIdx = {}; for (var cnt = 0; cnt < tab.groups.length; cnt++) { groupsIdx[tab.groups[cnt].id] = tab.groups[cnt]; } for (var cnt = 0; cnt < nodes.length; cnt++) { var group = groupsIdx[nodes[cnt].group]; if (group != null && (/^ui_/.test(nodes[cnt].type) && nodes[cnt].type !== 'ui_linkDE' && nodes[cnt].type !== 'ui_toastDE' && nodes[cnt].type !== 'ui_ui_controlDE' && nodes[cnt].type !== 'ui_audioDE' && nodes[cnt].type !== 'ui_baseDE' && nodes[cnt].type !== 'ui_groupDE' && nodes[cnt].type !== 'ui_tabDE')) { var widget = { id: nodes[cnt].id, type: nodes[cnt].type, order: nodes[cnt].order, width: nodes[cnt].width, height: nodes[cnt].height, auto: nodes[cnt].width == 0 ? true : false }; group.widgets.push(widget); if (!isLayoutToolSupported(nodes[cnt].type)) { console.log("LayoutTool warning: Unsupported widget. Widget="+JSON.stringify(widget)); } } } return tab; } ////////////////////////////////////////////////// // Update node information in the edited widget //////////////////////////////////////////////////// function putTabDataToNodes() { // Delete old flow spacer node for (var cnt = 0; cnt < oldSpacer.length; cnt++) { RED.nodes.remove(oldSpacer[cnt]); RED.nodes.dirty(true); RED.view.redraw(true); } var t_groups = tabDatas.groups; for (var cnt1 = 0; cnt1 < t_groups.length; cnt1++) { var n_group = RED.nodes.node(t_groups[cnt1].id); n_group.width = t_groups[cnt1].width; var t_widgets = t_groups[cnt1].widgets; for (var cnt2 = 0; cnt2 < t_widgets.length; cnt2++) { var n_widget = RED.nodes.node(t_widgets[cnt2].id); if (n_widget != null) { if (n_widget.group !== n_group.id) { var oldGroupNode = RED.nodes.node(n_widget.group); if (oldGroupNode) { var index = oldGroupNode.users.indexOf(n_widget); oldGroupNode.users.splice(index,1); } n_widget.group = n_group.id; n_group.users.push(n_widget); } n_widget.order = t_widgets[cnt2].order; if (t_widgets[cnt2].auto === true ) { n_widget.width = 0; n_widget.height = 0; } else { n_widget.width = t_widgets[cnt2].width; n_widget.height = t_widgets[cnt2].height; } n_widget.changed = true; n_widget.dirty = true; RED.editor.validateNode(n_widget); RED.events.emit("layout:update",n_widget); RED.nodes.dirty(true); RED.view.redraw(true); } else { // Add a spacer node if (t_widgets[cnt2].type === 'ui_spacerDE') { var spaceNode = { _def: RED.nodes.getType("ui_spacerDE"), type: "ui_spacerDE", hasUsers: false, users: [], id: RED.nodes.id(), tab: tabDatas.id, group: n_group.id, order: t_widgets[cnt2].order, name: "spacer", width: t_widgets[cnt2].width, height: t_widgets[cnt2].height, z: RED.workspaces.active(), label: function() { return this.name + " " + this.width + "x" + this.height; } }; RED.nodes.add(spaceNode); RED.editor.validateNode(spaceNode); RED.nodes.dirty(true); RED.view.redraw(true); } } }; } RED.sidebar.info.refresh(); } //////////////////////////////////////// // Sort by order //////////////////////////////////////// function compareOrder(a, b) { var r = 0; if (a.order < b.order) { r = -1; } else if (a.order > b.order) { r = 1; } return r; } //////////////////////////////////////// // Sort by XY //////////////////////////////////////// function compareXY(a, b) { var r = 0; if (a.y < b.y) { r = -1; } else if (a.y > b.y) { r = 1; } else if (a.x < b.x) { r = -1; } else if (a.x > b.x) { r = 1; } return r; } /////////////////////////////////////////////////////// // Placeable location search (placed in the upper left) /////////////////////////////////////////////////////// function search_point(width, height, maxWidth, maxHeight, tbl) { for (var y=0; y < maxHeight; y++) { for (var x=0; x < maxWidth; x++) { if (check_matrix(x, y, width, height, maxWidth, tbl)) { fill_matrix(x, y, width, height, maxWidth, tbl); return {x:x, y:y}; } } } return false; } // Check placement position function check_matrix(px, py, width, height, maxWidth, tbl) { if (px+width > maxWidth) return false; for (var y=py; y < py+height; y++) { for (var x=px; x<px+width; x++) { if (tbl[maxWidth*y+x]) return false; } } return true; } // Mark the placement position function fill_matrix(px, py, width, height, maxWidth, tbl) { for (var y=py; y < py+height; y++) { for (var x=px; x < px+width; x++) { tbl[maxWidth*y+x] = 1; } } } //////////////////////////////////////////////////// // Apply edit result to tab information for editing //////////////////////////////////////////////////// function saveGridDatas() { var groups = tabDatas.groups; for (var cnt = 0; cnt < groups.length; cnt++) { // Get layout editing results var gridID = '#grid'+cnt; var serializedData = []; $(gridID+'.grid-stack > .grid-stack-item:visible').each( function (index) { el = $(this); var node = el.data('_gridstack_node'); serializedData.push({ id: el[0].dataset.noderedid, type: el[0].dataset.noderedtype, group: groups[cnt].id, width: Number(node.width), height: Number(node.height), x: node.x, y: node.y, auto: (el[0].dataset.noderedsizeauto == 'true') ? true : false }); }); var width = Number(groups[cnt].width); var height = 0; // Search group height for (var i=0; i < serializedData.length; i++) { var wd = serializedData[i]; if (height < wd.y + wd.height) { height = wd.y + wd.height; } } // Place widget on table var tbl = new Array(width * height); for (var i = 0; i< tbl.length; i++) { tbl[i]=0; } for (var i = 0; i < serializedData.length; i++) { var wd = serializedData[i]; for (var y = wd.y; y < wd.y+wd.height; y++) { for (var x = wd.x; x < wd.x+wd.width; x++) { tbl[width*y+x]=1; } } } // Add Spacer to Blank for (var y = 0; y < height; y++) { var spacerAdded = false; for (var x = 0; x < width; x++) { if (tbl[width*y+x]===0) { if (!spacerAdded) { // Add 1x1 spacer serializedData.push({ x: x, y: y, z: RED.workspaces.active(), width: 1, height: 1, name: 'spacer', type: 'ui_spacer' }); spacerAdded = true; } else { // Extend the spacer width by 1 serializedData[serializedData.length-1].width += 1; } } else { spacerAdded = false; } } } // Sort Gridstack objects by x, y information serializedData.sort(compareXY); // Delete x and y elements as information for sorting, and give order var order = 0; for (i in serializedData) { order++; delete serializedData[i].x; delete serializedData[i].y; serializedData[i].order = order; } // Update widget information in group with edited data var group = groups[cnt]; delete group.widgets; group.widgets = serializedData; } // Save process call putTabDataToNodes(); }; //////////////////////////////////////////////////// // Get default height for automatic settings //////////////////////////////////////////////////// function getDefaultHeight(nodeID, groupWidth) { var redNode = RED.nodes.node(nodeID); var height = 1; if (redNode.type === 'ui_gauge') { if (redNode.gtype === 'gage') { height = Math.round(groupWidth/2)+1; } else if (redNode.gtype === 'wave') { if (groupWidth < 3) { height = 1; } else { height = Math.round(groupWidth*0.75); } } else { // donut or compass if (groupWidth < 3) { height = 1; } else if (groupWidth < 11) { height = groupWidth - 1; } else { height = Math.round(groupWidth*0.95); } } } else if (redNode.type === 'ui_chartDE') { height = Math.floor(groupWidth/2)+1; } else if (redNode.type === 'ui_formDE') { // var optNum = redNode.options.length; // Sub widget number // if (redNode.label) { // height = optNum + 2; // Label and Button // } else { // height = optNum + 1; // Button only // } height = redNode.rowCount } else if (redNode.type === 'ui_lineargaugeDE') { if (redNode.unit && redNode.name) { height = 5; } else { height = 4; } } else if (redNode.type === 'ui_listDE') { height = 5; } else if (redNode.type === 'ui_vegaDE') { height = 5; } return height; } ///////////////////////////// // Grid width change //////////////////////////// var changeGroupWidth = function(id) { var widthID = '#change-width'+id; var gridID = '#grid'+id; $(widthID).spinner( { min: 1, max: MAX_GROUP_WIDTH, spin: function(event, ui) { // Search current maximum width var serializedData = []; $(gridID+'.grid-stack > .grid-stack-item:visible').each( function (index) { el = $(this); var node = el.data('_gridstack_node'); serializedData.push({ width: Number(node.width), x: node.x, auto: (el[0].dataset.noderedsizeauto == 'true') ? true : false }); }); var maxWidth = 0; for (var i=0; i < serializedData.length; i++) { var wd = serializedData[i]; if (wd.auto == false) { if (maxWidth < wd.x + wd.width) { maxWidth = wd.x + wd.width; } } } var width = ui.value; if (width < maxWidth) { width = maxWidth; } var grid = $(gridID+'.grid-stack').data('gridstack'); $(gridID+'.grid-stack').css("width", width * 40); $(gridID+'.grid-stack').css("background-size", 100/width+"% 43px"); grid.setColumn(tabDatas.groups[id].width, true); grid.setColumn(width, true); tabDatas.groups[id].width = width; $(gridID+'.grid-stack > .grid-stack-item:visible').each( function(idx, el) { el = $(el); var node = el.data('_gridstack_node'); var auto = (el[0].dataset.noderedsizeauto == 'true') ? true : false; var type = el[0].dataset.noderedtype; grid.resizable(el, !auto); if (auto === true) { grid.resize(el, width, getDefaultHeight(node.id, width)); } }); if (width !== ui.value) { event.stopPropagation(); event.preventDefault(); } } }); }; ////////////////////////////////// // Move between groups of widgets ////////////////////////////////// function handleMove(grid) { return function(ev, prevWidget, newWidget) { var elem = newWidget.el[0]; if (elem.getAttribute("data-noderedsizeauto") === "true") { var id = elem.getAttribute("data-noderedid"); var width = grid.grid.column; var height = getDefaultHeight(id, width); grid.move(elem, 0, newWidget.y); grid.resize(elem, width, height); var en = $(elem).find('.nr-dashboard-layout-resize-enable'); en.off('click'); en.on('click',layoutResizeEnable); en[0].setAttribute("title",c_("layout.auto")); } else { var ds = $(elem).find('.nr-dashboard-layout-resize-disable'); ds.off('click'); ds.on('click',layoutResizeDisable); ds[0].setAttribute("title",c_("layout.manual")); } }; } ////////////////////////////////////////// // Widget size change (start event) ////////////////////////////////////////// var resizeGroupWidget = function(id) { var gridID = '#grid'+id; var grid = $(gridID+'.grid-stack').data('gridstack'); $(gridID+'.grid-stack').on('resizestart', function(event, ui) { // Reset group width grid.setColumn(tabDatas.groups[id].width, true); }); } ////////////////////////////////////////// // Widget drag (start event) ////////////////////////////////////////// var dragGroupWidget = function(id) { var gridID = '#grid'+id; var grid = $(gridID+'.grid-stack').data('gridstack'); $(gridID+'.grid-stack').on('dragstart', function(event, ui) { // Reset group width grid.setColumn(tabDatas.groups[id].width, true); }); } ////////////////////////////////////////// // Layout resize Disable (Auto:false) ////////////////////////////////////////// var layoutResizeDisable = function(e) { var target = $(e.target); var el = target.parents('.grid-stack-item:visible'); var grid = target.parents('.grid-stack').data('gridstack'); var node = el.data('_gridstack_node'); var id = Number(target.parents('.grid-stack').attr('id').slice(4)); var width = Number(tabDatas.groups[id].width); var nodeID = el[0].dataset.noderedid; var height = getDefaultHeight(nodeID, width); grid.move(el, 0, node.y); grid.resize(el, width, height); grid.resizable(el, false); el.find('.nr-dashboard-layout-resize-disable').off('click'); el.attr({'data-noderedsizeauto':'true'}); target.removeClass().addClass('fa fa-unlock nr-dashboard-layout-resize-enable'); el.find('.nr-dashboard-layout-resize-enable')[0].setAttribute("title",c_("layout.auto")); el.find('.nr-dashboard-layout-resize-enable').on('click',layoutResizeEnable); } ////////////////////////////////////////// // Layout resize Enable (Auto:true) ////////////////////////////////////////// var layoutResizeEnable = function(e) { var target = $(e.target); var el = target.parents('.grid-stack-item:visible'); var grid = target.parents('.grid-stack').data('gridstack'); grid.resizable(el, true); el.find('.nr-dashboard-layout-resize-enable').off('click'); el.attr({'data-noderedsizeauto':'false'}); target.removeClass().addClass('fa fa-lock nr-dashboard-layout-resize-disable'); el.find('.nr-dashboard-layout-resize-disable')[0].setAttribute("title",c_("layout.manual")); el.find('.nr-dashboard-layout-resize-disable').on('click',layoutResizeDisable); } ////////////////////////////////////////// // Check dashboard layout tool supported ////////////////////////////////////////// function isLayoutToolSupported(nodeType) { if (nodeType.indexOf("ui_") !== 0) { return false; } else { return true; } } RED.nodes.registerType('ui_baseDE', { category: 'config', defaults: { name: {}, theme: {}, site: {} }, hasUsers: false, paletteLabel: 'Dashboard', label: function() { return this.name || 'Node-RED Dashboard'; }, labelStyle: function() { return this.name ? "node_label_italic" : ""; }, onpaletteremove: function() { RED.sidebar.removeTab("dashboard"); RED.events.off("editor:save",editSaveEventHandler); RED.events.off("nodes:add",nodesAddEventHandler); RED.events.off("nodes:remove",nodesRemoveEventHandler); RED.events.off("layout:update",layoutUpdateEventHandler); // Dashboard layout tool }, onpaletteadd: function() { var globalDashboardNode = null; var editor; var baseStyles = ['base-color']; var configurableStyles = ['page-titlebar-backgroundColor', 'page-backgroundColor', 'page-sidebar-backgroundColor', 'group-textColor', 'group-borderColor', 'group-backgroundColor', 'widget-textColor', 'widget-backgroundColor','widget-borderColor']; var baseFontName = "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"; var aTheme = {primary:"indigo", accents:"blue", warn:"red", background:"grey", palette:"light"}; // tiny colour implementation var colours = { leastReadable: function(base, colours) { var least = tinycolor.readability(base, colours[0]); var leastColor = colours[0]; for (var i=1; i<colours.length; i++) { var readability = tinycolor.readability(base, colours[i]); if (readability < least) { least = readability; leastColor = colours[i]; } } return leastColor; }, whiteGreyMostReadable: function (base) { var rgb = tinycolor(base).toRgb(); var level = ((rgb.r*299) + (rgb.g*587) + (rgb.b*114))/1000; var readable = (level >= 128) ? '#111111' : '#eeeeee'; return readable; }, whiteBlackLeastReadable: function(base) { return this.leastReadable(base, ["#000000", "#ffffff"]); }, calculate_page_backgroundColor: function(base) { var pageBackground = "#fafafa"; var theme = "light"; if (globalDashboardNode && globalDashboardNode.hasOwnProperty("theme") && globalDashboardNode.theme.hasOwnProperty("name")) { theme = globalDashboardNode.theme.name.split('-')[1]; } if (theme === "dark") { pageBackground = "#111111"; } else if (theme === "custom") { var whiteOrBlack = this.whiteBlackLeastReadable(base); if (whiteOrBlack === "#000000") { pageBackground = "#111111"; } } return pageBackground; }, calculate_page_sidebar_backgroundColor: function(base) { if (this.whiteBlackLeastReadable(base) === "#000000") { return "#333333"; } else { return "#ffffff"; } }, calculate_page_titlebar_backgroundColor: function(base) { return base; }, calculate_group_textColor: function(base) { var groupTextColour = tinycolor(base).lighten(15).toHexString(); //if (this.whiteBlackLeastReadable(base) === "#ffffff") { groupTextColour = "#000000"; } return groupTextColour; }, calculate_group_backgroundColor: function(base) { var groupBackground = "#ffffff"; var theme = "light"; if (globalDashboardNode && globalDashboardNode.hasOwnProperty("theme") && globalDashboardNode.theme.hasOwnProperty("name")) { theme = globalDashboardNode.theme.name.split('-')[1]; } if (theme === "dark") { groupBackground = "#333333"; } else if (theme === "custom") { var whiteOrBlack = this.whiteBlackLeastReadable(base); if (whiteOrBlack === "#000000") { groupBackground = "#333333"; } } return groupBackground; }, calculate_group_borderColor: function(base) { var groupBackground = this.calculate_group_backgroundColor(base); return this.leastReadable(groupBackground, ["#ffffff", "#555555"]); }, calculate_widget_textColor: function(base) { //most readable against group background var groupBackground = this.calculate_group_backgroundColor(base); return tinycolor.mostReadable(groupBackground, ["#111111", "#eeeeee"]).toHexString(); }, calculate_widget_backgroundColor: function(base) { //return tinycolor(base).darken(5).toHexString() return tinycolor(base).toHexString(); }, calculate_widget_borderColor: function(base) { var widgetBorder = "#ffffff"; var theme = "light"; if (globalDashboardNode && globalDashboardNode.hasOwnProperty("theme") && globalDashboardNode.theme.hasOwnProperty("name")) { theme = globalDashboardNode.theme.name.split('-')[1]; } if (theme === "dark") { widgetBorder = "#333333"; } else if (theme === "custom") { var whiteOrBlack = this.whiteBlackLeastReadable(base); if (whiteOrBlack === "#000000") { widgetBorder = "#333333"; } } return widgetBorder; }, calculate_base_font: function(base) { return baseFontName; } } var sizes = { sx: 48, // width of <1> grid square sy: 48, // height of <1> grid square gx: 6, // gap between groups gy: 6, // gap between groups cx: 6, // gap between components cy: 6, // gap between components px: 0, // padding of group's cards py: 0 // padding of group's cards }; ensureDashboardNode = function(createMissing) { if (globalDashboardNode !== null) { // Check if it has been deleted beneath us var n = RED.nodes.node(globalDashboardNode.id); if (n === null) { globalDashboardNode = null; } } // Find the old dashboard node if (globalDashboardNode === null) { var bases = []; RED.nodes.eachConfig(function(n) { if (n.type === 'ui_baseDE') { bases.push(n); } }); // make sure we only have one ui_base node // at the moment this will just use our existing one - deleting any new base node and theme // at some point we may want to make this an option to select one or the other. while (bases.length > 1) { var n = bases.pop(); console.log("Removing ui_base node "+n.id); RED.nodes.remove(n.id); RED.nodes.dirty(true); } if (bases.length === 1) { globalDashboardNode = bases[0]; } // If there is no dashboard node, ensure we create it after // initialising var noDashboardNode = (globalDashboardNode === null); // set up theme state var themeState = {}; var baseColor = "#0094CE" for (var i=0; i<baseStyles.length; i++) { themeState[baseStyles[i]] = { default:baseColor, value:baseColor, edited:false }; } for (var j = 0; j < configurableStyles.length; j++) { var underscore = configurableStyles[j].split("-").join("_"); var colour = colours['calculate_'+underscore](baseColor); themeState[configurableStyles[j]] = {value:colour, edited:false}; } themeState["base-font"] = {value:baseFontName}; var missingFields = (!globalDashboardNode || !globalDashboardNode.theme || !globalDashboardNode.site || !globalDashboardNode.site.sizes ); if (missingFields && createMissing) { var lightTheme = { default: baseColor, baseColor: baseColor, baseFont: baseFontName, edited: false } var darkTheme = { default: "#097479", baseColor: "#097479", baseFont: baseFontName, edited: false } var customTheme = { name: 'Untitled Theme 1', default: "#4B7930", baseColor: "#4B7930", baseFont: baseFontName } var oldThemeName; if (globalDashboardNode && typeof(globalDashboardNode.theme === 'string')) { oldThemeName = globalDashboardNode.theme; } var theme = { name: oldThemeName || "theme-light", lightTheme: lightTheme, darkTheme: darkTheme, customTheme: customTheme, themeState: themeState, angularTheme: aTheme } var site_name = c_("site.title"); var site_date_format = c_("site.date-format"); var site = { name:site_name, hideToolbar:"false", allowSwipe:"false", lockMenu:"false", allowTempTheme:"true", dateFormat:site_date_format, sizes:sizes }; if (globalDashboardNode !== null) { if (typeof globalDashboardNode.site !== "undefined") { site = { name: globalDashboardNode.site.name || globalDashboardNode.name, hideToolbar: globalDashboardNode.site.hideToolbar, lockMenu: globalDashboardNode.site.lockMenu, allowSwipe: globalDashboardNode.site.allowSwipe, allowTempTheme: globalDashboardNode.site.allowTempTheme, dateFormat: globalDashboardNode.site.dateFormat, sizes: globalDashboardNode.site.sizes } } if (globalDashboardNode.theme.hasOwnProperty("angularTheme")) { aTheme = globalDashboardNode.theme.angularTheme; } else { globalDashboardNode.theme.angularTheme = aTheme; } } if (noDashboardNode) { globalDashboardNode = { id: RED.nodes.id(), _def: RED.nodes.getType("ui_baseDE"), type: "ui_baseDE", site: site, theme: theme, users: [] } RED.nodes.add(globalDashboardNode); RED.editor.validateNode(globalDashboardNode); } else { globalDashboardNode["_def"] = RED.nodes.getType("ui_baseDE"); globalDashboardNode.site = site; globalDashboardNode.theme = theme; delete globalDashboardNode.name; } $("#nr-db-field-font").val(baseFontName); RED.nodes.dirty(true); } } } var content = $("<div>").css({"position":"relative","height":"100%"}); var mainContent = $("<div>",{class:"nr-db-sb"}).appendTo(content); var form = $('<form class="dialog-form">').appendTo(mainContent); // Dashboard Tabs markup var divTab = $('<div class="red-ui-tabs">').appendTo(form); var ulDashboardTabs = $('<ul id="dashboard-tabs-list"></ul>').appendTo(divTab); var layout_label = c_("label.layout"); var site_label = c_("label.site"); var theme_label = c_("label.theme"); var angular_label = c_("label.angular"); var liLayoutTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Layout"><span>'+layout_label+'</span></a></li>').appendTo(ulDashboardTabs); var liSiteTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Site" style="width:60px;"><span>'+site_label+'</span></a></li>').appendTo(ulDashboardTabs); var liThemeTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Theme" style="width:80px;"><span>'+theme_label+'</span></a></li>').appendTo(ulDashboardTabs); var liAngularTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Angular" style="width:80px;"><span>'+angular_label+'</span></a></li>').appendTo(ulDashboardTabs); // Link out to dashboard $.getJSON('uisettings',function(data) { if (data.hasOwnProperty("path")) { uip = data.path; } var lnk = document.location.host+RED.settings.httpNodeRoot+"/"+uip; var re = new RegExp('\/{1,}','g'); lnk = lnk.replace(re,'/'); if (!RED.hasOwnProperty("actions")) { RED.keyboard.add("*",/* d */ 68,{ctrl:true, shift:true},function() { window.open(document.location.protocol+"//"+lnk, "nr-dashboard") }); } else { RED.actions.add("dashboard:show-dashboard",function() { window.open(document.location.protocol+"//"+lnk, "nr-dashboard") }); RED.keyboard.add("*","ctrl-shift-d","dashboard:show-dashboard"); } $('<span id="dash-link-button" class="editor-button" style="position:absolute; right:0px;"><i class="fa fa-external-link"></i></span>') .click(function(evt) { window.open(document.location.protocol+"//"+lnk); evt.preventDefault(); }) .appendTo(ulDashboardTabs); }); // Dashboard Tab containers var layoutTab = $('<div id="dashboard-layout" style="height:calc(100% - 48px)">').appendTo(form); var siteTab = $('<div id="dashboard-site" style="display:none;">').appendTo(form); var themeTab = $('<div id="dashboard-theme" style="display:none;">').appendTo(form); var angularTab = $('<div id="dashboard-angular" style="display:none;">').appendTo(form); ulDashboardTabs.children().first().addClass("active"); // Tab logic var onTabClick = function() { //Toggle tabs ulDashboardTabs.children().removeClass("active"); ulDashboardTabs.children().css({"transition": "width 100ms"}); $(this).parent().addClass("active"); var selectedTab = $(this)[0].title; if (selectedTab === 'Layout') { themeTab.hide(); siteTab.hide(); angularTab.hide(); layoutTab.show(); } else if (selectedTab === 'Angular') {