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