UI: New widget type 'HTML Card'
This commit is contained in:
parent
c860753bc8
commit
d18ca19b3b
File diff suppressed because one or more lines are too long
@ -46,6 +46,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
|
||||
$window.TbAnalogueRadialGauge = TbAnalogueRadialGauge;
|
||||
$window.TbDigitalGauge = TbDigitalGauge;
|
||||
$window.TbMapWidget = TbMapWidget;
|
||||
$window.cssjs = cssjs;
|
||||
|
||||
var cssParser = new cssjs();
|
||||
cssParser.testMode = false;
|
||||
|
||||
@ -141,6 +141,14 @@ export default angular.module('thingsboard.types', [])
|
||||
bundleAlias: "gpio_widgets",
|
||||
alias: "basic_gpio_control"
|
||||
}
|
||||
},
|
||||
static: {
|
||||
value: "static",
|
||||
name: "widget.static",
|
||||
template: {
|
||||
bundleAlias: "cards",
|
||||
alias: "html_card"
|
||||
}
|
||||
}
|
||||
},
|
||||
systemBundleAlias: {
|
||||
|
||||
@ -139,6 +139,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
|
||||
vm.widgetBackgroundColor = widgetBackgroundColor;
|
||||
vm.widgetPadding = widgetPadding;
|
||||
vm.showWidgetTitle = showWidgetTitle;
|
||||
vm.dropWidgetShadow = dropWidgetShadow;
|
||||
vm.hasTimewindow = hasTimewindow;
|
||||
vm.editWidget = editWidget;
|
||||
vm.exportWidget = exportWidget;
|
||||
@ -521,6 +522,14 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
|
||||
}
|
||||
}
|
||||
|
||||
function dropWidgetShadow(widget) {
|
||||
if (angular.isDefined(widget.config.dropShadow)) {
|
||||
return widget.config.dropShadow;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function hasTimewindow(widget) {
|
||||
return widget.type === types.widgetType.timeseries.value;
|
||||
}
|
||||
|
||||
@ -28,8 +28,10 @@
|
||||
<li gridster-item="widget" ng-repeat="widget in vm.widgets">
|
||||
<md-menu md-position-mode="target target" tb-mousepoint-menu>
|
||||
<div tb-expand-fullscreen
|
||||
expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp"
|
||||
ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}"
|
||||
expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget"
|
||||
ng-class="{'tb-highlighted': vm.isHighlighted(widget),
|
||||
'tb-not-highlighted': vm.isNotHighlighted(widget),
|
||||
'md-whiteframe-4dp': vm.dropWidgetShadow(widget)}"
|
||||
tb-mousedown="vm.widgetMouseDown($event, widget)"
|
||||
tb-mousemove="vm.widgetMouseMove($event, widget)"
|
||||
tb-mouseup="vm.widgetMouseUp($event, widget)"
|
||||
|
||||
41
ui/src/app/components/react/json-form-css.jsx
Normal file
41
ui/src/app/components/react/json-form-css.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import ThingsboardAceEditor from './json-form-ace-editor.jsx';
|
||||
import 'brace/mode/css';
|
||||
import beautify from 'js-beautify';
|
||||
|
||||
const css_beautify = beautify.css;
|
||||
|
||||
class ThingsboardCss extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onTidyCss = this.onTidyCss.bind(this);
|
||||
}
|
||||
|
||||
onTidyCss(css) {
|
||||
return css_beautify(css, {indent_size: 4});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ThingsboardAceEditor {...this.props} mode='css' onTidy={this.onTidyCss} {...this.state}></ThingsboardAceEditor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThingsboardCss;
|
||||
41
ui/src/app/components/react/json-form-html.jsx
Normal file
41
ui/src/app/components/react/json-form-html.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import ThingsboardAceEditor from './json-form-ace-editor.jsx';
|
||||
import 'brace/mode/html';
|
||||
import beautify from 'js-beautify';
|
||||
|
||||
const html_beautify = beautify.html;
|
||||
|
||||
class ThingsboardHtml extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onTidyHtml = this.onTidyHtml.bind(this);
|
||||
}
|
||||
|
||||
onTidyHtml(html) {
|
||||
return html_beautify(html, {indent_size: 4});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ThingsboardAceEditor {...this.props} mode='html' onTidy={this.onTidyHtml} {...this.state}></ThingsboardAceEditor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThingsboardHtml;
|
||||
@ -19,6 +19,8 @@ import { utils } from 'react-schema-form';
|
||||
import ThingsboardArray from './json-form-array.jsx';
|
||||
import ThingsboardJavaScript from './json-form-javascript.jsx';
|
||||
import ThingsboardJson from './json-form-json.jsx';
|
||||
import ThingsboardHtml from './json-form-html.jsx';
|
||||
import ThingsboardCss from './json-form-css.jsx';
|
||||
import ThingsboardColor from './json-form-color.jsx'
|
||||
import ThingsboardRcSelect from './json-form-rc-select.jsx';
|
||||
import ThingsboardNumber from './json-form-number.jsx';
|
||||
@ -52,6 +54,8 @@ class ThingsboardSchemaForm extends React.Component {
|
||||
'array': ThingsboardArray,
|
||||
'javascript': ThingsboardJavaScript,
|
||||
'json': ThingsboardJson,
|
||||
'html': ThingsboardHtml,
|
||||
'css': ThingsboardCss,
|
||||
'color': ThingsboardColor,
|
||||
'rc-select': ThingsboardRcSelect,
|
||||
'fieldset': ThingsboardFieldSet
|
||||
|
||||
@ -74,11 +74,12 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
|
||||
scope.selectedTab = 0;
|
||||
scope.title = ngModelCtrl.$viewValue.title;
|
||||
scope.showTitle = ngModelCtrl.$viewValue.showTitle;
|
||||
scope.dropShadow = angular.isDefined(ngModelCtrl.$viewValue.dropShadow) ? ngModelCtrl.$viewValue.dropShadow : true;
|
||||
scope.backgroundColor = ngModelCtrl.$viewValue.backgroundColor;
|
||||
scope.color = ngModelCtrl.$viewValue.color;
|
||||
scope.padding = ngModelCtrl.$viewValue.padding;
|
||||
scope.timewindow = ngModelCtrl.$viewValue.timewindow;
|
||||
if (scope.widgetType !== types.widgetType.rpc.value) {
|
||||
if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value) {
|
||||
if (scope.datasources) {
|
||||
scope.datasources.splice(0, scope.datasources.length);
|
||||
} else {
|
||||
@ -89,7 +90,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
|
||||
scope.datasources.push({value: ngModelCtrl.$viewValue.datasources[i]});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (scope.widgetType === types.widgetType.rpc.value) {
|
||||
if (ngModelCtrl.$viewValue.targetDeviceAliasIds && ngModelCtrl.$viewValue.targetDeviceAliasIds.length > 0) {
|
||||
var aliasId = ngModelCtrl.$viewValue.targetDeviceAliasIds[0];
|
||||
if (scope.deviceAliases[aliasId]) {
|
||||
@ -140,18 +141,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
|
||||
if (scope.widgetType === types.widgetType.rpc.value) {
|
||||
valid = value && value.targetDeviceAliasIds && value.targetDeviceAliasIds.length > 0;
|
||||
ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
|
||||
} else {
|
||||
} else if (scope.widgetType !== types.widgetType.static.value) {
|
||||
valid = value && value.datasources && value.datasources.length > 0;
|
||||
ngModelCtrl.$setValidity('datasources', valid);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch('title + showTitle + backgroundColor + color + padding + intervalSec', function () {
|
||||
scope.$watch('title + showTitle + dropShadow + backgroundColor + color + padding + intervalSec', function () {
|
||||
if (ngModelCtrl.$viewValue) {
|
||||
var value = ngModelCtrl.$viewValue;
|
||||
value.title = scope.title;
|
||||
value.showTitle = scope.showTitle;
|
||||
value.dropShadow = scope.dropShadow;
|
||||
value.backgroundColor = scope.backgroundColor;
|
||||
value.color = scope.color;
|
||||
value.padding = scope.padding;
|
||||
@ -177,7 +179,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
|
||||
}, true);
|
||||
|
||||
scope.$watch('datasources', function () {
|
||||
if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value) {
|
||||
if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value
|
||||
&& scope.widgetType !== types.widgetType.static.value) {
|
||||
var value = ngModelCtrl.$viewValue;
|
||||
if (value.datasources) {
|
||||
value.datasources.splice(0, value.datasources.length);
|
||||
@ -235,7 +238,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
|
||||
};
|
||||
|
||||
scope.updateDatasourcesAccordionState = function () {
|
||||
if (scope.widgetType !== types.widgetType.rpc.value) {
|
||||
if (scope.widgetType !== types.widgetType.rpc.value &&
|
||||
scope.widgetType !== types.widgetType.static.value) {
|
||||
if (scope.datasourcesAccordion) {
|
||||
scope.updateDatasourcesAccordionStatePending = false;
|
||||
var expand = scope.datasources && scope.datasources.length < 4;
|
||||
|
||||
@ -31,6 +31,11 @@
|
||||
ng-model="showTitle">{{ 'widget-config.display-title' | translate }}
|
||||
</md-checkbox>
|
||||
</div>
|
||||
<div layout="row" layout-padding>
|
||||
<md-checkbox flex aria-label="{{ 'widget-config.drop-shadow' | translate }}"
|
||||
ng-model="dropShadow">{{ 'widget-config.drop-shadow' | translate }}
|
||||
</md-checkbox>
|
||||
</div>
|
||||
<div flex
|
||||
md-color-picker
|
||||
ng-model="backgroundColor"
|
||||
@ -64,7 +69,7 @@
|
||||
<tb-timewindow as-button="true" flex ng-model="timewindow"></tb-timewindow>
|
||||
</div>
|
||||
<v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
|
||||
ng-show="widgetType !== types.widgetType.rpc.value">
|
||||
ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value">
|
||||
<v-pane id="datasources-pane" expanded="forceExpandDatasources">
|
||||
<v-pane-header>
|
||||
{{ 'widget-config.datasources' | translate }}
|
||||
|
||||
@ -66,6 +66,10 @@ export default function AddWidgetController($scope, widgetService, deviceService
|
||||
link = 'widgetsConfigRpc';
|
||||
break;
|
||||
}
|
||||
case types.widgetType.static.value: {
|
||||
link = 'widgetsConfigStatic';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return link;
|
||||
|
||||
@ -41,6 +41,7 @@ export default function DashboardController(types, widgetService, userService,
|
||||
vm.latestWidgetTypes = [];
|
||||
vm.timeseriesWidgetTypes = [];
|
||||
vm.rpcWidgetTypes = [];
|
||||
vm.staticWidgetTypes = [];
|
||||
vm.widgetEditMode = $state.$current.data.widgetEditMode;
|
||||
vm.widgets = [];
|
||||
|
||||
@ -82,6 +83,7 @@ export default function DashboardController(types, widgetService, userService,
|
||||
vm.latestWidgetTypes = [];
|
||||
vm.timeseriesWidgetTypes = [];
|
||||
vm.rpcWidgetTypes = [];
|
||||
vm.staticWidgetTypes = [];
|
||||
if (vm.widgetsBundle) {
|
||||
var bundleAlias = vm.widgetsBundle.alias;
|
||||
var isSystem = vm.widgetsBundle.tenantId.id === types.id.nullUid;
|
||||
@ -127,6 +129,8 @@ export default function DashboardController(types, widgetService, userService,
|
||||
vm.latestWidgetTypes.push(widget);
|
||||
} else if (widgetTypeInfo.type === types.widgetType.rpc.value) {
|
||||
vm.rpcWidgetTypes.push(widget);
|
||||
} else if (widgetTypeInfo.type === types.widgetType.static.value) {
|
||||
vm.staticWidgetTypes.push(widget);
|
||||
}
|
||||
top += sizeY;
|
||||
loadNextOrComplete(i);
|
||||
@ -442,6 +446,10 @@ export default function DashboardController(types, widgetService, userService,
|
||||
link = 'widgetsConfigRpc';
|
||||
break;
|
||||
}
|
||||
case types.widgetType.static.value: {
|
||||
link = 'widgetsConfigStatic';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return link;
|
||||
@ -490,6 +498,7 @@ export default function DashboardController(types, widgetService, userService,
|
||||
vm.timeseriesWidgetTypes = [];
|
||||
vm.latestWidgetTypes = [];
|
||||
vm.rpcWidgetTypes = [];
|
||||
vm.staticWidgetTypes = [];
|
||||
}
|
||||
|
||||
function addWidgetFromType(event, widget) {
|
||||
|
||||
@ -136,7 +136,7 @@
|
||||
</header-pane>
|
||||
<div>
|
||||
<md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
|
||||
vm.rpcWidgetTypes.length > 0"
|
||||
vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
|
||||
flex
|
||||
class="tb-absolute-fill" md-border-bottom>
|
||||
<md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
|
||||
@ -169,9 +169,19 @@
|
||||
on-widget-clicked="vm.addWidgetFromType(event, widget)">
|
||||
</tb-dashboard>
|
||||
</md-tab>
|
||||
<md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
|
||||
<tb-dashboard
|
||||
widgets="vm.staticWidgetTypes"
|
||||
is-edit="false"
|
||||
is-mobile="true"
|
||||
is-edit-action-enabled="false"
|
||||
is-remove-action-enabled="false"
|
||||
on-widget-clicked="vm.addWidgetFromType(event, widget)">
|
||||
</tb-dashboard>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
<span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
|
||||
vm.rpcWidgetTypes.length === 0 && vm.widgetsBundle"
|
||||
vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
|
||||
layout-align="center center"
|
||||
style="text-transform: uppercase; display: flex;"
|
||||
class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
|
||||
|
||||
@ -305,31 +305,33 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
|
||||
for (var i = 0; i < widgetTypes.length; i++) {
|
||||
var widgetType = widgetTypes[i];
|
||||
var widgetInfo = widgetService.toWidgetInfo(widgetType);
|
||||
var sizeX = widgetInfo.sizeX*2;
|
||||
var sizeY = widgetInfo.sizeY*2;
|
||||
var col = Math.floor(Math.max(0, (20 - sizeX)/2));
|
||||
var widget = {
|
||||
isSystemType: isSystem,
|
||||
bundleAlias: bundleAlias,
|
||||
typeAlias: widgetInfo.alias,
|
||||
type: widgetInfo.type,
|
||||
title: widgetInfo.widgetName,
|
||||
sizeX: sizeX,
|
||||
sizeY: sizeY,
|
||||
row: 0,
|
||||
col: col,
|
||||
config: angular.fromJson(widgetInfo.defaultConfig)
|
||||
};
|
||||
if (widgetInfo.type !== types.widgetType.static.value) {
|
||||
var sizeX = widgetInfo.sizeX * 2;
|
||||
var sizeY = widgetInfo.sizeY * 2;
|
||||
var col = Math.floor(Math.max(0, (20 - sizeX) / 2));
|
||||
var widget = {
|
||||
isSystemType: isSystem,
|
||||
bundleAlias: bundleAlias,
|
||||
typeAlias: widgetInfo.alias,
|
||||
type: widgetInfo.type,
|
||||
title: widgetInfo.widgetName,
|
||||
sizeX: sizeX,
|
||||
sizeY: sizeY,
|
||||
row: 0,
|
||||
col: col,
|
||||
config: angular.fromJson(widgetInfo.defaultConfig)
|
||||
};
|
||||
|
||||
widget.config.title = widgetInfo.widgetName;
|
||||
widget.config.datasources = [datasource];
|
||||
var length;
|
||||
if (scope.attributeScope === types.latestTelemetry && widgetInfo.type !== types.widgetType.rpc.value) {
|
||||
length = scope.widgetsListCache.push([widget]);
|
||||
scope.widgetsList.push(length === 1 ? [widget] : []);
|
||||
} else if (widgetInfo.type === types.widgetType.latest.value) {
|
||||
length = scope.widgetsListCache.push([widget]);
|
||||
scope.widgetsList.push(length === 1 ? [widget] : []);
|
||||
widget.config.title = widgetInfo.widgetName;
|
||||
widget.config.datasources = [datasource];
|
||||
var length;
|
||||
if (scope.attributeScope === types.latestTelemetry && widgetInfo.type !== types.widgetType.rpc.value) {
|
||||
length = scope.widgetsListCache.push([widget]);
|
||||
scope.widgetsList.push(length === 1 ? [widget] : []);
|
||||
} else if (widgetInfo.type === types.widgetType.latest.value) {
|
||||
length = scope.widgetsListCache.push([widget]);
|
||||
scope.widgetsList.push(length === 1 ? [widget] : []);
|
||||
}
|
||||
}
|
||||
}
|
||||
scope.widgetsLoaded = true;
|
||||
|
||||
@ -45,7 +45,7 @@ var pluginActionsClazzHelpLinkMap = {
|
||||
'org.thingsboard.server.extensions.rest.action.RestApiCallPluginAction': 'pluginActionRestApiCall'
|
||||
};
|
||||
|
||||
var helpBaseUrl = "http://thingsboard.io";
|
||||
var helpBaseUrl = "https://thingsboard.io";
|
||||
|
||||
export default angular.module('thingsboard.help', [])
|
||||
.constant('helpLinks',
|
||||
@ -86,6 +86,7 @@ export default angular.module('thingsboard.help', [])
|
||||
widgetsConfigTimeseries: helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries",
|
||||
widgetsConfigLatest: helpBaseUrl + "/docs/user-guide/ui/dashboards#latest",
|
||||
widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc",
|
||||
widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static",
|
||||
},
|
||||
getPluginLink: function(plugin) {
|
||||
var link = 'plugins';
|
||||
|
||||
@ -53,6 +53,13 @@
|
||||
</md-icon>
|
||||
<span translate>{{vm.types.widgetType.rpc.name}}</span>
|
||||
</md-button>
|
||||
<md-button class="tb-card-button md-raised md-primary" layout="column"
|
||||
ng-click="vm.typeSelected(vm.types.widgetType.static.value)">
|
||||
<md-icon class="material-icons tb-md-96"
|
||||
aria-label="{{ vm.types.widgetType.static.name | translate }}">font_download
|
||||
</md-icon>
|
||||
<span translate>{{vm.types.widgetType.static.name}}</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
@ -603,6 +603,7 @@
|
||||
"timeseries": "Time series",
|
||||
"latest-values": "Latest values",
|
||||
"rpc": "Control widget",
|
||||
"static": "Static widget",
|
||||
"select-widget-type": "Select widget type",
|
||||
"missing-widget-title-error": "Widget title must be specified!",
|
||||
"widget-saved": "Widget saved",
|
||||
@ -663,6 +664,7 @@
|
||||
"title": "Title",
|
||||
"general-settings": "General settings",
|
||||
"display-title": "Display title",
|
||||
"drop-shadow": "Drop shadow",
|
||||
"background-color": "Background color",
|
||||
"text-color": "Text color",
|
||||
"padding": "Padding",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user