UI: New widget type 'HTML Card'

This commit is contained in:
Igor Kulikov 2017-02-06 11:55:43 +02:00
parent c860753bc8
commit d18ca19b3b
17 changed files with 191 additions and 36 deletions

File diff suppressed because one or more lines are too long

View File

@ -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;

View File

@ -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: {

View File

@ -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;
}

View File

@ -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)"

View 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;

View 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;

View File

@ -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

View File

@ -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;

View File

@ -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 }}

View File

@ -66,6 +66,10 @@ export default function AddWidgetController($scope, widgetService, deviceService
link = 'widgetsConfigRpc';
break;
}
case types.widgetType.static.value: {
link = 'widgetsConfigStatic';
break;
}
}
}
return link;

View File

@ -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) {

View File

@ -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>

View File

@ -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;

View File

@ -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';

View File

@ -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>

View File

@ -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",