<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-secondary-background-selected, 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 #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: #f8f8f8; border: solid 2px #C0C0C0; margin: auto; min-height: 42px; display: table-cell; background-image: linear-gradient(#C0C0C0 1px, transparent 0), linear-gradient(90deg, #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 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_base/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_base/gs/gridstack.min.js', function() { loadGsLib('ui_base/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].type == "ui_tab" && nodes[cnt].id == tabID) { 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_group" && 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_link' && nodes[cnt].type !== 'ui_toast' && nodes[cnt].type !== 'ui_ui_control' && nodes[cnt].type !== 'ui_audio' && nodes[cnt].type !== 'ui_base' && nodes[cnt].type !== 'ui_group' && nodes[cnt].type !== 'ui_tab')) { 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_spacer') { var spaceNode = { _def: RED.nodes.getType("ui_spacer"), type: "ui_spacer", 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_chart') { height = Math.floor(groupWidth/2)+1; } else if (redNode.type === 'ui_form') { // 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_lineargauge') { if (redNode.unit && redNode.name) { height = 5; } else { height = 4; } } else if (redNode.type === 'ui_list') { height = 5; } else if (redNode.type === 'ui_vega') { 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_base', { 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_base') { 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_base"), type: "ui_base", site: site, theme: theme, users: [] } RED.nodes.add(globalDashboardNode); RED.editor.validateNode(globalDashboardNode); } else { globalDashboardNode["_def"] = RED.nodes.getType("ui_base"); 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') { themeTab.hide(); siteTab.hide(); angularTab.show(); layoutTab.hide(); } else if (selectedTab === 'Theme') { layoutTab.hide(); siteTab.hide(); angularTab.hide(); themeTab.show(); if ($("#nr-db-field-theme option:selected").val() === 'theme-custom') { themeSettingsContainer.show(); } else { themeSettingsContainer.hide(); } } else { layoutTab.hide(); themeTab.hide(); angularTab.hide(); siteTab.show(); } } ulDashboardTabs.find("li.red-ui-tab a").on("click",onTabClick) // Site Tab var divTitle = $('<div>',{class:"form-row compact"}).appendTo(siteTab); $('<div>').html('<b>'+c_("label.title")+'</b>').appendTo(divTitle); $('<input type="text" id="nr-db-field-title">').val(site_name).css("width","100%") .on("change", function() { if (!globalDashboardNode || globalDashboardNode.site.name !== $(this).val()) { //ensureDashboardNode(true); globalDashboardNode.site.name = $(this).val(); } RED.nodes.dirty(true); }) .appendTo(divTitle); var divHideToolbar = $('<div>',{class:"form-row compact"}).appendTo(siteTab); $('<div>').html('<b>'+c_("label.options")+'</b>').appendTo(divHideToolbar); $('<select id="nr-db-field-hideToolbar">') .css("width","100%") .append($('<option>', { value:"false", text:c_("title-bar.show"), selected:true })) .append($('<option>', { value:"true", text:c_("title-bar.hide") })) .val("false") .on("change", function() { if (!globalDashboardNode || globalDashboardNode.site.hideToolbar !== $(this).val()) { //ensureDashboardNode(true); globalDashboardNode.site.hideToolbar = $(this).val(); } RED.nodes.dirty(true); }) .appendTo(divHideToolbar); var divLockMenu = $('<div>',{class:"form-row compact"}).appendTo(siteTab); $('<select id="nr-db-field-lockMenu">') .css("width","100%") .append($('<option>', { value:"false", text:c_("lock.clicked"), selected:true })) .append($('<option>', { value:"true", text:c_("lock.locked") })) .append($('<option>', { value:"icon", text:c_("lock.locked-icon") })) .val("false") .on("change", function() { if (!globalDashboardNode || globalDashboardNode.site.lockMenu !== $(this).val()) { //ensureDashboardNode(true); globalDashboardNode.site.lockMenu = $(this).val(); } RED.nodes.dirty(true); }) .appendTo(divLockMenu); var divAllowSwipe = $('<div>',{class:"form-row compact"}).appendTo(siteTab); $('<select id="nr-db-field-allowSwipe">') .css("width","100%") .append($('<option>', { value:"false", text:c_("swipe.no-swipe"), selected:true })) .append($('<option>', { value:"true", text:c_("swipe.allow-swipe") })) .append($('<option>', { value:"mouse", text:c_("swipe.allow-swipe-mouse") })) .append($('<option>', { value:"menu", text:c_("swipe.show-menu") })) .val("false") .on("change", function() { if (!globalDashboardNode || globalDashboardNode.site.allowSwipe !== $(this).val()) { //ensureDashboardNode(true); globalDashboardNode.site.allowSwipe = $(this).val(); RED.nodes.dirty(true); } }) .appendTo(divAllowSwipe); var divAllowTempTheme = $('<div>',{class:"form-row compact"}).appendTo(siteTab); $('<select id="nr-db-field-allowTempTheme">') .css("width","100%") .append($('<option>', { value:"true", text:c_("temp.allow-theme"), selected:true })) .append($('<option>', { value:"false", text:c_("temp.no-theme") })) .append($('<option>', { value:"none", text:c_("temp.none") })) .val("true") .on("change", function() { if (!globalDashboardNode || globalDashboardNode.site.allowTempTheme !== $(this).val()) { //ensureDashboardNode(true); globalDashboardNode.site.allowTempTheme = $(this).val(); } if ($('#nr-db-field-allowTempTheme').val() === "none") { ulDashboardTabs.children().eq(2).addClass("hidden"); ulDashboardTabs.children().eq(3).removeClass("hidden"); } else { ulDashboardTabs.children().eq(2).removeClass("hidden"); ulDashboardTabs.children().eq(3).addClass("hidden"); } RED.nodes.dirty(true); }) .appendTo(divAllowTempTheme); var site_name = c_("site.title"); var site_date_format = c_("site.date-format"); var divDateFormat = $('<div>',{class:"form-row"}).appendTo(siteTab); $('<div>').html('<b>'+c_("label.date-format")+'</b>') .css("width","80%") .css("display","inline-block") .appendTo(divDateFormat); $('<div>').html("<a href='https://momentjs.com/docs/#/displaying/format/' target='_new'><i class='fa fa-info-circle' style='color:grey;'></i></a>") .css("display","inline-block") .css("margin-right","6px") .css("float","right") .appendTo(divDateFormat); $('<input type="text" id="nr-db-field-dateFormat">').val(site_date_format).css("width","100%") .on("change", function() { if (!globalDashboardNode || globalDashboardNode.site.dateFormat !== $(this).val()) { //ensureDashboardNode(true); globalDashboardNode.site.dateFormat = $(this).val(); } RED.nodes.dirty(true); }) .appendTo(divDateFormat); var divSetSizes = $('<div>',{class:"form-row"}).appendTo(siteTab); $('<span style="width:45%; display:inline-block">').html('<b>'+c_("label.sizes")+'</b>').appendTo(divSetSizes); $('<span style="width:25%; display:inline-block; font-size:smaller">').text(c_("label.horizontal")).appendTo(divSetSizes); $('<span style="width:20%; display:inline-block; font-size:smaller">').text(c_("label.vertical")).appendTo(divSetSizes); $('<i id="sizes-reset" class="fa fa-undo nr-db-resetIcon"></i>') .css({opacity:1.0}) .click(function(e) { $("#nr-db-field-sx").val(sizes.sx); globalDashboardNode.site.sizes.sx = sizes.sx; $("#nr-db-field-sy").val(sizes.sy); globalDashboardNode.site.sizes.sy = sizes.sy; $("#nr-db-field-px").val(sizes.px); globalDashboardNode.site.sizes.px = sizes.px; $("#nr-db-field-py").val(sizes.py); globalDashboardNode.site.sizes.py = sizes.py; $("#nr-db-field-gx").val(sizes.gx); globalDashboardNode.site.sizes.gx = sizes.gx; $("#nr-db-field-gy").val(sizes.gy); globalDashboardNode.site.sizes.gy = sizes.gy; $("#nr-db-field-cx").val(sizes.cx); globalDashboardNode.site.sizes.cx = sizes.cx; $("#nr-db-field-cy").val(sizes.cy); globalDashboardNode.site.sizes.cy = sizes.cy; RED.nodes.dirty(true); }) .appendTo(divSetSizes); $('<br/><span style="width:45%; display:inline-block">').text(c_("label.widget-size")).appendTo(divSetSizes); $('<input type="number" name="sx" min="24" id="nr-db-field-sx">').val(48).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.sx=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes); $('<input type="number" name="sy" min="24" id="nr-db-field-sy">').val(48).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.sy=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); $('<br/><span style="width:45%; display:inline-block">').text(c_("label.widget-spacing")).appendTo(divSetSizes); $('<input type="number" name="cx" min="0" id="nr-db-field-cx">').val(6).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.cx=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes); $('<input type="number" name="cy" min="0" id="nr-db-field-cy">').val(6).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.cy=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); $('<br/><span style="width:45%; display:inline-block">').text(c_("label.group-padding")).appendTo(divSetSizes); $('<input type="number" name="px" min="0" id="nr-db-field-px">').val(0).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.px=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes); $('<input type="number" name="py" min="0" id="nr-db-field-py">').val(0).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.py=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); $('<br/><span style="width:45%; display:inline-block">').text(c_("label.group-spacing")).appendTo(divSetSizes); $('<input type="number" name="gx" min="0" id="nr-db-field-gx">').val(6).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.gx=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes); $('<input type="number" name="gy" min="0" id="nr-db-field-gy">').val(6).css("width","20%") .on("change", function() { //ensureDashboardNode(true); globalDashboardNode.site.sizes.gy=Number($(this).val()); RED.nodes.dirty(true); } ) .appendTo(divSetSizes); // Angular Theme Tab var changed = function() { ensureDashboardNode(true); globalDashboardNode.theme.angularTheme = aTheme; RED.nodes.dirty(true); } var angColorList = ["red", "pink", "purple", "deep-purple", "indigo", "blue", "light-blue", "cyan", "teal", "green", "light-green", "lime", "yellow", "amber", "orange", "deep-orange", "brown", "grey", "blue-grey"]; var angColors = ""; angColorList.forEach(function(c) { angColors += '<option value="' + c + '">' + c + '</option>'; }); var divPrimStyle = $('<div>',{class:"form-row"}).appendTo(angularTab); $('<span style="width:45%; display:inline-block">') .html('<b>'+c_("style.primary")+'</b>') .appendTo(divPrimStyle); $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>') .css({opacity:1.0}) .click(function(e) { $("#nr-db-field-angPrimary").val("indigo"); globalDashboardNode.theme.angularTheme.primary = "indigo"; RED.nodes.dirty(true); }) .appendTo(divPrimStyle); $('<select id="nr-db-field-angPrimary">'+angColors+'</select>') .css("width","100%") .val(aTheme.primary) .on("change", function() { aTheme.primary = $(this).val(); changed(); }) .appendTo(divPrimStyle); var divAccStyle = $('<div>',{class:"form-row"}).appendTo(angularTab); $('<span style="width:45%; display:inline-block">') .html('<b>'+c_("style.accents")+'</b>') .appendTo(divAccStyle); $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>') .css({opacity:1.0}) .click(function(e) { $("#nr-db-field-angAccents").val("blue"); globalDashboardNode.theme.angularTheme.accents = "blue"; RED.nodes.dirty(true); }) .appendTo(divAccStyle); $('<select id="nr-db-field-angAccents">'+angColors+'</select>') .css("width","100%") .val(aTheme.accents) .on("change", function() { aTheme.accents = $(this).val(); changed(); }) .appendTo(divAccStyle); var divWarnStyle = $('<div>',{class:"form-row"}).appendTo(angularTab); $('<span style="width:45%; display:inline-block">') .html('<b>'+c_("style.warnings")+'</b>') .appendTo(divWarnStyle); $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>') .css({opacity:1.0}) .click(function(e) { $("#nr-db-field-angWarn").val("red"); globalDashboardNode.theme.angularTheme.warn = "red"; RED.nodes.dirty(true); }) .appendTo(divWarnStyle); $('<select id="nr-db-field-angWarn">'+angColors+'</select>') .css("width","100%") .val(aTheme.warn) .on("change", function() { aTheme.warn = $(this).val(); changed(); }) .appendTo(divWarnStyle); var divBackStyle = $('<div>',{class:"form-row"}).appendTo(angularTab); $('<span style="width:45%; display:inline-block">') .html('<b>'+c_("style.background")+'</b>') .appendTo(divBackStyle); $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>') .css({opacity:1.0}) .click(function(e) { $("#nr-db-field-angBackground").val("grey"); globalDashboardNode.theme.angularTheme.background = "grey"; RED.nodes.dirty(true); }) .appendTo(divBackStyle); $('<select id="nr-db-field-angBackground">'+angColors+'</select>') .css("width","100%") .val(aTheme.background) .on("change", function() { aTheme.background = $(this).val(); changed(); }) .appendTo(divBackStyle); var divPalStyle = $('<div>',{class:"form-row"}).appendTo(angularTab); $('<span style="width:45%; display:inline-block">') .html('<b>'+c_("style.palette")+'</b>') .appendTo(divPalStyle); var lightdark = '<option value="light">' +c_("style.light")+ '</option>'; lightdark += '<option value="dark">' +c_("style.dark")+ '</option>'; $('<select id="nr-db-field-angLook">'+lightdark+'</select>') .css("width","100%") .val(aTheme.palette) .on("change", function() { aTheme.palette = $(this).val(); changed(); }) .appendTo(divPalStyle); // Theme Tab // For all customisable styles, generate and apply the css var generateColours = function(base) { var theme = globalDashboardNode.theme.name.split('-')[1]; if (!globalDashboardNode.theme.themeState.hasOwnProperty["base-font"]) { if (globalDashboardNode.theme[theme+"Theme"].baseFont === "Helvetica Neue") { globalDashboardNode.theme[theme+"Theme"].baseFont = baseFontName; } globalDashboardNode.theme.themeState["base-font"] = {value:globalDashboardNode.theme[theme+"Theme"].baseFont}; $("#nr-db-field-font").val(globalDashboardNode.theme[theme+"Theme"].baseFont); } for (var i=0; i<configurableStyles.length; i++) { var styleID = configurableStyles[i]; var underscore = styleID.split("-").join("_"); if (!globalDashboardNode.theme.themeState.hasOwnProperty(styleID)) { globalDashboardNode.theme.themeState[styleID] = {value:"#fff",edited:false}; } if (!globalDashboardNode.theme.themeState[styleID].edited || globalDashboardNode.theme[theme+'Theme'].reset) { var colour = colours['calculate_'+underscore](base); globalDashboardNode.theme.themeState[styleID].value = colour; } setColourPickerColour(styleID, globalDashboardNode.theme.themeState[styleID].value, globalDashboardNode.theme.themeState[styleID].edited); } globalDashboardNode.theme[theme+'Theme'].reset = false; } var divThemeStyle = $('<div>',{class:"form-row"}).appendTo(themeTab); $('<label class="nr-db-theme-label">').text(c_("theme.style")).appendTo(divThemeStyle); var themeSelection = $('<select id="nr-db-field-theme">'+ '<option value="theme-light">'+c_("style.light")+'</option>'+ '<option value="theme-dark">'+c_("style.dark")+'</option>'+ '<option value="theme-custom">'+c_("style.custom")+'</option>'+ '</select>') .css("width","100%") .on("change", function() { if (!globalDashboardNode || globalDashboardNode.theme.name !== $(this).val()) { //ensureDashboardNode(true); var theme = globalDashboardNode.theme.name.split('-')[1]; var baseColour = globalDashboardNode.theme[theme+'Theme'].baseColor; var baseFont = globalDashboardNode.theme[theme+'Theme'].baseFont; globalDashboardNode.theme.name = $(this).val(); theme = globalDashboardNode.theme.name.split('-')[1]; if (theme !== "custom") { baseColour = globalDashboardNode.theme[theme+'Theme'].default; } else { baseColour = globalDashboardNode.theme[theme+'Theme'].baseColor; } setColourPickerColour("base-color", baseColour); globalDashboardNode.theme.themeState['base-color'].value = baseColour; globalDashboardNode.theme.themeState['base-color'].default = baseColour; globalDashboardNode.theme.themeState['base-font'] = {value:baseFont}; $("#nr-db-field-font").val(baseFont); globalDashboardNode.theme[theme+'Theme'].reset = true; //generate colours for all colour settings from base colour generateColours(baseColour); RED.nodes.dirty(true); } $('#base-color-reset').remove(); if ($(this).val() === 'theme-custom') { $("#custom-theme-library-container").show(); //TODO undo this at some point $("#custom-theme-settings").show(); //addResetButton('base-color', baseSettingsUl.children()); } else { $("#custom-theme-library-container").hide(); $("#custom-theme-settings").hide(); addLightAndDarkResetButton('base-color', baseSettingsUl.children().first()); } }) .appendTo(divThemeStyle); var customThemeLibraryContainer = $('<div id="custom-theme-library-container">').appendTo(themeTab); $('<label class="nr-db-theme-label">').text(c_("theme.custom-profile")).appendTo(customThemeLibraryContainer); $('<input type="text" id="ui-sidebar-name" style="vertical-align:top;" placeholder="profile name (not blank)">') .val(c_("theme.custom-profile-name")) .on("change", function() { if (!globalDashboardNode || globalDashboardNode.theme.customTheme.name !== $(this).val()) { //ensureDashboardNode(true); globalDashboardNode.theme.customTheme.name = $(this).val(); if (editor) { editor.setValue(JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site}),1); RED.nodes.dirty(true); } } }) .keyup(function() { if ($(this).val().length === 0) { $("#custom-theme-library-container div").css("pointer-events","none"); } else { $("#custom-theme-library-container div").css("pointer-events","inherit"); } }) .appendTo(customThemeLibraryContainer); $('<input type="hidden" id="nr-db-field-format">').appendTo(customThemeLibraryContainer); $('<div style="display:none;" class="node-text-editor" id="nr-db-field-format-editor"></div>').appendTo(customThemeLibraryContainer); var baseThemeSettingsContainer = $('<div id="base-theme-settings">').appendTo(themeTab); var baseSettings = $('<div>',{class:"form-row"}).appendTo(baseThemeSettingsContainer); $('<label class="nr-db-theme-label">').text(c_("theme.base-settings")).appendTo(baseSettings); var baseSettingsUl = $('<ul id="base-settings-ul" class="red-ui-dashboard-theme-styles"></ul>').appendTo(baseSettings); var baseColourItem = $('<li class="red-ui-dashboard-theme-item"><span>'+c_("base.colour")+'</span></li>').appendTo(baseSettingsUl); var spanColorContainer = $('<span class="nr-db-color-pick-container"></span>').appendTo(baseColourItem); $('<input id="base-color" class="nr-db-field-themeColor" type="color" value="#ffffff"/>') .on("change", function() { //ensureDashboardNode(true); var value = $(this).val(); var lightThemeMatch = globalDashboardNode.theme.lightTheme.baseColor === value; var darkThemeMatch = globalDashboardNode.theme.darkTheme.baseColor === value; var customThemeMatch = globalDashboardNode.theme.customTheme.baseColor === value; if (!globalDashboardNode || !lightThemeMatch || !darkThemeMatch || !customThemeMatch) { var theme = globalDashboardNode.theme.name.split('-')[1]; globalDashboardNode.theme[theme+'Theme'].baseColor = value; if (globalDashboardNode.theme.name === 'theme-light' || globalDashboardNode.theme.name === 'theme-dark') { //for light and dark themes, reset the colours globalDashboardNode.theme[theme+'Theme'].reset = true; } generateColours(value); editor.setValue(JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site}),1); colourPickerChangeHandler($(this).attr('id'), value); } }) .appendTo(spanColorContainer); var baseFontItem = $('<li class="red-ui-dashboard-theme-item"><span>'+c_("base.font")+'</span></li>').appendTo(baseSettingsUl); var fontSelector = $('<select id="nr-db-field-font">'+ '<option value="'+baseFontName+'" style="font-family:'+baseFontName+'">'+c_("font.system")+'</option>'+ '<option value="Arial,Arial,Helvetica,sans-serif" style="font-family:Arial,Arial,Helvetica,sans-serif">Arial</option>'+ '<option value="Arial Black,Arial Black,Gadget,sans-serif" style="font-family:Arial Black,Arial Black,Gadget,sans-serif">Arial Black</option>'+ '<option value="Arial Narrow,Nimbus Sans L,sans-serif" style="font-family:Arial Narrow,Nimbus Sans L,sans-serif">Arial Narrow</option>'+ '<option value="Century Gothic,CenturyGothic,AppleGothic,sans-serif" style="font-family:Century Gothic,CenturyGothic,AppleGothic,sans-serif">Century Gothic</option>'+ '<option value="Copperplate,Copperplate Gothic Light,fantasy" style="font-family:Copperplate,Copperplate Gothic Light,fantasy;">Copperplate</option>'+ '<option value="Courier,monospace" style="font-family:Courier,monospace;">Courier</option>'+ '<option value="Georgia,Georgia,serif" style="font-family:Georgia,Georgia,serif">Georgia</option>'+ '<option value="Gill Sans,Geneva,sans-serif" style="font-family:Gill Sans,Geneva,sans-serif;">Gill Sans</option>'+ //'<option value="Helvetica Neue,Helvetica,sans-serif" style="font-family:Helvetica Neue,Helvetica,sans-serif">Helvetica Neue</option>'+ '<option value="Impact,Impact,Charcoal,sans-serif" style="font-family:Impact,Impact,Charcoal,sans-serif">Impact</option>'+ '<option value="Lucida Sans Typewriter,Lucida Console,Monaco,monospace" style="font-family:Lucida Console,Monaco,monospace">Lucida Console</option>'+ '<option value="Lucida Sans Unicode,Lucida Grande,sans-serif" style="font-family:Lucida Sans Unicode,Lucida Grande,sans-serif">Lucida Sans</option>'+ '<option value="Palatino Linotype,Palatino,Book Antiqua,serif" style="font-family:Palatino Linotype,Palatino,Book Antiqua,serif">Palatino Linotype</option>'+ '<option value="Tahoma,Geneva,sans-serif" style="font-family:Tahoma,Geneva,sans-serif">Tahoma</optionstyle="font-family:>'+ '<option value="Times New Roman,Times,serif" style="font-family:Times New Roman,Times,serif">Times New Roman</option>'+ '<option value="Trebuchet MS,Helvetica,sans-serif" style="font-family:Trebuchet MS,Helvetica,sans-serif">Trebuchet MS</option>'+ '<option value="Verdana,Verdana,Geneva,sans-serif" style="font-family:Verdana,Verdana,Geneva,sans-serif">Verdana</option>'+ '</select>') .on("change", function() { //ensureDashboardNode(true); var theme = globalDashboardNode.theme.name.split('-')[1]; globalDashboardNode.theme[theme+'Theme'].baseFont = $(this).val(); globalDashboardNode.theme.themeState['base-font'] = {value:$(this).val()}; RED.nodes.dirty(true); }) .appendTo(baseFontItem); var themeSettingsContainer = $('<div id="custom-theme-settings">').appendTo(themeTab); // Markup // Page styles var divPageStyle = $('<div>',{class:"form-row"}).appendTo(themeSettingsContainer); $('<label class="nr-db-theme-label">').text(c_("theme.page-settings")).appendTo(divPageStyle); var pageStyles = $('<ul class="red-ui-dashboard-theme-styles"></ul>').appendTo(themeSettingsContainer); addCustomisableStyle('page-titlebar-backgroundColor', c_("theme.page.title"), pageStyles); addCustomisableStyle('page-backgroundColor', c_("theme.page.page"), pageStyles); addCustomisableStyle('page-sidebar-backgroundColor', c_("theme.page.side"), pageStyles); // Group styles var divGroupStyle = $('<div>',{class:"form-row"}).appendTo(themeSettingsContainer); $('<label class="nr-db-theme-label">').text(c_("theme.group-settings")).appendTo(divGroupStyle); var groupStyles = $('<ul class="red-ui-dashboard-theme-styles"></ul>').appendTo(themeSettingsContainer); addCustomisableStyle('group-textColor', c_("theme.group.text"), groupStyles); addCustomisableStyle('group-borderColor', c_("theme.group.border"), groupStyles); addCustomisableStyle('group-backgroundColor', c_("theme.group.background"), groupStyles); // Widget styles var divWidgetStyle = $('<div>',{class:"form-row"}).appendTo(themeSettingsContainer); $('<label class="nr-db-theme-label">').text(c_("theme.widget-settings")).appendTo(divWidgetStyle); var widgetStyles = $('<ul class="red-ui-dashboard-theme-styles"></ul>').appendTo(themeSettingsContainer); addCustomisableStyle('widget-textColor', c_("theme.widget.text"), widgetStyles); addCustomisableStyle('widget-backgroundColor', c_("theme.widget.colour"), widgetStyles); addCustomisableStyle('widget-borderColor', c_("theme.widget.background"), widgetStyles); function addCustomisableStyle(id, name, parentUl) { var styleLi = $('<li class="red-ui-dashboard-theme-item"><span>'+name+'</span></li>').appendTo(parentUl); var spanColorContainer = $('<span class="nr-db-color-pick-container"></span>').appendTo(styleLi); $('<input id="'+id+'" class="nr-db-field-themeColor" type="color" value="#ffffff"/>') .on("change", function() { colourPickerChangeHandler($(this).attr('id'), $(this).val()); }) .appendTo(spanColorContainer); addResetButton(id, styleLi); } function colourPickerChangeHandler(id, value) { $("#"+id).css("background-color", value); $("#"+id+"-reset").css({opacity:1}); globalDashboardNode.theme.themeState[id].edited = true; globalDashboardNode.theme.themeState[id].value = value; if (editor) { editor.setValue(JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site}),1); } RED.nodes.dirty(true); } function addResetButton(id, parent) { var resetToDefault = $('<i id="'+id+'-reset" class="fa fa-undo nr-db-resetIcon"></i>') .css({opacity:0.2}) .click(function(e) { resetClick(e); }) .appendTo(parent); } function addLightAndDarkResetButton(id, parent) { if ($("#" + id + "-reset").length === 0) { var resetToDefault = $('<i id="'+id+'-reset" class="fa fa-undo nr-db-resetIcon"></i>') .css({opacity:1}) .click(function(e) { lightAndDarkResetClick(e); }) .appendTo(parent); globalDashboardNode.theme[globalDashboardNode.theme.name.split('-')[1] + 'Theme'].edited = true; } } function lightAndDarkResetClick(e) { var elementID = e.target.id.split('-reset')[0]; var key = globalDashboardNode.theme.name.split('-')[1] + 'Theme'; //sanity check - light and dark only allow base-color-reset if (elementID === 'base-color') { // && globalDashboardNode.theme[key].edited) { var defaultColor = globalDashboardNode.theme[key].default; globalDashboardNode.theme[key].reset = true; generateColours(defaultColor); setColourPickerColour(elementID, defaultColor); $("#"+elementID+"-reset").css({opacity:0.2}); globalDashboardNode.theme.themeState[elementID].value = defaultColor; globalDashboardNode.theme[key].baseColor = defaultColor; globalDashboardNode.theme[key].edited = false; RED.nodes.dirty(true); } } function resetClick(e) { //take off -reset var elementID = e.target.id.split('-reset')[0]; if (globalDashboardNode.theme.themeState[elementID].edited) { var defaultColor = globalDashboardNode.theme.themeState['base-color'].value; var colour; //set colour if (elementID === 'base-color') { colour = defaultColor; generateColours(colour); } else { var underscore = elementID.split('-').join('_'); colour = colours['calculate_'+underscore](defaultColor); } setColourPickerColour(elementID, colour); $("#"+elementID+"-reset").css({opacity:0.2}); globalDashboardNode.theme.themeState[elementID].edited = false; globalDashboardNode.theme.themeState[elementID].value = colour; RED.nodes.dirty(true); } } function setColourPickerColour(id, val, ed) { $("#"+id).val(val); $("#"+id).css("background-color", val); //call mostReadableGreyWhite to set text colour var textColor = colours.whiteGreyMostReadable(val); $("#"+id).css("color", textColor); if (ed === true) { $("#"+id+"-reset").css({opacity:1}); } else { $("#"+id+"-reset").css({opacity:0.2}); } } //Layout Tab var divTabs = $('<div>',{class:"form-row",style:"position:relative"}).appendTo(layoutTab); $('<label>').html('<b>'+c_("layout.tab-and-link")+'</b>').appendTo(divTabs); var buttonGroup = $('<div>',{class:"nr-db-sb-list-button-group"}).appendTo(divTabs); //Toggle expand buttons $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-angle-double-up"></i></a>') .click(function(evt) { tabContainer.find(".nr-db-sb-group-list-container").slideUp().addClass('nr-db-sb-collapsed'); tabContainer.find(".nr-db-sb-tab-list-header>.nr-db-sb-list-chevron").css({"transform":"rotate(-90deg)"}); evt.preventDefault(); }) .appendTo(buttonGroup); $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-angle-double-down"></i></a>') .click(function(evt) { tabContainer.find(".nr-db-sb-group-list-container").slideDown().removeClass('nr-db-sb-collapsed'); tabContainer.find(".nr-db-sb-tab-list-header>.nr-db-sb-list-chevron").css({"transform":""}); evt.preventDefault(); }) .appendTo(buttonGroup); //Add item button $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> '+c_("layout.tab")+'</a>') .click(function(evt) { tabContainer.editableList('addItem',{type: 'ui_tab'}); evt.preventDefault(); }) .appendTo(buttonGroup); $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> '+c_("layout.link")+'</a>') .click(function(evt) { tabContainer.editableList('addItem',{type: 'ui_link'}); evt.preventDefault(); }) .appendTo(buttonGroup); var tabLists = {}; var groupLists = {}; // toggle slide tab group content var titleToggle = function (id,content,chevron) { return function(evt) { if (content.is(":visible")) { content.slideUp(); chevron.css({"transform":"rotate(-90deg)"}); content.addClass('nr-db-sb-collapsed'); listStates[id] = false; } else { content.slideDown(); chevron.css({"transform":""}); content.removeClass('nr-db-sb-collapsed'); listStates[id] = true; } }; } var addTabOrLinkItem = function(container,i,item) { ensureDashboardNode(true); // create node if needed if (!item.node) { var defaultItem = { 'ui_tab': { _def: RED.nodes.getType('ui_tab'), type: 'ui_tab', users: [], icon: 'dashboard', name: 'Tab' }, 'ui_link': { _def: RED.nodes.getType('ui_link'), type: 'ui_link', users: [], icon: 'open_in_browser', name: 'Link', target: 'newtab' } } item.node = defaultItem[item.type] item.node.id = RED.nodes.id() item.node.order = i+1 item.node.name += ' '+item.node.order listElements[item.node.id] = container; if (item.type === 'ui_tab') { item.groups = []; } RED.nodes.add(item.node); RED.editor.validateNode(item.node); RED.history.push({ t:'add', nodes:[item.node.id], dirty:RED.nodes.dirty() }); RED.nodes.dirty(true); } else if (item.type === undefined) { item.type = item.node.type } listElements[item.node.id] = container; if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) { RED.nodes.updateConfigNodeUsers(item.node); } // title var titleRow = $('<div>',{class:"nr-db-sb-list-header nr-db-sb-tab-list-header"}).appendTo(container); switch (item.type) { case 'ui_tab': { container.addClass("nr-db-sb-tab-list-item"); $('<i class="nr-db-sb-list-handle nr-db-sb-tab-list-handle fa fa-bars"></i>').appendTo(titleRow); var chevron = $('<i class="fa fa-angle-down nr-db-sb-list-chevron">',{style:"width:10px;"}).appendTo(titleRow); var tabicon = "fa-object-group"; //var tabicon = item.node.disabled ? "fa-window-close-o" : item.node.hidden ? "fa-eye-slash" : "fa-object-group"; $('<i>',{class:"nr-db-sb-icon nr-db-sb-tab-icon fa "+tabicon}).appendTo(titleRow); var tabhide = item.node.hidden ? " nr-db-sb-title-hidden" : ""; var tabable = item.node.disabled ? " nr-db-sb-title-disabled" : ""; $('<span>',{class:"nr-db-sb-title"+tabhide+tabable}).text(item.node.name||"").appendTo(titleRow); break; } case 'ui_link': { $('<i class="nr-db-sb-list-handle fa fa-bars"></i>').appendTo(titleRow); var title = $('<div class="nr-db-sb-link">').appendTo(titleRow); var nameContainer = $('<div class="nr-db-sb-link-name-container">').appendTo(title); $('<i class="fa fa-external-link"></i>').appendTo(nameContainer); $('<span class="nr-db-sb-link-name">').text(item.node.name||"untitled").appendTo(nameContainer); $('<div class="nr-db-sb-link-url">').text(item.node.link||"http://").appendTo(title); break; } } // buttons var buttonGroup = $('<div>',{class:"nr-db-sb-list-header-button-group",id: item.node.id}).appendTo(titleRow); if (item.type === 'ui_tab') { var addGroupButton = $('<a href="#" class="nr-db-sb-tab-add-group-button editor-button editor-button-small nr-db-sb-list-header-button" ><i class="fa fa-plus"></i> '+c_("layout.group")+'</a>').appendTo(buttonGroup); } var editButton = $('<a href="#" class="nr-db-sb-tab-edit-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.edit")+'</a>').appendTo(buttonGroup); editButton.on('click',function(evt) { RED.editor.editConfig("", item.type, item.node.id); evt.stopPropagation(); evt.preventDefault(); }); // Dashboard layout tool if (item.type === 'ui_tab') { var layoutButton = $('<a href="#" class="nr-db-sb-tab-edit-layout-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.layout")+'</a>').appendTo(buttonGroup); layoutButton.on('click',function(evt) { var editTabName = item.node.name ? item.node.name : item.node.id; var trayOptions = { title: c_("layout.layout-editor") + " : " + editTabName, width: Infinity, buttons: [ { id: "node-dialog-cancel", text: RED._("common.label.cancel"), click: function() { // clean editor RED.tray.close(); } }, { id: "node-dialog-ok", text: RED._("common.label.done"), class: "primary", click: function() { // Save data after editing saveGridDatas(); RED.tray.close(); } } ], resize: function(dimensions) {}, open: function(tray) { // Get widget of specified tab from node information tabDatas = getTabDataFromNodes(item.node.id); // The width that can be handled by Layout is up to MAX_GROUP_WIDTH // Groups exceeding the maximum width are not supported. var tmpGroups = tabDatas.groups; tmpGroups.sort(compareOrder); var groups = []; for (var cnt = 0; cnt < tmpGroups.length; cnt++) { if (tmpGroups[cnt].width <= MAX_GROUP_WIDTH) { groups.push(tmpGroups[cnt]); } } tabDatas.groups = groups; var editor = $('<div></div>',{addClass: 'nr-dashboard-layout-container-fluid'}); var row = $('<div></div>',{addClass: 'nr-dashboard-layout-row'}); var span_num = Math.floor(12 / groups.length); // bootstrap grid 12 splits span_num = span_num < 2 ? 2 : span_num; // max 6 groups per row for (var cnt = 0; cnt < groups.length; cnt++) { if (cnt !=0 && (cnt % 6) == 0) { editor.append(row); editor.append('<div><br></div>'); row = $('<div></div>',{addClass: 'nr-dashboard-layout-row'}); } var span = $('<div></div>',{addClass: 'nr-dashboard-layout-span' + span_num}); var groupName = groups[cnt].name ? groups[cnt].name : groups[cnt].id; var title = $('<div></div>', { style: "margin-top:2px; margin-bottom:2px;" }); var title_group = $('<div></div>', { title: groupName, style: "margin-left:4px; margin-right:8px; overflow:hidden;" }).appendTo(title); $("<b/>").text(groupName).appendTo(title_group); var title_width = $('<div></div>', { style: "text-align:right; margin-right:8px;" }).appendTo(title); $("<span/>", { style: "margin_right: 8px;" }).text(c_("layout.width")+': ').appendTo(title_width); var changeWidth = $('<input>', { id: 'change-width' + cnt, value: groups[cnt].width, style: 'width:30px;', readonly: true, 'node-id': groups[cnt].id, }); title_width.append(changeWidth); title.css('white-space','nowrap'); title.css('overflow','hidden'); var gridstack = $('<div></div>', { id: 'grid'+cnt, addClass: 'grid-stack' }); span.append(title); span.append(gridstack); row.append(span); } if (groups.length != 0) { editor.append(row); } // Show layout editor in tray var trayBody = tray.find('.red-ui-tray-body, .editor-tray-body'); trayBody.css('overflow','auto'); trayBody.append(editor); ///////////////////////////////////////// // Editor screen generation ///////////////////////////////////////// oldSpacer = []; widthChange = []; widgetResize = []; widgetDrag = []; for (var cnt=0; cnt < groups.length; cnt++) { // Gridstack.js option var options = { acceptWidgets: true, alwaysShowResizeHandle: true, cellHeight: 42, disableOneColumnMode : true, float: true, verticalMargin: 1 }; var gridID='#grid' + cnt; // gridstack generation $(gridID).gridstack(options); // Clear the contents of Grid var grid = $(gridID+'.grid-stack').data('gridstack'); grid.removeAll(); $(gridID).on("dropped", handleMove(grid)); // Set the width of the display area of gridstack var groupWidth = Number(groups[cnt].width); $(gridID+'.grid-stack').css("width", groupWidth * 40); $(gridID+'.grid-stack').css("background-size", 100/groupWidth+"% 43px"); $(gridID+'.grid-stack').attr("node-id", groups[cnt].id); $(gridID+'.grid-stack').attr("grid-column", groups[cnt].width); grid.setColumn(groupWidth, true); // Determination of placement position of widget of Grid var widgets = groups[cnt].widgets; widgets.sort(compareOrder); var tbl = {}; for (var cnt2 = 0; cnt2 < widgets.length; cnt2++) { // Set default value when there is auto width if (widgets[cnt2].auto == true) { widgets[cnt2].width = groupWidth; // Adjust to the group width } else if (widgets[cnt2].width > groupWidth) { widgets[cnt2].width = groupWidth; } // Auto support if (widgets[cnt2].auto === true || widgets[cnt2].type === 'ui_form') { widgets[cnt2].height = getDefaultHeight(widgets[cnt2].id, groupWidth); } // Calculate coordinates to be placed var point = search_point(Number(widgets[cnt2].width), Number(widgets[cnt2].height), groupWidth, 256, tbl); if (point) { widgets[cnt2].x = point.x; widgets[cnt2].y = point.y; } } var items = GridStackUI.Utils.sort(widgets); items.forEach(function (node) { var minHeight = null; var maxHeight = null; // ui_form is fixed to height 2 if (node.type === 'ui_form') { minHeight = node.height; maxHeight = node.height; } if (node.type !== 'ui_spacer') { var dispNode = RED.nodes.node(node.id); var dispType = dispNode._def.paletteLabel; var dispLabel = dispNode._def.label; try { dispLabel = (typeof dispLabel === "function" ? dispLabel.call(dispNode) : dispLabel)||""; } catch(err) { console.log("Definition error: " + node.type + ".label",err); dispLabel = dispType; } var item = $('<div></div>', { 'data-noderedtype': node.type, 'data-noderedid': node.id, 'data-nodereddisptype': dispType, 'data-nodereddisplabel': dispLabel, 'data-noderedsizeauto': node.auto }); var itemContent = $('<div></div>', { addClass: 'grid-stack-item-content', title: dispLabel + ':' + dispType }); if (node.auto === true) { itemContent.append('<i class="fa fa-unlock nr-dashboard-layout-resize-enable" title="'+c_("layout.auto")+'"></i>'); } else { itemContent.append('<i class="fa fa-lock nr-dashboard-layout-resize-disable" title="'+c_("layout.manual")+'"></i>'); } itemContent.append('<b>'+ dispLabel +'</b><br/>'+ dispType); item.append(itemContent); grid.addWidget( item, node.x, node.y, node.width, node.height, false, null, null, minHeight, maxHeight, node.id); } else { // Record the spacer node ID to be deleted oldSpacer.push(node.id); } }); $(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; grid.resizable(el, !auto); }); // Group width change widthChange.push(new changeGroupWidth(cnt)); // Resize widget in group (start event) widgetResize.push(new resizeGroupWidget(cnt)); // Dragging widgets in a group (start event) widgetDrag.push(new dragGroupWidget(cnt)); } $('.grid-stack>.grid-stack-item>.grid-stack-item-content>.nr-dashboard-layout-resize-disable').on('click',layoutResizeDisable); $('.grid-stack>.grid-stack-item>.grid-stack-item-content>.nr-dashboard-layout-resize-enable').on('click',layoutResizeEnable); }, close: function() {}, show: function() {} } RED.tray.show(trayOptions); evt.stopPropagation(); evt.preventDefault(); }); } if (item.type === 'ui_tab') { var content = $('<div>',{class:"nr-db-sb-group-list-container"}).appendTo(container); // ui_tab group chevron if (listStates.hasOwnProperty(item.node.id) && !listStates[item.node.id]) { content.hide(); chevron.css({"transform":"rotate(-90deg)"}); content.addClass('nr-db-sb-collapsed'); listStates[item.node.id] = false; } else { listStates[item.node.id] = true; } titleRow.click(titleToggle(item.node.id,content,chevron)); // ui_tab group list var ol = $('<ol>',{class:"nr-db-sb-group-list"}).appendTo(content).editableList({ sortable:".nr-db-sb-group-list-header", addButton: false, height: 'auto', connectWith: ".nr-db-sb-group-list", addItem: function(container,i,group) { if (!group.node) { group.node = { id: RED.nodes.id(), _def: RED.nodes.getType("ui_group"), type: "ui_group", users: [], tab: item.node.id, order: i+1, name: "Group "+(i+1), width: 6, disp: true }; listElements[group.node.id] = container; RED.nodes.add(group.node); RED.editor.validateNode(group.node); group.widgets = []; RED.history.push({ t:'add', nodes:[group.node.id], dirty:RED.nodes.dirty() }); RED.nodes.dirty(true); if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) { RED.nodes.updateConfigNodeUsers(group.node); } } else { if (group.node.order === undefined) { group.node.order = i+1; } } var groupNode = group.node; elementParents[groupNode] = item.node.id; var titleRow = $('<div>',{class:"nr-db-sb-list-header nr-db-sb-group-list-header"}).appendTo(container); $('<i class="nr-db-sb-list-handle nr-db-sb-group-list-handle fa fa-bars"></i>').appendTo(titleRow); var chevron = $('<i class="fa fa-angle-down nr-db-sb-list-chevron">',{style:"width:10px;"}).appendTo(titleRow); $('<i class="nr-db-sb-icon nr-db-sb-group-icon fa fa-table"></i>').appendTo(titleRow); var title = $('<span class="nr-db-sb-title">').text(groupNode.name||groupNode.id||"").appendTo(titleRow); listElements[groupNode.id] = container; var buttonGroup = $('<div>',{class:"nr-db-sb-list-header-button-group",id:groupNode.id}).appendTo(titleRow); var spacerButton = $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> '+c_("layout.spacer")+'</a>').appendTo(buttonGroup); spacerButton.on('click',function(evt) { var spaceNode = { _def: RED.nodes.getType("ui_spacer"), type: "ui_spacer", hasUsers: false, users: [], id: RED.nodes.id(), tab: item.node.name, group: group.node.id, order: i+1, name: "spacer", width: 1, height:1, z: RED.workspaces.active(), label: function() { return "spacer " + this.width + "x" + this.height; } }; RED.nodes.add(spaceNode); RED.editor.validateNode(spaceNode); RED.history.push({ t:'add', nodes:[spaceNode.id], dirty:RED.nodes.dirty() }); RED.nodes.dirty(true); RED.view.redraw(); evt.stopPropagation(); evt.preventDefault(); }); var editButton = $('<a href="#" class="nr-db-sb-edit-group-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.edit")+'</a>').appendTo(buttonGroup); var content = $('<div>',{class:"nr-db-sb-widget-list-container"}).appendTo(container); if (!listStates.hasOwnProperty(groupNode.id) || !listStates[groupNode.id]) { content.hide(); chevron.css({"transform":"rotate(-90deg)"}); content.addClass('nr-db-sb-collapsed'); listStates[groupNode.id] = false; } else { listStates[groupNode.id] = true; } var ol = $('<ol>',{class:"nr-db-sb-widget-list"}).appendTo(content).editableList({ sortable:".nr-db-sb-widget-list-header", addButton: false, height: 'auto', connectWith: ".nr-db-sb-widget-list", addItem: function(container,i,widgetNode) { elementParents[widgetNode.id] = groupNode.id; var titleRow = $('<div>',{class:"nr-db-sb-list-header nr-db-sb-widget-list-header"}).appendTo(container); $('<i class="nr-db-sb-list-handle nr-db-sb-widget-list-handle fa fa-bars"></i>').appendTo(titleRow); $('<i class="nr-db-sb-icon nr-db-sb-widget-icon fa fa-picture-o"></i>').click(function(e) { e.preventDefault(); RED.search.show(widgetNode.id); }).appendTo(titleRow); var l = widgetNode._def.label; try { l = (typeof l === "function" ? l.call(widgetNode) : l)||""; } catch(err) { console.log("Definition error: "+d.type+".label",err); l = d.type; } var title = $('<span class="nr-db-sb-title">').text(l).appendTo(titleRow); listElements[widgetNode.id] = container; var buttonGroup = $('<div>',{class:"nr-db-sb-list-header-button-group"}).appendTo(titleRow); var editButton = $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.edit")+'</a>').appendTo(buttonGroup); container.on('mouseover',function() { widgetNode.highlighted = true; widgetNode.dirty = true; RED.view.redraw(); }); container.on('mouseout',function() { widgetNode.highlighted = false; widgetNode.dirty = true; RED.view.redraw(); }); editButton.on('click',function(evt) { RED.editor.edit(widgetNode); evt.stopPropagation(); evt.preventDefault(); }); }, sortItems: function(items) { var historyEvents = []; items.each(function(i,el) { var node = el.data('data'); var hev = { t:'edit', node:node, changes:{ order:node.order, group:node.group }, dirty:node.dirty, changed:node.changed }; historyEvents.push(hev); var changed = false; if (node.order !== i+1) { node.order = i+1; changed = true; } if (node.group !== group.node.id) { var oldGroupNode = RED.nodes.node(node.group); if (oldGroupNode) { var index = oldGroupNode.users.indexOf(node); oldGroupNode.users.splice(index,1); } node.group = group.node.id; group.node.users.push(node); changed = true; } if (changed) { node.dirty = true; node.changed = true; } }) RED.history.push({ t:'multi', events: historyEvents }); RED.nodes.dirty(true); RED.view.redraw(); } }); ol.css("min-height","5px"); if (groupNode.id) { groupLists[groupNode.id] = ol; } titleRow.click(titleToggle(groupNode.id,content,chevron)); editButton.on('click',function(evt) { RED.editor.editConfig("", groupNode.type, groupNode.id); evt.stopPropagation(); evt.preventDefault(); }); group.widgets.forEach(function(widget) { ol.editableList('addItem',widget); }) }, sortItems: function(items) { var historyEvents = []; items.each(function(i,el) { var groupData = el.data('data'); var node = groupData.node; var hev = { t:'edit', node:node, changes:{ order:node.order, tab:node.tab }, dirty:node.dirty, changed:node.changed }; historyEvents.push(hev); var changed = false; if (node.order !== i+1) { node.order = i+1; changed = true; } if (changed) { node.dirty = true; node.changed = true; } if (node.tab !== item.node.id) { var oldTabNode = RED.nodes.node(node.tab); if (oldTabNode) { var index = oldTabNode.users.indexOf(node); oldTabNode.users.splice(index,1); } node.tab = item.node.id; item.node.users.push(node); changed = true; } }) RED.history.push({ t:'multi', events: historyEvents }); RED.nodes.dirty(true); RED.view.redraw(); } }) tabLists[item.node.id] = ol; addGroupButton.click(function(evt) { ol.editableList('addItem',{}); evt.stopPropagation(); evt.preventDefault(); }); item.groups.forEach(function(group) { ol.editableList('addItem',group); }); } } var tabContainer = $('<ol>',{class:"nr-db-sb-tab-list"}).appendTo(divTabs).editableList({ sortable:".nr-db-sb-tab-list-header", addButton: false, addItem: addTabOrLinkItem, sortItems: function(items) { var historyEvents = []; items.each(function(i,el) { var itemData = el.data('data'); var node = itemData.node; var hev = { t:'edit', node:node, changes:{ order:node.order }, dirty:node.dirty, changed:node.changed } historyEvents.push(hev); var changed = false; if (node.order !== i+1) { node.order = i+1; changed = true; } if (changed) { node.dirty = true; node.changed = true; } }) RED.history.push({ t:'multi', events: historyEvents }); RED.nodes.dirty(true); RED.view.redraw(); } }); var orphanedWidgets = $('<div>',{class:"form-row"}).appendTo(layoutTab); $('<span><i class="fa fa-info-circle"></i> There <span id="nr-db-missing-group-count"></span> not in a group. Click <a id="nr-db-add-missing-groups" href="#">here</a> to create the missing groups</span>').appendTo(orphanedWidgets); orphanedWidgets.find('a').click(function(event) { var unknownGroups = {}; RED.nodes.eachNode(function(node) { if (/^ui_/.test(node.type) && node.type !== 'ui_link' && node.type !== 'ui_toast' && node.type !== 'ui_ui_control') { if (!RED.nodes.node(node.group)) { var g = node.group || "_BLANK_"; unknownGroups[g] = unknownGroups[g] || []; unknownGroups[g].push(node); } } }); var tab = null; var tabs = tabContainer.editableList('items'); tabs.first().each(function(i,el) { var tabData = el.data('data'); tab = tabData.node; }); var hev = []; if (tab === null) { tab = { id: RED.nodes.id(), _def: RED.nodes.getType("ui_tab"), type: "ui_tab", users: [], order: 0, name: "Tab", icon: "dashboard" }; RED.nodes.add(tab); RED.editor.validateNode(tab); hev.push(tab.id); } for (var groupId in unknownGroups) { if (unknownGroups.hasOwnProperty(groupId)) { var groupNode = { id: RED.nodes.id(), _def: RED.nodes.getType("ui_group"), type: "ui_group", users: [], tab: tab.id, order: i+1, name: (groupId==="_BLANK_"?"Group":groupId), width: 6, disp: true }; hev.push(groupNode.id); RED.nodes.add(groupNode); RED.editor.validateNode(groupNode); if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) { RED.nodes.updateConfigNodeUsers(groupNode); } var widgets = unknownGroups[groupId]; for (var i=0; i<widgets.length; i++) { widgets[i].group = groupNode.id; widgets[i].changed = true; widgets[i].dirty = true; if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) { RED.nodes.updateConfigNodeUsers(widgets[i]); } RED.editor.validateNode(widgets[i]); } } } RED.history.push({ t:'add', nodes: hev, dirty:RED.nodes.dirty() }); RED.nodes.dirty(true); refresh(); refreshOrphanedWidgets(); RED.view.redraw(); event.preventDefault(); }); var listElements = {}; var dashboard = []; var listStates = {}; var elementParents = {}; var awaitingGroups = {}; var awaitingTabs = {}; function getCurrentList() { var currentList = []; var tabs = tabContainer.editableList('items'); var open = false; tabs.each(function(i,el) { var tabData = el.data('data'); var tab = []; var groups = el.find('.nr-db-sb-group-list').editableList('items'); groups.each(function(j,el) { var group = []; var groupData = el.data('data'); var widgets = el.find('.nr-db-sb-widget-list').editableList('items'); widgets.each(function(k,el) { var widgetData = el.data('data'); group.push(widgetData.id); }) tab.push({id:groupData.node.id, widgets:group}); }); currentList.push({id:tabData.node.id,groups:tab}); }); return currentList; } function refreshOrphanedWidgets() { var unknownGroups = {}; var count = 0; RED.nodes.eachNode(function(node) { if (/^ui_/.test(node.type) && node.type !== 'ui_link' && node.type !== 'ui_toast' && node.type !== 'ui_ui_control' && (node.type === 'ui_template' && node.templateScope !== 'global')) { if (!RED.nodes.node(node.group)) { var g = node.group || "_BLANK_"; unknownGroups[g] = unknownGroups[g] || []; unknownGroups[g].push(node); count++; } } }); if (count > 0) { orphanedWidgets.show(); $("#nr-db-missing-group-count").text((count===1?"is ":"are ")+count+" widget"+(count === 1?"":"s")) } else { orphanedWidgets.hide(); } } function refresh() { var currentList = getCurrentList(); dashboard = []; var tabs = {}; var groups = {}; var elements = []; var groupElements = {}; var tabGroups = {}; var groupId; var group; var tabId; var tab; var unknownGroups = 0; // Find all the tabs and groups RED.nodes.eachConfig(function(node) { switch (node.type) { case 'ui_tab': case 'ui_link': { tabs[node.id] = node; //tabContainer.editableList('addItem',node); break; } case 'ui_group': { groups[node.id] = node; break; } case 'ui_spacer': { if (groups.hasOwnProperty(node.group)) { groupElements[node.group] = groupElements[node.group]||[]; groupElements[node.group].push(node); } break; } } }); for (groupId in groups) { if (groups.hasOwnProperty(groupId)) { group = groups[groupId]; if (tabs.hasOwnProperty(group.tab)) { // This group belongs to a tab tabGroups[group.tab] = tabGroups[group.tab]||[]; tabGroups[group.tab].push(group); } else { unknownGroups++; } } } // Find all ui widgets - list them by their group id RED.nodes.eachNode(function(node) { if (/^ui_/.test(node.type)) { if (groups.hasOwnProperty(node.group)) { groupElements[node.group] = groupElements[node.group]||[]; groupElements[node.group].push(node); } else if ((node.type !== 'ui_toast')&&(node.type !== 'ui_ui_control')&&(node.type === 'ui_template' && node.templateScope !== 'global')) { unknownGroups++; } } }); if (unknownGroups > 0) { $("#nr-db-missing-group-count").text((unknownGroups===1?"is ":"are ")+unknownGroups+" widget"+(unknownGroups === 1?"":"s")) orphanedWidgets.show(); } else { orphanedWidgets.hide(); } // Sort each group's array of widgets for (groupId in groupElements) { if (groupElements.hasOwnProperty(groupId)) { group = groupElements[groupId]; groupElements[groupId] = group.map(function(v,i) { return {n:v,i:i} }).sort(function(A,B) { if (A.n.order < B.n.order) { return A.n.order!==0?-1:1;} if (A.n.order > B.n.order) { return B.n.order!==0?1:-1;} return A.i - B.i; }).map(function(v) { return v.n}) } } // Sort each tabs's array of groups for (tabId in tabGroups) { if (tabGroups.hasOwnProperty(tabId)) { tab = tabGroups[tabId]; tabGroups[tabId] = tab.map(function(v,i) { return {n:v,i:i} }).sort(function(A,B) { if (A.n.order < B.n.order) { return -1;} if (A.n.order > B.n.order) { return 1;} return A.i - B.i; }).map(function(v) { return v.n}) } } var tabIds = Object.keys(tabs).map(function(v,i) { return {n:tabs[v],i:i} }).sort(function(A,B) { if (A.n.order < B.n.order) { return -1;} if (A.n.order > B.n.order) { return 1;} return A.i - B.i; }).map(function(v) { return v.n.id}); tabIds.forEach(function(tabId) { var tab = {node:tabs[tabId],groups:[]}; if (tabGroups[tabId]) { tabGroups[tabId].forEach(function(groupNode) { var group = {node:groupNode,widgets:[]}; if (groupElements[groupNode.id]) { group.widgets = groupElements[groupNode.id]; } tab.groups.push(group); }); } dashboard.push(tab); }); var newList = dashboard.map(function(t) { return { id: t.node.id, groups: t.groups.map(function(g) { return { id: g.node.id, widgets: g.widgets.map(function(w) { return w.id; }) } }) } }); if (JSON.stringify(newList)!=JSON.stringify(currentList)) { listElements = {}; groupLists = {}; tabLists = {}; tabs = {}; groups = {}; elementParents = {}; tabContainer.empty(); dashboard.forEach(function(tab) { tabContainer.editableList('addItem',tab); }); } //ensureDashboardNode(true); if (globalDashboardNode) { $("#nr-db-field-title").val(globalDashboardNode.site.name); $("#nr-db-field-allowSwipe").val(globalDashboardNode.site.allowSwipe || "false"); $("#nr-db-field-allowTempTheme").val(globalDashboardNode.site.allowTempTheme || "true"); $("#nr-db-field-hideToolbar").val(globalDashboardNode.site.hideToolbar || "false"); $("#nr-db-field-dateFormat").val(globalDashboardNode.site.dateFormat); if (typeof globalDashboardNode.site.sizes !== "object") { globalDashboardNode.site.sizes = sizes; } $("#nr-db-field-sx").val(globalDashboardNode.site.sizes.sx); $("#nr-db-field-sy").val(globalDashboardNode.site.sizes.sy); $("#nr-db-field-px").val(globalDashboardNode.site.sizes.px); $("#nr-db-field-py").val(globalDashboardNode.site.sizes.py); $("#nr-db-field-cx").val(globalDashboardNode.site.sizes.cx); $("#nr-db-field-cy").val(globalDashboardNode.site.sizes.cy); $("#nr-db-field-gx").val(globalDashboardNode.site.sizes.gx); $("#nr-db-field-gy").val(globalDashboardNode.site.sizes.gy); if (typeof globalDashboardNode.theme.angularTheme !== "object") { globalDashboardNode.theme.angularTheme = aTheme; } $("#nr-db-field-angPrimary").val(globalDashboardNode.theme.angularTheme.primary || "indigo"); $("#nr-db-field-angAccents").val(globalDashboardNode.theme.angularTheme.accents || "blue"); $("#nr-db-field-angWarn").val(globalDashboardNode.theme.angularTheme.warn || "red"); $("#nr-db-field-angBackground").val(globalDashboardNode.theme.angularTheme.background || "grey"); $("#nr-db-field-angLook").val(globalDashboardNode.theme.angularTheme.palette || "light"); $("#nr-db-field-theme").val(globalDashboardNode.theme.name); $("#ui-sidebar-name").val(globalDashboardNode.theme.customTheme.name); if (globalDashboardNode.theme.name === 'theme-custom') { $("#custom-theme-library-container").show(); $("#custom-theme-settings").show(); } else { $("#custom-theme-library-container").hide(); $("#custom-theme-settings").hide(); } if ($('#nr-db-field-allowTempTheme').val() === "none") { ulDashboardTabs.children().eq(2).addClass("hidden"); ulDashboardTabs.children().eq(3).removeClass("hidden"); } else { ulDashboardTabs.children().eq(2).removeClass("hidden"); ulDashboardTabs.children().eq(3).addClass("hidden"); } //set colour start if (typeof globalDashboardNode.theme.name !== "string") { globalDashboardNode.theme.name = "theme-light"; } var currentTheme = globalDashboardNode.theme.name.split("-")[1]; var startingValue = globalDashboardNode.theme[currentTheme+"Theme"].baseColor; setColourPickerColour("base-color", startingValue); $("#nr-db-field-font").val(globalDashboardNode.theme[currentTheme+"Theme"].baseFont); generateColours(startingValue); if (globalDashboardNode.theme.name === 'theme-light' || globalDashboardNode.theme.name === 'theme-dark') { addLightAndDarkResetButton('base-color', $('#base-settings-ul').children().first()); } if (editor === undefined) { editor = RED.editor.createEditor({ id: 'nr-db-field-format-editor', mode: 'ace/mode/javascript', value: JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site}) }); RED.library.create({ url:"themes", // where to get the data from type:"theme", // the type of object the library is for editor: editor, // the field name the main text body goes to mode:"ace/mode/javascript", fields:['name'], elementPrefix:"ui-sidebar-" }); } editor.on('input', function() { // Check for any changes on the editor object // i.e. has the theme been customised compared // to what is stored var editorObject = JSON.parse(editor.getValue()); //Update theme object if necessary if (JSON.stringify(editorObject.theme) !== JSON.stringify(globalDashboardNode.theme.themeState)) { globalDashboardNode.theme.themeState = editorObject.theme; if ($("#ui-sidebar-name").val() !== globalDashboardNode.theme.customTheme.name) { globalDashboardNode.theme.customTheme.name = $("#ui-sidebar-name").val(); globalDashboardNode.theme.customTheme.baseColor = globalDashboardNode.theme.themeState["base-color"].value; setColourPickerColour("base-color", globalDashboardNode.theme.customTheme.baseColor); generateColours(globalDashboardNode.theme.themeState["base-color"].value); RED.nodes.dirty(true); } } if (JSON.stringify(aTheme) !== JSON.stringify(globalDashboardNode.theme.angularTheme)) { globalDashboardNode.theme.angularTheme = aTheme; } //Update site object if necessary if (JSON.stringify(editorObject.site) !== JSON.stringify(globalDashboardNode.site)) { globalDashboardNode.site = editorObject.site; $("#nr-db-field-title").val(globalDashboardNode.site.name); $("#nr-db-field-hideToolbar").val(globalDashboardNode.site.hideToolbar); $("#nr-db-field-allowSwipe").val(globalDashboardNode.site.allowSwipe); $("#nr-db-field-allowTempTheme").val(globalDashboardNode.site.allowTempTheme); $("#nr-db-field-dateFormat").val(globalDashboardNode.site.dateFormat); $("#nr-db-field-sx").val(globalDashboardNode.site.sizes.sx); $("#nr-db-field-sy").val(globalDashboardNode.site.sizes.sy); $("#nr-db-field-px").val(globalDashboardNode.site.sizes.px); $("#nr-db-field-py").val(globalDashboardNode.site.sizes.py); $("#nr-db-field-gx").val(globalDashboardNode.site.sizes.gx); $("#nr-db-field-gy").val(globalDashboardNode.site.sizes.gy); $("#nr-db-field-cx").val(globalDashboardNode.site.sizes.cx); $("#nr-db-field-cy").val(globalDashboardNode.site.sizes.cy); RED.nodes.dirty(true); } }); } awaitingGroups = {}; awaitingTabs = {}; } RED.sidebar.addTab({ id: "dashboard", label: c_("label.dashboard"), name: "Dashboard", content: content, closeable: true, pinned: true, iconClass: "fa fa-bar-chart", disableOnEdit: true, onchange: function() { refresh(); } }); editSaveEventHandler = function(node) { if (/^ui_/.test(node.type)) { if (node.type === "ui_tab" || node.type === "ui_group") { if (listElements[node.id]) { // Existing element listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").text(node.name||node.id); if (node.type === "ui_group") { refresh(); } else { if (node.hidden === true) { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").addClass('nr-db-sb-title-hidden'); } else { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").removeClass('nr-db-sb-title-hidden'); } if (node.disabled === true) { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").addClass('nr-db-sb-title-disabled'); } else { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").removeClass('nr-db-sb-title-disabled'); } } } else if (node.type === "ui_tab") { // Adding a tab tabContainer.editableList('addItem',{node:node,groups:[]}) } else { // Adding a group if (tabLists[node.tab]) { tabLists[node.tab].editableList('addItem',{node:node,widgets:[]}) } } } else if (node.type === "ui_link") { if (listElements[node.id]) { var container = listElements[node.id]; container.find(".nr-db-sb-link-name").text(node.name||"untitled"); container.find(".nr-db-sb-link-url").text(node.link); } } else { refreshOrphanedWidgets(); if (listElements[node.id]) { if (node.group != elementParents[node.id]) { // Moved to a different group if (groupLists[elementParents[node.id]]) { groupLists[elementParents[node.id]].editableList('removeItem',listElements[node.id].data('data')) } if (groupLists[node.group]) { groupLists[node.group].editableList('removeItem',node) groupLists[node.group].editableList('addItem',node); } } else { var l = node._def.label; try { l = (typeof l === "function" ? l.call(node) : l)||""; } catch(err) { console.log("Definition error: "+d.type+".label",err); l = d.type; } listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").text(l); } } else { if (groupLists[node.group]) { if (node.order === 0) { node.order = groupLists[node.group].editableList('length'); } groupLists[node.group].editableList('addItem',node); } } } } }; RED.events.on("editor:save",editSaveEventHandler); // Dashboard layout tool layoutUpdateEventHandler = function(node) { if (/^ui_/.test(node.type) && node.type !== 'ui_link' && node.type !== 'ui_toast' && node.type !== 'ui_ui_control' && node.type !== 'ui_audio' && node.type !== 'ui_base' && node.type !== 'ui_group' && node.type !== 'ui_tab') { if (listElements[node.id]) { if (node.group != elementParents[node.id]) { // Moved to a different group if (groupLists[elementParents[node.id]]) { groupLists[elementParents[node.id]].editableList('removeItem',listElements[node.id].data('data')) } if (groupLists[node.group]) { groupLists[node.group].editableList('removeItem',node) groupLists[node.group].editableList('addItem',node); groupLists[node.group].editableList('sort',function(a,b) {return a.order-b.order;}); } } else { groupLists[node.group].editableList('sort',function(a,b) {return a.order-b.order;}); } } } }; RED.events.on("layout:update",layoutUpdateEventHandler); var pendingAdd = []; var pendingAddTimer = null; function handlePendingAdds() { var hasTabs = false; var hasGroups = false; pendingAdd.sort(function(A,B) { hasTabs = hasTabs || A.type === "ui_tab" || B.type === "ui_tab"; hasGroups = hasGroups || A.type === "ui_group" || B.type === "ui_group"; if (A.type === B.type) { return 0; } if (A.type === "ui_tab") { return -1; } else if (B.type === "ui_tab") { return 1; } else if (A.type === "ui_group") { return -1; } else if (B.type === "ui_group") { return 1; } return 0 }); var updateList = {}; for (var i=0; i<pendingAdd.length; i++) { var node = pendingAdd[i]; if (listElements[node.id]) { continue; } if (node.type === "ui_tab") { tabContainer.editableList('addItem',{node:node,groups:[]}); } else { if (hasTabs) { // We've added some tabs, need to give jquery time to add the lists pendingAdd = pendingAdd.slice(i); pendingAddTimer = setTimeout(handlePendingAdds,50); return; } if (node.type === "ui_group") { if (tabLists[node.tab]) { tabLists[node.tab].editableList('addItem',{node:node,widgets:[]}); } } else { if (hasGroups) { // We've added some tabs, need to give jquery time to add the lists pendingAdd = pendingAdd.slice(i); pendingAddTimer = setTimeout(handlePendingAdds,50); return; } if (groupLists[node.group]) { groupLists[node.group].editableList('addItem',node) if (node.order >= 0) { updateList[node.group] = true; } } else { refreshOrphanedWidgets(); } } } } Object.keys(updateList).forEach(function (group) { var list = groupLists[group]; if (list) { list.editableList("sort", function(a,b) {return a.order-b.order;}); } }); pendingAdd = []; } nodesAddEventHandler = function(node) { if (/^ui_/.test(node.type) && !listElements[node.id]) { pendingAdd.push(node); clearTimeout(pendingAddTimer); pendingAddTimer = setTimeout(handlePendingAdds,100); } }; RED.events.on("nodes:add", nodesAddEventHandler); nodesRemoveEventHandler = function(node) { if (/^ui_/.test(node.type)) { if (node.type === "ui_tab" || node.type === "ui_link") { if (listElements[node.id]) { tabContainer.editableList('removeItem',listElements[node.id].data('data')); delete tabLists[node.id]; } } else if (node.type === "ui_group") { if (tabLists[node.tab] && listElements[node.id]) { tabLists[node.tab].editableList('removeItem',listElements[node.id].data('data')); } delete groupLists[node.id]; } else { if (groupLists[node.group]) { groupLists[node.group].editableList('removeItem',node) } } refreshOrphanedWidgets(); delete listElements[node.id]; } }; RED.events.on("nodes:remove", nodesRemoveEventHandler); } }); $.widget("nodereddashboard.elementSizerByNum", { _create: function() { var that = this; var has_height = this.options.has_height; var pos = this.options.pos; var c_width = has_height ? '15%' : '6%'; var container = $('<div>').css({ position: 'absolute', background: 'white', padding: '10px 10px 10px 10px', border: '1px solid grey', zIndex: '20', borderRadius: "4px", display:"none", width: c_width }).appendTo(document.body); var box0 = $("<div>").css({ fontSize: '13px', color: '#aaa', float: 'left', paddingTop: '1px' }).appendTo(container); var width = $(this.options.width).val(); var height = has_height ? $(this.options.height).val() : undefined; var max_w = ''; var groupNode = this.options.groupNode; if(groupNode) { max_w = 'max="'+groupNode.width+'"'; } width = (width > 0) ? width : 1; height = (height > 0) ? height : 1; var in0 = $('<input type="number" min="1" '+max_w+'>') .css("width", has_height ? "40%" : "100%") .val(width) .appendTo(box0); if(has_height) { var pad = $('<span>') .text(" x ") .appendTo(box0); var in1 = $('<input type="number" min="1">') .css("width", "40%") .val(height) .appendTo(box0); } var closeTimer; var closeFunc = function() { var w = in0.val(); var h = has_height ? in1.val() : undefined; var label = that.options.label; label.text(w+(has_height ? (' x '+h) : '')); $(that.options.width).val(w).change(); if(has_height) { $(that.options.height).val(h).change(); } that.destroy(); }; container.keypress(function(e) { if(e.which === 13) { // pressed ENTER container.fadeOut(100, closeFunc); } }); container.on('mouseleave', function(e) { closeTimer = setTimeout(function() { container.fadeOut(200, closeFunc); }, 100); }); container.on('mouseenter', function(e) { clearTimeout(closeTimer); }); container.css({ top: (pos.top -10)+"px", left: (pos.left +10)+"px" }); container.fadeIn(200); } }); $.widget( "nodereddashboard.elementSizer", { _create: function() { var that = this; var gridWidth = 6; var width = parseInt($(this.options.width).val()||0); var height = parseInt(this.options.hasOwnProperty('height')?$(this.options.height).val():"1")||0; var hasAuto = (!this.options.hasOwnProperty('auto') || this.options.auto); this.element.css({ minWidth: this.element.height()+4 }); var auto_text = c_("auto"); var sizeLabel = (width === 0 && height === 0)?auto_text:width+(this.options.hasOwnProperty('height')?" x "+height:""); this.element.text(sizeLabel).on('mousedown',function(evt) { evt.stopPropagation(); evt.preventDefault(); var width = parseInt($(that.options.width).val()||0); var height = parseInt(that.options.hasOwnProperty('height')?$(that.options.height).val():"1")||0; var maxWidth = 0; var maxHeight; var fixedWidth = false; var fixedHeight = false; var group = $(that.options.group).val(); if (group) { var groupNode = RED.nodes.node(group); if (groupNode) { gridWidth = Math.max(6,groupNode.width,+width); maxWidth = groupNode.width || gridWidth; fixedWidth = true; } maxHeight = Math.max(6,+height+1); } else { gridWidth = Math.max(12,+width); maxWidth = gridWidth; maxHeight = 1; fixedHeight = true; } var pos = $(this).offset(); var container = $('<div>').css({ position: 'absolute', background: 'white', padding: '5px 10px 10px 10px', border: '1px solid grey', zIndex: '20', borderRadius: "4px", display:"none" }).appendTo(document.body); var closeTimer; container.on('mouseleave',function(evt) { closeTimer = setTimeout(function() { container.fadeOut(200, function() { $(this).remove(); }); },100) }); container.on('mouseenter',function() { clearTimeout(closeTimer); }) var label = $("<div>").css({ fontSize: '13px', color: '#aaa', float: 'left', paddingTop: '1px' }).appendTo(container).text((width === 0 && height === 0)?auto_text:(width+(that.options.hasOwnProperty('height')?" x "+height:""))); label.hover(function() { $(this).css('text-decoration', 'underline'); }, function() { $(this).css('text-decoration', 'none'); }); label.click(function(e) { var group = $(that.options.group).val(); var groupNode = null; if(group) { groupNode = RED.nodes.node(group); if(groupNode === null) { return; } } $(that).elementSizerByNum({ width: that.options.width, height: that.options.height, groupNode: groupNode, pos: pos, label: that.element, has_height: that.options.hasOwnProperty('height') }); closeTimer = setTimeout(function() { container.fadeOut(200, function() { $(this).remove(); }); },100) }); var buttonRow = $('<div>',{style:"text-align:right; height:25px;"}).appendTo(container); if (hasAuto) { var button = $('<a>',{href:"#",class:"editor-button editor-button-small",style:"margin-bottom:5px"}) .text(auto_text) .appendTo(buttonRow) .on('mouseup',function(evt) { that.element.text(auto_text) $(that.options.width).val(0).change(); $(that.options.height).val(0).change(); evt.preventDefault(); container.fadeOut(200, function() { $(this).remove(); }); }); } var cellBorder = "1px dashed lightGray"; var cellBorderExisting = "1px solid gray"; var cellBorderHighlight = "1px dashed black"; var rows = []; function addRow(i) { var row = $('<div>').css({padding:0,margin:0,height:"25px","box-sizing":"border-box"}).appendTo(container); rows.push(row); cells.push([]) for (var j=0; j<gridWidth; j++) { addCell(i,j); } } function addCell(i,j) { var row = rows[i]; var cell = $('<div>').css({ display:"inline-block", width: "25px", height: "25px", borderRight: (j===(width-1)&&i<height)?cellBorderExisting:cellBorder, borderBottom: (i===(height-1)&&j<width)?cellBorderExisting:cellBorder, boxSizing: "border-box", cursor:"pointer", background: (j<maxWidth)?"#fff":"#eee" }).appendTo(row); cells[i].push(cell); if (j===0) { cell.css({borderLeft:((i<=height-1)?cellBorderExisting:cellBorder)}); } if (i===0) { cell.css({borderTop:((j<=width-1)?cellBorderExisting:cellBorder)}); } if (j<maxWidth) { cell.data("w",j); cell.data("h",i); cell.on("mouseup",function() { that.element.text(($(this).data("w")+1)+(that.options.hasOwnProperty('height')?" x "+($(this).data("h")+1):"")) $(that.options.width).val($(this).data("w")+1).change(); $(that.options.height).val($(this).data("h")+1).change(); container.fadeOut(200, function() { $(this).remove(); }); }); cell.on("mouseover",function() { var w = $(this).data("w"); var h = $(this).data("h"); label.text((w+1)+(that.options.hasOwnProperty('height')?" x "+(h+1):"")); for (var y = 0; y<maxHeight; y++) { for (var x = 0; x<maxWidth; x++) { cells[y][x].css({ background: (y<=h && x<=w)?'#ddd':'#fff', borderLeft: (x===0&&y<=h)?cellBorderHighlight:(x===0)?((y<=height-1)?cellBorderExisting:cellBorder):'', borderTop: (y===0&&x<=w)?cellBorderHighlight:(y===0)?((x<=width-1)?cellBorderExisting:cellBorder):'', borderRight: (x===w&&y<=h)?cellBorderHighlight:((x===width-1&&y<=height-1)?cellBorderExisting:cellBorder), borderBottom: (y===h&&x<=w)?cellBorderHighlight:((y===height-1&&x<=width-1)?cellBorderExisting:cellBorder) }) } } if (!fixedHeight && h === maxHeight-1) { addRow(maxHeight++) } if (!fixedWidth && w === maxWidth-1) { maxWidth++; gridWidth++; for (var r=0; r<maxHeight; r++) { addCell(r,maxWidth-1); } } }) } } var cells = []; for (var i=0; i<maxHeight; i++) { addRow(i); } container.css({ top:(pos.top)+"px", left:(pos.left)+"px" }); container.fadeIn(200); }) } }); })(jQuery); </script> <script type="text/html" data-template-name="ui_base"> <div class='form-row'> This <i>ui_base</i> node is the main node that all<br/>other dashboard widget nodes communicate to.<br/> <br/>One instance is required to support the dashboard.<br/> <br/>If you have no dashboard you can delete this node.<br/> It will be re-created automatically if required.<br/> </div> </script>