From 09b73d5afcc66baf942a776f83a0295700f83d22 Mon Sep 17 00:00:00 2001 From: Paolo Cristiani <42511852+pgrisu@users.noreply.github.com> Date: Tue, 20 Aug 2019 10:51:11 +0200 Subject: [PATCH] Add new hidden widget cards for latest and timeseries value --- .../json/system/widget_bundles/cards.json | 34 +++++++++++++++++- ui/src/app/components/dashboard.directive.js | 35 +++++++++++++++---- .../components/widget/widget.controller.js | 2 +- .../layouts/dashboard-layout.tpl.html | 1 + 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 11cdb388ab..f136ed5665 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -132,6 +132,38 @@ "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" \\\"+ data['temperature'] +\\\" °C\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Entities hierarchy\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"widgetStyle\":{},\"actions\":{}}" } + }, + { + "alias": "hidden_latest", + "name": "Hidden Latest", + "descriptor": { + "type": "latest", + "sizeX": 4, + "sizeY": 2, + "resources": [], + "templateHtml": "", + "templateCss": "", + "controllerScript": "let onDataUpdFunct = function() {};\n\nself.onInit = function() {\n var onDataUpdateFunction = self.ctx.settings.onDataUpdateFunction;\n if (angular.isDefined(onDataUpdateFunction) && onDataUpdateFunction.length > 0) {\n onDataUpdFunct = new Function('ctx', onDataUpdateFunction);\n }\n}\n\nself.onDataUpdated = function() {\n onDataUpdFunct(self.ctx);\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"hidden\": {\n \"title\": \"Hidden\",\n \"type\": \"boolean\",\n \"default\": true,\n \"readOnly\": true\n },\n \"onDataUpdateFunction\": {\n \"title\": \"On data update function: f(ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n }\n },\n \"form\": [\n \"hidden\",\n {\n \"key\": \"onDataUpdateFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "dataKeySettingsSchema": "", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"hidden\":true},\"title\":\"Hidden Latest\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + } + }, + { + "alias": "hidden_timeseries", + "name": "Hidden Timeseries", + "descriptor": { + "type": "timeseries", + "sizeX": 4, + "sizeY": 2, + "resources": [], + "templateHtml": "", + "templateCss": "", + "controllerScript": "let onDataUpdFunct = function() {};\n\nself.onInit = function() {\n var onDataUpdateFunction = self.ctx.settings.onDataUpdateFunction;\n if (angular.isDefined(onDataUpdateFunction) && onDataUpdateFunction.length > 0) {\n onDataUpdFunct = new Function('ctx', onDataUpdateFunction);\n }\n}\n\nself.onDataUpdated = function() {\n onDataUpdFunct(self.ctx);\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"hidden\": {\n \"title\": \"Hidden\",\n \"type\": \"boolean\",\n \"default\": true,\n \"readOnly\": true\n },\n \"onDataUpdateFunction\": {\n \"title\": \"On data update function: f(ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n }\n },\n \"form\": [\n \"hidden\",\n {\n \"key\": \"onDataUpdateFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "dataKeySettingsSchema": "", + "defaultConfig": "{\"datasources\":[{\"type\":\"timeseries\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"hidden\":true},\"title\":\"Hidden Timeseries\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + } } ] -} \ No newline at end of file +} diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js index d7e8b6247c..a5057b0d49 100644 --- a/ui/src/app/components/dashboard.directive.js +++ b/ui/src/app/components/dashboard.directive.js @@ -58,6 +58,7 @@ function Dashboard() { columns: '=', margins: '=', isEdit: '=', + hideWidgets: '=?', autofillHeight: '=', mobileAutofillHeight: '=?', mobileRowHeight: '=?', @@ -100,6 +101,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ vm.gridster = null; + vm.hideWidgets = angular.isDefined(vm.hideWidgets) ? vm.hideWidgets : false; + vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false; vm.isMobileSize = false; @@ -122,10 +125,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ maxRows: 100, columns: vm.columns ? vm.columns : 24, margins: vm.margins ? vm.margins : [10, 10], - minSizeX: 1, - minSizeY: 1, - defaultSizeX: 8, - defaultSizeY: 6, + minSizeX: vm.hideWidgets ? 0 : 1, + minSizeY: vm.hideWidgets ? 0 : 1, + defaultSizeX: vm.hideWidgets ? 0 : 8, + defaultSizeY: vm.hideWidgets ? 0 : 6, resizable: { enabled: vm.isEdit }, @@ -143,11 +146,11 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ vm.widgetIds = []; vm.widgetItemMap = { - sizeX: 'vm.widgetLayoutInfo[widget.id].sizeX', - sizeY: 'vm.widgetLayoutInfo[widget.id].sizeY', + sizeX: 'vm.widgetIsHidden(widget) ? 0 : vm.widgetLayoutInfo[widget.id].sizeX', + sizeY: 'vm.widgetIsHidden(widget) ? 0 : vm.widgetLayoutInfo[widget.id].sizeY', row: 'vm.widgetLayoutInfo[widget.id].row', col: 'vm.widgetLayoutInfo[widget.id].col', - minSizeY: 'widget.minSizeY', + minSizeY: 'vm.widgetIsHidden(widget) ? 0 : widget.minSizeY', maxSizeY: 'widget.maxSizeY' }; @@ -195,6 +198,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ vm.widgetActions = widgetActions; vm.dropWidgetShadow = dropWidgetShadow; vm.enableWidgetFullscreen = enableWidgetFullscreen; + vm.widgetIsHidden = widgetIsHidden; vm.hasTimewindow = hasTimewindow; vm.hasAggregation = hasAggregation; vm.editWidget = editWidget; @@ -335,6 +339,13 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ $scope.$broadcast('toggleDashboardEditMode', vm.isEdit); }); + $scope.$watch('vm.hideWidgets', function () { + vm.gridsterOpts.minSizeX = vm.hideWidgets ? 0 : 1, + vm.gridsterOpts.minSizeY = vm.hideWidgets ? 0 : 1, + vm.gridsterOpts.defaultSizeX = vm.hideWidgets ? 0 : 8, + vm.gridsterOpts.defaultSizeY = vm.hideWidgets ? 0 : 6 + }); + $scope.$watch('vm.isMobileSize', function (newVal, prevVal) { if (!angular.equals(newVal, prevVal)) { $scope.$broadcast('mobileModeChanged', vm.isMobileSize); @@ -1017,6 +1028,16 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ } } + function widgetIsHidden(widget) { + if (!vm.hideWidgets) + return false; + if (angular.isDefined(widget.config.settings.hidden)) { + return widget.config.settings.hidden; + } else { + return false; + } + } + function hasTimewindow(widget) { if (widget.type === types.widgetType.timeseries.value || widget.type === types.widgetType.alarm.value) { return angular.isDefined(widget.config.useDashboardTimewindow) ? diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js index 126c793425..31ddf56a00 100644 --- a/ui/src/app/components/widget/widget.controller.js +++ b/ui/src/app/components/widget/widget.controller.js @@ -865,7 +865,7 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL var sizeChanged = false; if (!widgetContext.width || widgetContext.width != width || !widgetContext.height || widgetContext.height != height) { - if (width > 0 && height > 0) { + if ((width > 0 && height > 0) || (widgetContext.settings.hidden === true)) { widgetContext.$container.css('height', height + 'px'); widgetContext.$container.css('width', width + 'px'); widgetContext.width = width; diff --git a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html index 14e04ff8a4..d515d2447b 100644 --- a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html +++ b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html @@ -49,6 +49,7 @@ state-controller="vm.dashboardCtx.stateController" dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" is-edit="vm.isEdit" + hide-widgets="!vm.isEdit" autofill-height="vm.layoutCtx.gridSettings.autoFillHeight && !vm.isEdit" mobile-autofill-height="vm.layoutCtx.gridSettings.mobileAutoFillHeight && !vm.isEdit" mobile-row-height="vm.layoutCtx.gridSettings.mobileRowHeight"